Store check type in catalog (#6561)

This commit is contained in:
Freddy 2019-10-17 20:33:11 +02:00 committed by GitHub
parent db9cc99497
commit caf658d0d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 239 additions and 4 deletions

View File

@ -2269,6 +2269,7 @@ func (a *Agent) addServiceInternal(req *addServiceRequest) error {
ServiceID: service.ID, ServiceID: service.ID,
ServiceName: service.Service, ServiceName: service.Service,
ServiceTags: service.Tags, ServiceTags: service.Tags,
Type: chkType.Type(),
} }
if chkType.Status != "" { if chkType.Status != "" {
check.Status = chkType.Status check.Status = chkType.Status
@ -3617,6 +3618,7 @@ func (a *Agent) EnableServiceMaintenance(serviceID, reason, token string) error
ServiceID: service.ID, ServiceID: service.ID,
ServiceName: service.Service, ServiceName: service.Service,
Status: api.HealthCritical, Status: api.HealthCritical,
Type: "maintenance",
} }
a.AddCheck(check, nil, true, token, ConfigSourceLocal) a.AddCheck(check, nil, true, token, ConfigSourceLocal)
a.logger.Printf("[INFO] agent: Service %q entered maintenance mode", serviceID) a.logger.Printf("[INFO] agent: Service %q entered maintenance mode", serviceID)
@ -3663,6 +3665,7 @@ func (a *Agent) EnableNodeMaintenance(reason, token string) {
Name: "Node Maintenance Mode", Name: "Node Maintenance Mode",
Notes: reason, Notes: reason,
Status: api.HealthCritical, Status: api.HealthCritical,
Type: "maintenance",
} }
a.AddCheck(check, nil, true, token, ConfigSourceLocal) a.AddCheck(check, nil, true, token, ConfigSourceLocal)
a.logger.Printf("[INFO] agent: Node entered maintenance mode") a.logger.Printf("[INFO] agent: Node entered maintenance mode")

View File

