diff --git a/setup.py b/setup.py index ebf98a45e..ee2bc4376 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,7 @@ 'sonic_platform_base', 'sonic_platform_base.sonic_eeprom', 'sonic_platform_base.sonic_sfp', - 'sonic_platform_base.sonic_ssd', + 'sonic_platform_base.sonic_storage', 'sonic_platform_base.sonic_pcie', 'sonic_platform_base.sonic_thermal_control', 'sonic_platform_base.sonic_xcvr', diff --git a/sonic_platform_base/sonic_ssd/ssd_generic.py b/sonic_platform_base/sonic_ssd/ssd_generic.py deleted file mode 100644 index 0b076c726..000000000 --- a/sonic_platform_base/sonic_ssd/ssd_generic.py +++ /dev/null @@ -1,267 +0,0 @@ -# -# ssd_generic.py -# -# Generic implementation of the SSD health API -# SSD models supported: -# - InnoDisk -# - StorFly -# - Virtium - -try: - import re - import subprocess - from .ssd_base import SsdBase -except ImportError as e: - raise ImportError (str(e) + "- required module not found") - -SMARTCTL = "smartctl {} -a" -INNODISK = "iSmart -d {}" -VIRTIUM = "SmartCmd -m {}" -TRANSCEND = "scopepro -all {}" - -NOT_AVAILABLE = "N/A" - -# Set Vendor Specific IDs -INNODISK_HEALTH_ID = 169 -INNODISK_TEMPERATURE_ID = 194 -SWISSBIT_HEALTH_ID = 248 -SWISSBIT_TEMPERATURE_ID = 194 -TRANSCEND_HEALTH_ID = 169 -TRANSCEND_TEMPERATURE_ID = 194 - -class SsdUtil(SsdBase): - """ - Generic implementation of the SSD health API - """ - model = NOT_AVAILABLE - serial = NOT_AVAILABLE - firmware = NOT_AVAILABLE - temperature = NOT_AVAILABLE - health = NOT_AVAILABLE - ssd_info = NOT_AVAILABLE - vendor_ssd_info = NOT_AVAILABLE - - def __init__(self, diskdev): - self.vendor_ssd_utility = { - "Generic" : { "utility" : SMARTCTL, "parser" : self.parse_generic_ssd_info }, - "InnoDisk" : { "utility" : INNODISK, "parser" : self.parse_innodisk_info }, - "M.2" : { "utility" : INNODISK, "parser" : self.parse_innodisk_info }, - "StorFly" : { "utility" : VIRTIUM, "parser" : self.parse_virtium_info }, - "Virtium" : { "utility" : VIRTIUM, "parser" : self.parse_virtium_info }, - "Swissbit" : { "utility" : SMARTCTL, "parser" : self.parse_swissbit_info }, - "Transcend" : { "utility" : TRANSCEND, "parser" : self.parse_transcend_info }, - } - - self.dev = diskdev - # Generic part - self.fetch_generic_ssd_info(diskdev) - self.parse_generic_ssd_info() - - # Known vendor part - if self.model: - vendor = self._parse_vendor() - if vendor: - self.fetch_vendor_ssd_info(diskdev, vendor) - self.parse_vendor_ssd_info(vendor) - else: - # No handler registered for this disk model - pass - else: - # Failed to get disk model - self.model = "Unknown" - - def _execute_shell(self, cmd): - process = subprocess.Popen(cmd.split(), universal_newlines=True, stdout=subprocess.PIPE) - output, error = process.communicate() - return output - - def _parse_re(self, pattern, buffer): - res_list = re.findall(pattern, buffer) - return res_list[0] if res_list else NOT_AVAILABLE - - def _parse_vendor(self): - model_short = self.model.split()[0] - if model_short in self.vendor_ssd_utility: - return model_short - elif self.model.startswith('VSF'): - return 'Virtium' - elif self.model.startswith('SFS'): - return 'Swissbit' - elif self.model.startswith('TS'): - return 'Transcend' - else: - return None - - def fetch_generic_ssd_info(self, diskdev): - self.ssd_info = self._execute_shell(self.vendor_ssd_utility["Generic"]["utility"].format(diskdev)) - - # Health and temperature values may be overwritten with vendor specific data - def parse_generic_ssd_info(self): - if "nvme" in self.dev: - self.model = self._parse_re('Model Number:\s*(.+?)\n', self.ssd_info) - - health_raw = self._parse_re('Percentage Used\s*(.+?)\n', self.ssd_info) - if health_raw == NOT_AVAILABLE: - self.health = NOT_AVAILABLE - else: - health_raw = health_raw.split()[-1] - self.health = 100 - float(health_raw.strip('%')) - - temp_raw = self._parse_re('Temperature\s*(.+?)\n', self.ssd_info) - if temp_raw == NOT_AVAILABLE: - self.temperature = NOT_AVAILABLE - else: - temp_raw = temp_raw.split()[-2] - self.temperature = float(temp_raw) - else: - self.model = self._parse_re('Device Model:\s*(.+?)\n', self.ssd_info) - - health_raw = self._parse_re('Remaining_Lifetime_Perc\s*(.+?)\n', self.ssd_info) - if health_raw == NOT_AVAILABLE: - self.health = NOT_AVAILABLE - else: - self.health = health_raw.split()[-1] - - temp_raw = self._parse_re('Temperature_Celsius\s*(.+?)\n', self.ssd_info) - if temp_raw == NOT_AVAILABLE: - self.temperature = NOT_AVAILABLE - else: - self.temperature = temp_raw.split()[-6] - - self.serial = self._parse_re('Serial Number:\s*(.+?)\n', self.ssd_info) - self.firmware = self._parse_re('Firmware Version:\s*(.+?)\n', self.ssd_info) - - def parse_innodisk_info(self): - if self.vendor_ssd_info: - self.health = self._parse_re('Health:\s*(.+?)%', self.vendor_ssd_info) - self.temperature = self._parse_re('Temperature\s*\[\s*(.+?)\]', self.vendor_ssd_info) - - if self.health == NOT_AVAILABLE: - health_raw = self.parse_id_number(INNODISK_HEALTH_ID) - if health_raw == NOT_AVAILABLE: - self.health = NOT_AVAILABLE - else: - self.health = health_raw.split()[-1] - if self.temperature == NOT_AVAILABLE: - temp_raw = self.parse_id_number(INNODISK_TEMPERATURE_ID) - if temp_raw == NOT_AVAILABLE: - self.temperature = NOT_AVAILABLE - else: - self.temperature = temp_raw.split()[-6] - - def parse_virtium_info(self): - if self.vendor_ssd_info: - self.temperature = self._parse_re('Temperature_Celsius\s*\d*\s*(\d+?)\s+', self.vendor_ssd_info) - nand_endurance = self._parse_re('NAND_Endurance\s*\d*\s*(\d+?)\s+', self.vendor_ssd_info) - avg_erase_count = self._parse_re('Average_Erase_Count\s*\d*\s*(\d+?)\s+', self.vendor_ssd_info) - if nand_endurance != NOT_AVAILABLE and avg_erase_count != NOT_AVAILABLE: - try: - self.health = 100 - (float(avg_erase_count) * 100 / float(nand_endurance)) - except (ValueError, ZeroDivisionError): - pass - else: - if self.model == 'VSFDM8XC240G-V11-T': - # The ID of "Remaining Life Left" attribute on 'VSFDM8XC240G-V11-T' device is 231 - # However, it is not recognized by SmartCmd nor smartctl so far - # We need to parse it using the ID number - pattern = '231\s*Reserved_Attribute\s*\d*\s*(\d+?)\s+' - else: - pattern = 'Remaining_Life_Left\s*\d*\s*(\d+?)\s+' - try: - self.health = float(self._parse_re(pattern, self.vendor_ssd_info)) - except ValueError: - pass - - def parse_swissbit_info(self): - if self.ssd_info: - health_raw = self.parse_id_number(SWISSBIT_HEALTH_ID) - if health_raw == NOT_AVAILABLE: - self.health = NOT_AVAILABLE - else: - self.health = health_raw.split()[-1] - temp_raw = self.parse_id_number(SWISSBIT_TEMPERATURE_ID) - if temp_raw == NOT_AVAILABLE: - self.temperature = NOT_AVAILABLE - else: - self.temperature = temp_raw.split()[8] - - def parse_transcend_info(self): - if self.vendor_ssd_info: - self.model = self._parse_re('Model\s*:(.+?)\s*\n', self.vendor_ssd_info) - self.serial = self._parse_re('Serial No\s*:(.+?)\s*\n', self.vendor_ssd_info) - self.firmware = self._parse_re('FW Version\s*:(.+?)\s*\n', self.vendor_ssd_info) - health_raw = self._parse_re('{}\s*(.+?)\n'.format(hex(TRANSCEND_HEALTH_ID).upper()[2:]), self.vendor_ssd_info) #169 -> A9 - if health_raw == NOT_AVAILABLE: - self.health = NOT_AVAILABLE - else: - self.health = health_raw.split()[-1] - temp_raw = self._parse_re('{}\s*(.+?)\n'.format(hex(TRANSCEND_TEMPERATURE_ID).upper()[2:]), self.vendor_ssd_info) #194 -> C2 - if temp_raw == NOT_AVAILABLE: - self.temperature = NOT_AVAILABLE - else: - self.temperature = temp_raw.split()[-1] - - def fetch_vendor_ssd_info(self, diskdev, model): - self.vendor_ssd_info = self._execute_shell(self.vendor_ssd_utility[model]["utility"].format(diskdev)) - - def parse_vendor_ssd_info(self, model): - self.vendor_ssd_utility[model]["parser"]() - - def get_health(self): - """ - Retrieves current disk health in percentages - - Returns: - A float number of current ssd health - e.g. 83.5 - """ - return self.health - - def get_temperature(self): - """ - Retrieves current disk temperature in Celsius - - Returns: - A float number of current temperature in Celsius - e.g. 40.1 - """ - return self.temperature - - def get_model(self): - """ - Retrieves model for the given disk device - - Returns: - A string holding disk model as provided by the manufacturer - """ - return self.model - - def get_firmware(self): - """ - Retrieves firmware version for the given disk device - - Returns: - A string holding disk firmware version as provided by the manufacturer - """ - return self.firmware - - def get_serial(self): - """ - Retrieves serial number for the given disk device - - Returns: - A string holding disk serial number as provided by the manufacturer - """ - return self.serial - - def get_vendor_output(self): - """ - Retrieves vendor specific data for the given disk device - - Returns: - A string holding some vendor specific disk information - """ - return self.vendor_ssd_info - - def parse_id_number(self, id): - return self._parse_re('{}\s*(.+?)\n'.format(id), self.ssd_info) diff --git a/sonic_platform_base/sonic_ssd/__init__.py b/sonic_platform_base/sonic_storage/__init__.py similarity index 100% rename from sonic_platform_base/sonic_ssd/__init__.py rename to sonic_platform_base/sonic_storage/__init__.py diff --git a/sonic_platform_base/sonic_ssd/ssd_emmc.py b/sonic_platform_base/sonic_storage/emmc.py similarity index 78% rename from sonic_platform_base/sonic_ssd/ssd_emmc.py rename to sonic_platform_base/sonic_storage/emmc.py index dfa7036f3..92ada4af5 100644 --- a/sonic_platform_base/sonic_ssd/ssd_emmc.py +++ b/sonic_platform_base/sonic_storage/emmc.py @@ -1,5 +1,5 @@ # -# ssd_emmc.py +# emmc.py # # Implementation of SSD Utility API for eMMC. # It reads eMMC health, model, firmware, and serial from /sys/block/*. @@ -7,15 +7,16 @@ try: import os - from .ssd_base import SsdBase + from .storage_common import StorageCommon except ImportError as e: raise ImportError(str(e) + "- required module not found") -class EmmcUtil(SsdBase): +class EmmcUtil(StorageCommon): def __init__(self, diskdev): self.diskdev = diskdev self.path = os.path.join('/sys/block', os.path.basename(diskdev)) + StorageCommon.__init__(self, diskdev) def _read_device_entry(self, entry, default=None): path = os.path.join(self.path, 'device', entry) @@ -49,3 +50,15 @@ def get_serial(self): def get_vendor_output(self): return '' + + def get_disk_io_reads(self): + return 'N/A' + + def get_disk_io_writes(self): + return 'N/A' + + def get_reserved_blocks(self): + return 'N/A' + + def fetch_parse_info(self, diskdev=None): + return \ No newline at end of file diff --git a/sonic_platform_base/sonic_storage/ssd.py b/sonic_platform_base/sonic_storage/ssd.py new file mode 100644 index 000000000..710311add --- /dev/null +++ b/sonic_platform_base/sonic_storage/ssd.py @@ -0,0 +1,451 @@ +# +# ssd_generic.py +# +# Generic implementation of the SSD health API +# SSD models supported: +# - InnoDisk +# - StorFly +# - Virtium + +try: + import re + import subprocess + + from .storage_common import StorageCommon + from sonic_py_common import syslogger +except ImportError as e: + raise ImportError (str(e) + "- required module not found") + + +SMARTCTL = "smartctl {} -a" +INNODISK = "iSmart -d {}" +VIRTIUM = "SmartCmd -m {}" +TRANSCEND = "scopepro -all {}" + +NOT_AVAILABLE = "N/A" + +# Generic IDs + +GENERIC_HEALTH_ID = 169 +GENERIC_IO_READS_ID = 242 +GENERIC_IO_WRITES_ID = 241 +GENERIC_RESERVED_BLOCKS_ID = [170, 232] + +# Set Vendor Specific IDs +INNODISK_HEALTH_ID = 169 +INNODISK_TEMPERATURE_ID = 194 +INNODISK_IO_WRITES_ID = 241 +INNODISK_IO_READS_ID = 242 +INNODISK_RESERVED_BLOCKS_ID = 232 + +SWISSBIT_HEALTH_ID = 248 +SWISSBIT_TEMPERATURE_ID = 194 + +VIRTIUM_HEALTH_ID = 231 +VIRTIUM_RESERVED_BLOCKS_ID = 232 +VIRTIUM_IO_WRITES_ID = 241 +VIRTIUM_IO_READS_ID = 242 + +MICRON_RESERVED_BLOCKS_ID = [170, 180] +MICRON_IO_WRITES_ID = 246 +MICRON_ERASE_FAIL_COUNT_ID = 172 +MICRON_AVG_ERASE_COUNT_ID = 173 +MICRON_PERC_LIFETIME_REMAIN_ID = 202 + +INTEL_MEDIA_WEAROUT_INDICATOR_ID = 233 +TRANSCEND_HEALTH_ID = 169 +TRANSCEND_TEMPERATURE_ID = 194 + +class SsdUtil(StorageCommon): + """ + Generic implementation of the SSD health API + """ + model = NOT_AVAILABLE + serial = NOT_AVAILABLE + firmware = NOT_AVAILABLE + temperature = NOT_AVAILABLE + health = NOT_AVAILABLE + ssd_info = NOT_AVAILABLE + vendor_ssd_info = NOT_AVAILABLE + fs_io_reads = NOT_AVAILABLE + fs_io_writes = NOT_AVAILABLE + disk_io_reads = NOT_AVAILABLE + disk_io_writes = NOT_AVAILABLE + reserved_blocks = NOT_AVAILABLE + + def __init__(self, diskdev): + + self.log_identifier = "SsdUtil" + self.log = syslogger.SysLogger(self.log_identifier) + + self.vendor_ssd_utility = { + "Generic" : { "utility" : SMARTCTL, "parser" : self.parse_generic_ssd_info }, + "InnoDisk" : { "utility" : INNODISK, "parser" : self.parse_innodisk_info }, + "M.2" : { "utility" : INNODISK, "parser" : self.parse_innodisk_info }, + "StorFly" : { "utility" : VIRTIUM, "parser" : self.parse_virtium_info }, + "Virtium" : { "utility" : VIRTIUM, "parser" : self.parse_virtium_info }, + "Swissbit" : { "utility" : SMARTCTL, "parser" : self.parse_swissbit_info }, + "Micron" : { "utility" : SMARTCTL, "parser" : self.parse_micron_info }, + "Intel" : { "utility" : SMARTCTL, "parser" : self.parse_intel_info }, + "Transcend" : { "utility" : TRANSCEND, "parser" : self.parse_transcend_info }, + } + + self.dev = diskdev + self.fetch_parse_info(diskdev) + + StorageCommon.__init__(self, diskdev) + + def fetch_parse_info(self, diskdev): + + # Generic part + self.fetch_generic_ssd_info(diskdev) + self.parse_generic_ssd_info() + + # Known vendor part + if self.model: + vendor = self._parse_vendor() + if vendor: + + self.fetch_vendor_ssd_info(diskdev, vendor) + try: + self.parse_vendor_ssd_info(vendor) + except Exception as ex: + self.log.log_error("{}".format(str(ex))) + + else: + # No handler registered for this disk model + pass + else: + # Failed to get disk model + self.model = "Unknown" + + def _execute_shell(self, cmd): + process = subprocess.Popen(cmd.split(), universal_newlines=True, stdout=subprocess.PIPE) + output, error = process.communicate() + return output + + def _parse_re(self, pattern, buffer): + res_list = re.findall(pattern, buffer) + return res_list[0] if res_list else NOT_AVAILABLE + + def _parse_vendor(self): + model_short = self.model.split()[0] + if model_short in self.vendor_ssd_utility: + return model_short + elif self.model.startswith('VSF'): + return 'Virtium' + elif self.model.startswith('SFS'): + return 'Swissbit' + elif re.search(r'\bmicron\b', self.model.split('_')[0], re.I): + return 'Micron' + elif re.search(r'\bintel\b', self.model, re.I): + return 'Intel' + elif self.model.startswith('TS'): + return 'Transcend' + else: + return None + + def fetch_generic_ssd_info(self, diskdev): + self.ssd_info = self._execute_shell(self.vendor_ssd_utility["Generic"]["utility"].format(diskdev)) + + # Health and temperature values may be overwritten with vendor specific data + def parse_generic_ssd_info(self): + if "nvme" in self.dev: + self.model = self._parse_re('Model Number:\s*(.+?)\n', self.ssd_info) + + health_raw = self._parse_re('Percentage Used\s*(.+?)\n', self.ssd_info) + if health_raw == NOT_AVAILABLE: + self.health = NOT_AVAILABLE + else: + health_raw = health_raw.split()[-1] + self.health = 100 - float(health_raw.strip('%')) + + temp_raw = self._parse_re('Temperature\s*(.+?)\n', self.ssd_info) + if temp_raw == NOT_AVAILABLE: + self.temperature = NOT_AVAILABLE + else: + temp_raw = temp_raw.split()[-2] + self.temperature = float(temp_raw) + else: + self.model = self._parse_re('Device Model:\s*(.+?)\n', self.ssd_info) + + health_raw = self._parse_re('Remaining_Lifetime_Perc\s*(.+?)\n', self.ssd_info) + if health_raw == NOT_AVAILABLE: + health_raw = self.parse_id_number(GENERIC_HEALTH_ID, self.ssd_info) + if health_raw == NOT_AVAILABLE: + self.health = NOT_AVAILABLE + else: self.health = health_raw.split()[-1] + else: + self.health = health_raw.split()[-1] + + temp_raw = self._parse_re('Temperature_Celsius\s*(.+?)\n', self.ssd_info) + if temp_raw == NOT_AVAILABLE: + self.temperature = NOT_AVAILABLE + else: + self.temperature = temp_raw.split()[7].split()[0] + + self.serial = self._parse_re('Serial Number:\s*(.+?)\n', self.ssd_info) + self.firmware = self._parse_re('Firmware Version:\s*(.+?)\n', self.ssd_info) + + io_reads_raw = self.parse_id_number(GENERIC_IO_READS_ID, self.ssd_info) + self.disk_io_reads = NOT_AVAILABLE if io_reads_raw == NOT_AVAILABLE else io_reads_raw.split()[-1] + + io_writes_raw = self.parse_id_number(GENERIC_IO_WRITES_ID, self.ssd_info) + self.disk_io_writes = NOT_AVAILABLE if io_writes_raw == NOT_AVAILABLE else io_writes_raw.split()[-1] + + for ID in GENERIC_RESERVED_BLOCKS_ID: + rbc_raw = self.parse_id_number(ID, self.ssd_info) + if rbc_raw == NOT_AVAILABLE: self.reserved_blocks = NOT_AVAILABLE + else: + self.reserved_blocks = rbc_raw.split()[-1] + break + + def parse_innodisk_info(self): + if self.vendor_ssd_info: + if self.health == NOT_AVAILABLE: self.health = self._parse_re('Health:\s*(.+?)%', self.vendor_ssd_info) + if self.temperature == NOT_AVAILABLE: self.temperature = self._parse_re('Temperature\s*\[\s*(.+?)\]', self.vendor_ssd_info) + if self.firmware == NOT_AVAILABLE: self.firmware = (self._parse_re('.*FW.*', self.vendor_ssd_info)).split()[-1] + if self.serial == NOT_AVAILABLE: self.serial = (self._parse_re('.*Serial.*', self.vendor_ssd_info)).split()[-1] + + if self.health == NOT_AVAILABLE: + health_raw = self.parse_id_number("[{}]".format(hex(INNODISK_HEALTH_ID)[2:]).upper(), self.vendor_ssd_info) + if health_raw == NOT_AVAILABLE: + self.health = NOT_AVAILABLE + else: + self.health = health_raw.split()[-2].strip("[]") + if self.temperature == NOT_AVAILABLE: + temp_raw = self.parse_id_number("[{}]".format(hex(INNODISK_TEMPERATURE_ID)[2:]).upper(), self.vendor_ssd_info) + if temp_raw == NOT_AVAILABLE: + self.temperature = NOT_AVAILABLE + else: + self.temperature = temp_raw.split()[-6] + if self.disk_io_reads == NOT_AVAILABLE: + io_reads_raw = self.parse_id_number("[{}]".format(hex(INNODISK_IO_READS_ID)[2:]).upper(), self.vendor_ssd_info) + if io_reads_raw == NOT_AVAILABLE: + self.disk_io_reads == NOT_AVAILABLE + else: + self.disk_io_reads = io_reads_raw.split()[-2].strip("[]") + if self.disk_io_writes == NOT_AVAILABLE: + io_writes_raw = self.parse_id_number("[{}]".format(hex(INNODISK_IO_WRITES_ID)[2:]).upper(), self.vendor_ssd_info) + if io_writes_raw == NOT_AVAILABLE: + self.disk_io_writes == NOT_AVAILABLE + else: + self.disk_io_writes = io_writes_raw.split()[-2].strip("[]") + if self.reserved_blocks == NOT_AVAILABLE: + rbc_raw = self.parse_id_number("[{}]".format(hex(INNODISK_RESERVED_BLOCKS_ID)[2:]).upper(), self.vendor_ssd_info) + if rbc_raw == NOT_AVAILABLE: + self.reserved_blocks == NOT_AVAILABLE + else: + self.reserved_blocks = rbc_raw.split()[-2].strip("[]") + + def parse_virtium_info(self): + if self.vendor_ssd_info: + self.temperature = self._parse_re('Temperature_Celsius\s*\d*\s*(\d+?)\s+', self.vendor_ssd_info) + nand_endurance = self._parse_re('NAND_Endurance\s*\d*\s*(\d+?)\s+', self.vendor_ssd_info) + avg_erase_count = self._parse_re('Average_Erase_Count\s*\d*\s*(\d+?)\s+', self.vendor_ssd_info) + if nand_endurance != NOT_AVAILABLE and avg_erase_count != NOT_AVAILABLE: + try: + self.health = 100 - (float(avg_erase_count) * 100 / float(nand_endurance)) + except (ValueError, ZeroDivisionError) as ex: + self.log.log_info("SsdUtil parse_virtium_info exception: {}".format(ex)) + pass + else: + health_raw = NOT_AVAILABLE + try: + if self.model == 'VSFDM8XC240G-V11-T': + # The ID of "Remaining Life Left" attribute on 'VSFDM8XC240G-V11-T' device is 231 + # However, it is not recognized by SmartCmd nor smartctl so far + # We need to parse it using the ID number + health_raw = self.parse_id_number(VIRTIUM_HEALTH_ID, self.vendor_ssd_info) + self.health = float(health_raw.split()[2]) if health_raw != NOT_AVAILABLE else NOT_AVAILABLE + else: + pattern = 'Remaining_Life_Left\s*\d*\s*(\d+?)\s+' + health_raw = self._parse_re(pattern, self.vendor_ssd_info) + self.health = float(health_raw.split()[-1]) if health_raw != NOT_AVAILABLE else NOT_AVAILABLE + except ValueError as ex: + self.log.log_info("SsdUtil parse_virtium_info exception: {}".format(ex)) + pass + + if self.disk_io_reads == NOT_AVAILABLE: + io_reads_raw = self.parse_id_number(VIRTIUM_IO_READS_ID, self.vendor_ssd_info) + if io_reads_raw == NOT_AVAILABLE: + self.disk_io_reads == NOT_AVAILABLE + else: + self.disk_io_reads = io_reads_raw.split()[-1] + + if self.disk_io_writes == NOT_AVAILABLE: + io_writes_raw = self.parse_id_number(VIRTIUM_IO_WRITES_ID, self.vendor_ssd_info) + if io_writes_raw == NOT_AVAILABLE: + self.disk_io_writes == NOT_AVAILABLE + else: + self.disk_io_writes = io_writes_raw.split()[-1] + + if self.reserved_blocks == NOT_AVAILABLE: + rbc_raw = self.parse_id_number(VIRTIUM_RESERVED_BLOCKS_ID, self.vendor_ssd_info) + if rbc_raw == NOT_AVAILABLE: + self.reserved_blocks == NOT_AVAILABLE + else: + self.reserved_blocks = rbc_raw.split()[-1] + + def parse_swissbit_info(self): + if self.ssd_info: + health_raw = self.parse_id_number(SWISSBIT_HEALTH_ID, self.ssd_info) + if health_raw == NOT_AVAILABLE: + self.health = NOT_AVAILABLE + else: + self.health = health_raw.split()[-1] + temp_raw = self.parse_id_number(SWISSBIT_TEMPERATURE_ID, self.ssd_info) + if temp_raw == NOT_AVAILABLE: + self.temperature = NOT_AVAILABLE + else: + self.temperature = temp_raw.split()[8] + + def parse_micron_info(self): + if self.vendor_ssd_info: + health_raw = self._parse_re('{}\s*(.+?)\n'.format('Percent_Lifetime_Used'), self.vendor_ssd_info) + if health_raw == NOT_AVAILABLE: + health_raw = self._parse_re('{}\s*(.+?)\n'.format('Percent_Lifetime_Remain'), self.vendor_ssd_info) + self.health = health_raw.split()[-1] + else: + self.health = str(100 - int(health_raw.split()[-1])) + + if health_raw == NOT_AVAILABLE: + average_erase_count = self.parse_id_number(MICRON_AVG_ERASE_COUNT_ID, self.vendor_ssd_info) + erase_fail_count = self.parse_id_number(MICRON_ERASE_FAIL_COUNT_ID, self.vendor_ssd_info) + + if average_erase_count != NOT_AVAILABLE and erase_fail_count != NOT_AVAILABLE: + try: + self.health = 100 - (float(average_erase_count) * 100 / float(nand_endurance)) + except (ValueError, ZeroDivisionError) as ex: + self.log.log_info("SsdUtil parse_micron_info exception: {}".format(ex)) + pass + + io_writes_raw = self.parse_id_number(MICRON_IO_WRITES_ID, self.vendor_ssd_info) + self.disk_io_writes = NOT_AVAILABLE if io_writes_raw == NOT_AVAILABLE else io_writes_raw.split()[-1] + + for ID in MICRON_RESERVED_BLOCKS_ID: + rbc_raw = self.parse_id_number(ID, self.vendor_ssd_info) + + if rbc_raw == NOT_AVAILABLE: self.reserved_blocks = NOT_AVAILABLE + else: + self.reserved_blocks = rbc_raw.split()[-1] + break + + def parse_intel_info(self): + if self.vendor_ssd_info: + health_raw = self.parse_id_number(INTEL_MEDIA_WEAROUT_INDICATOR_ID, self.vendor_ssd_info) + self.health = NOT_AVAILABLE if health_raw == NOT_AVAILABLE else str(100 - float(health_raw.split()[-1])) + + def parse_transcend_info(self): + if self.vendor_ssd_info: + self.model = self._parse_re('Model\s*:(.+?)\s*\n', self.vendor_ssd_info) + self.serial = self._parse_re('Serial No\s*:(.+?)\s*\n', self.vendor_ssd_info) + self.firmware = self._parse_re('FW Version\s*:(.+?)\s*\n', self.vendor_ssd_info) + health_raw = self._parse_re('{}\s*(.+?)\n'.format(hex(TRANSCEND_HEALTH_ID).upper()[2:]), self.vendor_ssd_info) #169 -> A9 + if health_raw == NOT_AVAILABLE: + self.health = NOT_AVAILABLE + else: + self.health = health_raw.split()[-1] + temp_raw = self._parse_re('{}\s*(.+?)\n'.format(hex(TRANSCEND_TEMPERATURE_ID).upper()[2:]), self.vendor_ssd_info) #194 -> C2 + if temp_raw == NOT_AVAILABLE: + self.temperature = NOT_AVAILABLE + else: + self.temperature = temp_raw.split()[-1] + + def fetch_vendor_ssd_info(self, diskdev, model): + self.vendor_ssd_info = self._execute_shell(self.vendor_ssd_utility[model]["utility"].format(diskdev)) + + def parse_vendor_ssd_info(self, model): + self.vendor_ssd_utility[model]["parser"]() + + def get_health(self): + """ + Retrieves current disk health in percentages + + Returns: + A float number of current ssd health + e.g. 83.5 + """ + return self.health + + def get_temperature(self): + """ + Retrieves current disk temperature in Celsius + + Returns: + A float number of current temperature in Celsius + e.g. 40.1 + """ + return self.temperature + + def get_model(self): + """ + Retrieves model for the given disk device + + Returns: + A string holding disk model as provided by the manufacturer + """ + return self.model + + def get_firmware(self): + """ + Retrieves firmware version for the given disk device + + Returns: + A string holding disk firmware version as provided by the manufacturer + """ + return self.firmware + + def get_serial(self): + """ + Retrieves serial number for the given disk device + + Returns: + A string holding disk serial number as provided by the manufacturer + """ + return self.serial + + def get_disk_io_reads(self): + """ + Retrieves the total number of Input/Output (I/O) reads done on an SSD + + Returns: + An integer value of the total number of I/O reads + """ + return self.disk_io_reads + + def get_disk_io_writes(self): + """ + Retrieves the total number of Input/Output (I/O) writes done on an SSD + + Returns: + An integer value of the total number of I/O writes + """ + return self.disk_io_writes + + def get_reserved_blocks(self): + """ + Retrieves the total number of reserved blocks in an SSD + + Returns: + An integer value of the total number of reserved blocks + """ + return self.reserved_blocks + + def get_vendor_output(self): + """ + Retrieves vendor specific data for the given disk device + + Returns: + A string holding some vendor specific disk information + """ + return self.vendor_ssd_info + + def parse_id_number(self, id, buffer): + if buffer: + buffer_lines = buffer.split('\n') + for line in buffer_lines: + if line.strip().startswith(str(id)): + return line[len(str(id)):] + + return NOT_AVAILABLE diff --git a/sonic_platform_base/sonic_ssd/ssd_base.py b/sonic_platform_base/sonic_storage/storage_base.py similarity index 63% rename from sonic_platform_base/sonic_ssd/ssd_base.py rename to sonic_platform_base/sonic_storage/storage_base.py index 2c80d621f..f07fc6d4e 100644 --- a/sonic_platform_base/sonic_ssd/ssd_base.py +++ b/sonic_platform_base/sonic_storage/storage_base.py @@ -1,13 +1,13 @@ # -# ssd_base.py +# storage_base.py # # Base class for implementing common SSD health features # -class SsdBase(object): +class StorageBase(object): """ - Base class for interfacing with a SSD + Base class for interfacing with a storage disk """ def __init__(self, diskdev): """ @@ -23,7 +23,7 @@ def get_health(self): Retrieves current disk health in percentages Returns: - A float number of current ssd health + A float number of current disk health e.g. 83.5 """ raise NotImplementedError @@ -73,3 +73,30 @@ def get_vendor_output(self): A string holding some vendor specific disk information """ raise NotImplementedError + + def get_disk_io_reads(self): + """ + Retrieves the total number of Input/Output (I/O) reads done on a storage disk + + Returns: + An integer value of the total number of I/O reads + """ + raise NotImplementedError + + def get_disk_io_writes(self): + """ + Retrieves the total number of Input/Output (I/O) writes done on a storage disk + + Returns: + An integer value of the total number of I/O writes + """ + raise NotImplementedError + + def get_reserved_blocks(self): + """ + Retrieves the total number of reserved blocks in an storage disk + + Returns: + An integer value of the total number of reserved blocks + """ + raise NotImplementedError diff --git a/sonic_platform_base/sonic_storage/storage_common.py b/sonic_platform_base/sonic_storage/storage_common.py new file mode 100644 index 000000000..7b6d313a4 --- /dev/null +++ b/sonic_platform_base/sonic_storage/storage_common.py @@ -0,0 +1,68 @@ +# +# storage_common.py +# +# Class for common implementation functions, +# i.e. functions that are storage device type agnostic. +# + +try: + import os + import sys + import psutil + from sonic_py_common import syslogger + from .storage_base import StorageBase +except ImportError as e: + raise ImportError (str(e) + "- required module not found") + +class StorageCommon(StorageBase, object): + def __init__(self, diskdev): + """ + Constructor + + Args: + Block device path for which we need to get information + """ + self.log_identifier = "StorageCommon" + self.log = syslogger.SysLogger(self.log_identifier) + + self.storage_disk = os.path.basename(diskdev) + + def get_fs_io_reads(self): + """ + Function to get the latest reads on the disk by parsing the /proc/diskstats file + + Returns: + The total number of procfs reads + + Args: + N/A + """ + + fsstats_reads = 0 + try: + fsstats_reads = int(psutil.disk_io_counters(perdisk=True, nowrap=True)[self.storage_disk].read_count) + except Exception as ex: + self.log.log_warning("get_fs_io_reads exception: {}".format(ex)) + pass + + return fsstats_reads + + def get_fs_io_writes(self): + """ + Function to get the latest disk writes by parsing the /proc/diskstats file + + Returns: + The total number of procfs writes + + Args: + N/A + """ + + fsstats_writes = 0 + try: + fsstats_writes = psutil.disk_io_counters(perdisk=True, nowrap=True)[self.storage_disk].write_count + except Exception as ex: + self.log.log_warning("get_fs_io_writes exception: {}".format(ex)) + pass + + return fsstats_writes diff --git a/sonic_platform_base/sonic_storage/storage_devices.py b/sonic_platform_base/sonic_storage/storage_devices.py new file mode 100644 index 000000000..3613c23e0 --- /dev/null +++ b/sonic_platform_base/sonic_storage/storage_devices.py @@ -0,0 +1,88 @@ +# +# storage_devices.py +# +# Composition class for the instantiation of objects of various storage device classes, +# i.e. SsdUtil, EmmcUtil +# + + +try: + import os + import sys + from sonic_py_common import syslogger + from sonic_platform_base.sonic_storage.ssd import SsdUtil + from sonic_platform_base.sonic_storage.emmc import EmmcUtil +except ImportError as e: + raise ImportError (str(e) + "- required module not found") + +# Not currently supported +try: + from sonic_platform_base.sonic_storage.usb import UsbUtil +except ImportError as e: + pass + +BASE_PATH = "/sys/block" +BLKDEV_BASE_PATH = "/dev" + +class StorageDevices: + def __init__(self): + self.log_identifier = "StorageDevices" + self.log = syslogger.SysLogger(self.log_identifier) + self.devices = {} + + # Populate the self.devices dictionary with as many key-values pairs as storage disks, + # where key is the name of the storage disk and temporary value is None. + self._get_storage_devices() + + def _get_storage_devices(self): + """ + Function to get a list of storage disks in the switch by populating the self.devices dictionary with storage disk identifiers found in the /sys/block directory. + + Args: N/A + + Returns: N/A + + """ + fdlist = os.listdir(BASE_PATH) + for fd in fdlist: + if 'boot' in fd or 'loop' in fd: + continue + else: + # Populate value for each key in dictionary with corresponding storage class object + self.devices[fd] = self._storage_device_object_factory(fd) + + def _storage_device_object_factory(self, key): + """ + A function that returns instances of storage device utility classes + based on the disk identifiers obtained in the _get_storage_devices + + Args: N/A + + Returns: N/A + + """ + + blkdev = os.path.join(BLKDEV_BASE_PATH, key) + diskdev = os.path.join(BASE_PATH, key) + + if key.startswith('sd'): + path = os.path.join(diskdev, "device") + if "ata" in os.path.realpath(path): + try: + return SsdUtil(blkdev) + except Exception as e: + self.log.log_warning("Failed to instantiate SsdUtil object. Error: {}".format(str(e)), True) + + elif "usb" in os.path.realpath(path): + try: + return UsbUtil(blkdev) + except Exception as e: + self.log.log_warning("Failed to instantiate UsbUtil object. Error: {}".format(str(e)), True) + + elif "mmcblk" in key: + try: + return EmmcUtil(key) + except Exception as e: + self.log.log_warning("Failed to instantiate EmmcUtil object. Error: {}".format(str(e)), True) + + return None diff --git a/tests/ssd_emmc_test.py b/tests/test_emmc.py similarity index 60% rename from tests/ssd_emmc_test.py rename to tests/test_emmc.py index d8a5fefbb..f0f50a275 100644 --- a/tests/ssd_emmc_test.py +++ b/tests/test_emmc.py @@ -1,22 +1,20 @@ import sys -if sys.version_info.major == 3: - from unittest.mock import mock_open, patch -else: - from mock import mock_open, patch +import mock -from sonic_platform_base.sonic_ssd.ssd_emmc import EmmcUtil +from sonic_platform_base.sonic_storage.emmc import EmmcUtil mocked_files = { '/sys/block/emmctest/device/enhanced_area_offset': '0', '/sys/block/emmctest/device/life_time': '0x02 0x02', '/sys/block/emmctest/device/name': 'Test eMMC device', '/sys/block/emmctest/device/fwrev': '0xAA00000000000000', - '/sys/block/emmctest/device/serial': '0xabcdefef' + '/sys/block/emmctest/device/serial': '0xabcdefef', + '/proc/mounts' : '/dev/emmctestp1 /host ext4 rw,relatime 0 0' } def build_mocked_sys_fs_open(files): - mocks = dict([(fname, mock_open(read_data=cnt).return_value) + mocks = dict([(fname, mock.mock_open(read_data=cnt).return_value) for fname, cnt in files.items()]) def mopen(fname): @@ -27,9 +25,10 @@ def mopen(fname): return mopen -class TestSsdEMMC: +class TestEMMC: + + @mock.patch('builtins.open', new=build_mocked_sys_fs_open(mocked_files)) - @patch('builtins.open', new=build_mocked_sys_fs_open(mocked_files)) def test_check(self, *args): util = EmmcUtil('emmctest') @@ -38,3 +37,8 @@ def test_check(self, *args): assert (util.get_model() == 'Test eMMC device') assert (util.get_firmware() == '0xAA00000000000000') assert (util.get_serial() == '0xabcdefef') + assert (util.get_disk_io_reads() == 'N/A') + assert (util.get_disk_io_writes() == 'N/A') + assert (util.get_reserved_blocks() == 'N/A') + assert (util.fetch_parse_info() == None) + diff --git a/tests/ssd_generic_test.py b/tests/test_ssd.py similarity index 65% rename from tests/ssd_generic_test.py rename to tests/test_ssd.py index 338e27fff..085583f0a 100644 --- a/tests/ssd_generic_test.py +++ b/tests/test_ssd.py @@ -5,7 +5,7 @@ else: import mock -from sonic_platform_base.sonic_ssd.ssd_generic import SsdUtil +from sonic_platform_base.sonic_storage.ssd import SsdUtil output_nvme_ssd = """smartctl 7.2 2020-12-30 r5155 [x86_64-linux-5.10.0-8-2-amd64] (local build) Copyright (C) 2002-20, Bruce Allen, Christian Franke, www.smartmontools.org @@ -152,22 +152,29 @@ Selective Self-tests/Logging not supported""" -output_Innodisk_ssd = """smartctl 6.6 2017-11-05 r4594 [x86_64-linux-4.19.0-12-2-amd64] (local build) -Copyright (C) 2002-17, Bruce Allen, Christian Franke, www.smartmontools.org +output_ssd_leading_trailing_spaces = """ + 241 Host_Writes_32MiB 0x0002 100 001 000 Old_age Always - 178564 + 242 Host_Reads_32MiB 0x0002 100 001 000 Old_age Always - 760991 +""" + +output_Innodisk_ssd = """smartctl 7.2 2020-12-30 r5155 [x86_64-linux-5.10.0-23-2-amd64] (local build) +Copyright (C) 2002-20, Bruce Allen, Christian Franke, www.smartmontools.org === START OF INFORMATION SECTION === -Model Family: Innodisk 1ME3/3ME/3SE SSDs -Device Model: InnoDisk Corp. - mSATA 3ME -Serial Number: 20171126AAAA11730156 -Firmware Version: S140714 -User Capacity: 32,017,047,552 bytes [32.0 GB] +Model Family: Innodisk 3IE3/3ME3/3ME4 SSDs +Device Model: InnoDisk Corp. - mSATA 3IE3 +Serial Number: BCA11802090990501 +LU WWN Device Id: 5 24693f 2ca22d959 +Firmware Version: S16425cG +User Capacity: 16,013,942,784 bytes [16.0 GB] Sector Size: 512 bytes logical/physical Rotation Rate: Solid State Device Form Factor: 2.5 inches +TRIM Command: Available Device is: In smartctl database [for details use: -P show] -ATA Version is: ACS-2 (minor revision not indicated) +ATA Version is: ATA8-ACS (minor revision not indicated) SATA Version is: SATA 3.0, 6.0 Gb/s (current: 6.0 Gb/s) -Local Time is: Thu Mar 31 08:24:17 2022 UTC +Local Time is: Thu May 23 08:13:07 2024 UTC SMART support is: Available - device has SMART capability. SMART support is: Enabled @@ -186,7 +193,7 @@ power-saving mode. Supports SMART auto save timer. Error logging capability: (0x00) Error logging NOT supported. - General Purpose Logging supported. + No General Purpose Logging support. SCT capabilities: (0x0039) SCT Status supported. SCT Error Recovery Control supported. SCT Feature Control supported. @@ -198,28 +205,33 @@ 1 Raw_Read_Error_Rate 0x0000 000 000 000 Old_age Offline - 0 2 Throughput_Performance 0x0000 000 000 000 Old_age Offline - 0 3 Spin_Up_Time 0x0000 000 000 000 Old_age Offline - 0 - 5 Reallocated_Sector_Ct 0x0002 100 100 000 Old_age Always - 0 + 5 Later_Bad_Block 0x0013 100 100 001 Pre-fail Always - 0 7 Seek_Error_Rate 0x0000 000 000 000 Old_age Offline - 0 8 Seek_Time_Performance 0x0000 000 000 000 Old_age Offline - 0 - 9 Power_On_Hours 0x0002 100 100 000 Old_age Always - 32474 + 9 Power_On_Hours 0x0002 140 000 000 Old_age Always - 49036 10 Spin_Retry_Count 0x0000 000 000 000 Old_age Offline - 0 - 12 Power_Cycle_Count 0x0002 100 100 000 Old_age Always - 297 + 12 Power_Cycle_Count 0x0002 012 000 000 Old_age Always - 2828 +163 Total_Bad_Block_Count 0x0000 000 000 000 Old_age Offline - 19 168 SATA_PHY_Error_Count 0x0000 000 000 000 Old_age Offline - 0 -169 Unknown_Innodisk_Attr 0x0000 000 000 000 Old_age Offline - 0x000000000000 +169 Remaining_Lifetime_Perc 0x0000 092 000 000 Old_age Offline - 92 175 Bad_Cluster_Table_Count 0x0000 000 000 000 Old_age Offline - 0 192 Power-Off_Retract_Count 0x0000 000 000 000 Old_age Offline - 0 - 1 Raw_Read_Error_Rate 0x0000 000 000 000 Old_age Offline - 2199023255552 -197 Current_Pending_Sector 0x0000 000 000 000 Old_age Offline - 0 +194 Temperature_Celsius 0x0000 030 100 000 Old_age Offline - 30 (2 100 0 0 0) +197 Current_Pending_Sector 0x0012 000 100 000 Old_age Always - 0 +225 Data_Log_Write_Count 0x0000 000 074 000 Old_age Offline - 38494758 240 Write_Head 0x0000 000 000 000 Old_age Offline - 0 -225 Unknown_Innodisk_Attr 0x0000 000 000 000 Old_age Offline - 0 -170 Bad_Block_Count 0x0003 100 100 --- Pre-fail Always - 0 47 0 -173 Erase_Count 0x0002 100 100 --- Old_age Always - 0 7280 7192 -229 Flash_ID 0x0002 100 100 --- Old_age Always - 0x50769394de98 -236 Unstable_Power_Count 0x0002 100 100 --- Old_age Always - 0 -235 Later_Bad_Block 0x0002 100 000 --- Old_age Always - 0 -176 Uncorr_RECORD_Count 0x0000 100 000 --- Old_age Offline - 0 - -Read SMART Log Directory failed: scsi error badly formed scsi parameters +165 Max_Erase_Count 0x0002 183 001 000 Old_age Always - 1463 +167 Average_Erase_Count 0x0002 175 001 000 Old_age Always - 1455 +170 Spare_Block_Count 0x0003 100 001 000 Pre-fail Always - 59 +171 Program_Fail_Count 0x0002 000 001 000 Old_age Always - 0 +172 Erase_Fail_Count 0x0002 000 001 000 Old_age Always - 0 +174 Unknown_Attribute 0x0003 100 001 000 Pre-fail Always - 76 +177 Wear_Leveling_Count 0x0002 100 001 000 Old_age Always - 2811 +229 Flash_ID 0x0002 100 001 000 Old_age Always - 0x51769394de98 +232 Spares_Remaining_Perc 0x0003 100 001 000 Pre-fail Always - 0 +235 Later_Bad_Blk_Inf_R/W/E 0x0002 000 000 000 Old_age Always - 0 0 0 +241 Host_Writes_32MiB 0x0002 100 001 000 Old_age Always - 150370 +242 Host_Reads_32MiB 0x0002 100 001 000 Old_age Always - 73954 SMART Error Log not supported @@ -232,29 +244,81 @@ output_Innodisk_vendor_info = """******************************************************************************************** * Innodisk iSMART V3.9.41 2018/05/25 * ******************************************************************************************** -Model Name: InnoDisk Corp. - mSATA 3ME -FW Version: S140714 -Serial Number: 20171126AAAA11730156 -Health: 82.34% -Capacity: 29.818199 GB -P/E Cycle: 3000 -Lifespan : 0 (Years : 0 Months : 0 Days : 0) +Model Name: InnoDisk Corp. - mSATA 3IE3 +FW Version: S16425cG +Serial Number: BCA11802090990501 +Health: 92.725% +Capacity: 14.914146 GB +P/E Cycle: 20000 +Lifespan : 25000 (Years : 68 Months : 6 Days : 0) +iAnalyzer: Disable Write Protect: Disable InnoRobust: Enable -------------------------------------------------------------------------------------------- ID SMART Attributes Value Raw Value -------------------------------------------------------------------------------------------- -[09] Power On Hours [32474] [0902006464DA7E0000000000] -[0C] Power Cycle Count [ 297] [0C0200646429010000000000] -[AA] Total Bad Block Count [ 47] [AA0300646400002F00000000] -[AD] Erase Count Max. [ 7280] [AD02006464181C701C000000] -[AD] Erase Count Avg. [ 7192] [AD02006464181C701C000000] -[C2] Temperature [ 0] [000000000000000000000000] -[EB] Later Bad Block [ 0] [EB0200640000000000000000] -[EB] Read Block [ 0] [EB0200640000000000000000] -[EB] Write Block [ 0] [EB0200640000000000000000] -[EB] Erase Block [ 0] [EB0200640000000000000000] -[EC] Unstable Power Count [ 0] [EC0200646400000000000000] +[09] Power On Hours [49036] [0902008C008CBF0000000000] +[0C] Power Cycle Count [ 2828] [0C02000C000C0B0000000000] +[A5] Maximum Erase Count [ 1463] [A50200B701B7050000000000] +[A7] Average Erase Count [ 1455] [A70200AF01AF050000000000] +[AB] Program fail count [ 0] [AB0200000100000000000000] +[AC] Erase fail count [ 0] [AC0200000100000000000000] +[AD] Erase Count [ 0] [000000000000000000000000] +[BB] Reported Uncorrect count [ 0] [000000000000000000000000] +[C2] Temperature [ 30] [C200001E641E00000064021E] +[E8] Percentage os spare remaining [ 0] [E80300640100000000000000] +[EB] Later Bad Block [ 0] [EB0200000000000000000000] +[EB] Read Block [ 0] [EB0200000000000000000000] +[EB] Write Block [ 0] [EB0200000000000000000000] +[EB] Erase Block [ 0] [EB0200000000000000000000] +[F1] Total LBAs Written [150370] [F102006401624B0200000000] +[F2] Total LBAs Read [73954] [F202006401E2200100000000] +-------------------------------------------------------------------------------------------- + Read & Write +-------------------------------------------------------------------------------------------- +Sequential Read = 1% (0) +Random Read = 0% (0) +Sequential Write = 0% (0) +Random Write = 0% (0) +-------------------------------------------------------------------------------------------- +Sequential Read +-------------------------------------------------------------------------------------------- +Size Percentage Count +8M 0% (0) +4M 0% (0) +1M 0% (0) +128K 0% (0) +64K 0% (0) +32K 0% (0) +-------------------------------------------------------------------------------------------- +Sequential Write +-------------------------------------------------------------------------------------------- +Size Percentage Count +8M 0% (0) +4M 0% (0) +1M 0% (0) +128K 0% (0) +64K 0% (0) +32K 0% (0) +-------------------------------------------------------------------------------------------- +Random Read +-------------------------------------------------------------------------------------------- +Size Percentage Count +64K 0% (0) +32K 0% (0) +16K 0% (0) +8K 0% (0) +4K 0% (0) +-------------------------------------------------------------------------------------------- +Random Write +-------------------------------------------------------------------------------------------- +Size Percentage Count +64K 0% (0) +32K 0% (0) +16K 0% (0) +8K 0% (0) +4K 0% (0) + """ output_lack_info_ssd = """smartctl 7.2 2020-12-30 r5155 [x86_64-linux-5.10.0-8-2-amd64] (local build) @@ -668,6 +732,23 @@ ID Attribute High Raw Low Raw Value Worst Threshold """ +output_virtium_generic_trick_number = """ +smartctl 7.4 2023-08-01 r5530 [x86_64-linux-6.1.0-11-2-amd64] (local build) +Copyright (C) 2002-23, Bruce Allen, Christian Franke, www.smartmontools.org + +ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE + +194 Temperature_Celsius 0x0023 058 241 000 Pre-fail Always - 42 (Min/Max 29/115) +241 Total_LBAs_Written 0x0012 100 100 000 Old_age Always - 18782480803 +""" + +output_virtium_vendor_trick_number = """ +SMART attributes + ID Attribute High Raw Low Raw Value Worst Threshold +194 Temperature_Celsius 241 42 100 100 0 +241 Total_LBAs_Written 0 18782480803 100 100 0 +""" + output_swissbit_vendor = """ smartctl 7.2 2020-12-30 r5155 [x86_64-linux-5.10.0-23-2-amd64] (local build) Copyright (C) 2002-20, Bruce Allen, Christian Franke, www.smartmontools.org @@ -819,8 +900,237 @@ Health Percentage: 71% """ -class TestSsdGeneric: - @mock.patch('sonic_platform_base.sonic_ssd.ssd_generic.SsdUtil._execute_shell', mock.MagicMock(return_value=output_nvme_ssd)) +output_micron_ssd="""smartctl 6.6 2017-11-05 r4594 [x86_64-linux-4.9.0-14-2-amd64] (local build) +Copyright (C) 2002-17, Bruce Allen, Christian Franke, www.smartmontools.org + +=== START OF INFORMATION SECTION === +Model Family: Crucial/Micron MX1/2/300, M5/600, 1100 Client SSDs +Device Model: Micron_M550_MTFDDAT064MAY +Serial Number: MSA1827061P +LU WWN Device Id: 5 00a075 10d9c54a7 +Firmware Version: MU01 +User Capacity: 64,023,257,088 bytes [64.0 GB] +Sector Sizes: 512 bytes logical, 4096 bytes physical +Rotation Rate: Solid State Device +Form Factor: < 1.8 inches +Device is: In smartctl database [for details use: -P show] +ATA Version is: ACS-2, ATA8-ACS T13/1699-D revision 6 +SATA Version is: SATA 3.1, 6.0 Gb/s (current: 3.0 Gb/s) +Local Time is: Mon May 20 18:31:29 2024 UTC +SMART support is: Available - device has SMART capability. +SMART support is: Enabled + +=== START OF READ SMART DATA SECTION === +SMART overall-health self-assessment test result: PASSED + +General SMART Values: +Offline data collection status: (0x80) Offline data collection activity + was never started. + Auto Offline Data Collection: Enabled. +Self-test execution status: ( 0) The previous self-test routine completed + without error or no self-test has ever + been run. +Total time to complete Offline +data collection: ( 295) seconds. +Offline data collection +capabilities: (0x7b) SMART execute Offline immediate. + Auto Offline data collection on/off support. + Suspend Offline collection upon new + command. + Offline surface scan supported. + Self-test supported. + Conveyance Self-test supported. + Selective Self-test supported. +SMART capabilities: (0x0003) Saves SMART data before entering + power-saving mode. + Supports SMART auto save timer. +Error logging capability: (0x01) Error logging supported. + General Purpose Logging supported. +Short self-test routine +recommended polling time: ( 2) minutes. +Extended self-test routine +recommended polling time: ( 3) minutes. +Conveyance self-test routine +recommended polling time: ( 3) minutes. +SCT capabilities: (0x0035) SCT Status supported. + SCT Feature Control supported. + SCT Data Table supported. + +SMART Attributes Data Structure revision number: 16 +Vendor Specific SMART Attributes with Thresholds: +ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE + 1 Raw_Read_Error_Rate 0x002f 100 100 000 Pre-fail Always - 0 + 5 Reallocate_NAND_Blk_Cnt 0x0033 100 100 000 Pre-fail Always - 0 + 9 Power_On_Hours 0x0032 100 100 000 Old_age Always - 74245 + 12 Power_Cycle_Count 0x0032 100 100 000 Old_age Always - 344 +171 Program_Fail_Count 0x0032 100 100 000 Old_age Always - 0 +172 Erase_Fail_Count 0x0032 100 100 000 Old_age Always - 0 +173 Ave_Block-Erase_Count 0x0032 075 075 000 Old_age Always - 757 +174 Unexpect_Power_Loss_Ct 0x0032 100 100 000 Old_age Always - 334 +180 Unused_Reserve_NAND_Blk 0x0033 000 000 000 Pre-fail Always - 475 +183 SATA_Interfac_Downshift 0x0032 100 100 000 Old_age Always - 0 +184 Error_Correction_Count 0x0032 100 100 000 Old_age Always - 0 +187 Reported_Uncorrect 0x0032 100 100 000 Old_age Always - 0 +194 Temperature_Celsius 0x0022 068 048 000 Old_age Always - 32 (Min/Max 4/52) +196 Reallocated_Event_Count 0x0032 100 100 000 Old_age Always - 16 +197 Current_Pending_Sector 0x0032 100 100 000 Old_age Always - 0 +198 Offline_Uncorrectable 0x0030 100 100 000 Old_age Offline - 0 +199 UDMA_CRC_Error_Count 0x0032 100 100 000 Old_age Always - 0 +202 Percent_Lifetime_Used 0x0031 075 075 000 Pre-fail Offline - 25 +206 Write_Error_Rate 0x000e 100 100 000 Old_age Always - 0 +210 Success_RAIN_Recov_Cnt 0x0032 100 100 000 Old_age Always - 0 +246 Total_Host_Sector_Write 0x0032 100 100 000 Old_age Always - 9607694422 +247 Host_Program_Page_Count 0x0032 100 100 000 Old_age Always - 340097266 +248 Bckgnd_Program_Page_Cnt 0x0032 100 100 000 Old_age Always - 2861592324 + +SMART Error Log Version: 1 +No Errors Logged + +SMART Self-test log structure revision number 1 +Num Test_Description Status Remaining LifeTime(hours) LBA_of_first_error +# 1 Vendor (0xff) Completed without error 00% 4927 - +# 2 Vendor (0xff) Completed without error 00% 4899 - +# 3 Vendor (0xff) Completed without error 00% 4879 - +# 4 Vendor (0xff) Completed without error 00% 4850 - +# 5 Vendor (0xff) Completed without error 00% 4830 - +# 6 Vendor (0xff) Completed without error 00% 4804 - +# 7 Vendor (0xff) Completed without error 00% 4792 - +# 8 Vendor (0xff) Completed without error 00% 4772 - +# 9 Vendor (0xff) Completed without error 00% 4752 - +#10 Vendor (0xff) Completed without error 00% 4731 - +#11 Vendor (0xff) Completed without error 00% 4711 - +#12 Vendor (0xff) Completed without error 00% 4691 - +#13 Vendor (0xff) Completed without error 00% 4671 - +#14 Vendor (0xff) Completed without error 00% 4635 - +#15 Vendor (0xff) Completed without error 00% 4614 - +#16 Vendor (0xff) Completed without error 00% 4594 - +#17 Vendor (0xff) Completed without error 00% 4574 - +#18 Vendor (0xff) Completed without error 00% 4554 - +#19 Vendor (0xff) Completed without error 00% 4534 - +#20 Vendor (0xff) Completed without error 00% 4513 - +#21 Vendor (0xff) Completed without error 00% 4493 - + +SMART Selective self-test log data structure revision number 1 + SPAN MIN_LBA MAX_LBA CURRENT_TEST_STATUS + 1 0 0 Not_testing + 2 0 0 Not_testing + 3 0 0 Not_testing + 4 0 0 Not_testing + 5 0 0 Not_testing +Selective self-test flags (0x0): + After scanning selected spans, do NOT read-scan remainder of disk. +If Selective self-test is pending on power-up, resume after 0 minute delay.""" + + +output_intel_ssd="""=== START OF INFORMATION SECTION === +Model Family: Intel S4510/S4610/S4500/S4600 Series SSDs +Device Model: INTEL SSDSCKKB240G8 +Serial Number: BTYH12260KTW240J +LU WWN Device Id: 5 5cd2e4 154717dbe +Firmware Version: XC311132 +User Capacity: 128,000,000,000 bytes [128 GB] +Sector Sizes: 512 bytes logical, 4096 bytes physical +Rotation Rate: Solid State Device +Form Factor: M.2 +TRIM Command: Available, deterministic, zeroed +Device is: In smartctl database [for details use: -P show] +ATA Version is: ACS-3 T13/2161-D revision 5 +SATA Version is: SATA 3.2, 6.0 Gb/s (current: 6.0 Gb/s) +Local Time is: Mon May 20 19:26:53 2024 UTC +SMART support is: Available - device has SMART capability. +SMART support is: Enabled + +=== START OF READ SMART DATA SECTION === +SMART overall-health self-assessment test result: PASSED + +General SMART Values: +Offline data collection status: (0x02) Offline data collection activity + was completed without error. + Auto Offline Data Collection: Disabled. +Self-test execution status: ( 0) The previous self-test routine completed + without error or no self-test has ever + been run. +Total time to complete Offline +data collection: ( 20) seconds. +Offline data collection +capabilities: (0x79) SMART execute Offline immediate. + No Auto Offline data collection support. + Suspend Offline collection upon new + command. + Offline surface scan supported. + Self-test supported. + Conveyance Self-test supported. + Selective Self-test supported. +SMART capabilities: (0x0003) Saves SMART data before entering + power-saving mode. + Supports SMART auto save timer. +Error logging capability: (0x01) Error logging supported. + General Purpose Logging supported. +Short self-test routine +recommended polling time: ( 1) minutes. +Extended self-test routine +recommended polling time: ( 2) minutes. +Conveyance self-test routine +recommended polling time: ( 2) minutes. +SCT capabilities: (0x003d) SCT Status supported. + SCT Error Recovery Control supported. + SCT Feature Control supported. + SCT Data Table supported. + +SMART Attributes Data Structure revision number: 1 +Vendor Specific SMART Attributes with Thresholds: +ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE + 5 Reallocated_Sector_Ct 0x0032 100 100 000 Old_age Always - 0 + 9 Power_On_Hours 0x0032 100 100 000 Old_age Always - 9201 + 12 Power_Cycle_Count 0x0032 100 100 000 Old_age Always - 638 +170 Available_Reservd_Space 0x0033 100 100 010 Pre-fail Always - 0 +171 Program_Fail_Count 0x0032 100 100 000 Old_age Always - 0 +172 Erase_Fail_Count 0x0032 100 100 000 Old_age Always - 0 +174 Unsafe_Shutdown_Count 0x0032 100 100 000 Old_age Always - 589 +175 Power_Loss_Cap_Test 0x0033 100 100 010 Pre-fail Always - 2207 (638 65535) +183 SATA_Downshift_Count 0x0032 100 100 000 Old_age Always - 0 +184 End-to-End_Error_Count 0x0033 100 100 090 Pre-fail Always - 0 +187 Uncorrectable_Error_Cnt 0x0032 100 100 000 Old_age Always - 0 +190 Drive_Temperature 0x0022 066 060 000 Old_age Always - 34 (Min/Max 23/40) +192 Unsafe_Shutdown_Count 0x0032 100 100 000 Old_age Always - 589 +194 Temperature_Celsius 0x0022 100 100 000 Old_age Always - 34 +197 Pending_Sector_Count 0x0012 100 100 000 Old_age Always - 0 +199 CRC_Error_Count 0x003e 100 100 000 Old_age Always - 0 +225 Host_Writes_32MiB 0x0032 100 100 000 Old_age Always - 44554 +226 Workld_Media_Wear_Indic 0x0032 100 100 000 Old_age Always - 92 +227 Workld_Host_Reads_Perc 0x0032 100 100 000 Old_age Always - 26 +228 Workload_Minutes 0x0032 100 100 000 Old_age Always - 543324 +232 Available_Reservd_Space 0x0033 100 100 010 Pre-fail Always - 0 +233 Media_Wearout_Indicator 0x0032 100 100 000 Old_age Always - 0 +234 Thermal_Throttle_Status 0x0032 100 100 000 Old_age Always - 0/0 +235 Power_Loss_Cap_Test 0x0033 100 100 010 Pre-fail Always - 2207 (638 65535) +241 Host_Writes_32MiB 0x0032 100 100 000 Old_age Always - 44554 +242 Host_Reads_32MiB 0x0032 100 100 000 Old_age Always - 18922 +243 NAND_Writes_32MiB 0x0032 100 100 000 Old_age Always - 90287 + +SMART Error Log Version: 1 +No Errors Logged + +SMART Self-test log structure revision number 1 +Num Test_Description Status Remaining LifeTime(hours) LBA_of_first_error +# 1 Extended offline Completed without error 00% 16 - +# 2 Extended offline Completed without error 00% 10 - +# 3 Extended offline Completed without error 00% 3 - + +SMART Selective self-test log data structure revision number 1 + SPAN MIN_LBA MAX_LBA CURRENT_TEST_STATUS + 1 0 0 Not_testing + 2 0 0 Not_testing + 3 0 0 Not_testing + 4 0 0 Not_testing + 5 0 0 Not_testing +Selective self-test flags (0x0): + After scanning selected spans, do NOT read-scan remainder of disk. +If Selective self-test is pending on power-up, resume after 0 minute delay. +""" + +class TestSsd: + @mock.patch('sonic_platform_base.sonic_storage.ssd.SsdUtil._execute_shell', mock.MagicMock(return_value=output_nvme_ssd)) def test_nvme_ssd(self): # Test parsing nvme ssd info nvme_ssd = SsdUtil('/dev/nvme0n1') @@ -830,7 +1140,7 @@ def test_nvme_ssd(self): assert(nvme_ssd.get_temperature() == 37) assert(nvme_ssd.get_serial() == "A0221030722410000027") - @mock.patch('sonic_platform_base.sonic_ssd.ssd_generic.SsdUtil._execute_shell', mock.MagicMock(return_value=output_lack_info_ssd)) + @mock.patch('sonic_platform_base.sonic_storage.ssd.SsdUtil._execute_shell', mock.MagicMock(return_value=output_lack_info_ssd)) def test_nvme_ssd_with_na_path(self): # Test parsing nvme ssd info which lack of expected sections nvme_ssd = SsdUtil('/dev/nvme0n1') @@ -839,8 +1149,11 @@ def test_nvme_ssd_with_na_path(self): assert(nvme_ssd.get_firmware() == "N/A") assert(nvme_ssd.get_temperature() == "N/A") assert(nvme_ssd.get_serial() == "N/A") + assert(nvme_ssd.get_disk_io_reads() == "N/A") + assert(nvme_ssd.get_disk_io_writes() == "N/A") + assert(nvme_ssd.get_reserved_blocks() == "N/A") - @mock.patch('sonic_platform_base.sonic_ssd.ssd_generic.SsdUtil._execute_shell', mock.MagicMock(return_value=output_ssd)) + @mock.patch('sonic_platform_base.sonic_storage.ssd.SsdUtil._execute_shell', mock.MagicMock(return_value=output_ssd)) def test_ssd(self): # Test parsing a normal ssd info ssd = SsdUtil('/dev/sda') @@ -849,8 +1162,19 @@ def test_ssd(self): assert(ssd.get_firmware() == 'S16425i') assert(ssd.get_temperature() == '30') assert(ssd.get_serial() == 'BCA11712280210689') + assert(ssd.get_disk_io_reads() == '760991') + assert(ssd.get_disk_io_writes() == '178564') + assert(ssd.get_reserved_blocks() == '146') - @mock.patch('sonic_platform_base.sonic_ssd.ssd_generic.SsdUtil._execute_shell', mock.MagicMock(return_value=output_lack_info_ssd)) + @mock.patch('sonic_platform_base.sonic_storage.ssd.SsdUtil._execute_shell', mock.MagicMock(return_value=output_ssd_leading_trailing_spaces)) + def test_ssd_leading_trailing_spaces(self): + # Test parsing a normal ssd info + ssd = SsdUtil('/dev/sda') + + assert(ssd.get_disk_io_writes() == '178564') + assert(ssd.get_disk_io_reads() == '760991') + + @mock.patch('sonic_platform_base.sonic_storage.ssd.SsdUtil._execute_shell', mock.MagicMock(return_value=output_lack_info_ssd)) def test_ssd_with_na_path(self): # Test parsing normal ssd info which lack of expected sections ssd = SsdUtil('/dev/sda') @@ -859,45 +1183,72 @@ def test_ssd_with_na_path(self): assert(ssd.get_firmware() == "N/A") assert(ssd.get_temperature() == "N/A") assert(ssd.get_serial() == "N/A") + assert(ssd.get_disk_io_reads() == "N/A") + assert(ssd.get_disk_io_writes() == "N/A") + assert(ssd.get_reserved_blocks() == "N/A") - @mock.patch('sonic_platform_base.sonic_ssd.ssd_generic.SsdUtil._execute_shell', mock.MagicMock(return_value=output_Innodisk_ssd)) + @mock.patch('sonic_platform_base.sonic_storage.ssd.SsdUtil._execute_shell', mock.MagicMock(return_value=output_Innodisk_ssd)) def test_Innodisk_ssd(self): # Test parsing Innodisk ssd info Innodisk_ssd = SsdUtil('/dev/sda') - assert(Innodisk_ssd.get_health() == '0x000000000000') - assert(Innodisk_ssd.get_model() == 'InnoDisk Corp. - mSATA 3ME') - assert(Innodisk_ssd.get_firmware() == "S140714") - assert(Innodisk_ssd.get_temperature() == 'N/A') - assert(Innodisk_ssd.get_serial() == "20171126AAAA11730156") + assert(Innodisk_ssd.get_health() == '92') + assert(Innodisk_ssd.get_model() == 'InnoDisk Corp. - mSATA 3IE3') + assert(Innodisk_ssd.get_temperature() == '30') + assert(Innodisk_ssd.get_serial() == "BCA11802090990501") Innodisk_ssd.vendor_ssd_info = output_Innodisk_vendor_info Innodisk_ssd.parse_vendor_ssd_info('InnoDisk') - assert(Innodisk_ssd.get_health() == '82.34') - assert(Innodisk_ssd.get_model() == 'InnoDisk Corp. - mSATA 3ME') - assert(Innodisk_ssd.get_firmware() == "S140714") - assert(Innodisk_ssd.get_temperature() == '0') - assert(Innodisk_ssd.get_serial() == "20171126AAAA11730156") + assert(Innodisk_ssd.get_health() == '92') + assert(Innodisk_ssd.get_model() == 'InnoDisk Corp. - mSATA 3IE3') + assert(Innodisk_ssd.get_firmware() == "S16425cG") + assert(Innodisk_ssd.get_temperature() == '30') + assert(Innodisk_ssd.get_serial() == "BCA11802090990501") + + assert(Innodisk_ssd.get_disk_io_reads() == '73954') + assert(Innodisk_ssd.get_disk_io_writes() == '150370') + assert(Innodisk_ssd.get_reserved_blocks() == '59') + - @mock.patch('sonic_platform_base.sonic_ssd.ssd_generic.SsdUtil._execute_shell', mock.MagicMock(return_value=output_Innodisk_missing_names_ssd)) + @mock.patch('sonic_platform_base.sonic_storage.ssd.SsdUtil._execute_shell', mock.MagicMock(return_value=output_Innodisk_vendor_info)) + @mock.patch('sonic_platform_base.sonic_storage.ssd.SsdUtil.model', "InnoDisk") + @mock.patch('sonic_platform_base.sonic_storage.ssd.SsdUtil.disk_io_reads', "N/A") + @mock.patch('sonic_platform_base.sonic_storage.ssd.SsdUtil.disk_io_writes', "N/A") + @mock.patch('sonic_platform_base.sonic_storage.ssd.SsdUtil.reserved_blocks', "N/A") + def test_Innodisk_no_info_ssd(self): + + # Test parsing Innodisk ssd info + with mock.patch.object(SsdUtil, 'parse_generic_ssd_info', new=mock.MagicMock(return_value=None)): + Innodisk_ssd = SsdUtil('/dev/sda') + assert(Innodisk_ssd.get_health() == '92.725') + assert(Innodisk_ssd.get_model() == 'InnoDisk') + assert(Innodisk_ssd.get_firmware() == "S16425cG") + assert(Innodisk_ssd.get_temperature() == '30') + assert(Innodisk_ssd.get_serial() == "BCA11802090990501") + assert(Innodisk_ssd.get_disk_io_reads() == '73954') + assert(Innodisk_ssd.get_disk_io_writes() == '150370') + assert(Innodisk_ssd.get_reserved_blocks() == '0') + + + @mock.patch('sonic_platform_base.sonic_storage.ssd.SsdUtil._execute_shell', mock.MagicMock(return_value=output_Innodisk_missing_names_ssd)) def test_Innodisk_missing_names_ssd(self): # Test parsing Innodisk ssd info Innodisk_ssd = SsdUtil('/dev/sda') Innodisk_ssd.vendor_ssd_info = '' Innodisk_ssd.parse_vendor_ssd_info('InnoDisk') assert(Innodisk_ssd.get_health() == '94') - assert(Innodisk_ssd.get_temperature() == '39') + assert(Innodisk_ssd.get_temperature() == 'N/A') - @mock.patch('sonic_platform_base.sonic_ssd.ssd_generic.SsdUtil._execute_shell', mock.MagicMock(return_value=output_Innodisk_missing_names_ssd)) + @mock.patch('sonic_platform_base.sonic_storage.ssd.SsdUtil._execute_shell', mock.MagicMock(return_value=output_Innodisk_missing_names_ssd)) def test_Innodisk_missing_names_ssd_2(self): # Test parsing Innodisk ssd info Innodisk_ssd = SsdUtil('/dev/sda') Innodisk_ssd.vendor_ssd_info = 'ERROR message from cmd' Innodisk_ssd.parse_vendor_ssd_info('InnoDisk') assert(Innodisk_ssd.get_health() == '94') - assert(Innodisk_ssd.get_temperature() == '39') + assert(Innodisk_ssd.get_temperature() == 'N/A') - @mock.patch('sonic_platform_base.sonic_ssd.ssd_generic.SsdUtil._execute_shell') + @mock.patch('sonic_platform_base.sonic_storage.ssd.SsdUtil._execute_shell') def test_virtium_ssd(self, mock_exec): mock_exec.side_effect = [output_virtium_generic_vsfdm8xc240g_v11_t, output_virtium_vendor_vsfdm8xc240g_v11_t] virtium_ssd = SsdUtil('/dev/sda') @@ -906,6 +1257,9 @@ def test_virtium_ssd(self, mock_exec): assert virtium_ssd.get_firmware() == "0913-000" assert virtium_ssd.get_temperature() == '34' assert virtium_ssd.get_serial() == "60237-0037" + assert virtium_ssd.get_disk_io_reads() == "45606297" + assert virtium_ssd.get_disk_io_writes() == "302116658" + assert virtium_ssd.get_reserved_blocks() == "0" mock_exec.side_effect = [output_virtium_generic, output_virtium_vendor] virtium_ssd = SsdUtil('/dev/sda') @@ -914,6 +1268,9 @@ def test_virtium_ssd(self, mock_exec): assert virtium_ssd.get_firmware() == "0202-001" assert virtium_ssd.get_temperature() == '17' assert virtium_ssd.get_serial() == "52586-0705" + assert virtium_ssd.get_disk_io_reads() == "1482095" + assert virtium_ssd.get_disk_io_writes() == "629509" + assert virtium_ssd.get_reserved_blocks() == "100" mock_exec.side_effect = [output_virtium_generic, output_virtium_no_remain_life] virtium_ssd = SsdUtil('/dev/sda') @@ -927,7 +1284,13 @@ def test_virtium_ssd(self, mock_exec): virtium_ssd = SsdUtil('/dev/sda') assert virtium_ssd.get_health() == "N/A" - @mock.patch('sonic_platform_base.sonic_ssd.ssd_generic.SsdUtil._execute_shell') + mock_exec.side_effect = [output_virtium_generic_trick_number, output_virtium_vendor_trick_number] + virtium_ssd = SsdUtil('/dev/sda') + assert virtium_ssd.get_disk_io_writes() == "18782480803" + assert virtium_ssd.get_temperature() == "42" + + + @mock.patch('sonic_platform_base.sonic_storage.ssd.SsdUtil._execute_shell') def test_swissbit_ssd(self, mock_exec): mock_exec.return_value = output_swissbit_vendor swissbit_ssd = SsdUtil('/dev/sda') @@ -937,7 +1300,7 @@ def test_swissbit_ssd(self, mock_exec): assert swissbit_ssd.get_temperature() == '25' assert swissbit_ssd.get_serial() == "00006022750795000010" - @mock.patch('sonic_platform_base.sonic_ssd.ssd_generic.SsdUtil._execute_shell') + @mock.patch('sonic_platform_base.sonic_storage.ssd.SsdUtil._execute_shell') def test_transcend_ssd(self, mock_exec): mock_exec.return_value = output_transcend_vendor transcend_ssd = SsdUtil('/dev/sda') @@ -948,3 +1311,30 @@ def test_transcend_ssd(self, mock_exec): assert transcend_ssd.get_firmware() == "O0918B" assert transcend_ssd.get_temperature() == '40' assert transcend_ssd.get_serial() == "F318410080" + + @mock.patch('sonic_platform_base.sonic_storage.ssd.SsdUtil._execute_shell', mock.MagicMock(return_value=output_micron_ssd)) + def test_micron_ssd(self): + # Test parsing a normal ssd info + micron_ssd = SsdUtil('/dev/sda') + assert(micron_ssd.get_health() == '75') + assert(micron_ssd.get_model() == 'Micron_M550_MTFDDAT064MAY') + assert(micron_ssd.get_firmware() == 'MU01') + assert(micron_ssd.get_temperature() == '32') + assert(micron_ssd.get_serial() == 'MSA1827061P') + assert(micron_ssd.get_disk_io_reads() == 'N/A') + assert(micron_ssd.get_disk_io_writes() == '9607694422') + assert(micron_ssd.get_reserved_blocks() == '475') + + + @mock.patch('sonic_platform_base.sonic_storage.ssd.SsdUtil._execute_shell', mock.MagicMock(return_value=output_intel_ssd)) + def test_intel_ssd(self): + # Test parsing a normal ssd info + intel_ssd = SsdUtil('/dev/sda') + assert(intel_ssd.get_health() == '100.0') + assert(intel_ssd.get_model() == 'INTEL SSDSCKKB240G8') + assert(intel_ssd.get_firmware() == 'XC311132') + assert(intel_ssd.get_temperature() == '34') + assert(intel_ssd.get_serial() == 'BTYH12260KTW240J') + assert(intel_ssd.get_disk_io_reads() == '18922') + assert(intel_ssd.get_disk_io_writes() == '44554') + assert(intel_ssd.get_reserved_blocks() == '0') diff --git a/tests/test_storage_common.py b/tests/test_storage_common.py new file mode 100644 index 000000000..445f20cc8 --- /dev/null +++ b/tests/test_storage_common.py @@ -0,0 +1,50 @@ +import os +import sys +import types +from mock import MagicMock, patch +#import psutil + +from sonic_platform_base.sonic_storage.storage_common import StorageCommon + +class DiskIOCounters(): + def __init__(self, perdisk=True, nowrap=True): + + self.read_count = 18038 + self.write_count = 95836 + +class TestStorageCommon: + + @patch('psutil.disk_io_counters', MagicMock(return_value={'sda': DiskIOCounters()})) + def test_get_reads_writes(self): + + common_object = StorageCommon('/dev/sda') + + reads = common_object.get_fs_io_reads() + writes = common_object.get_fs_io_writes() + + assert (reads == 18038) + assert (writes == 95836) + + + def test_init(self): + common_object = StorageCommon('/dev/sda') + + assert common_object.storage_disk == 'sda' + + + def test_get_reads_writes_bad_disk(self): + common_object = StorageCommon('/dev/glorp') + + with patch('psutil.disk_io_counters', MagicMock()) as mock_psutil_output: + + class DiskIOCounters(): + def __init__(self, perdisk=True, nowrap=True): + pass + + mock_psutil_output.return_value = {'glorp': DiskIOCounters()} + reads = common_object.get_fs_io_reads() + writes = common_object.get_fs_io_writes() + + assert (reads == 0) + assert (writes == 0) + diff --git a/tests/test_storage_devices.py b/tests/test_storage_devices.py new file mode 100644 index 000000000..38da27228 --- /dev/null +++ b/tests/test_storage_devices.py @@ -0,0 +1,61 @@ +import sys +from mock import patch, MagicMock +import pytest + +from sonic_platform_base.sonic_storage.storage_devices import StorageDevices + +mocked_basepath = ['loop1', 'mmcblk0', 'loop6', 'mmcblk0boot0', 'loop4', 'loop2', 'loop0', 'loop7', 'mmcblk0boot1', 'sda', 'loop5', 'loop3'] +mocked_devices = { 'mmcblk0' : None, 'sda' : None} +mock_realpath = "/sys/devices/pci0000:00/0000:00:18.0/ata5/host4/target4:0:0/4:0:0:0" + + +class TestStorageDevices: + + @patch('os.listdir', MagicMock(return_value=['sdi'])) + @patch('os.path.realpath', MagicMock(return_value=mock_realpath)) + @patch('sonic_platform_base.sonic_storage.ssd.SsdUtil') + def test_get_storage_devices_sda_obj(self, mock_ssdutil): + + mock_ssdutil = MagicMock() + mock_ssdutil.return_value = MagicMock() + + storage = StorageDevices() + + assert (list(storage.devices.keys()) == ['sdi']) + + + @patch('os.listdir', MagicMock(return_value=['sdj'])) + @patch('os.path.realpath', MagicMock(return_value="usb")) + def test_get_storage_devices_usb_obj(self): + + storage = StorageDevices() + + assert (list(storage.devices.keys()) == ['sdj']) + assert (storage.devices['sdj'] == None) + + + @patch('os.listdir', MagicMock(return_value=['mmcblk0'])) + @patch('sonic_platform_base.sonic_storage.emmc.EmmcUtil') + def test_get_storage_devices_emmc_obj(self, mock_emmcutil): + + mock_emmcutil = MagicMock() + mock_emmcutil.return_value = MagicMock() + + storage = StorageDevices() + + assert (list(storage.devices.keys()) == ['mmcblk0']) + assert storage.devices['mmcblk0'] != None + + + + @patch('os.listdir', MagicMock(return_value=mocked_basepath)) + def test_get_storage_devices_none(self): + + def mock_factory(self, dummy_key): + return None + + with patch.object(StorageDevices, '_storage_device_object_factory', new=mock_factory): + storage = StorageDevices() + assert storage.devices == mocked_devices + +