Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New command: consul debug #4754

Merged
merged 30 commits into from
Oct 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f174357
vendor: add gopsutil/disk
pearkes Oct 5, 2018
012174d
agent/debug: add package for debugging, host info
pearkes Oct 2, 2018
880faee
api: add v1/agent/host endpoint
pearkes Oct 2, 2018
044963d
agent: add v1/agent/host endpoint
pearkes Oct 2, 2018
e7ab5a2
agent/debug: add basic test for host metrics
pearkes Oct 4, 2018
ce4e938
api: add debug/pprof endpoints
pearkes Oct 4, 2018
eb40381
command/debug: implementation of static capture
pearkes Oct 2, 2018
d0eb2b8
command/debug: tests and only configured targets
pearkes Oct 3, 2018
7ab094c
command/debug: add methods for dynamic data capture
pearkes Oct 4, 2018
8557359
command/debug: add pprof
pearkes Oct 4, 2018
4b54c9e
command/debug: timing, wg, logs to disk
pearkes Oct 4, 2018
67b0739
command/debug: add a usage section
pearkes Oct 5, 2018
1109be7
website: add docs for consul debug
pearkes Oct 9, 2018
dac640e
agent/host: require operator:read
pearkes Oct 9, 2018
1078edc
api/host: improve docs and no retry timing
pearkes Oct 9, 2018
686c498
command/debug: fail on extra arguments
pearkes Oct 9, 2018
a85da3c
command/debug: fixup file permissions to 0644
pearkes Oct 9, 2018
804b80a
command/debug: remove server flags
pearkes Oct 9, 2018
4b70e5d
command/debug: improve clarity of usage section
pearkes Oct 9, 2018
b94c1e0
api/debug: add Trace for profiling, fix profile
pearkes Oct 9, 2018
dbd1b9f
command/debug: capture profile and trace at the same time
pearkes Oct 9, 2018
f1fe0a6
command/debug: add index document
pearkes Oct 10, 2018
cc500c6
command/debug: use "clusters" in place of members
pearkes Oct 11, 2018
9e342c6
command/debug: remove address in output
pearkes Oct 11, 2018
707c032
command/debug: improve comment on metrics sleep
pearkes Oct 11, 2018
32771a4
command/debug: clarify usage
pearkes Oct 11, 2018
52d467b
agent: always register pprof handlers and protect
pearkes Oct 16, 2018
3bd84d3
command/debug: use trace.out instead of .prof
pearkes Oct 16, 2018
33926f9
agent: fix comment wording
banks Oct 17, 2018
a113697
agent: wrap table driven tests in t.run()
pearkes Oct 17, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions agent/agent_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/hashicorp/consul/agent/cache-types"
"github.com/hashicorp/consul/agent/checks"
"github.com/hashicorp/consul/agent/config"
"github.com/hashicorp/consul/agent/debug"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/ipaddr"
Expand Down Expand Up @@ -1463,3 +1464,27 @@ type connectAuthorizeResp struct {
Authorized bool // True if authorized, false if not
Reason string // Reason for the Authorized value (whether true or false)
}