@ -492,6 +492,9 @@ func (s *HTTPServer) AgentRegisterCheck(resp http.ResponseWriter, req *http.Requ
return nil, nil return nil, nil
} }
// Store the type of check based on the definition
health.Type = chkType.Type()
if health.ServiceID != "" { if health.ServiceID != "" {
// fixup the service name so that vetCheckRegister requires the right ACLs // fixup the service name so that vetCheckRegister requires the right ACLs
service := s.agent.State.Service(health.ServiceID) service := s.agent.State.Service(health.ServiceID)

View File

@ -2478,6 +2478,11 @@ func testAgent_RegisterService(t *testing.T, extraHCL string) {
if len(checks) != 3 { if len(checks) != 3 {
t.Fatalf("bad: %v", checks) t.Fatalf("bad: %v", checks)
} }
for _, c := range checks {
if c.Type != "ttl" {
t.Fatalf("expected ttl check type, got %s", c.Type)
}
}
if len(a.checkTTLs) != 3 { if len(a.checkTTLs) != 3 {
t.Fatalf("missing test check ttls: %v", a.checkTTLs) t.Fatalf("missing test check ttls: %v", a.checkTTLs)
@ -4115,6 +4120,11 @@ func TestAgent_RegisterCheck_Service(t *testing.T) {
if result["memcache_check2"].ServiceID != "memcache" { if result["memcache_check2"].ServiceID != "memcache" {
t.Fatalf("bad: %#v", result["memcached_check2"]) t.Fatalf("bad: %#v", result["memcached_check2"])
} }
// Make sure the new check has the right type
if result["memcache_check2"].Type != "ttl" {
t.Fatalf("expected TTL type, got %s", result["memcache_check2"].Type)
}
} }
func TestAgent_Monitor(t *testing.T) { func TestAgent_Monitor(t *testing.T) {

View File

@ -393,6 +393,7 @@ func testAgent_AddService(t *testing.T, extraHCL string) {
ServiceID: "svcid1", ServiceID: "svcid1",
ServiceName: "svcname1", ServiceName: "svcname1",
ServiceTags: []string{"tag1"}, ServiceTags: []string{"tag1"},
Type: "ttl",
}, },
}, },
}, },
@ -438,6 +439,7 @@ func testAgent_AddService(t *testing.T, extraHCL string) {
ServiceID: "svcid2", ServiceID: "svcid2",
ServiceName: "svcname2", ServiceName: "svcname2",
ServiceTags: []string{"tag2"}, ServiceTags: []string{"tag2"},
Type: "ttl",
}, },
"check-noname": &structs.HealthCheck{ "check-noname": &structs.HealthCheck{
Node: "node1", Node: "node1",
@ -447,6 +449,7 @@ func testAgent_AddService(t *testing.T, extraHCL string) {
ServiceID: "svcid2", ServiceID: "svcid2",
ServiceName: "svcname2", ServiceName: "svcname2",
ServiceTags: []string{"tag2"}, ServiceTags: []string{"tag2"},
Type: "ttl",
}, },
"service:svcid2:3": &structs.HealthCheck{ "service:svcid2:3": &structs.HealthCheck{
Node: "node1", Node: "node1",
@ -456,6 +459,7 @@ func testAgent_AddService(t *testing.T, extraHCL string) {
ServiceID: "svcid2", ServiceID: "svcid2",
ServiceName: "svcname2", ServiceName: "svcname2",
ServiceTags: []string{"tag2"}, ServiceTags: []string{"tag2"},
Type: "ttl",
}, },
"service:svcid2:4": &structs.HealthCheck{ "service:svcid2:4": &structs.HealthCheck{
Node: "node1", Node: "node1",
@ -465,6 +469,7 @@ func testAgent_AddService(t *testing.T, extraHCL string) {
ServiceID: "svcid2", ServiceID: "svcid2",
ServiceName: "svcname2", ServiceName: "svcname2",
ServiceTags: []string{"tag2"}, ServiceTags: []string{"tag2"},
Type: "ttl",
}, },
}, },
}, },
@ -828,8 +833,23 @@ func testAgent_RemoveServiceRemovesAllChecks(t *testing.T, extraHCL string) {
svc := &structs.NodeService{ID: "redis", Service: "redis", Port: 8000} svc := &structs.NodeService{ID: "redis", Service: "redis", Port: 8000}
chk1 := &structs.CheckType{CheckID: "chk1", Name: "chk1", TTL: time.Minute} chk1 := &structs.CheckType{CheckID: "chk1", Name: "chk1", TTL: time.Minute}
chk2 := &structs.CheckType{CheckID: "chk2", Name: "chk2", TTL: 2 * time.Minute} chk2 := &structs.CheckType{CheckID: "chk2", Name: "chk2", TTL: 2 * time.Minute}
hchk1 := &structs.HealthCheck{Node: "node1", CheckID: "chk1", Name: "chk1", Status: "critical", ServiceID: "redis", ServiceName: "redis"} hchk1 := &structs.HealthCheck{
hchk2 := &structs.HealthCheck{Node: "node1", CheckID: "chk2", Name: "chk2", Status: "critical", ServiceID: "redis", ServiceName: "redis"} Node: "node1",
CheckID: "chk1",
Name: "chk1",
Status: "critical",
ServiceID: "redis",
ServiceName: "redis",
Type: "ttl",
}
hchk2 := &structs.HealthCheck{Node: "node1",
CheckID: "chk2",
Name: "chk2",
Status: "critical",
ServiceID: "redis",
ServiceName: "redis",
Type: "ttl",
}
// register service with chk1 // register service with chk1
if err := a.AddService(svc, []*structs.CheckType{chk1}, false, "", ConfigSourceLocal); err != nil { if err := a.AddService(svc, []*structs.CheckType{chk1}, false, "", ConfigSourceLocal); err != nil {

View File

@ -520,6 +520,59 @@ func TestCatalogServices_NodeMetaFilter(t *testing.T) {
} }
} }
func TestCatalogRegister_checkRegistration(t *testing.T) {
t.Parallel()
a := NewTestAgent(t, t.Name(), "")
defer a.Shutdown()
// Register node with a service and check
check := structs.HealthCheck{
Node: "foo",
CheckID: "foo-check",
Name: "foo check",
ServiceID: "api",
Definition: structs.HealthCheckDefinition{
TCP: "localhost:8888",
Interval: 5 * time.Second,
},
}
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "api",
},
Check: &check,
}
var out struct{}
if err := a.RPC("Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
retry.Run(t, func(r *retry.R) {
req, _ := http.NewRequest("GET", "/v1/health/checks/api", nil)
resp := httptest.NewRecorder()
obj, err := a.srv.HealthServiceChecks(resp, req)
if err != nil {
r.Fatalf("err: %v", err)
}
checks := obj.(structs.HealthChecks)
if len(checks) != 1 {
r.Fatalf("expected 1 check, got: %d", len(checks))
}
if checks[0].CheckID != check.CheckID {
r.Fatalf("expected check id %s, got %s", check.Type, checks[0].Type)
}
if checks[0].Type != "tcp" {
r.Fatalf("expected check type tcp, got %s", checks[0].Type)
}
})
}
func TestCatalogServiceNodes(t *testing.T) { func TestCatalogServiceNodes(t *testing.T) {
t.Parallel() t.Parallel()
a := NewTestAgent(t, t.Name(), "") a := NewTestAgent(t, t.Name(), "")

View File

@ -127,6 +127,13 @@ func (c *Catalog) Register(args *structs.RegisterRequest, reply *struct{}) error
check.Node = args.Node check.Node = args.Node
} }
checkPreApply(check) checkPreApply(check)
// Populate check type for cases when a check is registered in the catalog directly
// and not via anti-entropy
if check.Type == "" {
chkType := check.CheckType()
check.Type = chkType.Type()
}
} }
// Check the complete register request against the given ACL policy. // Check the complete register request against the given ACL policy.

