Identify drives by a Scrutiny UUID instead of wwn (#960)
* Generate a UUIDv5 from a random namespace based on WWN, model name, and serial number * Migrate sqlite and influxdb data accordingly * Update frontend API routes and components * Fixes #923
This commit is contained in:
@@ -207,10 +207,10 @@ type SmartInfo struct {
|
||||
ID int `json:"id"`
|
||||
SubsystemID int `json:"subsystem_id"`
|
||||
} `json:"nvme_pci_vendor"`
|
||||
NvmeIeeeOuiIdentifier int `json:"nvme_ieee_oui_identifier"`
|
||||
NvmeTotalCapacity int64 `json:"nvme_total_capacity"`
|
||||
NvmeControllerID int `json:"nvme_controller_id"`
|
||||
NvmeNumberOfNamespaces int `json:"nvme_number_of_namespaces"`
|
||||
NvmeIeeeOuiIdentifier uint32 `json:"nvme_ieee_oui_identifier"`
|
||||
NvmeTotalCapacity int64 `json:"nvme_total_capacity"`
|
||||
NvmeControllerID int `json:"nvme_controller_id"`
|
||||
NvmeNumberOfNamespaces int `json:"nvme_number_of_namespaces"`
|
||||
NvmeNamespaces []struct {
|
||||
ID int `json:"id"`
|
||||
Size struct {
|
||||
@@ -226,6 +226,10 @@ type SmartInfo struct {
|
||||
Bytes int64 `json:"bytes"`
|
||||
} `json:"utilization"`
|
||||
FormattedLbaSize int `json:"formatted_lba_size"`
|
||||
Eui64 struct {
|
||||
Oui uint32 `json:"oui"`
|
||||
ExtId uint64 `json:"ext_id"`
|
||||
} `json:"eui64"`
|
||||
} `json:"nvme_namespaces"`
|
||||
NvmeSmartHealthInformationLog NvmeSmartHealthInformationLog `json:"nvme_smart_health_information_log"`
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models/collector"
|
||||
"time"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
type DeviceWrapper struct {
|
||||
@@ -19,7 +21,7 @@ type Device struct {
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time
|
||||
|
||||
WWN string `json:"wwn" gorm:"primary_key"`
|
||||
WWN string `json:"wwn"`
|
||||
|
||||
DeviceName string `json:"device_name"`
|
||||
DeviceUUID string `json:"device_uuid"`
|
||||
@@ -45,6 +47,7 @@ type Device struct {
|
||||
|
||||
// Data set by Scrutiny
|
||||
DeviceStatus pkg.DeviceStatus `json:"device_status"`
|
||||
ScrutinyUUID uuid.UUID `json:"scrutiny_uuid" gorm:"primaryKey;uniqueIndex"`
|
||||
}
|
||||
|
||||
func (dv *Device) IsAta() bool {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models/measurements"
|
||||
"time"
|
||||
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models/measurements"
|
||||
)
|
||||
|
||||
// This is used in server_test.go
|
||||
type DeviceSummaryWrapper struct {
|
||||
Success bool `json:"success"`
|
||||
Errors []error `json:"errors"`
|
||||
|
||||
@@ -10,11 +10,13 @@ import (
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models/collector"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/thresholds"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
type Smart struct {
|
||||
Date time.Time `json:"date"`
|
||||
DeviceWWN string `json:"device_wwn"` //(tag)
|
||||
DeviceWWN string `json:"device_wwn` // deprecated
|
||||
ScrutinyUUID uuid.UUID `json:"scrutiny_uuid"` //(tag)
|
||||
DeviceProtocol string `json:"device_protocol"`
|
||||
|
||||
//Metrics (fields)
|
||||
@@ -31,7 +33,7 @@ type Smart struct {
|
||||
|
||||
func (sm *Smart) Flatten() (tags map[string]string, fields map[string]interface{}) {
|
||||
tags = map[string]string{
|
||||
"device_wwn": sm.DeviceWWN,
|
||||
"scrutiny_uuid": sm.ScrutinyUUID.String(),
|
||||
"device_protocol": sm.DeviceProtocol,
|
||||
}
|
||||
|
||||
@@ -53,10 +55,15 @@ func (sm *Smart) Flatten() (tags map[string]string, fields map[string]interface{
|
||||
func NewSmartFromInfluxDB(attrs map[string]interface{}) (*Smart, error) {
|
||||
//go though the massive map returned from influxdb. If a key is associated with the Smart struct, assign it. If it starts with "attr.*" group it by attributeId, and pass to attribute inflate.
|
||||
|
||||
scrutiny_uuid, err := uuid.FromString(attrs["scrutiny_uuid"].(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sm := Smart{
|
||||
//required fields
|
||||
Date: attrs["_time"].(time.Time),
|
||||
DeviceWWN: attrs["device_wwn"].(string),
|
||||
ScrutinyUUID: scrutiny_uuid,
|
||||
DeviceProtocol: attrs["device_protocol"].(string),
|
||||
|
||||
Attributes: map[string]SmartAttribute{},
|
||||
@@ -112,14 +119,14 @@ func NewSmartFromInfluxDB(attrs map[string]interface{}) (*Smart, error) {
|
||||
|
||||
}
|
||||
|
||||
log.Printf("Found Smart Device (%s) Attributes (%v)", sm.DeviceWWN, len(sm.Attributes))
|
||||
log.Printf("Found Smart Device (%s) Attributes (%v)", sm.ScrutinyUUID, len(sm.Attributes))
|
||||
|
||||
return &sm, nil
|
||||
}
|
||||
|
||||
// Parse Collector SMART data results and create Smart object (and associated SmartAtaAttribute entries)
|
||||
func (sm *Smart) FromCollectorSmartInfo(wwn string, info collector.SmartInfo) error {
|
||||
sm.DeviceWWN = wwn
|
||||
func (sm *Smart) FromCollectorSmartInfo(scrutiny_uuid uuid.UUID, info collector.SmartInfo) error {
|
||||
sm.ScrutinyUUID = scrutiny_uuid
|
||||
sm.Date = time.Unix(info.LocalTime.TimeT, 0)
|
||||
|
||||
//smart metrics
|
||||
@@ -133,11 +140,12 @@ func (sm *Smart) FromCollectorSmartInfo(wwn string, info collector.SmartInfo) er
|
||||
sm.DeviceProtocol = info.Device.Protocol
|
||||
// process ATA/NVME/SCSI protocol data
|
||||
sm.Attributes = map[string]SmartAttribute{}
|
||||
if sm.DeviceProtocol == pkg.DeviceProtocolAta {
|
||||
switch sm.DeviceProtocol {
|
||||
case pkg.DeviceProtocolAta:
|
||||
sm.ProcessAtaSmartInfo(info.AtaSmartAttributes.Table)
|
||||
} else if sm.DeviceProtocol == pkg.DeviceProtocolNvme {
|
||||
case pkg.DeviceProtocolNvme:
|
||||
sm.ProcessNvmeSmartInfo(info.NvmeSmartHealthInformationLog)
|
||||
} else if sm.DeviceProtocol == pkg.DeviceProtocolScsi {
|
||||
case pkg.DeviceProtocolScsi:
|
||||
sm.ProcessScsiSmartInfo(info.ScsiGrownDefectList, info.ScsiErrorCounterLog)
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ func (sa *SmartNvmeAttribute) Inflate(key string, val interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
//populate attribute status, using SMART Thresholds & Observed Metadata
|
||||
// populate attribute status, using SMART Thresholds & Observed Metadata
|
||||
// Chainable
|
||||
func (sa *SmartNvmeAttribute) PopulateAttributeStatus() *SmartNvmeAttribute {
|
||||
|
||||
|
||||
@@ -67,9 +67,8 @@ func (sa *SmartScsiAttribute) Inflate(key string, val interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//populate attribute status, using SMART Thresholds & Observed Metadata
|
||||
//Chainable
|
||||
// populate attribute status, using SMART Thresholds & Observed Metadata
|
||||
// Chainable
|
||||
func (sa *SmartScsiAttribute) PopulateAttributeStatus() *SmartScsiAttribute {
|
||||
|
||||
//-1 is a special number meaning no threshold.
|
||||
|
||||
@@ -10,15 +10,17 @@ import (
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models/collector"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models/measurements"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSmart_Flatten(t *testing.T) {
|
||||
//setup
|
||||
timeNow := time.Now()
|
||||
smartUUID := uuid.Must(uuid.NewV4())
|
||||
smart := measurements.Smart{
|
||||
Date: timeNow,
|
||||
DeviceWWN: "test-wwn",
|
||||
ScrutinyUUID: smartUUID,
|
||||
DeviceProtocol: pkg.DeviceProtocolAta,
|
||||
Temp: 50,
|
||||
PowerOnHours: 10,
|
||||
@@ -31,16 +33,17 @@ func TestSmart_Flatten(t *testing.T) {
|
||||
tags, fields := smart.Flatten()
|
||||
|
||||
//assert
|
||||
require.Equal(t, map[string]string{"device_protocol": "ATA", "device_wwn": "test-wwn"}, tags)
|
||||
require.Equal(t, map[string]string{"device_protocol": "ATA", "scrutiny_uuid": smartUUID.String()}, tags)
|
||||
require.Equal(t, map[string]interface{}{"power_cycle_count": int64(10), "power_on_hours": int64(10), "temp": int64(50)}, fields)
|
||||
}
|
||||
|
||||
func TestSmart_Flatten_ATA(t *testing.T) {
|
||||
//setup
|
||||
timeNow := time.Now()
|
||||
smartUUID := uuid.Must(uuid.NewV4())
|
||||
smart := measurements.Smart{
|
||||
Date: timeNow,
|
||||
DeviceWWN: "test-wwn",
|
||||
ScrutinyUUID: smartUUID,
|
||||
DeviceProtocol: pkg.DeviceProtocolAta,
|
||||
Temp: 50,
|
||||
PowerOnHours: 10,
|
||||
@@ -72,7 +75,7 @@ func TestSmart_Flatten_ATA(t *testing.T) {
|
||||
tags, fields := smart.Flatten()
|
||||
|
||||
//assert
|
||||
require.Equal(t, map[string]string{"device_protocol": "ATA", "device_wwn": "test-wwn"}, tags)
|
||||
require.Equal(t, map[string]string{"device_protocol": "ATA", "scrutiny_uuid": smartUUID.String()}, tags)
|
||||
require.Equal(t, map[string]interface{}{
|
||||
"attr.1.attribute_id": "1",
|
||||
"attr.1.failure_rate": float64(0),
|
||||
@@ -107,9 +110,10 @@ func TestSmart_Flatten_ATA(t *testing.T) {
|
||||
func TestSmart_Flatten_SCSI(t *testing.T) {
|
||||
//setup
|
||||
timeNow := time.Now()
|
||||
smartUUID := uuid.Must(uuid.NewV4())
|
||||
smart := measurements.Smart{
|
||||
Date: timeNow,
|
||||
DeviceWWN: "test-wwn",
|
||||
ScrutinyUUID: smartUUID,
|
||||
DeviceProtocol: pkg.DeviceProtocolScsi,
|
||||
Temp: 50,
|
||||
PowerOnHours: 10,
|
||||
@@ -127,7 +131,7 @@ func TestSmart_Flatten_SCSI(t *testing.T) {
|
||||
tags, fields := smart.Flatten()
|
||||
|
||||
//assert
|
||||
require.Equal(t, map[string]string{"device_protocol": "SCSI", "device_wwn": "test-wwn"}, tags)
|
||||
require.Equal(t, map[string]string{"device_protocol": "SCSI", "scrutiny_uuid": smartUUID.String()}, tags)
|
||||
require.Equal(t, map[string]interface{}{
|
||||
"attr.read_errors_corrected_by_eccfast.attribute_id": "read_errors_corrected_by_eccfast",
|
||||
"attr.read_errors_corrected_by_eccfast.failure_rate": float64(0),
|
||||
@@ -145,9 +149,10 @@ func TestSmart_Flatten_SCSI(t *testing.T) {
|
||||
func TestSmart_Flatten_NVMe(t *testing.T) {
|
||||
//setup
|
||||
timeNow := time.Now()
|
||||
smartUUID := uuid.Must(uuid.NewV4())
|
||||
smart := measurements.Smart{
|
||||
Date: timeNow,
|
||||
DeviceWWN: "test-wwn",
|
||||
ScrutinyUUID: smartUUID,
|
||||
DeviceProtocol: pkg.DeviceProtocolNvme,
|
||||
Temp: 50,
|
||||
PowerOnHours: 10,
|
||||
@@ -165,7 +170,7 @@ func TestSmart_Flatten_NVMe(t *testing.T) {
|
||||
tags, fields := smart.Flatten()
|
||||
|
||||
//assert
|
||||
require.Equal(t, map[string]string{"device_protocol": "NVMe", "device_wwn": "test-wwn"}, tags)
|
||||
require.Equal(t, map[string]string{"device_protocol": "NVMe", "scrutiny_uuid": smartUUID.String()}, tags)
|
||||
require.Equal(t, map[string]interface{}{
|
||||
"attr.available_spare.attribute_id": "available_spare",
|
||||
"attr.available_spare.failure_rate": float64(0),
|
||||
@@ -182,9 +187,10 @@ func TestSmart_Flatten_NVMe(t *testing.T) {
|
||||
func TestNewSmartFromInfluxDB_ATA(t *testing.T) {
|
||||
//setup
|
||||
timeNow := time.Now()
|
||||
smartUUID := uuid.Must(uuid.NewV4())
|
||||
attrs := map[string]interface{}{
|
||||
"_time": timeNow,
|
||||
"device_wwn": "test-wwn",
|
||||
"scrutiny_uuid": smartUUID.String(),
|
||||
"device_protocol": pkg.DeviceProtocolAta,
|
||||
"attr.1.attribute_id": "1",
|
||||
"attr.1.failure_rate": float64(0),
|
||||
@@ -209,7 +215,7 @@ func TestNewSmartFromInfluxDB_ATA(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &measurements.Smart{
|
||||
Date: timeNow,
|
||||
DeviceWWN: "test-wwn",
|
||||
ScrutinyUUID: smartUUID,
|
||||
DeviceProtocol: "ATA",
|
||||
Temp: 50,
|
||||
PowerOnHours: 10,
|
||||
@@ -230,9 +236,10 @@ func TestNewSmartFromInfluxDB_ATA(t *testing.T) {
|
||||
func TestNewSmartFromInfluxDB_NVMe(t *testing.T) {
|
||||
//setup
|
||||
timeNow := time.Now()
|
||||
smartUUID := uuid.Must(uuid.NewV4())
|
||||
attrs := map[string]interface{}{
|
||||
"_time": timeNow,
|
||||
"device_wwn": "test-wwn",
|
||||
"scrutiny_uuid": smartUUID.String(),
|
||||
"device_protocol": pkg.DeviceProtocolNvme,
|
||||
"attr.available_spare.attribute_id": "available_spare",
|
||||
"attr.available_spare.failure_rate": float64(0),
|
||||
@@ -253,7 +260,7 @@ func TestNewSmartFromInfluxDB_NVMe(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &measurements.Smart{
|
||||
Date: timeNow,
|
||||
DeviceWWN: "test-wwn",
|
||||
ScrutinyUUID: smartUUID,
|
||||
DeviceProtocol: "NVMe",
|
||||
Temp: 50,
|
||||
PowerOnHours: 10,
|
||||
@@ -269,9 +276,10 @@ func TestNewSmartFromInfluxDB_NVMe(t *testing.T) {
|
||||
func TestNewSmartFromInfluxDB_SCSI(t *testing.T) {
|
||||
//setup
|
||||
timeNow := time.Now()
|
||||
smartUUID := uuid.Must(uuid.NewV4())
|
||||
attrs := map[string]interface{}{
|
||||
"_time": timeNow,
|
||||
"device_wwn": "test-wwn",
|
||||
"scrutiny_uuid": smartUUID.String(),
|
||||
"device_protocol": pkg.DeviceProtocolScsi,
|
||||
"attr.read_errors_corrected_by_eccfast.attribute_id": "read_errors_corrected_by_eccfast",
|
||||
"attr.read_errors_corrected_by_eccfast.failure_rate": float64(0),
|
||||
@@ -292,7 +300,7 @@ func TestNewSmartFromInfluxDB_SCSI(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &measurements.Smart{
|
||||
Date: timeNow,
|
||||
DeviceWWN: "test-wwn",
|
||||
ScrutinyUUID: smartUUID,
|
||||
DeviceProtocol: "SCSI",
|
||||
Temp: 50,
|
||||
PowerOnHours: 10,
|
||||
@@ -320,11 +328,12 @@ func TestFromCollectorSmartInfo(t *testing.T) {
|
||||
|
||||
//test
|
||||
smartMdl := measurements.Smart{}
|
||||
err = smartMdl.FromCollectorSmartInfo("WWN-test", smartJson)
|
||||
smartUUID := uuid.Must(uuid.NewV4())
|
||||
err = smartMdl.FromCollectorSmartInfo(smartUUID, smartJson)
|
||||
|
||||
//assert
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
|
||||
require.Equal(t, smartUUID, smartMdl.ScrutinyUUID)
|
||||
require.Equal(t, pkg.DeviceStatusPassed, smartMdl.Status)
|
||||
require.Equal(t, 18, len(smartMdl.Attributes))
|
||||
|
||||
@@ -352,11 +361,12 @@ func TestFromCollectorSmartInfo_Fail_Smart(t *testing.T) {
|
||||
|
||||
//test
|
||||
smartMdl := measurements.Smart{}
|
||||
err = smartMdl.FromCollectorSmartInfo("WWN-test", smartJson)
|
||||
smartUUID := uuid.Must(uuid.NewV4())
|
||||
err = smartMdl.FromCollectorSmartInfo(smartUUID, smartJson)
|
||||
|
||||
//assert
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
|
||||
require.Equal(t, smartUUID, smartMdl.ScrutinyUUID)
|
||||
require.Equal(t, pkg.DeviceStatusFailedSmart, smartMdl.Status)
|
||||
require.Equal(t, 0, len(smartMdl.Attributes))
|
||||
}
|
||||
@@ -376,11 +386,12 @@ func TestFromCollectorSmartInfo_Fail_ScrutinySmart(t *testing.T) {
|
||||
|
||||
//test
|
||||
smartMdl := measurements.Smart{}
|
||||
err = smartMdl.FromCollectorSmartInfo("WWN-test", smartJson)
|
||||
smartUUID := uuid.Must(uuid.NewV4())
|
||||
err = smartMdl.FromCollectorSmartInfo(smartUUID, smartJson)
|
||||
|
||||
//assert
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
|
||||
require.Equal(t, smartUUID, smartMdl.ScrutinyUUID)
|
||||
require.Equal(t, pkg.DeviceStatusFailedScrutiny|pkg.DeviceStatusFailedSmart, smartMdl.Status)
|
||||
require.Equal(t, 17, len(smartMdl.Attributes))
|
||||
}
|
||||
@@ -400,11 +411,12 @@ func TestFromCollectorSmartInfo_Fail_ScrutinyNonCriticalFailed(t *testing.T) {
|
||||
|
||||
//test
|
||||
smartMdl := measurements.Smart{}
|
||||
err = smartMdl.FromCollectorSmartInfo("WWN-test", smartJson)
|
||||
smartUUID := uuid.Must(uuid.NewV4())
|
||||
err = smartMdl.FromCollectorSmartInfo(smartUUID, smartJson)
|
||||
|
||||
//assert
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
|
||||
require.Equal(t, smartUUID, smartMdl.ScrutinyUUID)
|
||||
require.Equal(t, pkg.DeviceStatusFailedScrutiny, smartMdl.Status)
|
||||
require.Equal(t, pkg.AttributeStatusFailedScrutiny, smartMdl.Attributes["199"].GetStatus(),
|
||||
"scrutiny should detect that %d failed (status: %d, %s)",
|
||||
@@ -433,11 +445,12 @@ func TestFromCollectorSmartInfo_NVMe_Fail_Scrutiny(t *testing.T) {
|
||||
|
||||
//test
|
||||
smartMdl := measurements.Smart{}
|
||||
err = smartMdl.FromCollectorSmartInfo("WWN-test", smartJson)
|
||||
smartUUID := uuid.Must(uuid.NewV4())
|
||||
err = smartMdl.FromCollectorSmartInfo(smartUUID, smartJson)
|
||||
|
||||
//assert
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
|
||||
require.Equal(t, smartUUID, smartMdl.ScrutinyUUID)
|
||||
require.Equal(t, pkg.DeviceStatusFailedScrutiny, smartMdl.Status)
|
||||
require.Equal(t, pkg.AttributeStatusFailedScrutiny, smartMdl.Attributes["media_errors"].GetStatus(),
|
||||
"scrutiny should detect that %s failed (status: %d, %s)",
|
||||
@@ -464,11 +477,12 @@ func TestFromCollectorSmartInfo_Nvme(t *testing.T) {
|
||||
|
||||
//test
|
||||
smartMdl := measurements.Smart{}
|
||||
err = smartMdl.FromCollectorSmartInfo("WWN-test", smartJson)
|
||||
smartUUID := uuid.Must(uuid.NewV4())
|
||||
err = smartMdl.FromCollectorSmartInfo(smartUUID, smartJson)
|
||||
|
||||
//assert
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
|
||||
require.Equal(t, smartUUID, smartMdl.ScrutinyUUID)
|
||||
require.Equal(t, pkg.DeviceStatusPassed, smartMdl.Status)
|
||||
require.Equal(t, 16, len(smartMdl.Attributes))
|
||||
|
||||
@@ -491,11 +505,12 @@ func TestFromCollectorSmartInfo_Scsi(t *testing.T) {
|
||||
|
||||
//test
|
||||
smartMdl := measurements.Smart{}
|
||||
err = smartMdl.FromCollectorSmartInfo("WWN-test", smartJson)
|
||||
smartUUID := uuid.Must(uuid.NewV4())
|
||||
err = smartMdl.FromCollectorSmartInfo(smartUUID, smartJson)
|
||||
|
||||
//assert
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
|
||||
require.Equal(t, smartUUID, smartMdl.ScrutinyUUID)
|
||||
require.Equal(t, pkg.DeviceStatusPassed, smartMdl.Status)
|
||||
require.Equal(t, 13, len(smartMdl.Attributes))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user