From: Patrick Seidensal Date: Tue, 24 Sep 2019 20:22:22 +0000 (+0200) Subject: mgr/dashboard: smart: add support for NVMe X-Git-Tag: v15.1.0~16^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=0eaf9b548843279972013ad1de16b60562d0eedb;p=ceph.git mgr/dashboard: smart: add support for NVMe Fixes: https://tracker.ceph.com/issues/42483 Signed-off-by: Patrick Seidensal --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts index ba98a8683f55..4a11c8e63bc2 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts @@ -79,6 +79,7 @@ import { ServicesComponent } from './services/services.component'; MgrModulesModule, TypeaheadModule.forRoot(), TimepickerModule.forRoot(), + BsDatepickerModule.forRoot(), NgBootstrapFormValidationModule, CephSharedModule ], diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.spec.ts index 2cd2784b9e88..e747c66416ff 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.spec.ts @@ -84,7 +84,8 @@ describe('OsdListComponent', () => { CephModule, ReactiveFormsModule, RouterTestingModule, - CoreModule + CoreModule, + RouterTestingModule ], declarations: [], providers: [ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/ceph-shared.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/ceph-shared.module.ts index e595f07f3d9a..9f523ca232b5 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/ceph-shared.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/ceph-shared.module.ts @@ -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 index 000000000000..514c2966c04f --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/fixtures/smart_data_version_1_0_hdd_response.json @@ -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 index 000000000000..fce50658a448 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/fixtures/smart_data_version_1_0_nvme_response.json @@ -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 index 514c2966c04f..000000000000 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/fixtures/smart_data_version_1_0_response.json +++ /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 - } - } -} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.html index 588eb0fbd92a..16f940e6d4dd 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.html @@ -1,33 +1,35 @@ - The data received has the JSON format version 2.x and is currently incompatible with the dashboard. - {{ device.value.userMessage }} + {{ device.value.userMessage }} - - - {{ device.value.smart.data.self_test.status.string }} - + + + passed - - {{ device.value.smart.data.self_test.status.string }} - + failed + @@ -36,10 +38,18 @@ - + + [columns]="smartDataColumns"> + + No SMART data available for this device. @@ -48,5 +58,5 @@ - S.M.A.R.T data is loading. + SMART data is loading. diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.spec.ts index 3f11c8c7faa4..a3a685646507 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.spec.ts @@ -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; 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(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'); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.ts index 3938b887007f..15734e9388ad 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.ts @@ -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') }, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/host.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/host.service.ts index b1dcaec12f8d..089a6a99d5ff 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/host.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/host.service.ts @@ -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(`${this.baseURL}/${hostname}/smart`); } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/osd.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/osd.service.ts index d78093e35ae3..0994ba5b223f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/osd.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/osd.service.ts @@ -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(`${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 index 000000000000..2f7f82c4b2ae --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/smart.ts @@ -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; +} diff --git a/src/pybind/mgr/dashboard/services/ceph_service.py b/src/pybind/mgr/dashboard/services/ceph_service.py index 11f19090db7a..3a842c4e1a1d 100644 --- a/src/pybind/mgr/dashboard/services/ceph_service.py +++ b/src/pybind/mgr/dashboard/services/ceph_service.py @@ -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):