View File

@ -336,6 +336,7 @@ func TestHealthServiceChecks(t *testing.T) {
Node: a.Config.NodeName, Node: a.Config.NodeName,
Name: "consul check", Name: "consul check",
ServiceID: "consul", ServiceID: "consul",
Type: "grpc",
}, },
} }
@ -357,6 +358,9 @@ func TestHealthServiceChecks(t *testing.T) {
if len(nodes) != 1 { if len(nodes) != 1 {
t.Fatalf("bad: %v", obj) t.Fatalf("bad: %v", obj)
} }
if nodes[0].Type != "grpc" {
t.Fatalf("expected grpc check type, got %s", nodes[0].Type)
}
} }
func TestHealthServiceChecks_NodeMetaFilter(t *testing.T) { func TestHealthServiceChecks_NodeMetaFilter(t *testing.T) {
@ -970,6 +974,59 @@ func TestHealthServiceNodes_PassingFilter(t *testing.T) {
}) })
} }
func TestHealthServiceNodes_CheckType(t *testing.T) {
t.Parallel()
a := NewTestAgent(t, t.Name(), "")
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
req, _ := http.NewRequest("GET", "/v1/health/service/consul?dc=dc1", nil)
resp := httptest.NewRecorder()
obj, err := a.srv.HealthServiceNodes(resp, req)
require.NoError(t, err)
assertIndex(t, resp)
// Should be 1 health check for consul
nodes := obj.(structs.CheckServiceNodes)
if len(nodes) != 1 {
t.Fatalf("expected 1 node, got %d", len(nodes))
}
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: a.Config.NodeName,
Address: "127.0.0.1",
NodeMeta: map[string]string{"somekey": "somevalue"},
Check: &structs.HealthCheck{
Node: a.Config.NodeName,
Name: "consul check",
ServiceID: "consul",
Type: "grpc",
},
}
var out struct{}
require.NoError(t, a.RPC("Catalog.Register", args, &out))
req, _ = http.NewRequest("GET", "/v1/health/service/consul?dc=dc1", nil)
resp = httptest.NewRecorder()
obj, err = a.srv.HealthServiceNodes(resp, req)
require.NoError(t, err)
assertIndex(t, resp)
// Should be a non-nil empty list for checks
nodes = obj.(structs.CheckServiceNodes)
require.Len(t, nodes, 1)
require.Len(t, nodes[0].Checks, 2)
for _, check := range nodes[0].Checks {
if check.Name == "consul check" && check.Type != "grpc" {
t.Fatalf("exptected grpc check type, got %s", check.Type)
}
}
}
func TestHealthServiceNodes_WanTranslation(t *testing.T) { func TestHealthServiceNodes_WanTranslation(t *testing.T) {
t.Parallel() t.Parallel()
a1 := NewTestAgent(t, t.Name(), ` a1 := NewTestAgent(t, t.Name(), `

View File

@ -126,3 +126,24 @@ func (c *CheckType) IsDocker() bool {
func (c *CheckType) IsGRPC() bool { func (c *CheckType) IsGRPC() bool {
return c.GRPC != "" && c.Interval > 0 return c.GRPC != "" && c.Interval > 0
} }
func (c *CheckType) Type() string {
switch {
case c.IsGRPC():
return "grpc"
case c.IsHTTP():
return "http"
case c.IsTTL():
return "ttl"
case c.IsTCP():
return "tcp"
case c.IsAlias():
return "alias"
case c.IsDocker():
return "docker"
case c.IsScript():
return "script"
default:
return ""
}
}

View File

@ -1139,6 +1139,7 @@ type HealthCheck struct {
ServiceID string // optional associated service ServiceID string // optional associated service
ServiceName string // optional service name ServiceName string // optional service name
ServiceTags []string // optional service tags ServiceTags []string // optional service tags
Type string // Check type: http/ttl/tcp/etc
Definition HealthCheckDefinition `bexpr:"-"` Definition HealthCheckDefinition `bexpr:"-"`
@ -1254,6 +1255,32 @@ func (c *HealthCheck) Clone() *HealthCheck {
return clone return clone
} }
func (c *HealthCheck) CheckType() *CheckType {
return &CheckType{
CheckID: c.CheckID,
Name: c.Name,
Status: c.Status,
Notes: c.Notes,
ScriptArgs: c.Definition.ScriptArgs,
AliasNode: c.Definition.AliasNode,
AliasService: c.Definition.AliasService,
HTTP: c.Definition.HTTP,
GRPC: c.Definition.GRPC,
GRPCUseTLS: c.Definition.GRPCUseTLS,
Header: c.Definition.Header,
Method: c.Definition.Method,
TCP: c.Definition.TCP,
Interval: c.Definition.Interval,
DockerContainerID: c.Definition.DockerContainerID,
Shell: c.Definition.Shell,
TLSSkipVerify: c.Definition.TLSSkipVerify,
Timeout: c.Definition.Timeout,
TTL: c.Definition.TTL,
DeregisterCriticalServiceAfter: c.Definition.DeregisterCriticalServiceAfter,
}
}
// HealthChecks is a collection of HealthCheck structs. // HealthChecks is a collection of HealthCheck structs.
type HealthChecks []*HealthCheck type HealthChecks []*HealthCheck

View File

@ -462,6 +462,11 @@ var expectedFieldConfigHealthCheck bexpr.FieldConfigurations = bexpr.FieldConfig
SupportedOperations: []bexpr.MatchOperator{bexpr.MatchIsEmpty, bexpr.MatchIsNotEmpty, bexpr.MatchIn, bexpr.MatchNotIn}, SupportedOperations: []bexpr.MatchOperator{bexpr.MatchIsEmpty, bexpr.MatchIsNotEmpty, bexpr.MatchIn, bexpr.MatchNotIn},
StructFieldName: "ServiceTags", StructFieldName: "ServiceTags",
}, },
"Type": &bexpr.FieldConfiguration{
CoerceFn: bexpr.CoerceString,
SupportedOperations: []bexpr.MatchOperator{bexpr.MatchEqual, bexpr.MatchNotEqual, bexpr.MatchIn, bexpr.MatchNotIn, bexpr.MatchMatches, bexpr.MatchNotMatches},
StructFieldName: "Type",
},
} }
var expectedFieldConfigCheckServiceNode bexpr.FieldConfigurations = bexpr.FieldConfigurations{ var expectedFieldConfigCheckServiceNode bexpr.FieldConfigurations = bexpr.FieldConfigurations{

View File

@ -52,6 +52,7 @@ type AgentCheck struct {
Output string Output string
ServiceID string ServiceID string
ServiceName string ServiceName string
Type string
Definition HealthCheckDefinition Definition HealthCheckDefinition
} }

View File

@ -777,6 +777,9 @@ func TestAPI_AgentChecks(t *testing.T) {
if chk.Status != HealthCritical { if chk.Status != HealthCritical {
t.Fatalf("check not critical: %v", chk) t.Fatalf("check not critical: %v", chk)
} }
if chk.Type != "ttl" {
t.Fatalf("expected type ttl, got %s", chk.Type)
}
if err := agent.CheckDeregister("foo"); err != nil { if err := agent.CheckDeregister("foo"); err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
@ -951,6 +954,9 @@ func TestAPI_AgentChecks_serviceBound(t *testing.T) {
if check.ServiceID != "redis" { if check.ServiceID != "redis" {
t.Fatalf("missing service association for check: %v", check) t.Fatalf("missing service association for check: %v", check)
} }
if check.Type != "ttl" {
t.Fatalf("expected type ttl, got %s", check.Type)
}
} }
func TestAPI_AgentChecks_Docker(t *testing.T) { func TestAPI_AgentChecks_Docker(t *testing.T) {
@ -997,6 +1003,9 @@ func TestAPI_AgentChecks_Docker(t *testing.T) {
if check.ServiceID != "redis" { if check.ServiceID != "redis" {
t.Fatalf("missing service association for check: %v", check) t.Fatalf("missing service association for check: %v", check)
} }
if check.Type != "docker" {
t.Fatalf("expected type ttl, got %s", check.Type)
}
} }
func TestAPI_AgentJoin(t *testing.T) { func TestAPI_AgentJoin(t *testing.T) {
@ -1159,6 +1168,9 @@ func TestAPI_ServiceMaintenance(t *testing.T) {
if strings.Contains(check.CheckID, "maintenance") { if strings.Contains(check.CheckID, "maintenance") {
t.Fatalf("should have removed health check") t.Fatalf("should have removed health check")
} }
if check.Type != "maintenance" {
t.Fatalf("expected type 'maintenance', got %s", check.Type)
}
} }
} }
@ -1207,6 +1219,9 @@ func TestAPI_NodeMaintenance(t *testing.T) {
if strings.Contains(check.CheckID, "maintenance") { if strings.Contains(check.CheckID, "maintenance") {
t.Fatalf("should have removed health check") t.Fatalf("should have removed health check")
} }
if check.Type != "maintenance" {
t.Fatalf("expected type 'maintenance', got %s", check.Type)
}
} }
} }

