func TestSensorObserve(t *testing.T) { cmd1, _ := commands.NewCommand("./testdata/test.sh doStuff --debug", "1s") sensor := &Sensor{checkCmd: cmd1} if val, err := sensor.observe(); err != nil { t.Fatalf("Unexpected error from sensor check: %s", err) } else if val != "Running doStuff with args: --debug\n" { t.Fatalf("Unexpected output from sensor check: %s", val) } // Ensure we can run it more than once if _, err := sensor.observe(); err != nil { t.Fatalf("Unexpected error from sensor check (x2): %s", err) } // Ensure bad commands return error cmd2, _ := commands.NewCommand("./testdata/doesNotExist.sh", "0") sensor = &Sensor{checkCmd: cmd2} if val, err := sensor.observe(); err == nil { t.Fatalf("Expected error from sensor check but got %s", val) } else if err.Error() != "fork/exec ./testdata/doesNotExist.sh: no such file or directory" { t.Fatalf("Unexpected error from invalid sensor check: %s", err) } }
// NewBackends creates a new backend from a raw config structure func NewBackends(raw []interface{}, disc discovery.ServiceBackend) ([]*Backend, error) { if raw == nil { return []*Backend{}, nil } var backends []*Backend if err := utils.DecodeRaw(raw, &backends); err != nil { return nil, fmt.Errorf("Backend configuration error: %v", err) } for _, b := range backends { if err := utils.ValidateServiceName(b.Name); err != nil { return nil, err } if b.OnChangeExec == nil { return nil, fmt.Errorf("`onChange` is required in backend %s", b.Name) } cmd, err := commands.NewCommand(b.OnChangeExec, b.Timeout) if err != nil { return nil, fmt.Errorf("Could not parse `onChange` in backend %s: %s", b.Name, err) } cmd.Name = fmt.Sprintf("%s.health", b.Name) b.onChangeCmd = cmd if b.Poll < 1 { return nil, fmt.Errorf("`poll` must be > 0 in backend %s", b.Name) } b.onChangeCmd = cmd b.discoveryService = disc } return backends, nil }
func parseTask(task *Task) error { if task.Command == nil { return fmt.Errorf("Task did not provide a command") } freq, err := utils.ParseDuration(task.Frequency) if err != nil { return fmt.Errorf("Unable to parse frequency %s: %v", task.Frequency, err) } if freq < time.Millisecond { return fmt.Errorf("Frequency %v cannot be less that %v", freq, taskMinDuration) } task.freqDuration = freq if task.Timeout == "" { task.Timeout = task.Frequency } cmd, err := commands.NewCommand(task.Command, task.Timeout) if cmd.TimeoutDuration < taskMinDuration { return fmt.Errorf("Timeout %v cannot be less that %v", cmd.TimeoutDuration, taskMinDuration) } cmd.Name = fmt.Sprintf("task[%s]", task.Name) task.cmd = cmd return nil }
func TestHealthCheckBad(t *testing.T) { cmd1, _ := commands.NewCommand("./testdata/test.sh failStuff", "") service := &Service{ healthCheckCmd: cmd1, } if err := service.CheckHealth(); err == nil { t.Errorf("Expected error from CheckHealth but got nil") } }
// NewSensors creates new sensors from a raw config func NewSensors(raw []interface{}) ([]*Sensor, error) { var sensors []*Sensor if err := utils.DecodeRaw(raw, &sensors); err != nil { return nil, fmt.Errorf("Sensor configuration error: %v", err) } for _, s := range sensors { check, err := commands.NewCommand(s.CheckExec, s.Timeout) if err != nil { return nil, fmt.Errorf("could not parse check in sensor %s: %s", s.Name, err) } check.Name = fmt.Sprintf("%s.sensor", s.Name) s.checkCmd = check // the prometheus client lib's API here is baffling... they don't expose // an interface or embed their Opts type in each of the Opts "subtypes", // so we can't share the initialization. switch { case s.Type == "counter": s.collector = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: s.Namespace, Subsystem: s.Subsystem, Name: s.Name, Help: s.Help, }) case s.Type == "gauge": s.collector = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: s.Namespace, Subsystem: s.Subsystem, Name: s.Name, Help: s.Help, }) case s.Type == "histogram": s.collector = prometheus.NewHistogram(prometheus.HistogramOpts{ Namespace: s.Namespace, Subsystem: s.Subsystem, Name: s.Name, Help: s.Help, }) case s.Type == "summary": s.collector = prometheus.NewSummary(prometheus.SummaryOpts{ Namespace: s.Namespace, Subsystem: s.Subsystem, Name: s.Name, Help: s.Help, }) default: return nil, fmt.Errorf("invalid sensor type: %s", s.Type) } // we're going to unregister before every attempt to register // so that we can reload config prometheus.Unregister(s.collector) if err := prometheus.Register(s.collector); err != nil { return nil, err } } return sensors, nil }
func getSignalTestConfig() *App { service, _ := services.NewService( "test-service", 1, 1, 1, nil, nil, nil, &NoopServiceBackend{}) app := EmptyApp() cmd, _ := commands.NewCommand([]string{ "./testdata/test.sh", "interruptSleep"}, "0") app.Command = cmd app.StopTimeout = 5 app.Services = []*services.Service{service} return app }
func TestHealthCheck(t *testing.T) { cmd1, _ := commands.NewCommand("./testdata/test.sh doStuff --debug", "1s") service := &Service{ healthCheckCmd: cmd1, } if err := service.CheckHealth(); err != nil { t.Errorf("Unexpected error CheckHealth: %s", err) } // Ensure we can run it more than once if err := service.CheckHealth(); err != nil { t.Errorf("Unexpected error CheckHealth (x2): %s", err) } }
func TestOnChangeCmd(t *testing.T) { cmd1, _ := commands.NewCommand("./testdata/test.sh doStuff --debug", "1s") backend := &Backend{ onChangeCmd: cmd1, } if err := backend.OnChange(); err != nil { t.Errorf("Unexpected error OnChange: %s", err) } // Ensure we can run it more than once if err := backend.OnChange(); err != nil { t.Errorf("Unexpected error OnChange (x2): %s", err) } }
// Run starts the application and blocks until finished func (a *App) Run() { // Set up handlers for polling and to accept signal interrupts if 1 == os.Getpid() { reapChildren() } args := getArgs(flag.Args()) cmd, err := commands.NewCommand(args, "0") if err != nil { log.Errorf("Unable to parse command arguments: %v", err) } cmd.Name = "APP" a.Command = cmd a.handleSignals() if a.PreStartCmd != nil { // Run the preStart handler, if any, and exit if it returns an error fields := log.Fields{"process": "PreStart"} if code, err := commands.RunAndWait(a.PreStartCmd, fields); err != nil { os.Exit(code) } } a.handleCoprocesses() a.handlePolling() if a.Command != nil { // Run our main application and capture its stdout/stderr. // This will block until the main application exits and then os.Exit // with the exit code of that application. code, err := commands.RunAndWait(a.Command, nil) if err != nil { log.Println(err) } // Run the PostStop handler, if any, and exit if it returns an error if a.PostStopCmd != nil { fields := log.Fields{"process": "PostStop"} if postStopCode, err := commands.RunAndWait(a.PostStopCmd, fields); err != nil { os.Exit(postStopCode) } } os.Exit(code) } // block forever, as we're polling in the two polling functions and // did not os.Exit by waiting on an external application. select {} }
func parseCoprocess(coprocess *Coprocess) error { if coprocess.Command == nil { return fmt.Errorf("Coprocess did not provide a command") } cmd, err := commands.NewCommand(coprocess.Command, "0") if err != nil { return fmt.Errorf("Could not parse `coprocess` command %s: %s", coprocess.Name, err) } if coprocess.Name == "" { args := append([]string{cmd.Exec}, cmd.Args...) coprocess.Name = strings.Join(args, " ") } cmd.Name = fmt.Sprintf("coprocess[%s]", coprocess.Name) coprocess.cmd = cmd return parseCoprocessRestarts(coprocess) }
func TestSensorPollAction(t *testing.T) { testServer := httptest.NewServer(prometheus.UninstrumentedHandler()) defer testServer.Close() cmd, _ := commands.NewCommand("./testdata/test.sh measureStuff", "0") sensor := &Sensor{ Type: "counter", checkCmd: cmd, collector: prometheus.NewCounter(prometheus.CounterOpts{ Namespace: "telemetry", Subsystem: "sensors", Name: "TestSensorPollAction", Help: "help", })} prometheus.MustRegister(sensor.collector) sensor.PollAction() resp := getFromTestServer(t, testServer) if strings.Count(resp, "telemetry_sensors_TestSensorPollAction 42") != 1 { t.Fatalf("Failed to get match for sensor in response: %s", resp) } }
func parseService(s *Service, disc discovery.ServiceBackend) error { if err := utils.ValidateServiceName(s.Name); err != nil { return err } hostname, _ := os.Hostname() s.ID = fmt.Sprintf("%s-%s", s.Name, hostname) s.discoveryService = disc if s.Poll < 1 { return fmt.Errorf("`poll` must be > 0 in service %s", s.Name) } if s.TTL < 1 { return fmt.Errorf("`ttl` must be > 0 in service %s", s.Name) } if s.Port < 1 { return fmt.Errorf("`port` must be > 0 in service %s", s.Name) } // if the HealthCheckExec is nil then we'll have no health check // command; this is useful for the telemetry service if s.HealthCheckExec != nil { cmd, err := commands.NewCommand(s.HealthCheckExec, s.Timeout) if err != nil { return fmt.Errorf("Could not parse `health` in service %s: %s", s.Name, err) } cmd.Name = fmt.Sprintf("%s.health", s.Name) s.healthCheckCmd = cmd } interfaces, ifaceErr := utils.ToStringArray(s.Interfaces) if ifaceErr != nil { return ifaceErr } ipAddress, err := utils.GetIP(interfaces) if err != nil { return err } s.IPAddress = ipAddress var consulExtras *discovery.ConsulExtras if s.ConsulConfig != nil { if s.ConsulConfig.DeregisterCriticalServiceAfter != "" { if _, err := time.ParseDuration(s.ConsulConfig.DeregisterCriticalServiceAfter); err != nil { return fmt.Errorf("Could not parse consul `deregisterCriticalServiceAfter` in service %s: %s", s.Name, err) } } consulExtras = &discovery.ConsulExtras{ DeregisterCriticalServiceAfter: s.ConsulConfig.DeregisterCriticalServiceAfter, EnableTagOverride: s.ConsulConfig.EnableTagOverride, } } s.definition = &discovery.ServiceDefinition{ ID: s.ID, Name: s.Name, Port: s.Port, TTL: s.TTL, Tags: s.Tags, IPAddress: s.IPAddress, ConsulExtras: consulExtras, } return nil }
func TestSensorBadRecord(t *testing.T) { cmd, _ := commands.NewCommand("./testdata/test.sh doStuff --debug", "0") sensor := &Sensor{checkCmd: cmd} sensor.PollAction() // logs but no crash }
func TestSensorBadPollAction(t *testing.T) { cmd, _ := commands.NewCommand("./testdata/doesNotExist.sh", "0") sensor := &Sensor{checkCmd: cmd} sensor.PollAction() // logs but no crash }