From 1a5158212ceae74f3ff23fcdd5e075938d175c58 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Mon, 3 Feb 2014 15:15:35 -0800 Subject: [PATCH] agent: First pass at parsing service and check definition --- command/agent/agent_endpoint.go | 33 ++-------- command/agent/command.go | 20 +++++- command/agent/config.go | 108 +++++++++++++++++++++++++++++++- command/agent/structs.go | 51 ++++++++++++--- 4 files changed, 173 insertions(+), 39 deletions(-) diff --git a/command/agent/agent_endpoint.go b/command/agent/agent_endpoint.go index 2552d46466..d43d7490b3 100644 --- a/command/agent/agent_endpoint.go +++ b/command/agent/agent_endpoint.go @@ -69,18 +69,7 @@ func (s *HTTPServer) AgentRegisterCheck(resp http.ResponseWriter, req *http.Requ } // Construct the health check - health := structs.HealthCheck{ - Node: s.agent.config.NodeName, - CheckID: args.ID, - Name: args.Name, - Status: structs.HealthUnknown, - Notes: args.Notes, - } - - // Fixup the ID if not given - if health.CheckID == "" && health.Name != "" { - health.CheckID = health.Name - } + health := args.HealthCheck(s.agent.config.NodeName) // Verify the check type chkType := &args.CheckType @@ -91,7 +80,7 @@ func (s *HTTPServer) AgentRegisterCheck(resp http.ResponseWriter, req *http.Requ } // Add the check - return s.agent.AddCheck(&health, chkType), nil + return s.agent.AddCheck(health, chkType), nil } func (s *HTTPServer) AgentDeregisterCheck(resp http.ResponseWriter, req *http.Request) (interface{}, error) { @@ -132,21 +121,11 @@ func (s *HTTPServer) AgentRegisterService(resp http.ResponseWriter, req *http.Re return nil, nil } - // Construct the health check - ns := structs.NodeService{ - ID: args.ID, - Service: args.Name, - Tag: args.Tag, - Port: args.Port, - } - - // Fixup the ID if not given - if ns.ID == "" && ns.Service != "" { - ns.ID = ns.Service - } + // Get the node service + ns := args.NodeService() // Verify the check type - chkType := args.Check + chkType := args.CheckType() if chkType != nil && !chkType.Valid() { resp.WriteHeader(400) resp.Write([]byte("Must provide TTL or Script and Interval!")) @@ -154,7 +133,7 @@ func (s *HTTPServer) AgentRegisterService(resp http.ResponseWriter, req *http.Re } // Add the check - return s.agent.AddService(&ns, chkType), nil + return s.agent.AddService(ns, chkType), nil } func (s *HTTPServer) AgentDeregisterService(resp http.ResponseWriter, req *http.Request) (interface{}, error) { diff --git a/command/agent/command.go b/command/agent/command.go index f97d45b01f..35d1135ca4 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -197,7 +197,25 @@ func (c *Command) Run(args []string) int { defer c.httpServer.Shutdown() } - // TODO: Register services/checks + // Register the services + for _, service := range config.Services { + ns := service.NodeService() + chkType := service.CheckType() + if err := c.agent.AddService(ns, chkType); err != nil { + c.Ui.Error(fmt.Sprintf("Failed to register service '%s': %v", service.Name, err)) + return 1 + } + } + + // Register the checks + for _, check := range config.Checks { + health := check.HealthCheck(config.NodeName) + chkType := &check.CheckType + if err := c.agent.AddCheck(health, chkType); err != nil { + c.Ui.Error(fmt.Sprintf("Failed to register check '%s': %v %v", check.Name, err, check)) + return 1 + } + } // Let the agent know we've finished registration c.agent.StartSync() diff --git a/command/agent/config.go b/command/agent/config.go index 425895f49f..39e5966b0f 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -93,6 +93,12 @@ type Config struct { // the INT signal. Defaults false. This can be changed on reload. SkipLeaveOnInt bool `mapstructure:"skip_leave_on_interrupt"` + // Checks holds the provided check definitions + Checks []*CheckDefinition + + // Services holds the provided service definitions + Services []*ServiceDefinition + // ConsulConfig can either be provided or a default one created ConsulConfig *consul.Config } @@ -124,14 +130,29 @@ func (c *Config) EncryptBytes() ([]byte, error) { // format and decodes it into a proper Config structure. func DecodeConfig(r io.Reader) (*Config, error) { var raw interface{} + var result Config dec := json.NewDecoder(r) if err := dec.Decode(&raw); err != nil { return nil, err } + // Check the result type + if obj, ok := raw.(map[string]interface{}); ok { + // Check for a "service" or "check" key, meaning + // this is actually a definition entry + if sub, ok := obj["service"]; ok { + service, err := DecodeServiceDefinition(sub) + result.Services = append(result.Services, service) + return &result, err + } else if sub, ok := obj["check"]; ok { + check, err := DecodeCheckDefinition(sub) + result.Checks = append(result.Checks, check) + return &result, err + } + } + // Decode var md mapstructure.Metadata - var result Config msdec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ Metadata: &md, Result: &result, @@ -147,6 +168,85 @@ func DecodeConfig(r io.Reader) (*Config, error) { return &result, nil } +// DecodeServiceDefinition is used to decode a service definition +func DecodeServiceDefinition(raw interface{}) (*ServiceDefinition, error) { + var sub interface{} + rawMap, ok := raw.(map[string]interface{}) + if !ok { + goto AFTER_FIX + } + sub, ok = rawMap["check"] + if !ok { + goto AFTER_FIX + } + if err := FixupCheckType(sub); err != nil { + return nil, err + } +AFTER_FIX: + var md mapstructure.Metadata + var result ServiceDefinition + msdec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + Metadata: &md, + Result: &result, + }) + if err != nil { + return nil, err + } + if err := msdec.Decode(raw); err != nil { + return nil, err + } + return &result, nil +} + +func FixupCheckType(raw interface{}) error { + // Handle decoding of time durations + rawMap, ok := raw.(map[string]interface{}) + if !ok { + return nil + } + if ttl, ok := rawMap["ttl"]; ok { + ttlS, ok := ttl.(string) + if ok { + if dur, err := time.ParseDuration(ttlS); err != nil { + return err + } else { + rawMap["ttl"] = dur + } + } + } + if interval, ok := rawMap["interval"]; ok { + intervalS, ok := interval.(string) + if ok { + if dur, err := time.ParseDuration(intervalS); err != nil { + return err + } else { + rawMap["interval"] = dur + } + } + } + return nil +} + +// DecodeCheckDefinition is used to decode a check definition +func DecodeCheckDefinition(raw interface{}) (*CheckDefinition, error) { + if err := FixupCheckType(raw); err != nil { + return nil, err + } + var md mapstructure.Metadata + var result CheckDefinition + msdec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + Metadata: &md, + Result: &result, + }) + if err != nil { + return nil, err + } + if err := msdec.Decode(raw); err != nil { + return nil, err + } + return &result, nil +} + // MergeConfig merges two configurations together to make a single new // configuration. func MergeConfig(a, b *Config) *Config { @@ -210,6 +310,12 @@ func MergeConfig(a, b *Config) *Config { if b.SkipLeaveOnInt == true { result.SkipLeaveOnInt = true } + if b.Checks != nil { + result.Checks = append(result.Checks, b.Checks...) + } + if b.Services != nil { + result.Services = append(result.Services, b.Services...) + } return &result } diff --git a/command/agent/structs.go b/command/agent/structs.go index 606e1cae37..876ce4f8d8 100644 --- a/command/agent/structs.go +++ b/command/agent/structs.go @@ -1,25 +1,56 @@ package agent +import ( + "github.com/hashicorp/consul/consul/structs" +) + // ServiceDefinition is used to JSON decode the Service definitions type ServiceDefinition struct { ID string Name string Tag string Port int - Check *CheckType + Check CheckType +} + +func (s *ServiceDefinition) NodeService() *structs.NodeService { + ns := &structs.NodeService{ + ID: s.ID, + Service: s.Name, + Tag: s.Tag, + Port: s.Port, + } + if ns.ID == "" && ns.Service != "" { + ns.ID = ns.Service + } + return ns +} + +func (s *ServiceDefinition) CheckType() *CheckType { + if s.Check.Script == "" && s.Check.Interval == 0 && s.Check.TTL == 0 { + return nil + } + return &s.Check } // ChecKDefinition is used to JSON decode the Check definitions type CheckDefinition struct { - ID string - Name string - Notes string - CheckType + ID string + Name string + Notes string + CheckType `mapstructure:",squash"` } -// UnionDefinition is used to decode when we don't know if -// we are being given a ServiceDefinition or a CheckDefinition -type UnionDefinition struct { - Service *ServiceDefinition - Check *CheckDefinition +func (c *CheckDefinition) HealthCheck(node string) *structs.HealthCheck { + health := &structs.HealthCheck{ + Node: node, + CheckID: c.ID, + Name: c.Name, + Status: structs.HealthUnknown, + Notes: c.Notes, + } + if health.CheckID == "" && health.Name != "" { + health.CheckID = health.Name + } + return health }