// AgentHost
//
// GET /v1/agent/host
//
// Retrieves information about resources available and in-use for the
// host the agent is running on such as CPU, memory, and disk usage. Requires
// a operator:read ACL token.
func (s *HTTPServer) AgentHost(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
// Fetch the ACL token, if any, and enforce agent policy.
var token string
s.parseToken(req, &token)
rule, err := s.agent.resolveToken(token)
if err != nil {
return nil, err
}
// TODO(pearkes): Is agent:read appropriate here? There could be relatively
// sensitive information made available in this API
if rule != nil && !rule.OperatorRead() {
return nil, acl.ErrPermissionDenied
}

return debug.CollectHostInfo(), nil
}
67 changes: 60 additions & 7 deletions agent/agent_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/hashicorp/consul/agent/checks"
"github.com/hashicorp/consul/agent/config"
"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/debug"
"github.com/hashicorp/consul/agent/local"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
Expand Down Expand Up @@ -1877,9 +1878,9 @@ func TestAgent_RegisterService_TranslateKeys(t *testing.T) {

json := `
{
"name":"test",
"port":8000,
"enable_tag_override": true,
"name":"test",
"port":8000,
"enable_tag_override": true,
"meta": {
"some": "meta",
"enable_tag_override": "meta is 'opaque' so should not get translated"
Expand Down Expand Up @@ -1929,9 +1930,9 @@ func TestAgent_RegisterService_TranslateKeys(t *testing.T) {
]
},
"sidecar_service": {
"name":"test-proxy",
"port":8001,
"enable_tag_override": true,
"name":"test-proxy",
"port":8001,
"enable_tag_override": true,
"meta": {
"some": "meta",
"enable_tag_override": "sidecar_service.meta is 'opaque' so should not get translated"
Expand Down Expand Up @@ -2791,7 +2792,7 @@ func TestAgent_RegisterServiceDeregisterService_Sidecar(t *testing.T) {
require := require.New(t)

// Constrain auto ports to 1 available to make it deterministic
hcl := `ports {
hcl := `ports {
sidecar_min_port = 2222
sidecar_max_port = 2222
}
Expand Down Expand Up @@ -5537,3 +5538,55 @@ func testAllowProxyConfig() string {
}
`
}

func TestAgent_Host(t *testing.T) {
t.Parallel()
assert := assert.New(t)

dc1 := "dc1"
a := NewTestAgent(t.Name(), `
acl_datacenter = "`+dc1+`"
acl_default_policy = "allow"
acl_master_token = "master"
acl_agent_token = "agent"
acl_agent_master_token = "towel"
acl_enforce_version_8 = true
`)
defer a.Shutdown()

testrpc.WaitForLeader(t, a.RPC, "dc1")
req, _ := http.NewRequest("GET", "/v1/agent/host?token=master", nil)
resp := httptest.NewRecorder()
respRaw, err := a.srv.AgentHost(resp, req)
assert.Nil(err)
assert.Equal(http.StatusOK, resp.Code)
assert.NotNil(respRaw)

obj := respRaw.(*debug.HostInfo)
assert.NotNil(obj.CollectionTime)
assert.Empty(obj.Errors)
}

func TestAgent_HostBadACL(t *testing.T) {
t.Parallel()
assert := assert.New(t)

dc1 := "dc1"
a := NewTestAgent(t.Name(), `
acl_datacenter = "`+dc1+`"
acl_default_policy = "deny"
acl_master_token = "root"
acl_agent_token = "agent"
acl_agent_master_token = "towel"
acl_enforce_version_8 = true
`)
defer a.Shutdown()

testrpc.WaitForLeader(t, a.RPC, "dc1")
req, _ := http.NewRequest("GET", "/v1/agent/host?token=agent", nil)
resp := httptest.NewRecorder()
respRaw, err := a.srv.AgentHost(resp, req)
assert.EqualError(err, "ACL not found")
assert.Equal(http.StatusOK, resp.Code)
assert.Nil(respRaw)
}
59 changes: 59 additions & 0 deletions agent/debug/host.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package debug

import (
"time"

"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/host"
"github.com/shirou/gopsutil/mem"
)

const (
// DiskUsagePath is the path to check usage of the disk.
// Must be a filessytem path such as "/", not device file path like "/dev/vda1"
DiskUsagePath = "/"
)

// HostInfo includes information about resources on the host as well as
// collection time and
type HostInfo struct {
Memory *mem.VirtualMemoryStat
CPU []cpu.InfoStat
Host *host.InfoStat
Disk *disk.UsageStat
CollectionTime int64
Errors []error
}

// CollectHostInfo queries the host system and returns HostInfo. Any
// errors encountered will be returned in HostInfo.Errors
func CollectHostInfo() *HostInfo {
info := &HostInfo{CollectionTime: time.Now().UTC().UnixNano()}

if h, err := host.Info(); err != nil {
info.Errors = append(info.Errors, err)
} else {
info.Host = h
}

if v, err := mem.VirtualMemory(); err != nil {
info.Errors = append(info.Errors, err)
} else {
info.Memory = v
}

if d, err := disk.Usage(DiskUsagePath); err != nil {
info.Errors = append(info.Errors, err)
} else {
info.Disk = d
}

if c, err := cpu.Info(); err != nil {
info.Errors = append(info.Errors, err)
} else {
info.CPU = c
}

return info
}
20 changes: 20 additions & 0 deletions agent/debug/host_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package debug

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestCollectHostInfo(t *testing.T) {
assert := assert.New(t)

host := CollectHostInfo()

assert.Nil(host.Errors)

assert.NotNil(host.CollectionTime)
assert.NotNil(host.Host)
assert.NotNil(host.Disk)
assert.NotNil(host.Memory)
}
48 changes: 42 additions & 6 deletions agent/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,41 @@ func (s *HTTPServer) handler(enableDebug bool) http.Handler {
mux.Handle(pattern, gzipHandler)
}

// handlePProf takes the given pattern and pprof handler
// and wraps it to add authorization and metrics
handlePProf := func(pattern string, handler http.HandlerFunc) {
wrapper := func(resp http.ResponseWriter, req *http.Request) {
var token string
s.parseToken(req, &token)

rule, err := s.agent.resolveToken(token)
if err != nil {
resp.WriteHeader(http.StatusForbidden)
return
}

// If enableDebug is not set, and ACLs are disabled, write
// an unauthorized response
if !enableDebug {
if s.checkACLDisabled(resp, req) {
return
}
}

// If the token provided does not have the necessary permissions,
// write a forbidden response
if rule != nil && !rule.OperatorRead() {
resp.WriteHeader(http.StatusForbidden)
return
}

// Call the pprof handler
handler(resp, req)
}

handleFuncMetrics(pattern, http.HandlerFunc(wrapper))
}

mux.HandleFunc("/", s.Index)
for pattern, fn := range endpoints {
thisFn := fn
Expand All @@ -151,12 +186,13 @@ func (s *HTTPServer) handler(enableDebug bool) http.Handler {
}
handleFuncMetrics(pattern, s.wrap(bound, methods))
}
if enableDebug {
handleFuncMetrics("/debug/pprof/", pprof.Index)
handleFuncMetrics("/debug/pprof/cmdline", pprof.Cmdline)
handleFuncMetrics("/debug/pprof/profile", pprof.Profile)
handleFuncMetrics("/debug/pprof/symbol", pprof.Symbol)
}

// Register wrapped pprof handlers
handlePProf("/debug/pprof/", pprof.Index)
handlePProf("/debug/pprof/cmdline", pprof.Cmdline)
handlePProf("/debug/pprof/profile", pprof.Profile)
handlePProf("/debug/pprof/symbol", pprof.Symbol)
handlePProf("/debug/pprof/trace", pprof.Trace)

if s.IsUIEnabled() {
legacy_ui, err := strconv.ParseBool(os.Getenv("CONSUL_UI_LEGACY"))
Expand Down
1 change: 1 addition & 0 deletions agent/http_oss.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ func init() {
registerEndpoint("/v1/acl/replication", []string{"GET"}, (*HTTPServer).ACLReplicationStatus)
registerEndpoint("/v1/agent/token/", []string{"PUT"}, (*HTTPServer).AgentToken)
registerEndpoint("/v1/agent/self", []string{"GET"}, (*HTTPServer).AgentSelf)
registerEndpoint("/v1/agent/host", []string{"GET"}, (*HTTPServer).AgentHost)
registerEndpoint("/v1/agent/maintenance", []string{"PUT"}, (*HTTPServer).AgentNodeMaintenance)
registerEndpoint("/v1/agent/reload", []string{"PUT"}, (*HTTPServer).AgentReload)
registerEndpoint("/v1/agent/monitor", []string{"GET"}, (*HTTPServer).AgentMonitor)
Expand Down
Loading