func TestACL_vetCheckUpdate(t *testing.T) { config := nextConfig() config.ACLEnforceVersion8 = Bool(true) dir, agent := makeAgent(t, config) defer os.RemoveAll(dir) defer agent.Shutdown() testutil.WaitForLeader(t, agent.RPC, "dc1") m := MockServer{catalogPolicy} if err := agent.InjectEndpoint("ACL", &m); err != nil { t.Fatalf("err: %v", err) } // Update a check that doesn't exist. err := agent.vetCheckUpdate("node-rw", "my-check") if err == nil || !strings.Contains(err.Error(), "Unknown check") { t.Fatalf("err: %v", err) } // Update service check with write privs. agent.state.AddService(&structs.NodeService{ ID: "my-service", Service: "service", }, "") agent.state.AddCheck(&structs.HealthCheck{ CheckID: types.CheckID("my-service-check"), ServiceID: "my-service", ServiceName: "service", }, "") err = agent.vetCheckUpdate("service-rw", "my-service-check") if err != nil { t.Fatalf("err: %v", err) } // Update service check without write privs. err = agent.vetCheckUpdate("service-ro", "my-service-check") if err == nil || !strings.Contains(err.Error(), permissionDenied) { t.Fatalf("err: %v", err) } // Update node check with write privs. agent.state.AddCheck(&structs.HealthCheck{ CheckID: types.CheckID("my-node-check"), }, "") err = agent.vetCheckUpdate("node-rw", "my-node-check") if err != nil { t.Fatalf("err: %v", err) } // Update without write privs. err = agent.vetCheckUpdate("node-ro", "my-node-check") if err == nil || !strings.Contains(err.Error(), permissionDenied) { t.Fatalf("err: %v", err) } }
func expectHTTPStatus(t *testing.T, url string, status string) { mock := &MockNotify{ state: make(map[types.CheckID]string), updates: make(map[types.CheckID]int), output: make(map[types.CheckID]string), } check := &CheckHTTP{ Notify: mock, CheckID: types.CheckID("foo"), HTTP: url, Interval: 10 * time.Millisecond, Logger: log.New(os.Stderr, "", log.LstdFlags), } check.Start() defer check.Stop() testutil.WaitForResult(func() (bool, error) { // Should have at least 2 updates if mock.updates["foo"] < 2 { return false, fmt.Errorf("should have 2 updates %v", mock.updates) } if mock.state["foo"] != status { return false, fmt.Errorf("should be %v %v", status, mock.state) } // Allow slightly more data than CheckBufSize, for the header if n := len(mock.output["foo"]); n > (CheckBufSize + 256) { return false, fmt.Errorf("output too long: %d (%d-byte limit)", n, CheckBufSize) } return true, nil }, func(err error) { t.Fatalf("err: %s", err) }) }
func TestDockerCheckUseShellFromEnv(t *testing.T) { mock := &MockNotify{ state: make(map[types.CheckID]string), updates: make(map[types.CheckID]int), output: make(map[types.CheckID]string), } os.Setenv("SHELL", "/bin/bash") check := &CheckDocker{ Notify: mock, CheckID: types.CheckID("foo"), Script: "/health.sh", DockerContainerID: "54432bad1fc7", Interval: 10 * time.Millisecond, Logger: log.New(os.Stderr, "", log.LstdFlags), dockerClient: &fakeDockerClientWithNoErrors{}, } check.Start() defer check.Stop() time.Sleep(50 * time.Millisecond) if check.Shell != "/bin/bash" { t.Fatalf("Shell should be: %v , actual: %v", "/bin/bash", check.Shell) } os.Setenv("SHELL", "") }
func expectDockerCheckStatus(t *testing.T, dockerClient DockerClient, status string, output string) { mock := &MockNotify{ state: make(map[types.CheckID]string), updates: make(map[types.CheckID]int), output: make(map[types.CheckID]string), } check := &CheckDocker{ Notify: mock, CheckID: types.CheckID("foo"), Script: "/health.sh", DockerContainerID: "54432bad1fc7", Shell: "/bin/sh", Interval: 10 * time.Millisecond, Logger: log.New(os.Stderr, "", log.LstdFlags), dockerClient: dockerClient, } check.Start() defer check.Stop() time.Sleep(50 * time.Millisecond) // Should have at least 2 updates if mock.updates["foo"] < 2 { t.Fatalf("should have 2 updates %v", mock.updates) } if mock.state["foo"] != status { t.Fatalf("should be %v %v", status, mock.state) } if mock.output["foo"] != output { t.Fatalf("should be %v %v", output, mock.output) } }
func TestDockerCheckTruncateOutput(t *testing.T) { mock := &MockNotify{ state: make(map[types.CheckID]string), updates: make(map[types.CheckID]int), output: make(map[types.CheckID]string), } check := &CheckDocker{ Notify: mock, CheckID: types.CheckID("foo"), Script: "/health.sh", DockerContainerID: "54432bad1fc7", Shell: "/bin/sh", Interval: 10 * time.Millisecond, Logger: log.New(os.Stderr, "", log.LstdFlags), dockerClient: &fakeDockerClientWithLongOutput{}, } check.Start() defer check.Stop() time.Sleep(50 * time.Millisecond) // Allow for extra bytes for the truncation message if len(mock.output["foo"]) > CheckBufSize+100 { t.Fatalf("output size is too long") } }
func TestCheckHTTP_TLSSkipVerify_true_fail(t *testing.T) { server := mockTLSHTTPServer(500) defer server.Close() mock := &MockNotify{ state: make(map[types.CheckID]string), updates: make(map[types.CheckID]int), output: make(map[types.CheckID]string), } check := &CheckHTTP{ Notify: mock, CheckID: types.CheckID("skipverify_true"), HTTP: server.URL, Interval: 5 * time.Millisecond, Logger: log.New(os.Stderr, "", log.LstdFlags), TLSSkipVerify: true, } check.Start() defer check.Stop() if !check.httpClient.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify { t.Fatalf("should be true") } testutil.WaitForResult(func() (bool, error) { if mock.state["skipverify_true"] != structs.HealthCritical { return false, fmt.Errorf("should be critical %v", mock.state) } return true, nil }, func(err error) { t.Fatalf("err: %s", err) }) }
func TestCheckMonitor_Timeout(t *testing.T) { mock := &MockNotify{ state: make(map[types.CheckID]string), updates: make(map[types.CheckID]int), output: make(map[types.CheckID]string), } check := &CheckMonitor{ Notify: mock, CheckID: types.CheckID("foo"), Script: "sleep 1 && exit 0", Interval: 10 * time.Millisecond, Timeout: 5 * time.Millisecond, Logger: log.New(os.Stderr, "", log.LstdFlags), ReapLock: &sync.RWMutex{}, } check.Start() defer check.Stop() time.Sleep(50 * time.Millisecond) // Should have at least 2 updates if mock.updates["foo"] < 2 { t.Fatalf("should have at least 2 updates %v", mock.updates) } if mock.state["foo"] != "critical" { t.Fatalf("should be critical %v", mock.state) } }
func TestCheckHTTPTimeout(t *testing.T) { server := mockSlowHTTPServer(200, 10*time.Millisecond) defer server.Close() mock := &MockNotify{ state: make(map[types.CheckID]string), updates: make(map[types.CheckID]int), output: make(map[types.CheckID]string), } check := &CheckHTTP{ Notify: mock, CheckID: types.CheckID("bar"), HTTP: server.URL, Timeout: 5 * time.Millisecond, Interval: 10 * time.Millisecond, Logger: log.New(os.Stderr, "", log.LstdFlags), } check.Start() defer check.Stop() time.Sleep(50 * time.Millisecond) // Should have at least 2 updates if mock.updates["bar"] < 2 { t.Fatalf("should have at least 2 updates %v", mock.updates) } if mock.state["bar"] != structs.HealthCritical { t.Fatalf("should be critical %v", mock.state) } }
func expectTCPStatus(t *testing.T, tcp string, status string) { mock := &MockNotify{ state: make(map[types.CheckID]string), updates: make(map[types.CheckID]int), output: make(map[types.CheckID]string), } check := &CheckTCP{ Notify: mock, CheckID: types.CheckID("foo"), TCP: tcp, Interval: 10 * time.Millisecond, Logger: log.New(os.Stderr, "", log.LstdFlags), } check.Start() defer check.Stop() time.Sleep(50 * time.Millisecond) // Should have at least 2 updates if mock.updates["foo"] < 2 { t.Fatalf("should have 2 updates %v", mock.updates) } if mock.state["foo"] != status { t.Fatalf("should be %v %v", status, mock.state) } }
func expectStatus(t *testing.T, script, status string) { mock := &MockNotify{ state: make(map[types.CheckID]string), updates: make(map[types.CheckID]int), output: make(map[types.CheckID]string), } check := &CheckMonitor{ Notify: mock, CheckID: types.CheckID("foo"), Script: script, Interval: 10 * time.Millisecond, Logger: log.New(os.Stderr, "", log.LstdFlags), ReapLock: &sync.RWMutex{}, } check.Start() defer check.Stop() testutil.WaitForResult(func() (bool, error) { // Should have at least 2 updates if mock.updates["foo"] < 2 { return false, fmt.Errorf("should have 2 updates %v", mock.updates) } if mock.state["foo"] != status { return false, fmt.Errorf("should be %v %v", status, mock.state) } return true, nil }, func(err error) { t.Fatalf("err: %s", err) }) }
func expectHTTPStatus(t *testing.T, url string, status string) { mock := &MockNotify{ state: make(map[types.CheckID]string), updates: make(map[types.CheckID]int), output: make(map[types.CheckID]string), } check := &CheckHTTP{ Notify: mock, CheckID: types.CheckID("foo"), HTTP: url, Interval: 10 * time.Millisecond, Logger: log.New(os.Stderr, "", log.LstdFlags), } check.Start() defer check.Stop() time.Sleep(50 * time.Millisecond) // Should have at least 2 updates if mock.updates["foo"] < 2 { t.Fatalf("should have 2 updates %v", mock.updates) } if mock.state["foo"] != status { t.Fatalf("should be %v %v", status, mock.state) } // Allow slightly more data than CheckBufSize, for the header if n := len(mock.output["foo"]); n > (CheckBufSize + 256) { t.Fatalf("output too long: %d (%d-byte limit)", n, CheckBufSize) } }
func TestCheckMonitor_RandomStagger(t *testing.T) { mock := &MockNotify{ state: make(map[types.CheckID]string), updates: make(map[types.CheckID]int), output: make(map[types.CheckID]string), } check := &CheckMonitor{ Notify: mock, CheckID: types.CheckID("foo"), Script: "exit 0", Interval: 25 * time.Millisecond, Logger: log.New(os.Stderr, "", log.LstdFlags), ReapLock: &sync.RWMutex{}, } check.Start() defer check.Stop() time.Sleep(50 * time.Millisecond) // Should have at least 1 update if mock.updates["foo"] < 1 { t.Fatalf("should have 1 or more updates %v", mock.updates) } if mock.state["foo"] != structs.HealthPassing { t.Fatalf("should be %v %v", structs.HealthPassing, mock.state) } }
func TestAgent_checkCriticalTime(t *testing.T) { config := nextConfig() l := new(localState) l.Init(config, nil) // Add a passing check and make sure it's not critical. checkID := types.CheckID("redis:1") chk := &structs.HealthCheck{ Node: "node", CheckID: checkID, Name: "redis:1", ServiceID: "redis", Status: structs.HealthPassing, } l.AddCheck(chk, "") if checks := l.CriticalChecks(); len(checks) > 0 { t.Fatalf("should not have any critical checks") } // Set it to warning and make sure that doesn't show up as critical. l.UpdateCheck(checkID, structs.HealthWarning, "") if checks := l.CriticalChecks(); len(checks) > 0 { t.Fatalf("should not have any critical checks") } // Fail the check and make sure the time looks reasonable. l.UpdateCheck(checkID, structs.HealthCritical, "") if crit, ok := l.CriticalChecks()[checkID]; !ok { t.Fatalf("should have a critical check") } else if crit.CriticalFor > time.Millisecond { t.Fatalf("bad: %#v", crit) } // Wait a while, then fail it again and make sure the time keeps track // of the initial failure, and doesn't reset here. time.Sleep(10 * time.Millisecond) l.UpdateCheck(chk.CheckID, structs.HealthCritical, "") if crit, ok := l.CriticalChecks()[checkID]; !ok { t.Fatalf("should have a critical check") } else if crit.CriticalFor < 5*time.Millisecond || crit.CriticalFor > 15*time.Millisecond { t.Fatalf("bad: %#v", crit) } // Set it passing again. l.UpdateCheck(checkID, structs.HealthPassing, "") if checks := l.CriticalChecks(); len(checks) > 0 { t.Fatalf("should not have any critical checks") } // Fail the check and make sure the time looks like it started again // from the latest failure, not the original one. l.UpdateCheck(checkID, structs.HealthCritical, "") if crit, ok := l.CriticalChecks()[checkID]; !ok { t.Fatalf("should have a critical check") } else if crit.CriticalFor > time.Millisecond { t.Fatalf("bad: %#v", crit) } }
func (s *HTTPServer) AgentDeregisterCheck(resp http.ResponseWriter, req *http.Request) (interface{}, error) { checkID := types.CheckID(strings.TrimPrefix(req.URL.Path, "/v1/agent/check/deregister/")) if err := s.agent.RemoveCheck(checkID, true); err != nil { return nil, err } s.syncChanges() return nil, nil }
// Register is used register that a node is providing a given service. func (c *Catalog) Register(args *structs.RegisterRequest, reply *struct{}) error { if done, err := c.srv.forward("Catalog.Register", args, args, reply); done { return err } defer metrics.MeasureSince([]string{"consul", "catalog", "register"}, time.Now()) // Verify the args if args.Node == "" || args.Address == "" { return fmt.Errorf("Must provide node and address") } if args.Service != nil { // If no service id, but service name, use default if args.Service.ID == "" && args.Service.Service != "" { args.Service.ID = args.Service.Service } // Verify ServiceName provided if ID if args.Service.ID != "" && args.Service.Service == "" { return fmt.Errorf("Must provide service name with ID") } // Apply the ACL policy if any // The 'consul' service is excluded since it is managed // automatically internally. if args.Service.Service != ConsulServiceName { acl, err := c.srv.resolveToken(args.Token) if err != nil { return err } else if acl != nil && !acl.ServiceWrite(args.Service.Service) { c.srv.logger.Printf("[WARN] consul.catalog: Register of service '%s' on '%s' denied due to ACLs", args.Service.Service, args.Node) return permissionDeniedErr } } } if args.Check != nil { args.Checks = append(args.Checks, args.Check) args.Check = nil } for _, check := range args.Checks { if check.CheckID == "" && check.Name != "" { check.CheckID = types.CheckID(check.Name) } if check.Node == "" { check.Node = args.Node } } _, err := c.srv.raftApply(structs.RegisterRequestType, args) if err != nil { c.srv.logger.Printf("[ERR] consul.catalog: Register failed: %v", err) return err } return nil }
func (s *HTTPServer) AgentCheckFail(resp http.ResponseWriter, req *http.Request) (interface{}, error) { checkID := types.CheckID(strings.TrimPrefix(req.URL.Path, "/v1/agent/check/fail/")) note := req.URL.Query().Get("note") if err := s.agent.updateTTLCheck(checkID, structs.HealthCritical, note); err != nil { return nil, err } s.syncChanges() return nil, nil }
func TestCheckHTTP_disablesKeepAlives(t *testing.T) { check := &CheckHTTP{ CheckID: types.CheckID("foo"), HTTP: "http://foo.bar/baz", Interval: 10 * time.Second, Logger: log.New(os.Stderr, "", log.LstdFlags), } check.Start() defer check.Stop() if !check.httpClient.Transport.(*http.Transport).DisableKeepAlives { t.Fatalf("should have disabled keepalives") } }
func TestAgent_RegisterCheck(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?token=abc123", nil) if err != nil { t.Fatalf("err: %v", err) } args := &CheckDefinition{ Name: "test", CheckType: CheckType{ TTL: 15 * time.Second, }, } 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 checkID := types.CheckID("test") if _, ok := srv.agent.state.Checks()[checkID]; !ok { t.Fatalf("missing test check") } if _, ok := srv.agent.checkTTLs[checkID]; !ok { t.Fatalf("missing test check ttl") } // Ensure the token was configured if token := srv.agent.state.CheckToken(checkID); token == "" { t.Fatalf("missing token") } // By default, checks start in critical state. state := srv.agent.state.Checks()[checkID] if state.Status != structs.HealthCritical { t.Fatalf("bad: %v", state) } }
func (s *HTTPServer) AgentDeregisterCheck(resp http.ResponseWriter, req *http.Request) (interface{}, error) { checkID := types.CheckID(strings.TrimPrefix(req.URL.Path, "/v1/agent/check/deregister/")) // Get the provided token, if any, and vet against any ACL policies. var token string s.parseToken(req, &token) if err := s.agent.vetCheckUpdate(token, checkID); err != nil { return nil, err } if err := s.agent.RemoveCheck(checkID, true); err != nil { return nil, err } s.syncChanges() return nil, nil }
func TestCheckTTL(t *testing.T) { mock := &MockNotify{ state: make(map[types.CheckID]string), updates: make(map[types.CheckID]int), output: make(map[types.CheckID]string), } check := &CheckTTL{ Notify: mock, CheckID: types.CheckID("foo"), TTL: 100 * time.Millisecond, Logger: log.New(os.Stderr, "", log.LstdFlags), } check.Start() defer check.Stop() time.Sleep(50 * time.Millisecond) check.SetStatus(structs.HealthPassing, "test-output") if mock.updates["foo"] != 1 { t.Fatalf("should have 1 updates %v", mock.updates) } if mock.state["foo"] != structs.HealthPassing { t.Fatalf("should be passing %v", mock.state) } // Ensure we don't fail early time.Sleep(75 * time.Millisecond) if mock.updates["foo"] != 1 { t.Fatalf("should have 1 updates %v", mock.updates) } // Wait for the TTL to expire time.Sleep(75 * time.Millisecond) if mock.updates["foo"] != 2 { t.Fatalf("should have 2 updates %v", mock.updates) } if mock.state["foo"] != structs.HealthCritical { t.Fatalf("should be critical %v", mock.state) } if !strings.Contains(mock.output["foo"], "test-output") { t.Fatalf("should have retained output %v", mock.output) } }
func (s *HTTPServer) AgentCheckFail(resp http.ResponseWriter, req *http.Request) (interface{}, error) { checkID := types.CheckID(strings.TrimPrefix(req.URL.Path, "/v1/agent/check/fail/")) note := req.URL.Query().Get("note") // Get the provided token, if any, and vet against any ACL policies. var token string s.parseToken(req, &token) if err := s.agent.vetCheckUpdate(token, checkID); err != nil { return nil, err } if err := s.agent.updateTTLCheck(checkID, structs.HealthCritical, note); err != nil { return nil, err } s.syncChanges() return nil, nil }
func (c *CheckDefinition) HealthCheck(node string) *structs.HealthCheck { health := &structs.HealthCheck{ Node: node, CheckID: c.ID, Name: c.Name, Status: structs.HealthCritical, Notes: c.Notes, ServiceID: c.ServiceID, } if c.Status != "" { health.Status = c.Status } if health.CheckID == "" && health.Name != "" { health.CheckID = types.CheckID(health.Name) } return health }
// AgentCheckUpdate is a PUT-based alternative to the GET-based Pass/Warn/Fail // APIs. func (s *HTTPServer) AgentCheckUpdate(resp http.ResponseWriter, req *http.Request) (interface{}, error) { if req.Method != "PUT" { resp.WriteHeader(405) return nil, nil } var update checkUpdate if err := decodeBody(req, &update, nil); err != nil { resp.WriteHeader(400) resp.Write([]byte(fmt.Sprintf("Request decode failed: %v", err))) return nil, nil } switch update.Status { case structs.HealthPassing: case structs.HealthWarning: case structs.HealthCritical: default: resp.WriteHeader(400) resp.Write([]byte(fmt.Sprintf("Invalid check status: '%s'", update.Status))) return nil, nil } total := len(update.Output) if total > CheckBufSize { update.Output = fmt.Sprintf("%s ... (captured %d of %d bytes)", update.Output[:CheckBufSize], CheckBufSize, total) } checkID := types.CheckID(strings.TrimPrefix(req.URL.Path, "/v1/agent/check/update/")) // Get the provided token, if any, and vet against any ACL policies. var token string s.parseToken(req, &token) if err := s.agent.vetCheckUpdate(token, checkID); err != nil { return nil, err } if err := s.agent.updateTTLCheck(checkID, update.Status, update.Output); err != nil { return nil, err } s.syncChanges() return nil, nil }
func TestAgent_RegisterCheck_Passing(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 checkID := types.CheckID("test") if _, ok := srv.agent.state.Checks()[checkID]; !ok { t.Fatalf("missing test check") } if _, ok := srv.agent.checkTTLs[checkID]; !ok { t.Fatalf("missing test check ttl") } state := srv.agent.state.Checks()[checkID] if state.Status != structs.HealthPassing { t.Fatalf("bad: %v", state) } }
func TestCheckHTTP_TLSSkipVerify_false(t *testing.T) { server := mockTLSHTTPServer(200) defer server.Close() mock := &MockNotify{ state: make(map[types.CheckID]string), updates: make(map[types.CheckID]int), output: make(map[types.CheckID]string), } check := &CheckHTTP{ Notify: mock, CheckID: types.CheckID("skipverify_false"), HTTP: server.URL, Interval: 100 * time.Millisecond, Logger: log.New(os.Stderr, "", log.LstdFlags), TLSSkipVerify: false, } check.Start() defer check.Stop() if check.httpClient.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify { t.Fatalf("should be false") } testutil.WaitForResult(func() (bool, error) { // This should fail due to an invalid SSL cert if mock.state["skipverify_false"] != structs.HealthCritical { return false, fmt.Errorf("should be critical %v", mock.state) } if !strings.Contains(mock.output["skipverify_false"], "certificate signed by unknown authority") { return false, fmt.Errorf("should fail with certificate error %v", mock.output) } return true, nil }, func(err error) { t.Fatalf("err: %s", err) }) }
func TestCheckMonitor_LimitOutput(t *testing.T) { mock := &MockNotify{ state: make(map[types.CheckID]string), updates: make(map[types.CheckID]int), output: make(map[types.CheckID]string), } check := &CheckMonitor{ Notify: mock, CheckID: types.CheckID("foo"), Script: "od -N 81920 /dev/urandom", Interval: 25 * time.Millisecond, Logger: log.New(os.Stderr, "", log.LstdFlags), } check.Start() defer check.Stop() time.Sleep(50 * time.Millisecond) // Allow for extra bytes for the truncation message if len(mock.output["foo"]) > CheckBufSize+100 { t.Fatalf("output size is too long") } }
func TestCheckHTTPTimeout(t *testing.T) { server := mockSlowHTTPServer(200, 10*time.Millisecond) defer server.Close() mock := &MockNotify{ state: make(map[types.CheckID]string), updates: make(map[types.CheckID]int), output: make(map[types.CheckID]string), } check := &CheckHTTP{ Notify: mock, CheckID: types.CheckID("bar"), HTTP: server.URL, Timeout: 5 * time.Millisecond, Interval: 10 * time.Millisecond, Logger: log.New(os.Stderr, "", log.LstdFlags), } check.Start() defer check.Stop() testutil.WaitForResult(func() (bool, error) { // Should have at least 2 updates if mock.updates["bar"] < 2 { return false, fmt.Errorf("should have at least 2 updates %v", mock.updates) } if mock.state["bar"] != structs.HealthCritical { return false, fmt.Errorf("should be critical %v", mock.state) } return true, nil }, func(err error) { t.Fatalf("err: %s", err) }) }
// serviceMaintCheckID returns the ID of a given service's maintenance check func serviceMaintCheckID(serviceID string) types.CheckID { return types.CheckID(fmt.Sprintf("%s:%s", serviceMaintCheckPrefix, serviceID)) }
// Register is used register that a node is providing a given service. func (c *Catalog) Register(args *structs.RegisterRequest, reply *struct{}) error { if done, err := c.srv.forward("Catalog.Register", args, args, reply); done { return err } defer metrics.MeasureSince([]string{"consul", "catalog", "register"}, time.Now()) // Verify the args. if args.Node == "" || args.Address == "" { return fmt.Errorf("Must provide node and address") } // Fetch the ACL token, if any. acl, err := c.srv.resolveToken(args.Token) if err != nil { return err } // Handle a service registration. if args.Service != nil { // If no service id, but service name, use default if args.Service.ID == "" && args.Service.Service != "" { args.Service.ID = args.Service.Service } // Verify ServiceName provided if ID. if args.Service.ID != "" && args.Service.Service == "" { return fmt.Errorf("Must provide service name with ID") } // Apply the ACL policy if any. The 'consul' service is excluded // since it is managed automatically internally (that behavior // is going away after version 0.8). We check this same policy // later if version 0.8 is enabled, so we can eventually just // delete this and do all the ACL checks down there. if args.Service.Service != ConsulServiceName { if acl != nil && !acl.ServiceWrite(args.Service.Service) { return permissionDeniedErr } } } // Move the old format single check into the slice, and fixup IDs. if args.Check != nil { args.Checks = append(args.Checks, args.Check) args.Check = nil } for _, check := range args.Checks { if check.CheckID == "" && check.Name != "" { check.CheckID = types.CheckID(check.Name) } if check.Node == "" { check.Node = args.Node } } // Check the complete register request against the given ACL policy. if acl != nil && c.srv.config.ACLEnforceVersion8 { state := c.srv.fsm.State() _, ns, err := state.NodeServices(args.Node) if err != nil { return fmt.Errorf("Node lookup failed: %v", err) } if err := vetRegisterWithACL(acl, args, ns); err != nil { return err } } _, err = c.srv.raftApply(structs.RegisterRequestType, args) if err != nil { return err } return nil }
// AddService is used to add a service entry. // This entry is persistent and the agent will make a best effort to // ensure it is registered func (a *Agent) AddService(service *structs.NodeService, chkTypes CheckTypes, persist bool, token string) error { if service.Service == "" { return fmt.Errorf("Service name missing") } if service.ID == "" && service.Service != "" { service.ID = service.Service } for _, check := range chkTypes { if !check.Valid() { return fmt.Errorf("Check type is not valid") } } // Warn if the service name is incompatible with DNS if !dnsNameRe.MatchString(service.Service) { a.logger.Printf("[WARN] Service name %q will not be discoverable "+ "via DNS due to invalid characters. Valid characters include "+ "all alpha-numerics and dashes.", service.Service) } // Warn if any tags are incompatible with DNS for _, tag := range service.Tags { if !dnsNameRe.MatchString(tag) { a.logger.Printf("[WARN] Service tag %q will not be discoverable "+ "via DNS due to invalid characters. Valid characters include "+ "all alpha-numerics and dashes.", tag) } } // Pause the service syncs during modification a.PauseSync() defer a.ResumeSync() // Take a snapshot of the current state of checks (if any), and // restore them before resuming anti-entropy. snap := a.snapshotCheckState() defer a.restoreCheckState(snap) // Add the service a.state.AddService(service, token) // Persist the service to a file if persist && !a.config.DevMode { if err := a.persistService(service); err != nil { return err } } // Create an associated health check for i, chkType := range chkTypes { checkID := fmt.Sprintf("service:%s", service.ID) if len(chkTypes) > 1 { checkID += fmt.Sprintf(":%d", i+1) } check := &structs.HealthCheck{ Node: a.config.NodeName, CheckID: types.CheckID(checkID), Name: fmt.Sprintf("Service '%s' check", service.Service), Status: structs.HealthCritical, Notes: chkType.Notes, ServiceID: service.ID, ServiceName: service.Service, } if chkType.Status != "" { check.Status = chkType.Status } if err := a.AddCheck(check, chkType, persist, token); err != nil { return err } } return nil }