/* ** Zabbix ** Copyright (C) 2001-2023 Zabbix SIA ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **/ package smart import ( "fmt" "reflect" "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) { 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", }, "disk_name": "sda", "disk_type": "ssd", "rotation_rate": 0, } type args struct { deviceJsons map[string]jsonDevice } tests := []struct { name string args args want []interface{} wantErr bool }{ {"+one_drive", args{map[string]jsonDevice{"/dev/sda": {jsonData: jsonSdaStr}}}, []interface{}{sdaOutStr}, false}, {"-failed_json", args{map[string]jsonDevice{"/dev/sda": {jsonData: `{"device":}`}}}, nil, true}, {"-failed_device_data_json", args{map[string]jsonDevice{"/dev/sda": {jsonData: `{"device": foo,"rotation_rate": 0}`}}}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := setDiskFields(tt.args.deviceJsons) if (err != nil) != tt.wantErr { t.Errorf("setDiskFields() error = %v, wantErr %v", err, tt.wantErr) return } if fmt.Sprint(got) != fmt.Sprint(tt.want) { t.Errorf("setDiskFields() = %v, want %v", got, tt.want) } }) } } func Test_getRateFromJson(t *testing.T) { type args struct { in map[string]interface{} } tests := []struct { name string args args wantOut int }{ {"rate", args{map[string]interface{}{"rotation_rate": 10}}, 10}, {"multiple_fields", args{map[string]interface{}{"foobar": "abc", "rotation_rate": 10}}, 10}, {"no_rate", args{map[string]interface{}{"foobar": "abc"}}, 0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if gotOut := getRateFromJson(tt.args.in); gotOut != tt.wantOut { t.Errorf("getRateFromJson() = %v, want %v", gotOut, tt.wantOut) } }) } } func Test_getTypeFromJson(t *testing.T) { map1 := make(map[string]interface{}) map1["device"] = map[string]interface{}{"type": "sat"} map2 := make(map[string]interface{}) map2["device"] = map[string]interface{}{"type": "sat", "foobar": "abc"} map3 := make(map[string]interface{}) map3["device"] = map[string]interface{}{"foobar": "abc"} type args struct { in map[string]interface{} } tests := []struct { name string args args wantOut string }{ {"type", args{map1}, "sat"}, {"multiple_fields", args{map2}, "sat"}, {"no_type", args{map3}, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if gotOut := getTypeFromJson(tt.args.in); gotOut != tt.wantOut { t.Errorf("getTypeFromJson() = %v, want %v", gotOut, tt.wantOut) } }) } } func Test_getTablesFromJson(t *testing.T) { map1 := make(map[string]interface{}) map1["table"] = []interface{}{table1, table2, attrTable} map2 := make(map[string]interface{}) map2["table"] = []interface{}{table1, table2, table4} attrTable1 := map[string]interface{}{"ata_smart_attributes": map1} attrTable2 := map[string]interface{}{"ata_smart_attributes": map2} attrTable3 := map[string]interface{}{"ata_smart_attributes": nil} attrTable4 := map[string]interface{}{"ata_smart_attributes": []table{}} attrTable5 := map[string]interface{}{"ata_smart_attributes": map[string][]table{}} type args struct { in map[string]interface{} } tests := []struct { name string args args want []table }{ {"attr_table", args{attrTable1}, []table{table1, table2, attrTable}}, {"no_attr_table", args{attrTable2}, []table{table1, table2, table4}}, {"no_table", args{attrTable3}, nil}, {"incorrect_table_value", args{attrTable4}, nil}, {"empty_map", args{attrTable5}, nil}, {"no_ata_attributes", args{nil}, nil}, {"empty_ata_attributes", args{map[string]interface{}{}}, nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := getTablesFromJson(tt.args.in); !reflect.DeepEqual(got, tt.want) { t.Errorf("getTablesFromJson() = %v, want %v", got, tt.want) } }) } } func Test_getAttributeType(t *testing.T) { type args struct { devType string rate int tables []table } tests := []struct { name string args args want string }{ {"ssd_no_tables", args{"SAT", 0, nil}, "ssd"}, {"ssd_tables_no_spin_up_table", args{"SAT", 0, []table{table1, table2, table4}}, "ssd"}, {"hdd_no_tables", args{"SAT", 12, nil}, "hdd"}, {"hdd_rate_spin_up_table", args{"SAT", 12, []table{table1, table2, table4, attrTable}}, "hdd"}, {"hdd_no_rate_spin_up_table", args{"SAT", 0, []table{table1, table2, table4, attrTable}}, "hdd"}, {"hdd_no_spin_up_table", args{"SAT", 12, []table{table1, table2, table4}}, "hdd"}, {"unknown_no_attr_table", args{"unknown", 1000, []table{table1, table2, table4}}, "unknown"}, {"unknown_value_table", args{"unknown", 1000, []table{table1, table2, table4, attrTable}}, "unknown"}, {"unknown_no_rate_no_tables", args{"unknown", 0, nil}, "unknown"}, {"unknown_no_rate_no_attr_table", args{"unknown", 0, []table{table1, table2, table4}}, "unknown"}, {"unknown_no_rate_value_table", args{"unknown", 0, []table{table1, table2, table4, attrTable}}, "unknown"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := getAttributeType(tt.args.devType, tt.args.rate, tt.args.tables); got != tt.want { t.Errorf("getAttributeType() = %v, want %v", got, tt.want) } }) } } 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 rate int tables []table } tests := []struct { name string args args wantOut string }{ {"ssd_no_tables", args{"SAT", 0, nil}, "ssd"}, {"ssd_tables_no_spin_up_table", args{"SAT", 0, []table{table1, table2, table4}}, "ssd"}, {"hdd_no_tables", args{"SAT", 12, nil}, "hdd"}, {"hdd_rate_spin_up_table", args{"SAT", 12, []table{table1, table2, table4, attrTable}}, "hdd"}, {"hdd_no_rate_spin_up_table", args{"SAT", 0, []table{table1, table2, table4, attrTable}}, "hdd"}, {"hdd_no_spin_up_table", args{"SAT", 12, []table{table1, table2, table4}}, "hdd"}, {"nvme_no_tables", args{"nvme", 1000, nil}, "nvme"}, {"nvme_no_attr_table", args{"nvme", 1000, []table{table1, table2, table4}}, "nvme"}, {"nvme_value_table", args{"nvme", 1000, []table{table1, table2, table4, attrTable}}, "nvme"}, {"nvme_no_rate_no_tables", args{"nvme", 0, nil}, "nvme"}, {"nvme_no_rate_no_attr_table", args{"nvme", 0, []table{table1, table2, table4}}, "nvme"}, {"nvme_no_rate_value_table", args{"nvme", 0, []table{table1, table2, table4, attrTable}}, "nvme"}, {"unknown_no_tables", args{"unknown", 1000, nil}, "unknown"}, {"unknown_no_attr_table", args{"unknown", 1000, []table{table1, table2, table4}}, "unknown"}, {"unknown_value_table", args{"unknown", 1000, []table{table1, table2, table4, attrTable}}, "unknown"}, {"unknown_no_rate_no_tables", args{"unknown", 0, nil}, "unknown"}, {"unknown_no_rate_no_attr_table", args{"unknown", 0, []table{table1, table2, table4}}, "unknown"}, {"unknown_no_rate_value_table", args{"unknown", 0, []table{table1, table2, table4, attrTable}}, "unknown"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if gotOut := getType(tt.args.devType, tt.args.rate, tt.args.tables); gotOut != tt.wantOut { t.Errorf("getType() = %v, want %v", gotOut, tt.wantOut) } }) } } func Test_getTypeByRateAndAttr(t *testing.T) { type args struct { rate int tables []table } tests := []struct { name string args args want string }{ {"zero_rate_zero_spin_up", args{0, []table{table1, table2}}, "ssd"}, {"zero_rate_no_tables", args{0, nil}, "ssd"}, {"negative_rate_no_tables", args{-1000, nil}, "ssd"}, {"positive_rate_spin_up_table", args{12, []table{table1, table2, table3, attrTable}}, "hdd"}, {"positive_rate_no_tables", args{12, nil}, "hdd"}, {"zero_rate_spin_up_table", args{0, []table{table1, table2, table3, attrTable}}, "hdd"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := getTypeByRateAndAttr(tt.args.rate, tt.args.tables); got != tt.want { t.Errorf("getTypeByRate() = %v, want %v", got, tt.want) } }) } }