Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/zabbix/zabbix.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAndrew Biba <andrew.biba@zabbix.com>2022-04-06 15:49:51 +0300
committerAndrew Biba <andrew.biba@zabbix.com>2022-04-06 15:49:51 +0300
commitf721d53c0bfc1371fe9e45c97ae6c2c5057d579b (patch)
treedbd40b870feaaef2f6b6ce01b92dc7ec6bfda394 /src
parentf64e246298a9e60ef3ed94f95c506aab4264b6bd (diff)
parent43b03544c94598d1be9454f08587b6d70a180d0e (diff)
...G.....T [ZBXNEXT-7559] updated smart.disk.discovery and smart.disk.get metrics for Zabbix agent 2 and updated S.M.A.R.T passive and active templates
* commit '43b03544c94598d1be9454f08587b6d70a180d0e': ...G.....T [ZBXNEXT-7559] updated chnagelog file ...G...... [ZBXNEXT-7559] added debug logging of output buffer in the case of system command execution failure ...G...... [ZBXNEXT-7559] added -n flag to sudo execution of smartctl for smart.* plugin in Zabbix agent 2 ...G...... [ZBXNEXT-7559] fixed smart.disk.discovery for megaraid ...G...... [ZBXNEXT-7559] code clean up ...G...... [ZBXNEXT-7559] added raidtype to smart.disk.discovery response and as second parameter for smart.disk.get ...G...... [ZBXNEXT-7559] code clean up ...G...... [ZBXNEXT-7559] fixed smart.disk.get[path] attribute raw field json key ...G...... [ZBXNEXT-7559] changed self_test_passed output key to better match rest of keys ...G...... [ZBXNEXT-7559] changed attributes in smart.disk.discovery to be an array .......... [ZBXNEXT-7559] added change log ...G...... [ZBXNEXT-7559] fixed smart.disk.discovery path output field for raid devices ...G...... [ZBXNEXT-7559] fixed smart.disk.get[path] for raid devices ...G...... [ZBXNEXT-7559] code clean up ...G...... [ZBXNEXT-7559] code clean up ...G...... [ZBXNEXT-7559] updated unit test ...G...... [ZBXNEXT-7559] added unit test ...G...... [ZBXNEXT-7559] added PATH parameter to smart.disk.get key in Zabbix agent 2 ...G.....T [ZBXNEXT-7559] updated smart.disk.discovery, smart.disk.get metrics for Zabbix agent 2 and S.M.A.R.T passive and active templates
Diffstat (limited to 'src')
-rw-r--r--src/go/pkg/zbxcmd/zbxcmd_nix.go5
-rw-r--r--src/go/plugins/smart/smart.go270
-rw-r--r--src/go/plugins/smart/smart_nix.go2
-rw-r--r--src/go/plugins/smart/smart_test.go422
-rw-r--r--src/go/plugins/smart/smartfs.go125
5 files changed, 748 insertions, 76 deletions
diff --git a/src/go/pkg/zbxcmd/zbxcmd_nix.go b/src/go/pkg/zbxcmd/zbxcmd_nix.go
index addd629406e..59f83a49303 100644
--- a/src/go/pkg/zbxcmd/zbxcmd_nix.go
+++ b/src/go/pkg/zbxcmd/zbxcmd_nix.go
@@ -1,5 +1,4 @@
//go:build !windows
-// +build !windows
/*
** Zabbix
@@ -65,7 +64,9 @@ func execute(s string, timeout time.Duration, path string, strict bool) (string,
// we need to check error after t.Stop so we can inform the user if timeout was reached and Zabbix agent2 terminated the command
if strict && werr != nil && !errors.Is(werr, syscall.ECHILD) {
- return "", fmt.Errorf("Command execution failed: %s", werr)
+ log.Debugf("Command [%s] execution failed: %s\n%s", s, werr, b.String())
+
+ return "", fmt.Errorf("Command execution failed: %w", werr)
}
if MaxExecuteOutputLenB <= len(b.String()) {
diff --git a/src/go/plugins/smart/smart.go b/src/go/plugins/smart/smart.go
index f71407b6b97..36bb16d9b8a 100644
--- a/src/go/plugins/smart/smart.go
+++ b/src/go/plugins/smart/smart.go
@@ -21,12 +21,27 @@ package smart
import (
"encoding/json"
+ "fmt"
+ "strings"
"zabbix.com/pkg/conf"
"zabbix.com/pkg/plugin"
"zabbix.com/pkg/zbxerr"
)
+const (
+ twoParameters = 2
+ oneParameter = 1
+ all = 0
+
+ firstParameter = 0
+ secondParameter = 1
+
+ diskGet = "smart.disk.get"
+ diskDiscovery = "smart.disk.discovery"
+ attributeDiscovery = "smart.attribute.discovery"
+)
+
// Options -
type Options struct {
plugin.SystemOptions `conf:"optional,name=System"`
@@ -61,7 +76,7 @@ func (p *Plugin) Validate(options interface{}) error {
// Export -
func (p *Plugin) Export(key string, params []string, ctx plugin.ContextProvider) (result interface{}, err error) {
- if len(params) > 0 {
+ if len(params) > 0 && key != diskGet {
return nil, zbxerr.ErrorTooManyParameters
}
@@ -72,84 +87,217 @@ func (p *Plugin) Export(key string, params []string, ctx plugin.ContextProvider)
var jsonArray []byte
switch key {
- case "smart.disk.discovery":
- out := []device{}
-
- r, err := p.execute(false)
+ case diskDiscovery:
+ jsonArray, err = p.diskDiscovery()
if err != nil {
- return nil, err
- }
-
- for _, dev := range r.devices {
- out = append(out, device{
- Name: cutPrefix(dev.Info.Name),
- DeviceType: getType(dev.Info.DevType, dev.RotationRate, dev.SmartAttributes.Table),
- Model: dev.ModelName, SerialNumber: dev.SerialNumber,
- })
+ return nil, zbxerr.ErrorCannotFetchData.Wrap(err)
}
- jsonArray, err = json.Marshal(out)
+ case diskGet:
+ jsonArray, err = p.diskGet(params)
if err != nil {
- return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err)
+ return nil, zbxerr.ErrorCannotFetchData.Wrap(err)
}
- case "smart.disk.get":
- r, err := p.execute(true)
+ case attributeDiscovery:
+ jsonArray, err = p.attributeDiscovery()
if err != nil {
- return nil, err
+ return nil, zbxerr.ErrorCannotFetchData.Wrap(err)
}
- fields, err := setDiskFields(r.jsonDevices)
- if err != nil {
- return nil, err
- }
+ default:
+ return nil, zbxerr.ErrorUnsupportedMetric
+ }
- if fields == nil {
- jsonArray, err = json.Marshal([]string{})
- if err != nil {
- return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err)
- }
+ return string(jsonArray), nil
+}
- break
- }
+func (p *Plugin) diskDiscovery() (jsonArray []byte, err error) {
+ out := []device{}
+
+ r, err := p.execute(false)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, dev := range r.devices {
+ out = append(out, device{
+ Name: cutPrefix(dev.Info.Name),
+ DeviceType: getType(dev.Info.DevType, dev.RotationRate, dev.SmartAttributes.Table),
+ Model: dev.ModelName,
+ SerialNumber: dev.SerialNumber,
+ Path: dev.Info.name,
+ RaidType: dev.Info.raidType,
+ Attributes: getAttributes(dev),
+ })
+ }
+
+ jsonArray, err = json.Marshal(out)
+ if err != nil {
+ return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err)
+ }
+
+ return
+}
+
+func (p *Plugin) diskGet(params []string) ([]byte, error) {
+ switch len(params) {
+ case twoParameters:
+ return p.diskGetSingle(params[firstParameter], params[secondParameter])
+ case oneParameter:
+ return p.diskGetSingle(params[firstParameter], "")
+ case all:
+ return p.diskGetAll()
+ default:
+ return nil, zbxerr.ErrorTooManyParameters
+ }
+}
+
+func (p *Plugin) diskGetSingle(path, raidType string) ([]byte, error) {
+ executable := path
+
+ if raidType != "" {
+ executable = fmt.Sprintf("%s -d %s", executable, raidType)
+ }
+
+ device, err := p.executeSingle(executable)
+ if err != nil {
+ return nil, err
+ }
+
+ out, err := setSingleDiskFields(device)
+ if err != nil {
+ return nil, err
+ }
+
+ jsonArray, err := json.Marshal(out)
+ if err != nil {
+ return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err)
+ }
+
+ return jsonArray, nil
+}
+
+func (p *Plugin) diskGetAll() (jsonArray []byte, err error) {
+ r, err := p.execute(true)
+ if err != nil {
+ return nil, err
+ }
- jsonArray, err = json.Marshal(fields)
+ fields, err := setDiskFields(r.jsonDevices)
+ if err != nil {
+ return nil, err
+ }
+
+ if fields == nil {
+ jsonArray, err = json.Marshal([]string{})
if err != nil {
return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err)
}
- case "smart.attribute.discovery":
- out := []attribute{}
+ return
+ }
- r, err := p.execute(false)
- if err != nil {
- return nil, err
- }
+ jsonArray, err = json.Marshal(fields)
+ if err != nil {
+ return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err)
+ }
- for _, dev := range r.devices {
- t := getAttributeType(dev.Info.DevType, dev.RotationRate, dev.SmartAttributes.Table)
- for _, attr := range dev.SmartAttributes.Table {
- out = append(
- out, attribute{
- Name: cutPrefix(dev.Info.Name),
- DeviceType: t,
- ID: attr.ID,
- Attrname: attr.Attrname,
- Thresh: attr.Thresh,
- })
- }
+ return
+}
+
+func (p *Plugin) attributeDiscovery() (jsonArray []byte, err error) {
+ out := []attribute{}
+
+ r, err := p.execute(false)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, dev := range r.devices {
+ t := getAttributeType(dev.Info.DevType, dev.RotationRate, dev.SmartAttributes.Table)
+ for _, attr := range dev.SmartAttributes.Table {
+ out = append(
+ out, attribute{
+ Name: cutPrefix(dev.Info.Name),
+ DeviceType: t,
+ ID: attr.ID,
+ Attrname: attr.Attrname,
+ Thresh: attr.Thresh,
+ })
}
+ }
- jsonArray, err = json.Marshal(out)
- if err != nil {
- return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err)
+ jsonArray, err = json.Marshal(out)
+ if err != nil {
+ return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err)
+ }
+
+ return
+}
+
+// setSingleDiskFields goes through provided device json data and sets required output fields.
+// It returns an error if there is an issue with unmarshal for the provided input JSON map.
+func setSingleDiskFields(dev []byte) (out map[string]interface{}, err error) {
+ attr := make(map[string]interface{})
+ if err = json.Unmarshal(dev, &attr); err != nil {
+ return out, zbxerr.ErrorCannotUnmarshalJSON.Wrap(err)
+ }
+
+ var sd singleDevice
+ if err = json.Unmarshal(dev, &sd); err != nil {
+ return out, zbxerr.ErrorCannotUnmarshalJSON.Wrap(err)
+ }
+
+ diskType := getType(getTypeFromJson(attr), getRateFromJson(attr), getTablesFromJson(attr))
+
+ out = map[string]interface{}{}
+ out["disk_type"] = diskType
+ out["firmware_version"] = sd.Firmware
+ out["model_name"] = sd.ModelName
+ out["serial_number"] = sd.SerialNumber
+ out["exit_status"] = sd.Smartctl.ExitStatus
+
+ var errors []string
+ for _, msg := range sd.Smartctl.Messages {
+ errors = append(errors, msg.Str)
+ }
+
+ out["error"] = strings.Join(errors, ", ")
+ out["self_test_passed"] = setSelfTest(sd)
+
+ if diskType == nvmeType {
+ out["temperature"] = sd.HealthLog.Temperature
+ out["power_on_time"] = sd.HealthLog.PowerOnTime
+ out["critical_warning"] = sd.HealthLog.CriticalWarning
+ out["media_errors"] = sd.HealthLog.MediaErrors
+ out["percentage_used"] = sd.HealthLog.Percentage_used
+ } else {
+ out["temperature"] = sd.Temperature.Current
+ out["power_on_time"] = sd.PowerOnTime.Hours
+ out["critical_warning"] = 0
+ out["media_errors"] = 0
+ out["percentage_used"] = 0
+ }
+
+ for _, a := range sd.SmartAttributes.Table {
+ if a.Name == unknownAttrName {
+ continue
}
- default:
- return nil, zbxerr.ErrorUnsupportedMetric
+ out[strings.ToLower(a.Name)] = singleRequestAttribute{a.Raw.Value, a.Raw.Str}
}
- return string(jsonArray), nil
+ return
+}
+
+// setSelfTest determines if device is self test capable and if the test is passed.
+func setSelfTest(sd singleDevice) *bool {
+ if sd.Data.Capabilities.SelfTestsSupported {
+ return &sd.Data.SelfTest.Status.Passed
+ }
+
+ return nil
}
// setDiskFields goes through provided device json map and sets disk_name
@@ -242,6 +390,18 @@ func getAttributeType(devType string, rate int, tables []table) string {
return getTypeByRateAndAttr(rate, tables)
}
+func getAttributes(in deviceParser) (out string) {
+ for _, table := range in.SmartAttributes.Table {
+ if table.Attrname == unknownAttrName {
+ continue
+ }
+
+ out = out + " " + table.Attrname
+ }
+
+ return strings.TrimSpace(out)
+}
+
func getType(devType string, rate int, tables []table) string {
switch devType {
case nvmeType:
diff --git a/src/go/plugins/smart/smart_nix.go b/src/go/plugins/smart/smart_nix.go
index 447406c1d3b..5e87a39de9c 100644
--- a/src/go/plugins/smart/smart_nix.go
+++ b/src/go/plugins/smart/smart_nix.go
@@ -39,7 +39,7 @@ func (p *Plugin) executeSmartctl(args string, strict bool) ([]byte, error) {
var out string
var err error
- executable := fmt.Sprintf("sudo %s %s", path, args)
+ executable := fmt.Sprintf("sudo -n %s %s", path, args)
p.Tracef("executing smartctl command: %s", executable)
diff --git a/src/go/plugins/smart/smart_test.go b/src/go/plugins/smart/smart_test.go
index eb73c16c8c6..bce0bc96f34 100644
--- a/src/go/plugins/smart/smart_test.go
+++ b/src/go/plugins/smart/smart_test.go
@@ -25,20 +25,390 @@ import (
"testing"
)
+const (
+ nvme = `{
+ "smartctl": {
+ "exit_status": 0
+ },
+ "device": {
+ "name": "/dev/nvme0",
+ "type": "nvme"
+ },
+ "model_name": "INTEL SSDPEKNW512G8H",
+ "serial_number": "BTNH115603K7512A",
+ "firmware_version": "HPS1",
+ "smart_status": {
+ "passed": true
+ },
+ "nvme_smart_health_information_log": {
+ "critical_warning": 0,
+ "temperature": 25,
+ "percentage_used": 0,
+ "power_on_hours": 2222,
+ "media_errors": 0
+ }
+ }`
+
+ hdd = `{
+ "json_format_version": [
+ 1,
+ 0
+ ],
+ "smartctl": {
+ "version": [
+ 7,
+ 2
+ ],
+ "svn_revision": "5155",
+ "platform_info": "x86_64-linux-5.13.0-30-generic",
+ "build_info": "(local build)",
+ "argv": [
+ "smartctl",
+ "-a",
+ "-j",
+ "/dev/sda"
+ ],
+ "exit_status": 0
+ },
+ "device": {
+ "name": "/dev/sda",
+ "info_name": "/dev/sda [SAT]",
+ "type": "sat",
+ "protocol": "ATA"
+ },
+ "model_family": "Seagate Surveillance",
+ "model_name": "ST1000VX000-1ES162",
+ "serial_number": "Z4Y7SJBD",
+ "wwn": {
+ "naa": 5,
+ "oui": 3152,
+ "id": 2071267458
+ },
+ "firmware_version": "CV26",
+ "rotation_rate": 7200,
+ "ata_smart_data": {
+ "self_test": {
+ "status": {
+ "value": 0,
+ "string": "completed without error",
+ "passed": true
+ }
+ },
+ "capabilities": {
+ "self_tests_supported": true
+ }
+ },
+ "ata_smart_attributes": {
+ "table": [
+ {
+ "id": 1,
+ "name": "Raw_Read_Error_Rate",
+ "raw": {
+ "value": 182786912,
+ "string": "182786912"
+ }
+ },
+ {
+ "id": 3,
+ "name": "Spin_Up_Time",
+ "raw": {
+ "value": 0,
+ "string": "0"
+ }
+ }
+ ]
+ },
+ "power_on_time": {
+ "hours": 39153
+ },
+ "temperature": {
+ "current": 30
+ }
+ }`
+
+ ssd = `
+ {
+ "json_format_version": [
+ 1,
+ 0
+ ],
+ "smartctl": {
+ "exit_status": 0
+ },
+ "device": {
+ "name": "/dev/sda",
+ "info_name": "/dev/sda",
+ "type": "ata",
+ "protocol": "ATA"
+ },
+ "model_name": "TS128GMTS800",
+ "serial_number": "D486530350",
+ "firmware_version": "O1225G",
+ "rotation_rate": 0,
+ "smart_status": {
+ "passed": true
+ },
+ "ata_smart_data": {
+ "self_test": {
+ "status": {
+ "passed": true
+ }
+ },
+ "capabilities": {
+ "values": [
+ 113,
+ 2
+ ],
+ "self_tests_supported": true
+ }
+ },
+ "ata_smart_attributes": {
+ "table": [
+ {
+ "name": "Raw_Read_Error_Rate",
+ "value": 100,
+ "raw": {
+ "value": 0,
+ "string": "0"
+ }
+ },
+ {
+ "name": "Reallocated_Sector_Ct",
+ "raw": {
+ "value": 10,
+ "string": "10"
+ }
+ }
+ ]
+ },
+ "power_on_time": {
+ "hours": 732
+ },
+ "temperature": {
+ "current": 18
+ }
+ }`
+
+ ssdUnknown = `
+ {
+ "json_format_version": [
+ 1,
+ 0
+ ],
+ "smartctl": {
+ "exit_status": 0
+ },
+ "device": {
+ "name": "/dev/sda",
+ "info_name": "/dev/sda",
+ "type": "ata",
+ "protocol": "ATA"
+ },
+ "model_name": "TS128GMTS800",
+ "serial_number": "D486530350",
+ "firmware_version": "O1225G",
+ "rotation_rate": 0,
+ "smart_status": {
+ "passed": true
+ },
+ "ata_smart_data": {
+ "self_test": {
+ "status": {
+ "passed": true
+ }
+ },
+ "capabilities": {
+ "values": [
+ 113,
+ 2
+ ],
+ "self_tests_supported": true
+ }
+ },
+ "ata_smart_attributes": {
+ "table": [
+ {
+ "name": "Raw_Read_Error_Rate",
+ "value": 100,
+ "raw": {
+ "value": 0,
+ "string": "0"
+ }
+ },
+ {
+ "name": "Unknown_Attribute",
+ "value": 0,
+ "raw": {
+ "value": 0,
+ "string": "0"
+ }
+ },
+ {
+ "name": "Reallocated_Sector_Ct",
+ "raw": {
+ "value": 10,
+ "string": "10"
+ }
+ }
+ ]
+ },
+ "power_on_time": {
+ "hours": 732
+ },
+ "temperature": {
+ "current": 18
+ }
+ }`
+)
+
var (
table1 = table{"test1", 1, 11}
table2 = table{"test2", 2, 22}
table3 = table{"test3", 3, 33}
table4 = table{"test4", 4, 44}
attrTable = table{"Spin_Up_Time", 5, 55}
+ unknown = table{"Unknown_Attribute", 0, 0}
)
+func Test_setSingleDiskFields(t *testing.T) {
+ var nilReference *bool
+
+ selftestSuccess := true
+
+ type args struct {
+ dev []byte
+ }
+ tests := []struct {
+ name string
+ args args
+ wantOut map[string]interface{}
+ wantErr bool
+ }{
+ {
+ "nvme_device",
+ args{[]byte(nvme)},
+ map[string]interface{}{
+ "critical_warning": 0,
+ "disk_type": "nvme",
+ "error": "",
+ "exit_status": 0,
+ "firmware_version": "HPS1",
+ "media_errors": 0,
+ "model_name": "INTEL SSDPEKNW512G8H",
+ "percentage_used": 0,
+ "power_on_time": 2222,
+ "self_test_passed": nilReference,
+ "serial_number": "BTNH115603K7512A",
+ "temperature": 25,
+ },
+ false,
+ },
+ {
+ "hdd_device",
+ args{[]byte(hdd)},
+ map[string]interface{}{
+ "critical_warning": 0,
+ "disk_type": "hdd",
+ "error": "",
+ "exit_status": 0,
+ "firmware_version": "CV26",
+ "media_errors": 0,
+ "model_name": "ST1000VX000-1ES162",
+ "percentage_used": 0,
+ "power_on_time": 39153,
+ "self_test_passed": &selftestSuccess,
+ "serial_number": "Z4Y7SJBD",
+ "temperature": 30,
+ "raw_read_error_rate": singleRequestAttribute{
+ Value: 182786912,
+ Raw: "182786912",
+ },
+ "spin_up_time": singleRequestAttribute{
+ Value: 0,
+ Raw: "0",
+ },
+ },
+ false,
+ },
+ {
+ "ssd_device",
+ args{[]byte(ssd)},
+ map[string]interface{}{
+ "critical_warning": 0,
+ "disk_type": "ssd",
+ "error": "",
+ "exit_status": 0,
+ "firmware_version": "O1225G",
+ "media_errors": 0,
+ "model_name": "TS128GMTS800",
+ "percentage_used": 0,
+ "power_on_time": 732,
+ "self_test_passed": &selftestSuccess,
+ "serial_number": "D486530350",
+ "temperature": 18,
+ "raw_read_error_rate": singleRequestAttribute{
+ Value: 0,
+ Raw: "0",
+ },
+ "reallocated_sector_ct": singleRequestAttribute{
+ Value: 10,
+ Raw: "10",
+ },
+ },
+ false,
+ },
+ {
+ "ssd_device_with_unknown_attribute",
+ args{[]byte(ssdUnknown)},
+ map[string]interface{}{
+ "critical_warning": 0,
+ "disk_type": "ssd",
+ "error": "",
+ "exit_status": 0,
+ "firmware_version": "O1225G",
+ "media_errors": 0,
+ "model_name": "TS128GMTS800",
+ "percentage_used": 0,
+ "power_on_time": 732,
+ "self_test_passed": &selftestSuccess,
+ "serial_number": "D486530350",
+ "temperature": 18,
+ "raw_read_error_rate": singleRequestAttribute{
+ Value: 0,
+ Raw: "0",
+ },
+ "reallocated_sector_ct": singleRequestAttribute{
+ Value: 10,
+ Raw: "10",
+ },
+ },
+ false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ gotOut, err := setSingleDiskFields(tt.args.dev)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("setSingleDiskFields() error = %v, wantErr %v", err, tt.wantErr)
+
+ return
+ }
+
+ if !reflect.DeepEqual(gotOut, tt.wantOut) {
+ t.Errorf("setSingleDiskFields() = %v, want %v", gotOut, tt.wantOut)
+ }
+ })
+ }
+}
+
func Test_setDiskFields(t *testing.T) {
- //nolint:lll
- jsonSdaStr := `{"device": {"name": "/dev/sda","info_name": "/dev/sda [SAT]","type": "sat","protocol": "ATA"},"rotation_rate": 0}`
- //nolint:lll
+ jsonSdaStr := `{
+ "device": {"name": "/dev/sda","info_name": "/dev/sda [SAT]","type": "sat","protocol": "ATA"},"rotation_rate": 0
+ }`
sdaOutStr := map[string]interface{}{
- "device": map[string]interface{}{"name": "/dev/sda", "info_name": "/dev/sda [SAT]", "type": "sat", "protocol": "ATA"},
+ "device": map[string]interface{}{
+ "name": "/dev/sda", "info_name": "/dev/sda [SAT]", "type": "sat", "protocol": "ATA",
+ },
"disk_name": "sda", "disk_type": "ssd", "rotation_rate": 0,
}
@@ -196,6 +566,50 @@ func Test_getAttributeType(t *testing.T) {
}
}
+func Test_getAttributes(t *testing.T) {
+ type args struct {
+ in deviceParser
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ }{
+ {
+ "attributes_set",
+ args{deviceParser{SmartAttributes: smartAttributes{Table: []table{table1, table2}}}},
+ "test1 test2",
+ },
+ {
+ "attributes_table_empty",
+ args{deviceParser{SmartAttributes: smartAttributes{Table: []table{}}}},
+ "",
+ },
+ {
+ "unknown_attributes_table_empty",
+ args{deviceParser{SmartAttributes: smartAttributes{Table: []table{table1, unknown, table2}}}},
+ "test1 test2",
+ },
+ {
+ "attributes_missing",
+ args{deviceParser{}},
+ "",
+ },
+ {
+ "parser_missing",
+ args{},
+ "",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := getAttributes(tt.args.in); got != tt.want {
+ t.Errorf("getAttributes() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
func Test_getType(t *testing.T) {
type args struct {
devType string
diff --git a/src/go/plugins/smart/smartfs.go b/src/go/plugins/smart/smartfs.go
index a01b87c4422..b695e853ba4 100644
--- a/src/go/plugins/smart/smartfs.go
+++ b/src/go/plugins/smart/smartfs.go
@@ -42,7 +42,8 @@ const (
ssdType = "ssd"
hddType = "hdd"
- spinUpAttrName = "Spin_Up_Time"
+ spinUpAttrName = "Spin_Up_Time"
+ unknownAttrName = "Unknown_Attribute"
ataSmartAttrFieldName = "ata_smart_attributes"
ataSmartAttrTableFieldName = "table"
@@ -68,12 +69,79 @@ type device struct {
DeviceType string `json:"{#DISKTYPE}"`
Model string `json:"{#MODEL}"`
SerialNumber string `json:"{#SN}"`
+ Path string `json:"{#PATH}"`
+ RaidType string `json:"{#RAIDTYPE}"`
+ Attributes string `json:"{#ATTRIBUTES}"`
}
type jsonDevice struct {
serialNumber string
jsonData string
}
+type singleDevice struct {
+ DiskType string `json:"disk_type"`
+ Firmware string `json:"firmware_version"`
+ ModelName string `json:"model_name"`
+ SerialNumber string `json:"serial_number"`
+ Smartctl smartctlField `json:"smartctl"`
+ HealthLog healthLog `json:"nvme_smart_health_information_log"`
+ SmartAttributes singelRequestTables `json:"ata_smart_attributes"`
+ Data ataData `json:"ata_smart_data"`
+ Temperature temperature `json:"temperature"`
+ PowerOnTime power `json:"power_on_time"`
+ Err string `json:"-"`
+ SelfTest bool `json:"-"`
+}
+
+type healthLog struct {
+ Temperature int `json:"temperature"`
+ PowerOnTime int `json:"power_on_hours"`
+ CriticalWarning int `json:"critical_warning"`
+ MediaErrors int `json:"media_errors"`
+ Percentage_used int `json:"percentage_used"`
+}
+
+type temperature struct {
+ Current int `json:"current"`
+}
+
+type power struct {
+ Hours int `json:"hours"`
+}
+
+type singelRequestTables struct {
+ Table []singelRequestRaw `json:"table"`
+}
+
+type singelRequestRaw struct {
+ Name string `json:"name"`
+ Raw rawField `json:"raw"`
+}
+
+type singleRequestAttribute struct {
+ Value int `json:"value"`
+ Raw string `json:"raw"`
+}
+
+type rawField struct {
+ Value int `json:"value"`
+ Str string `json:"string"`
+}
+
+type ataData struct {
+ SelfTest selfTest `json:"self_test"`
+ Capabilities capabilities `json:"capabilities"`
+}
+type capabilities struct {
+ SelfTestsSupported bool `json:"self_tests_supported"`
+}
+type selfTest struct {
+ Status status `json:"status"`
+}
+type status struct {
+ Passed bool `json:"passed"`
+}
+
type attribute struct {
Name string `json:"{#NAME}"`
DeviceType string `json:"{#DISKTYPE}"`
@@ -96,6 +164,8 @@ type deviceInfo struct {
Name string `json:"name"`
InfoName string `json:"info_name"`
DevType string `json:"type"`
+ name string `json:"-"`
+ raidType string `json:"-"`
}
type smartctl struct {
@@ -184,7 +254,17 @@ func (p *Plugin) execute(jsonRunner bool) (*runner, error) {
return r, err
}
-//executeBase executed runners for basic devices retrived from smartctl
+// executeSingle returns device data for single device from smartctl based on provided path.
+func (p *Plugin) executeSingle(path string) (device []byte, err error) {
+ device, err = p.executeSmartctl(fmt.Sprintf("-a %s -j", path), false)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to execute smartctl: %w.", err)
+ }
+
+ return
+}
+
+//executeBase executed runners for basic devices retrieved from smartctl.
func (r *runner) executeBase(basicDev []deviceInfo, jsonRunner bool) error {
r.startBasicRunners(jsonRunner)
@@ -197,7 +277,7 @@ func (r *runner) executeBase(basicDev []deviceInfo, jsonRunner bool) error {
return r.waitForExecution()
}
-//executeRaids executes runners for raid devices (except megaraid) retrived from smartctl
+//executeRaids executes runners for raid devices (except megaraid) retrieved from smartctl
func (r *runner) executeRaids(raids []deviceInfo, jsonRunner bool) {
raidTypes := []string{"3ware", "areca", "cciss", "sat"}
@@ -216,7 +296,7 @@ func (r *runner) executeRaids(raids []deviceInfo, jsonRunner bool) {
r.waitForRaidExecution(r.raidDone)
}
-//executeMegaRaids executes runners for megaraid devices retrived from smartctl
+//executeMegaRaids executes runners for megaraid devices retrieved from smartctl
func (r *runner) executeMegaRaids(megaraids []deviceInfo, jsonRunner bool) {
r.megaraids = make(chan raidParameters, len(megaraids))
@@ -381,6 +461,8 @@ func (r *runner) getBasicDevices(jsonRunner bool) {
return
}
+ dp.Info.name = name
+
r.mux.Lock()
if jsonRunner {
@@ -454,10 +536,14 @@ runner:
}
if dp.SmartStatus != nil {
+ dp.Info.name = raid.name
+
if raid.rType == satType {
dp.Info.Name = fmt.Sprintf("%s %s", raid.name, raid.rType)
+ dp.Info.raidType = raid.rType
} else {
dp.Info.Name = fmt.Sprintf("%s %s,%d", raid.name, raid.rType, i)
+ dp.Info.raidType = fmt.Sprintf("%s,%d", raid.rType, i)
}
if r.setRaidDevices(dp, device, raid.rType, jsonRunner) {
@@ -523,6 +609,8 @@ func (r *runner) getMegaRaidDevices(jsonRunner bool) {
}
dp.Info.Name = fmt.Sprintf("%s %s", raid.name, raid.rType)
+ dp.Info.name = raid.name
+ dp.Info.raidType = raid.rType
r.setRaidDevices(dp, device, raid.rType, jsonRunner)
}
@@ -619,32 +707,41 @@ func (dp *deviceParser) checkErr() (err error) {
func (p *Plugin) getDevices() (basic, raid, megaraid []deviceInfo, err error) {
basicTmp, err := p.scanDevices("--scan -j")
if err != nil {
- return nil, nil, nil, fmt.Errorf("Failed to scan for devices: %s.", err)
+ return nil, nil, nil, fmt.Errorf("Failed to scan for devices: %w.", err)
}
raidTmp, err := p.scanDevices("--scan -d sat -j")
if err != nil {
- return nil, nil, nil, fmt.Errorf("Failed to scan for sat devices: %s.", err)
+ return nil, nil, nil, fmt.Errorf("Failed to scan for sat devices: %w.", err)
}
-raid:
- for _, tmp := range basicTmp {
- for _, r := range raidTmp {
+ basic, raid, megaraid = formatDeviceOutput(basicTmp, raidTmp)
+
+ return
+}
+
+// formatDeviceOutput removes raid devices from basic device list and separates megaraid devices from the rest of raid
+// devices.
+func formatDeviceOutput(basic, raid []deviceInfo) (basicDev, raidDev, megaraidDev []deviceInfo) {
+loop:
+ for _, tmp := range basic {
+ for _, r := range raid {
if tmp.Name == r.Name {
- continue raid
+ continue loop
}
}
- basic = append(basic, tmp)
+ basicDev = append(basicDev, tmp)
}
- for _, r := range raidTmp {
+ for _, r := range raid {
if strings.Contains(r.DevType, "megaraid") {
- megaraid = append(megaraid, r)
+ megaraidDev = append(megaraidDev, r)
+
continue
}
- raid = append(raid, r)
+ raidDev = append(raidDev, r)
}
return