diff --git a/setup.py b/setup.py index d5cf5cf5d1b5..22e8a2dd8eb7 100644 --- a/setup.py +++ b/setup.py @@ -16,6 +16,7 @@ 'sonic_platform_base', 'sonic_platform_base.sonic_eeprom', 'sonic_platform_base.sonic_sfp', + 'sonic_platform_base.sonic_ssd', 'sonic_psu', 'sonic_sfp', ], diff --git a/sonic_platform_base/sonic_ssd/__init__.py b/sonic_platform_base/sonic_ssd/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sonic_platform_base/sonic_ssd/ssd_base.py b/sonic_platform_base/sonic_ssd/ssd_base.py new file mode 100644 index 000000000000..2c80d621fb6e --- /dev/null +++ b/sonic_platform_base/sonic_ssd/ssd_base.py @@ -0,0 +1,75 @@ +# +# ssd_base.py +# +# Base class for implementing common SSD health features +# + + +class SsdBase(object): + """ + Base class for interfacing with a SSD + """ + def __init__(self, diskdev): + """ + Constructor + + Args: + diskdev: Linux device name to get parameters for + """ + pass + + def get_health(self): + """ + Retrieves current disk health in percentages + + Returns: + A float number of current ssd health + e.g. 83.5 + """ + raise NotImplementedError + + def get_temperature(self): + """ + Retrieves current disk temperature in Celsius + + Returns: + A float number of current temperature in Celsius + e.g. 40.1 + """ + raise NotImplementedError + + def get_model(self): + """ + Retrieves model for the given disk device + + Returns: + A string holding disk model as provided by the manufacturer + """ + raise NotImplementedError + + def get_firmware(self): + """ + Retrieves firmware version for the given disk device + + Returns: + A string holding disk firmware version as provided by the manufacturer + """ + raise NotImplementedError + + def get_serial(self): + """ + Retrieves serial number for the given disk device + + Returns: + A string holding disk serial number as provided by the manufacturer + """ + raise NotImplementedError + + def get_vendor_output(self): + """ + Retrieves vendor specific data for the given disk device + + Returns: + A string holding some vendor specific disk information + """ + raise NotImplementedError diff --git a/sonic_platform_base/sonic_ssd/ssd_generic.py b/sonic_platform_base/sonic_ssd/ssd_generic.py new file mode 100755 index 000000000000..1390217ca571 --- /dev/null +++ b/sonic_platform_base/sonic_ssd/ssd_generic.py @@ -0,0 +1,157 @@ +# +# ssd_generic.py +# +# Generic implementation of the SSD health API +# SSD models supported: +# - InnoDisk +# - StorFly +# - Virtium + +try: + import exceptions # Python 2 +except ImportError: + import builtins as exceptions # Python 3 +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 {}" + +NOT_AVAILABLE = "N/A" + + +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 } + } + + self.dev = diskdev + # Generic part + self.fetch_generic_ssd_info(diskdev) + self.parse_generic_ssd_info() + + # Known vendor part + if self.model: + model_short = self.model.split()[0] + if self.vendor_ssd_utility.has_key(model_short): + self.fetch_vendor_ssd_info(diskdev, model_short) + self.parse_vendor_ssd_info(model_short) + 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(), 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 fetch_generic_ssd_info(self, diskdev): + self.ssd_info = self._execute_shell(self.vendor_ssd_utility["Generic"]["utility"].format(diskdev)) + + def parse_generic_ssd_info(self): + self.model = self._parse_re('Device Model:\s*(.+?)\n', self.ssd_info) + 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): + self.health = self._parse_re('Health:\s*(.+?)%', self.vendor_ssd_info) + self.temperature = self._parse_re('Temperature\s*\[\s*(.+?)\]', self.vendor_ssd_info) + + def parse_virtium_info(self): + 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) + try: + self.health = 100 - (float(avg_erase_count) * 100 / float(nand_endurance)) + except ValueError: + pass + + 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