From c80c760d413acf851676e320bff1f6fd60d7697a Mon Sep 17 00:00:00 2001 From: noerw Date: Tue, 26 Jun 2018 00:51:38 +0200 Subject: [PATCH] refactor healtcheck interface now using first class functions for checkers, should be simpler to plug functionality is the same --- core/healthcheck_measurement_age.go | 36 ++++++++ core/healthcheck_measurement_faulty.go | 49 +++++++++++ core/healthcheck_measurement_minmax.go | 50 +++++++++++ core/healthchecks.go | 112 +++++++------------------ core/osem_api.go | 24 +++--- 5 files changed, 176 insertions(+), 95 deletions(-) create mode 100644 core/healthcheck_measurement_age.go create mode 100644 core/healthcheck_measurement_faulty.go create mode 100644 core/healthcheck_measurement_minmax.go diff --git a/core/healthcheck_measurement_age.go b/core/healthcheck_measurement_age.go new file mode 100644 index 0000000..f33a637 --- /dev/null +++ b/core/healthcheck_measurement_age.go @@ -0,0 +1,36 @@ +package core + +import ( + "fmt" + "time" +) + +var checkMeasurementAge = checkType{ + name: "measurement_age", + toString: func(r CheckResult) string { + return fmt.Sprintf("No measurement from %s (%s) since %s", r.TargetName, r.Target, r.Value) + }, + checkFunc: func(e NotifyEvent, s Sensor, b Box) (CheckResult, error) { + result := CheckResult{ + Event: e.Type, + Target: s.Id, + TargetName: s.Phenomenon, + Threshold: e.Threshold, + Value: s.LastMeasurement.Date.String(), + Status: CheckOk, + } + + thresh, err := time.ParseDuration(e.Threshold) + if err != nil { + return CheckResult{}, err + } + + if time.Since(s.LastMeasurement.Date) > thresh { + result.Status = CheckErr + } + + result.Value = s.LastMeasurement.Date.String() + + return result, nil + }, +} diff --git a/core/healthcheck_measurement_faulty.go b/core/healthcheck_measurement_faulty.go new file mode 100644 index 0000000..d6005ab --- /dev/null +++ b/core/healthcheck_measurement_faulty.go @@ -0,0 +1,49 @@ +package core + +import ( + "fmt" + "strconv" +) + +var checkMeasurementFaulty = checkType{ + name: "measurement_faulty", + toString: func(r CheckResult) string { + return fmt.Sprintf("Sensor %s (%s) reads presumably faulty value of %s", r.TargetName, r.Target, r.Value) + }, + checkFunc: func(e NotifyEvent, s Sensor, b Box) (CheckResult, error) { + result := CheckResult{ + Event: e.Type, + Target: s.Id, + TargetName: s.Phenomenon, + Threshold: e.Threshold, + Value: s.LastMeasurement.Value, + Status: CheckOk, + } + + val, err := strconv.ParseFloat(s.LastMeasurement.Value, 64) + if err != nil { + return result, err + } + + if faultyVals[faultyValue{ + sensor: s.Type, + val: val, + }] { + result.Status = CheckErr + } + + return result, nil + }, +} + +type faultyValue struct { + sensor string + val float64 +} + +var faultyVals = map[faultyValue]bool{ + faultyValue{sensor: "BMP280", val: 0.0}: true, + faultyValue{sensor: "HDC1008", val: 0.0}: true, + faultyValue{sensor: "HDC1008", val: -40}: true, + faultyValue{sensor: "SDS 011", val: 0.0}: true, +} diff --git a/core/healthcheck_measurement_minmax.go b/core/healthcheck_measurement_minmax.go new file mode 100644 index 0000000..441d3fd --- /dev/null +++ b/core/healthcheck_measurement_minmax.go @@ -0,0 +1,50 @@ +package core + +import ( + "fmt" + "strconv" +) + +var checkMeasurementMin = checkType{ + name: "measurement_min", + toString: func(r CheckResult) string { + return fmt.Sprintf("Sensor %s (%s) reads low value of %s", r.TargetName, r.Target, r.Value) + }, + checkFunc: validateMeasurementMinMax, +} + +var checkMeasurementMax = checkType{ + name: "measurement_min", + toString: func(r CheckResult) string { + return fmt.Sprintf("Sensor %s (%s) reads high value of %s", r.TargetName, r.Target, r.Value) + }, + checkFunc: validateMeasurementMinMax, +} + +func validateMeasurementMinMax(e NotifyEvent, s Sensor, b Box) (CheckResult, error) { + result := CheckResult{ + Event: e.Type, + Target: s.Id, + TargetName: s.Phenomenon, + Threshold: e.Threshold, + Value: s.LastMeasurement.Value, + Status: CheckOk, + } + + thresh, err := strconv.ParseFloat(e.Threshold, 64) + if err != nil { + return result, err + } + + val, err := strconv.ParseFloat(s.LastMeasurement.Value, 64) + if err != nil { + return result, err + } + + if e.Type == eventMeasurementValMax && val > thresh || + e.Type == eventMeasurementValMin && val < thresh { + result.Status = CheckErr + } + + return result, nil +} diff --git a/core/healthchecks.go b/core/healthchecks.go index e0200ed..76ce74f 100644 --- a/core/healthchecks.go +++ b/core/healthchecks.go @@ -4,8 +4,8 @@ import ( "crypto/sha256" "encoding/hex" "fmt" - "strconv" - "time" + + log "github.com/sirupsen/logrus" ) const ( @@ -18,34 +18,27 @@ const ( eventTargetAll = "all" // if event.Target is this value, all sensors will be checked ) -type checkType = struct{ description string } - -var checkTypes = map[string]checkType{ - eventMeasurementAge: checkType{"No measurement from %s (%s) since %s"}, - eventMeasurementValMin: checkType{"Sensor %s (%s) reads low value of %s"}, - eventMeasurementValMax: checkType{"Sensor %s (%s) reads high value of %s"}, - eventMeasurementValFaulty: checkType{"Sensor %s (%s) reads presumably faulty value of %s"}, +type checkType = struct { + name string // name that is used in config + toString func(result CheckResult) string // error message when check failed + checkFunc func(event NotifyEvent, sensor Sensor, context Box) (CheckResult, error) } -type FaultyValue struct { - sensor string - val float64 -} - -var faultyVals = map[FaultyValue]bool{ - FaultyValue{sensor: "BMP280", val: 0.0}: true, - FaultyValue{sensor: "HDC1008", val: 0.0}: true, - FaultyValue{sensor: "HDC1008", val: -40}: true, - FaultyValue{sensor: "SDS 011", val: 0.0}: true, +var checkers = map[string]checkType{ + checkMeasurementAge.name: checkMeasurementAge, + checkMeasurementMin.name: checkMeasurementMin, + checkMeasurementMax.name: checkMeasurementMax, + checkMeasurementFaulty.name: checkMeasurementFaulty, } type CheckResult struct { - Status string - Event string - Target string + Status string // should be CheckOk | CheckErr TargetName string Value string - Threshold string + Target string + + Event string // these should be copied from the NotifyEvent + Threshold string } func (r CheckResult) EventID() string { @@ -59,12 +52,13 @@ func (r CheckResult) String() string { if r.Status == CheckOk { return fmt.Sprintf("%s %s (on sensor %s (%s) with value %s)\n", r.Event, r.Status, r.TargetName, r.Target, r.Value) } else { - return fmt.Sprintf("%s: "+checkTypes[r.Event].description+"\n", r.Status, r.TargetName, r.Target, r.Value) + return fmt.Sprintf("%s: %s"+"\n", r.Status, checkers[r.Event].toString(r)) } } func (box Box) RunChecks() ([]CheckResult, error) { var results = []CheckResult{} + boxLogger := log.WithField("box", box.Id) for _, event := range box.NotifyConf.Events { for _, s := range box.Sensors { @@ -73,68 +67,18 @@ func (box Box) RunChecks() ([]CheckResult, error) { continue } - // a validator must set these values - var ( - status = CheckOk - target = s.Id - targetName = s.Phenomenon - value string - ) - - if event.Target == eventTargetAll || event.Target == s.Id { - - switch event.Type { - case eventMeasurementAge: - thresh, err := time.ParseDuration(event.Threshold) - if err != nil { - return nil, err - } - if time.Since(s.LastMeasurement.Date) > thresh { - status = CheckErr - } - - value = s.LastMeasurement.Date.String() - - case eventMeasurementValMin, eventMeasurementValMax: - thresh, err := strconv.ParseFloat(event.Threshold, 64) - if err != nil { - return nil, err - } - val, err2 := strconv.ParseFloat(s.LastMeasurement.Value, 64) - if err2 != nil { - return nil, err2 - } - if event.Type == eventMeasurementValMax && val > thresh || - event.Type == eventMeasurementValMin && val < thresh { - status = CheckErr - } - - value = s.LastMeasurement.Value - - case eventMeasurementValFaulty: - val, err := strconv.ParseFloat(s.LastMeasurement.Value, 64) - if err != nil { - return nil, err - } - if faultyVals[FaultyValue{ - sensor: s.Type, - val: val, - }] { - status = CheckErr - } - - value = s.LastMeasurement.Value - } + checker := checkers[event.Type] + if checker.checkFunc == nil { + boxLogger.Warnf("ignoring unknown event type %s", event.Type) + continue + } - results = append(results, CheckResult{ - Threshold: event.Threshold, - Event: event.Type, - Target: target, - TargetName: targetName, - Value: value, - Status: status, - }) + result, err := checker.checkFunc(event, s, box) + if err != nil { + boxLogger.Errorf("error checking event %s", event.Type) } + + results = append(results, result) } } diff --git a/core/osem_api.go b/core/osem_api.go index 06f9331..59ed708 100644 --- a/core/osem_api.go +++ b/core/osem_api.go @@ -49,17 +49,19 @@ type NotifyConfig struct { Events []NotifyEvent `json:"events"` } +type Sensor struct { + Id string `json:"_id"` + Phenomenon string `json:"title"` + Type string `json:"sensorType"` + LastMeasurement *struct { + Value string `json:"value"` + Date time.Time `json:"createdAt"` + } `json:"lastMeasurement"` +} + type Box struct { - Id string `json:"_id"` - Name string `json:"name"` - Sensors []struct { - Id string `json:"_id"` - Phenomenon string `json:"title"` - Type string `json:"sensorType"` - LastMeasurement *struct { - Value string `json:"value"` - Date time.Time `json:"createdAt"` - } `json:"lastMeasurement"` - } `json:"sensors"` + Id string `json:"_id"` + Name string `json:"name"` + Sensors []Sensor `json:"sensors"` NotifyConf *NotifyConfig `json:"healthcheck"` }