added AttributeStatus bit flag

ensure DeviceStatus is a valid bit flag.
[docs] added running tests section to contribution guide.
make sure UI correctly treats scrutiny failures as failed.
pull/276/head^2
Jason Kulatunga 2 years ago
parent b5dad487e5
commit 488fcfc820

@ -169,3 +169,19 @@ docker run -it --rm -p 8080:8080 \
ghcr.io/analogj/scrutiny:master-omnibus ghcr.io/analogj/scrutiny:master-omnibus
/opt/scrutiny/bin/scrutiny-collector-metrics run /opt/scrutiny/bin/scrutiny-collector-metrics run
``` ```
# Running Tests
```bash
docker run -p 8086:8086 -d --rm \
-e DOCKER_INFLUXDB_INIT_MODE=setup \
-e DOCKER_INFLUXDB_INIT_USERNAME=admin \
-e DOCKER_INFLUXDB_INIT_PASSWORD=password12345 \
-e DOCKER_INFLUXDB_INIT_ORG=scrutiny \
-e DOCKER_INFLUXDB_INIT_BUCKET=metrics \
-e DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=my-super-secret-auth-token \
influxdb:2.2
go test ./...
```

@ -4,25 +4,34 @@ const DeviceProtocolAta = "ATA"
const DeviceProtocolScsi = "SCSI" const DeviceProtocolScsi = "SCSI"
const DeviceProtocolNvme = "NVMe" const DeviceProtocolNvme = "NVMe"
const SmartAttributeStatusPassed = 0 type AttributeStatus uint8
const SmartAttributeStatusFailed = 1
const SmartAttributeStatusWarning = 2
const SmartWhenFailedFailingNow = "FAILING_NOW" const (
const SmartWhenFailedInThePast = "IN_THE_PAST" // AttributeStatusPassed binary, 1,2,4,8,16,32,etc
AttributeStatusPassed AttributeStatus = 0
AttributeStatusFailedSmart AttributeStatus = 1
AttributeStatusWarningScrutiny AttributeStatus = 2
AttributeStatusFailedScrutiny AttributeStatus = 4
)
const AttributeWhenFailedFailingNow = "FAILING_NOW"
const AttributeWhenFailedInThePast = "IN_THE_PAST"
//const SmartStatusPassed = "passed" func AttributeStatusSet(b, flag AttributeStatus) AttributeStatus { return b | flag }
//const SmartStatusFailed = "failed" func AttributeStatusClear(b, flag AttributeStatus) AttributeStatus { return b &^ flag }
func AttributeStatusToggle(b, flag AttributeStatus) AttributeStatus { return b ^ flag }
func AttributeStatusHas(b, flag AttributeStatus) bool { return b&flag != 0 }
type DeviceStatus int type DeviceStatus uint8
const ( const (
// DeviceStatusPassed binary, 1,2,4,8,16,32,etc
DeviceStatusPassed DeviceStatus = 0 DeviceStatusPassed DeviceStatus = 0
DeviceStatusFailedSmart DeviceStatus = iota DeviceStatusFailedSmart DeviceStatus = 1
DeviceStatusFailedScrutiny DeviceStatus = iota DeviceStatusFailedScrutiny DeviceStatus = 2
) )
func Set(b, flag DeviceStatus) DeviceStatus { return b | flag } func DeviceStatusSet(b, flag DeviceStatus) DeviceStatus { return b | flag }
func Clear(b, flag DeviceStatus) DeviceStatus { return b &^ flag } func DeviceStatusClear(b, flag DeviceStatus) DeviceStatus { return b &^ flag }
func Toggle(b, flag DeviceStatus) DeviceStatus { return b ^ flag } func DeviceStatusToggle(b, flag DeviceStatus) DeviceStatus { return b ^ flag }
func Has(b, flag DeviceStatus) bool { return b&flag != 0 } func DeviceStatusHas(b, flag DeviceStatus) bool { return b&flag != 0 }

@ -58,7 +58,7 @@ func (sr *scrutinyRepository) UpdateDeviceStatus(ctx context.Context, wwn string
return device, fmt.Errorf("Could not get device from DB: %v", err) return device, fmt.Errorf("Could not get device from DB: %v", err)
} }
device.DeviceStatus = pkg.Set(device.DeviceStatus, status) device.DeviceStatus = pkg.DeviceStatusSet(device.DeviceStatus, status)
return device, sr.gormClient.Model(&device).Updates(device).Error return device, sr.gormClient.Model(&device).Updates(device).Error
} }

