Skip to content

Commit

Permalink
make sure that the device host id is provided in notifications (if av…
Browse files Browse the repository at this point in the history
…ailable).

fixes #337
  • Loading branch information
AnalogJ committed Aug 4, 2022
1 parent 9a4a8de commit d41d535
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 17 deletions.
1 change: 1 addition & 0 deletions docs/TROUBLESHOOTING_NOTIFICATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
```

59 changes: 42 additions & 17 deletions webapp/backend/pkg/notify/notify.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -115,16 +116,23 @@ 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,
Test: test,
}

//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()
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
83 changes: 83 additions & 0 deletions webapp/backend/pkg/notify/notify_test.go
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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)
}

0 comments on commit d41d535

Please sign in to comment.