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.
This commit is contained in:
Ryan Mills 2015-04-12 00:53:48 +00:00
parent dd15b47bdf
commit 275af975e8
9 changed files with 258 additions and 3 deletions

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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!"))

View File

@ -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) {

View File

@ -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")

View File

@ -39,6 +39,8 @@ type CheckType struct {
Timeout time.Duration
TTL time.Duration
Status string
Notes string
}
type CheckTypes []*CheckType

View File

@ -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
}

View File

@ -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"