@ -21,9 +21,9 @@ type Device struct {
WWN string `json:"wwn" gorm:"primary_key"` WWN string `json:"wwn" gorm:"primary_key"`
DeviceName string `json:"device_name"` DeviceName string `json:"device_name"`
DeviceUUID string `json:"device_uuid"` DeviceUUID string `json:"device_uuid"`
DeviceSerialID string `json:"device_serial_id"` DeviceSerialID string `json:"device_serial_id"`
DeviceLabel string `json:"device_label"` DeviceLabel string `json:"device_label"`
Manufacturer string `json:"manufacturer"` Manufacturer string `json:"manufacturer"`
ModelName string `json:"model_name"` ModelName string `json:"model_name"`
@ -166,7 +166,7 @@ func (dv *Device) UpdateFromCollectorSmartInfo(info collector.SmartInfo) error {
dv.DeviceProtocol = info.Device.Protocol dv.DeviceProtocol = info.Device.Protocol
if !info.SmartStatus.Passed { if !info.SmartStatus.Passed {
dv.DeviceStatus = pkg.Set(dv.DeviceStatus, pkg.DeviceStatusFailedSmart) dv.DeviceStatus = pkg.DeviceStatusSet(dv.DeviceStatus, pkg.DeviceStatusFailedSmart)
} }
return nil return nil

@ -110,7 +110,7 @@ func (sm *Smart) FromCollectorSmartInfo(wwn string, info collector.SmartInfo) er
sm.PowerCycleCount = info.PowerCycleCount sm.PowerCycleCount = info.PowerCycleCount
sm.PowerOnHours = info.PowerOnTime.Hours sm.PowerOnHours = info.PowerOnTime.Hours
if !info.SmartStatus.Passed { if !info.SmartStatus.Passed {
sm.Status = pkg.DeviceStatusFailedSmart sm.Status = pkg.DeviceStatusSet(sm.Status, pkg.DeviceStatusFailedSmart)
} }
sm.DeviceProtocol = info.Device.Protocol sm.DeviceProtocol = info.Device.Protocol
@ -148,8 +148,9 @@ func (sm *Smart) ProcessAtaSmartInfo(tableItems []collector.AtaSmartAttributesTa
} }
attrModel.PopulateAttributeStatus() attrModel.PopulateAttributeStatus()
sm.Attributes[strconv.Itoa(collectorAttr.ID)] = &attrModel sm.Attributes[strconv.Itoa(collectorAttr.ID)] = &attrModel
if attrModel.Status == pkg.SmartAttributeStatusFailed {
sm.Status = pkg.Set(sm.Status, pkg.DeviceStatusFailedScrutiny) if pkg.AttributeStatusHas(attrModel.Status, pkg.AttributeStatusFailedScrutiny) {
sm.Status = pkg.DeviceStatusSet(sm.Status, pkg.DeviceStatusFailedScrutiny)
} }
} }
} }
@ -178,8 +179,8 @@ func (sm *Smart) ProcessNvmeSmartInfo(nvmeSmartHealthInformationLog collector.Nv
//find analyzed attribute status //find analyzed attribute status
for _, val := range sm.Attributes { for _, val := range sm.Attributes {
if val.GetStatus() == pkg.SmartAttributeStatusFailed { if pkg.AttributeStatusHas(val.GetStatus(), pkg.AttributeStatusFailedScrutiny) {
sm.Status = pkg.Set(sm.Status, pkg.DeviceStatusFailedScrutiny) sm.Status = pkg.DeviceStatusSet(sm.Status, pkg.DeviceStatusFailedScrutiny)
} }
} }
} }
@ -204,8 +205,8 @@ func (sm *Smart) ProcessScsiSmartInfo(defectGrownList int64, scsiErrorCounterLog
//find analyzed attribute status //find analyzed attribute status
for _, val := range sm.Attributes { for _, val := range sm.Attributes {
if val.GetStatus() == pkg.SmartAttributeStatusFailed { if pkg.AttributeStatusHas(val.GetStatus(), pkg.AttributeStatusFailedScrutiny) {
sm.Status = pkg.Set(sm.Status, pkg.DeviceStatusFailedScrutiny) sm.Status = pkg.DeviceStatusSet(sm.Status, pkg.DeviceStatusFailedScrutiny)
} }
} }
} }

