]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: smart: add support for NVMe 32708/head
authorPatrick Seidensal <pseidensal@suse.com>
Tue, 24 Sep 2019 20:22:22 +0000 (22:22 +0200)
committerPatrick Seidensal <pseidensal@suse.com>
Mon, 20 Jan 2020 09:07:51 +0000 (10:07 +0100)
Fixes: https://tracker.ceph.com/issues/42483
Signed-off-by: Patrick Seidensal <pseidensal@suse.com>
13 files changed:
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/ceph-shared.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/fixtures/smart_data_version_1_0_hdd_response.json [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/fixtures/smart_data_version_1_0_nvme_response.json [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/fixtures/smart_data_version_1_0_response.json [deleted file]
src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/host.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/osd.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/models/smart.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/services/ceph_service.py

index ba98a8683f5592eb57474e3bef2755f72a8fd791..4a11c8e63bc29119578a3bab222bb9451496ea7b 100644 (file)
@@ -79,6 +79,7 @@ import { ServicesComponent } from './services/services.component';
     MgrModulesModule,
     TypeaheadModule.forRoot(),
     TimepickerModule.forRoot(),
+    BsDatepickerModule.forRoot(),
     NgBootstrapFormValidationModule,
     CephSharedModule
   ],
index 2cd2784b9e8899c634e6032f617f4af4e456fe20..e747c66416ff8042e43e0dc59a692d6410c2d688 100644 (file)
@@ -84,7 +84,8 @@ describe('OsdListComponent', () => {
       CephModule,
       ReactiveFormsModule,
       RouterTestingModule,
-      CoreModule
+      CoreModule,
+      RouterTestingModule
     ],
     declarations: [],
     providers: [
index e595f07f3d9ac4bc1ef3b19986a48ca12fca1495..9f523ca232b50c8ae635a2c4eef23f3c48827f6f 100644 (file)
@@ -5,7 +5,6 @@ import { DataTableModule } from '../../shared/datatable/datatable.module';
 import { SharedModule } from '../../shared/shared.module';
 import { DeviceListComponent } from './device-list/device-list.component';
 import { SmartListComponent } from './smart-list/smart-list.component';
-
 @NgModule({
   imports: [CommonModule, DataTableModule, SharedModule, TabsModule],
   exports: [DeviceListComponent, SmartListComponent],
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/fixtures/smart_data_version_1_0_hdd_response.json b/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/fixtures/smart_data_version_1_0_hdd_response.json
new file mode 100644 (file)
index 0000000..514c296
--- /dev/null
@@ -0,0 +1,570 @@
+{
+  "WDC_WD1003FBYX-01Y7B1_WD-WCAW11111111": {
+    "ata_sct_capabilities": {
+      "data_table_supported": true,
+      "error_recovery_control_supported": true,
+      "feature_control_supported": true,
+      "value": 12351
+    },
+    "ata_smart_attributes": {
+      "revision": 16,
+      "table": [
+        {
+          "flags": {
+            "auto_keep": true,
+            "error_rate": true,
+            "event_count": false,
+            "performance": true,
+            "prefailure": true,
+            "string": "POSR-K ",
+            "updated_online": true,
+            "value": 47
+          },
+          "id": 1,
+          "name": "Raw_Read_Error_Rate",
+          "raw": {
+            "string": "1",
+            "value": 1
+          },
+          "thresh": 51,
+          "value": 200,
+          "when_failed": "",
+          "worst": 200
+        },
+        {
+          "flags": {
+            "auto_keep": true,
+            "error_rate": false,
+            "event_count": false,
+            "performance": true,
+            "prefailure": true,
+            "string": "POS--K ",
+            "updated_online": true,
+            "value": 39
+          },
+          "id": 3,
+          "name": "Spin_Up_Time",
+          "raw": {
+            "string": "4250",
+            "value": 4250
+          },
+          "thresh": 21,
+          "value": 175,
+          "when_failed": "",
+          "worst": 172
+        },
+        {
+          "flags": {
+            "auto_keep": true,
+            "error_rate": false,
+            "event_count": true,
+            "performance": false,
+            "prefailure": false,
+            "string": "-O--CK ",
+            "updated_online": true,
+            "value": 50
+          },
+          "id": 4,
+          "name": "Start_Stop_Count",
+          "raw": {
+            "string": "1657",
+            "value": 1657
+          },
+          "thresh": 0,
+          "value": 99,
+          "when_failed": "",
+          "worst": 99
+        },
+        {
+          "flags": {
+            "auto_keep": true,
+            "error_rate": false,
+            "event_count": true,
+            "performance": false,
+            "prefailure": true,
+            "string": "PO--CK ",
+            "updated_online": true,
+            "value": 51
+          },
+          "id": 5,
+          "name": "Reallocated_Sector_Ct",
+          "raw": {
+            "string": "0",
+            "value": 0
+          },
+          "thresh": 140,
+          "value": 200,
+          "when_failed": "",
+          "worst": 200
+        },
+        {
+          "flags": {
+            "auto_keep": true,
+            "error_rate": true,
+            "event_count": false,
+            "performance": true,
+            "prefailure": false,
+            "string": "-OSR-K ",
+            "updated_online": true,
+            "value": 46
+          },
+          "id": 7,
+          "name": "Seek_Error_Rate",
+          "raw": {
+            "string": "0",
+            "value": 0
+          },
+          "thresh": 0,
+          "value": 200,
+          "when_failed": "",
+          "worst": 200
+        },
+        {
+          "flags": {
+            "auto_keep": true,
+            "error_rate": false,
+            "event_count": true,
+            "performance": false,
+            "prefailure": false,
+            "string": "-O--CK ",
+            "updated_online": true,
+            "value": 50
+          },
+          "id": 9,
+          "name": "Power_On_Hours",
+          "raw": {
+            "string": "15807",
+            "value": 15807
+          },
+          "thresh": 0,
+          "value": 79,
+          "when_failed": "",
+          "worst": 79
+        },
+        {
+          "flags": {
+            "auto_keep": true,
+            "error_rate": false,
+            "event_count": true,
+            "performance": false,
+            "prefailure": false,
+            "string": "-O--CK ",
+            "updated_online": true,
+            "value": 50
+          },
+          "id": 10,
+          "name": "Spin_Retry_Count",
+          "raw": {
+            "string": "0",
+            "value": 0
+          },
+          "thresh": 0,
+          "value": 100,
+          "when_failed": "",
+          "worst": 100
+        },
+        {
+          "flags": {
+            "auto_keep": true,
+            "error_rate": false,
+            "event_count": true,
+            "performance": false,
+            "prefailure": false,
+            "string": "-O--CK ",
+            "updated_online": true,
+            "value": 50
+          },
+          "id": 11,
+          "name": "Calibration_Retry_Count",
+          "raw": {
+            "string": "0",
+            "value": 0
+          },
+          "thresh": 0,
+          "value": 100,
+          "when_failed": "",
+          "worst": 100
+        },
+        {
+          "flags": {
+            "auto_keep": true,
+            "error_rate": false,
+            "event_count": true,
+            "performance": false,
+            "prefailure": false,
+            "string": "-O--CK ",
+            "updated_online": true,
+            "value": 50
+          },
+          "id": 12,
+          "name": "Power_Cycle_Count",
+          "raw": {
+            "string": "1370",
+            "value": 1370
+          },
+          "thresh": 0,
+          "value": 99,
+          "when_failed": "",
+          "worst": 99
+        },
+        {
+          "flags": {
+            "auto_keep": true,
+            "error_rate": false,
+            "event_count": true,
+            "performance": false,
+            "prefailure": false,
+            "string": "-O--CK ",
+            "updated_online": true,
+            "value": 50
+          },
+          "id": 192,
+          "name": "Power-Off_Retract_Count",
+          "raw": {
+            "string": "111",
+            "value": 111
+          },
+          "thresh": 0,
+          "value": 200,
+          "when_failed": "",
+          "worst": 200
+        },
+        {
+          "flags": {
+            "auto_keep": true,
+            "error_rate": false,
+            "event_count": true,
+            "performance": false,
+            "prefailure": false,
+            "string": "-O--CK ",
+            "updated_online": true,
+            "value": 50
+          },
+          "id": 193,
+          "name": "Load_Cycle_Count",
+          "raw": {
+            "string": "1545",
+            "value": 1545
+          },
+          "thresh": 0,
+          "value": 200,
+          "when_failed": "",
+          "worst": 200
+        },
+        {
+          "flags": {
+            "auto_keep": true,
+            "error_rate": false,
+            "event_count": false,
+            "performance": false,
+            "prefailure": false,
+            "string": "-O---K ",
+            "updated_online": true,
+            "value": 34
+          },
+          "id": 194,
+          "name": "Temperature_Celsius",
+          "raw": {
+            "string": "47",
+            "value": 47
+          },
+          "thresh": 0,
+          "value": 100,
+          "when_failed": "",
+          "worst": 89
+        },
+        {
+          "flags": {
+            "auto_keep": true,
+            "error_rate": false,
+            "event_count": true,
+            "performance": false,
+            "prefailure": false,
+            "string": "-O--CK ",
+            "updated_online": true,
+            "value": 50
+          },
+          "id": 196,
+          "name": "Reallocated_Event_Count",
+          "raw": {
+            "string": "0",
+            "value": 0
+          },
+          "thresh": 0,
+          "value": 200,
+          "when_failed": "",
+          "worst": 200
+        },
+        {
+          "flags": {
+            "auto_keep": true,
+            "error_rate": false,
+            "event_count": true,
+            "performance": false,
+            "prefailure": false,
+            "string": "-O--CK ",
+            "updated_online": true,
+            "value": 50
+          },
+          "id": 197,
+          "name": "Current_Pending_Sector",
+          "raw": {
+            "string": "0",
+            "value": 0
+          },
+          "thresh": 0,
+          "value": 200,
+          "when_failed": "",
+          "worst": 200
+        },
+        {
+          "flags": {
+            "auto_keep": true,
+            "error_rate": false,
+            "event_count": true,
+            "performance": false,
+            "prefailure": false,
+            "string": "----CK ",
+            "updated_online": false,
+            "value": 48
+          },
+          "id": 198,
+          "name": "Offline_Uncorrectable",
+          "raw": {
+            "string": "0",
+            "value": 0
+          },
+          "thresh": 0,
+          "value": 200,
+          "when_failed": "",
+          "worst": 200
+        },
+        {
+          "flags": {
+            "auto_keep": true,
+            "error_rate": false,
+            "event_count": true,
+            "performance": false,
+            "prefailure": false,
+            "string": "-O--CK ",
+            "updated_online": true,
+            "value": 50
+          },
+          "id": 199,
+          "name": "UDMA_CRC_Error_Count",
+          "raw": {
+            "string": "0",
+            "value": 0
+          },
+          "thresh": 0,
+          "value": 200,
+          "when_failed": "",
+          "worst": 200
+        },
+        {
+          "flags": {
+            "auto_keep": false,
+            "error_rate": true,
+            "event_count": false,
+            "performance": false,
+            "prefailure": false,
+            "string": "---R-- ",
+            "updated_online": false,
+            "value": 8
+          },
+          "id": 200,
+          "name": "Multi_Zone_Error_Rate",
+          "raw": {
+            "string": "0",
+            "value": 0
+          },
+          "thresh": 0,
+          "value": 200,
+          "when_failed": "",
+          "worst": 200
+        }
+      ]
+    },
+    "ata_smart_data": {
+      "capabilities": {
+        "attribute_autosave_enabled": true,
+        "conveyance_self_test_supported": true,
+        "error_logging_supported": true,
+        "exec_offline_immediate_supported": true,
+        "gp_logging_supported": true,
+        "offline_is_aborted_upon_new_cmd": false,
+        "offline_surface_scan_supported": true,
+        "selective_self_test_supported": true,
+        "self_tests_supported": true,
+        "values": [
+          123,
+          3
+        ]
+      },
+      "offline_data_collection": {
+        "completion_seconds": 16500,
+        "status": {
+          "string": "was suspended by an interrupting command from host",
+          "value": 132
+        }
+      },
+      "self_test": {
+        "polling_minutes": {
+          "conveyance": 5,
+          "extended": 162,
+          "short": 2
+        },
+        "status": {
+          "passed": true,
+          "string": "completed without error",
+          "value": 0
+        }
+      }
+    },
+    "ata_smart_error_log": {
+      "summary": {
+        "count": 0,
+        "revision": 1
+      }
+    },
+    "ata_smart_selective_self_test_log": {
+      "flags": {
+        "remainder_scan_enabled": false,
+        "value": 0
+      },
+      "power_up_scan_resume_minutes": 0,
+      "revision": 1,
+      "table": [
+        {
+          "lba_max": 0,
+          "lba_min": 0,
+          "status": {
+            "string": "Not_testing",
+            "value": 0
+          }
+        },
+        {
+          "lba_max": 0,
+          "lba_min": 0,
+          "status": {
+            "string": "Not_testing",
+            "value": 0
+          }
+        },
+        {
+          "lba_max": 0,
+          "lba_min": 0,
+          "status": {
+            "string": "Not_testing",
+            "value": 0
+          }
+        },
+        {
+          "lba_max": 0,
+          "lba_min": 0,
+          "status": {
+            "string": "Not_testing",
+            "value": 0
+          }
+        },
+        {
+          "lba_max": 0,
+          "lba_min": 0,
+          "status": {
+            "string": "Not_testing",
+            "value": 0
+          }
+        }
+      ]
+    },
+    "ata_smart_self_test_log": {
+      "standard": {
+        "count": 0,
+        "revision": 1
+      }
+    },
+    "ata_version": {
+      "major_value": 510,
+      "minor_value": 0,
+      "string": "ATA8-ACS (minor revision not indicated)"
+    },
+    "device": {
+      "info_name": "/dev/sde [SAT]",
+      "name": "/dev/sde",
+      "protocol": "ATA",
+      "type": "sat"
+    },
+    "firmware_version": "01.01V02",
+    "in_smartctl_database": true,
+    "interface_speed": {
+      "current": {
+        "bits_per_unit": 100000000,
+        "sata_value": 2,
+        "string": "3.0 Gb/s",
+        "units_per_second": 30
+      },
+      "max": {
+        "bits_per_unit": 100000000,
+        "sata_value": 6,
+        "string": "3.0 Gb/s",
+        "units_per_second": 30
+      }
+    },
+    "json_format_version": [
+      1,
+      0
+    ],
+    "local_time": {
+      "asctime": "Mon Sep  2 12:39:01 2019 UTC",
+      "time_t": 1567427941
+    },
+    "logical_block_size": 512,
+    "model_family": "Western Digital RE4",
+    "model_name": "WDC WD1003FBYX-01Y7B1",
+    "nvme_smart_health_information_add_log_error": "nvme returned an error: sudo: exit status: 1",
+    "nvme_smart_health_information_add_log_error_code": -22,
+    "nvme_vendor": "wdc_wd1003fbyx-01y7b1",
+    "physical_block_size": 512,
+    "power_cycle_count": 1370,
+    "power_on_time": {
+      "hours": 15807
+    },
+    "rotation_rate": 7200,
+    "sata_version": {
+      "string": "SATA 3.0",
+      "value": 63
+    },
+    "serial_number": "WD-WCAW11111111",
+    "smart_status": {
+      "passed": true
+    },
+    "smartctl": {
+      "argv": [
+        "smartctl",
+        "-a",
+        "/dev/sde",
+        "--json"
+      ],
+      "build_info": "(SUSE RPM)",
+      "exit_status": 0,
+      "platform_info": "x86_64-linux-5.0.0-25-generic",
+      "svn_revision": "4917",
+      "version": [
+        7,
+        0
+      ]
+    },
+    "temperature": {
+      "current": 47
+    },
+    "user_capacity": {
+      "blocks": 1953525168,
+      "bytes": 1000204886016
+    },
+    "wwn": {
+      "id": 11601695629,
+      "naa": 5,
+      "oui": 5358
+    }
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/fixtures/smart_data_version_1_0_nvme_response.json b/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/fixtures/smart_data_version_1_0_nvme_response.json
new file mode 100644 (file)
index 0000000..fce5065
--- /dev/null
@@ -0,0 +1,134 @@
+{
+  "Samsung_SSD_970_EVO_Plus_1TB_S4EXXXXXXXXXXXX": {
+    "device": {
+      "info_name": "/dev/nvme0n1",
+      "name": "/dev/nvme0n1",
+      "protocol": "NVMe",
+      "type": "nvme"
+    },
+    "firmware_version": "1B2QEXM7",
+    "json_format_version": [1, 0],
+    "local_time": { "asctime": "Thu Oct 24 10:17:06 2019 CEST", "time_t": 1571905026 },
+    "logical_block_size": 512,
+    "model_name": "Samsung SSD 970 EVO Plus 1TB",
+    "nvme_controller_id": 4,
+    "nvme_ieee_oui_identifier": 9528,
+    "nvme_namespaces": [
+      {
+        "capacity": { "blocks": 1953525168, "bytes": 1000204886016 },
+        "eui64": { "ext_id": 367510189547, "oui": 9528 },
+        "formatted_lba_size": 512,
+        "id": 1,
+        "size": { "blocks": 1953525168, "bytes": 1000204886016 },
+        "utilization": { "blocks": 102347056, "bytes": 52401692672 }
+      }
+    ],
+    "nvme_number_of_namespaces": 1,
+    "nvme_pci_vendor": { "id": 5197, "subsystem_id": 5197 },
+    "nvme_smart_health_information_add_log_error": "nvme returned an error: sudo: exit status: 231",
+    "nvme_smart_health_information_add_log_error_code": -22,
+    "nvme_smart_health_information_log": {
+      "available_spare": 100,
+      "available_spare_threshold": 10,
+      "controller_busy_time": 29,
+      "critical_comp_time": 0,
+      "critical_warning": 0,
+      "data_units_read": 28800,
+      "data_units_written": 558814,
+      "host_reads": 480163,
+      "host_writes": 2340561,
+      "media_errors": 0,
+      "num_err_log_entries": 2,
+      "percentage_used": 0,
+      "power_cycles": 4,
+      "power_on_hours": 13,
+      "temperature": 42,
+      "temperature_sensors": [42, 46],
+      "unsafe_shutdowns": 2,
+      "warning_temp_time": 0
+    },
+    "nvme_total_capacity": 1000204886016,
+    "nvme_unallocated_capacity": 0,
+    "nvme_vendor": "samsung",
+    "power_cycle_count": 4,
+    "power_on_time": { "hours": 13 },
+    "serial_number": "S4EXXXXXXXXXXXX",
+    "smart_status": { "nvme": { "value": 0 }, "passed": true },
+    "smartctl": {
+      "argv": ["smartctl", "-a", "--json=o", "/dev/nvme0n1"],
+      "build_info": "(local build)",
+      "exit_status": 0,
+      "output": [
+        "smartctl 7.0 2018-12-30 r4883 [x86_64-linux-5.0.0-32-generic] (local build)",
+        "Copyright (C) 2002-18, Bruce Allen, Christian Franke, www.smartmontools.org",
+        "",
+        "=== START OF INFORMATION SECTION ===",
+        "Model Number:                       Samsung SSD 970 EVO Plus 1TB",
+        "Serial Number:                      S4EXXXXXXXXXXXX",
+        "Firmware Version:                   1B2QEXM7",
+        "PCI Vendor/Subsystem ID:            0x144d",
+        "IEEE OUI Identifier:                0x002538",
+        "Total NVM Capacity:                 1.000.204.886.016 [1,00 TB]",
+        "Unallocated NVM Capacity:           0",
+        "Controller ID:                      4",
+        "Number of Namespaces:               1",
+        "Namespace 1 Size/Capacity:          1.000.204.886.016 [1,00 TB]",
+        "Namespace 1 Utilization:            52.401.692.672 [52,4 GB]",
+        "Namespace 1 Formatted LBA Size:     512",
+        "Namespace 1 IEEE EUI-64:            002538 55915075eb",
+        "Local Time is:                      Thu Oct 24 10:17:06 2019 CEST",
+        "Firmware Updates (0x16):            3 Slots, no Reset required",
+        "Optional Admin Commands (0x0017):   Security Format Frmw_DL Self_Test",
+        "Optional NVM Commands (0x005f):     Comp Wr_Unc DS_Mngmt Wr_Zero Sav/Sel_Feat Timestmp",
+        "Maximum Data Transfer Size:         512 Pages",
+        "Warning  Comp. Temp. Threshold:     85 Celsius",
+        "Critical Comp. Temp. Threshold:     85 Celsius",
+        "",
+        "Supported Power States",
+        "St Op     Max   Active     Idle   RL RT WL WT  Ent_Lat  Ex_Lat",
+        " 0 +     7.80W       -        -    0  0  0  0        0       0",
+        " 1 +     6.00W       -        -    1  1  1  1        0       0",
+        " 2 +     3.40W       -        -    2  2  2  2        0       0",
+        " 3 -   0.0700W       -        -    3  3  3  3      210    1200",
+        " 4 -   0.0100W       -        -    4  4  4  4     2000    8000",
+        "",
+        "Supported LBA Sizes (NSID 0x1)",
+        "Id Fmt  Data  Metadt  Rel_Perf",
+        " 0 +     512       0         0",
+        "",
+        "=== START OF SMART DATA SECTION ===",
+        "SMART overall-health self-assessment test result: PASSED",
+        "",
+        "SMART/Health Information (NVMe Log 0x02)",
+        "Critical Warning:                   0x00",
+        "Temperature:                        42 Celsius",
+        "Available Spare:                    100%",
+        "Available Spare Threshold:          10%",
+        "Percentage Used:                    0%",
+        "Data Units Read:                    28.800 [14,7 GB]",
+        "Data Units Written:                 558.814 [286 GB]",
+        "Host Read Commands:                 480.163",
+        "Host Write Commands:                2.340.561",
+        "Controller Busy Time:               29",
+        "Power Cycles:                       4",
+        "Power On Hours:                     13",
+        "Unsafe Shutdowns:                   2",
+        "Media and Data Integrity Errors:    0",
+        "Error Information Log Entries:      2",
+        "Warning  Comp. Temperature Time:    0",
+        "Critical Comp. Temperature Time:    0",
+        "Temperature Sensor 1:               42 Celsius",
+        "Temperature Sensor 2:               46 Celsius",
+        "",
+        "Error Information (NVMe Log 0x01, max 64 entries)",
+        "No Errors Logged",
+        ""
+      ],
+      "platform_info": "x86_64-linux-5.0.0-32-generic",
+      "svn_revision": "4883",
+      "version": [7, 0]
+    },
+    "temperature": { "current": 42 },
+    "user_capacity": { "blocks": 1953525168, "bytes": 1000204886016 }
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/fixtures/smart_data_version_1_0_response.json b/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/fixtures/smart_data_version_1_0_response.json
deleted file mode 100644 (file)
index 514c296..0000000
+++ /dev/null
@@ -1,570 +0,0 @@
-{
-  "WDC_WD1003FBYX-01Y7B1_WD-WCAW11111111": {
-    "ata_sct_capabilities": {
-      "data_table_supported": true,
-      "error_recovery_control_supported": true,
-      "feature_control_supported": true,
-      "value": 12351
-    },
-    "ata_smart_attributes": {
-      "revision": 16,
-      "table": [
-        {
-          "flags": {
-            "auto_keep": true,
-            "error_rate": true,
-            "event_count": false,
-            "performance": true,
-            "prefailure": true,
-            "string": "POSR-K ",
-            "updated_online": true,
-            "value": 47
-          },
-          "id": 1,
-          "name": "Raw_Read_Error_Rate",
-          "raw": {
-            "string": "1",
-            "value": 1
-          },
-          "thresh": 51,
-          "value": 200,
-          "when_failed": "",
-          "worst": 200
-        },
-        {
-          "flags": {
-            "auto_keep": true,
-            "error_rate": false,
-            "event_count": false,
-            "performance": true,
-            "prefailure": true,
-            "string": "POS--K ",
-            "updated_online": true,
-            "value": 39
-          },
-          "id": 3,
-          "name": "Spin_Up_Time",
-          "raw": {
-            "string": "4250",
-            "value": 4250
-          },
-          "thresh": 21,
-          "value": 175,
-          "when_failed": "",
-          "worst": 172
-        },
-        {
-          "flags": {
-            "auto_keep": true,
-            "error_rate": false,
-            "event_count": true,
-            "performance": false,
-            "prefailure": false,
-            "string": "-O--CK ",
-            "updated_online": true,
-            "value": 50
-          },
-          "id": 4,
-          "name": "Start_Stop_Count",
-          "raw": {
-            "string": "1657",
-            "value": 1657
-          },
-          "thresh": 0,
-          "value": 99,
-          "when_failed": "",
-          "worst": 99
-        },
-        {
-          "flags": {
-            "auto_keep": true,
-            "error_rate": false,
-            "event_count": true,
-            "performance": false,
-            "prefailure": true,
-            "string": "PO--CK ",
-            "updated_online": true,
-            "value": 51
-          },
-          "id": 5,
-          "name": "Reallocated_Sector_Ct",
-          "raw": {
-            "string": "0",
-            "value": 0
-          },
-          "thresh": 140,
-          "value": 200,
-          "when_failed": "",
-          "worst": 200
-        },
-        {
-          "flags": {
-            "auto_keep": true,
-            "error_rate": true,
-            "event_count": false,
-            "performance": true,
-            "prefailure": false,
-            "string": "-OSR-K ",
-            "updated_online": true,
-            "value": 46
-          },
-          "id": 7,
-          "name": "Seek_Error_Rate",
-          "raw": {
-            "string": "0",
-            "value": 0
-          },
-          "thresh": 0,
-          "value": 200,
-          "when_failed": "",
-          "worst": 200
-        },
-        {
-          "flags": {
-            "auto_keep": true,
-            "error_rate": false,
-            "event_count": true,
-            "performance": false,
-            "prefailure": false,
-            "string": "-O--CK ",
-            "updated_online": true,
-            "value": 50
-          },
-          "id": 9,
-          "name": "Power_On_Hours",
-          "raw": {
-            "string": "15807",
-            "value": 15807
-          },
-          "thresh": 0,
-          "value": 79,
-          "when_failed": "",
-          "worst": 79
-        },
-        {
-          "flags": {
-            "auto_keep": true,
-            "error_rate": false,
-            "event_count": true,
-            "performance": false,
-            "prefailure": false,
-            "string": "-O--CK ",
-            "updated_online": true,
-            "value": 50
-          },
-          "id": 10,
-          "name": "Spin_Retry_Count",
-          "raw": {
-            "string": "0",
-            "value": 0
-          },
-          "thresh": 0,
-          "value": 100,
-          "when_failed": "",
-          "worst": 100
-        },
-        {
-          "flags": {
-            "auto_keep": true,
-            "error_rate": false,
-            "event_count": true,
-            "performance": false,
-            "prefailure": false,
-            "string": "-O--CK ",
-            "updated_online": true,
-            "value": 50
-          },
-          "id": 11,
-          "name": "Calibration_Retry_Count",
-          "raw": {
-            "string": "0",
-            "value": 0
-          },
-          "thresh": 0,
-          "value": 100,
-          "when_failed": "",
-          "worst": 100
-        },
-        {
-          "flags": {
-            "auto_keep": true,
-            "error_rate": false,
-            "event_count": true,
-            "performance": false,
-            "prefailure": false,
-            "string": "-O--CK ",
-            "updated_online": true,
-            "value": 50
-          },
-          "id": 12,
-          "name": "Power_Cycle_Count",
-          "raw": {
-            "string": "1370",
-            "value": 1370
-          },
-          "thresh": 0,
-          "value": 99,
-          "when_failed": "",
-          "worst": 99
-        },
-        {
-          "flags": {
-            "auto_keep": true,
-            "error_rate": false,
-            "event_count": true,
-            "performance": false,
-            "prefailure": false,
-            "string": "-O--CK ",
-            "updated_online": true,
-            "value": 50
-          },
-          "id": 192,
-          "name": "Power-Off_Retract_Count",
-          "raw": {
-            "string": "111",
-            "value": 111
-          },
-          "thresh": 0,
-          "value": 200,
-          "when_failed": "",
-          "worst": 200
-        },
-        {
-          "flags": {
-            "auto_keep": true,
-            "error_rate": false,
-            "event_count": true,
-            "performance": false,
-            "prefailure": false,
-            "string": "-O--CK ",
-            "updated_online": true,
-            "value": 50
-          },
-          "id": 193,
-          "name": "Load_Cycle_Count",
-          "raw": {
-            "string": "1545",
-            "value": 1545
-          },
-          "thresh": 0,
-          "value": 200,
-          "when_failed": "",
-          "worst": 200
-        },
-        {
-          "flags": {
-            "auto_keep": true,
-            "error_rate": false,
-            "event_count": false,
-            "performance": false,
-            "prefailure": false,
-            "string": "-O---K ",
-            "updated_online": true,
-            "value": 34
-          },
-          "id": 194,
-          "name": "Temperature_Celsius",
-          "raw": {
-            "string": "47",
-            "value": 47
-          },
-          "thresh": 0,
-          "value": 100,
-          "when_failed": "",
-          "worst": 89
-        },
-        {
-          "flags": {
-            "auto_keep": true,
-            "error_rate": false,
-            "event_count": true,
-            "performance": false,
-            "prefailure": false,
-            "string": "-O--CK ",
-            "updated_online": true,
-            "value": 50
-          },
-          "id": 196,
-          "name": "Reallocated_Event_Count",
-          "raw": {
-            "string": "0",
-            "value": 0
-          },
-          "thresh": 0,
-          "value": 200,
-          "when_failed": "",
-          "worst": 200
-        },
-        {
-          "flags": {
-            "auto_keep": true,
-            "error_rate": false,
-            "event_count": true,
-            "performance": false,
-            "prefailure": false,
-            "string": "-O--CK ",
-            "updated_online": true,
-            "value": 50
-          },
-          "id": 197,
-          "name": "Current_Pending_Sector",
-          "raw": {
-            "string": "0",
-            "value": 0
-          },
-          "thresh": 0,
-          "value": 200,
-          "when_failed": "",
-          "worst": 200
-        },
-        {
-          "flags": {
-            "auto_keep": true,
-            "error_rate": false,
-            "event_count": true,
-            "performance": false,
-            "prefailure": false,
-            "string": "----CK ",
-            "updated_online": false,
-            "value": 48
-          },
-          "id": 198,
-          "name": "Offline_Uncorrectable",
-          "raw": {
-            "string": "0",
-            "value": 0
-          },
-          "thresh": 0,
-          "value": 200,
-          "when_failed": "",
-          "worst": 200
-        },
-        {
-          "flags": {
-            "auto_keep": true,
-            "error_rate": false,
-            "event_count": true,
-            "performance": false,
-            "prefailure": false,
-            "string": "-O--CK ",
-            "updated_online": true,
-            "value": 50
-          },
-          "id": 199,
-          "name": "UDMA_CRC_Error_Count",
-          "raw": {
-            "string": "0",
-            "value": 0
-          },
-          "thresh": 0,
-          "value": 200,
-          "when_failed": "",
-          "worst": 200
-        },
-        {
-          "flags": {
-            "auto_keep": false,
-            "error_rate": true,
-            "event_count": false,
-            "performance": false,
-            "prefailure": false,
-            "string": "---R-- ",
-            "updated_online": false,
-            "value": 8
-          },
-          "id": 200,
-          "name": "Multi_Zone_Error_Rate",
-          "raw": {
-            "string": "0",
-            "value": 0
-          },
-          "thresh": 0,
-          "value": 200,
-          "when_failed": "",
-          "worst": 200
-        }
-      ]
-    },
-    "ata_smart_data": {
-      "capabilities": {
-        "attribute_autosave_enabled": true,
-        "conveyance_self_test_supported": true,
-        "error_logging_supported": true,
-        "exec_offline_immediate_supported": true,
-        "gp_logging_supported": true,
-        "offline_is_aborted_upon_new_cmd": false,
-        "offline_surface_scan_supported": true,
-        "selective_self_test_supported": true,
-        "self_tests_supported": true,
-        "values": [
-          123,
-          3
-        ]
-      },
-      "offline_data_collection": {
-        "completion_seconds": 16500,
-        "status": {
-          "string": "was suspended by an interrupting command from host",
-          "value": 132
-        }
-      },
-      "self_test": {
-        "polling_minutes": {
-          "conveyance": 5,
-          "extended": 162,
-          "short": 2
-        },
-        "status": {
-          "passed": true,
-          "string": "completed without error",
-          "value": 0
-        }
-      }
-    },
-    "ata_smart_error_log": {
-      "summary": {
-        "count": 0,
-        "revision": 1
-      }
-    },
-    "ata_smart_selective_self_test_log": {
-      "flags": {
-        "remainder_scan_enabled": false,
-        "value": 0
-      },
-      "power_up_scan_resume_minutes": 0,
-      "revision": 1,
-      "table": [
-        {
-          "lba_max": 0,
-          "lba_min": 0,
-          "status": {
-            "string": "Not_testing",
-            "value": 0
-          }
-        },
-        {
-          "lba_max": 0,
-          "lba_min": 0,
-          "status": {
-            "string": "Not_testing",
-            "value": 0
-          }
-        },
-        {
-          "lba_max": 0,
-          "lba_min": 0,
-          "status": {
-            "string": "Not_testing",
-            "value": 0
-          }
-        },
-        {
-          "lba_max": 0,
-          "lba_min": 0,
-          "status": {
-            "string": "Not_testing",
-            "value": 0
-          }
-        },
-        {
-          "lba_max": 0,
-          "lba_min": 0,
-          "status": {
-            "string": "Not_testing",
-            "value": 0
-          }
-        }
-      ]
-    },
-    "ata_smart_self_test_log": {
-      "standard": {
-        "count": 0,
-        "revision": 1
-      }
-    },
-    "ata_version": {
-      "major_value": 510,
-      "minor_value": 0,
-      "string": "ATA8-ACS (minor revision not indicated)"
-    },
-    "device": {
-      "info_name": "/dev/sde [SAT]",
-      "name": "/dev/sde",
-      "protocol": "ATA",
-      "type": "sat"
-    },
-    "firmware_version": "01.01V02",
-    "in_smartctl_database": true,
-    "interface_speed": {
-      "current": {
-        "bits_per_unit": 100000000,
-        "sata_value": 2,
-        "string": "3.0 Gb/s",
-        "units_per_second": 30
-      },
-      "max": {
-        "bits_per_unit": 100000000,
-        "sata_value": 6,
-        "string": "3.0 Gb/s",
-        "units_per_second": 30
-      }
-    },
-    "json_format_version": [
-      1,
-      0
-    ],
-    "local_time": {
-      "asctime": "Mon Sep  2 12:39:01 2019 UTC",
-      "time_t": 1567427941
-    },
-    "logical_block_size": 512,
-    "model_family": "Western Digital RE4",
-    "model_name": "WDC WD1003FBYX-01Y7B1",
-    "nvme_smart_health_information_add_log_error": "nvme returned an error: sudo: exit status: 1",
-    "nvme_smart_health_information_add_log_error_code": -22,
-    "nvme_vendor": "wdc_wd1003fbyx-01y7b1",
-    "physical_block_size": 512,
-    "power_cycle_count": 1370,
-    "power_on_time": {
-      "hours": 15807
-    },
-    "rotation_rate": 7200,
-    "sata_version": {
-      "string": "SATA 3.0",
-      "value": 63
-    },
-    "serial_number": "WD-WCAW11111111",
-    "smart_status": {
-      "passed": true
-    },
-    "smartctl": {
-      "argv": [
-        "smartctl",
-        "-a",
-        "/dev/sde",
-        "--json"
-      ],
-      "build_info": "(SUSE RPM)",
-      "exit_status": 0,
-      "platform_info": "x86_64-linux-5.0.0-25-generic",
-      "svn_revision": "4917",
-      "version": [
-        7,
-        0
-      ]
-    },
-    "temperature": {
-      "current": 47
-    },
-    "user_capacity": {
-      "blocks": 1953525168,
-      "bytes": 1000204886016
-    },
-    "wwn": {
-      "id": 11601695629,
-      "naa": 5,
-      "oui": 5358
-    }
-  }
-}
index 588eb0fbd92aca6b338a31a7f87f762305b57365..16f940e6d4ddbd1966e4e03b05c5a9e1802f4c73 100644 (file)
@@ -1,33 +1,35 @@
 <ng-container *ngIf="!loading; else isLoading">
   <ng-container *ngIf="incompatible; else isCompatible">
-    <cd-alert-panel type="warning"
+    <cd-alert-panel id="alert-wrong-format"
+                    type="warning"
                     i18n>The data received has the JSON format version 2.x and is currently incompatible with the dashboard.</cd-alert-panel>
   </ng-container>
   <ng-template #isCompatible>
     <tabset *ngFor="let device of data | keyvalue">
       <tab [heading]="device.value.device + ' (' + device.value.identifier + ')'">
         <ng-container *ngIf="device.value.error; else noError">
-          <cd-alert-panel type="warning">{{ device.value.userMessage }}</cd-alert-panel>
+          <cd-alert-panel id="alert-error"
+                          type="warning">{{ device.value.userMessage }}</cd-alert-panel>
         </ng-container>
         <ng-template #noError>
-          <ng-container *ngIf="device.value.smart.data.self_test.status.passed; else selfTestFailed">
-            <cd-alert-panel
-              size="slim"
-              type="info"
-              i18n-title
-              title="SMART overall-health self-assessment test result">
-              {{ device.value.smart.data.self_test.status.string }}
-            </cd-alert-panel>
+          <!-- HDD/NVMe self test -->
+          <ng-container *ngIf="device.value.info.smart_status.passed; else selfTestFailed">
+            <cd-alert-panel id="alert-self-test-passed"
+                            size="slim"
+                            type="info"
+                            i18n-title
+                            title="SMART overall-health self-assessment test result"
+                            i18n>passed</cd-alert-panel>
           </ng-container>
           <ng-template #selfTestFailed>
-            <cd-alert-panel
-              size="slim"
-              type="warning"
-              i18n-title
-              title="SMART overall-health self-assessment test result">
-              {{ device.value.smart.data.self_test.status.string }}
-            </cd-alert-panel>
+            <cd-alert-panel id="alert-self-test-failed"
+                            size="slim"
+                            type="warning"
+                            i18n-title
+                            title="SMART overall-health self-assessment test result"
+                            i18n>failed</cd-alert-panel>
           </ng-template>
+
           <tabset>
             <tab i18n-heading
                  heading="Device Information">
             </tab>
 
             <tab i18n-heading
-                 heading="S.M.A.R.T">
-              <cd-table [data]="device.value.smart.attributes.table"
+                 heading="SMART">
+              <cd-table *ngIf="device.value.smart.attributes"
+                        [data]="device.value.smart.attributes.table"
                         updateSelectionOnRefresh="never"
-                        [columns]="columns"></cd-table>
+                        [columns]="smartDataColumns"></cd-table>
+              <cd-table-key-value *ngIf="device.value.smart.nvmeData"
+                                  [renderObjects]="true"
+                                  [data]="device.value.smart.nvmeData"
+                                  updateSelectionOnRefresh="never"></cd-table-key-value>
+              <cd-alert-panel *ngIf="!device.value.smart.attributes && !device.value.smart.nvmeData"
+                              type="info"
+                              i18n>No SMART data available for this device.</cd-alert-panel>
             </tab>
           </tabset>
         </ng-template>
@@ -48,5 +58,5 @@
   </ng-template>
 </ng-container>
 <ng-template #isLoading>
-  <cd-loading-panel i18n>S.M.A.R.T data is loading.</cd-loading-panel>
+  <cd-loading-panel i18n>SMART data is loading.</cd-loading-panel>
 </ng-template>
index 3f11c8c7faa4963ece721cefda5363c383f6ea04..a3a68564650789282019273ba9bba21a642bb207 100644 (file)
@@ -4,11 +4,12 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { By } from '@angular/platform-browser';
 import { TabsetComponent, TabsetConfig, TabsModule } from 'ngx-bootstrap/tabs';
 
-import _ = require('lodash');
+import * as _ from 'lodash';
 import { of } from 'rxjs';
 
 import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
 import { OsdService } from '../../../shared/api/osd.service';
+import { HddSmartDataV1, NvmeSmartDataV1, SmartDataResult } from '../../../shared/models/smart';
 import { SharedModule } from '../../../shared/shared.module';
 import { SmartListComponent } from './smart-list.component';
 
@@ -17,24 +18,8 @@ describe('OsdSmartListComponent', () => {
   let fixture: ComponentFixture<SmartListComponent>;
   let osdService: OsdService;
 
-  const SMART_DATA_VERSION_1_0 = require('./fixtures/smart_data_version_1_0_response.json');
-
-  const spyOnGetSmartData = (fn: (id: number) => any) =>
-    spyOn(osdService, 'getSmartData').and.callFake(fn);
-
-  /**
-   * Initializes the component after it spied upon the `getSmartData()` method of `OsdService`.
-   * @param data The data to be used to return when `getSmartData()` is called.
-   * @param simpleChanges (optional) The changes to be used for `ngOnChanges()` method.
-   */
-  const initializeComponentWithData = (data?: any, simpleChanges?: SimpleChanges) => {
-    spyOnGetSmartData(() => of(data || SMART_DATA_VERSION_1_0));
-    component.ngOnInit();
-    const changes: SimpleChanges = simpleChanges || {
-      osdId: new SimpleChange(null, 0, true)
-    };
-    component.ngOnChanges(changes);
-  };
+  const SMART_DATA_HDD_VERSION_1_0: HddSmartDataV1 = require('./fixtures/smart_data_version_1_0_hdd_response.json');
+  const SMART_DATA_NVME_VERSION_1_0: NvmeSmartDataV1 = require('./fixtures/smart_data_version_1_0_nvme_response.json');
 
   /**
    * Sets attributes for _all_ returned devices according to the given path. The syntax is the same
@@ -51,15 +36,48 @@ describe('OsdSmartListComponent', () => {
    */
   const patchData = (path: string, newValue: any): any => {
     return _.reduce(
-      _.cloneDeep(SMART_DATA_VERSION_1_0),
+      _.cloneDeep(SMART_DATA_HDD_VERSION_1_0),
       (result, dataObj, deviceId) => {
-        result[deviceId] = _.set(dataObj, path, newValue);
+        result[deviceId] = _.set<any>(dataObj, path, newValue);
         return result;
       },
       {}
     );
   };
 
+  /**
+   * Initializes the component after it spied upon the `getSmartData()` method
+   * of `OsdService`. Determines which data is returned.
+   */
+  const initializeComponentWithData = (
+    dataType: 'hdd_v1' | 'nvme_v1',
+    patch: { [path: string]: any } = null,
+    simpleChanges?: SimpleChanges
+  ) => {
+    let data = null;
+    switch (dataType) {
+      case 'hdd_v1':
+        data = SMART_DATA_HDD_VERSION_1_0;
+        break;
+      case 'nvme_v1':
+        data = SMART_DATA_NVME_VERSION_1_0;
+        break;
+    }
+
+    if (_.isObject(patch)) {
+      _.each(patch, (replacement, path) => {
+        data = patchData(path, replacement);
+      });
+    }
+
+    spyOn(osdService, 'getSmartData').and.callFake(() => of(data));
+    component.ngOnInit();
+    const changes: SimpleChanges = simpleChanges || {
+      osdId: new SimpleChange(null, 0, true)
+    };
+    component.ngOnChanges(changes);
+  };
+
   configureTestBed({
     declarations: [SmartListComponent],
     imports: [TabsModule, SharedModule, HttpClientTestingModule],
@@ -78,8 +96,8 @@ describe('OsdSmartListComponent', () => {
     expect(component).toBeTruthy();
   });
 
-  describe('tests version 1.x', () => {
-    beforeEach(() => initializeComponentWithData());
+  describe('tests HDD version 1.x', () => {
+    beforeEach(() => initializeComponentWithData('hdd_v1'));
 
     it('should return with proper keys', () => {
       _.each(component.data, (smartData, _deviceId) => {
@@ -93,22 +111,39 @@ describe('OsdSmartListComponent', () => {
         'ata_smart_selective_self_test_log',
         'ata_smart_data'
       ];
+      _.each(component.data, (smartData: SmartDataResult, _deviceId) => {
+        _.each(excludes, (exclude) => expect(smartData.info[exclude]).toBeUndefined());
+      });
+    });
+  });
+
+  describe('tests NVMe version 1.x', () => {
+    beforeEach(() => initializeComponentWithData('nvme_v1'));
+
+    it('should return with proper keys', () => {
       _.each(component.data, (smartData, _deviceId) => {
+        expect(_.keys(smartData)).toEqual(['info', 'smart', 'device', 'identifier']);
+      });
+    });
+
+    it('should not contain excluded keys in `info`', () => {
+      const excludes = ['nvme_smart_health_information_log'];
+      _.each(component.data, (smartData: SmartDataResult, _deviceId) => {
         _.each(excludes, (exclude) => expect(smartData.info[exclude]).toBeUndefined());
       });
     });
   });
 
   it('should not work for version 2.x', () => {
-    initializeComponentWithData(patchData('json_format_version', [2, 0]));
+    initializeComponentWithData('nvme_v1', { json_format_version: [2, 0] });
     expect(component.data).toEqual({});
     expect(component.incompatible).toBeTruthy();
   });
 
   it('should display info panel for passed self test', () => {
-    initializeComponentWithData();
+    initializeComponentWithData('hdd_v1');
     fixture.detectChanges();
-    const alertPanel = fixture.debugElement.query(By.css('cd-alert-panel'));
+    const alertPanel = fixture.debugElement.query(By.css('cd-alert-panel#alert-self-test-passed'));
     expect(component.incompatible).toBe(false);
     expect(component.loading).toBe(false);
     expect(alertPanel.attributes.size).toBe('slim');
@@ -117,9 +152,9 @@ describe('OsdSmartListComponent', () => {
   });
 
   it('should display warning panel for failed self test', () => {
-    initializeComponentWithData(patchData('ata_smart_data.self_test.status.passed', false));
+    initializeComponentWithData('hdd_v1', { 'smart_status.passed': false });
     fixture.detectChanges();
-    const alertPanel = fixture.debugElement.query(By.css('cd-alert-panel'));
+    const alertPanel = fixture.debugElement.query(By.css('cd-alert-panel#alert-self-test-failed'));
     expect(component.incompatible).toBe(false);
     expect(component.loading).toBe(false);
     expect(alertPanel.attributes.size).toBe('slim');
index 3938b887007f63f7ab8232a2d61fb7b7287d669e..15734e9388ad022e6eebba6f85414b613b7e4eae 100644 (file)
@@ -2,13 +2,15 @@ import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/cor
 import { I18n } from '@ngx-translate/i18n-polyfill';
 import * as _ from 'lodash';
 import { HostService } from '../../../shared/api/host.service';
-import {
-  OsdService,
-  SmartAttribute,
-  SmartDataV1,
-  SmartError
-} from '../../../shared/api/osd.service';
+import { OsdService } from '../../../shared/api/osd.service';
 import { CdTableColumn } from '../../../shared/models/cd-table-column';
+import {
+  HddSmartDataV1,
+  NvmeSmartDataV1,
+  SmartDataResult,
+  SmartError,
+  SmartErrorResult
+} from '../../../shared/models/smart';
 
 @Component({
   selector: 'cd-smart-list',
@@ -24,17 +26,9 @@ export class SmartListComponent implements OnInit, OnChanges {
   loading = false;
   incompatible = false;
 
-  data: {
-    [deviceId: string]: {
-      info: { [key: string]: number | string | boolean };
-      smart: {
-        revision: number;
-        table: SmartAttribute[];
-      };
-    };
-  } = {};
+  data: { [deviceId: string]: SmartDataResult | SmartErrorResult } = {};
 
-  columns: CdTableColumn[];
+  smartDataColumns: CdTableColumn[];
 
   constructor(
     private i18n: I18n,
@@ -42,12 +36,20 @@ export class SmartListComponent implements OnInit, OnChanges {
     private hostService: HostService
   ) {}
 
-  private isSmartError(data: SmartDataV1 | SmartError): data is SmartError {
-    return (data as SmartError).error !== undefined;
+  isSmartError(data: any): data is SmartError {
+    return _.get(data, 'error') !== undefined;
+  }
+
+  isNvmeSmartData(data: any): data is NvmeSmartDataV1 {
+    return _.get(data, 'device.protocol', '').toLowerCase() === 'nvme';
+  }
+
+  isHddSmartData(data: any): data is HddSmartDataV1 {
+    return _.get(data, 'device.protocol', '').toLowerCase() === 'ata';
   }
 
   private fetchData(data) {
-    const result = {};
+    const result: { [deviceId: string]: SmartDataResult | SmartErrorResult } = {};
     _.each(data, (smartData, deviceId) => {
       if (this.isSmartError(smartData)) {
         let userMessage = '';
@@ -64,7 +66,7 @@ export class SmartListComponent implements OnInit, OnChanges {
             code: smartData.smartctl_error_code
           });
         }
-        result[deviceId] = {
+        const _result: SmartErrorResult = {
           error: smartData.error,
           smartctl_error_code: smartData.smartctl_error_code,
           smartctl_output: smartData.smartctl_output,
@@ -72,28 +74,19 @@ export class SmartListComponent implements OnInit, OnChanges {
           device: smartData.dev,
           identifier: smartData.nvme_vendor
         };
+        result[deviceId] = _result;
         return;
       }
 
       // Prepare S.M.A.R.T data
       if (smartData.json_format_version[0] === 1) {
         // Version 1.x
-        const excludes = [
-          'ata_smart_attributes',
-          'ata_smart_selective_self_test_log',
-          'ata_smart_data'
-        ];
-        const info = _.pickBy(smartData, (_value, key) => !excludes.includes(key));
-        // Build result
-        result[deviceId] = {
-          info: info,
-          smart: {
-            attributes: smartData.ata_smart_attributes,
-            data: smartData.ata_smart_data
-          },
-          device: info.device.name,
-          identifier: info.serial_number
-        };
+        if (this.isHddSmartData(smartData)) {
+          result[deviceId] = this.extractHddData(smartData);
+        } else if (this.isNvmeSmartData(smartData)) {
+          result[deviceId] = this.extractNvmeData(smartData);
+        }
+        return;
       } else {
         this.incompatible = true;
       }
@@ -102,6 +95,35 @@ export class SmartListComponent implements OnInit, OnChanges {
     this.loading = false;
   }
 
+  private extractNvmeData(smartData: NvmeSmartDataV1): SmartDataResult {
+    const info = _.omitBy(smartData, (_value, key) =>
+      ['nvme_smart_health_information_log'].includes(key)
+    );
+    return {
+      info: info,
+      smart: {
+        nvmeData: smartData.nvme_smart_health_information_log
+      },
+      device: smartData.device.name,
+      identifier: smartData.serial_number
+    };
+  }
+
+  private extractHddData(smartData: HddSmartDataV1): SmartDataResult {
+    const info = _.omitBy(smartData, (_value, key) =>
+      ['ata_smart_attributes', 'ata_smart_selective_self_test_log', 'ata_smart_data'].includes(key)
+    );
+    return {
+      info: info,
+      smart: {
+        attributes: smartData.ata_smart_attributes,
+        data: smartData.ata_smart_data
+      },
+      device: info.device.name,
+      identifier: info.serial_number
+    };
+  }
+
   private updateData() {
     this.loading = true;
 
@@ -113,7 +135,7 @@ export class SmartListComponent implements OnInit, OnChanges {
   }
 
   ngOnInit() {
-    this.columns = [
+    this.smartDataColumns = [
       { prop: 'id', name: this.i18n('ID') },
       { prop: 'name', name: this.i18n('Name') },
       { prop: 'raw.value', name: this.i18n('Raw') },
index b1dcaec12f8d689c8b6089ad35a84a308d89f244..089a6a99d5ff781f7fe538ae393ac35f4d5b9756 100644 (file)
@@ -5,6 +5,7 @@ import { Observable } from 'rxjs';
 import { map } from 'rxjs/operators';
 
 import { CdDevice } from '../models/devices';
+import { SmartDataResponseV1 } from '../models/smart';
 import { DeviceService } from '../services/device.service';
 import { ApiModule } from './api.module';
 
@@ -35,6 +36,6 @@ export class HostService {
   }
 
   getSmartData(hostname) {
-    return this.http.get(`${this.baseURL}/${hostname}/smart`);
+    return this.http.get<SmartDataResponseV1>(`${this.baseURL}/${hostname}/smart`);
   }
 }
index d78093e35ae370318fdd53380dee434b0b76f084..0994ba5b223fc00c3ec102d3a7daf523bc28ed22 100644 (file)
@@ -5,131 +5,10 @@ import { I18n } from '@ngx-translate/i18n-polyfill';
 import { map } from 'rxjs/operators';
 
 import { CdDevice } from '../models/devices';
+import { SmartDataResponseV1 } from '../models/smart';
 import { DeviceService } from '../services/device.service';
 import { ApiModule } from './api.module';
 
-export interface SmartAttribute {
-  flags: {
-    auto_keep: boolean;
-    error_rate: boolean;
-    event_count: boolean;
-    performance: boolean;
-    prefailure: boolean;
-    string: string;
-    updated_online: boolean;
-    value: number;
-  };
-  id: number;
-  name: string;
-  raw: { string: string; value: number };
-  thresh: number;
-  value: number;
-  when_failed: string;
-  worst: number;
-}
-
-export interface SmartError {
-  dev: string;
-  error: string;
-  nvme_smart_health_information_add_log_error: string;
-  nvme_smart_health_information_add_log_error_code: number;
-  nvme_vendor: string;
-  smartctl_error_code: number;
-  smartctl_output: string;
-}
-
-export interface SmartDataV1 {
-  ata_sct_capabilities: {
-    data_table_supported: boolean;
-    error_recovery_control_supported: boolean;
-    feature_control_supported: boolean;
-    value: number;
-  };
-  ata_smart_attributes: {
-    revision: number;
-    table: SmartAttribute[];
-  };
-  ata_smart_data: {
-    capabilities: {
-      attribute_autosave_enabled: boolean;
-      conveyance_self_test_supported: boolean;
-      error_logging_supported: boolean;
-      exec_offline_immediate_supported: boolean;
-      gp_logging_supported: boolean;
-      offline_is_aborted_upon_new_cmd: boolean;
-      offline_surface_scan_supported: boolean;
-      selective_self_test_supported: boolean;
-      self_tests_supported: boolean;
-      values: number[];
-    };
-    offline_data_collection: {
-      completion_seconds: number;
-      status: { string: string; value: number };
-    };
-    self_test: {
-      polling_minutes: { conveyance: number; extended: number; short: number };
-      status: { passed: boolean; string: string; value: number };
-    };
-  };
-  ata_smart_error_log: { summary: { count: number; revision: number } };
-  ata_smart_selective_self_test_log: {
-    flags: { remainder_scan_enabled: boolean; value: number };
-    power_up_scan_resume_minutes: number;
-    revision: number;
-    table: {
-      lba_max: number;
-      lba_min: number;
-      status: { string: string; value: number };
-    }[];
-  };
-  ata_smart_self_test_log: { standard: { count: number; revision: number } };
-  ata_version: { major_value: number; minor_value: number; string: string };
-  device: { info_name: string; name: string; protocol: string; type: string };
-  firmware_version: string;
-  in_smartctl_database: boolean;
-  interface_speed: {
-    current: {
-      bits_per_unit: number;
-      sata_value: number;
-      string: string;
-      units_per_second: number;
-    };
-    max: {
-      bits_per_unit: number;
-      sata_value: number;
-      string: string;
-      units_per_second: number;
-    };
-  };
-  json_format_version: number[];
-  local_time: { asctime: string; time_t: number };
-  logical_block_size: number;
-  model_family: string;
-  model_name: string;
-  nvme_smart_health_information_add_log_error: string;
-  nvme_smart_health_information_add_log_error_code: number;
-  nvme_vendor: string;
-  physical_block_size: number;
-  power_cycle_count: number;
-  power_on_time: { hours: number };
-  rotation_rate: number;
-  sata_version: { string: string; value: number };
-  serial_number: string;
-  smart_status: { passed: boolean };
-  smartctl: {
-    argv: string[];
-    build_info: string;
-    exit_status: number;
-    output: string[];
-    platform_info: string;
-    svn_revision: string;
-    version: number[];
-  };
-  temperature: { current: number };
-  user_capacity: { blocks: number; bytes: number };
-  wwn: { id: number; naa: number; oui: number };
-}
-
 @Injectable({
   providedIn: ApiModule
 })
@@ -201,9 +80,7 @@ export class OsdService {
    * @param id OSD ID
    */
   getSmartData(id: number) {
-    return this.http.get<{ [deviceId: string]: SmartDataV1 | SmartError }>(
-      `${this.path}/${id}/smart`
-    );
+    return this.http.get<SmartDataResponseV1>(`${this.path}/${id}/smart`);
   }
 
   scrub(id, deep) {
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/smart.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/smart.ts
new file mode 100644 (file)
index 0000000..2f7f82c
--- /dev/null
@@ -0,0 +1,231 @@
+export interface SmartAttribute {
+  flags: {
+    auto_keep: boolean;
+    error_rate: boolean;
+    event_count: boolean;
+    performance: boolean;
+    prefailure: boolean;
+    string: string;
+    updated_online: boolean;
+    value: number;
+  };
+  id: number;
+  name: string;
+  raw: { string: string; value: number };
+  thresh: number;
+  value: number;
+  when_failed: string;
+  worst: number;
+}
+
+/**
+ * The error structure returned from the back-end if SMART data couldn't be
+ * retrieved.
+ */
+export interface SmartError {
+  dev: string;
+  error: string;
+  nvme_smart_health_information_add_log_error: string;
+  nvme_smart_health_information_add_log_error_code: number;
+  nvme_vendor: string;
+  smartctl_error_code: number;
+  smartctl_output: string;
+}
+
+/**
+ * Common smartctl output structure.
+ */
+interface SmartCtlOutput {
+  argv: string[];
+  build_info: string;
+  exit_status: number;
+  output: string[];
+  platform_info: string;
+  svn_revision: string;
+  version: number[];
+}
+
+/**
+ * Common smartctl device structure.
+ */
+interface SmartCtlDevice {
+  info_name: string;
+  name: string;
+  protocol: string;
+  type: string;
+}
+
+/**
+ * smartctl data structure shared among HDD/NVMe.
+ */
+interface SmartCtlBaseDataV1 {
+  device: SmartCtlDevice;
+  firmware_version: string;
+  json_format_version: number[];
+  local_time: { asctime: string; time_t: number };
+  logical_block_size: number;
+  model_name: string;
+  nvme_smart_health_information_add_log_error: string;
+  nvme_smart_health_information_add_log_error_code: number;
+  nvme_vendor: string;
+  power_cycle_count: number;
+  power_on_time: { hours: number };
+  serial_number: string;
+  smart_status: { passed: boolean; nvme?: { value: number } };
+  smartctl: SmartCtlOutput;
+  temperature: { current: number };
+  user_capacity: { blocks: number; bytes: number };
+}
+
+/**
+ * Result structure of `smartctl` applied on an HDD. Returned by the back-end.
+ */
+export interface HddSmartDataV1 extends SmartCtlBaseDataV1 {
+  ata_sct_capabilities: {
+    data_table_supported: boolean;
+    error_recovery_control_supported: boolean;
+    feature_control_supported: boolean;
+    value: number;
+  };
+  ata_smart_attributes: {
+    revision: number;
+    table: SmartAttribute[];
+  };
+  ata_smart_data: {
+    capabilities: {
+      attribute_autosave_enabled: boolean;
+      conveyance_self_test_supported: boolean;
+      error_logging_supported: boolean;
+      exec_offline_immediate_supported: boolean;
+      gp_logging_supported: boolean;
+      offline_is_aborted_upon_new_cmd: boolean;
+      offline_surface_scan_supported: boolean;
+      selective_self_test_supported: boolean;
+      self_tests_supported: boolean;
+      values: number[];
+    };
+    offline_data_collection: {
+      completion_seconds: number;
+      status: { string: string; value: number };
+    };
+    self_test: {
+      polling_minutes: { conveyance: number; extended: number; short: number };
+      status: { passed: boolean; string: string; value: number };
+    };
+  };
+  ata_smart_error_log: { summary: { count: number; revision: number } };
+  ata_smart_selective_self_test_log: {
+    flags: { remainder_scan_enabled: boolean; value: number };
+    power_up_scan_resume_minutes: number;
+    revision: number;
+    table: {
+      lba_max: number;
+      lba_min: number;
+      status: { string: string; value: number };
+    }[];
+  };
+  ata_smart_self_test_log: { standard: { count: number; revision: number } };
+  ata_version: { major_value: number; minor_value: number; string: string };
+  in_smartctl_database: boolean;
+  interface_speed: {
+    current: {
+      bits_per_unit: number;
+      sata_value: number;
+      string: string;
+      units_per_second: number;
+    };
+    max: {
+      bits_per_unit: number;
+      sata_value: number;
+      string: string;
+      units_per_second: number;
+    };
+  };
+  model_family: string;
+  physical_block_size: number;
+  rotation_rate: number;
+  sata_version: { string: string; value: number };
+  smart_status: { passed: boolean };
+  smartctl: SmartCtlOutput;
+  wwn: { id: number; naa: number; oui: number };
+}
+
+/**
+ * Result structure of `smartctl` returned by Ceph and then back-end applied on
+ * an NVMe.
+ */
+export interface NvmeSmartDataV1 extends SmartCtlBaseDataV1 {
+  nvme_controller_id: number;
+  nvme_ieee_oui_identifier: number;
+  nvme_namespaces: {
+    capacity: { blocks: number; bytes: number };
+    eui64: { ext_id: number; oui: number };
+    formatted_lba_size: number;
+    id: number;
+    size: { blocks: number; bytes: number };
+    utilization: { blocks: number; bytes: number };
+  }[];
+  nvme_number_of_namespaces: number;
+  nvme_pci_vendor: { id: number; subsystem_id: number };
+  nvme_smart_health_information_log: {
+    available_spare: number;
+    available_spare_threshold: number;
+    controller_busy_time: number;
+    critical_comp_time: number;
+    critical_warning: number;
+    data_units_read: number;
+    data_units_written: number;
+    host_reads: number;
+    host_writes: number;
+    media_errors: number;
+    num_err_log_entries: number;
+    percentage_used: number;
+    power_cycles: number;
+    power_on_hours: number;
+    temperature: number;
+    temperature_sensors: number[];
+    unsafe_shutdowns: number;
+    warning_temp_time: number;
+  };
+  nvme_total_capacity: number;
+  nvme_unallocated_capacity: number;
+}
+
+/**
+ * The shared fields each result has after it has been processed by the front-end.
+ */
+interface SmartBasicResult {
+  device: string;
+  identifier: string;
+}
+
+/**
+ * The SMART data response structure of the back-end. Per device it will either
+ * contain the structure for a HDD, NVMe or an error.
+ */
+export interface SmartDataResponseV1 {
+  [deviceId: string]: HddSmartDataV1 | NvmeSmartDataV1 | SmartError;
+}
+
+/**
+ * The SMART data result after it has been processed by the front-end.
+ */
+export interface SmartDataResult extends SmartBasicResult {
+  info: { [key: string]: any };
+  smart: {
+    attributes?: any;
+    data?: any;
+    nvmeData?: any;
+  };
+}
+
+/**
+ * The SMART error result after is has been processed by the front-end. If SMART
+ * data couldn't be retrieved, this is the structure which is returned.
+ */
+export interface SmartErrorResult extends SmartBasicResult {
+  error: string;
+  smartctl_error_code: number;
+  smartctl_output: string;
+  userMessage: string;
+}
index 11f19090db7ae678a2da2e6b05bbabcf679f3191..3a842c4e1a1d9c50c88132dc735f95b734b5885c 100644 (file)
@@ -2,7 +2,6 @@
 from __future__ import absolute_import
 import json
 import logging
-from six.moves import reduce
 
 import rados
 
@@ -173,27 +172,27 @@ class CephService(object):
             raise SendCommandError(outs, prefix, argdict, r)
 
         try:
-            return json.loads(outb)
+            return json.loads(outb or outs)
         except Exception:  # pylint: disable=broad-except
             return outb
 
     @staticmethod
-    def get_smart_data_by_device(device):
-        dev_id = device['devid']
+    def _get_smart_data_by_device(device):
         if 'daemons' in device and device['daemons']:
             daemons = [daemon for daemon in device['daemons'] if daemon.startswith('osd')]
             if daemons:
                 svc_type, svc_id = daemons[0].split('.')
-                dev_smart_data = CephService.send_command(svc_type, 'smart', svc_id, devid=dev_id)
+                dev_smart_data = CephService.send_command(
+                    svc_type, 'smart', svc_id, devid=device['devid'])
                 for dev_id, dev_data in dev_smart_data.items():
                     if 'error' in dev_data:
                         logger.warning(
                             '[SMART] error retrieving smartctl data for device ID "%s": %s', dev_id,
                             dev_data)
                 return dev_smart_data
-            logger.warning('[SMART] no OSD service found for device ID "%s"', dev_id)
+            logger.warning('[SMART] no OSD service found for device ID "%s"', device['devid'])
             return {}
-        logger.warning('[SMART] key "daemon" not found for device ID "%s"', dev_id)
+        logger.warning('[SMART] key "daemon" not found for device ID "%s"', device['devid'])
         return {}
 
     @staticmethod
@@ -204,20 +203,24 @@ class CephService(object):
     @staticmethod
     def get_smart_data_by_host(hostname):
         # type: (str) -> dict
-        return reduce(lambda a, b: a.update(b) or a, [
-            CephService.get_smart_data_by_device(device)
-            for device in CephService.get_devices_by_host(hostname)
-        ], {})
+        devices = CephService.get_devices_by_host(hostname)
+        smart_data = {}
+        if devices:
+            for device in devices:
+                if device['devid'] not in smart_data:
+                    smart_data.update(CephService._get_smart_data_by_device(device))
+        return smart_data
 
     @staticmethod
     def get_smart_data_by_daemon(daemon_type, daemon_id):
         # type: (str, str) -> dict
         smart_data = CephService.send_command(daemon_type, 'smart', daemon_id)
-        for _, dev_data in smart_data.items():
-            if 'error' in dev_data:
-                logger.warning('[SMART] Error retrieving smartctl data for daemon "%s.%s"',
-                               daemon_type, daemon_id)
-        return smart_data
+        if smart_data:
+            for _, dev_data in smart_data.items():
+                if 'error' in dev_data:
+                    logger.warning('[SMART] Error retrieving smartctl data for daemon "%s.%s"',
+                                   daemon_type, daemon_id)
+        return smart_data or {}
 
     @classmethod
     def get_rates(cls, svc_type, svc_name, path):