View File

@ -36,6 +36,7 @@ type HealthCheck struct {
ServiceID string ServiceID string
ServiceName string ServiceName string
ServiceTags []string ServiceTags []string
Type string
Definition HealthCheckDefinition Definition HealthCheckDefinition

View File

@ -223,6 +223,7 @@ func TestAPI_HealthChecks(t *testing.T) {
ServiceID: "foo", ServiceID: "foo",
ServiceName: "foo", ServiceName: "foo",
ServiceTags: []string{"bar"}, ServiceTags: []string{"bar"},
Type: "ttl",
}, },
} }
@ -272,8 +273,11 @@ func TestAPI_HealthChecks_NodeMetaFilter(t *testing.T) {
if meta.LastIndex == 0 { if meta.LastIndex == 0 {
r.Fatalf("bad: %v", meta) r.Fatalf("bad: %v", meta)
} }
if len(checks) == 0 { if len(checks) != 1 {
r.Fatalf("Bad: %v", checks) r.Fatalf("expected 1 check, got %d", len(checks))
}
if checks[0].Type != "ttl" {
r.Fatalf("expected type ttl, got %s", checks[0].Type)
} }
}) })
} }
@ -356,6 +360,12 @@ func TestAPI_HealthService_SingleTag(t *testing.T) {
require.NotEqual(r, meta.LastIndex, 0) require.NotEqual(r, meta.LastIndex, 0)
require.Len(r, services, 1) require.Len(r, services, 1)
require.Equal(r, services[0].Service.ID, "foo1") require.Equal(r, services[0].Service.ID, "foo1")
for _, check := range services[0].Checks {
if check.CheckID == "service:foo1" && check.Type != "ttl" {
r.Fatalf("expected type ttl, got %s", check.Type)
}
}
}) })
} }
func TestAPI_HealthService_MultipleTags(t *testing.T) { func TestAPI_HealthService_MultipleTags(t *testing.T) {

View File

@ -194,6 +194,7 @@ func TestAPI_ClientTxn(t *testing.T) {
DeregisterCriticalServiceAfter: ReadableDuration(20 * time.Second), DeregisterCriticalServiceAfter: ReadableDuration(20 * time.Second),
DeregisterCriticalServiceAfterDuration: 20 * time.Second, DeregisterCriticalServiceAfterDuration: 20 * time.Second,
}, },
Type: "tcp",
CreateIndex: ret.Results[4].Check.CreateIndex, CreateIndex: ret.Results[4].Check.CreateIndex,
ModifyIndex: ret.Results[4].Check.CreateIndex, ModifyIndex: ret.Results[4].Check.CreateIndex,
}, },
@ -212,6 +213,7 @@ func TestAPI_ClientTxn(t *testing.T) {
DeregisterCriticalServiceAfter: ReadableDuration(160 * time.Second), DeregisterCriticalServiceAfter: ReadableDuration(160 * time.Second),
DeregisterCriticalServiceAfterDuration: 160 * time.Second, DeregisterCriticalServiceAfterDuration: 160 * time.Second,
}, },
Type: "tcp",
CreateIndex: ret.Results[4].Check.CreateIndex, CreateIndex: ret.Results[4].Check.CreateIndex,
ModifyIndex: ret.Results[4].Check.CreateIndex, ModifyIndex: ret.Results[4].Check.CreateIndex,
}, },