@ -18,13 +18,13 @@ type SmartAtaAttribute struct {
WhenFailed string `json:"when_failed"` WhenFailed string `json:"when_failed"`
//Generated data //Generated data
TransformedValue int64 `json:"transformed_value"` TransformedValue int64 `json:"transformed_value"`
Status int64 `json:"status"` Status pkg.AttributeStatus `json:"status"`
StatusReason string `json:"status_reason,omitempty"` StatusReason string `json:"status_reason,omitempty"`
FailureRate float64 `json:"failure_rate,omitempty"` FailureRate float64 `json:"failure_rate,omitempty"`
} }
func (sa *SmartAtaAttribute) GetStatus() int64 { func (sa *SmartAtaAttribute) GetStatus() pkg.AttributeStatus {
return sa.Status return sa.Status
} }
@ -77,7 +77,7 @@ func (sa *SmartAtaAttribute) Inflate(key string, val interface{}) {
case "transformed_value": case "transformed_value":
sa.TransformedValue = val.(int64) sa.TransformedValue = val.(int64)
case "status": case "status":
sa.Status = val.(int64) sa.Status = val.(pkg.AttributeStatus)
case "status_reason": case "status_reason":
sa.StatusReason = val.(string) sa.StatusReason = val.(string)
case "failure_rate": case "failure_rate":
@ -89,16 +89,16 @@ func (sa *SmartAtaAttribute) Inflate(key string, val interface{}) {
//populate attribute status, using SMART Thresholds & Observed Metadata //populate attribute status, using SMART Thresholds & Observed Metadata
// Chainable // Chainable
func (sa *SmartAtaAttribute) PopulateAttributeStatus() *SmartAtaAttribute { func (sa *SmartAtaAttribute) PopulateAttributeStatus() *SmartAtaAttribute {
if strings.ToUpper(sa.WhenFailed) == pkg.SmartWhenFailedFailingNow { if strings.ToUpper(sa.WhenFailed) == pkg.AttributeWhenFailedFailingNow {
//this attribute has previously failed //this attribute has previously failed
sa.Status = pkg.SmartAttributeStatusFailed sa.Status = pkg.AttributeStatusSet(sa.Status, pkg.AttributeStatusFailedSmart)
sa.StatusReason = "Attribute is failing manufacturer SMART threshold" sa.StatusReason += "Attribute is failing manufacturer SMART threshold"
//if the Smart Status is failed, we should exit early, no need to look at thresholds. //if the Smart Status is failed, we should exit early, no need to look at thresholds.
return sa return sa
} else if strings.ToUpper(sa.WhenFailed) == pkg.SmartWhenFailedInThePast { } else if strings.ToUpper(sa.WhenFailed) == pkg.AttributeWhenFailedInThePast {
sa.Status = pkg.SmartAttributeStatusWarning sa.Status = pkg.AttributeStatusSet(sa.Status, pkg.AttributeStatusWarningScrutiny)
sa.StatusReason = "Attribute has previously failed manufacturer SMART threshold" sa.StatusReason += "Attribute has previously failed manufacturer SMART threshold"
} }
if smartMetadata, ok := thresholds.AtaMetadata[sa.AttributeId]; ok { if smartMetadata, ok := thresholds.AtaMetadata[sa.AttributeId]; ok {
@ -138,16 +138,16 @@ func (sa *SmartAtaAttribute) ValidateThreshold(smartMetadata thresholds.AtaAttri
if smartMetadata.Critical { if smartMetadata.Critical {
if obsThresh.AnnualFailureRate >= 0.10 { if obsThresh.AnnualFailureRate >= 0.10 {
sa.Status = pkg.SmartAttributeStatusFailed sa.Status = pkg.AttributeStatusSet(sa.Status, pkg.AttributeStatusFailedScrutiny)
sa.StatusReason = "Observed Failure Rate for Critical Attribute is greater than 10%" sa.StatusReason += "Observed Failure Rate for Critical Attribute is greater than 10%"
} }
} else { } else {
if obsThresh.AnnualFailureRate >= 0.20 { if obsThresh.AnnualFailureRate >= 0.20 {
sa.Status = pkg.SmartAttributeStatusFailed sa.Status = pkg.AttributeStatusSet(sa.Status, pkg.AttributeStatusFailedScrutiny)
sa.StatusReason = "Observed Failure Rate for Attribute is greater than 20%" sa.StatusReason += "Observed Failure Rate for Non-Critical Attribute is greater than 20%"
} else if obsThresh.AnnualFailureRate >= 0.10 { } else if obsThresh.AnnualFailureRate >= 0.10 {
sa.Status = pkg.SmartAttributeStatusWarning sa.Status = pkg.AttributeStatusSet(sa.Status, pkg.AttributeStatusWarningScrutiny)
sa.StatusReason = "Observed Failure Rate for Attribute is greater than 10%" sa.StatusReason += "Observed Failure Rate for Non-Critical Attribute is greater than 10%"
} }
} }
@ -157,7 +157,7 @@ func (sa *SmartAtaAttribute) ValidateThreshold(smartMetadata thresholds.AtaAttri
} }
// no bucket found // no bucket found
if smartMetadata.Critical { if smartMetadata.Critical {
sa.Status = pkg.SmartAttributeStatusWarning sa.Status = pkg.AttributeStatusSet(sa.Status, pkg.AttributeStatusWarningScrutiny)
sa.StatusReason = "Could not determine Observed Failure Rate for Critical Attribute" sa.StatusReason = "Could not determine Observed Failure Rate for Critical Attribute"
} }

@ -1,7 +1,9 @@
package measurements package measurements
import "github.com/analogj/scrutiny/webapp/backend/pkg"
type SmartAttribute interface { type SmartAttribute interface {
Flatten() (fields map[string]interface{}) Flatten() (fields map[string]interface{})
Inflate(key string, val interface{}) Inflate(key string, val interface{})
GetStatus() int64 GetStatus() pkg.AttributeStatus
} }

@ -12,13 +12,13 @@ type SmartNvmeAttribute struct {
Value int64 `json:"value"` Value int64 `json:"value"`
Threshold int64 `json:"thresh"` Threshold int64 `json:"thresh"`
TransformedValue int64 `json:"transformed_value"` TransformedValue int64 `json:"transformed_value"`
Status int64 `json:"status"` Status pkg.AttributeStatus `json:"status"`
StatusReason string `json:"status_reason,omitempty"` StatusReason string `json:"status_reason,omitempty"`
FailureRate float64 `json:"failure_rate,omitempty"` FailureRate float64 `json:"failure_rate,omitempty"`
} }
func (sa *SmartNvmeAttribute) GetStatus() int64 { func (sa *SmartNvmeAttribute) GetStatus() pkg.AttributeStatus {
return sa.Status return sa.Status
} }
@ -54,7 +54,7 @@ func (sa *SmartNvmeAttribute) Inflate(key string, val interface{}) {
case "transformed_value": case "transformed_value":
sa.TransformedValue = val.(int64) sa.TransformedValue = val.(int64)
case "status": case "status":
sa.Status = val.(int64) sa.Status = val.(pkg.AttributeStatus)
case "status_reason": case "status_reason":
sa.StatusReason = val.(string) sa.StatusReason = val.(string)
case "failure_rate": case "failure_rate":
@ -72,8 +72,8 @@ func (sa *SmartNvmeAttribute) PopulateAttributeStatus() *SmartNvmeAttribute {
//check what the ideal is. Ideal tells us if we our recorded value needs to be above, or below the threshold //check what the ideal is. Ideal tells us if we our recorded value needs to be above, or below the threshold
if (smartMetadata.Ideal == "low" && sa.Value > sa.Threshold) || if (smartMetadata.Ideal == "low" && sa.Value > sa.Threshold) ||
(smartMetadata.Ideal == "high" && sa.Value < sa.Threshold) { (smartMetadata.Ideal == "high" && sa.Value < sa.Threshold) {
sa.Status = pkg.SmartAttributeStatusFailed sa.Status = pkg.AttributeStatusSet(sa.Status, pkg.AttributeStatusFailedScrutiny)
sa.StatusReason = "Attribute is failing recommended SMART threshold" sa.StatusReason += "Attribute is failing recommended SMART threshold"
} }
} }
} }

@ -12,13 +12,13 @@ type SmartScsiAttribute struct {
Value int64 `json:"value"` Value int64 `json:"value"`
Threshold int64 `json:"thresh"` Threshold int64 `json:"thresh"`
TransformedValue int64 `json:"transformed_value"` TransformedValue int64 `json:"transformed_value"`
Status int64 `json:"status"` Status pkg.AttributeStatus `json:"status"`
StatusReason string `json:"status_reason,omitempty"` StatusReason string `json:"status_reason,omitempty"`
FailureRate float64 `json:"failure_rate,omitempty"` FailureRate float64 `json:"failure_rate,omitempty"`
} }
func (sa *SmartScsiAttribute) GetStatus() int64 { func (sa *SmartScsiAttribute) GetStatus() pkg.AttributeStatus {
return sa.Status return sa.Status
} }
@ -54,7 +54,7 @@ func (sa *SmartScsiAttribute) Inflate(key string, val interface{}) {
case "transformed_value": case "transformed_value":
sa.TransformedValue = val.(int64) sa.TransformedValue = val.(int64)
case "status": case "status":
sa.Status = val.(int64) sa.Status = val.(pkg.AttributeStatus)
case "status_reason": case "status_reason":
sa.StatusReason = val.(string) sa.StatusReason = val.(string)
case "failure_rate": case "failure_rate":
@ -73,7 +73,7 @@ func (sa *SmartScsiAttribute) PopulateAttributeStatus() *SmartScsiAttribute {
//check what the ideal is. Ideal tells us if we our recorded value needs to be above, or below the threshold //check what the ideal is. Ideal tells us if we our recorded value needs to be above, or below the threshold
if (smartMetadata.Ideal == "low" && sa.Value > sa.Threshold) || if (smartMetadata.Ideal == "low" && sa.Value > sa.Threshold) ||
(smartMetadata.Ideal == "high" && sa.Value < sa.Threshold) { (smartMetadata.Ideal == "high" && sa.Value < sa.Threshold) {
sa.Status = pkg.SmartAttributeStatusFailed sa.Status = pkg.AttributeStatusSet(sa.Status, pkg.AttributeStatusFailedScrutiny)
sa.StatusReason = "Attribute is failing recommended SMART threshold" sa.StatusReason = "Attribute is failing recommended SMART threshold"
} }
} }

@ -77,7 +77,7 @@ func TestSmart_Flatten_ATA(t *testing.T) {
"attr.1.failure_rate": float64(0), "attr.1.failure_rate": float64(0),
"attr.1.raw_string": "0", "attr.1.raw_string": "0",
"attr.1.raw_value": int64(0), "attr.1.raw_value": int64(0),
"attr.1.status": int64(0), "attr.1.status": pkg.AttributeStatus(0),
"attr.1.status_reason": "", "attr.1.status_reason": "",
"attr.1.thresh": int64(1), "attr.1.thresh": int64(1),
"attr.1.transformed_value": int64(0), "attr.1.transformed_value": int64(0),
@ -89,7 +89,7 @@ func TestSmart_Flatten_ATA(t *testing.T) {
"attr.2.failure_rate": float64(0), "attr.2.failure_rate": float64(0),
"attr.2.raw_string": "108", "attr.2.raw_string": "108",
"attr.2.raw_value": int64(108), "attr.2.raw_value": int64(108),
"attr.2.status": int64(0), "attr.2.status": pkg.AttributeStatus(0),
"attr.2.status_reason": "", "attr.2.status_reason": "",
"attr.2.thresh": int64(54), "attr.2.thresh": int64(54),
"attr.2.transformed_value": int64(0), "attr.2.transformed_value": int64(0),
@ -130,7 +130,7 @@ func TestSmart_Flatten_SCSI(t *testing.T) {
require.Equal(t, map[string]interface{}{ 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.attribute_id": "read_errors_corrected_by_eccfast",
"attr.read_errors_corrected_by_eccfast.failure_rate": float64(0), "attr.read_errors_corrected_by_eccfast.failure_rate": float64(0),
"attr.read_errors_corrected_by_eccfast.status": int64(0), "attr.read_errors_corrected_by_eccfast.status": pkg.AttributeStatus(0),
"attr.read_errors_corrected_by_eccfast.status_reason": "", "attr.read_errors_corrected_by_eccfast.status_reason": "",
"attr.read_errors_corrected_by_eccfast.thresh": int64(0), "attr.read_errors_corrected_by_eccfast.thresh": int64(0),
"attr.read_errors_corrected_by_eccfast.transformed_value": int64(0), "attr.read_errors_corrected_by_eccfast.transformed_value": int64(0),
@ -168,7 +168,7 @@ func TestSmart_Flatten_NVMe(t *testing.T) {
require.Equal(t, map[string]interface{}{ require.Equal(t, map[string]interface{}{
"attr.available_spare.attribute_id": "available_spare", "attr.available_spare.attribute_id": "available_spare",
"attr.available_spare.failure_rate": float64(0), "attr.available_spare.failure_rate": float64(0),
"attr.available_spare.status": int64(0), "attr.available_spare.status": pkg.AttributeStatus(0),
"attr.available_spare.status_reason": "", "attr.available_spare.status_reason": "",
"attr.available_spare.thresh": int64(0), "attr.available_spare.thresh": int64(0),
"attr.available_spare.transformed_value": int64(0), "attr.available_spare.transformed_value": int64(0),
@ -189,7 +189,7 @@ func TestNewSmartFromInfluxDB_ATA(t *testing.T) {
"attr.1.failure_rate": float64(0), "attr.1.failure_rate": float64(0),
"attr.1.raw_string": "108", "attr.1.raw_string": "108",
"attr.1.raw_value": int64(108), "attr.1.raw_value": int64(108),
"attr.1.status": int64(0), "attr.1.status": pkg.AttributeStatus(0),
"attr.1.status_reason": "", "attr.1.status_reason": "",
"attr.1.thresh": int64(54), "attr.1.thresh": int64(54),
"attr.1.transformed_value": int64(0), "attr.1.transformed_value": int64(0),
@ -235,7 +235,7 @@ func TestNewSmartFromInfluxDB_NVMe(t *testing.T) {
"device_protocol": pkg.DeviceProtocolNvme, "device_protocol": pkg.DeviceProtocolNvme,
"attr.available_spare.attribute_id": "available_spare", "attr.available_spare.attribute_id": "available_spare",
"attr.available_spare.failure_rate": float64(0), "attr.available_spare.failure_rate": float64(0),
"attr.available_spare.status": int64(0), "attr.available_spare.status": pkg.AttributeStatus(0),
"attr.available_spare.status_reason": "", "attr.available_spare.status_reason": "",
"attr.available_spare.thresh": int64(0), "attr.available_spare.thresh": int64(0),
"attr.available_spare.transformed_value": int64(0), "attr.available_spare.transformed_value": int64(0),
@ -274,7 +274,7 @@ func TestNewSmartFromInfluxDB_SCSI(t *testing.T) {
"device_protocol": pkg.DeviceProtocolScsi, "device_protocol": pkg.DeviceProtocolScsi,
"attr.read_errors_corrected_by_eccfast.attribute_id": "read_errors_corrected_by_eccfast", "attr.read_errors_corrected_by_eccfast.attribute_id": "read_errors_corrected_by_eccfast",
"attr.read_errors_corrected_by_eccfast.failure_rate": float64(0), "attr.read_errors_corrected_by_eccfast.failure_rate": float64(0),
"attr.read_errors_corrected_by_eccfast.status": int64(0), "attr.read_errors_corrected_by_eccfast.status": pkg.AttributeStatus(0),
"attr.read_errors_corrected_by_eccfast.status_reason": "", "attr.read_errors_corrected_by_eccfast.status_reason": "",
"attr.read_errors_corrected_by_eccfast.thresh": int64(0), "attr.read_errors_corrected_by_eccfast.thresh": int64(0),
"attr.read_errors_corrected_by_eccfast.transformed_value": int64(0), "attr.read_errors_corrected_by_eccfast.transformed_value": int64(0),
@ -328,9 +328,12 @@ func TestFromCollectorSmartInfo(t *testing.T) {
require.Equal(t, 18, len(smartMdl.Attributes)) require.Equal(t, 18, len(smartMdl.Attributes))
//check that temperature was correctly parsed //check that temperature was correctly parsed
require.Equal(t, int64(163210330144), smartMdl.Attributes["194"].(*measurements.SmartAtaAttribute).RawValue) require.Equal(t, int64(163210330144), smartMdl.Attributes["194"].(*measurements.SmartAtaAttribute).RawValue)
require.Equal(t, int64(32), smartMdl.Attributes["194"].(*measurements.SmartAtaAttribute).TransformedValue) require.Equal(t, int64(32), smartMdl.Attributes["194"].(*measurements.SmartAtaAttribute).TransformedValue)
//ensure that Scrutiny warning for a non critical attribute does not set device status to failed.
require.Equal(t, pkg.AttributeStatusWarningScrutiny, smartMdl.Attributes["3"].GetStatus())
} }
func TestFromCollectorSmartInfo_Fail_Smart(t *testing.T) { func TestFromCollectorSmartInfo_Fail_Smart(t *testing.T) {
@ -402,7 +405,7 @@ func TestFromCollectorSmartInfo_Fail_ScrutinyNonCriticalFailed(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, "WWN-test", smartMdl.DeviceWWN) require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
require.Equal(t, pkg.DeviceStatusFailedScrutiny, smartMdl.Status) require.Equal(t, pkg.DeviceStatusFailedScrutiny, smartMdl.Status)
require.Equal(t, int64(pkg.SmartAttributeStatusFailed), smartMdl.Attributes["199"].GetStatus(), require.Equal(t, pkg.AttributeStatusFailedScrutiny, smartMdl.Attributes["199"].GetStatus(),
"scrutiny should detect that %d failed (status: %d, %s)", "scrutiny should detect that %d failed (status: %d, %s)",
smartMdl.Attributes["199"].(*measurements.SmartAtaAttribute).AttributeId, smartMdl.Attributes["199"].(*measurements.SmartAtaAttribute).AttributeId,
smartMdl.Attributes["199"].GetStatus(), smartMdl.Attributes["199"].(*measurements.SmartAtaAttribute).StatusReason, smartMdl.Attributes["199"].GetStatus(), smartMdl.Attributes["199"].(*measurements.SmartAtaAttribute).StatusReason,
@ -435,7 +438,7 @@ func TestFromCollectorSmartInfo_NVMe_Fail_Scrutiny(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, "WWN-test", smartMdl.DeviceWWN) require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
require.Equal(t, pkg.DeviceStatusFailedScrutiny, smartMdl.Status) require.Equal(t, pkg.DeviceStatusFailedScrutiny, smartMdl.Status)
require.Equal(t, int64(pkg.SmartAttributeStatusFailed), smartMdl.Attributes["media_errors"].GetStatus(), require.Equal(t, pkg.AttributeStatusFailedScrutiny, smartMdl.Attributes["media_errors"].GetStatus(),
"scrutiny should detect that %s failed (status: %d, %s)", "scrutiny should detect that %s failed (status: %d, %s)",
smartMdl.Attributes["media_errors"].(*measurements.SmartNvmeAttribute).AttributeId, smartMdl.Attributes["media_errors"].(*measurements.SmartNvmeAttribute).AttributeId,
smartMdl.Attributes["media_errors"].GetStatus(), smartMdl.Attributes["media_errors"].GetStatus(),

@ -122,11 +122,17 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
// @ Private methods // @ Private methods
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
getAttributeStatusName(attribute_status){ getAttributeStatusName(attribute_status){
if(attribute_status == 0){ //from Constants.go
// AttributeStatusPassed AttributeStatus = 0
// AttributeStatusFailedSmart AttributeStatus = 1
// AttributeStatusWarningScrutiny AttributeStatus = 2
// AttributeStatusFailedScrutiny AttributeStatus = 4
if(attribute_status === 0){
return "passed" return "passed"
} else if (attribute_status == 1){ } else if (attribute_status & 1 != 0 || attribute_status & 4 != 0 ){
return "failed" return "failed"
} else if (attribute_status == 2){ } else if (attribute_status & 2 != 0){
return "warn" return "warn"
} }
return return

Loading…
Cancel
Save