open-consul/command/agent/agent_endpoint.go
Ryan Uber 4f14ba6326 agent: allow config reload to modify checks/services persistence
This change consolidates loading services and checks from both config
and persisted state into methods on the agent. As part of this, we
introduce optional persistence when calling RemoveCheck/RemoveService.

Fixes a bug where config reloads would kill persisted services/checks.
Also fixes an edge case:

1. A service or check is registered via the HTTP API
2. A new service or check definition with the same ID is added to config
3. Config is reloaded

The desired behavior (which this implements) is:

1. All services and checks deregistered in memory
2. All services and checks in config are registered first
3. All persisted checks are restored using the same logic as the agent
   start sequence, which prioritizes config over persisted, and removes
   any persistence files if new config counterparts are present.
2014-11-26 12:46:42 -08:00

179 lines
5 KiB
Go

package agent
import (
"fmt"
"github.com/hashicorp/consul/consul/structs"
"github.com/hashicorp/serf/serf"
"net/http"
"strings"
)
type AgentSelf struct {
Config *Config
Member serf.Member
}
func (s *HTTPServer) AgentSelf(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
return AgentSelf{
Config: s.agent.config,
Member: s.agent.LocalMember(),
}, nil
}
func (s *HTTPServer) AgentServices(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
services := s.agent.state.Services()
return services, nil
}
func (s *HTTPServer) AgentChecks(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
checks := s.agent.state.Checks()
return checks, nil
}
func (s *HTTPServer) AgentMembers(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
// Check if the WAN is being queried
wan := false
if other := req.URL.Query().Get("wan"); other != "" {
wan = true
}
if wan {
return s.agent.WANMembers(), nil
} else {
return s.agent.LANMembers(), nil
}
}
func (s *HTTPServer) AgentJoin(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
// Check if the WAN is being queried
wan := false
if other := req.URL.Query().Get("wan"); other != "" {
wan = true
}
// Get the address
addr := strings.TrimPrefix(req.URL.Path, "/v1/agent/join/")
if wan {
_, err := s.agent.JoinWAN([]string{addr})
return nil, err
} else {
_, err := s.agent.JoinLAN([]string{addr})
return nil, err
}
}
func (s *HTTPServer) AgentForceLeave(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
addr := strings.TrimPrefix(req.URL.Path, "/v1/agent/force-leave/")
return nil, s.agent.ForceLeave(addr)
}
func (s *HTTPServer) AgentRegisterCheck(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
var args CheckDefinition
// Fixup the type decode of TTL or Interval
decodeCB := func(raw interface{}) error {
return FixupCheckType(raw)
}
if err := decodeBody(req, &args, decodeCB); err != nil {
resp.WriteHeader(400)
resp.Write([]byte(fmt.Sprintf("Request decode failed: %v", err)))
return nil, nil
}
// Verify the check has a name
if args.Name == "" {
resp.WriteHeader(400)
resp.Write([]byte("Missing check name"))
return nil, nil
}
// Construct the health check
health := args.HealthCheck(s.agent.config.NodeName)
// Verify the check type
chkType := &args.CheckType
if !chkType.Valid() {
resp.WriteHeader(400)
resp.Write([]byte("Must provide TTL or Script and Interval!"))
return nil, nil
}
// Add the check
return nil, s.agent.AddCheck(health, chkType, true)
}
func (s *HTTPServer) AgentDeregisterCheck(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
checkID := strings.TrimPrefix(req.URL.Path, "/v1/agent/check/deregister/")
return nil, s.agent.RemoveCheck(checkID, true)
}
func (s *HTTPServer) AgentCheckPass(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
checkID := strings.TrimPrefix(req.URL.Path, "/v1/agent/check/pass/")
note := req.URL.Query().Get("note")
return nil, s.agent.UpdateCheck(checkID, structs.HealthPassing, note)
}
func (s *HTTPServer) AgentCheckWarn(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
checkID := strings.TrimPrefix(req.URL.Path, "/v1/agent/check/warn/")
note := req.URL.Query().Get("note")
return nil, s.agent.UpdateCheck(checkID, structs.HealthWarning, note)
}
func (s *HTTPServer) AgentCheckFail(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
checkID := strings.TrimPrefix(req.URL.Path, "/v1/agent/check/fail/")
note := req.URL.Query().Get("note")
return nil, s.agent.UpdateCheck(checkID, structs.HealthCritical, note)
}
func (s *HTTPServer) AgentRegisterService(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
var args ServiceDefinition
// Fixup the type decode of TTL or Interval if a check if provided
decodeCB := func(raw interface{}) error {
rawMap, ok := raw.(map[string]interface{})
if !ok {
return nil
}
var check interface{}
for k, v := range rawMap {
if strings.ToLower(k) == "check" {
check = v
}
}
if check == nil {
return nil
}
return FixupCheckType(check)
}
if err := decodeBody(req, &args, decodeCB); err != nil {
resp.WriteHeader(400)
resp.Write([]byte(fmt.Sprintf("Request decode failed: %v", err)))
return nil, nil
}
// Verify the service has a name
if args.Name == "" {
resp.WriteHeader(400)
resp.Write([]byte("Missing service name"))
return nil, nil
}
// Get the node service
ns := args.NodeService()
// Verify the check type
chkType := args.CheckType()
if chkType != nil && !chkType.Valid() {
resp.WriteHeader(400)
resp.Write([]byte("Must provide TTL or Script and Interval!"))
return nil, nil
}
// Add the check
return nil, s.agent.AddService(ns, chkType, true)
}
func (s *HTTPServer) AgentDeregisterService(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
serviceID := strings.TrimPrefix(req.URL.Path, "/v1/agent/service/deregister/")
return nil, s.agent.RemoveService(serviceID, true)
}