From d41d535ab71532e9ba20a6bec69c8f90f34d3bb6 Mon Sep 17 00:00:00 2001 From: Jason Kulatunga Date: Wed, 3 Aug 2022 20:55:34 -0700 Subject: [PATCH] make sure that the device host id is provided in notifications (if available). fixes #337 --- docs/TROUBLESHOOTING_NOTIFICATIONS.md | 1 + webapp/backend/pkg/notify/notify.go | 59 ++++++++++++----- webapp/backend/pkg/notify/notify_test.go | 83 ++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 17 deletions(-) diff --git a/docs/TROUBLESHOOTING_NOTIFICATIONS.md b/docs/TROUBLESHOOTING_NOTIFICATIONS.md index 2baba6a..2f2abee 100644 --- a/docs/TROUBLESHOOTING_NOTIFICATIONS.md +++ b/docs/TROUBLESHOOTING_NOTIFICATIONS.md @@ -21,5 +21,6 @@ SCRUTINY_DEVICE_NAME - eg. /dev/sda SCRUTINY_DEVICE_TYPE - ATA/SCSI/NVMe SCRUTINY_DEVICE_SERIAL - eg. WDDJ324KSO SCRUTINY_MESSAGE - eg. "Scrutiny SMART error notification for device: %s\nFailure Type: %s\nDevice Name: %s\nDevice Serial: %s\nDevice Type: %s\nDate: %s" +SCRUTINY_HOST_ID - (optional) eg. "my-custom-host-id" ``` diff --git a/webapp/backend/pkg/notify/notify.go b/webapp/backend/pkg/notify/notify.go index 657b007..3dbe661 100644 --- a/webapp/backend/pkg/notify/notify.go +++ b/webapp/backend/pkg/notify/notify.go @@ -101,12 +101,13 @@ func ShouldNotify(device models.Device, smartAttrs measurements.Smart, statusThr } } -// TODO: include host and/or user label for device. +// TODO: include user label for device. type Payload struct { - DeviceType string `json:"device_type"` //ATA/SCSI/NVMe - DeviceName string `json:"device_name"` //dev/sda - DeviceSerial string `json:"device_serial"` //WDDJ324KSO - Test bool `json:"test"` // false + HostId string `json:"host_id,omitempty"` //host id (optional) + DeviceType string `json:"device_type"` //ATA/SCSI/NVMe + DeviceName string `json:"device_name"` //dev/sda + DeviceSerial string `json:"device_serial"` //WDDJ324KSO + Test bool `json:"test"` // false //private, populated during init (marked as Public for JSON serialization) Date string `json:"date"` //populated by Send function. @@ -115,8 +116,9 @@ type Payload struct { Message string `json:"message"` } -func NewPayload(device models.Device, test bool) Payload { +func NewPayload(device models.Device, test bool, currentTime ...time.Time) Payload { payload := Payload{ + HostId: strings.TrimSpace(device.HostId), DeviceType: device.DeviceType, DeviceName: device.DeviceName, DeviceSerial: device.SerialNumber, @@ -124,7 +126,13 @@ func NewPayload(device models.Device, test bool) Payload { } //validate that the Payload is populated - sendDate := time.Now() + var sendDate time.Time + if currentTime != nil && len(currentTime) > 0 { + sendDate = currentTime[0] + } else { + sendDate = time.Now() + } + payload.Date = sendDate.Format(time.RFC3339) payload.FailureType = payload.GenerateFailureType(device.DeviceStatus) payload.Subject = payload.GenerateSubject() @@ -148,25 +156,39 @@ func (p *Payload) GenerateFailureType(deviceStatus pkg.DeviceStatus) string { func (p *Payload) GenerateSubject() string { //generate a detailed failure message - return fmt.Sprintf("Scrutiny SMART error (%s) detected on device: %s", p.FailureType, p.DeviceName) + var subject string + if len(p.HostId) > 0 { + subject = fmt.Sprintf("Scrutiny SMART error (%s) detected on [host]device: [%s]%s", p.FailureType, p.HostId, p.DeviceName) + } else { + subject = fmt.Sprintf("Scrutiny SMART error (%s) detected on device: %s", p.FailureType, p.DeviceName) + } + return subject } func (p *Payload) GenerateMessage() string { //generate a detailed failure message - message := fmt.Sprintf( - `Scrutiny SMART error notification for device: %s -Failure Type: %s -Device Name: %s -Device Serial: %s -Device Type: %s -Date: %s`, p.DeviceName, p.FailureType, p.DeviceName, p.DeviceSerial, p.DeviceType, p.Date) + messageParts := []string{} + + messageParts = append(messageParts, fmt.Sprintf("Scrutiny SMART error notification for device: %s", p.DeviceName)) + if len(p.HostId) > 0 { + messageParts = append(messageParts, fmt.Sprintf("Host Id: %s", p.HostId)) + } + + messageParts = append(messageParts, + fmt.Sprintf("Failure Type: %s", p.FailureType), + fmt.Sprintf("Device Name: %s", p.DeviceName), + fmt.Sprintf("Device Serial: %s", p.DeviceSerial), + fmt.Sprintf("Device Type: %s", p.DeviceType), + "", + fmt.Sprintf("Date: %s", p.Date), + ) if p.Test { - message = "TEST NOTIFICATION:\n" + message + messageParts = append([]string{"TEST NOTIFICATION:"}, messageParts...) } - return message + return strings.Join(messageParts, "\n") } func New(logger logrus.FieldLogger, appconfig config.Interface, device models.Device, test bool) Notify { @@ -287,6 +309,9 @@ func (n *Notify) SendScriptNotification(scriptUrl string) error { copyEnv = append(copyEnv, fmt.Sprintf("SCRUTINY_DEVICE_TYPE=%s", n.Payload.DeviceType)) copyEnv = append(copyEnv, fmt.Sprintf("SCRUTINY_DEVICE_SERIAL=%s", n.Payload.DeviceSerial)) copyEnv = append(copyEnv, fmt.Sprintf("SCRUTINY_MESSAGE=%s", n.Payload.Message)) + if len(n.Payload.HostId) > 0 { + copyEnv = append(copyEnv, fmt.Sprintf("SCRUTINY_HOST_ID=%s", n.Payload.HostId)) + } err := utils.CmdExec(scriptPath, []string{}, "", copyEnv, "") if err != nil { n.Logger.Errorf("An error occurred while executing script %s: %v", scriptPath, err) diff --git a/webapp/backend/pkg/notify/notify_test.go b/webapp/backend/pkg/notify/notify_test.go index b891ede..c76a924 100644 --- a/webapp/backend/pkg/notify/notify_test.go +++ b/webapp/backend/pkg/notify/notify_test.go @@ -1,11 +1,13 @@ package notify import ( + "fmt" "github.com/analogj/scrutiny/webapp/backend/pkg" "github.com/analogj/scrutiny/webapp/backend/pkg/models" "github.com/analogj/scrutiny/webapp/backend/pkg/models/measurements" "github.com/stretchr/testify/require" "testing" + "time" ) func TestShouldNotify_MustSkipPassingDevices(t *testing.T) { @@ -159,3 +161,84 @@ func TestShouldNotify_MetricsStatusFilterAttributesCritical_MetricsStatusThresho //assert require.False(t, ShouldNotify(device, smartAttrs, statusThreshold, notifyFilterAttributes)) } + +func TestNewPayload(t *testing.T) { + t.Parallel() + + //setup + device := models.Device{ + SerialNumber: "FAKEWDDJ324KSO", + DeviceType: pkg.DeviceProtocolAta, + DeviceName: "/dev/sda", + DeviceStatus: pkg.DeviceStatusFailedScrutiny, + } + currentTime := time.Now() + //test + + payload := NewPayload(device, false, currentTime) + + //assert + require.Equal(t, "Scrutiny SMART error (ScrutinyFailure) detected on device: /dev/sda", payload.Subject) + require.Equal(t, fmt.Sprintf(`Scrutiny SMART error notification for device: /dev/sda +Failure Type: ScrutinyFailure +Device Name: /dev/sda +Device Serial: FAKEWDDJ324KSO +Device Type: ATA + +Date: %s`, currentTime.Format(time.RFC3339)), payload.Message) +} + +func TestNewPayload_TestMode(t *testing.T) { + t.Parallel() + + //setup + device := models.Device{ + SerialNumber: "FAKEWDDJ324KSO", + DeviceType: pkg.DeviceProtocolAta, + DeviceName: "/dev/sda", + DeviceStatus: pkg.DeviceStatusFailedScrutiny, + } + currentTime := time.Now() + //test + + payload := NewPayload(device, true, currentTime) + + //assert + require.Equal(t, "Scrutiny SMART error (EmailTest) detected on device: /dev/sda", payload.Subject) + require.Equal(t, fmt.Sprintf(`TEST NOTIFICATION: +Scrutiny SMART error notification for device: /dev/sda +Failure Type: EmailTest +Device Name: /dev/sda +Device Serial: FAKEWDDJ324KSO +Device Type: ATA + +Date: %s`, currentTime.Format(time.RFC3339)), payload.Message) +} + +func TestNewPayload_WithHostId(t *testing.T) { + t.Parallel() + + //setup + device := models.Device{ + SerialNumber: "FAKEWDDJ324KSO", + DeviceType: pkg.DeviceProtocolAta, + DeviceName: "/dev/sda", + DeviceStatus: pkg.DeviceStatusFailedScrutiny, + HostId: "custom-host", + } + currentTime := time.Now() + //test + + payload := NewPayload(device, false, currentTime) + + //assert + require.Equal(t, "Scrutiny SMART error (ScrutinyFailure) detected on [host]device: [custom-host]/dev/sda", payload.Subject) + require.Equal(t, fmt.Sprintf(`Scrutiny SMART error notification for device: /dev/sda +Host Id: custom-host +Failure Type: ScrutinyFailure +Device Name: /dev/sda +Device Serial: FAKEWDDJ324KSO +Device Type: ATA + +Date: %s`, currentTime.Format(time.RFC3339)), payload.Message) +}