From 275af975e823836af3ad91863af7aaee355c2284 Mon Sep 17 00:00:00 2001 From: Ryan Mills Date: Sun, 12 Apr 2015 00:53:48 +0000 Subject: [PATCH 1/2] Allow specifying a status field in the agent/service/register and agent/check/register endpoints. This status must be one of the valid check statuses: 'passing', 'warning', 'critical', 'unknown'. If the status field is not present or the empty string, the default of 'critical' is used. --- api/agent.go | 1 + api/agent_test.go | 109 ++++++++++++++++++++++++++- command/agent/agent.go | 3 + command/agent/agent_endpoint.go | 11 +++ command/agent/agent_endpoint_test.go | 79 +++++++++++++++++++ command/agent/agent_test.go | 45 ++++++++++- command/agent/check.go | 2 + command/agent/structs.go | 4 + consul/structs/structs.go | 7 ++ 9 files changed, 258 insertions(+), 3 deletions(-) diff --git a/api/agent.go b/api/agent.go index 4b144fe181..e56a18dcd2 100644 --- a/api/agent.go +++ b/api/agent.go @@ -68,6 +68,7 @@ type AgentServiceCheck struct { Timeout string `json:",omitempty"` TTL string `json:",omitempty"` HTTP string `json:",omitempty"` + Status string `json:",omitempty"` } type AgentServiceChecks []*AgentServiceCheck diff --git a/api/agent_test.go b/api/agent_test.go index 60cc4ae1e6..2dc25d8c0d 100644 --- a/api/agent_test.go +++ b/api/agent_test.go @@ -56,6 +56,50 @@ func TestAgent_Services(t *testing.T) { t.Fatalf("err: %v", err) } + services, err := agent.Services() + if err != nil { + t.Fatalf("err: %v", err) + } + if _, ok := services["foo"]; !ok { + t.Fatalf("missing service: %v", services) + } + checks, err := agent.Checks() + if err != nil { + t.Fatalf("err: %v", err) + } + chk, ok := checks["service:foo"] + if !ok { + t.Fatalf("missing check: %v", checks) + } + + // Checks should default to critical + if chk.Status != "critical" { + t.Fatalf("Bad: %#v", chk) + } + + if err := agent.ServiceDeregister("foo"); err != nil { + t.Fatalf("err: %v", err) + } +} + +func TestAgent_Services_CheckPassing(t *testing.T) { + c, s := makeClient(t) + defer s.stop() + + agent := c.Agent() + reg := &AgentServiceRegistration{ + Name: "foo", + Tags: []string{"bar", "baz"}, + Port: 8000, + Check: &AgentServiceCheck{ + TTL: "15s", + Status: "passing", + }, + } + if err := agent.ServiceRegister(reg); err != nil { + t.Fatalf("err: %v", err) + } + services, err := agent.Services() if err != nil { t.Fatalf("err: %v", err) @@ -68,15 +112,38 @@ func TestAgent_Services(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } - if _, ok := checks["service:foo"]; !ok { + chk, ok := checks["service:foo"] + if !ok { t.Fatalf("missing check: %v", checks) } + if chk.Status != "passing" { + t.Fatalf("Bad: %#v", chk) + } if err := agent.ServiceDeregister("foo"); err != nil { t.Fatalf("err: %v", err) } } +func TestAgent_Services_CheckBadStatus(t *testing.T) { + c, s := makeClient(t) + defer s.stop() + + agent := c.Agent() + reg := &AgentServiceRegistration{ + Name: "foo", + Tags: []string{"bar", "baz"}, + Port: 8000, + Check: &AgentServiceCheck{ + TTL: "15s", + Status: "fluffy", + }, + } + if err := agent.ServiceRegister(reg); err == nil { + t.Fatalf("bad status accepted") + } +} + func TestAgent_ServiceAddress(t *testing.T) { c, s := makeClient(t) defer s.stop() @@ -224,9 +291,47 @@ func TestAgent_Checks(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } - if _, ok := checks["foo"]; !ok { + chk, ok := checks["foo"] + if !ok { t.Fatalf("missing check: %v", checks) } + if chk.Status != "critical" { + t.Fatalf("check not critical: %v", chk) + } + + if err := agent.CheckDeregister("foo"); err != nil { + t.Fatalf("err: %v", err) + } +} + +func TestAgent_CheckStartPassing(t *testing.T) { + c, s := makeClient(t) + defer s.stop() + + agent := c.Agent() + + reg := &AgentCheckRegistration{ + Name: "foo", + AgentServiceCheck: AgentServiceCheck{ + Status: "passing", + }, + } + reg.TTL = "15s" + if err := agent.CheckRegister(reg); err != nil { + t.Fatalf("err: %v", err) + } + + checks, err := agent.Checks() + if err != nil { + t.Fatalf("err: %v", err) + } + chk, ok := checks["foo"] + if !ok { + t.Fatalf("missing check: %v", checks) + } + if chk.Status != "passing" { + t.Fatalf("check not passing: %v", chk) + } if err := agent.CheckDeregister("foo"); err != nil { t.Fatalf("err: %v", err) diff --git a/command/agent/agent.go b/command/agent/agent.go index c741240ac7..d256dd9931 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -645,6 +645,9 @@ func (a *Agent) AddService(service *structs.NodeService, chkTypes CheckTypes, pe ServiceID: service.ID, ServiceName: service.Service, } + if chkType.Status != "" { + check.Status = chkType.Status + } if err := a.AddCheck(check, chkType, persist); err != nil { return err } diff --git a/command/agent/agent_endpoint.go b/command/agent/agent_endpoint.go index 78299c78a6..f7d9d92e5f 100644 --- a/command/agent/agent_endpoint.go +++ b/command/agent/agent_endpoint.go @@ -86,6 +86,12 @@ func (s *HTTPServer) AgentRegisterCheck(resp http.ResponseWriter, req *http.Requ return nil, nil } + if args.Status != "" && !structs.ValidStatus(args.Status) { + resp.WriteHeader(400) + resp.Write([]byte("Bad check status")) + return nil, nil + } + // Construct the health check health := args.HealthCheck(s.agent.config.NodeName) @@ -192,6 +198,11 @@ func (s *HTTPServer) AgentRegisterService(resp http.ResponseWriter, req *http.Re // Verify the check type chkTypes := args.CheckTypes() for _, check := range chkTypes { + if check.Status != "" && !structs.ValidStatus(check.Status) { + resp.WriteHeader(400) + resp.Write([]byte("Status for checks must 'passing', 'warning', 'critical', 'unknown'")) + return nil, nil + } if !check.Valid() { resp.WriteHeader(400) resp.Write([]byte("Must provide TTL or Script and Interval!")) diff --git a/command/agent/agent_endpoint_test.go b/command/agent/agent_endpoint_test.go index 0387f38901..414f33d842 100644 --- a/command/agent/agent_endpoint_test.go +++ b/command/agent/agent_endpoint_test.go @@ -280,6 +280,85 @@ func TestHTTPAgentRegisterCheck(t *testing.T) { if _, ok := srv.agent.checkTTLs["test"]; !ok { t.Fatalf("missing test check ttl") } + + // By default, checks start in critical state. + state := srv.agent.state.Checks()["test"] + if state.Status != structs.HealthCritical { + t.Fatalf("bad: %v", state) + } +} + +func TestHTTPAgentRegisterCheckPassing(t *testing.T) { + dir, srv := makeHTTPServer(t) + defer os.RemoveAll(dir) + defer srv.Shutdown() + defer srv.agent.Shutdown() + + // Register node + req, err := http.NewRequest("GET", "/v1/agent/check/register", nil) + if err != nil { + t.Fatalf("err: %v", err) + } + args := &CheckDefinition{ + Name: "test", + CheckType: CheckType{ + TTL: 15 * time.Second, + }, + Status: structs.HealthPassing, + } + req.Body = encodeReq(args) + + obj, err := srv.AgentRegisterCheck(nil, req) + if err != nil { + t.Fatalf("err: %v", err) + } + if obj != nil { + t.Fatalf("bad: %v", obj) + } + + // Ensure we have a check mapping + if _, ok := srv.agent.state.Checks()["test"]; !ok { + t.Fatalf("missing test check") + } + + if _, ok := srv.agent.checkTTLs["test"]; !ok { + t.Fatalf("missing test check ttl") + } + + state := srv.agent.state.Checks()["test"] + if state.Status != structs.HealthPassing { + t.Fatalf("bad: %v", state) + } +} + +func TestHTTPAgentRegisterCheckBadStatus(t *testing.T) { + dir, srv := makeHTTPServer(t) + defer os.RemoveAll(dir) + defer srv.Shutdown() + defer srv.agent.Shutdown() + + // Register node + req, err := http.NewRequest("GET", "/v1/agent/check/register", nil) + if err != nil { + t.Fatalf("err: %v", err) + } + args := &CheckDefinition{ + Name: "test", + CheckType: CheckType{ + TTL: 15 * time.Second, + }, + Status: "fluffy", + } + req.Body = encodeReq(args) + + resp := httptest.NewRecorder() + if _, err := srv.AgentRegisterCheck(resp, req); err != nil { + t.Fatalf("err: %v", err) + } + if resp.Code != 400 { + t.Fatalf("accepted bad status") + } + } func TestHTTPAgentDeregisterCheck(t *testing.T) { diff --git a/command/agent/agent_test.go b/command/agent/agent_test.go index 33db4f6e2b..3861f9f8cb 100644 --- a/command/agent/agent_test.go +++ b/command/agent/agent_test.go @@ -337,10 +337,53 @@ func TestAgent_AddCheck(t *testing.T) { } // Ensure we have a check mapping - if _, ok := agent.state.Checks()["mem"]; !ok { + sChk, ok := agent.state.Checks()["mem"] + if !ok { t.Fatalf("missing mem check") } + // Ensure our check is in the right state + if sChk.Status != structs.HealthCritical { + t.Fatalf("check not critical") + } + + // Ensure a TTL is setup + if _, ok := agent.checkMonitors["mem"]; !ok { + t.Fatalf("missing mem monitor") + } +} + +func TestAgent_AddCheck_StartPassing(t *testing.T) { + dir, agent := makeAgent(t, nextConfig()) + defer os.RemoveAll(dir) + defer agent.Shutdown() + + health := &structs.HealthCheck{ + Node: "foo", + CheckID: "mem", + Name: "memory util", + Status: structs.HealthPassing, + } + chk := &CheckType{ + Script: "exit 0", + Interval: 15 * time.Second, + } + err := agent.AddCheck(health, chk, false) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Ensure we have a check mapping + sChk, ok := agent.state.Checks()["mem"] + if !ok { + t.Fatalf("missing mem check") + } + + // Ensure our check is in the right state + if sChk.Status != structs.HealthPassing { + t.Fatalf("check not passing") + } + // Ensure a TTL is setup if _, ok := agent.checkMonitors["mem"]; !ok { t.Fatalf("missing mem monitor") diff --git a/command/agent/check.go b/command/agent/check.go index bae76ca5d2..e2ae9f0980 100644 --- a/command/agent/check.go +++ b/command/agent/check.go @@ -39,6 +39,8 @@ type CheckType struct { Timeout time.Duration TTL time.Duration + Status string + Notes string } type CheckTypes []*CheckType diff --git a/command/agent/structs.go b/command/agent/structs.go index 84f606e46c..6c32bdb042 100644 --- a/command/agent/structs.go +++ b/command/agent/structs.go @@ -45,6 +45,7 @@ type CheckDefinition struct { Name string Notes string ServiceID string + Status string CheckType `mapstructure:",squash"` } @@ -57,6 +58,9 @@ func (c *CheckDefinition) HealthCheck(node string) *structs.HealthCheck { Notes: c.Notes, ServiceID: c.ServiceID, } + if c.Status != "" { + health.Status = c.Status + } if health.CheckID == "" && health.Name != "" { health.CheckID = health.Name } diff --git a/consul/structs/structs.go b/consul/structs/structs.go index b95711bc44..bf4f859d21 100644 --- a/consul/structs/structs.go +++ b/consul/structs/structs.go @@ -36,6 +36,13 @@ const ( HealthCritical = "critical" ) +func ValidStatus(s string) bool { + return s == HealthUnknown || + s == HealthPassing || + s == HealthWarning || + s == HealthCritical +} + const ( // Client tokens have rules applied ACLTypeClient = "client" From c92d45dd9b2ed8495bb7a74a4c6c2294d6df689c Mon Sep 17 00:00:00 2001 From: Ryan Mills Date: Mon, 13 Apr 2015 20:46:01 +0000 Subject: [PATCH 2/2] Remove 'unknown' as one of the valid states when setting the initial state of a check. --- consul/structs/structs.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/consul/structs/structs.go b/consul/structs/structs.go index bf4f859d21..6472e5d742 100644 --- a/consul/structs/structs.go +++ b/consul/structs/structs.go @@ -37,8 +37,7 @@ const ( ) func ValidStatus(s string) bool { - return s == HealthUnknown || - s == HealthPassing || + return s == HealthPassing || s == HealthWarning || s == HealthCritical }