Refactor test package and some field types
Fairly big test refactor and changing some of the fields from pointers
This commit is contained in:
parent
a0a6b8199a
commit
e0af17a599
@ -76,7 +76,7 @@ Each monitor allows the following configuration:
|
|||||||
|`alert_down`|A list of Alerts to be triggered when the monitor is in a "down" state|
|
|`alert_down`|A list of Alerts to be triggered when the monitor is in a "down" state|
|
||||||
|`alert_up`|A list of Alerts to be triggered when the monitor moves to an "up" state|
|
|`alert_up`|A list of Alerts to be triggered when the monitor moves to an "up" state|
|
||||||
|`check_interval`|The interval at which this monitor should be checked. This must be greater than the global `check_interval` value|
|
|`check_interval`|The interval at which this monitor should be checked. This must be greater than the global `check_interval` value|
|
||||||
|`alert_after`|Allows specifying the number of failed checks before an alert should be triggered|
|
|`alert_after`|Allows specifying the number of failed checks before an alert should be triggered. A value of 1 will start sending alerts after the first failure.|
|
||||||
|`alert_every`|Allows specifying how often an alert should be retriggered. There are a few magic numbers here. Defaults to `-1` for an exponential backoff. Setting to `0` disables re-alerting. Positive values will allow retriggering after the specified number of checks|
|
|`alert_every`|Allows specifying how often an alert should be retriggered. There are a few magic numbers here. Defaults to `-1` for an exponential backoff. Setting to `0` disables re-alerting. Positive values will allow retriggering after the specified number of checks|
|
||||||
|
|
||||||
### Alerts
|
### Alerts
|
||||||
|
11
alert.go
11
alert.go
@ -162,14 +162,3 @@ func (alert Alert) Send(notice AlertNotice) (outputStr string, err error) {
|
|||||||
|
|
||||||
return outputStr, err
|
return outputStr, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLogAlert creates an alert that does basic logging using echo
|
|
||||||
func NewLogAlert() *Alert {
|
|
||||||
return &Alert{
|
|
||||||
Name: "log",
|
|
||||||
Command: []string{
|
|
||||||
"echo",
|
|
||||||
"{{.MonitorName}} {{if .IsUp}}has recovered{{else}}check has failed {{.FailureCount}} times{{end}}",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
package main
|
package main_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
m "git.iamthefij.com/iamthefij/minitor-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAlertIsValid(t *testing.T) {
|
func TestAlertIsValid(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
alert Alert
|
alert m.Alert
|
||||||
expected bool
|
expected bool
|
||||||
name string
|
name string
|
||||||
}{
|
}{
|
||||||
{Alert{Command: []string{"echo", "test"}}, true, "Command only"},
|
{m.Alert{Command: []string{"echo", "test"}}, true, "Command only"},
|
||||||
{Alert{ShellCommand: "echo test"}, true, "CommandShell only"},
|
{m.Alert{ShellCommand: "echo test"}, true, "CommandShell only"},
|
||||||
{Alert{}, false, "No commands"},
|
{m.Alert{}, false, "No commands"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
@ -31,56 +33,40 @@ func TestAlertIsValid(t *testing.T) {
|
|||||||
|
|
||||||
func TestAlertSend(t *testing.T) {
|
func TestAlertSend(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
alert Alert
|
alert m.Alert
|
||||||
notice AlertNotice
|
notice m.AlertNotice
|
||||||
expectedOutput string
|
expectedOutput string
|
||||||
expectErr bool
|
expectErr bool
|
||||||
name string
|
name string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Alert{Command: []string{"echo", "{{.MonitorName}}"}},
|
m.Alert{Command: []string{"echo", "{{.MonitorName}}"}},
|
||||||
AlertNotice{MonitorName: "test"},
|
m.AlertNotice{MonitorName: "test"},
|
||||||
"test\n",
|
"test\n",
|
||||||
false,
|
false,
|
||||||
"Command with template",
|
"Command with template",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Alert{ShellCommand: "echo {{.MonitorName}}"},
|
m.Alert{ShellCommand: "echo {{.MonitorName}}"},
|
||||||
AlertNotice{MonitorName: "test"},
|
m.AlertNotice{MonitorName: "test"},
|
||||||
"test\n",
|
"test\n",
|
||||||
false,
|
false,
|
||||||
"Command shell with template",
|
"Command shell with template",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Alert{Command: []string{"echo", "{{.Bad}}"}},
|
m.Alert{Command: []string{"echo", "{{.Bad}}"}},
|
||||||
AlertNotice{MonitorName: "test"},
|
m.AlertNotice{MonitorName: "test"},
|
||||||
"",
|
"",
|
||||||
true,
|
true,
|
||||||
"Command with bad template",
|
"Command with bad template",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Alert{ShellCommand: "echo {{.Bad}}"},
|
m.Alert{ShellCommand: "echo {{.Bad}}"},
|
||||||
AlertNotice{MonitorName: "test"},
|
m.AlertNotice{MonitorName: "test"},
|
||||||
"",
|
"",
|
||||||
true,
|
true,
|
||||||
"Command shell with bad template",
|
"Command shell with bad template",
|
||||||
},
|
},
|
||||||
// Test default log alert down
|
|
||||||
{
|
|
||||||
*NewLogAlert(),
|
|
||||||
AlertNotice{MonitorName: "Test", FailureCount: 1, IsUp: false},
|
|
||||||
"Test check has failed 1 times\n",
|
|
||||||
false,
|
|
||||||
"Default log alert down",
|
|
||||||
},
|
|
||||||
// Test default log alert up
|
|
||||||
{
|
|
||||||
*NewLogAlert(),
|
|
||||||
AlertNotice{MonitorName: "Test", IsUp: true},
|
|
||||||
"Test has recovered\n",
|
|
||||||
false,
|
|
||||||
"Default log alert up",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
@ -109,8 +95,8 @@ func TestAlertSend(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAlertSendNoTemplates(t *testing.T) {
|
func TestAlertSendNoTemplates(t *testing.T) {
|
||||||
alert := Alert{}
|
alert := m.Alert{}
|
||||||
notice := AlertNotice{}
|
notice := m.AlertNotice{}
|
||||||
|
|
||||||
output, err := alert.Send(notice)
|
output, err := alert.Send(notice)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -120,13 +106,13 @@ func TestAlertSendNoTemplates(t *testing.T) {
|
|||||||
|
|
||||||
func TestAlertBuildTemplate(t *testing.T) {
|
func TestAlertBuildTemplate(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
alert Alert
|
alert m.Alert
|
||||||
expectErr bool
|
expectErr bool
|
||||||
name string
|
name string
|
||||||
}{
|
}{
|
||||||
{Alert{Command: []string{"echo", "test"}}, false, "Command only"},
|
{m.Alert{Command: []string{"echo", "test"}}, false, "Command only"},
|
||||||
{Alert{ShellCommand: "echo test"}, false, "CommandShell only"},
|
{m.Alert{ShellCommand: "echo test"}, false, "CommandShell only"},
|
||||||
{Alert{}, true, "No commands"},
|
{m.Alert{}, true, "No commands"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
|
@ -113,6 +113,8 @@ func (config *Config) Init() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, monitor := range config.Monitors {
|
for _, monitor := range config.Monitors {
|
||||||
|
// TODO: Move this to a Monitor.Init() method
|
||||||
|
|
||||||
// Parse the check_interval string into a time.Duration
|
// Parse the check_interval string into a time.Duration
|
||||||
if monitor.CheckIntervalStr != nil {
|
if monitor.CheckIntervalStr != nil {
|
||||||
monitor.CheckInterval, err = time.ParseDuration(*monitor.CheckIntervalStr)
|
monitor.CheckInterval, err = time.ParseDuration(*monitor.CheckIntervalStr)
|
||||||
@ -122,8 +124,10 @@ func (config *Config) Init() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set default values for monitor alerts
|
// Set default values for monitor alerts
|
||||||
if monitor.AlertAfter == nil {
|
if monitor.AlertAfter == 0 && config.DefaultAlertAfter != nil {
|
||||||
monitor.AlertAfter = config.DefaultAlertAfter
|
monitor.AlertAfter = *config.DefaultAlertAfter
|
||||||
|
} else if monitor.AlertAfter == 0 {
|
||||||
|
monitor.AlertAfter = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if monitor.AlertEvery == nil {
|
if monitor.AlertEvery == nil {
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package main
|
package main_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
m "git.iamthefij.com/iamthefij/minitor-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLoadConfig(t *testing.T) {
|
func TestLoadConfig(t *testing.T) {
|
||||||
@ -11,12 +13,11 @@ func TestLoadConfig(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
}{
|
}{
|
||||||
{"./test/does-not-exist", true, "Invalid config path"},
|
{"./test/does-not-exist", true, "Invalid config path"},
|
||||||
// {"./test/invalid-config-missing-alerts.yml", true, "Invalid config missing alerts"},
|
{"./test/invalid-config-missing-alerts.hcl", true, "Invalid config missing alerts"},
|
||||||
// {"./test/invalid-config-type.yml", true, "Invalid config type for key"},
|
{"./test/invalid-config-type.hcl", true, "Invalid config type for key"},
|
||||||
// {"./test/invalid-config-unknown-alert.yml", true, "Invalid config unknown alert"},
|
{"./test/invalid-config-unknown-alert.hcl", true, "Invalid config unknown alert"},
|
||||||
// {"./test/valid-config-default-values.yml", false, "Valid config file with default values"},
|
{"./test/valid-config-default-values.hcl", false, "Valid config file with default values"},
|
||||||
{"./test/valid-config.hcl", false, "Valid config file"},
|
{"./test/valid-config.hcl", false, "Valid config file"},
|
||||||
// {"./test/valid-default-log-alert.yml", true, "Invalid config file no log alert"},
|
|
||||||
}
|
}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
c := c
|
c := c
|
||||||
@ -24,7 +25,7 @@ func TestLoadConfig(t *testing.T) {
|
|||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
_, err := LoadConfig(c.configPath)
|
_, err := m.LoadConfig(c.configPath)
|
||||||
hasErr := (err != nil)
|
hasErr := (err != nil)
|
||||||
|
|
||||||
if hasErr != c.expectErr {
|
if hasErr != c.expectErr {
|
||||||
@ -39,7 +40,7 @@ func TestLoadConfig(t *testing.T) {
|
|||||||
func TestMultiLineConfig(t *testing.T) {
|
func TestMultiLineConfig(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
config, err := LoadConfig("./test/valid-verify-multi-line.hcl")
|
config, err := m.LoadConfig("./test/valid-verify-multi-line.hcl")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("TestMultiLineConfig(load), expected=no_error actual=%v", err)
|
t.Fatalf("TestMultiLineConfig(load), expected=no_error actual=%v", err)
|
||||||
}
|
}
|
||||||
@ -87,7 +88,7 @@ func TestMultiLineConfig(t *testing.T) {
|
|||||||
t.Logf("bytes actual =%v", []byte(actual))
|
t.Logf("bytes actual =%v", []byte(actual))
|
||||||
}
|
}
|
||||||
|
|
||||||
actual, err = alert.Send(AlertNotice{})
|
actual, err = alert.Send(m.AlertNotice{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Execution of alert failed")
|
t.Errorf("Execution of alert failed")
|
||||||
}
|
}
|
||||||
|
12
main.go
12
main.go
@ -24,7 +24,7 @@ var (
|
|||||||
errUnknownAlert = errors.New("unknown alert")
|
errUnknownAlert = errors.New("unknown alert")
|
||||||
)
|
)
|
||||||
|
|
||||||
func sendAlerts(config *Config, monitor *Monitor, alertNotice *AlertNotice) error {
|
func SendAlerts(config *Config, monitor *Monitor, alertNotice *AlertNotice) error {
|
||||||
slog.Debugf("Received an alert notice from %s", alertNotice.MonitorName)
|
slog.Debugf("Received an alert notice from %s", alertNotice.MonitorName)
|
||||||
alertNames := monitor.GetAlertNames(alertNotice.IsUp)
|
alertNames := monitor.GetAlertNames(alertNotice.IsUp)
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ func sendAlerts(config *Config, monitor *Monitor, alertNotice *AlertNotice) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkMonitors(config *Config) error {
|
func CheckMonitors(config *Config) error {
|
||||||
// TODO: Run this in goroutines and capture exceptions
|
// TODO: Run this in goroutines and capture exceptions
|
||||||
for _, monitor := range config.Monitors {
|
for _, monitor := range config.Monitors {
|
||||||
if monitor.ShouldCheck() {
|
if monitor.ShouldCheck() {
|
||||||
@ -77,7 +77,7 @@ func checkMonitors(config *Config) error {
|
|||||||
Metrics.CountCheck(monitor.Name, success, monitor.LastCheckMilliseconds(), hasAlert)
|
Metrics.CountCheck(monitor.Name, success, monitor.LastCheckMilliseconds(), hasAlert)
|
||||||
|
|
||||||
if alertNotice != nil {
|
if alertNotice != nil {
|
||||||
err := sendAlerts(config, monitor, alertNotice)
|
err := SendAlerts(config, monitor, alertNotice)
|
||||||
// If there was an error in sending an alert, exit early and bubble it up
|
// If there was an error in sending an alert, exit early and bubble it up
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -89,7 +89,7 @@ func checkMonitors(config *Config) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendStartupAlerts(config *Config, alertNames []string) error {
|
func SendStartupAlerts(config *Config, alertNames []string) error {
|
||||||
for _, alertName := range alertNames {
|
for _, alertName := range alertNames {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@ -148,14 +148,14 @@ func main() {
|
|||||||
if *startupAlerts != "" {
|
if *startupAlerts != "" {
|
||||||
alertNames := strings.Split(*startupAlerts, ",")
|
alertNames := strings.Split(*startupAlerts, ",")
|
||||||
|
|
||||||
err = sendStartupAlerts(&config, alertNames)
|
err = SendStartupAlerts(&config, alertNames)
|
||||||
|
|
||||||
slog.OnErrPanicf(err, "Error running startup alerts")
|
slog.OnErrPanicf(err, "Error running startup alerts")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start main loop
|
// Start main loop
|
||||||
for {
|
for {
|
||||||
err = checkMonitors(&config)
|
err = CheckMonitors(&config)
|
||||||
slog.OnErrPanicf(err, "Error checking monitors")
|
slog.OnErrPanicf(err, "Error checking monitors")
|
||||||
|
|
||||||
time.Sleep(config.CheckInterval)
|
time.Sleep(config.CheckInterval)
|
||||||
|
161
main_test.go
161
main_test.go
@ -1,125 +1,92 @@
|
|||||||
package main
|
package main_test
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
m "git.iamthefij.com/iamthefij/minitor-go"
|
||||||
|
)
|
||||||
|
|
||||||
func Ptr[T any](v T) *T {
|
func Ptr[T any](v T) *T {
|
||||||
return &v
|
return &v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCheckConfig tests the checkConfig function
|
||||||
|
// It also tests results for potentially invalid configuration. For example, no alerts
|
||||||
func TestCheckMonitors(t *testing.T) {
|
func TestCheckMonitors(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
config Config
|
config m.Config
|
||||||
expectErr bool
|
expectFailureError bool
|
||||||
name string
|
expectRecoverError bool
|
||||||
|
name string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
config: Config{
|
config: m.Config{
|
||||||
CheckIntervalStr: "1s",
|
CheckIntervalStr: "1s",
|
||||||
Monitors: []*Monitor{
|
Monitors: []*m.Monitor{
|
||||||
{
|
{
|
||||||
Name: "Success",
|
Name: "Success",
|
||||||
Command: []string{"true"},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectErr: false,
|
expectFailureError: false,
|
||||||
name: "Monitor success, no alerts",
|
expectRecoverError: false,
|
||||||
|
name: "No alerts",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config: Config{
|
config: m.Config{
|
||||||
CheckIntervalStr: "1s",
|
CheckIntervalStr: "1s",
|
||||||
Monitors: []*Monitor{
|
Monitors: []*m.Monitor{
|
||||||
{
|
{
|
||||||
Name: "Failure",
|
Name: "Failure",
|
||||||
Command: []string{"false"},
|
|
||||||
AlertAfter: Ptr(1),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
name: "Monitor failure, no alerts",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
config: Config{
|
|
||||||
CheckIntervalStr: "1s",
|
|
||||||
Monitors: []*Monitor{
|
|
||||||
{
|
|
||||||
Name: "Success",
|
|
||||||
Command: []string{"ls"},
|
|
||||||
alertCount: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
name: "Monitor recovery, no alerts",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
config: Config{
|
|
||||||
CheckIntervalStr: "1s",
|
|
||||||
Monitors: []*Monitor{
|
|
||||||
{
|
|
||||||
Name: "Failure",
|
|
||||||
Command: []string{"false"},
|
|
||||||
AlertDown: []string{"unknown"},
|
AlertDown: []string{"unknown"},
|
||||||
AlertAfter: Ptr(1),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
name: "Monitor failure, unknown alerts",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
config: Config{
|
|
||||||
CheckIntervalStr: "1s",
|
|
||||||
Monitors: []*Monitor{
|
|
||||||
{
|
|
||||||
Name: "Success",
|
|
||||||
Command: []string{"true"},
|
|
||||||
AlertUp: []string{"unknown"},
|
AlertUp: []string{"unknown"},
|
||||||
alertCount: 1,
|
AlertAfter: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectErr: true,
|
expectFailureError: true,
|
||||||
name: "Monitor recovery, unknown alerts",
|
expectRecoverError: true,
|
||||||
|
name: "Unknown alerts",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config: Config{
|
config: m.Config{
|
||||||
CheckIntervalStr: "1s",
|
CheckIntervalStr: "1s",
|
||||||
Monitors: []*Monitor{
|
Monitors: []*m.Monitor{
|
||||||
{
|
{
|
||||||
Name: "Failure",
|
Name: "Failure",
|
||||||
Command: []string{"false"},
|
|
||||||
AlertDown: []string{"good"},
|
AlertDown: []string{"good"},
|
||||||
AlertAfter: Ptr(1),
|
AlertUp: []string{"good"},
|
||||||
|
AlertAfter: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Alerts: []*Alert{{
|
Alerts: []*m.Alert{{
|
||||||
Name: "good",
|
Name: "good",
|
||||||
Command: []string{"true"},
|
Command: []string{"true"},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
expectErr: false,
|
expectFailureError: false,
|
||||||
name: "Monitor failure, successful alert",
|
expectRecoverError: false,
|
||||||
|
name: "Successful alert",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config: Config{
|
config: m.Config{
|
||||||
CheckIntervalStr: "1s",
|
CheckIntervalStr: "1s",
|
||||||
Monitors: []*Monitor{
|
Monitors: []*m.Monitor{
|
||||||
{
|
{
|
||||||
Name: "Failure",
|
Name: "Failure",
|
||||||
Command: []string{"false"},
|
|
||||||
AlertDown: []string{"bad"},
|
AlertDown: []string{"bad"},
|
||||||
AlertAfter: Ptr(1),
|
AlertUp: []string{"bad"},
|
||||||
|
AlertAfter: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Alerts: []*Alert{{
|
Alerts: []*m.Alert{{
|
||||||
Name: "bad",
|
Name: "bad",
|
||||||
Command: []string{"false"},
|
Command: []string{"false"},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
expectErr: true,
|
expectFailureError: true,
|
||||||
name: "Monitor failure, bad alert",
|
expectRecoverError: true,
|
||||||
|
name: "Failing alert",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,11 +101,25 @@ func TestCheckMonitors(t *testing.T) {
|
|||||||
t.Errorf("checkMonitors(%s): unexpected error reading config: %v", c.name, err)
|
t.Errorf("checkMonitors(%s): unexpected error reading config: %v", c.name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = checkMonitors(&c.config)
|
for _, check := range []struct {
|
||||||
if err == nil && c.expectErr {
|
shellCmd string
|
||||||
t.Errorf("checkMonitors(%s): Expected panic, the code did not panic", c.name)
|
name string
|
||||||
} else if err != nil && !c.expectErr {
|
expectErr bool
|
||||||
t.Errorf("checkMonitors(%s): Did not expect an error, but we got one anyway: %v", c.name, err)
|
}{
|
||||||
|
{"false", "Failure", c.expectFailureError}, {"true", "Success", c.expectRecoverError},
|
||||||
|
} {
|
||||||
|
// Set the shell command for this check
|
||||||
|
c.config.Monitors[0].ShellCommand = check.shellCmd
|
||||||
|
|
||||||
|
// Run the check
|
||||||
|
err = m.CheckMonitors(&c.config)
|
||||||
|
|
||||||
|
// Check the results
|
||||||
|
if err == nil && check.expectErr {
|
||||||
|
t.Errorf("checkMonitors(%s:%s): Expected error, the code did not error", c.name, check.name)
|
||||||
|
} else if err != nil && !check.expectErr {
|
||||||
|
t.Errorf("checkMonitors(%s:%s): Did not expect an error, but we got one anyway: %v", c.name, check.name, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -146,26 +127,23 @@ func TestCheckMonitors(t *testing.T) {
|
|||||||
|
|
||||||
func TestFirstRunAlerts(t *testing.T) {
|
func TestFirstRunAlerts(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
config Config
|
config m.Config
|
||||||
expectErr bool
|
expectErr bool
|
||||||
startupAlerts []string
|
startupAlerts []string
|
||||||
name string
|
name string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
config: Config{},
|
config: m.Config{
|
||||||
expectErr: false,
|
CheckIntervalStr: "1s",
|
||||||
startupAlerts: []string{},
|
},
|
||||||
name: "Empty",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
config: Config{},
|
|
||||||
expectErr: true,
|
expectErr: true,
|
||||||
startupAlerts: []string{"missing"},
|
startupAlerts: []string{"missing"},
|
||||||
name: "Unknown",
|
name: "Unknown",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config: Config{
|
config: m.Config{
|
||||||
Alerts: []*Alert{
|
CheckIntervalStr: "1s",
|
||||||
|
Alerts: []*m.Alert{
|
||||||
{
|
{
|
||||||
Name: "good",
|
Name: "good",
|
||||||
Command: []string{"true"},
|
Command: []string{"true"},
|
||||||
@ -177,8 +155,9 @@ func TestFirstRunAlerts(t *testing.T) {
|
|||||||
name: "Successful alert",
|
name: "Successful alert",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config: Config{
|
config: m.Config{
|
||||||
Alerts: []*Alert{
|
CheckIntervalStr: "1s",
|
||||||
|
Alerts: []*m.Alert{
|
||||||
{
|
{
|
||||||
Name: "bad",
|
Name: "bad",
|
||||||
Command: []string{"false"},
|
Command: []string{"false"},
|
||||||
@ -202,7 +181,7 @@ func TestFirstRunAlerts(t *testing.T) {
|
|||||||
t.Errorf("sendFirstRunAlerts(%s): unexpected error reading config: %v", c.name, err)
|
t.Errorf("sendFirstRunAlerts(%s): unexpected error reading config: %v", c.name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = sendStartupAlerts(&c.config, c.startupAlerts)
|
err = m.SendStartupAlerts(&c.config, c.startupAlerts)
|
||||||
if err == nil && c.expectErr {
|
if err == nil && c.expectErr {
|
||||||
t.Errorf("sendFirstRunAlerts(%s): Expected error, the code did not error", c.name)
|
t.Errorf("sendFirstRunAlerts(%s): Expected error, the code did not error", c.name)
|
||||||
} else if err != nil && !c.expectErr {
|
} else if err != nil && !c.expectErr {
|
||||||
|
24
monitor.go
24
monitor.go
@ -15,7 +15,7 @@ type Monitor struct { //nolint:maligned
|
|||||||
CheckInterval time.Duration
|
CheckInterval time.Duration
|
||||||
|
|
||||||
Name string `hcl:"name,label"`
|
Name string `hcl:"name,label"`
|
||||||
AlertAfter *int `hcl:"alert_after,optional"`
|
AlertAfter int `hcl:"alert_after,optional"`
|
||||||
AlertEvery *int `hcl:"alert_every,optional"`
|
AlertEvery *int `hcl:"alert_every,optional"`
|
||||||
AlertDown []string `hcl:"alert_down,optional"`
|
AlertDown []string `hcl:"alert_down,optional"`
|
||||||
AlertUp []string `hcl:"alert_up,optional"`
|
AlertUp []string `hcl:"alert_up,optional"`
|
||||||
@ -34,9 +34,10 @@ type Monitor struct { //nolint:maligned
|
|||||||
// IsValid returns a boolean indicating if the Monitor has been correctly
|
// IsValid returns a boolean indicating if the Monitor has been correctly
|
||||||
// configured
|
// configured
|
||||||
func (monitor Monitor) IsValid() bool {
|
func (monitor Monitor) IsValid() bool {
|
||||||
|
// TODO: Refactor and return an error containing more information on what was invalid
|
||||||
hasCommand := len(monitor.Command) > 0
|
hasCommand := len(monitor.Command) > 0
|
||||||
hasShellCommand := monitor.ShellCommand != ""
|
hasShellCommand := monitor.ShellCommand != ""
|
||||||
hasValidAlertAfter := monitor.GetAlertAfter() > 0
|
hasValidAlertAfter := monitor.AlertAfter > 0
|
||||||
hasAlertDown := len(monitor.AlertDown) > 0
|
hasAlertDown := len(monitor.AlertDown) > 0
|
||||||
|
|
||||||
hasAtLeastOneCommand := hasCommand || hasShellCommand
|
hasAtLeastOneCommand := hasCommand || hasShellCommand
|
||||||
@ -48,6 +49,10 @@ func (monitor Monitor) IsValid() bool {
|
|||||||
hasAlertDown
|
hasAlertDown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (monitor Monitor) LastOutput() string {
|
||||||
|
return monitor.lastOutput
|
||||||
|
}
|
||||||
|
|
||||||
// ShouldCheck returns a boolean indicating if the Monitor is ready to be
|
// ShouldCheck returns a boolean indicating if the Monitor is ready to be
|
||||||
// be checked again
|
// be checked again
|
||||||
func (monitor Monitor) ShouldCheck() bool {
|
func (monitor Monitor) ShouldCheck() bool {
|
||||||
@ -126,20 +131,20 @@ func (monitor *Monitor) success() (notice *AlertNotice) {
|
|||||||
func (monitor *Monitor) failure() (notice *AlertNotice) {
|
func (monitor *Monitor) failure() (notice *AlertNotice) {
|
||||||
monitor.failureCount++
|
monitor.failureCount++
|
||||||
// If we haven't hit the minimum failures, we can exit
|
// If we haven't hit the minimum failures, we can exit
|
||||||
if monitor.failureCount < monitor.GetAlertAfter() {
|
if monitor.failureCount < monitor.AlertAfter {
|
||||||
slog.Debugf(
|
slog.Debugf(
|
||||||
"%s failed but did not hit minimum failures. "+
|
"%s failed but did not hit minimum failures. "+
|
||||||
"Count: %v alert after: %v",
|
"Count: %v alert after: %v",
|
||||||
monitor.Name,
|
monitor.Name,
|
||||||
monitor.failureCount,
|
monitor.failureCount,
|
||||||
monitor.GetAlertAfter(),
|
monitor.AlertAfter,
|
||||||
)
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take number of failures after minimum
|
// Take number of failures after minimum
|
||||||
failureCount := (monitor.failureCount - monitor.GetAlertAfter())
|
failureCount := (monitor.failureCount - monitor.AlertAfter)
|
||||||
|
|
||||||
// Use alert cadence to determine if we should alert
|
// Use alert cadence to determine if we should alert
|
||||||
switch {
|
switch {
|
||||||
@ -168,15 +173,6 @@ func (monitor *Monitor) failure() (notice *AlertNotice) {
|
|||||||
return notice
|
return notice
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAlertAfter will get or return the default alert after value
|
|
||||||
func (monitor Monitor) GetAlertAfter() int {
|
|
||||||
if monitor.AlertAfter == nil {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return *monitor.AlertAfter
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAlertNames gives a list of alert names for a given monitor status
|
// GetAlertNames gives a list of alert names for a given monitor status
|
||||||
func (monitor Monitor) GetAlertNames(up bool) []string {
|
func (monitor Monitor) GetAlertNames(up bool) []string {
|
||||||
if up {
|
if up {
|
||||||
|
211
monitor_test.go
211
monitor_test.go
@ -1,22 +1,25 @@
|
|||||||
package main
|
package main_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
m "git.iamthefij.com/iamthefij/minitor-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestMonitorIsValid tests the Monitor.IsValid()
|
// TestMonitorIsValid tests the Monitor.IsValid()
|
||||||
func TestMonitorIsValid(t *testing.T) {
|
func TestMonitorIsValid(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
monitor Monitor
|
monitor m.Monitor
|
||||||
expected bool
|
expected bool
|
||||||
name string
|
name string
|
||||||
}{
|
}{
|
||||||
{Monitor{Command: []string{"echo", "test"}, AlertDown: []string{"log"}}, true, "Command only"},
|
{m.Monitor{AlertAfter: 1, Command: []string{"echo", "test"}, AlertDown: []string{"log"}}, true, "Command only"},
|
||||||
{Monitor{ShellCommand: "echo test", AlertDown: []string{"log"}}, true, "CommandShell only"},
|
{m.Monitor{AlertAfter: 1, ShellCommand: "echo test", AlertDown: []string{"log"}}, true, "CommandShell only"},
|
||||||
{Monitor{Command: []string{"echo", "test"}}, false, "No AlertDown"},
|
{m.Monitor{AlertAfter: 1, Command: []string{"echo", "test"}}, false, "No AlertDown"},
|
||||||
{Monitor{AlertDown: []string{"log"}}, false, "No commands"},
|
{m.Monitor{AlertAfter: 1, AlertDown: []string{"log"}}, false, "No commands"},
|
||||||
{Monitor{Command: []string{"echo", "test"}, AlertDown: []string{"log"}, AlertAfter: Ptr(-1)}, false, "Invalid alert threshold, -1"},
|
{m.Monitor{AlertAfter: -1, Command: []string{"echo", "test"}, AlertDown: []string{"log"}}, false, "Invalid alert threshold, -1"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
@ -35,75 +38,63 @@ func TestMonitorIsValid(t *testing.T) {
|
|||||||
|
|
||||||
// TestMonitorShouldCheck tests the Monitor.ShouldCheck()
|
// TestMonitorShouldCheck tests the Monitor.ShouldCheck()
|
||||||
func TestMonitorShouldCheck(t *testing.T) {
|
func TestMonitorShouldCheck(t *testing.T) {
|
||||||
timeNow := time.Now()
|
t.Parallel()
|
||||||
timeTenSecAgo := time.Now().Add(time.Second * -10)
|
|
||||||
timeTwentySecAgo := time.Now().Add(time.Second * -20)
|
|
||||||
fifteenSeconds := time.Second * 15
|
|
||||||
|
|
||||||
cases := []struct {
|
// Create a monitor that should check every second and then verify it checks with some sleeps
|
||||||
monitor Monitor
|
monitor := m.Monitor{ShellCommand: "true", CheckInterval: time.Second}
|
||||||
expected bool
|
|
||||||
name string
|
if !monitor.ShouldCheck() {
|
||||||
}{
|
t.Errorf("New monitor should be ready to check")
|
||||||
{Monitor{}, true, "Empty"},
|
|
||||||
{Monitor{lastCheck: timeNow, CheckInterval: fifteenSeconds}, false, "Just checked"},
|
|
||||||
{Monitor{lastCheck: timeTenSecAgo, CheckInterval: fifteenSeconds}, false, "-10s"},
|
|
||||||
{Monitor{lastCheck: timeTwentySecAgo, CheckInterval: fifteenSeconds}, true, "-20s"},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
monitor.Check()
|
||||||
c := c
|
|
||||||
|
|
||||||
t.Run(c.name, func(t *testing.T) {
|
if monitor.ShouldCheck() {
|
||||||
t.Parallel()
|
t.Errorf("Monitor should not be ready to check after a check")
|
||||||
|
}
|
||||||
|
|
||||||
actual := c.monitor.ShouldCheck()
|
time.Sleep(time.Second)
|
||||||
if actual != c.expected {
|
|
||||||
t.Errorf("ShouldCheck(%v), expected=%t actual=%t", c.name, c.expected, actual)
|
if !monitor.ShouldCheck() {
|
||||||
}
|
t.Errorf("Monitor should be ready to check after a second")
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestMonitorIsUp tests the Monitor.IsUp()
|
// TestMonitorIsUp tests the Monitor.IsUp()
|
||||||
func TestMonitorIsUp(t *testing.T) {
|
func TestMonitorIsUp(t *testing.T) {
|
||||||
cases := []struct {
|
t.Parallel()
|
||||||
monitor Monitor
|
|
||||||
expected bool
|
// Creating a monitor that should alert after 2 failures. The monitor should be considered up until we reach two failed checks
|
||||||
name string
|
monitor := m.Monitor{ShellCommand: "false", AlertAfter: 2}
|
||||||
}{
|
if !monitor.IsUp() {
|
||||||
{Monitor{}, true, "Empty"},
|
t.Errorf("New monitor should be considered up")
|
||||||
{Monitor{alertCount: 1}, false, "Has alert"},
|
|
||||||
{Monitor{alertCount: -1}, false, "Negative alerts"},
|
|
||||||
{Monitor{alertCount: 0}, true, "No alerts"},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
monitor.Check()
|
||||||
c := c
|
|
||||||
|
|
||||||
t.Run(c.name, func(t *testing.T) {
|
if !monitor.IsUp() {
|
||||||
t.Parallel()
|
t.Errorf("Monitor should be considered up with one failure and no alerts")
|
||||||
|
}
|
||||||
|
|
||||||
actual := c.monitor.IsUp()
|
monitor.Check()
|
||||||
if actual != c.expected {
|
|
||||||
t.Errorf("IsUp(%v), expected=%t actual=%t", c.name, c.expected, actual)
|
if monitor.IsUp() {
|
||||||
}
|
t.Errorf("Monitor should be considered down with one alert")
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestMonitorGetAlertNames tests that proper alert names are returned
|
// TestMonitorGetAlertNames tests that proper alert names are returned
|
||||||
func TestMonitorGetAlertNames(t *testing.T) {
|
func TestMonitorGetAlertNames(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
monitor Monitor
|
monitor m.Monitor
|
||||||
up bool
|
up bool
|
||||||
expected []string
|
expected []string
|
||||||
name string
|
name string
|
||||||
}{
|
}{
|
||||||
{Monitor{}, true, nil, "Empty up"},
|
{m.Monitor{}, true, nil, "Empty up"},
|
||||||
{Monitor{}, false, nil, "Empty down"},
|
{m.Monitor{}, false, nil, "Empty down"},
|
||||||
{Monitor{AlertUp: []string{"alert"}}, true, []string{"alert"}, "Return up"},
|
{m.Monitor{AlertUp: []string{"alert"}}, true, []string{"alert"}, "Return up"},
|
||||||
{Monitor{AlertDown: []string{"alert"}}, false, []string{"alert"}, "Return down"},
|
{m.Monitor{AlertDown: []string{"alert"}}, false, []string{"alert"}, "Return down"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
@ -113,57 +104,30 @@ func TestMonitorGetAlertNames(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
actual := c.monitor.GetAlertNames(c.up)
|
actual := c.monitor.GetAlertNames(c.up)
|
||||||
if !EqualSliceString(actual, c.expected) {
|
if !reflect.DeepEqual(actual, c.expected) {
|
||||||
t.Errorf("GetAlertNames(%v), expected=%v actual=%v", c.name, c.expected, actual)
|
t.Errorf("GetAlertNames(%v), expected=%v actual=%v", c.name, c.expected, actual)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestMonitorSuccess tests the Monitor.success()
|
|
||||||
func TestMonitorSuccess(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
monitor Monitor
|
|
||||||
expectNotice bool
|
|
||||||
name string
|
|
||||||
}{
|
|
||||||
{Monitor{}, false, "Empty"},
|
|
||||||
{Monitor{alertCount: 0}, false, "No alerts"},
|
|
||||||
{Monitor{alertCount: 1}, true, "Has alert"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
c := c
|
|
||||||
|
|
||||||
t.Run(c.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
notice := c.monitor.success()
|
|
||||||
hasNotice := (notice != nil)
|
|
||||||
|
|
||||||
if hasNotice != c.expectNotice {
|
|
||||||
t.Errorf("success(%v), expected=%t actual=%t", c.name, c.expectNotice, hasNotice)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestMonitorFailureAlertAfter tests that alerts will not trigger until
|
// TestMonitorFailureAlertAfter tests that alerts will not trigger until
|
||||||
// hitting the threshold provided by AlertAfter
|
// hitting the threshold provided by AlertAfter
|
||||||
func TestMonitorFailureAlertAfter(t *testing.T) {
|
func TestMonitorFailureAlertAfter(t *testing.T) {
|
||||||
var alertEvery int = 1
|
var alertEveryOne int = 1
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
monitor Monitor
|
monitor m.Monitor
|
||||||
|
numChecks int
|
||||||
expectNotice bool
|
expectNotice bool
|
||||||
name string
|
name string
|
||||||
}{
|
}{
|
||||||
{Monitor{AlertAfter: Ptr(1)}, true, "Empty"}, // Defaults to true because and AlertEvery default to 0
|
{m.Monitor{ShellCommand: "false", AlertAfter: 1}, 1, true, "Empty After 1"}, // Defaults to true because and AlertEvery default to 0
|
||||||
{Monitor{failureCount: 0, AlertAfter: Ptr(1), AlertEvery: &alertEvery}, true, "Alert after 1: first failure"},
|
{m.Monitor{ShellCommand: "false", AlertAfter: 1, AlertEvery: &alertEveryOne}, 1, true, "Alert after 1: first failure"},
|
||||||
{Monitor{failureCount: 1, AlertAfter: Ptr(1), AlertEvery: &alertEvery}, true, "Alert after 1: second failure"},
|
{m.Monitor{ShellCommand: "false", AlertAfter: 1, AlertEvery: &alertEveryOne}, 2, true, "Alert after 1: second failure"},
|
||||||
{Monitor{failureCount: 0, AlertAfter: Ptr(20), AlertEvery: &alertEvery}, false, "Alert after 20: first failure"},
|
{m.Monitor{ShellCommand: "false", AlertAfter: 20, AlertEvery: &alertEveryOne}, 1, false, "Alert after 20: first failure"},
|
||||||
{Monitor{failureCount: 19, AlertAfter: Ptr(20), AlertEvery: &alertEvery}, true, "Alert after 20: 20th failure"},
|
{m.Monitor{ShellCommand: "false", AlertAfter: 20, AlertEvery: &alertEveryOne}, 20, true, "Alert after 20: 20th failure"},
|
||||||
{Monitor{failureCount: 20, AlertAfter: Ptr(20), AlertEvery: &alertEvery}, true, "Alert after 20: 21st failure"},
|
{m.Monitor{ShellCommand: "false", AlertAfter: 20, AlertEvery: &alertEveryOne}, 21, true, "Alert after 20: 21st failure"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
@ -172,8 +136,12 @@ func TestMonitorFailureAlertAfter(t *testing.T) {
|
|||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
notice := c.monitor.failure()
|
hasNotice := false
|
||||||
hasNotice := (notice != nil)
|
|
||||||
|
for i := 0; i < c.numChecks; i++ {
|
||||||
|
_, notice := c.monitor.Check()
|
||||||
|
hasNotice = (notice != nil)
|
||||||
|
}
|
||||||
|
|
||||||
if hasNotice != c.expectNotice {
|
if hasNotice != c.expectNotice {
|
||||||
t.Errorf("failure(%v), expected=%t actual=%t", c.name, c.expectNotice, hasNotice)
|
t.Errorf("failure(%v), expected=%t actual=%t", c.name, c.expectNotice, hasNotice)
|
||||||
@ -185,39 +153,18 @@ func TestMonitorFailureAlertAfter(t *testing.T) {
|
|||||||
// TestMonitorFailureAlertEvery tests that alerts will trigger
|
// TestMonitorFailureAlertEvery tests that alerts will trigger
|
||||||
// on the expected intervals
|
// on the expected intervals
|
||||||
func TestMonitorFailureAlertEvery(t *testing.T) {
|
func TestMonitorFailureAlertEvery(t *testing.T) {
|
||||||
var alertEvery0, alertEvery1, alertEvery2 int
|
|
||||||
alertEvery0 = 0
|
|
||||||
alertEvery1 = 1
|
|
||||||
alertEvery2 = 2
|
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
monitor Monitor
|
monitor m.Monitor
|
||||||
expectNotice bool
|
expectedNotice []bool
|
||||||
name string
|
name string
|
||||||
}{
|
}{
|
||||||
/*
|
{m.Monitor{ShellCommand: "false", AlertAfter: 1}, []bool{true}, "No AlertEvery set"}, // Defaults to true because AlertAfter and AlertEvery default to nil
|
||||||
TODO: Actually found a bug in original implementation. There is an inconsistency in the way AlertAfter is treated.
|
|
||||||
For "First alert only" (ie. AlertEvery=0), it is the number of failures to ignore before alerting, so AlertAfter=1
|
|
||||||
will ignore the first failure and alert on the second failure
|
|
||||||
For other intervals (ie. AlertEvery=1), it is essentially indexed on one. Essentially making AlertAfter=1 trigger
|
|
||||||
on the first failure.
|
|
||||||
|
|
||||||
For usabilty, this should be consistent. Consistent with what though? minitor-py? Or itself? Dun dun duuuunnnnn!
|
|
||||||
*/
|
|
||||||
{Monitor{AlertAfter: Ptr(1)}, true, "Empty"}, // Defaults to true because AlertAfter and AlertEvery default to nil
|
|
||||||
// Alert first time only, after 1
|
// Alert first time only, after 1
|
||||||
{Monitor{failureCount: 0, AlertAfter: Ptr(1), AlertEvery: &alertEvery0}, true, "Alert first time only after 1: first failure"},
|
{m.Monitor{ShellCommand: "false", AlertAfter: 1, AlertEvery: Ptr(0)}, []bool{true, false, false}, "Alert first time only after 1"},
|
||||||
{Monitor{failureCount: 1, AlertAfter: Ptr(1), AlertEvery: &alertEvery0}, false, "Alert first time only after 1: second failure"},
|
|
||||||
{Monitor{failureCount: 2, AlertAfter: Ptr(1), AlertEvery: &alertEvery0}, false, "Alert first time only after 1: third failure"},
|
|
||||||
// Alert every time, after 1
|
// Alert every time, after 1
|
||||||
{Monitor{failureCount: 0, AlertAfter: Ptr(1), AlertEvery: &alertEvery1}, true, "Alert every time after 1: first failure"},
|
{m.Monitor{ShellCommand: "false", AlertAfter: 1, AlertEvery: Ptr(1)}, []bool{true, true, true}, "Alert every time after 1"},
|
||||||
{Monitor{failureCount: 1, AlertAfter: Ptr(1), AlertEvery: &alertEvery1}, true, "Alert every time after 1: second failure"},
|
|
||||||
{Monitor{failureCount: 2, AlertAfter: Ptr(1), AlertEvery: &alertEvery1}, true, "Alert every time after 1: third failure"},
|
|
||||||
// Alert every other time, after 1
|
// Alert every other time, after 1
|
||||||
{Monitor{failureCount: 0, AlertAfter: Ptr(1), AlertEvery: &alertEvery2}, true, "Alert every other time after 1: first failure"},
|
{m.Monitor{ShellCommand: "false", AlertAfter: 1, AlertEvery: Ptr(2)}, []bool{true, false, true, false}, "Alert every other time after 1"},
|
||||||
{Monitor{failureCount: 1, AlertAfter: Ptr(1), AlertEvery: &alertEvery2}, false, "Alert every other time after 1: second failure"},
|
|
||||||
{Monitor{failureCount: 2, AlertAfter: Ptr(1), AlertEvery: &alertEvery2}, true, "Alert every other time after 1: third failure"},
|
|
||||||
{Monitor{failureCount: 3, AlertAfter: Ptr(1), AlertEvery: &alertEvery2}, false, "Alert every other time after 1: fourth failure"},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
@ -226,11 +173,13 @@ func TestMonitorFailureAlertEvery(t *testing.T) {
|
|||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
notice := c.monitor.failure()
|
for i, expectNotice := range c.expectedNotice {
|
||||||
hasNotice := (notice != nil)
|
_, notice := c.monitor.Check()
|
||||||
|
hasNotice := (notice != nil)
|
||||||
|
|
||||||
if hasNotice != c.expectNotice {
|
if hasNotice != expectNotice {
|
||||||
t.Errorf("failure(%v), expected=%t actual=%t", c.name, c.expectNotice, hasNotice)
|
t.Errorf("failed %s check %d: expected=%t actual=%t", c.name, i, expectNotice, hasNotice)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -257,12 +206,12 @@ func TestMonitorFailureExponential(t *testing.T) {
|
|||||||
|
|
||||||
// Unlike previous tests, this one requires a static Monitor with repeated
|
// Unlike previous tests, this one requires a static Monitor with repeated
|
||||||
// calls to the failure method
|
// calls to the failure method
|
||||||
monitor := Monitor{failureCount: 0, AlertAfter: Ptr(1), AlertEvery: &alertEveryExp}
|
monitor := m.Monitor{ShellCommand: "false", AlertAfter: 1, AlertEvery: &alertEveryExp}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
// NOTE: These tests are not parallel because they rely on the state of the Monitor
|
// NOTE: These tests are not parallel because they rely on the state of the Monitor
|
||||||
notice := monitor.failure()
|
_, notice := monitor.Check()
|
||||||
hasNotice := (notice != nil)
|
hasNotice := (notice != nil)
|
||||||
|
|
||||||
if hasNotice != c.expectNotice {
|
if hasNotice != c.expectNotice {
|
||||||
@ -281,27 +230,27 @@ func TestMonitorCheck(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
monitor Monitor
|
monitor m.Monitor
|
||||||
expect expected
|
expect expected
|
||||||
name string
|
name string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Monitor{Command: []string{"echo", "success"}},
|
m.Monitor{AlertAfter: 1, Command: []string{"echo", "success"}},
|
||||||
expected{isSuccess: true, hasNotice: false, lastOutput: "success\n"},
|
expected{isSuccess: true, hasNotice: false, lastOutput: "success\n"},
|
||||||
"Test successful command",
|
"Test successful command",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Monitor{ShellCommand: "echo success"},
|
m.Monitor{AlertAfter: 1, ShellCommand: "echo success"},
|
||||||
expected{isSuccess: true, hasNotice: false, lastOutput: "success\n"},
|
expected{isSuccess: true, hasNotice: false, lastOutput: "success\n"},
|
||||||
"Test successful command shell",
|
"Test successful command shell",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Monitor{Command: []string{"total", "failure"}},
|
m.Monitor{AlertAfter: 1, Command: []string{"total", "failure"}},
|
||||||
expected{isSuccess: false, hasNotice: true, lastOutput: ""},
|
expected{isSuccess: false, hasNotice: true, lastOutput: ""},
|
||||||
"Test failed command",
|
"Test failed command",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Monitor{ShellCommand: "false"},
|
m.Monitor{AlertAfter: 1, ShellCommand: "false"},
|
||||||
expected{isSuccess: false, hasNotice: true, lastOutput: ""},
|
expected{isSuccess: false, hasNotice: true, lastOutput: ""},
|
||||||
"Test failed command shell",
|
"Test failed command shell",
|
||||||
},
|
},
|
||||||
@ -323,7 +272,7 @@ func TestMonitorCheck(t *testing.T) {
|
|||||||
t.Errorf("Check(%v) (notice), expected=%t actual=%t", c.name, c.expect.hasNotice, hasNotice)
|
t.Errorf("Check(%v) (notice), expected=%t actual=%t", c.name, c.expect.hasNotice, hasNotice)
|
||||||
}
|
}
|
||||||
|
|
||||||
lastOutput := c.monitor.lastOutput
|
lastOutput := c.monitor.LastOutput()
|
||||||
if lastOutput != c.expect.lastOutput {
|
if lastOutput != c.expect.lastOutput {
|
||||||
t.Errorf("Check(%v) (output), expected=%v actual=%v", c.name, c.expect.lastOutput, lastOutput)
|
t.Errorf("Check(%v) (output), expected=%v actual=%v", c.name, c.expect.lastOutput, lastOutput)
|
||||||
}
|
}
|
||||||
|
7
test/invalid-config-missing-alerts.hcl
Normal file
7
test/invalid-config-missing-alerts.hcl
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
check_interval = "1s"
|
||||||
|
|
||||||
|
monitor "Command" {
|
||||||
|
command = ["echo", "$PATH"]
|
||||||
|
alert_down = [ "alert_down", "log_shell", "log_command" ]
|
||||||
|
alert_every = 0
|
||||||
|
}
|
@ -1,8 +0,0 @@
|
|||||||
check_interval: 1
|
|
||||||
|
|
||||||
monitors:
|
|
||||||
- name: Command
|
|
||||||
command: ['echo', '$PATH']
|
|
||||||
alert_down: [ 'alert_down', 'log_shell', 'log_command' ]
|
|
||||||
# alert_every: -1
|
|
||||||
alert_every: 0
|
|
1
test/invalid-config-type.hcl
Normal file
1
test/invalid-config-type.hcl
Normal file
@ -0,0 +1 @@
|
|||||||
|
check_interval = "woops, I'm not an int!"
|
@ -1 +0,0 @@
|
|||||||
check_interval: woops, I'm not an int!
|
|
12
test/invalid-config-unknown-alert.hcl
Normal file
12
test/invalid-config-unknown-alert.hcl
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
check_interval = "1s"
|
||||||
|
|
||||||
|
monitor "Command" {
|
||||||
|
command = ["echo", "$PATH"]
|
||||||
|
alert_down = ["not_log"]
|
||||||
|
alert_every = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
alert "log" {
|
||||||
|
command = ["true"]
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
check_interval: 1
|
|
||||||
|
|
||||||
monitors:
|
|
||||||
- name: Command
|
|
||||||
command: ['echo', '$PATH']
|
|
||||||
alert_down: [ 'not_log']
|
|
||||||
# alert_every: -1
|
|
||||||
alert_every: 0
|
|
||||||
|
|
||||||
|
|
||||||
alerts:
|
|
||||||
log:
|
|
||||||
command: ['true']
|
|
11
test/valid-config-default-values.hcl
Normal file
11
test/valid-config-default-values.hcl
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
check_interval = "1s"
|
||||||
|
default_alert_down = ["log_command"]
|
||||||
|
default_alert_after = 1
|
||||||
|
|
||||||
|
monitor "Command" {
|
||||||
|
command = ["echo", "$PATH"]
|
||||||
|
}
|
||||||
|
|
||||||
|
alert "log_command" {
|
||||||
|
command = ["echo", "default", "'command!!!'", "{{.MonitorName}}"]
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
check_interval: 1
|
|
||||||
default_alert_down: ["log_command"]
|
|
||||||
default_alert_after: 1
|
|
||||||
|
|
||||||
monitors:
|
|
||||||
- name: Command
|
|
||||||
command: ["echo", "$PATH"]
|
|
||||||
|
|
||||||
alerts:
|
|
||||||
log_command:
|
|
||||||
command: ["echo", "regular", '"command!!!"', "{{.MonitorName}}"]
|
|
@ -1,8 +0,0 @@
|
|||||||
---
|
|
||||||
check_interval: 1
|
|
||||||
|
|
||||||
monitors:
|
|
||||||
- name: Command
|
|
||||||
command: ['echo', '$PATH']
|
|
||||||
alert_down: ['log']
|
|
||||||
alert_every: 0
|
|
Loading…
x
Reference in New Issue
Block a user