From dd83c8f9ee1773119593e813594ccdb5b24a3a13 Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Thu, 12 Oct 2023 04:24:59 +0000 Subject: [PATCH 01/31] Initial commit of ssdmond --- sonic-ssdmond/pytest.ini | 2 + sonic-ssdmond/scripts/ssdmond | 227 ++++++++++++++++++ sonic-ssdmond/setup.cfg | 2 + sonic-ssdmond/setup.py | 43 ++++ sonic-ssdmond/tests/__init__.py | 0 sonic-ssdmond/tests/mock_platform.py | 15 ++ .../mocked_libs/sonic_platform/__init__.py | 6 + .../tests/mocked_libs/sonic_platform/ssd.py | 13 + .../sonic_platform_base/__init__.py | 1 + .../sonic_platform_base/sonic_ssd/__init__.py | 0 .../sonic_platform_base/sonic_ssd/ssd_base.py | 122 ++++++++++ .../sonic_ssd/ssd_generic.py | 127 ++++++++++ .../tests/mocked_libs/swsscommon/__init__.py | 5 + .../mocked_libs/swsscommon/swsscommon.py | 66 +++++ sonic-ssdmond/tests/test_DaemonSsd.py | 40 +++ sonic-ssdmond/tests/test_ssd.py | 33 +++ 16 files changed, 702 insertions(+) create mode 100644 sonic-ssdmond/pytest.ini create mode 100644 sonic-ssdmond/scripts/ssdmond create mode 100644 sonic-ssdmond/setup.cfg create mode 100644 sonic-ssdmond/setup.py create mode 100644 sonic-ssdmond/tests/__init__.py create mode 100644 sonic-ssdmond/tests/mock_platform.py create mode 100644 sonic-ssdmond/tests/mocked_libs/sonic_platform/__init__.py create mode 100644 sonic-ssdmond/tests/mocked_libs/sonic_platform/ssd.py create mode 100644 sonic-ssdmond/tests/mocked_libs/sonic_platform_base/__init__.py create mode 100644 sonic-ssdmond/tests/mocked_libs/sonic_platform_base/sonic_ssd/__init__.py create mode 100644 sonic-ssdmond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py create mode 100644 sonic-ssdmond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_generic.py create mode 100644 sonic-ssdmond/tests/mocked_libs/swsscommon/__init__.py create mode 100644 sonic-ssdmond/tests/mocked_libs/swsscommon/swsscommon.py create mode 100644 sonic-ssdmond/tests/test_DaemonSsd.py create mode 100644 sonic-ssdmond/tests/test_ssd.py diff --git a/sonic-ssdmond/pytest.ini b/sonic-ssdmond/pytest.ini new file mode 100644 index 000000000..d90ee9ed9 --- /dev/null +++ b/sonic-ssdmond/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = --cov=scripts --cov-report html --cov-report term --cov-report xml --junitxml=test-results.xml -vv diff --git a/sonic-ssdmond/scripts/ssdmond b/sonic-ssdmond/scripts/ssdmond new file mode 100644 index 000000000..a43fbe2e3 --- /dev/null +++ b/sonic-ssdmond/scripts/ssdmond @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 + +""" + ssdmond + SSD device monitoring daemon for SONiC +""" + +import os +import signal +import sys +import threading +import subprocess +import shutil + +from sonic_py_common import daemon_base, device_info, logger +from swsscommon import swsscommon + +# +# Constants ==================================================================== +# + +# TODO: Once we no longer support Python 2, we can eliminate this and get the +# name using the 'name' field (e.g., `signal.SIGINT.name`) starting with Python 3.5 +SIGNALS_TO_NAMES_DICT = dict((getattr(signal, n), n) + for n in dir(signal) if n.startswith('SIG') and '_' not in n) + +SYSLOG_IDENTIFIER = "ssdmond" + +SSD_DEVICE_TABLE_NAME = "SSD_INFO" + +SSDMOND_MAIN_THREAD_SLEEP_SECS = 21600 #six hours + +SSDUTIL_LOAD_ERROR = 127 + +platform_ssdutil = None + +log = logger.Logger(SYSLOG_IDENTIFIER) + +exit_code = 0 + +# wrapper functions to call the platform api +def load_platform_ssdutil(diskdev): + """ + Loads platform specific or generic Ssdutil module from source + Raises an ImportError exception if none of above available + + Returns: + Instance of the class with SSD API implementation (vendor or generic) + """ + + # try to load platform specific module + try: + platform_path, _ = device_info.get_paths_to_platform_and_hwsku_dirs() + platform_plugins_path = os.path.join(platform_path, "plugins") + sys.path.append(os.path.abspath(platform_plugins_path)) + from ssd_util import SsdUtil + except ImportError as e: + log.log_warning("Platform specific SsdUtil module not found. Falling down to the generic implementation") + try: + from sonic_platform_base.sonic_ssd.ssd_generic import SsdUtil + except ImportError as e: + log.log_error("Failed to import default SsdUtil. Error: {}".format(str(e)), True) + raise e + + return SsdUtil(diskdev) + +def exec_cmd(cmd, verbose=False): + p = subprocess.Popen(cmd, shell=True, executable='/bin/bash', stdout=subprocess.PIPE, stderr=subprocess.PIPE) + outs, errs = p.communicate() + msg = '' + if outs: msg = outs.decode('utf8') + if outs and verbose: log.log_info('exec_cmd stdout = '+msg) + if errs: + log.log_error('exec_cmd stderr = '+errs) + msg = msg + errs.decode('utf8') + + return (p.returncode, msg) + +def get_ssds_on_device(): + ssd_device_list = [] + cmd = "lsblk -d -o name,type" + + rc, out = exec_cmd(cmd) + + if rc != 0: + log.log_error("FATAL: Unable to query SSDs on the device. Error: {}".format(out.strip())) + sys.exit(SSDUTIL_LOAD_ERROR) + + ssd_device_list += (line.split()[0].strip() for line in out.splitlines() if 'disk' in line) + + log.log_info("{}".format(ssd_device_list)) + return ssd_device_list + +def ssd_exists(ssd_device): + return os.path.exists("/dev/" + str(ssd_device)) + + +# +# Daemon ======================================================================= +# + + +class DaemonSsd(daemon_base.DaemonBase): + def __init__(self, log_identifier): + super(DaemonSsd, self).__init__(log_identifier) + + self.timeout = SSDMOND_MAIN_THREAD_SLEEP_SECS + self.stop_event = threading.Event() + self.state_db = None + self.device_table = None + self.ssd_device_list = get_ssds_on_device() + + self.static_fields = ["device_model", "serial", "firmware"] + self.read_static_fields = False + self.update_static_fields_statedb = False + + global platform_ssdutil + + platform_ssdutil = dict((ssd, None) for ssd in self.ssd_device_list) + + for ssd in self.ssd_device_list: + diskdev = ("/dev/" + str(ssd)) + platform_ssdutil[ssd] = load_platform_ssdutil(diskdev) + if platform_ssdutil[ssd] is None: + sys.exit(SSDUTIL_LOAD_ERROR) + + + # Connect to STATE_DB and create pcie device table + self.state_db = daemon_base.db_connect("STATE_DB") + self.device_table = swsscommon.Table(self.state_db, SSD_DEVICE_TABLE_NAME) + + def __del__(self): + if self.device_table: + table_keys = self.device_table.getKeys() + for tk in table_keys: + self.device_table._del(tk) + + + # Update the Static Fields to State DB + def update_static_fields_status_db(self, ssd_device, static_kvp_dict): + + for field in self.static_fields: + fvp = swsscommon.FieldValuePairs([(field, static_kvp_dict[field])]) + self.device_table.set(ssd_device, fvp) + + + # Check the SSD device + def get_static_fields(self): + + try: + # Get relevant information about each SSD on the device + for ssd_device in self.ssd_device_list: + + static_kvp_dict = dict((field, "") for field in self.static_fields) + + # Verify that the SSD exists in the expected location in the filesystem hierarchy + if not ssd_exists(ssd_device): + self.log_warning("{} not found. Moving on.".format(ssd_device)) + continue + + static_kvp_dict["device_model"] = platform_ssdutil[ssd_device].get_model() + static_kvp_dict["serial"] = platform_ssdutil[ssd_device].get_serial() + static_kvp_dict["firmware"] = platform_ssdutil[ssd_device].get_firmware() + + + # update SSD Device Status to DB + self.update_static_fields_status_db(ssd_device, static_kvp_dict) + + read_static_fields = True + update_static_fields_statedb = True + + except Exception as ex: + self.log_warning("get_static_fields() failed with: {}".format(str(ex))) + + # Get Dynamic attributes and update the State DB + def get_dynamic_attributes(self): + self.log_warning("nothing yet") + + # Override signal handler from DaemonBase + def signal_handler(self, sig, frame): + FATAL_SIGNALS = [signal.SIGINT, signal.SIGTERM] + NONFATAL_SIGNALS = [signal.SIGHUP] + + global exit_code + + if sig in FATAL_SIGNALS: + self.log_info("Caught signal '{}' - exiting...".format(SIGNALS_TO_NAMES_DICT[sig])) + exit_code = 128 + sig # Make sure we exit with a non-zero code so that supervisor will try to restart us + self.stop_event.set() + elif sig in NONFATAL_SIGNALS: + self.log_info("Caught signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) + else: + self.log_warning("Caught unhandled signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) + + # Main daemon logic + def run(self): + if self.stop_event.wait(self.timeout): + # We received a fatal signal + return False + + # Read and update Static Fields to the StateDB once + if not self.read_static_fields and not self.update_static_fields_statedb: + self.get_static_fields() + + # Repeatedly read and update Dynamic Fields to the StateDB + self.get_dynamic_attributes() + + return True +# +# Main ========================================================================= +# + + +def main(): + ssdmon = DaemonSsd(SYSLOG_IDENTIFIER) + + ssdmon.log_info("Starting up...") + + while ssdmon.run(): + pass + + ssdmon.log_info("Shutting down...") + + return exit_code + +if __name__ == '__main__': + sys.exit(main()) diff --git a/sonic-ssdmond/setup.cfg b/sonic-ssdmond/setup.cfg new file mode 100644 index 000000000..b7e478982 --- /dev/null +++ b/sonic-ssdmond/setup.cfg @@ -0,0 +1,2 @@ +[aliases] +test=pytest diff --git a/sonic-ssdmond/setup.py b/sonic-ssdmond/setup.py new file mode 100644 index 000000000..b9ea98450 --- /dev/null +++ b/sonic-ssdmond/setup.py @@ -0,0 +1,43 @@ +from setuptools import setup + +setup( + name='sonic-ssdmond', + version='1.0', + description='SSD status daemon for SONiC', + license='Apache 2.0', + author='SONiC Team', + author_email='linuxnetdev@microsoft.com', + url='https://github.com/sonic-net/sonic-platform-daemons', + maintainer='Ashwin Srinivasan', + maintainer_email='assrinivasan@microsoft.com', + scripts=[ + 'scripts/ssdmond', + ], + setup_requires=[ + 'pytest-runner', + 'wheel' + ], + install_requires=[ + 'enum34; python_version < "3.4"', + 'sonic-py-common', + ], + tests_require=[ + 'mock>=2.0.0; python_version < "3.3"', + 'pytest', + 'pytest-cov', + ], + classifiers=[ + 'Development Status :: 4 - Beta', + 'Environment :: No Input/Output (Daemon)', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'Intended Audience :: System Administrators', + 'License :: OSI Approved :: Apache Software License', + 'Natural Language :: English', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python :: 2.7', + 'Topic :: System :: Hardware', + ], + keywords='sonic SONiC ssd Ssd SSD ssdmon', + test_suite='setup.get_test_suite' +) diff --git a/sonic-ssdmond/tests/__init__.py b/sonic-ssdmond/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sonic-ssdmond/tests/mock_platform.py b/sonic-ssdmond/tests/mock_platform.py new file mode 100644 index 000000000..2dc460f53 --- /dev/null +++ b/sonic-ssdmond/tests/mock_platform.py @@ -0,0 +1,15 @@ + +""" + Mock implementation of sonic_platform package for unit testing +""" + +# TODO: Clean this up once we no longer need to support Python 2 +import sys +if sys.version_info.major == 3: + from unittest import mock +else: + import mock + +class MockSsdUtil(): + def __init__(self): + super(MockSsdUtil, self).__init__() \ No newline at end of file diff --git a/sonic-ssdmond/tests/mocked_libs/sonic_platform/__init__.py b/sonic-ssdmond/tests/mocked_libs/sonic_platform/__init__.py new file mode 100644 index 000000000..136bb0dcc --- /dev/null +++ b/sonic-ssdmond/tests/mocked_libs/sonic_platform/__init__.py @@ -0,0 +1,6 @@ +""" + Mock implementation of sonic_platform package for unit testing +""" + +from . import ssd + diff --git a/sonic-ssdmond/tests/mocked_libs/sonic_platform/ssd.py b/sonic-ssdmond/tests/mocked_libs/sonic_platform/ssd.py new file mode 100644 index 000000000..572f190ef --- /dev/null +++ b/sonic-ssdmond/tests/mocked_libs/sonic_platform/ssd.py @@ -0,0 +1,13 @@ +""" + Mock implementation of sonic_platform package for unit testing +""" + +from sonic_platform_base.ssd_base import SsdBase + + +class Ssd(SsdBase): + def __init__(self): + self.platform_ssdutil = "/tmp/Ssd" + + def __str__(self): + return self.platform_ssdutil diff --git a/sonic-ssdmond/tests/mocked_libs/sonic_platform_base/__init__.py b/sonic-ssdmond/tests/mocked_libs/sonic_platform_base/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/sonic-ssdmond/tests/mocked_libs/sonic_platform_base/__init__.py @@ -0,0 +1 @@ + diff --git a/sonic-ssdmond/tests/mocked_libs/sonic_platform_base/sonic_ssd/__init__.py b/sonic-ssdmond/tests/mocked_libs/sonic_platform_base/sonic_ssd/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sonic-ssdmond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py b/sonic-ssdmond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py new file mode 100644 index 000000000..ad6607b6f --- /dev/null +++ b/sonic-ssdmond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py @@ -0,0 +1,122 @@ +# +# pcie_base.py +# +# Abstract base class for implementing platform-specific +# PCIE functionality for SONiC +# + +try: + import abc +except ImportError as e: + raise ImportError(str(e) + " - required module not found") + +# +# 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 + + @abc.abstractmethod + def get_health(self): + """ + Retrieves current disk health in percentages + + Returns: + A float number of current ssd health + e.g. 83.5 + """ + return 91.6 + + @abc.abstractmethod + def get_temperature(self): + """ + Retrieves current disk temperature in Celsius + + Returns: + A float number of current temperature in Celsius + e.g. 40.1 + """ + return 32.3 + + @abc.abstractmethod + def get_model(self): + """ + Retrieves model for the given disk device + + Returns: + A string holding disk model as provided by the manufacturer + """ + return '' + + @abc.abstractmethod + 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 '' + + @abc.abstractmethod + 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 '' + + @abc.abstractmethod + def get_vendor_output(self): + """ + Retrieves vendor specific data for the given disk device + + Returns: + A string holding some vendor specific disk information + """ + return '' + + def get_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 20000 + + @abc.abstractmethod + def get_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 20005 + + @abc.abstractmethod + def get_reserves_blocks(self): + """ + Retrieves the total number of reserved blocks in an SSD + + Returns: + An integer value of the total number of reserved blocks + """ + return 3746218 \ No newline at end of file diff --git a/sonic-ssdmond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_generic.py b/sonic-ssdmond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_generic.py new file mode 100644 index 000000000..9cf829248 --- /dev/null +++ b/sonic-ssdmond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_generic.py @@ -0,0 +1,127 @@ +# +# 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 {}" + +NOT_AVAILABLE = "N/A" + +# Set Vendor Specific IDs +INNODISK_HEALTH_ID = 169 +INNODISK_TEMPERATURE_ID = 194 +SWISSBIT_HEALTH_ID = 248 +SWISSBIT_TEMPERATURE_ID = 194 + +class SsdUtil(SsdBase): + """ + Generic implementation of the SSD health API + """ + + def __init__(self, diskdev): + model = 'InnoDisk Corp. - mSATA 3IE3' + serial = 'BCA11712190600251' + firmware = 'S16425cG' + temperature = 32.3 + health = 91.6 + ssd_info = NOT_AVAILABLE + vendor_ssd_info = NOT_AVAILABLE + io_reads = 20000 + io_writes = 20005 + reserved_blocks = 3746218 + + 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 get_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.io_writes + + def get_io_reads(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.io_reads + + def get_reserves_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 diff --git a/sonic-ssdmond/tests/mocked_libs/swsscommon/__init__.py b/sonic-ssdmond/tests/mocked_libs/swsscommon/__init__.py new file mode 100644 index 000000000..012af621e --- /dev/null +++ b/sonic-ssdmond/tests/mocked_libs/swsscommon/__init__.py @@ -0,0 +1,5 @@ +''' + Mock implementation of swsscommon package for unit testing +''' + +from . import swsscommon diff --git a/sonic-ssdmond/tests/mocked_libs/swsscommon/swsscommon.py b/sonic-ssdmond/tests/mocked_libs/swsscommon/swsscommon.py new file mode 100644 index 000000000..ddb3cd686 --- /dev/null +++ b/sonic-ssdmond/tests/mocked_libs/swsscommon/swsscommon.py @@ -0,0 +1,66 @@ +''' + Mock implementation of swsscommon package for unit testing +''' + +STATE_DB = '' + + +class Table: + def __init__(self, db, table_name): + self.table_name = table_name + self.mock_dict = {} + + def _del(self, key): + del self.mock_dict[key] + pass + + def set(self, key, fvs): + self.mock_dict[key] = fvs.fv_dict + pass + + def get(self, key): + if key in self.mock_dict: + return self.mock_dict[key] + return None + + def get_size(self): + return (len(self.mock_dict)) + + def getKeys(self): + return list(self.mock_dict.keys()) + + +class FieldValuePairs: + fv_dict = {} + + def __init__(self, tuple_list): + if isinstance(tuple_list, list) and isinstance(tuple_list[0], tuple): + self.fv_dict = dict(tuple_list) + + def __setitem__(self, key, kv_tuple): + self.fv_dict[kv_tuple[0]] = kv_tuple[1] + + def __getitem__(self, key): + return self.fv_dict[key] + + def __eq__(self, other): + if not isinstance(other, FieldValuePairs): + # don't attempt to compare against unrelated types + return NotImplemented + + return self.fv_dict == other.fv_dict + + def __repr__(self): + return repr(self.fv_dict) + + def __str__(self): + return repr(self.fv_dict) + +class ConfigDBConnector: + pass + +class SonicDBConfig: + pass + +class SonicV2Connector: + pass diff --git a/sonic-ssdmond/tests/test_DaemonSsd.py b/sonic-ssdmond/tests/test_DaemonSsd.py new file mode 100644 index 000000000..6b18dc70e --- /dev/null +++ b/sonic-ssdmond/tests/test_DaemonSsd.py @@ -0,0 +1,40 @@ +import datetime +import os +import sys +from imp import load_source # Replace with importlib once we no longer need to support Python 2 + +import pytest + +# TODO: Clean this up once we no longer need to support Python 2 +if sys.version_info.major == 3: + from unittest import mock +else: + import mock + +from .mock_platform import MockSsdUtil + +SYSLOG_IDENTIFIER = 'ssd_daemon_test' +NOT_AVAILABLE = 'N/A' + + +tests_path = os.path.dirname(os.path.abspath(__file__)) + +# Add mocked_libs path so that the file under test can load mocked modules from there +mocked_libs_path = os.path.join(tests_path, "mocked_libs") +sys.path.insert(0, mocked_libs_path) + +from sonic_py_common import daemon_base +daemon_base.db_connect = mock.MagicMock() + +# Add path to the file under test so that we can load it +modules_path = os.path.dirname(tests_path) +scripts_path = os.path.join(modules_path, "scripts") +sys.path.insert(0, modules_path) +load_source('ssdmond', os.path.join(scripts_path, 'ssdmond')) +import ssdmond + + +class TestDaemonPcied(object): + """ + Test cases to cover functionality in DaemonPcied class + """ diff --git a/sonic-ssdmond/tests/test_ssd.py b/sonic-ssdmond/tests/test_ssd.py new file mode 100644 index 000000000..18141882d --- /dev/null +++ b/sonic-ssdmond/tests/test_ssd.py @@ -0,0 +1,33 @@ +import os +import sys +from imp import load_source # Replace with importlib once we no longer need to support Python 2 + +import pytest + +# TODO: Clean this up once we no longer need to support Python 2 +if sys.version_info >= (3, 3): + from unittest.mock import MagicMock, patch, mock_open +else: + from mock import MagicMock, patch, mock_open + +from .mock_platform import MockSsdUtil + +tests_path = os.path.dirname(os.path.abspath(__file__)) + +# Add mocked_libs path so that the file under test can load mocked modules from there +mocked_libs_path = os.path.join(tests_path, "mocked_libs") +sys.path.insert(0, mocked_libs_path) +from sonic_py_common import daemon_base, device_info + +# Add path to the file under test so that we can load it +modules_path = os.path.dirname(tests_path) +scripts_path = os.path.join(modules_path, "scripts") +sys.path.insert(0, modules_path) +load_source('ssdmond', os.path.join(scripts_path, 'ssdmond')) +import ssdmond + + +daemon_base.db_connect = MagicMock() + +SYSLOG_IDENTIFIER = 'ssd_test' +NOT_AVAILABLE = 'N/A' \ No newline at end of file From 276f08edc9367e315a71a0495dc679acfc14e55a Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Thu, 12 Oct 2023 07:41:51 +0000 Subject: [PATCH 02/31] Added support for dynamic fields. Unified table update function. Added logs. --- sonic-ssdmond/scripts/ssdmond | 53 +++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/sonic-ssdmond/scripts/ssdmond b/sonic-ssdmond/scripts/ssdmond index a43fbe2e3..f4ba1c079 100644 --- a/sonic-ssdmond/scripts/ssdmond +++ b/sonic-ssdmond/scripts/ssdmond @@ -111,6 +111,8 @@ class DaemonSsd(daemon_base.DaemonBase): self.ssd_device_list = get_ssds_on_device() self.static_fields = ["device_model", "serial", "firmware"] + self.dynamic_fields = ["health", "temperature", "io_reads", "io_writes", "reserved_blocks"] + self.read_static_fields = False self.update_static_fields_statedb = False @@ -136,15 +138,15 @@ class DaemonSsd(daemon_base.DaemonBase): self.device_table._del(tk) - # Update the Static Fields to State DB - def update_static_fields_status_db(self, ssd_device, static_kvp_dict): + # Update the SSD info to State DB + def update_ssd_info_status_db(self, ssd_device, ssd_fields, kvp_dict): - for field in self.static_fields: - fvp = swsscommon.FieldValuePairs([(field, static_kvp_dict[field])]) + for field in ssd_fields: + fvp = swsscommon.FieldValuePairs([(field, kvp_dict[field])]) self.device_table.set(ssd_device, fvp) - # Check the SSD device + # Get Static attributes and update the State DB, once def get_static_fields(self): try: @@ -162,9 +164,10 @@ class DaemonSsd(daemon_base.DaemonBase): static_kvp_dict["serial"] = platform_ssdutil[ssd_device].get_serial() static_kvp_dict["firmware"] = platform_ssdutil[ssd_device].get_firmware() + self.log_info("SSD: {}, Device Model: {}, Serial: {}, FW: {}".format(ssd_device, static_kvp_dict["device_model"], static_kvp_dict["serial"], static_kvp_dict["firmware"])) # update SSD Device Status to DB - self.update_static_fields_status_db(ssd_device, static_kvp_dict) + self.update_ssd_info_status_db(ssd_device, self.static_fields, static_kvp_dict) read_static_fields = True update_static_fields_statedb = True @@ -173,8 +176,38 @@ class DaemonSsd(daemon_base.DaemonBase): self.log_warning("get_static_fields() failed with: {}".format(str(ex))) # Get Dynamic attributes and update the State DB - def get_dynamic_attributes(self): - self.log_warning("nothing yet") + def get_dynamic_fields(self): + + try: + # Get relevant information about each SSD on the device + for ssd_device in self.ssd_device_list: + + dynamic_kvp_dict = dict((field, "") for field in self.dynamic_fields) + + # Verify that the SSD exists in the expected location in the filesystem hierarchy + if not ssd_exists(ssd_device): + self.log_info("{} not found. Moving on.".format(ssd_device)) + continue + + dynamic_kvp_dict["health"] = platform_ssdutil[ssd_device].get_health() + dynamic_kvp_dict["temperature"] = platform_ssdutil[ssd_device].get_temperature() + dynamic_kvp_dict["io_reads"] = platform_ssdutil[ssd_device].get_io_reads() + dynamic_kvp_dict["io_writes"] = platform_ssdutil[ssd_device].get_io_writes() + dynamic_kvp_dict["reserved_blocks"] = platform_ssdutil[ssd_device].get_reserved_blocks() + + self.log_info("SSD: {}, health: {}%, Temp: {}C, IO Reads: {}, IO Writes: {}, Reserved Blocks: {}" \ + .format(ssd_device, dynamic_kvp_dict["health"], dynamic_kvp_dict["temperature"], dynamic_kvp_dict["io_reads"], \ + dynamic_kvp_dict["io_writes"], dynamic_kvp_dict["reserved_blocks"])) + + # update SSD Device Status to DB + self.update_ssd_info_status_db(ssd_device, self.dynamic_fields, dynamic_kvp_dict) + + self.read_static_fields = True + self.update_static_fields_statedb = True + + except Exception as ex: + self.log_warning("get_dynamic_fields() failed with: {}".format(str(ex))) + # Override signal handler from DaemonBase def signal_handler(self, sig, frame): @@ -201,9 +234,11 @@ class DaemonSsd(daemon_base.DaemonBase): # Read and update Static Fields to the StateDB once if not self.read_static_fields and not self.update_static_fields_statedb: self.get_static_fields() + else: + self.log_info("static fields have already been read and updated to State DB") # Repeatedly read and update Dynamic Fields to the StateDB - self.get_dynamic_attributes() + self.get_dynamic_fields() return True # From d2f3d49e180702f638c192b448adf2c0ed74d5e9 Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Fri, 13 Oct 2023 10:15:57 +0000 Subject: [PATCH 03/31] Added eMMC checks. Added checks before accessing ssdutil objs. Added logging. --- sonic-ssdmond/scripts/ssdmond | 43 ++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/sonic-ssdmond/scripts/ssdmond b/sonic-ssdmond/scripts/ssdmond index f4ba1c079..487857e65 100644 --- a/sonic-ssdmond/scripts/ssdmond +++ b/sonic-ssdmond/scripts/ssdmond @@ -113,20 +113,26 @@ class DaemonSsd(daemon_base.DaemonBase): self.static_fields = ["device_model", "serial", "firmware"] self.dynamic_fields = ["health", "temperature", "io_reads", "io_writes", "reserved_blocks"] - self.read_static_fields = False - self.update_static_fields_statedb = False + self.read_static_fields = dict((ssd, False) for ssd in self.ssd_device_list) + self.updated_static_fields_statedb = dict((ssd, False) for ssd in self.ssd_device_list) global platform_ssdutil - platform_ssdutil = dict((ssd, None) for ssd in self.ssd_device_list) for ssd in self.ssd_device_list: - diskdev = ("/dev/" + str(ssd)) - platform_ssdutil[ssd] = load_platform_ssdutil(diskdev) + if 'mmc' in ssd: + self.log_warning("eMMC SSD currently unsupported. Moving on to next disk on device.") + continue + else: + diskdev = ("/dev/" + str(ssd)) + platform_ssdutil[ssd] = load_platform_ssdutil(diskdev) + if platform_ssdutil[ssd] is None: sys.exit(SSDUTIL_LOAD_ERROR) + + # Connect to STATE_DB and create pcie device table self.state_db = daemon_base.db_connect("STATE_DB") self.device_table = swsscommon.Table(self.state_db, SSD_DEVICE_TABLE_NAME) @@ -153,11 +159,19 @@ class DaemonSsd(daemon_base.DaemonBase): # Get relevant information about each SSD on the device for ssd_device in self.ssd_device_list: + if platform_ssdutil[ssd_device] is None: + self.log_warning("{} does not have an ssdutil object. Static Information cannot be gathered. Skipping.".format(ssd_device)) + continue + + if self.read_static_fields[ssd_device] and self.updated_static_fields_statedb[ssd_device]: + self.log_info("Static information from {} has already been parsed and updated to StateDB.".format(ssd_device)) + continue + static_kvp_dict = dict((field, "") for field in self.static_fields) # Verify that the SSD exists in the expected location in the filesystem hierarchy if not ssd_exists(ssd_device): - self.log_warning("{} not found. Moving on.".format(ssd_device)) + self.log_warning("{} disk not found. Moving on.".format(ssd_device)) continue static_kvp_dict["device_model"] = platform_ssdutil[ssd_device].get_model() @@ -165,12 +179,11 @@ class DaemonSsd(daemon_base.DaemonBase): static_kvp_dict["firmware"] = platform_ssdutil[ssd_device].get_firmware() self.log_info("SSD: {}, Device Model: {}, Serial: {}, FW: {}".format(ssd_device, static_kvp_dict["device_model"], static_kvp_dict["serial"], static_kvp_dict["firmware"])) + self.read_static_fields[ssd_device] = True # update SSD Device Status to DB self.update_ssd_info_status_db(ssd_device, self.static_fields, static_kvp_dict) - - read_static_fields = True - update_static_fields_statedb = True + self.updated_static_fields_statedb[ssd_device] = True except Exception as ex: self.log_warning("get_static_fields() failed with: {}".format(str(ex))) @@ -182,6 +195,10 @@ class DaemonSsd(daemon_base.DaemonBase): # Get relevant information about each SSD on the device for ssd_device in self.ssd_device_list: + if platform_ssdutil[ssd_device] is None: + self.log_warning("{} does not have an ssdutil object. Dynamic Information cannot be gathered. Skipping.".format(ssd_device)) + continue + dynamic_kvp_dict = dict((field, "") for field in self.dynamic_fields) # Verify that the SSD exists in the expected location in the filesystem hierarchy @@ -202,9 +219,6 @@ class DaemonSsd(daemon_base.DaemonBase): # update SSD Device Status to DB self.update_ssd_info_status_db(ssd_device, self.dynamic_fields, dynamic_kvp_dict) - self.read_static_fields = True - self.update_static_fields_statedb = True - except Exception as ex: self.log_warning("get_dynamic_fields() failed with: {}".format(str(ex))) @@ -232,10 +246,7 @@ class DaemonSsd(daemon_base.DaemonBase): return False # Read and update Static Fields to the StateDB once - if not self.read_static_fields and not self.update_static_fields_statedb: - self.get_static_fields() - else: - self.log_info("static fields have already been read and updated to State DB") + self.get_static_fields() # Repeatedly read and update Dynamic Fields to the StateDB self.get_dynamic_fields() From 4582ffcb860575fd4508946ffde8aa17057f3e8b Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Fri, 20 Oct 2023 20:02:51 +0000 Subject: [PATCH 04/31] Removed references to 'ssd' and its variants and replaced with 'storage*' to include all disk types --- {sonic-ssdmond => sonic-stormond}/pytest.ini | 0 .../scripts/stormond | 157 +++++++++--------- {sonic-ssdmond => sonic-stormond}/setup.cfg | 0 {sonic-ssdmond => sonic-stormond}/setup.py | 4 +- .../tests/__init__.py | 0 .../tests/mock_platform.py | 4 +- .../mocked_libs/sonic_platform/__init__.py | 0 .../tests/mocked_libs/sonic_platform/ssd.py | 0 .../sonic_platform_base/__init__.py | 0 .../sonic_platform_base/sonic_ssd/__init__.py | 0 .../sonic_platform_base/sonic_ssd/ssd_base.py | 0 .../sonic_ssd/ssd_generic.py | 0 .../tests/mocked_libs/swsscommon/__init__.py | 0 .../mocked_libs/swsscommon/swsscommon.py | 0 .../tests/test_DaemonStorage.py | 12 +- .../tests/test_StorageDevice.py | 8 +- 16 files changed, 88 insertions(+), 97 deletions(-) rename {sonic-ssdmond => sonic-stormond}/pytest.ini (100%) rename sonic-ssdmond/scripts/ssdmond => sonic-stormond/scripts/stormond (52%) rename {sonic-ssdmond => sonic-stormond}/setup.cfg (100%) rename {sonic-ssdmond => sonic-stormond}/setup.py (95%) rename {sonic-ssdmond => sonic-stormond}/tests/__init__.py (100%) rename {sonic-ssdmond => sonic-stormond}/tests/mock_platform.py (77%) rename {sonic-ssdmond => sonic-stormond}/tests/mocked_libs/sonic_platform/__init__.py (100%) rename {sonic-ssdmond => sonic-stormond}/tests/mocked_libs/sonic_platform/ssd.py (100%) rename {sonic-ssdmond => sonic-stormond}/tests/mocked_libs/sonic_platform_base/__init__.py (100%) rename {sonic-ssdmond => sonic-stormond}/tests/mocked_libs/sonic_platform_base/sonic_ssd/__init__.py (100%) rename {sonic-ssdmond => sonic-stormond}/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py (100%) rename {sonic-ssdmond => sonic-stormond}/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_generic.py (100%) rename {sonic-ssdmond => sonic-stormond}/tests/mocked_libs/swsscommon/__init__.py (100%) rename {sonic-ssdmond => sonic-stormond}/tests/mocked_libs/swsscommon/swsscommon.py (100%) rename sonic-ssdmond/tests/test_DaemonSsd.py => sonic-stormond/tests/test_DaemonStorage.py (75%) rename sonic-ssdmond/tests/test_ssd.py => sonic-stormond/tests/test_StorageDevice.py (82%) diff --git a/sonic-ssdmond/pytest.ini b/sonic-stormond/pytest.ini similarity index 100% rename from sonic-ssdmond/pytest.ini rename to sonic-stormond/pytest.ini diff --git a/sonic-ssdmond/scripts/ssdmond b/sonic-stormond/scripts/stormond similarity index 52% rename from sonic-ssdmond/scripts/ssdmond rename to sonic-stormond/scripts/stormond index 487857e65..951b46b22 100644 --- a/sonic-ssdmond/scripts/ssdmond +++ b/sonic-stormond/scripts/stormond @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """ - ssdmond + stormond SSD device monitoring daemon for SONiC """ @@ -24,43 +24,34 @@ from swsscommon import swsscommon SIGNALS_TO_NAMES_DICT = dict((getattr(signal, n), n) for n in dir(signal) if n.startswith('SIG') and '_' not in n) -SYSLOG_IDENTIFIER = "ssdmond" +SYSLOG_IDENTIFIER = "stormond" -SSD_DEVICE_TABLE_NAME = "SSD_INFO" +STORAGE_DEVICE_TABLE_NAME = "STORAGE_INFO" -SSDMOND_MAIN_THREAD_SLEEP_SECS = 21600 #six hours +STORMOND_MAIN_THREAD_SLEEP_SECS = 21600 #six hours -SSDUTIL_LOAD_ERROR = 127 +STORAGEUTIL_LOAD_ERROR = 127 -platform_ssdutil = None +platform_storageutil = None log = logger.Logger(SYSLOG_IDENTIFIER) exit_code = 0 # wrapper functions to call the platform api -def load_platform_ssdutil(diskdev): +def load_platform_storageutil(diskdev): """ - Loads platform specific or generic Ssdutil module from source - Raises an ImportError exception if none of above available + Loads generic Ssdutil module from source + Raises an ImportError exception if not available Returns: - Instance of the class with SSD API implementation (vendor or generic) + Instance of the class with SSD API implementation """ - - # try to load platform specific module try: - platform_path, _ = device_info.get_paths_to_platform_and_hwsku_dirs() - platform_plugins_path = os.path.join(platform_path, "plugins") - sys.path.append(os.path.abspath(platform_plugins_path)) - from ssd_util import SsdUtil + from sonic_platform_base.sonic_ssd.ssd_generic import SsdUtil except ImportError as e: - log.log_warning("Platform specific SsdUtil module not found. Falling down to the generic implementation") - try: - from sonic_platform_base.sonic_ssd.ssd_generic import SsdUtil - except ImportError as e: - log.log_error("Failed to import default SsdUtil. Error: {}".format(str(e)), True) - raise e + log.log_error("Failed to import default SsdUtil. Error: {}".format(str(e)), True) + raise e return SsdUtil(diskdev) @@ -76,23 +67,23 @@ def exec_cmd(cmd, verbose=False): return (p.returncode, msg) -def get_ssds_on_device(): - ssd_device_list = [] +def get_storage_on_device(): + storage_device_list = [] cmd = "lsblk -d -o name,type" rc, out = exec_cmd(cmd) if rc != 0: - log.log_error("FATAL: Unable to query SSDs on the device. Error: {}".format(out.strip())) - sys.exit(SSDUTIL_LOAD_ERROR) + log.log_error("FATAL: Unable to query disks on the device. Error: {}".format(out.strip())) + sys.exit(STORAGEUTIL_LOAD_ERROR) - ssd_device_list += (line.split()[0].strip() for line in out.splitlines() if 'disk' in line) + storage_device_list += (line.split()[0].strip() for line in out.splitlines() if 'disk' in line) - log.log_info("{}".format(ssd_device_list)) - return ssd_device_list + log.log_info("{}".format(storage_device_list)) + return storage_device_list -def ssd_exists(ssd_device): - return os.path.exists("/dev/" + str(ssd_device)) +def storage_disk_exists(storage_device): + return os.path.exists("/sys/block/{}/device".format(str(storage_device))) # @@ -100,42 +91,42 @@ def ssd_exists(ssd_device): # -class DaemonSsd(daemon_base.DaemonBase): +class DaemonStorage(daemon_base.DaemonBase): def __init__(self, log_identifier): - super(DaemonSsd, self).__init__(log_identifier) + super(DaemonStorage, self).__init__(log_identifier) - self.timeout = SSDMOND_MAIN_THREAD_SLEEP_SECS + self.timeout = STORMOND_MAIN_THREAD_SLEEP_SECS self.stop_event = threading.Event() self.state_db = None self.device_table = None - self.ssd_device_list = get_ssds_on_device() + self.storage_device_list = get_storage_on_device() self.static_fields = ["device_model", "serial", "firmware"] self.dynamic_fields = ["health", "temperature", "io_reads", "io_writes", "reserved_blocks"] - self.read_static_fields = dict((ssd, False) for ssd in self.ssd_device_list) - self.updated_static_fields_statedb = dict((ssd, False) for ssd in self.ssd_device_list) + self.read_static_fields = dict((disk, False) for disk in self.storage_device_list) + self.updated_static_fields_statedb = dict((disk, False) for disk in self.storage_device_list) - global platform_ssdutil - platform_ssdutil = dict((ssd, None) for ssd in self.ssd_device_list) + global platform_storageutil + platform_storageutil = dict((disk, None) for disk in self.storage_device_list) - for ssd in self.ssd_device_list: - if 'mmc' in ssd: - self.log_warning("eMMC SSD currently unsupported. Moving on to next disk on device.") + for disk in self.storage_device_list: + if 'mmc' in disk: + self.log_warning("eMMC disks currently unsupported. Moving on to next disk on device.") continue else: - diskdev = ("/dev/" + str(ssd)) - platform_ssdutil[ssd] = load_platform_ssdutil(diskdev) + diskdev = ("/dev/" + str(disk)) + platform_storageutil[disk] = load_platform_storageutil(diskdev) - if platform_ssdutil[ssd] is None: - sys.exit(SSDUTIL_LOAD_ERROR) + if platform_storageutil[disk] is None: + sys.exit(STORAGEUTIL_LOAD_ERROR) - # Connect to STATE_DB and create pcie device table + # Connect to STATE_DB and create Storage device table self.state_db = daemon_base.db_connect("STATE_DB") - self.device_table = swsscommon.Table(self.state_db, SSD_DEVICE_TABLE_NAME) + self.device_table = swsscommon.Table(self.state_db, STORAGE_DEVICE_TABLE_NAME) def __del__(self): if self.device_table: @@ -144,12 +135,12 @@ class DaemonSsd(daemon_base.DaemonBase): self.device_table._del(tk) - # Update the SSD info to State DB - def update_ssd_info_status_db(self, ssd_device, ssd_fields, kvp_dict): + # Update the Storage device info to State DB + def update_storage_info_status_db(self, disk_device, disk_fields, kvp_dict): - for field in ssd_fields: + for field in disk_fields: fvp = swsscommon.FieldValuePairs([(field, kvp_dict[field])]) - self.device_table.set(ssd_device, fvp) + self.device_table.set(disk_device, fvp) # Get Static attributes and update the State DB, once @@ -157,33 +148,33 @@ class DaemonSsd(daemon_base.DaemonBase): try: # Get relevant information about each SSD on the device - for ssd_device in self.ssd_device_list: + for storage_device in self.storage_device_list: - if platform_ssdutil[ssd_device] is None: - self.log_warning("{} does not have an ssdutil object. Static Information cannot be gathered. Skipping.".format(ssd_device)) + if platform_storageutil[storage_device] is None: + self.log_warning("{} does not have an ssdutil object. Static Information cannot be gathered. Skipping.".format(storage_device)) continue - if self.read_static_fields[ssd_device] and self.updated_static_fields_statedb[ssd_device]: - self.log_info("Static information from {} has already been parsed and updated to StateDB.".format(ssd_device)) + if self.read_static_fields[storage_device] and self.updated_static_fields_statedb[storage_device]: + self.log_info("Static information from {} has already been parsed and updated to StateDB.".format(storage_device)) continue static_kvp_dict = dict((field, "") for field in self.static_fields) # Verify that the SSD exists in the expected location in the filesystem hierarchy - if not ssd_exists(ssd_device): - self.log_warning("{} disk not found. Moving on.".format(ssd_device)) + if not storage_disk_exists(storage_device): + self.log_warning("{} disk not found. Moving on.".format(storage_device)) continue - static_kvp_dict["device_model"] = platform_ssdutil[ssd_device].get_model() - static_kvp_dict["serial"] = platform_ssdutil[ssd_device].get_serial() - static_kvp_dict["firmware"] = platform_ssdutil[ssd_device].get_firmware() + static_kvp_dict["device_model"] = platform_storageutil[storage_device].get_model() + static_kvp_dict["serial"] = platform_storageutil[storage_device].get_serial() + static_kvp_dict["firmware"] = platform_storageutil[storage_device].get_firmware() - self.log_info("SSD: {}, Device Model: {}, Serial: {}, FW: {}".format(ssd_device, static_kvp_dict["device_model"], static_kvp_dict["serial"], static_kvp_dict["firmware"])) - self.read_static_fields[ssd_device] = True + self.log_info("Storage Device: {}, Device Model: {}, Serial: {}, FW: {}".format(storage_device, static_kvp_dict["device_model"], static_kvp_dict["serial"], static_kvp_dict["firmware"])) + self.read_static_fields[storage_device] = True # update SSD Device Status to DB - self.update_ssd_info_status_db(ssd_device, self.static_fields, static_kvp_dict) - self.updated_static_fields_statedb[ssd_device] = True + self.update_storage_info_status_db(storage_device, self.static_fields, static_kvp_dict) + self.updated_static_fields_statedb[storage_device] = True except Exception as ex: self.log_warning("get_static_fields() failed with: {}".format(str(ex))) @@ -193,31 +184,31 @@ class DaemonSsd(daemon_base.DaemonBase): try: # Get relevant information about each SSD on the device - for ssd_device in self.ssd_device_list: + for storage_device in self.storage_device_list: - if platform_ssdutil[ssd_device] is None: - self.log_warning("{} does not have an ssdutil object. Dynamic Information cannot be gathered. Skipping.".format(ssd_device)) + if platform_storageutil[storage_device] is None: + self.log_warning("{} does not have an ssdutil object. Dynamic Information cannot be gathered. Skipping.".format(storage_device)) continue dynamic_kvp_dict = dict((field, "") for field in self.dynamic_fields) # Verify that the SSD exists in the expected location in the filesystem hierarchy - if not ssd_exists(ssd_device): - self.log_info("{} not found. Moving on.".format(ssd_device)) + if not storage_disk_exists(storage_device): + self.log_info("{} not found. Moving on.".format(storage_device)) continue - dynamic_kvp_dict["health"] = platform_ssdutil[ssd_device].get_health() - dynamic_kvp_dict["temperature"] = platform_ssdutil[ssd_device].get_temperature() - dynamic_kvp_dict["io_reads"] = platform_ssdutil[ssd_device].get_io_reads() - dynamic_kvp_dict["io_writes"] = platform_ssdutil[ssd_device].get_io_writes() - dynamic_kvp_dict["reserved_blocks"] = platform_ssdutil[ssd_device].get_reserved_blocks() + dynamic_kvp_dict["health"] = platform_storageutil[storage_device].get_health() + dynamic_kvp_dict["temperature"] = platform_storageutil[storage_device].get_temperature() + dynamic_kvp_dict["io_reads"] = platform_storageutil[storage_device].get_io_reads() + dynamic_kvp_dict["io_writes"] = platform_storageutil[storage_device].get_io_writes() + dynamic_kvp_dict["reserved_blocks"] = platform_storageutil[storage_device].get_reserved_blocks() - self.log_info("SSD: {}, health: {}%, Temp: {}C, IO Reads: {}, IO Writes: {}, Reserved Blocks: {}" \ - .format(ssd_device, dynamic_kvp_dict["health"], dynamic_kvp_dict["temperature"], dynamic_kvp_dict["io_reads"], \ + self.log_info("Storage Device: {}, health: {}%, Temp: {}C, IO Reads: {}, IO Writes: {}, Reserved Blocks: {}" \ + .format(storage_device, dynamic_kvp_dict["health"], dynamic_kvp_dict["temperature"], dynamic_kvp_dict["io_reads"], \ dynamic_kvp_dict["io_writes"], dynamic_kvp_dict["reserved_blocks"])) # update SSD Device Status to DB - self.update_ssd_info_status_db(ssd_device, self.dynamic_fields, dynamic_kvp_dict) + self.update_storage_info_status_db(storage_device, self.dynamic_fields, dynamic_kvp_dict) except Exception as ex: self.log_warning("get_dynamic_fields() failed with: {}".format(str(ex))) @@ -258,14 +249,14 @@ class DaemonSsd(daemon_base.DaemonBase): def main(): - ssdmon = DaemonSsd(SYSLOG_IDENTIFIER) + stormon = DaemonStorage(SYSLOG_IDENTIFIER) - ssdmon.log_info("Starting up...") + stormon.log_info("Starting up...") - while ssdmon.run(): + while stormon.run(): pass - ssdmon.log_info("Shutting down...") + stormon.log_info("Shutting down...") return exit_code diff --git a/sonic-ssdmond/setup.cfg b/sonic-stormond/setup.cfg similarity index 100% rename from sonic-ssdmond/setup.cfg rename to sonic-stormond/setup.cfg diff --git a/sonic-ssdmond/setup.py b/sonic-stormond/setup.py similarity index 95% rename from sonic-ssdmond/setup.py rename to sonic-stormond/setup.py index b9ea98450..9e469375c 100644 --- a/sonic-ssdmond/setup.py +++ b/sonic-stormond/setup.py @@ -1,7 +1,7 @@ from setuptools import setup setup( - name='sonic-ssdmond', + name='sonic-stormond', version='1.0', description='SSD status daemon for SONiC', license='Apache 2.0', @@ -11,7 +11,7 @@ maintainer='Ashwin Srinivasan', maintainer_email='assrinivasan@microsoft.com', scripts=[ - 'scripts/ssdmond', + 'scripts/stormond', ], setup_requires=[ 'pytest-runner', diff --git a/sonic-ssdmond/tests/__init__.py b/sonic-stormond/tests/__init__.py similarity index 100% rename from sonic-ssdmond/tests/__init__.py rename to sonic-stormond/tests/__init__.py diff --git a/sonic-ssdmond/tests/mock_platform.py b/sonic-stormond/tests/mock_platform.py similarity index 77% rename from sonic-ssdmond/tests/mock_platform.py rename to sonic-stormond/tests/mock_platform.py index 2dc460f53..a5b3070bf 100644 --- a/sonic-ssdmond/tests/mock_platform.py +++ b/sonic-stormond/tests/mock_platform.py @@ -10,6 +10,6 @@ else: import mock -class MockSsdUtil(): +class MockStorageDevice(): def __init__(self): - super(MockSsdUtil, self).__init__() \ No newline at end of file + super(MockStorageDevice, self).__init__() \ No newline at end of file diff --git a/sonic-ssdmond/tests/mocked_libs/sonic_platform/__init__.py b/sonic-stormond/tests/mocked_libs/sonic_platform/__init__.py similarity index 100% rename from sonic-ssdmond/tests/mocked_libs/sonic_platform/__init__.py rename to sonic-stormond/tests/mocked_libs/sonic_platform/__init__.py diff --git a/sonic-ssdmond/tests/mocked_libs/sonic_platform/ssd.py b/sonic-stormond/tests/mocked_libs/sonic_platform/ssd.py similarity index 100% rename from sonic-ssdmond/tests/mocked_libs/sonic_platform/ssd.py rename to sonic-stormond/tests/mocked_libs/sonic_platform/ssd.py diff --git a/sonic-ssdmond/tests/mocked_libs/sonic_platform_base/__init__.py b/sonic-stormond/tests/mocked_libs/sonic_platform_base/__init__.py similarity index 100% rename from sonic-ssdmond/tests/mocked_libs/sonic_platform_base/__init__.py rename to sonic-stormond/tests/mocked_libs/sonic_platform_base/__init__.py diff --git a/sonic-ssdmond/tests/mocked_libs/sonic_platform_base/sonic_ssd/__init__.py b/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_ssd/__init__.py similarity index 100% rename from sonic-ssdmond/tests/mocked_libs/sonic_platform_base/sonic_ssd/__init__.py rename to sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_ssd/__init__.py diff --git a/sonic-ssdmond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py b/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py similarity index 100% rename from sonic-ssdmond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py rename to sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py diff --git a/sonic-ssdmond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_generic.py b/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_generic.py similarity index 100% rename from sonic-ssdmond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_generic.py rename to sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_generic.py diff --git a/sonic-ssdmond/tests/mocked_libs/swsscommon/__init__.py b/sonic-stormond/tests/mocked_libs/swsscommon/__init__.py similarity index 100% rename from sonic-ssdmond/tests/mocked_libs/swsscommon/__init__.py rename to sonic-stormond/tests/mocked_libs/swsscommon/__init__.py diff --git a/sonic-ssdmond/tests/mocked_libs/swsscommon/swsscommon.py b/sonic-stormond/tests/mocked_libs/swsscommon/swsscommon.py similarity index 100% rename from sonic-ssdmond/tests/mocked_libs/swsscommon/swsscommon.py rename to sonic-stormond/tests/mocked_libs/swsscommon/swsscommon.py diff --git a/sonic-ssdmond/tests/test_DaemonSsd.py b/sonic-stormond/tests/test_DaemonStorage.py similarity index 75% rename from sonic-ssdmond/tests/test_DaemonSsd.py rename to sonic-stormond/tests/test_DaemonStorage.py index 6b18dc70e..b64fd536f 100644 --- a/sonic-ssdmond/tests/test_DaemonSsd.py +++ b/sonic-stormond/tests/test_DaemonStorage.py @@ -11,9 +11,9 @@ else: import mock -from .mock_platform import MockSsdUtil +from .mock_platform import MockStorageDevice -SYSLOG_IDENTIFIER = 'ssd_daemon_test' +SYSLOG_IDENTIFIER = 'storage_daemon_test' NOT_AVAILABLE = 'N/A' @@ -30,11 +30,11 @@ modules_path = os.path.dirname(tests_path) scripts_path = os.path.join(modules_path, "scripts") sys.path.insert(0, modules_path) -load_source('ssdmond', os.path.join(scripts_path, 'ssdmond')) -import ssdmond +load_source('storagemond', os.path.join(scripts_path, 'storagemond')) +import storagemond -class TestDaemonPcied(object): +class TestDaemonStorage(object): """ - Test cases to cover functionality in DaemonPcied class + Test cases to cover functionality in DaemonStorage class """ diff --git a/sonic-ssdmond/tests/test_ssd.py b/sonic-stormond/tests/test_StorageDevice.py similarity index 82% rename from sonic-ssdmond/tests/test_ssd.py rename to sonic-stormond/tests/test_StorageDevice.py index 18141882d..972132ddc 100644 --- a/sonic-ssdmond/tests/test_ssd.py +++ b/sonic-stormond/tests/test_StorageDevice.py @@ -10,7 +10,7 @@ else: from mock import MagicMock, patch, mock_open -from .mock_platform import MockSsdUtil +from .mock_platform import MockStorageDevice tests_path = os.path.dirname(os.path.abspath(__file__)) @@ -23,11 +23,11 @@ modules_path = os.path.dirname(tests_path) scripts_path = os.path.join(modules_path, "scripts") sys.path.insert(0, modules_path) -load_source('ssdmond', os.path.join(scripts_path, 'ssdmond')) -import ssdmond +load_source('stormond', os.path.join(scripts_path, 'stormond')) +import stormond daemon_base.db_connect = MagicMock() -SYSLOG_IDENTIFIER = 'ssd_test' +SYSLOG_IDENTIFIER = 'storagedevice_test' NOT_AVAILABLE = 'N/A' \ No newline at end of file From d8e3930684b1ea5db2d31cce322a7eaf35ab44fd Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Thu, 7 Dec 2023 01:36:40 +0000 Subject: [PATCH 05/31] Created StorageDevices class for device ratification and object creation --- sonic-stormond/scripts/stormond | 170 +++++++++++++------------------- 1 file changed, 67 insertions(+), 103 deletions(-) diff --git a/sonic-stormond/scripts/stormond b/sonic-stormond/scripts/stormond index 951b46b22..d617e7d90 100644 --- a/sonic-stormond/scripts/stormond +++ b/sonic-stormond/scripts/stormond @@ -28,63 +28,56 @@ SYSLOG_IDENTIFIER = "stormond" STORAGE_DEVICE_TABLE_NAME = "STORAGE_INFO" -STORMOND_MAIN_THREAD_SLEEP_SECS = 21600 #six hours +STORMOND_MAIN_THREAD_SLEEP_SECS = 3600 #one hour STORAGEUTIL_LOAD_ERROR = 127 -platform_storageutil = None +self.storage.devices = None log = logger.Logger(SYSLOG_IDENTIFIER) exit_code = 0 -# wrapper functions to call the platform api -def load_platform_storageutil(diskdev): - """ - Loads generic Ssdutil module from source - Raises an ImportError exception if not available - - Returns: - Instance of the class with SSD API implementation - """ - try: - from sonic_platform_base.sonic_ssd.ssd_generic import SsdUtil - except ImportError as e: - log.log_error("Failed to import default SsdUtil. Error: {}".format(str(e)), True) - raise e - - return SsdUtil(diskdev) - -def exec_cmd(cmd, verbose=False): - p = subprocess.Popen(cmd, shell=True, executable='/bin/bash', stdout=subprocess.PIPE, stderr=subprocess.PIPE) - outs, errs = p.communicate() - msg = '' - if outs: msg = outs.decode('utf8') - if outs and verbose: log.log_info('exec_cmd stdout = '+msg) - if errs: - log.log_error('exec_cmd stderr = '+errs) - msg = msg + errs.decode('utf8') - - return (p.returncode, msg) - -def get_storage_on_device(): - storage_device_list = [] - cmd = "lsblk -d -o name,type" - - rc, out = exec_cmd(cmd) - - if rc != 0: - log.log_error("FATAL: Unable to query disks on the device. Error: {}".format(out.strip())) - sys.exit(STORAGEUTIL_LOAD_ERROR) - - storage_device_list += (line.split()[0].strip() for line in out.splitlines() if 'disk' in line) - - log.log_info("{}".format(storage_device_list)) - return storage_device_list - -def storage_disk_exists(storage_device): - return os.path.exists("/sys/block/{}/device".format(str(storage_device))) - +class StorageDevices(): + def __init__(self, log_identifier): + self.devices = {} + self.BASE_PATH = "/sys/block" + self.BLKDEV_BASE_PATH = "/dev" + self.get_storage_devices() + self.get_storage_device_object() + + def get_storage_devices(self): + fdlist = os.listdir(self.BASE_PATH) + for fd in fdlist: + if 'boot' in fd or 'loop' in fd: + continue + else: + self.devices[fd] = None + + def get_storage_device_object(self): + for key in self.devices: + blkdev = os.path.join(self.BLKDEV_BASE_PATH, key) + diskdev = os.path.join(self.BASE_PATH, key) + if key.startswith('sd'): + path = os.path.join(diskdev, "device") + if "ata" in os.path.realpath(path): + try: + from sonic_platform_base.sonic_ssd.ssd_generic import SsdUtil + self.devices[key] = SsdUtil(blkdev) + except ImportError as e: + log.log_warning("Failed to import default SsdUtil. Error: {}".format(str(e)), True) + elif "usb" in os.path.realpath(path): + try: + from sonic_platform_base.sonic_ssd.ssd_usb import UsbUtil + self.devices[key] = UsbUtil(blkdev) + except ImportError as e: + log.log_warning("Failed to import default UsbUtil. Error: {}".format(str(e)), True) + elif "mmcblk" in key: + try: + from sonic_platform_base.sonic_ssd.ssd_emmc import EmmcUtil + self.devices[key] = EmmcUtil(key) + except ImportError as e: + log.log_warning("Failed to import default EmmcUtil. Error: {}".format(str(e)), True) # # Daemon ======================================================================= @@ -99,30 +92,13 @@ class DaemonStorage(daemon_base.DaemonBase): self.stop_event = threading.Event() self.state_db = None self.device_table = None - self.storage_device_list = get_storage_on_device() + self.storage = StorageDevices(log_identifier) self.static_fields = ["device_model", "serial", "firmware"] self.dynamic_fields = ["health", "temperature", "io_reads", "io_writes", "reserved_blocks"] - self.read_static_fields = dict((disk, False) for disk in self.storage_device_list) - self.updated_static_fields_statedb = dict((disk, False) for disk in self.storage_device_list) - - global platform_storageutil - platform_storageutil = dict((disk, None) for disk in self.storage_device_list) - - for disk in self.storage_device_list: - if 'mmc' in disk: - self.log_warning("eMMC disks currently unsupported. Moving on to next disk on device.") - continue - else: - diskdev = ("/dev/" + str(disk)) - platform_storageutil[disk] = load_platform_storageutil(diskdev) - - if platform_storageutil[disk] is None: - sys.exit(STORAGEUTIL_LOAD_ERROR) - - - + self.read_static_fields = dict((disk, False) for disk in self.storage.devices) + self.updated_static_fields_statedb = dict((disk, False) for disk in self.storage.devices) # Connect to STATE_DB and create Storage device table self.state_db = daemon_base.db_connect("STATE_DB") @@ -146,12 +122,11 @@ class DaemonStorage(daemon_base.DaemonBase): # Get Static attributes and update the State DB, once def get_static_fields(self): - try: - # Get relevant information about each SSD on the device - for storage_device in self.storage_device_list: - - if platform_storageutil[storage_device] is None: - self.log_warning("{} does not have an ssdutil object. Static Information cannot be gathered. Skipping.".format(storage_device)) + # Get relevant information about each SSD on the device + for storage_device in self.storage.devices: + try: + if self.storage.devices[storage_device] is None: + self.log_warning("{} does not have an instantiated object. Static Information cannot be gathered.".format(storage_device)) continue if self.read_static_fields[storage_device] and self.updated_static_fields_statedb[storage_device]: @@ -160,14 +135,9 @@ class DaemonStorage(daemon_base.DaemonBase): static_kvp_dict = dict((field, "") for field in self.static_fields) - # Verify that the SSD exists in the expected location in the filesystem hierarchy - if not storage_disk_exists(storage_device): - self.log_warning("{} disk not found. Moving on.".format(storage_device)) - continue - - static_kvp_dict["device_model"] = platform_storageutil[storage_device].get_model() - static_kvp_dict["serial"] = platform_storageutil[storage_device].get_serial() - static_kvp_dict["firmware"] = platform_storageutil[storage_device].get_firmware() + static_kvp_dict["device_model"] = self.storage.devices[storage_device].get_model() + static_kvp_dict["serial"] = self.storage.devices[storage_device].get_serial() + static_kvp_dict["firmware"] = self.storage.devices[storage_device].get_firmware() self.log_info("Storage Device: {}, Device Model: {}, Serial: {}, FW: {}".format(storage_device, static_kvp_dict["device_model"], static_kvp_dict["serial"], static_kvp_dict["firmware"])) self.read_static_fields[storage_device] = True @@ -176,32 +146,26 @@ class DaemonStorage(daemon_base.DaemonBase): self.update_storage_info_status_db(storage_device, self.static_fields, static_kvp_dict) self.updated_static_fields_statedb[storage_device] = True - except Exception as ex: - self.log_warning("get_static_fields() failed with: {}".format(str(ex))) + except Exception as ex: + self.log_warning("get_static_fields() failed with: {}".format(str(ex))) # Get Dynamic attributes and update the State DB def get_dynamic_fields(self): - try: - # Get relevant information about each SSD on the device - for storage_device in self.storage_device_list: - - if platform_storageutil[storage_device] is None: - self.log_warning("{} does not have an ssdutil object. Dynamic Information cannot be gathered. Skipping.".format(storage_device)) + # Get relevant information about each storage disk on the device + for storage_device in self.storage.devices: + try: + if self.storage.devices[storage_device] is None: + self.log_warning("{} does not have an instantiated object. Dynamic Information cannot be gathered.".format(storage_device)) continue dynamic_kvp_dict = dict((field, "") for field in self.dynamic_fields) - # Verify that the SSD exists in the expected location in the filesystem hierarchy - if not storage_disk_exists(storage_device): - self.log_info("{} not found. Moving on.".format(storage_device)) - continue - - dynamic_kvp_dict["health"] = platform_storageutil[storage_device].get_health() - dynamic_kvp_dict["temperature"] = platform_storageutil[storage_device].get_temperature() - dynamic_kvp_dict["io_reads"] = platform_storageutil[storage_device].get_io_reads() - dynamic_kvp_dict["io_writes"] = platform_storageutil[storage_device].get_io_writes() - dynamic_kvp_dict["reserved_blocks"] = platform_storageutil[storage_device].get_reserved_blocks() + dynamic_kvp_dict["health"] = self.storage.devices[storage_device].get_health() + dynamic_kvp_dict["temperature"] = self.storage.devices[storage_device].get_temperature() + dynamic_kvp_dict["io_reads"] = self.storage.devices[storage_device].get_io_reads() + dynamic_kvp_dict["io_writes"] = self.storage.devices[storage_device].get_io_writes() + dynamic_kvp_dict["reserved_blocks"] = self.storage.devices[storage_device].get_reserved_blocks() self.log_info("Storage Device: {}, health: {}%, Temp: {}C, IO Reads: {}, IO Writes: {}, Reserved Blocks: {}" \ .format(storage_device, dynamic_kvp_dict["health"], dynamic_kvp_dict["temperature"], dynamic_kvp_dict["io_reads"], \ @@ -210,8 +174,8 @@ class DaemonStorage(daemon_base.DaemonBase): # update SSD Device Status to DB self.update_storage_info_status_db(storage_device, self.dynamic_fields, dynamic_kvp_dict) - except Exception as ex: - self.log_warning("get_dynamic_fields() failed with: {}".format(str(ex))) + except Exception as ex: + self.log_warning("get_dynamic_fields() failed with: {}".format(str(ex))) # Override signal handler from DaemonBase From 8ad06d90f95212f7cc6ec351a39ec2cdddf45691 Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Fri, 9 Feb 2024 19:18:32 +0000 Subject: [PATCH 06/31] Renamed daemon to 'storagemond', changed unit tests to reflect the same --- sonic-stormond/scripts/{stormond => storagemond} | 0 .../mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py | 6 +++--- sonic-stormond/tests/test_StorageDevice.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename sonic-stormond/scripts/{stormond => storagemond} (100%) diff --git a/sonic-stormond/scripts/stormond b/sonic-stormond/scripts/storagemond similarity index 100% rename from sonic-stormond/scripts/stormond rename to sonic-stormond/scripts/storagemond diff --git a/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py b/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py index ad6607b6f..8f009d3d6 100644 --- a/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py +++ b/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py @@ -1,8 +1,8 @@ # -# pcie_base.py +# ssd_base.py # # Abstract base class for implementing platform-specific -# PCIE functionality for SONiC +# Storage information gathering functionality for SONiC # try: @@ -13,7 +13,7 @@ # # ssd_base.py # -# Base class for implementing common SSD health features +# Base class for implementing common Storage Device health features # diff --git a/sonic-stormond/tests/test_StorageDevice.py b/sonic-stormond/tests/test_StorageDevice.py index 972132ddc..21f589d07 100644 --- a/sonic-stormond/tests/test_StorageDevice.py +++ b/sonic-stormond/tests/test_StorageDevice.py @@ -23,8 +23,8 @@ modules_path = os.path.dirname(tests_path) scripts_path = os.path.join(modules_path, "scripts") sys.path.insert(0, modules_path) -load_source('stormond', os.path.join(scripts_path, 'stormond')) -import stormond +load_source('storagemond', os.path.join(scripts_path, 'storagemond')) +import storagemond daemon_base.db_connect = MagicMock() From b95d4e2d640063b4474c43fe209c199534c68800 Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Fri, 9 Feb 2024 19:37:52 +0000 Subject: [PATCH 07/31] Renamed storagemond directory and corresponding change to setup.py --- {sonic-stormond => sonic-storagemond}/pytest.ini | 0 .../scripts/storagemond | 12 ++++++------ {sonic-stormond => sonic-storagemond}/setup.cfg | 0 {sonic-stormond => sonic-storagemond}/setup.py | 6 +++--- .../tests/__init__.py | 0 .../tests/mock_platform.py | 0 .../tests/mocked_libs/sonic_platform/__init__.py | 0 .../tests/mocked_libs/sonic_platform/ssd.py | 0 .../mocked_libs/sonic_platform_base/__init__.py | 0 .../sonic_platform_base/sonic_ssd/__init__.py | 0 .../sonic_platform_base/sonic_ssd/ssd_base.py | 0 .../sonic_platform_base/sonic_ssd/ssd_generic.py | 0 .../tests/mocked_libs/swsscommon/__init__.py | 0 .../tests/mocked_libs/swsscommon/swsscommon.py | 0 .../tests/test_DaemonStorage.py | 0 .../tests/test_StorageDevice.py | 0 16 files changed, 9 insertions(+), 9 deletions(-) rename {sonic-stormond => sonic-storagemond}/pytest.ini (100%) rename {sonic-stormond => sonic-storagemond}/scripts/storagemond (96%) rename {sonic-stormond => sonic-storagemond}/setup.cfg (100%) rename {sonic-stormond => sonic-storagemond}/setup.py (87%) rename {sonic-stormond => sonic-storagemond}/tests/__init__.py (100%) rename {sonic-stormond => sonic-storagemond}/tests/mock_platform.py (100%) rename {sonic-stormond => sonic-storagemond}/tests/mocked_libs/sonic_platform/__init__.py (100%) rename {sonic-stormond => sonic-storagemond}/tests/mocked_libs/sonic_platform/ssd.py (100%) rename {sonic-stormond => sonic-storagemond}/tests/mocked_libs/sonic_platform_base/__init__.py (100%) rename {sonic-stormond => sonic-storagemond}/tests/mocked_libs/sonic_platform_base/sonic_ssd/__init__.py (100%) rename {sonic-stormond => sonic-storagemond}/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py (100%) rename {sonic-stormond => sonic-storagemond}/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_generic.py (100%) rename {sonic-stormond => sonic-storagemond}/tests/mocked_libs/swsscommon/__init__.py (100%) rename {sonic-stormond => sonic-storagemond}/tests/mocked_libs/swsscommon/swsscommon.py (100%) rename {sonic-stormond => sonic-storagemond}/tests/test_DaemonStorage.py (100%) rename {sonic-stormond => sonic-storagemond}/tests/test_StorageDevice.py (100%) diff --git a/sonic-stormond/pytest.ini b/sonic-storagemond/pytest.ini similarity index 100% rename from sonic-stormond/pytest.ini rename to sonic-storagemond/pytest.ini diff --git a/sonic-stormond/scripts/storagemond b/sonic-storagemond/scripts/storagemond similarity index 96% rename from sonic-stormond/scripts/storagemond rename to sonic-storagemond/scripts/storagemond index d617e7d90..1f2bfc8a6 100644 --- a/sonic-stormond/scripts/storagemond +++ b/sonic-storagemond/scripts/storagemond @@ -1,8 +1,8 @@ #!/usr/bin/env python3 """ - stormond - SSD device monitoring daemon for SONiC + storagemond + Strage Monitoring device monitoring daemon for SONiC """ import os @@ -24,7 +24,7 @@ from swsscommon import swsscommon SIGNALS_TO_NAMES_DICT = dict((getattr(signal, n), n) for n in dir(signal) if n.startswith('SIG') and '_' not in n) -SYSLOG_IDENTIFIER = "stormond" +SYSLOG_IDENTIFIER = "storagemond" STORAGE_DEVICE_TABLE_NAME = "STORAGE_INFO" @@ -122,7 +122,7 @@ class DaemonStorage(daemon_base.DaemonBase): # Get Static attributes and update the State DB, once def get_static_fields(self): - # Get relevant information about each SSD on the device + # Get relevant information about each Storage Device on the switch for storage_device in self.storage.devices: try: if self.storage.devices[storage_device] is None: @@ -142,7 +142,7 @@ class DaemonStorage(daemon_base.DaemonBase): self.log_info("Storage Device: {}, Device Model: {}, Serial: {}, FW: {}".format(storage_device, static_kvp_dict["device_model"], static_kvp_dict["serial"], static_kvp_dict["firmware"])) self.read_static_fields[storage_device] = True - # update SSD Device Status to DB + # update Storage Device Status to DB self.update_storage_info_status_db(storage_device, self.static_fields, static_kvp_dict) self.updated_static_fields_statedb[storage_device] = True @@ -171,7 +171,7 @@ class DaemonStorage(daemon_base.DaemonBase): .format(storage_device, dynamic_kvp_dict["health"], dynamic_kvp_dict["temperature"], dynamic_kvp_dict["io_reads"], \ dynamic_kvp_dict["io_writes"], dynamic_kvp_dict["reserved_blocks"])) - # update SSD Device Status to DB + # update Storage Device Status to DB self.update_storage_info_status_db(storage_device, self.dynamic_fields, dynamic_kvp_dict) except Exception as ex: diff --git a/sonic-stormond/setup.cfg b/sonic-storagemond/setup.cfg similarity index 100% rename from sonic-stormond/setup.cfg rename to sonic-storagemond/setup.cfg diff --git a/sonic-stormond/setup.py b/sonic-storagemond/setup.py similarity index 87% rename from sonic-stormond/setup.py rename to sonic-storagemond/setup.py index 9e469375c..a4eb129dc 100644 --- a/sonic-stormond/setup.py +++ b/sonic-storagemond/setup.py @@ -3,7 +3,7 @@ setup( name='sonic-stormond', version='1.0', - description='SSD status daemon for SONiC', + description='Storage Device status daemon for SONiC', license='Apache 2.0', author='SONiC Team', author_email='linuxnetdev@microsoft.com', @@ -11,7 +11,7 @@ maintainer='Ashwin Srinivasan', maintainer_email='assrinivasan@microsoft.com', scripts=[ - 'scripts/stormond', + 'scripts/storagemond', ], setup_requires=[ 'pytest-runner', @@ -38,6 +38,6 @@ 'Programming Language :: Python :: 2.7', 'Topic :: System :: Hardware', ], - keywords='sonic SONiC ssd Ssd SSD ssdmon', + keywords='sonic SONiC ssd Ssd SSD ssdmond storage stormond storagemond', test_suite='setup.get_test_suite' ) diff --git a/sonic-stormond/tests/__init__.py b/sonic-storagemond/tests/__init__.py similarity index 100% rename from sonic-stormond/tests/__init__.py rename to sonic-storagemond/tests/__init__.py diff --git a/sonic-stormond/tests/mock_platform.py b/sonic-storagemond/tests/mock_platform.py similarity index 100% rename from sonic-stormond/tests/mock_platform.py rename to sonic-storagemond/tests/mock_platform.py diff --git a/sonic-stormond/tests/mocked_libs/sonic_platform/__init__.py b/sonic-storagemond/tests/mocked_libs/sonic_platform/__init__.py similarity index 100% rename from sonic-stormond/tests/mocked_libs/sonic_platform/__init__.py rename to sonic-storagemond/tests/mocked_libs/sonic_platform/__init__.py diff --git a/sonic-stormond/tests/mocked_libs/sonic_platform/ssd.py b/sonic-storagemond/tests/mocked_libs/sonic_platform/ssd.py similarity index 100% rename from sonic-stormond/tests/mocked_libs/sonic_platform/ssd.py rename to sonic-storagemond/tests/mocked_libs/sonic_platform/ssd.py diff --git a/sonic-stormond/tests/mocked_libs/sonic_platform_base/__init__.py b/sonic-storagemond/tests/mocked_libs/sonic_platform_base/__init__.py similarity index 100% rename from sonic-stormond/tests/mocked_libs/sonic_platform_base/__init__.py rename to sonic-storagemond/tests/mocked_libs/sonic_platform_base/__init__.py diff --git a/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_ssd/__init__.py b/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_ssd/__init__.py similarity index 100% rename from sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_ssd/__init__.py rename to sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_ssd/__init__.py diff --git a/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py b/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py similarity index 100% rename from sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py rename to sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py diff --git a/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_generic.py b/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_generic.py similarity index 100% rename from sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_generic.py rename to sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_generic.py diff --git a/sonic-stormond/tests/mocked_libs/swsscommon/__init__.py b/sonic-storagemond/tests/mocked_libs/swsscommon/__init__.py similarity index 100% rename from sonic-stormond/tests/mocked_libs/swsscommon/__init__.py rename to sonic-storagemond/tests/mocked_libs/swsscommon/__init__.py diff --git a/sonic-stormond/tests/mocked_libs/swsscommon/swsscommon.py b/sonic-storagemond/tests/mocked_libs/swsscommon/swsscommon.py similarity index 100% rename from sonic-stormond/tests/mocked_libs/swsscommon/swsscommon.py rename to sonic-storagemond/tests/mocked_libs/swsscommon/swsscommon.py diff --git a/sonic-stormond/tests/test_DaemonStorage.py b/sonic-storagemond/tests/test_DaemonStorage.py similarity index 100% rename from sonic-stormond/tests/test_DaemonStorage.py rename to sonic-storagemond/tests/test_DaemonStorage.py diff --git a/sonic-stormond/tests/test_StorageDevice.py b/sonic-storagemond/tests/test_StorageDevice.py similarity index 100% rename from sonic-stormond/tests/test_StorageDevice.py rename to sonic-storagemond/tests/test_StorageDevice.py From 2a0b6b096d19e2f350123fbc7b65b4c747532511 Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Thu, 15 Feb 2024 00:40:25 +0000 Subject: [PATCH 08/31] Added support for FS IO RW. Cleaned up bugs. Renamed ssd_base --> storage_base --- sonic-storagemond/scripts/storagemond | 23 ++++++++++--------- .../{ssd_base.py => storage_base.py} | 4 ++-- 2 files changed, 14 insertions(+), 13 deletions(-) rename sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_ssd/{ssd_base.py => storage_base.py} (97%) diff --git a/sonic-storagemond/scripts/storagemond b/sonic-storagemond/scripts/storagemond index 1f2bfc8a6..b78969335 100644 --- a/sonic-storagemond/scripts/storagemond +++ b/sonic-storagemond/scripts/storagemond @@ -32,8 +32,6 @@ STORMOND_MAIN_THREAD_SLEEP_SECS = 3600 #one hour STORAGEUTIL_LOAD_ERROR = 127 -self.storage.devices = None - log = logger.Logger(SYSLOG_IDENTIFIER) exit_code = 0 @@ -95,7 +93,7 @@ class DaemonStorage(daemon_base.DaemonBase): self.storage = StorageDevices(log_identifier) self.static_fields = ["device_model", "serial", "firmware"] - self.dynamic_fields = ["health", "temperature", "io_reads", "io_writes", "reserved_blocks"] + self.dynamic_fields = ["health", "temperature", "fs_io_reads", "fs_io_writes", "disk_io_reads", "disk_io_writes", "reserved_blocks"] self.read_static_fields = dict((disk, False) for disk in self.storage.devices) self.updated_static_fields_statedb = dict((disk, False) for disk in self.storage.devices) @@ -115,7 +113,7 @@ class DaemonStorage(daemon_base.DaemonBase): def update_storage_info_status_db(self, disk_device, disk_fields, kvp_dict): for field in disk_fields: - fvp = swsscommon.FieldValuePairs([(field, kvp_dict[field])]) + fvp = swsscommon.FieldValuePairs([(field, str(kvp_dict[field]))]) self.device_table.set(disk_device, fvp) @@ -163,13 +161,16 @@ class DaemonStorage(daemon_base.DaemonBase): dynamic_kvp_dict["health"] = self.storage.devices[storage_device].get_health() dynamic_kvp_dict["temperature"] = self.storage.devices[storage_device].get_temperature() - dynamic_kvp_dict["io_reads"] = self.storage.devices[storage_device].get_io_reads() - dynamic_kvp_dict["io_writes"] = self.storage.devices[storage_device].get_io_writes() + dynamic_kvp_dict["fs_io_reads"] = self.storage.devices[storage_device].get_fs_io_reads() + dynamic_kvp_dict["fs_io_writes"] = self.storage.devices[storage_device].get_fs_io_writes() + dynamic_kvp_dict["disk_io_reads"] = self.storage.devices[storage_device].get_disk_io_reads() + dynamic_kvp_dict["disk_io_writes"] = self.storage.devices[storage_device].get_disk_io_writes() dynamic_kvp_dict["reserved_blocks"] = self.storage.devices[storage_device].get_reserved_blocks() - self.log_info("Storage Device: {}, health: {}%, Temp: {}C, IO Reads: {}, IO Writes: {}, Reserved Blocks: {}" \ - .format(storage_device, dynamic_kvp_dict["health"], dynamic_kvp_dict["temperature"], dynamic_kvp_dict["io_reads"], \ - dynamic_kvp_dict["io_writes"], dynamic_kvp_dict["reserved_blocks"])) + self.log_info("Storage Device: {}, health: {}%, Temp: {}C, FS IO Reads: {}, FS IO Writes: {}".format(\ + storage_device, dynamic_kvp_dict["health"], dynamic_kvp_dict["temperature"], dynamic_kvp_dict["fs_io_reads"],dynamic_kvp_dict["fs_io_writes"],)) + self.log_info("Disk IO Reads: {}, Disk IO Writes: {}, Reserved Blocks: {}".format(dynamic_kvp_dict["disk_io_reads"], dynamic_kvp_dict["disk_io_writes"], \ + dynamic_kvp_dict["reserved_blocks"])) # update Storage Device Status to DB self.update_storage_info_status_db(storage_device, self.dynamic_fields, dynamic_kvp_dict) @@ -215,12 +216,12 @@ class DaemonStorage(daemon_base.DaemonBase): def main(): stormon = DaemonStorage(SYSLOG_IDENTIFIER) - stormon.log_info("Starting up...") + stormon.log_info("Starting Storage Monitoring Daemon") while stormon.run(): pass - stormon.log_info("Shutting down...") + stormon.log_info("Shutting down Storage Monitoring Daemon") return exit_code diff --git a/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py b/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_ssd/storage_base.py similarity index 97% rename from sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py rename to sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_ssd/storage_base.py index 8f009d3d6..380a7a54a 100644 --- a/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_base.py +++ b/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_ssd/storage_base.py @@ -1,5 +1,5 @@ # -# ssd_base.py +# storage_base.py # # Abstract base class for implementing platform-specific # Storage information gathering functionality for SONiC @@ -11,7 +11,7 @@ raise ImportError(str(e) + " - required module not found") # -# ssd_base.py +# storage_base.py # # Base class for implementing common Storage Device health features # From bf910df6444096b6fa64cea3207e0ba022aaf52d Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Sat, 17 Feb 2024 09:24:01 +0000 Subject: [PATCH 09/31] Renamed test dirs, changed import to match platform-common dirname change --- sonic-storagemond/scripts/storagemond | 54 +++---------------- .../{sonic_ssd => sonic_storage}/__init__.py | 0 .../ssd_generic.py => sonic_storage/ssd.py} | 4 +- .../storage_base.py | 0 4 files changed, 9 insertions(+), 49 deletions(-) rename sonic-storagemond/tests/mocked_libs/sonic_platform_base/{sonic_ssd => sonic_storage}/__init__.py (100%) rename sonic-storagemond/tests/mocked_libs/sonic_platform_base/{sonic_ssd/ssd_generic.py => sonic_storage/ssd.py} (97%) rename sonic-storagemond/tests/mocked_libs/sonic_platform_base/{sonic_ssd => sonic_storage}/storage_base.py (100%) diff --git a/sonic-storagemond/scripts/storagemond b/sonic-storagemond/scripts/storagemond index b78969335..29e9f4f41 100644 --- a/sonic-storagemond/scripts/storagemond +++ b/sonic-storagemond/scripts/storagemond @@ -14,6 +14,7 @@ import shutil from sonic_py_common import daemon_base, device_info, logger from swsscommon import swsscommon +from sonic_platform_base.sonic_storage.storage_devices import StorageDevices # # Constants ==================================================================== @@ -36,47 +37,6 @@ log = logger.Logger(SYSLOG_IDENTIFIER) exit_code = 0 -class StorageDevices(): - def __init__(self, log_identifier): - self.devices = {} - self.BASE_PATH = "/sys/block" - self.BLKDEV_BASE_PATH = "/dev" - self.get_storage_devices() - self.get_storage_device_object() - - def get_storage_devices(self): - fdlist = os.listdir(self.BASE_PATH) - for fd in fdlist: - if 'boot' in fd or 'loop' in fd: - continue - else: - self.devices[fd] = None - - def get_storage_device_object(self): - for key in self.devices: - blkdev = os.path.join(self.BLKDEV_BASE_PATH, key) - diskdev = os.path.join(self.BASE_PATH, key) - if key.startswith('sd'): - path = os.path.join(diskdev, "device") - if "ata" in os.path.realpath(path): - try: - from sonic_platform_base.sonic_ssd.ssd_generic import SsdUtil - self.devices[key] = SsdUtil(blkdev) - except ImportError as e: - log.log_warning("Failed to import default SsdUtil. Error: {}".format(str(e)), True) - elif "usb" in os.path.realpath(path): - try: - from sonic_platform_base.sonic_ssd.ssd_usb import UsbUtil - self.devices[key] = UsbUtil(blkdev) - except ImportError as e: - log.log_warning("Failed to import default UsbUtil. Error: {}".format(str(e)), True) - elif "mmcblk" in key: - try: - from sonic_platform_base.sonic_ssd.ssd_emmc import EmmcUtil - self.devices[key] = EmmcUtil(key) - except ImportError as e: - log.log_warning("Failed to import default EmmcUtil. Error: {}".format(str(e)), True) - # # Daemon ======================================================================= # @@ -92,8 +52,8 @@ class DaemonStorage(daemon_base.DaemonBase): self.device_table = None self.storage = StorageDevices(log_identifier) - self.static_fields = ["device_model", "serial", "firmware"] - self.dynamic_fields = ["health", "temperature", "fs_io_reads", "fs_io_writes", "disk_io_reads", "disk_io_writes", "reserved_blocks"] + self.static_fields = ["device_model", "serial"] + self.dynamic_fields = ["firmware", "health", "temperature", "fs_io_reads", "fs_io_writes", "disk_io_reads", "disk_io_writes", "reserved_blocks"] self.read_static_fields = dict((disk, False) for disk in self.storage.devices) self.updated_static_fields_statedb = dict((disk, False) for disk in self.storage.devices) @@ -135,9 +95,8 @@ class DaemonStorage(daemon_base.DaemonBase): static_kvp_dict["device_model"] = self.storage.devices[storage_device].get_model() static_kvp_dict["serial"] = self.storage.devices[storage_device].get_serial() - static_kvp_dict["firmware"] = self.storage.devices[storage_device].get_firmware() - self.log_info("Storage Device: {}, Device Model: {}, Serial: {}, FW: {}".format(storage_device, static_kvp_dict["device_model"], static_kvp_dict["serial"], static_kvp_dict["firmware"])) + self.log_info("Storage Device: {}, Device Model: {}, Serial: {}".format(storage_device, static_kvp_dict["device_model"], static_kvp_dict["serial"])) self.read_static_fields[storage_device] = True # update Storage Device Status to DB @@ -159,6 +118,7 @@ class DaemonStorage(daemon_base.DaemonBase): dynamic_kvp_dict = dict((field, "") for field in self.dynamic_fields) + dynamic_kvp_dict["firmware"] = self.storage.devices[storage_device].get_firmware() dynamic_kvp_dict["health"] = self.storage.devices[storage_device].get_health() dynamic_kvp_dict["temperature"] = self.storage.devices[storage_device].get_temperature() dynamic_kvp_dict["fs_io_reads"] = self.storage.devices[storage_device].get_fs_io_reads() @@ -167,8 +127,8 @@ class DaemonStorage(daemon_base.DaemonBase): dynamic_kvp_dict["disk_io_writes"] = self.storage.devices[storage_device].get_disk_io_writes() dynamic_kvp_dict["reserved_blocks"] = self.storage.devices[storage_device].get_reserved_blocks() - self.log_info("Storage Device: {}, health: {}%, Temp: {}C, FS IO Reads: {}, FS IO Writes: {}".format(\ - storage_device, dynamic_kvp_dict["health"], dynamic_kvp_dict["temperature"], dynamic_kvp_dict["fs_io_reads"],dynamic_kvp_dict["fs_io_writes"],)) + self.log_info("Storage Device: {}, Firmware: {}, health: {}%, Temp: {}C, FS IO Reads: {}, FS IO Writes: {}".format(\ + storage_device, dynamic_kvp_dict["firmware"], dynamic_kvp_dict["health"], dynamic_kvp_dict["temperature"], dynamic_kvp_dict["fs_io_reads"],dynamic_kvp_dict["fs_io_writes"],)) self.log_info("Disk IO Reads: {}, Disk IO Writes: {}, Reserved Blocks: {}".format(dynamic_kvp_dict["disk_io_reads"], dynamic_kvp_dict["disk_io_writes"], \ dynamic_kvp_dict["reserved_blocks"])) diff --git a/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_ssd/__init__.py b/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_storage/__init__.py similarity index 100% rename from sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_ssd/__init__.py rename to sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_storage/__init__.py diff --git a/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_generic.py b/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_storage/ssd.py similarity index 97% rename from sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_generic.py rename to sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_storage/ssd.py index 9cf829248..a157aab81 100644 --- a/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_ssd/ssd_generic.py +++ b/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_storage/ssd.py @@ -10,7 +10,7 @@ try: import re import subprocess - from .ssd_base import SsdBase + from .storage_base import StorageBase except ImportError as e: raise ImportError (str(e) + "- required module not found") @@ -26,7 +26,7 @@ SWISSBIT_HEALTH_ID = 248 SWISSBIT_TEMPERATURE_ID = 194 -class SsdUtil(SsdBase): +class SsdUtil(StorageBase): """ Generic implementation of the SSD health API """ diff --git a/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_ssd/storage_base.py b/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_storage/storage_base.py similarity index 100% rename from sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_ssd/storage_base.py rename to sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_storage/storage_base.py From 08d5ecd3c46b7295b41b3d10c73a02281d167a3d Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Sat, 11 May 2024 07:33:53 +0000 Subject: [PATCH 10/31] Made core algorithm changes per HLD --- sonic-stormond/pytest.ini | 2 + sonic-stormond/scripts/stormond | 381 ++++++++++++++++++ sonic-stormond/setup.cfg | 2 + sonic-stormond/setup.py | 43 ++ sonic-stormond/tests/__init__.py | 0 sonic-stormond/tests/mock_platform.py | 15 + .../mocked_libs/sonic_platform/__init__.py | 6 + .../tests/mocked_libs/sonic_platform/ssd.py | 13 + .../sonic_platform_base/__init__.py | 1 + .../sonic_storage/__init__.py | 0 .../sonic_platform_base/sonic_storage/ssd.py | 127 ++++++ .../sonic_storage/storage_base.py | 122 ++++++ .../tests/mocked_libs/swsscommon/__init__.py | 5 + .../mocked_libs/swsscommon/swsscommon.py | 66 +++ sonic-stormond/tests/test_DaemonStorage.py | 40 ++ sonic-stormond/tests/test_StorageDevice.py | 33 ++ 16 files changed, 856 insertions(+) create mode 100644 sonic-stormond/pytest.ini create mode 100755 sonic-stormond/scripts/stormond create mode 100644 sonic-stormond/setup.cfg create mode 100644 sonic-stormond/setup.py create mode 100644 sonic-stormond/tests/__init__.py create mode 100644 sonic-stormond/tests/mock_platform.py create mode 100644 sonic-stormond/tests/mocked_libs/sonic_platform/__init__.py create mode 100644 sonic-stormond/tests/mocked_libs/sonic_platform/ssd.py create mode 100644 sonic-stormond/tests/mocked_libs/sonic_platform_base/__init__.py create mode 100644 sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/__init__.py create mode 100644 sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/ssd.py create mode 100644 sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/storage_base.py create mode 100644 sonic-stormond/tests/mocked_libs/swsscommon/__init__.py create mode 100644 sonic-stormond/tests/mocked_libs/swsscommon/swsscommon.py create mode 100644 sonic-stormond/tests/test_DaemonStorage.py create mode 100644 sonic-stormond/tests/test_StorageDevice.py diff --git a/sonic-stormond/pytest.ini b/sonic-stormond/pytest.ini new file mode 100644 index 000000000..d90ee9ed9 --- /dev/null +++ b/sonic-stormond/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = --cov=scripts --cov-report html --cov-report term --cov-report xml --junitxml=test-results.xml -vv diff --git a/sonic-stormond/scripts/stormond b/sonic-stormond/scripts/stormond new file mode 100755 index 000000000..3adfc5c77 --- /dev/null +++ b/sonic-stormond/scripts/stormond @@ -0,0 +1,381 @@ +#!/usr/bin/env python3 + +""" + stormond + Storage device Monitoring daemon for SONiC +""" + +import os +import signal +import sys +import threading +import subprocess +import shutil +import json +import time + +from sonic_py_common import daemon_base, device_info, logger +from swsscommon import swsscommon +from sonic_platform_base.sonic_storage.storage_devices import StorageDevices + +# +# Constants ==================================================================== +# + +# TODO: Once we no longer support Python 2, we can eliminate this and get the +# name using the 'name' field (e.g., `signal.SIGINT.name`) starting with Python 3.5 +SIGNALS_TO_NAMES_DICT = dict((getattr(signal, n), n) + for n in dir(signal) if n.startswith('SIG') and '_' not in n) + +SYSLOG_IDENTIFIER = "stormond" + +STORAGE_DEVICE_TABLE = "STORAGE_INFO" +FSSTATS_SYNC_TIME_KEY = "FSSTATS_SYNC" + +FSIO_RW_JSON_FILE = "/usr/share/stormond/fsio-rw-stats.json" + +STORMOND_MAIN_THREAD_SLEEP_SECS = 3600 #one hour +STORMOND_SYNC_TO_DISK_SECS = 86400 #one day + +STORAGEUTIL_LOAD_ERROR = 127 + +log = logger.Logger(SYSLOG_IDENTIFIER) + +exit_code = 0 + +# +# Daemon ======================================================================= +# + + +class DaemonStorage(daemon_base.DaemonBase): + + def __init__(self, log_identifier): + super(DaemonStorage, self).__init__(log_identifier) + + self.timeout = STORMOND_MAIN_THREAD_SLEEP_SECS + self.fsstats_sync_interval = STORMOND_SYNC_TO_DISK_SECS + self.stop_event = threading.Event() + self.state_db = None + self.config_db = None + self.device_table = None + self.storage = StorageDevices(log_identifier) + + # These booleans are for FSIO RW information reconciliation + self.fsio_json_file_loaded = False + self.use_fsio_json_baseline = False + self.statedb_storage_info_loaded = False + self.use_statedb_baseline = False + + # These dicts are to load info from disk/database into memory, respectively + self.fsio_rw_json = dict((disk, {}) for disk in self.storage.devices) + self.fsio_rw_statedb = dict((disk, {}) for disk in self.storage.devices) + + # This time is set at init and then subsequently after each FSIO JSON file sync + self.fsio_sync_time = time.time() + + # these are the config timeout values we get from the config_db: + self.config_fields = ["daemon_polling_interval" , "fsstats_sync_interval"] + + # These are the various static and dynamic fields that are posted to state_db + self.static_fields = ["device_model", "serial"] + self.dynamic_fields = ["firmware", \ + "health", \ + "temperature", \ + "latest_fsio_reads", \ + "latest_fsio_writes", \ + "total_fsio_reads", \ + "total_fsio_writes", \ + "disk_io_reads", \ + "disk_io_writes", \ + "reserved_blocks"] + + # These are the fields that we are interested in saving to disk to protect against + # reboots or crashes + self.statedb_json_sync_fields = self.dynamic_fields[3:7] + + # Connect to CONFIG_DB and get polling and sync intervals + self._get_configdb_intervals() + + # Connect to STATE_DB and create Storage device table + self.state_db = daemon_base.db_connect("STATE_DB") + self.device_table = swsscommon.Table(self.state_db, STORAGE_DEVICE_TABLE) + + # Load the FSIO RW values from state_db and JSON file and reconcile latest information + self._load_fsio_rw_statedb() + self._load_fsio_rw_json() + self._determine_sot() + + def _get_configdb_intervals(self): + self.config_db = daemon_base.db_connect("CONFIG_DB") + config_info = dict((field, None) for field in self.config_fields) + + for field in self.config_fields: + config_info[field] = self.config_db.hget('STORMOND_CONFIG|INTERVALS', field) + + if config_info[self.config_fields[0]] != None: + self.timeout = int(config_info[self.config_fields[0]]) + if config_info[self.config_fields[1]] != None: + self.fsstats_sync_interval = int(config_info[self.config_fields[1]]) + + self.log_info("Polling Interval set to {} seconds".format(self.timeout)) + self.log_info("FSIO JSON file Interval set to {} seconds".format(self.fsstats_sync_interval)) + + + # Get the total and latest FSIO reads and writes from JSON file + def _load_fsio_rw_json(self): + try: + if not os.path.exists(FSIO_RW_JSON_FILE): + self.log_info("{} not present.".format(FSIO_RW_JSON_FILE)) + return + + with open(FSIO_RW_JSON_FILE, 'r') as f: + self.fsio_rw_json = json.load(f) + self.fsio_json_file_loaded = True + + except Exception as e: + self.log_info("JSON file could not be loaded: {}".format(e)) + + return + + + # Sync the total and latest procfs reads and writes from STATE_DB to JSON file on disk + def _sync_fsio_rw_json(self): + + self.log_info("Syncing total and latest procfs reads and writes from STATE_DB to JSON file") + try: + json_file_dict = dict((disk, {}) for disk in self.storage.devices) + for device in self.storage.devices: + for field in self.statedb_json_sync_fields: + json_file_dict[device][field] = self.state_db.hget('STORAGE_INFO|{}'.format(device), field) + + self.fsio_sync_time = time.time() + json_file_dict["successful_sync_time"] = str(self.fsio_sync_time) + + with open(FSIO_RW_JSON_FILE, 'w+') as f: + f.write(json.dumps(json_file_dict)) + self.state_db.hset("{}|{}".format(STORAGE_DEVICE_TABLE,FSSTATS_SYNC_TIME_KEY), "successful_sync_time", str(self.fsio_sync_time)) + + except Exception as ex: + self.log_info("Unable to sync state_db to disk: {}".format(ex)) + + # Run a sanity check ont he state_db. If successful, get total, latest + # FSIO reads and writes for each storage device from STATE_DB + def _load_fsio_rw_statedb(self): + + # Sanity Check: + + # If the number of STORAGE_INFO|* keys does not equal the + # number of storage disks on the device + FSSTATS_SYNC field, + # there has been a corruption to the database. In this case we + # pivot to the JSON file being the Source of Truth. + try: + if (len(self.state_db.keys("STORAGE_INFO|*")) != (len(self.storage.devices) + 1)): + return + + # For each storage device on the switch, + for storage_device in self.storage.devices: + + # Get the total and latest procfs reads and writes from STATE_DB + for field in self.statedb_json_sync_fields: + self.fsio_rw_statedb[storage_device][field] = self.state_db.hget('STORAGE_INFO|{}'.format(storage_device), field) + + self.statedb_storage_info_loaded = True + except Exception as e: + self.log_info("Reading STATE_DB failed with: {}".format(e)) + + + def _determine_sot(self): + + # This daemon considers the storage information values held in the STATE_DB to be its + # Source of Truth. + + # If the state_db information did not load successfully but the JSON file did, + # we consider the JSON file to be the SoT. + + if self.statedb_storage_info_loaded == False and self.fsio_json_file_loaded == True: + self.use_fsio_json_baseline = True + self.use_statedb_baseline = False + + # If stormond is coming back up after a daemon crash, storage information would be saved in the + # STATE_DB. In that scenario, we use the STATE_DB information as the SoT and reconcile the FSIO + # reads and writes values. + elif self.statedb_storage_info_loaded == True: + self.use_fsio_json_baseline = False + self.use_statedb_baseline = True + + + def _reconcile_fsio_rw_values(self, fsio_dict, device): + + import pdb; pdb.set_trace() + + # If stormond is coming up for the first time, neither STATE_DB info nor JSON file would be present. + # In that case, neither resource would have any prior information stored. The baseline is 0 for every field. + if self.use_statedb_baseline == False and self.use_fsio_json_baseline == False: + fsio_dict["total_fsio_reads"] = fsio_dict["latest_fsio_reads"] + fsio_dict["total_fsio_writes"] = fsio_dict["latest_fsio_writes"] + + # If the daemon is re-init-ing after a planned reboot or powercycle, there would be no storage info + # in the STATE_DB. Therefore, we would need to parse the total and hitherto latest procfs reads + # and writes from the FSIO JSON file and use those reads/writes values as a baseline. + elif self.use_statedb_baseline == False and self.use_fsio_json_baseline == True: + fsio_dict["total_fsio_reads"] = str(int(self.fsio_rw_json[device]["total_fsio_reads"]) + int(fsio_dict["latest_fsio_reads"])) + fsio_dict["total_fsio_writes"] = str(int(self.fsio_rw_json[device]["total_fsio_writes"]) + int(fsio_dict["latest_fsio_writes"])) + + # The only scenario where there would be storage info present in the STATE_DB is when the daemon is + # coming back up after a crash. + + # In this scenario, we use the STATE_DB values as the SoT. We use the 'latest_fsio_reads/writes' + # values from STATE_DB, which is the values from the last invocation of get_fs_io_reads/writes + # on the storage disk that was posted to STATE_DB, and the values obtained from the most recent + # invocation of get_fs_io_reads/writes (prior to this function being called) to determine the + # additional procfs reads and writes that have happened on the FS while the daemon was down. + + # We then add these additional values to the previous values of total_fsio_reads/writes to + # determine the new total procfs reads/writes. + + elif self.use_statedb_baseline == True: + additional_procfs_reads = int(fsio_dict["latest_fsio_reads"]) - int(self.fsio_rw_statedb[device]["latest_fsio_reads"]) + additional_procfs_writes = int(fsio_dict["latest_fsio_writes"]) - int(self.fsio_rw_statedb[device]["latest_fsio_writes"]) + + fsio_dict["total_fsio_reads"] = str(int(self.fsio_rw_statedb[device]["total_fsio_reads"]) + additional_procfs_reads) + fsio_dict["total_fsio_writes"] = str(int(self.fsio_rw_statedb[device]["total_fsio_writes"]) + additional_procfs_writes) + + return fsio_dict["total_fsio_reads"], fsio_dict["total_fsio_writes"] + + + + # Update the Storage device info to State DB + def update_storage_info_status_db(self, disk_device, disk_fields, kvp_dict): + + for field in disk_fields: + fvp = swsscommon.FieldValuePairs([(field, str(kvp_dict[field]))]) + self.device_table.set(disk_device, fvp) + + + # Get Static attributes and update the State DB, once + def get_static_fields(self): + + # Get relevant information about each Storage Device on the switch + for storage_device in self.storage.devices: + try: + # Unlikely scenario + if self.storage.devices[storage_device] is None: + self.log_info("{} does not have an instantiated object. Static Information cannot be gathered.".format(storage_device)) + continue + + static_kvp_dict = dict((field, "") for field in self.static_fields) + + static_kvp_dict["device_model"] = self.storage.devices[storage_device].get_model() + static_kvp_dict["serial"] = self.storage.devices[storage_device].get_serial() + + self.log_info("Storage Device: {}, Device Model: {}, Serial: {}".format(storage_device, static_kvp_dict["device_model"], static_kvp_dict["serial"])) + + # update Storage Device Status to DB + self.update_storage_info_status_db(storage_device, self.static_fields, static_kvp_dict) + + except Exception as ex: + self.log_info("get_static_fields() failed with: {}".format(str(ex))) + + # Get Dynamic attributes and update the State DB + def get_dynamic_fields(self): + + # Get relevant information about each storage disk on the device + for storage_device in self.storage.devices: + try: + if self.storage.devices[storage_device] is None: + self.log_info("Storage device '{}' does not have an instantiated object. Dynamic Information cannot be gathered.".format(storage_device)) + continue + + dynamic_kvp_dict = dict((field, "") for field in self.dynamic_fields) + + dynamic_kvp_dict["firmware"] = self.storage.devices[storage_device].get_firmware() + dynamic_kvp_dict["health"] = self.storage.devices[storage_device].get_health() + dynamic_kvp_dict["temperature"] = self.storage.devices[storage_device].get_temperature() + dynamic_kvp_dict["latest_fsio_reads"] = self.storage.devices[storage_device].get_fs_io_reads() + dynamic_kvp_dict["latest_fsio_writes"] = self.storage.devices[storage_device].get_fs_io_writes() + dynamic_kvp_dict["disk_io_reads"] = self.storage.devices[storage_device].get_disk_io_reads() + dynamic_kvp_dict["disk_io_writes"] = self.storage.devices[storage_device].get_disk_io_writes() + dynamic_kvp_dict["reserved_blocks"] = self.storage.devices[storage_device].get_reserved_blocks() + + #import pdb; pdb.set_trace() + dynamic_kvp_dict["total_fsio_reads"], dynamic_kvp_dict["total_fsio_writes"] = self._reconcile_fsio_rw_values(dynamic_kvp_dict, storage_device) + + self.log_info("Storage Device: {}, Firmware: {}, health: {}%, Temp: {}C, FS IO Reads: {}, FS IO Writes: {}".format(\ + storage_device, dynamic_kvp_dict["firmware"], dynamic_kvp_dict["health"], dynamic_kvp_dict["temperature"], dynamic_kvp_dict["total_fsio_reads"],dynamic_kvp_dict["total_fsio_writes"])) + self.log_info("Latest FSIO Reads: {}, Latest FSIO Writes: {}".format(dynamic_kvp_dict["latest_fsio_reads"], dynamic_kvp_dict["latest_fsio_writes"])) + self.log_info("Disk IO Reads: {}, Disk IO Writes: {}, Reserved Blocks: {}".format(dynamic_kvp_dict["disk_io_reads"], dynamic_kvp_dict["disk_io_writes"], \ + dynamic_kvp_dict["reserved_blocks"])) + + # update Storage Device Status to DB + self.update_storage_info_status_db(storage_device, self.dynamic_fields, dynamic_kvp_dict) + + except Exception as ex: + self.log_info("get_dynamic_fields() failed with: {}".format(str(ex))) + + + # Override signal handler from DaemonBase + def signal_handler(self, sig, frame): + FATAL_SIGNALS = [signal.SIGINT, signal.SIGTERM] + NONFATAL_SIGNALS = [signal.SIGHUP] + + global exit_code + + if sig in FATAL_SIGNALS: + self.log_info("Caught signal '{}'".format(SIGNALS_TO_NAMES_DICT[sig])) + + self.log_info("Syncing latest procfs reads and writes to disk") + self._sync_fsio_rw_json() + + self.log_info(" Exiting.") + + # Make sure we exit with a non-zero code so that supervisor will try to restart us + exit_code = 128 + sig + self.stop_event.set() + elif sig in NONFATAL_SIGNALS: + self.log_info("Caught signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) + else: + self.log_info("Caught unhandled signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) + + # Main daemon logic + def run(self): + + if self.stop_event.wait(self.timeout): + # We received a fatal signal + return False + + # Check if time elapsed since init is > fsstats_sync_interval OR + # If sync interval has elapsed or if difference in elapsed_time and sync interval is less than polling interval + + # If so, sync the appropriate fields to FSIO JSON file + + elapsed_time = time.time() - self.fsio_sync_time + if (elapsed_time > self.fsstats_sync_interval) or ((self.fsstats_sync_interval - elapsed_time) < self.timeout): + self._sync_fsio_rw_json() + + # Repeatedly read and update Dynamic Fields to the StateDB + self.get_dynamic_fields() + + return True +# +# Main ========================================================================= +# + + +def main(): + stormon = DaemonStorage(SYSLOG_IDENTIFIER) + + stormon.log_info("Starting Storage Monitoring Daemon") + + # Read and update Static Fields to the StateDB once + stormon.get_static_fields() + + while stormon.run(): + pass + + stormon.log_info("Shutting down Storage Monitoring Daemon") + + return exit_code + +if __name__ == '__main__': + sys.exit(main()) diff --git a/sonic-stormond/setup.cfg b/sonic-stormond/setup.cfg new file mode 100644 index 000000000..b7e478982 --- /dev/null +++ b/sonic-stormond/setup.cfg @@ -0,0 +1,2 @@ +[aliases] +test=pytest diff --git a/sonic-stormond/setup.py b/sonic-stormond/setup.py new file mode 100644 index 000000000..5ea252bca --- /dev/null +++ b/sonic-stormond/setup.py @@ -0,0 +1,43 @@ +from setuptools import setup + +setup( + name='sonic-stormond', + version='1.0', + description='Storage Device Monitoring Daemon for SONiC', + license='Apache 2.0', + author='SONiC Team', + author_email='linuxnetdev@microsoft.com', + url='https://github.com/sonic-net/sonic-platform-daemons', + maintainer='Ashwin Srinivasan', + maintainer_email='assrinivasan@microsoft.com', + scripts=[ + 'scripts/stormond', + ], + setup_requires=[ + 'pytest-runner', + 'wheel' + ], + install_requires=[ + 'enum34; python_version < "3.4"', + 'sonic-py-common', + ], + tests_require=[ + 'mock>=2.0.0; python_version < "3.3"', + 'pytest', + 'pytest-cov', + ], + classifiers=[ + 'Development Status :: 4 - Beta', + 'Environment :: No Input/Output (Daemon)', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'Intended Audience :: System Administrators', + 'License :: OSI Approved :: Apache Software License', + 'Natural Language :: English', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python :: 2.7', + 'Topic :: System :: Hardware', + ], + keywords='sonic SONiC ssd Ssd SSD ssdmond storage stormond storagemond', + test_suite='setup.get_test_suite' +) diff --git a/sonic-stormond/tests/__init__.py b/sonic-stormond/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sonic-stormond/tests/mock_platform.py b/sonic-stormond/tests/mock_platform.py new file mode 100644 index 000000000..a5b3070bf --- /dev/null +++ b/sonic-stormond/tests/mock_platform.py @@ -0,0 +1,15 @@ + +""" + Mock implementation of sonic_platform package for unit testing +""" + +# TODO: Clean this up once we no longer need to support Python 2 +import sys +if sys.version_info.major == 3: + from unittest import mock +else: + import mock + +class MockStorageDevice(): + def __init__(self): + super(MockStorageDevice, self).__init__() \ No newline at end of file diff --git a/sonic-stormond/tests/mocked_libs/sonic_platform/__init__.py b/sonic-stormond/tests/mocked_libs/sonic_platform/__init__.py new file mode 100644 index 000000000..136bb0dcc --- /dev/null +++ b/sonic-stormond/tests/mocked_libs/sonic_platform/__init__.py @@ -0,0 +1,6 @@ +""" + Mock implementation of sonic_platform package for unit testing +""" + +from . import ssd + diff --git a/sonic-stormond/tests/mocked_libs/sonic_platform/ssd.py b/sonic-stormond/tests/mocked_libs/sonic_platform/ssd.py new file mode 100644 index 000000000..1fce3d024 --- /dev/null +++ b/sonic-stormond/tests/mocked_libs/sonic_platform/ssd.py @@ -0,0 +1,13 @@ +""" + Mock implementation of sonic_platform package for unit testing +""" + +from sonic_platform_base.storage_base import StorageBase + + +class Storage(StorageBase): + def __init__(self): + self.platform_Storageutil = "/tmp/Storage" + + def __str__(self): + return self.platform_Storageutil diff --git a/sonic-stormond/tests/mocked_libs/sonic_platform_base/__init__.py b/sonic-stormond/tests/mocked_libs/sonic_platform_base/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/sonic-stormond/tests/mocked_libs/sonic_platform_base/__init__.py @@ -0,0 +1 @@ + diff --git a/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/__init__.py b/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/ssd.py b/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/ssd.py new file mode 100644 index 000000000..67be574cf --- /dev/null +++ b/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/ssd.py @@ -0,0 +1,127 @@ +# +# ssd.py +# +# Generic implementation of the SSD health API +# SSD models supported: +# - InnoDisk +# - StorFly +# - Virtium + +try: + import re + import subprocess + from .storage_base import StorageBase +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" + +# Set Vendor Specific IDs +INNODISK_HEALTH_ID = 169 +INNODISK_TEMPERATURE_ID = 194 +SWISSBIT_HEALTH_ID = 248 +SWISSBIT_TEMPERATURE_ID = 194 + +class SsdUtil(StorageBase): + """ + Generic implementation of the SSD health API + """ + + def __init__(self, diskdev): + model = 'InnoDisk Corp. - mSATA 3IE3' + serial = 'BCA11712190600251' + firmware = 'S16425cG' + temperature = 32.3 + health = 91.6 + ssd_info = NOT_AVAILABLE + vendor_ssd_info = NOT_AVAILABLE + io_reads = 20000 + io_writes = 20005 + reserved_blocks = 3746218 + + 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 get_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.io_writes + + def get_io_reads(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.io_reads + + def get_reserves_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 diff --git a/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/storage_base.py b/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/storage_base.py new file mode 100644 index 000000000..2b8ca0aa0 --- /dev/null +++ b/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/storage_base.py @@ -0,0 +1,122 @@ +# +# storage_base.py +# +# Abstract base class for implementing platform-specific +# Storage information gathering functionality for SONiC +# + +try: + import abc +except ImportError as e: + raise ImportError(str(e) + " - required module not found") + +# +# storage_base.py +# +# Base class for implementing common Storage Device health features +# + + +class StorageBase(object): + """ + Base class for interfacing with a SSD + """ + def __init__(self, diskdev): + """ + Constructor + + Args: + diskdev: Linux device name to get parameters for + """ + pass + + @abc.abstractmethod + def get_health(self): + """ + Retrieves current disk health in percentages + + Returns: + A float number of current ssd health + e.g. 83.5 + """ + return 91.6 + + @abc.abstractmethod + def get_temperature(self): + """ + Retrieves current disk temperature in Celsius + + Returns: + A float number of current temperature in Celsius + e.g. 40.1 + """ + return 32.3 + + @abc.abstractmethod + def get_model(self): + """ + Retrieves model for the given disk device + + Returns: + A string holding disk model as provided by the manufacturer + """ + return '' + + @abc.abstractmethod + 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 '' + + @abc.abstractmethod + 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 '' + + @abc.abstractmethod + def get_vendor_output(self): + """ + Retrieves vendor specific data for the given disk device + + Returns: + A string holding some vendor specific disk information + """ + return '' + + def get_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 20000 + + @abc.abstractmethod + def get_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 20005 + + @abc.abstractmethod + def get_reserves_blocks(self): + """ + Retrieves the total number of reserved blocks in an SSD + + Returns: + An integer value of the total number of reserved blocks + """ + return 3746218 \ No newline at end of file diff --git a/sonic-stormond/tests/mocked_libs/swsscommon/__init__.py b/sonic-stormond/tests/mocked_libs/swsscommon/__init__.py new file mode 100644 index 000000000..012af621e --- /dev/null +++ b/sonic-stormond/tests/mocked_libs/swsscommon/__init__.py @@ -0,0 +1,5 @@ +''' + Mock implementation of swsscommon package for unit testing +''' + +from . import swsscommon diff --git a/sonic-stormond/tests/mocked_libs/swsscommon/swsscommon.py b/sonic-stormond/tests/mocked_libs/swsscommon/swsscommon.py new file mode 100644 index 000000000..ddb3cd686 --- /dev/null +++ b/sonic-stormond/tests/mocked_libs/swsscommon/swsscommon.py @@ -0,0 +1,66 @@ +''' + Mock implementation of swsscommon package for unit testing +''' + +STATE_DB = '' + + +class Table: + def __init__(self, db, table_name): + self.table_name = table_name + self.mock_dict = {} + + def _del(self, key): + del self.mock_dict[key] + pass + + def set(self, key, fvs): + self.mock_dict[key] = fvs.fv_dict + pass + + def get(self, key): + if key in self.mock_dict: + return self.mock_dict[key] + return None + + def get_size(self): + return (len(self.mock_dict)) + + def getKeys(self): + return list(self.mock_dict.keys()) + + +class FieldValuePairs: + fv_dict = {} + + def __init__(self, tuple_list): + if isinstance(tuple_list, list) and isinstance(tuple_list[0], tuple): + self.fv_dict = dict(tuple_list) + + def __setitem__(self, key, kv_tuple): + self.fv_dict[kv_tuple[0]] = kv_tuple[1] + + def __getitem__(self, key): + return self.fv_dict[key] + + def __eq__(self, other): + if not isinstance(other, FieldValuePairs): + # don't attempt to compare against unrelated types + return NotImplemented + + return self.fv_dict == other.fv_dict + + def __repr__(self): + return repr(self.fv_dict) + + def __str__(self): + return repr(self.fv_dict) + +class ConfigDBConnector: + pass + +class SonicDBConfig: + pass + +class SonicV2Connector: + pass diff --git a/sonic-stormond/tests/test_DaemonStorage.py b/sonic-stormond/tests/test_DaemonStorage.py new file mode 100644 index 000000000..d10ebf666 --- /dev/null +++ b/sonic-stormond/tests/test_DaemonStorage.py @@ -0,0 +1,40 @@ +import datetime +import os +import sys +from imp import load_source # Replace with importlib once we no longer need to support Python 2 + +import pytest + +# TODO: Clean this up once we no longer need to support Python 2 +if sys.version_info.major == 3: + from unittest import mock +else: + import mock + +from .mock_platform import MockStorageDevice + +SYSLOG_IDENTIFIER = 'storage_daemon_test' +NOT_AVAILABLE = 'N/A' + + +tests_path = os.path.dirname(os.path.abspath(__file__)) + +# Add mocked_libs path so that the file under test can load mocked modules from there +mocked_libs_path = os.path.join(tests_path, "mocked_libs") +sys.path.insert(0, mocked_libs_path) + +from sonic_py_common import daemon_base +daemon_base.db_connect = mock.MagicMock() + +# Add path to the file under test so that we can load it +modules_path = os.path.dirname(tests_path) +scripts_path = os.path.join(modules_path, "scripts") +sys.path.insert(0, modules_path) +load_source('stormond', os.path.join(scripts_path, 'stormond')) +import stormond + + +class TestDaemonStorage(object): + """ + Test cases to cover functionality in DaemonStorage class + """ diff --git a/sonic-stormond/tests/test_StorageDevice.py b/sonic-stormond/tests/test_StorageDevice.py new file mode 100644 index 000000000..972132ddc --- /dev/null +++ b/sonic-stormond/tests/test_StorageDevice.py @@ -0,0 +1,33 @@ +import os +import sys +from imp import load_source # Replace with importlib once we no longer need to support Python 2 + +import pytest + +# TODO: Clean this up once we no longer need to support Python 2 +if sys.version_info >= (3, 3): + from unittest.mock import MagicMock, patch, mock_open +else: + from mock import MagicMock, patch, mock_open + +from .mock_platform import MockStorageDevice + +tests_path = os.path.dirname(os.path.abspath(__file__)) + +# Add mocked_libs path so that the file under test can load mocked modules from there +mocked_libs_path = os.path.join(tests_path, "mocked_libs") +sys.path.insert(0, mocked_libs_path) +from sonic_py_common import daemon_base, device_info + +# Add path to the file under test so that we can load it +modules_path = os.path.dirname(tests_path) +scripts_path = os.path.join(modules_path, "scripts") +sys.path.insert(0, modules_path) +load_source('stormond', os.path.join(scripts_path, 'stormond')) +import stormond + + +daemon_base.db_connect = MagicMock() + +SYSLOG_IDENTIFIER = 'storagedevice_test' +NOT_AVAILABLE = 'N/A' \ No newline at end of file From f64714967202d23fb5fddcae8047aec5bbc56f69 Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Wed, 15 May 2024 05:56:17 +0000 Subject: [PATCH 11/31] Renamed dir per sonic naming convention --- sonic-storagemond/pytest.ini | 2 - sonic-storagemond/scripts/storagemond | 189 ------------------ sonic-storagemond/setup.cfg | 2 - sonic-storagemond/setup.py | 43 ---- sonic-storagemond/tests/__init__.py | 0 sonic-storagemond/tests/mock_platform.py | 15 -- .../mocked_libs/sonic_platform/__init__.py | 6 - .../tests/mocked_libs/sonic_platform/ssd.py | 13 -- .../sonic_platform_base/__init__.py | 1 - .../sonic_storage/__init__.py | 0 .../sonic_platform_base/sonic_storage/ssd.py | 127 ------------ .../sonic_storage/storage_base.py | 122 ----------- .../tests/mocked_libs/swsscommon/__init__.py | 5 - .../mocked_libs/swsscommon/swsscommon.py | 66 ------ sonic-storagemond/tests/test_DaemonStorage.py | 40 ---- sonic-storagemond/tests/test_StorageDevice.py | 33 --- 16 files changed, 664 deletions(-) delete mode 100644 sonic-storagemond/pytest.ini delete mode 100644 sonic-storagemond/scripts/storagemond delete mode 100644 sonic-storagemond/setup.cfg delete mode 100644 sonic-storagemond/setup.py delete mode 100644 sonic-storagemond/tests/__init__.py delete mode 100644 sonic-storagemond/tests/mock_platform.py delete mode 100644 sonic-storagemond/tests/mocked_libs/sonic_platform/__init__.py delete mode 100644 sonic-storagemond/tests/mocked_libs/sonic_platform/ssd.py delete mode 100644 sonic-storagemond/tests/mocked_libs/sonic_platform_base/__init__.py delete mode 100644 sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_storage/__init__.py delete mode 100644 sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_storage/ssd.py delete mode 100644 sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_storage/storage_base.py delete mode 100644 sonic-storagemond/tests/mocked_libs/swsscommon/__init__.py delete mode 100644 sonic-storagemond/tests/mocked_libs/swsscommon/swsscommon.py delete mode 100644 sonic-storagemond/tests/test_DaemonStorage.py delete mode 100644 sonic-storagemond/tests/test_StorageDevice.py diff --git a/sonic-storagemond/pytest.ini b/sonic-storagemond/pytest.ini deleted file mode 100644 index d90ee9ed9..000000000 --- a/sonic-storagemond/pytest.ini +++ /dev/null @@ -1,2 +0,0 @@ -[pytest] -addopts = --cov=scripts --cov-report html --cov-report term --cov-report xml --junitxml=test-results.xml -vv diff --git a/sonic-storagemond/scripts/storagemond b/sonic-storagemond/scripts/storagemond deleted file mode 100644 index 29e9f4f41..000000000 --- a/sonic-storagemond/scripts/storagemond +++ /dev/null @@ -1,189 +0,0 @@ -#!/usr/bin/env python3 - -""" - storagemond - Strage Monitoring device monitoring daemon for SONiC -""" - -import os -import signal -import sys -import threading -import subprocess -import shutil - -from sonic_py_common import daemon_base, device_info, logger -from swsscommon import swsscommon -from sonic_platform_base.sonic_storage.storage_devices import StorageDevices - -# -# Constants ==================================================================== -# - -# TODO: Once we no longer support Python 2, we can eliminate this and get the -# name using the 'name' field (e.g., `signal.SIGINT.name`) starting with Python 3.5 -SIGNALS_TO_NAMES_DICT = dict((getattr(signal, n), n) - for n in dir(signal) if n.startswith('SIG') and '_' not in n) - -SYSLOG_IDENTIFIER = "storagemond" - -STORAGE_DEVICE_TABLE_NAME = "STORAGE_INFO" - -STORMOND_MAIN_THREAD_SLEEP_SECS = 3600 #one hour - -STORAGEUTIL_LOAD_ERROR = 127 - -log = logger.Logger(SYSLOG_IDENTIFIER) - -exit_code = 0 - -# -# Daemon ======================================================================= -# - - -class DaemonStorage(daemon_base.DaemonBase): - def __init__(self, log_identifier): - super(DaemonStorage, self).__init__(log_identifier) - - self.timeout = STORMOND_MAIN_THREAD_SLEEP_SECS - self.stop_event = threading.Event() - self.state_db = None - self.device_table = None - self.storage = StorageDevices(log_identifier) - - self.static_fields = ["device_model", "serial"] - self.dynamic_fields = ["firmware", "health", "temperature", "fs_io_reads", "fs_io_writes", "disk_io_reads", "disk_io_writes", "reserved_blocks"] - - self.read_static_fields = dict((disk, False) for disk in self.storage.devices) - self.updated_static_fields_statedb = dict((disk, False) for disk in self.storage.devices) - - # Connect to STATE_DB and create Storage device table - self.state_db = daemon_base.db_connect("STATE_DB") - self.device_table = swsscommon.Table(self.state_db, STORAGE_DEVICE_TABLE_NAME) - - def __del__(self): - if self.device_table: - table_keys = self.device_table.getKeys() - for tk in table_keys: - self.device_table._del(tk) - - - # Update the Storage device info to State DB - def update_storage_info_status_db(self, disk_device, disk_fields, kvp_dict): - - for field in disk_fields: - fvp = swsscommon.FieldValuePairs([(field, str(kvp_dict[field]))]) - self.device_table.set(disk_device, fvp) - - - # Get Static attributes and update the State DB, once - def get_static_fields(self): - - # Get relevant information about each Storage Device on the switch - for storage_device in self.storage.devices: - try: - if self.storage.devices[storage_device] is None: - self.log_warning("{} does not have an instantiated object. Static Information cannot be gathered.".format(storage_device)) - continue - - if self.read_static_fields[storage_device] and self.updated_static_fields_statedb[storage_device]: - self.log_info("Static information from {} has already been parsed and updated to StateDB.".format(storage_device)) - continue - - static_kvp_dict = dict((field, "") for field in self.static_fields) - - static_kvp_dict["device_model"] = self.storage.devices[storage_device].get_model() - static_kvp_dict["serial"] = self.storage.devices[storage_device].get_serial() - - self.log_info("Storage Device: {}, Device Model: {}, Serial: {}".format(storage_device, static_kvp_dict["device_model"], static_kvp_dict["serial"])) - self.read_static_fields[storage_device] = True - - # update Storage Device Status to DB - self.update_storage_info_status_db(storage_device, self.static_fields, static_kvp_dict) - self.updated_static_fields_statedb[storage_device] = True - - except Exception as ex: - self.log_warning("get_static_fields() failed with: {}".format(str(ex))) - - # Get Dynamic attributes and update the State DB - def get_dynamic_fields(self): - - # Get relevant information about each storage disk on the device - for storage_device in self.storage.devices: - try: - if self.storage.devices[storage_device] is None: - self.log_warning("{} does not have an instantiated object. Dynamic Information cannot be gathered.".format(storage_device)) - continue - - dynamic_kvp_dict = dict((field, "") for field in self.dynamic_fields) - - dynamic_kvp_dict["firmware"] = self.storage.devices[storage_device].get_firmware() - dynamic_kvp_dict["health"] = self.storage.devices[storage_device].get_health() - dynamic_kvp_dict["temperature"] = self.storage.devices[storage_device].get_temperature() - dynamic_kvp_dict["fs_io_reads"] = self.storage.devices[storage_device].get_fs_io_reads() - dynamic_kvp_dict["fs_io_writes"] = self.storage.devices[storage_device].get_fs_io_writes() - dynamic_kvp_dict["disk_io_reads"] = self.storage.devices[storage_device].get_disk_io_reads() - dynamic_kvp_dict["disk_io_writes"] = self.storage.devices[storage_device].get_disk_io_writes() - dynamic_kvp_dict["reserved_blocks"] = self.storage.devices[storage_device].get_reserved_blocks() - - self.log_info("Storage Device: {}, Firmware: {}, health: {}%, Temp: {}C, FS IO Reads: {}, FS IO Writes: {}".format(\ - storage_device, dynamic_kvp_dict["firmware"], dynamic_kvp_dict["health"], dynamic_kvp_dict["temperature"], dynamic_kvp_dict["fs_io_reads"],dynamic_kvp_dict["fs_io_writes"],)) - self.log_info("Disk IO Reads: {}, Disk IO Writes: {}, Reserved Blocks: {}".format(dynamic_kvp_dict["disk_io_reads"], dynamic_kvp_dict["disk_io_writes"], \ - dynamic_kvp_dict["reserved_blocks"])) - - # update Storage Device Status to DB - self.update_storage_info_status_db(storage_device, self.dynamic_fields, dynamic_kvp_dict) - - except Exception as ex: - self.log_warning("get_dynamic_fields() failed with: {}".format(str(ex))) - - - # Override signal handler from DaemonBase - def signal_handler(self, sig, frame): - FATAL_SIGNALS = [signal.SIGINT, signal.SIGTERM] - NONFATAL_SIGNALS = [signal.SIGHUP] - - global exit_code - - if sig in FATAL_SIGNALS: - self.log_info("Caught signal '{}' - exiting...".format(SIGNALS_TO_NAMES_DICT[sig])) - exit_code = 128 + sig # Make sure we exit with a non-zero code so that supervisor will try to restart us - self.stop_event.set() - elif sig in NONFATAL_SIGNALS: - self.log_info("Caught signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) - else: - self.log_warning("Caught unhandled signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) - - # Main daemon logic - def run(self): - if self.stop_event.wait(self.timeout): - # We received a fatal signal - return False - - # Read and update Static Fields to the StateDB once - self.get_static_fields() - - # Repeatedly read and update Dynamic Fields to the StateDB - self.get_dynamic_fields() - - return True -# -# Main ========================================================================= -# - - -def main(): - stormon = DaemonStorage(SYSLOG_IDENTIFIER) - - stormon.log_info("Starting Storage Monitoring Daemon") - - while stormon.run(): - pass - - stormon.log_info("Shutting down Storage Monitoring Daemon") - - return exit_code - -if __name__ == '__main__': - sys.exit(main()) diff --git a/sonic-storagemond/setup.cfg b/sonic-storagemond/setup.cfg deleted file mode 100644 index b7e478982..000000000 --- a/sonic-storagemond/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[aliases] -test=pytest diff --git a/sonic-storagemond/setup.py b/sonic-storagemond/setup.py deleted file mode 100644 index a4eb129dc..000000000 --- a/sonic-storagemond/setup.py +++ /dev/null @@ -1,43 +0,0 @@ -from setuptools import setup - -setup( - name='sonic-stormond', - version='1.0', - description='Storage Device status daemon for SONiC', - license='Apache 2.0', - author='SONiC Team', - author_email='linuxnetdev@microsoft.com', - url='https://github.com/sonic-net/sonic-platform-daemons', - maintainer='Ashwin Srinivasan', - maintainer_email='assrinivasan@microsoft.com', - scripts=[ - 'scripts/storagemond', - ], - setup_requires=[ - 'pytest-runner', - 'wheel' - ], - install_requires=[ - 'enum34; python_version < "3.4"', - 'sonic-py-common', - ], - tests_require=[ - 'mock>=2.0.0; python_version < "3.3"', - 'pytest', - 'pytest-cov', - ], - classifiers=[ - 'Development Status :: 4 - Beta', - 'Environment :: No Input/Output (Daemon)', - 'Intended Audience :: Developers', - 'Intended Audience :: Information Technology', - 'Intended Audience :: System Administrators', - 'License :: OSI Approved :: Apache Software License', - 'Natural Language :: English', - 'Operating System :: POSIX :: Linux', - 'Programming Language :: Python :: 2.7', - 'Topic :: System :: Hardware', - ], - keywords='sonic SONiC ssd Ssd SSD ssdmond storage stormond storagemond', - test_suite='setup.get_test_suite' -) diff --git a/sonic-storagemond/tests/__init__.py b/sonic-storagemond/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/sonic-storagemond/tests/mock_platform.py b/sonic-storagemond/tests/mock_platform.py deleted file mode 100644 index a5b3070bf..000000000 --- a/sonic-storagemond/tests/mock_platform.py +++ /dev/null @@ -1,15 +0,0 @@ - -""" - Mock implementation of sonic_platform package for unit testing -""" - -# TODO: Clean this up once we no longer need to support Python 2 -import sys -if sys.version_info.major == 3: - from unittest import mock -else: - import mock - -class MockStorageDevice(): - def __init__(self): - super(MockStorageDevice, self).__init__() \ No newline at end of file diff --git a/sonic-storagemond/tests/mocked_libs/sonic_platform/__init__.py b/sonic-storagemond/tests/mocked_libs/sonic_platform/__init__.py deleted file mode 100644 index 136bb0dcc..000000000 --- a/sonic-storagemond/tests/mocked_libs/sonic_platform/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -""" - Mock implementation of sonic_platform package for unit testing -""" - -from . import ssd - diff --git a/sonic-storagemond/tests/mocked_libs/sonic_platform/ssd.py b/sonic-storagemond/tests/mocked_libs/sonic_platform/ssd.py deleted file mode 100644 index 572f190ef..000000000 --- a/sonic-storagemond/tests/mocked_libs/sonic_platform/ssd.py +++ /dev/null @@ -1,13 +0,0 @@ -""" - Mock implementation of sonic_platform package for unit testing -""" - -from sonic_platform_base.ssd_base import SsdBase - - -class Ssd(SsdBase): - def __init__(self): - self.platform_ssdutil = "/tmp/Ssd" - - def __str__(self): - return self.platform_ssdutil diff --git a/sonic-storagemond/tests/mocked_libs/sonic_platform_base/__init__.py b/sonic-storagemond/tests/mocked_libs/sonic_platform_base/__init__.py deleted file mode 100644 index 8b1378917..000000000 --- a/sonic-storagemond/tests/mocked_libs/sonic_platform_base/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_storage/__init__.py b/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_storage/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_storage/ssd.py b/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_storage/ssd.py deleted file mode 100644 index a157aab81..000000000 --- a/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_storage/ssd.py +++ /dev/null @@ -1,127 +0,0 @@ -# -# ssd_generic.py -# -# Generic implementation of the SSD health API -# SSD models supported: -# - InnoDisk -# - StorFly -# - Virtium - -try: - import re - import subprocess - from .storage_base import StorageBase -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" - -# Set Vendor Specific IDs -INNODISK_HEALTH_ID = 169 -INNODISK_TEMPERATURE_ID = 194 -SWISSBIT_HEALTH_ID = 248 -SWISSBIT_TEMPERATURE_ID = 194 - -class SsdUtil(StorageBase): - """ - Generic implementation of the SSD health API - """ - - def __init__(self, diskdev): - model = 'InnoDisk Corp. - mSATA 3IE3' - serial = 'BCA11712190600251' - firmware = 'S16425cG' - temperature = 32.3 - health = 91.6 - ssd_info = NOT_AVAILABLE - vendor_ssd_info = NOT_AVAILABLE - io_reads = 20000 - io_writes = 20005 - reserved_blocks = 3746218 - - 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 get_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.io_writes - - def get_io_reads(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.io_reads - - def get_reserves_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 diff --git a/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_storage/storage_base.py b/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_storage/storage_base.py deleted file mode 100644 index 380a7a54a..000000000 --- a/sonic-storagemond/tests/mocked_libs/sonic_platform_base/sonic_storage/storage_base.py +++ /dev/null @@ -1,122 +0,0 @@ -# -# storage_base.py -# -# Abstract base class for implementing platform-specific -# Storage information gathering functionality for SONiC -# - -try: - import abc -except ImportError as e: - raise ImportError(str(e) + " - required module not found") - -# -# storage_base.py -# -# Base class for implementing common Storage Device 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 - - @abc.abstractmethod - def get_health(self): - """ - Retrieves current disk health in percentages - - Returns: - A float number of current ssd health - e.g. 83.5 - """ - return 91.6 - - @abc.abstractmethod - def get_temperature(self): - """ - Retrieves current disk temperature in Celsius - - Returns: - A float number of current temperature in Celsius - e.g. 40.1 - """ - return 32.3 - - @abc.abstractmethod - def get_model(self): - """ - Retrieves model for the given disk device - - Returns: - A string holding disk model as provided by the manufacturer - """ - return '' - - @abc.abstractmethod - 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 '' - - @abc.abstractmethod - 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 '' - - @abc.abstractmethod - def get_vendor_output(self): - """ - Retrieves vendor specific data for the given disk device - - Returns: - A string holding some vendor specific disk information - """ - return '' - - def get_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 20000 - - @abc.abstractmethod - def get_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 20005 - - @abc.abstractmethod - def get_reserves_blocks(self): - """ - Retrieves the total number of reserved blocks in an SSD - - Returns: - An integer value of the total number of reserved blocks - """ - return 3746218 \ No newline at end of file diff --git a/sonic-storagemond/tests/mocked_libs/swsscommon/__init__.py b/sonic-storagemond/tests/mocked_libs/swsscommon/__init__.py deleted file mode 100644 index 012af621e..000000000 --- a/sonic-storagemond/tests/mocked_libs/swsscommon/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -''' - Mock implementation of swsscommon package for unit testing -''' - -from . import swsscommon diff --git a/sonic-storagemond/tests/mocked_libs/swsscommon/swsscommon.py b/sonic-storagemond/tests/mocked_libs/swsscommon/swsscommon.py deleted file mode 100644 index ddb3cd686..000000000 --- a/sonic-storagemond/tests/mocked_libs/swsscommon/swsscommon.py +++ /dev/null @@ -1,66 +0,0 @@ -''' - Mock implementation of swsscommon package for unit testing -''' - -STATE_DB = '' - - -class Table: - def __init__(self, db, table_name): - self.table_name = table_name - self.mock_dict = {} - - def _del(self, key): - del self.mock_dict[key] - pass - - def set(self, key, fvs): - self.mock_dict[key] = fvs.fv_dict - pass - - def get(self, key): - if key in self.mock_dict: - return self.mock_dict[key] - return None - - def get_size(self): - return (len(self.mock_dict)) - - def getKeys(self): - return list(self.mock_dict.keys()) - - -class FieldValuePairs: - fv_dict = {} - - def __init__(self, tuple_list): - if isinstance(tuple_list, list) and isinstance(tuple_list[0], tuple): - self.fv_dict = dict(tuple_list) - - def __setitem__(self, key, kv_tuple): - self.fv_dict[kv_tuple[0]] = kv_tuple[1] - - def __getitem__(self, key): - return self.fv_dict[key] - - def __eq__(self, other): - if not isinstance(other, FieldValuePairs): - # don't attempt to compare against unrelated types - return NotImplemented - - return self.fv_dict == other.fv_dict - - def __repr__(self): - return repr(self.fv_dict) - - def __str__(self): - return repr(self.fv_dict) - -class ConfigDBConnector: - pass - -class SonicDBConfig: - pass - -class SonicV2Connector: - pass diff --git a/sonic-storagemond/tests/test_DaemonStorage.py b/sonic-storagemond/tests/test_DaemonStorage.py deleted file mode 100644 index b64fd536f..000000000 --- a/sonic-storagemond/tests/test_DaemonStorage.py +++ /dev/null @@ -1,40 +0,0 @@ -import datetime -import os -import sys -from imp import load_source # Replace with importlib once we no longer need to support Python 2 - -import pytest - -# TODO: Clean this up once we no longer need to support Python 2 -if sys.version_info.major == 3: - from unittest import mock -else: - import mock - -from .mock_platform import MockStorageDevice - -SYSLOG_IDENTIFIER = 'storage_daemon_test' -NOT_AVAILABLE = 'N/A' - - -tests_path = os.path.dirname(os.path.abspath(__file__)) - -# Add mocked_libs path so that the file under test can load mocked modules from there -mocked_libs_path = os.path.join(tests_path, "mocked_libs") -sys.path.insert(0, mocked_libs_path) - -from sonic_py_common import daemon_base -daemon_base.db_connect = mock.MagicMock() - -# Add path to the file under test so that we can load it -modules_path = os.path.dirname(tests_path) -scripts_path = os.path.join(modules_path, "scripts") -sys.path.insert(0, modules_path) -load_source('storagemond', os.path.join(scripts_path, 'storagemond')) -import storagemond - - -class TestDaemonStorage(object): - """ - Test cases to cover functionality in DaemonStorage class - """ diff --git a/sonic-storagemond/tests/test_StorageDevice.py b/sonic-storagemond/tests/test_StorageDevice.py deleted file mode 100644 index 21f589d07..000000000 --- a/sonic-storagemond/tests/test_StorageDevice.py +++ /dev/null @@ -1,33 +0,0 @@ -import os -import sys -from imp import load_source # Replace with importlib once we no longer need to support Python 2 - -import pytest - -# TODO: Clean this up once we no longer need to support Python 2 -if sys.version_info >= (3, 3): - from unittest.mock import MagicMock, patch, mock_open -else: - from mock import MagicMock, patch, mock_open - -from .mock_platform import MockStorageDevice - -tests_path = os.path.dirname(os.path.abspath(__file__)) - -# Add mocked_libs path so that the file under test can load mocked modules from there -mocked_libs_path = os.path.join(tests_path, "mocked_libs") -sys.path.insert(0, mocked_libs_path) -from sonic_py_common import daemon_base, device_info - -# Add path to the file under test so that we can load it -modules_path = os.path.dirname(tests_path) -scripts_path = os.path.join(modules_path, "scripts") -sys.path.insert(0, modules_path) -load_source('storagemond', os.path.join(scripts_path, 'storagemond')) -import storagemond - - -daemon_base.db_connect = MagicMock() - -SYSLOG_IDENTIFIER = 'storagedevice_test' -NOT_AVAILABLE = 'N/A' \ No newline at end of file From a555cd43c8559fe9935463616afe33275671b9e7 Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Wed, 15 May 2024 22:41:57 +0000 Subject: [PATCH 12/31] Removed a rogue debugging statuement --- sonic-stormond/scripts/stormond | 2 -- 1 file changed, 2 deletions(-) diff --git a/sonic-stormond/scripts/stormond b/sonic-stormond/scripts/stormond index 3adfc5c77..6e59e939f 100755 --- a/sonic-stormond/scripts/stormond +++ b/sonic-stormond/scripts/stormond @@ -207,8 +207,6 @@ class DaemonStorage(daemon_base.DaemonBase): def _reconcile_fsio_rw_values(self, fsio_dict, device): - import pdb; pdb.set_trace() - # If stormond is coming up for the first time, neither STATE_DB info nor JSON file would be present. # In that case, neither resource would have any prior information stored. The baseline is 0 for every field. if self.use_statedb_baseline == False and self.use_fsio_json_baseline == False: From 898f876303a05c561c53c48eb628ea558a1f978d Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Wed, 22 May 2024 10:13:30 +0000 Subject: [PATCH 13/31] Made changes per review comments --- sonic-stormond/scripts/stormond | 90 ++++++++++++++++----------------- 1 file changed, 43 insertions(+), 47 deletions(-) diff --git a/sonic-stormond/scripts/stormond b/sonic-stormond/scripts/stormond index 6e59e939f..45c9d1dce 100755 --- a/sonic-stormond/scripts/stormond +++ b/sonic-stormond/scripts/stormond @@ -16,7 +16,7 @@ import time from sonic_py_common import daemon_base, device_info, logger from swsscommon import swsscommon -from sonic_platform_base.sonic_storage.storage_devices import StorageDevices +from sonic_platform_base.sonic_storage.storage_devices import StorageDevices, BLKDEV_BASE_PATH # # Constants ==================================================================== @@ -59,7 +59,7 @@ class DaemonStorage(daemon_base.DaemonBase): self.state_db = None self.config_db = None self.device_table = None - self.storage = StorageDevices(log_identifier) + self.storage = StorageDevices() # These booleans are for FSIO RW information reconciliation self.fsio_json_file_loaded = False @@ -68,14 +68,11 @@ class DaemonStorage(daemon_base.DaemonBase): self.use_statedb_baseline = False # These dicts are to load info from disk/database into memory, respectively - self.fsio_rw_json = dict((disk, {}) for disk in self.storage.devices) - self.fsio_rw_statedb = dict((disk, {}) for disk in self.storage.devices) + self.fsio_rw_json = {disk:{} for disk in self.storage.devices} + self.fsio_rw_statedb = {disk:{} for disk in self.storage.devices} # This time is set at init and then subsequently after each FSIO JSON file sync self.fsio_sync_time = time.time() - - # these are the config timeout values we get from the config_db: - self.config_fields = ["daemon_polling_interval" , "fsstats_sync_interval"] # These are the various static and dynamic fields that are posted to state_db self.static_fields = ["device_model", "serial"] @@ -94,9 +91,6 @@ class DaemonStorage(daemon_base.DaemonBase): # reboots or crashes self.statedb_json_sync_fields = self.dynamic_fields[3:7] - # Connect to CONFIG_DB and get polling and sync intervals - self._get_configdb_intervals() - # Connect to STATE_DB and create Storage device table self.state_db = daemon_base.db_connect("STATE_DB") self.device_table = swsscommon.Table(self.state_db, STORAGE_DEVICE_TABLE) @@ -108,15 +102,9 @@ class DaemonStorage(daemon_base.DaemonBase): def _get_configdb_intervals(self): self.config_db = daemon_base.db_connect("CONFIG_DB") - config_info = dict((field, None) for field in self.config_fields) - - for field in self.config_fields: - config_info[field] = self.config_db.hget('STORMOND_CONFIG|INTERVALS', field) - - if config_info[self.config_fields[0]] != None: - self.timeout = int(config_info[self.config_fields[0]]) - if config_info[self.config_fields[1]] != None: - self.fsstats_sync_interval = int(config_info[self.config_fields[1]]) + config_info = dict(self.config_db.hgetall('STORMOND_CONFIG|INTERVALS')) + self.timeout = int(config_info.get('daemon_polling_interval', STORMOND_MAIN_THREAD_SLEEP_SECS)) + self.fsstats_sync_interval = int(config_info.get('fsstats_sync_interval', STORMOND_SYNC_TO_DISK_SECS)) self.log_info("Polling Interval set to {} seconds".format(self.timeout)) self.log_info("FSIO JSON file Interval set to {} seconds".format(self.fsstats_sync_interval)) @@ -134,7 +122,7 @@ class DaemonStorage(daemon_base.DaemonBase): self.fsio_json_file_loaded = True except Exception as e: - self.log_info("JSON file could not be loaded: {}".format(e)) + self.log_error("JSON file could not be loaded: {}".format(e)) return @@ -143,8 +131,9 @@ class DaemonStorage(daemon_base.DaemonBase): def _sync_fsio_rw_json(self): self.log_info("Syncing total and latest procfs reads and writes from STATE_DB to JSON file") + + json_file_dict = {} try: - json_file_dict = dict((disk, {}) for disk in self.storage.devices) for device in self.storage.devices: for field in self.statedb_json_sync_fields: json_file_dict[device][field] = self.state_db.hget('STORAGE_INFO|{}'.format(device), field) @@ -153,13 +142,13 @@ class DaemonStorage(daemon_base.DaemonBase): json_file_dict["successful_sync_time"] = str(self.fsio_sync_time) with open(FSIO_RW_JSON_FILE, 'w+') as f: - f.write(json.dumps(json_file_dict)) + json.dump(json_file_dict, f) self.state_db.hset("{}|{}".format(STORAGE_DEVICE_TABLE,FSSTATS_SYNC_TIME_KEY), "successful_sync_time", str(self.fsio_sync_time)) except Exception as ex: - self.log_info("Unable to sync state_db to disk: {}".format(ex)) + self.log_error("Unable to sync state_db to disk: {}".format(ex)) - # Run a sanity check ont he state_db. If successful, get total, latest + # Run a sanity check on the state_db. If successful, get total, latest # FSIO reads and writes for each storage device from STATE_DB def _load_fsio_rw_statedb(self): @@ -182,7 +171,7 @@ class DaemonStorage(daemon_base.DaemonBase): self.statedb_storage_info_loaded = True except Exception as e: - self.log_info("Reading STATE_DB failed with: {}".format(e)) + self.log_error("Reading STATE_DB failed with: {}".format(e)) def _determine_sot(self): @@ -246,26 +235,25 @@ class DaemonStorage(daemon_base.DaemonBase): # Update the Storage device info to State DB def update_storage_info_status_db(self, disk_device, disk_fields, kvp_dict): - for field in disk_fields: - fvp = swsscommon.FieldValuePairs([(field, str(kvp_dict[field]))]) - self.device_table.set(disk_device, fvp) + fvp = swsscommon.FieldValuePairs([(field, str(value)) for field, value in kvp_dict.items()]) + self.device_table.set(disk_device, fvp) # Get Static attributes and update the State DB, once def get_static_fields(self): # Get relevant information about each Storage Device on the switch - for storage_device in self.storage.devices: + for storage_device, storage_object in self.storage.devices.items(): try: # Unlikely scenario - if self.storage.devices[storage_device] is None: + if storage_object is None: self.log_info("{} does not have an instantiated object. Static Information cannot be gathered.".format(storage_device)) continue - static_kvp_dict = dict((field, "") for field in self.static_fields) + static_kvp_dict = {} - static_kvp_dict["device_model"] = self.storage.devices[storage_device].get_model() - static_kvp_dict["serial"] = self.storage.devices[storage_device].get_serial() + static_kvp_dict["device_model"] = storage_object.get_model() + static_kvp_dict["serial"] = storage_object.get_serial() self.log_info("Storage Device: {}, Device Model: {}, Serial: {}".format(storage_device, static_kvp_dict["device_model"], static_kvp_dict["serial"])) @@ -273,30 +261,33 @@ class DaemonStorage(daemon_base.DaemonBase): self.update_storage_info_status_db(storage_device, self.static_fields, static_kvp_dict) except Exception as ex: - self.log_info("get_static_fields() failed with: {}".format(str(ex))) + self.log_error("get_static_fields() failed with: {}".format(str(ex))) # Get Dynamic attributes and update the State DB def get_dynamic_fields(self): # Get relevant information about each storage disk on the device - for storage_device in self.storage.devices: + for storage_device, storage_object in self.storage.devices.items(): try: - if self.storage.devices[storage_device] is None: - self.log_info("Storage device '{}' does not have an instantiated object. Dynamic Information cannot be gathered.".format(storage_device)) + if storage_object is None: + self.log_error("Storage device '{}' does not have an instantiated object. Dynamic Information cannot be gathered.".format(storage_device)) continue - dynamic_kvp_dict = dict((field, "") for field in self.dynamic_fields) + # Fetch the latest dynamic info + blkdevice = os.path.join(BLKDEV_BASE_PATH, storage_device) + storage_object._fetch_parse_info(blkdevice) + + dynamic_kvp_dict = {} - dynamic_kvp_dict["firmware"] = self.storage.devices[storage_device].get_firmware() - dynamic_kvp_dict["health"] = self.storage.devices[storage_device].get_health() - dynamic_kvp_dict["temperature"] = self.storage.devices[storage_device].get_temperature() - dynamic_kvp_dict["latest_fsio_reads"] = self.storage.devices[storage_device].get_fs_io_reads() - dynamic_kvp_dict["latest_fsio_writes"] = self.storage.devices[storage_device].get_fs_io_writes() - dynamic_kvp_dict["disk_io_reads"] = self.storage.devices[storage_device].get_disk_io_reads() - dynamic_kvp_dict["disk_io_writes"] = self.storage.devices[storage_device].get_disk_io_writes() - dynamic_kvp_dict["reserved_blocks"] = self.storage.devices[storage_device].get_reserved_blocks() + dynamic_kvp_dict["firmware"] = storage_object.get_firmware() + dynamic_kvp_dict["health"] = storage_object.get_health() + dynamic_kvp_dict["temperature"] = storage_object.get_temperature() + dynamic_kvp_dict["latest_fsio_reads"] = storage_object.get_fs_io_reads() + dynamic_kvp_dict["latest_fsio_writes"] = storage_object.get_fs_io_writes() + dynamic_kvp_dict["disk_io_reads"] = storage_object.get_disk_io_reads() + dynamic_kvp_dict["disk_io_writes"] = storage_object.get_disk_io_writes() + dynamic_kvp_dict["reserved_blocks"] = storage_object.get_reserved_blocks() - #import pdb; pdb.set_trace() dynamic_kvp_dict["total_fsio_reads"], dynamic_kvp_dict["total_fsio_writes"] = self._reconcile_fsio_rw_values(dynamic_kvp_dict, storage_device) self.log_info("Storage Device: {}, Firmware: {}, health: {}%, Temp: {}C, FS IO Reads: {}, FS IO Writes: {}".format(\ @@ -338,6 +329,10 @@ class DaemonStorage(daemon_base.DaemonBase): # Main daemon logic def run(self): + # Connect to CONFIG_DB and get polling and sync intervals -- + # this is to be able to dynamically configure the polling and sync times. + self._get_configdb_intervals() + if self.stop_event.wait(self.timeout): # We received a fatal signal return False @@ -351,6 +346,7 @@ class DaemonStorage(daemon_base.DaemonBase): if (elapsed_time > self.fsstats_sync_interval) or ((self.fsstats_sync_interval - elapsed_time) < self.timeout): self._sync_fsio_rw_json() + # Repeatedly read and update Dynamic Fields to the StateDB self.get_dynamic_fields() From 6ba0155e4947b866e4e8baac1e0e810587f3dc77 Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Wed, 22 May 2024 20:31:05 +0000 Subject: [PATCH 14/31] Added UTs, increased coverage --- sonic-stormond/tests/mock_platform.py | 15 --- sonic-stormond/tests/mock_swsscommon.py | 63 +++++++++ .../mocked_libs/sonic_platform/__init__.py | 2 +- .../tests/mocked_libs/sonic_platform/pcie.py | 13 ++ .../tests/mocked_libs/sonic_platform/ssd.py | 13 -- .../sonic_platform_base/sonic_storage/ssd.py | 127 ------------------ .../sonic_storage/storage_base.py | 52 +++---- .../sonic_storage/storage_devices.py | 12 ++ sonic-stormond/tests/test_DaemonStorage.py | 110 +++++++++++++-- sonic-stormond/tests/test_StorageDevice.py | 33 ----- 10 files changed, 201 insertions(+), 239 deletions(-) delete mode 100644 sonic-stormond/tests/mock_platform.py create mode 100644 sonic-stormond/tests/mock_swsscommon.py create mode 100644 sonic-stormond/tests/mocked_libs/sonic_platform/pcie.py delete mode 100644 sonic-stormond/tests/mocked_libs/sonic_platform/ssd.py delete mode 100644 sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/ssd.py create mode 100644 sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/storage_devices.py delete mode 100644 sonic-stormond/tests/test_StorageDevice.py diff --git a/sonic-stormond/tests/mock_platform.py b/sonic-stormond/tests/mock_platform.py deleted file mode 100644 index a5b3070bf..000000000 --- a/sonic-stormond/tests/mock_platform.py +++ /dev/null @@ -1,15 +0,0 @@ - -""" - Mock implementation of sonic_platform package for unit testing -""" - -# TODO: Clean this up once we no longer need to support Python 2 -import sys -if sys.version_info.major == 3: - from unittest import mock -else: - import mock - -class MockStorageDevice(): - def __init__(self): - super(MockStorageDevice, self).__init__() \ No newline at end of file diff --git a/sonic-stormond/tests/mock_swsscommon.py b/sonic-stormond/tests/mock_swsscommon.py new file mode 100644 index 000000000..3023099a6 --- /dev/null +++ b/sonic-stormond/tests/mock_swsscommon.py @@ -0,0 +1,63 @@ +''' + Mock implementation of swsscommon package for unit testing +''' + +from swsssdk import ConfigDBConnector, SonicDBConfig, SonicV2Connector + +STATE_DB = '' + + +class Table: + def __init__(self, db, table_name): + self.table_name = table_name + self.mock_dict = {} + self.mock_keys = ['sda'] + + def _del(self, key): + del self.mock_dict[key] + pass + + def set(self, key, fvs): + self.mock_dict[key] = fvs.fv_dict + pass + + def get(self, key): + if key in self.mock_dict: + return self.mock_dict[key] + return None + + def get_size(self): + return (len(self.mock_dict)) + + def getKeys(self): + return self.mock_keys + + def hgetall(self): + return self.mock_dict + + +class FieldValuePairs: + fv_dict = {} + + def __init__(self, tuple_list): + if isinstance(tuple_list, list) and isinstance(tuple_list[0], tuple): + self.fv_dict = dict(tuple_list) + + def __setitem__(self, key, kv_tuple): + self.fv_dict[kv_tuple[0]] = kv_tuple[1] + + def __getitem__(self, key): + return self.fv_dict[key] + + def __eq__(self, other): + if not isinstance(other, FieldValuePairs): + # don't attempt to compare against unrelated types + return NotImplemented + + return self.fv_dict == other.fv_dict + + def __repr__(self): + return repr(self.fv_dict) + + def __str__(self): + return repr(self.fv_dict) diff --git a/sonic-stormond/tests/mocked_libs/sonic_platform/__init__.py b/sonic-stormond/tests/mocked_libs/sonic_platform/__init__.py index 136bb0dcc..47d228696 100644 --- a/sonic-stormond/tests/mocked_libs/sonic_platform/__init__.py +++ b/sonic-stormond/tests/mocked_libs/sonic_platform/__init__.py @@ -2,5 +2,5 @@ Mock implementation of sonic_platform package for unit testing """ -from . import ssd +from . import pcie diff --git a/sonic-stormond/tests/mocked_libs/sonic_platform/pcie.py b/sonic-stormond/tests/mocked_libs/sonic_platform/pcie.py new file mode 100644 index 000000000..df68a999e --- /dev/null +++ b/sonic-stormond/tests/mocked_libs/sonic_platform/pcie.py @@ -0,0 +1,13 @@ +""" + Mock implementation of sonic_platform package for unit testing +""" + +from sonic_platform_base.pcie_base import PcieBase + + +class Pcie(PcieBase): + def __init__(self): + self.platform_pcieutil = "/tmp/Pcie" + + def __str__(self): + return self.platform_pcieutil diff --git a/sonic-stormond/tests/mocked_libs/sonic_platform/ssd.py b/sonic-stormond/tests/mocked_libs/sonic_platform/ssd.py deleted file mode 100644 index 1fce3d024..000000000 --- a/sonic-stormond/tests/mocked_libs/sonic_platform/ssd.py +++ /dev/null @@ -1,13 +0,0 @@ -""" - Mock implementation of sonic_platform package for unit testing -""" - -from sonic_platform_base.storage_base import StorageBase - - -class Storage(StorageBase): - def __init__(self): - self.platform_Storageutil = "/tmp/Storage" - - def __str__(self): - return self.platform_Storageutil diff --git a/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/ssd.py b/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/ssd.py deleted file mode 100644 index 67be574cf..000000000 --- a/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/ssd.py +++ /dev/null @@ -1,127 +0,0 @@ -# -# ssd.py -# -# Generic implementation of the SSD health API -# SSD models supported: -# - InnoDisk -# - StorFly -# - Virtium - -try: - import re - import subprocess - from .storage_base import StorageBase -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" - -# Set Vendor Specific IDs -INNODISK_HEALTH_ID = 169 -INNODISK_TEMPERATURE_ID = 194 -SWISSBIT_HEALTH_ID = 248 -SWISSBIT_TEMPERATURE_ID = 194 - -class SsdUtil(StorageBase): - """ - Generic implementation of the SSD health API - """ - - def __init__(self, diskdev): - model = 'InnoDisk Corp. - mSATA 3IE3' - serial = 'BCA11712190600251' - firmware = 'S16425cG' - temperature = 32.3 - health = 91.6 - ssd_info = NOT_AVAILABLE - vendor_ssd_info = NOT_AVAILABLE - io_reads = 20000 - io_writes = 20005 - reserved_blocks = 3746218 - - 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 get_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.io_writes - - def get_io_reads(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.io_reads - - def get_reserves_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 diff --git a/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/storage_base.py b/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/storage_base.py index 2b8ca0aa0..4ad4e8cf7 100644 --- a/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/storage_base.py +++ b/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/storage_base.py @@ -1,19 +1,7 @@ # # storage_base.py # -# Abstract base class for implementing platform-specific -# Storage information gathering functionality for SONiC -# - -try: - import abc -except ImportError as e: - raise ImportError(str(e) + " - required module not found") - -# -# storage_base.py -# -# Base class for implementing common Storage Device health features +# Base class for implementing common SSD health features # @@ -30,7 +18,6 @@ def __init__(self, diskdev): """ pass - @abc.abstractmethod def get_health(self): """ Retrieves current disk health in percentages @@ -39,9 +26,8 @@ def get_health(self): A float number of current ssd health e.g. 83.5 """ - return 91.6 + raise NotImplementedError - @abc.abstractmethod def get_temperature(self): """ Retrieves current disk temperature in Celsius @@ -50,9 +36,8 @@ def get_temperature(self): A float number of current temperature in Celsius e.g. 40.1 """ - return 32.3 + raise NotImplementedError - @abc.abstractmethod def get_model(self): """ Retrieves model for the given disk device @@ -60,9 +45,8 @@ def get_model(self): Returns: A string holding disk model as provided by the manufacturer """ - return '' + raise NotImplementedError - @abc.abstractmethod def get_firmware(self): """ Retrieves firmware version for the given disk device @@ -70,9 +54,8 @@ def get_firmware(self): Returns: A string holding disk firmware version as provided by the manufacturer """ - return '' + raise NotImplementedError - @abc.abstractmethod def get_serial(self): """ Retrieves serial number for the given disk device @@ -80,9 +63,8 @@ def get_serial(self): Returns: A string holding disk serial number as provided by the manufacturer """ - return '' + raise NotImplementedError - @abc.abstractmethod def get_vendor_output(self): """ Retrieves vendor specific data for the given disk device @@ -90,33 +72,31 @@ def get_vendor_output(self): Returns: A string holding some vendor specific disk information """ - return '' + raise NotImplementedError - def get_io_reads(self): + def get_disk_io_reads(self): """ - Retrieves the total number of Input/Output (I/O) reads done on an SSD + 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 """ - return 20000 + raise NotImplementedError - @abc.abstractmethod - def get_io_writes(self): + def get_disk_io_writes(self): """ - Retrieves the total number of Input/Output (I/O) writes done on an SSD + 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 """ - return 20005 + raise NotImplementedError - @abc.abstractmethod - def get_reserves_blocks(self): + def get_reserved_blocks(self): """ - Retrieves the total number of reserved blocks in an SSD + Retrieves the total number of reserved blocks in an storage disk Returns: An integer value of the total number of reserved blocks """ - return 3746218 \ No newline at end of file + raise NotImplementedError diff --git a/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/storage_devices.py b/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/storage_devices.py new file mode 100644 index 000000000..1ecffe13e --- /dev/null +++ b/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/storage_devices.py @@ -0,0 +1,12 @@ + +BLKDEV_BASE_PATH = '' + +class StorageDevices: + def __init__(self): + self.devices = {'sda' : None} + + def _get_storage_devices(self): + pass + + def _storage_device_object_factory(self, key): + pass \ No newline at end of file diff --git a/sonic-stormond/tests/test_DaemonStorage.py b/sonic-stormond/tests/test_DaemonStorage.py index d10ebf666..bab383135 100644 --- a/sonic-stormond/tests/test_DaemonStorage.py +++ b/sonic-stormond/tests/test_DaemonStorage.py @@ -1,40 +1,122 @@ import datetime import os import sys -from imp import load_source # Replace with importlib once we no longer need to support Python 2 - -import pytest +from imp import load_source # TODO: Clean this up once we no longer need to support Python 2 if sys.version_info.major == 3: - from unittest import mock + from unittest.mock import patch, MagicMock, mock_open else: - import mock - -from .mock_platform import MockStorageDevice - -SYSLOG_IDENTIFIER = 'storage_daemon_test' -NOT_AVAILABLE = 'N/A' - - -tests_path = os.path.dirname(os.path.abspath(__file__)) + from mock import patch, MagicMock, mock_open # Add mocked_libs path so that the file under test can load mocked modules from there +tests_path = os.path.dirname(os.path.abspath(__file__)) mocked_libs_path = os.path.join(tests_path, "mocked_libs") sys.path.insert(0, mocked_libs_path) +from .mocked_libs.swsscommon import swsscommon from sonic_py_common import daemon_base -daemon_base.db_connect = mock.MagicMock() # Add path to the file under test so that we can load it modules_path = os.path.dirname(tests_path) scripts_path = os.path.join(modules_path, "scripts") sys.path.insert(0, modules_path) load_source('stormond', os.path.join(scripts_path, 'stormond')) + import stormond +import pytest + + +log_identifier = 'storage_daemon_test' + + +#daemon_base.db_connect = MagicMock() + +config_intvls = ''' +daemon_polling_interval, +60, +fsstats_sync_interval, +300 +''' class TestDaemonStorage(object): """ Test cases to cover functionality in DaemonStorage class """ + + @patch('sonic_py_common.daemon_base.db_connect', MagicMock()) + def test_default_configdb_intervals_no_config(self): + + stormon_daemon = stormond.DaemonStorage(log_identifier) + + assert (stormon_daemon.timeout) == 3600 + assert (stormon_daemon.fsstats_sync_interval) == 86400 + + + @patch('sonic_py_common.daemon_base.db_connect', MagicMock()) + def test_storage_devices(self): + + def new_mock_factory(self, key): + return MagicMock() + + with patch('sonic_platform_base.sonic_storage.storage_devices.StorageDevices._storage_device_object_factory', new=new_mock_factory): + + stormon_daemon = stormond.DaemonStorage(log_identifier) + + assert(list(stormon_daemon.storage.devices.keys()) == ['sda']) + + @patch('os.path.exists', MagicMock(return_value=True)) + @patch('json.load', MagicMock(return_value={})) + @patch('sonic_py_common.daemon_base.db_connect', MagicMock()) + def test_load_fsio_rw_json(self): + + with patch('builtins.open', new_callable=mock_open, read_data='{}') as mock_fd: + stormon_daemon = stormond.DaemonStorage(log_identifier) + + assert stormon_daemon.fsio_json_file_loaded == True + + + @patch('os.path.exists', MagicMock(return_value=True)) + @patch('json.load', MagicMock(side_effect=Exception)) + @patch('sonic_py_common.daemon_base.db_connect', MagicMock()) + def test_load_fsio_rw_json_exception(self): + + with patch('builtins.open', new_callable=mock_open, read_data='{}') as mock_fd: + stormon_daemon = stormond.DaemonStorage(log_identifier) + + assert stormon_daemon.fsio_json_file_loaded == False + + @patch('sonic_py_common.daemon_base.db_connect') + def test_get_configdb_intervals(self, mock_daemon_base): + + mock_daemon_base = MagicMock() + + stormon_daemon = stormond.DaemonStorage(log_identifier) + stormon_daemon._get_configdb_intervals() + + assert mock_daemon_base.call_count == 0 + + + @patch('sonic_py_common.daemon_base.db_connect', MagicMock()) + @patch('json.dump', MagicMock()) + def test_sync_fsio_rw_json_exception(self): + stormon_daemon = stormond.DaemonStorage(log_identifier) + + with patch('builtins.open', new_callable=mock_open, read_data='{}') as mock_fd: + stormon_daemon._sync_fsio_rw_json() + + assert stormon_daemon.state_db.call_count == 0 + + @patch('sonic_py_common.daemon_base.db_connect', MagicMock()) + @patch('json.dump', MagicMock()) + @patch('time.time', MagicMock(return_value=1000)) + def test_sync_fsio_rw_json_happy(self): + stormon_daemon = stormond.DaemonStorage(log_identifier) + + with patch('builtins.open', new_callable=mock_open, read_data='{}') as mock_fd: + stormon_daemon._sync_fsio_rw_json() + + assert stormon_daemon.state_db.call_count == 0 + + diff --git a/sonic-stormond/tests/test_StorageDevice.py b/sonic-stormond/tests/test_StorageDevice.py deleted file mode 100644 index 972132ddc..000000000 --- a/sonic-stormond/tests/test_StorageDevice.py +++ /dev/null @@ -1,33 +0,0 @@ -import os -import sys -from imp import load_source # Replace with importlib once we no longer need to support Python 2 - -import pytest - -# TODO: Clean this up once we no longer need to support Python 2 -if sys.version_info >= (3, 3): - from unittest.mock import MagicMock, patch, mock_open -else: - from mock import MagicMock, patch, mock_open - -from .mock_platform import MockStorageDevice - -tests_path = os.path.dirname(os.path.abspath(__file__)) - -# Add mocked_libs path so that the file under test can load mocked modules from there -mocked_libs_path = os.path.join(tests_path, "mocked_libs") -sys.path.insert(0, mocked_libs_path) -from sonic_py_common import daemon_base, device_info - -# Add path to the file under test so that we can load it -modules_path = os.path.dirname(tests_path) -scripts_path = os.path.join(modules_path, "scripts") -sys.path.insert(0, modules_path) -load_source('stormond', os.path.join(scripts_path, 'stormond')) -import stormond - - -daemon_base.db_connect = MagicMock() - -SYSLOG_IDENTIFIER = 'storagedevice_test' -NOT_AVAILABLE = 'N/A' \ No newline at end of file From f62f27143067858ea1395e777371376211dfe05d Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Wed, 22 May 2024 21:05:27 +0000 Subject: [PATCH 15/31] Changed log_info to log_error --- sonic-stormond/scripts/stormond | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonic-stormond/scripts/stormond b/sonic-stormond/scripts/stormond index 45c9d1dce..d90448a97 100755 --- a/sonic-stormond/scripts/stormond +++ b/sonic-stormond/scripts/stormond @@ -247,7 +247,7 @@ class DaemonStorage(daemon_base.DaemonBase): try: # Unlikely scenario if storage_object is None: - self.log_info("{} does not have an instantiated object. Static Information cannot be gathered.".format(storage_device)) + self.log_error("{} does not have an instantiated object. Static Information cannot be gathered.".format(storage_device)) continue static_kvp_dict = {} From 7118c91f113f67dc588851b769e02ede82ce18d1 Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Thu, 23 May 2024 02:44:13 +0000 Subject: [PATCH 16/31] Changed function name to match corr. change in SsdUtil --- sonic-stormond/scripts/stormond | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonic-stormond/scripts/stormond b/sonic-stormond/scripts/stormond index d90448a97..0bfb5719b 100755 --- a/sonic-stormond/scripts/stormond +++ b/sonic-stormond/scripts/stormond @@ -275,7 +275,7 @@ class DaemonStorage(daemon_base.DaemonBase): # Fetch the latest dynamic info blkdevice = os.path.join(BLKDEV_BASE_PATH, storage_device) - storage_object._fetch_parse_info(blkdevice) + storage_object.fetch_parse_info(blkdevice) dynamic_kvp_dict = {} From d246ac54b296b21fe0cc47db6f7c9f0c2196f2fb Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Thu, 23 May 2024 09:55:58 +0000 Subject: [PATCH 17/31] Moved get_dynamic_field() to before timeout --- azure-pipelines.yml | 3 +++ sonic-stormond/scripts/stormond | 7 +++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 222e2c323..f4902f1eb 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -56,6 +56,9 @@ parameters: - name: sensormond root_dir: sonic-sensormond python3: true + - name: stormond + root_dir: sonic-stormond + python3: true - name: artifactBranch type: string default: 'refs/heads/master' diff --git a/sonic-stormond/scripts/stormond b/sonic-stormond/scripts/stormond index 0bfb5719b..f0bed21b0 100755 --- a/sonic-stormond/scripts/stormond +++ b/sonic-stormond/scripts/stormond @@ -333,6 +333,9 @@ class DaemonStorage(daemon_base.DaemonBase): # this is to be able to dynamically configure the polling and sync times. self._get_configdb_intervals() + # Repeatedly read and update Dynamic Fields to the StateDB + self.get_dynamic_fields() + if self.stop_event.wait(self.timeout): # We received a fatal signal return False @@ -346,10 +349,6 @@ class DaemonStorage(daemon_base.DaemonBase): if (elapsed_time > self.fsstats_sync_interval) or ((self.fsstats_sync_interval - elapsed_time) < self.timeout): self._sync_fsio_rw_json() - - # Repeatedly read and update Dynamic Fields to the StateDB - self.get_dynamic_fields() - return True # # Main ========================================================================= From 335b2c87000f7644ced67744ebd9d56f8b02b4b2 Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Thu, 23 May 2024 19:03:29 +0000 Subject: [PATCH 18/31] Cleaned up signal handling, added more UTs --- sonic-stormond/scripts/stormond | 4 +- sonic-stormond/tests/test_DaemonStorage.py | 148 +++++++++++++++++++++ 2 files changed, 150 insertions(+), 2 deletions(-) diff --git a/sonic-stormond/scripts/stormond b/sonic-stormond/scripts/stormond index f0bed21b0..aa858b260 100755 --- a/sonic-stormond/scripts/stormond +++ b/sonic-stormond/scripts/stormond @@ -316,7 +316,7 @@ class DaemonStorage(daemon_base.DaemonBase): self.log_info("Syncing latest procfs reads and writes to disk") self._sync_fsio_rw_json() - self.log_info(" Exiting.") + self.log_info("Exiting with {}".format(SIGNALS_TO_NAMES_DICT[sig])) # Make sure we exit with a non-zero code so that supervisor will try to restart us exit_code = 128 + sig @@ -324,7 +324,7 @@ class DaemonStorage(daemon_base.DaemonBase): elif sig in NONFATAL_SIGNALS: self.log_info("Caught signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) else: - self.log_info("Caught unhandled signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) + self.log_warning("Caught unhandled signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) # Main daemon logic def run(self): diff --git a/sonic-stormond/tests/test_DaemonStorage.py b/sonic-stormond/tests/test_DaemonStorage.py index bab383135..1a0a244a7 100644 --- a/sonic-stormond/tests/test_DaemonStorage.py +++ b/sonic-stormond/tests/test_DaemonStorage.py @@ -39,6 +39,11 @@ 300 ''' +fsio_dict = {"total_fsio_reads": "", "total_fsio_writes": "", "latest_fsio_reads": "1000", "latest_fsio_writes": "2000"} +fsio_json_dict = { 'sda' : {"total_fsio_reads": "10500", "total_fsio_writes": "21000", "latest_fsio_reads": "1000", "latest_fsio_writes": "2000"}} +fsio_statedb_dict = { 'sda' : {"total_fsio_reads": "10500", "total_fsio_writes": "21000", "latest_fsio_reads": "200", "latest_fsio_writes": "400"}} + +dynamic_dict = {'firmware': 'ILLBBK', 'health': '40', 'temperature': '5000', 'latest_fsio_reads': '150', 'latest_fsio_writes': '270', 'disk_io_reads': '1000', 'disk_io_writes': '2000', 'reserved_blocks': '3'} class TestDaemonStorage(object): """ @@ -119,4 +124,147 @@ def test_sync_fsio_rw_json_happy(self): assert stormon_daemon.state_db.call_count == 0 + @patch('sonic_py_common.daemon_base.db_connect', MagicMock()) + def test_reconcile_fsio_rw_values_init(self): + stormon_daemon = stormond.DaemonStorage(log_identifier) + stormon_daemon.use_statedb_baseline = False + stormon_daemon.use_fsio_json_baseline = False + + (reads, writes) = stormon_daemon._reconcile_fsio_rw_values(fsio_dict, MagicMock()) + + assert reads == '1000' + assert writes == '2000' + + + @patch('sonic_py_common.daemon_base.db_connect', MagicMock()) + def test_reconcile_fsio_rw_values_reboot(self): + stormon_daemon = stormond.DaemonStorage(log_identifier) + + stormon_daemon.use_statedb_baseline = False + stormon_daemon.use_fsio_json_baseline = True + stormon_daemon.fsio_rw_json = fsio_json_dict + + (reads, writes) = stormon_daemon._reconcile_fsio_rw_values(fsio_dict, 'sda') + + assert reads == '11500' + assert writes == '23000' + + + @patch('sonic_py_common.daemon_base.db_connect', MagicMock()) + def test_reconcile_fsio_rw_values_daemon_crash(self): + stormon_daemon = stormond.DaemonStorage(log_identifier) + + stormon_daemon.use_statedb_baseline = True + stormon_daemon.use_fsio_json_baseline = True + stormon_daemon.fsio_rw_statedb = fsio_statedb_dict + + (reads, writes) = stormon_daemon._reconcile_fsio_rw_values(fsio_dict, 'sda') + + assert reads == '11300' + assert writes == '22600' + + + @patch('sonic_py_common.daemon_base.db_connect', MagicMock()) + def test_update_storage_info_status_db(self): + stormon_daemon = stormond.DaemonStorage(log_identifier) + + stormon_daemon.update_storage_info_status_db('sda', 'mock_field', fsio_json_dict['sda']) + + assert stormon_daemon.device_table.getKeys() == ['sda'] + + + @patch('sonic_py_common.daemon_base.db_connect', MagicMock()) + def test_get_static_fields(self): + stormon_daemon = stormond.DaemonStorage(log_identifier) + + mock_storage_device_object = MagicMock() + mock_storage_device_object.get_model.return_value = "Skynet" + mock_storage_device_object.get_serial.return_value = "T1000" + + stormon_daemon.storage.devices = {'sda' : mock_storage_device_object} + stormon_daemon.get_static_fields() + assert stormon_daemon.device_table.getKeys() == ['sda'] + assert stormon_daemon.device_table.get('sda') == {'device_model': 'Skynet', 'serial': 'T1000'} + + + @patch('sonic_py_common.daemon_base.db_connect', MagicMock()) + def test_get_dynamic_fields(self): + stormon_daemon = stormond.DaemonStorage(log_identifier) + + mock_storage_device_object = MagicMock() + mock_storage_device_object.get_firmware.return_value = "ILLBBK" + mock_storage_device_object.get_health.return_value = "40" + mock_storage_device_object.get_temperature.return_value = "5000" + mock_storage_device_object.get_fs_io_reads.return_value = "150" + mock_storage_device_object.get_fs_io_writes.return_value = "270" + mock_storage_device_object.get_disk_io_reads.return_value = "1000" + mock_storage_device_object.get_disk_io_writes.return_value = "2000" + mock_storage_device_object.get_reserved_blocks.return_value = "3" + + stormon_daemon.storage.devices = {'sda' : mock_storage_device_object} + stormon_daemon.get_dynamic_fields() + + assert stormon_daemon.device_table.getKeys() == ['sda'] + for field, value in dynamic_dict.items(): + assert stormon_daemon.device_table.get('sda')[field] == value + + + @patch('sonic_py_common.daemon_base.db_connect', MagicMock()) + def test_signal_handler(self): + stormon_daemon = stormond.DaemonStorage(log_identifier) + stormon_daemon._sync_fsio_rw_json = MagicMock() + + stormon_daemon.stop_event.set = MagicMock() + stormon_daemon.log_info = MagicMock() + stormon_daemon.log_warning = MagicMock() + + # Test SIGHUP + stormon_daemon.signal_handler(stormond.signal.SIGHUP, None) + assert stormon_daemon.log_info.call_count == 1 + stormon_daemon.log_info.assert_called_with("Caught signal 'SIGHUP' - ignoring...") + assert stormon_daemon.log_warning.call_count == 0 + assert stormon_daemon.stop_event.set.call_count == 0 + assert stormond.exit_code == 0 + + # Reset + stormon_daemon.log_info.reset_mock() + stormon_daemon.log_warning.reset_mock() + stormon_daemon.stop_event.set.reset_mock() + + # Test SIGINT + test_signal = stormond.signal.SIGINT + stormon_daemon.signal_handler(test_signal, None) + assert stormon_daemon.log_info.call_count == 3 + stormon_daemon.log_info.assert_called_with("Exiting with SIGINT") + assert stormon_daemon.log_warning.call_count == 0 + assert stormon_daemon.stop_event.set.call_count == 1 + assert stormond.exit_code == (128 + test_signal) + + # Reset + stormon_daemon.log_info.reset_mock() + stormon_daemon.log_warning.reset_mock() + stormon_daemon.stop_event.set.reset_mock() + + # Test SIGTERM + test_signal = stormond.signal.SIGTERM + stormon_daemon.signal_handler(test_signal, None) + assert stormon_daemon.log_info.call_count == 3 + stormon_daemon.log_info.assert_called_with("Exiting with SIGTERM") + assert stormon_daemon.log_warning.call_count == 0 + assert stormon_daemon.stop_event.set.call_count == 1 + assert stormond.exit_code == (128 + test_signal) + + # Reset + stormon_daemon.log_info.reset_mock() + stormon_daemon.log_warning.reset_mock() + stormon_daemon.stop_event.set.reset_mock() + stormond.exit_code = 0 + + # Test an unhandled signal + stormon_daemon.signal_handler(stormond.signal.SIGUSR1, None) + assert stormon_daemon.log_warning.call_count == 1 + stormon_daemon.log_warning.assert_called_with("Caught unhandled signal 'SIGUSR1' - ignoring...") + assert stormon_daemon.log_info.call_count == 0 + assert stormon_daemon.stop_event.set.call_count == 0 + assert stormond.exit_code == 0 From b2ec9038eeee1617776a95dc2d180966d44c0b7c Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Thu, 23 May 2024 19:25:35 +0000 Subject: [PATCH 19/31] Added another UT --- sonic-stormond/tests/test_DaemonStorage.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/sonic-stormond/tests/test_DaemonStorage.py b/sonic-stormond/tests/test_DaemonStorage.py index 1a0a244a7..f973f0781 100644 --- a/sonic-stormond/tests/test_DaemonStorage.py +++ b/sonic-stormond/tests/test_DaemonStorage.py @@ -268,3 +268,20 @@ def test_signal_handler(self): assert stormon_daemon.log_info.call_count == 0 assert stormon_daemon.stop_event.set.call_count == 0 assert stormond.exit_code == 0 + + + @patch('sonic_py_common.daemon_base.db_connect', MagicMock()) + def test_run(self): + stormon_daemon = stormond.DaemonStorage(log_identifier) + stormon_daemon.get_dynamic_fields = MagicMock() + + def mock_intervals(): + stormon_daemon.timeout = 10 + stormon_daemon.fsstats_sync_interval = 30 + + with patch.object(stormon_daemon, '_get_configdb_intervals', new=mock_intervals): + stormon_daemon.run() + + assert stormon_daemon.get_dynamic_fields.call_count == 1 + + From b146d722f445aa2f6ad62df2bc73f19ac13d10f3 Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Sat, 25 May 2024 07:55:37 +0000 Subject: [PATCH 20/31] Fixed a bug that was causing sync-to-disk to fail --- sonic-stormond/scripts/stormond | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sonic-stormond/scripts/stormond b/sonic-stormond/scripts/stormond index aa858b260..6901bfe03 100755 --- a/sonic-stormond/scripts/stormond +++ b/sonic-stormond/scripts/stormond @@ -122,7 +122,7 @@ class DaemonStorage(daemon_base.DaemonBase): self.fsio_json_file_loaded = True except Exception as e: - self.log_error("JSON file could not be loaded: {}".format(e)) + self.log_error("JSON file could not be loaded: {}".format(str(e))) return @@ -132,7 +132,7 @@ class DaemonStorage(daemon_base.DaemonBase): self.log_info("Syncing total and latest procfs reads and writes from STATE_DB to JSON file") - json_file_dict = {} + json_file_dict = {disk:{} for disk in self.storage.devices} try: for device in self.storage.devices: for field in self.statedb_json_sync_fields: @@ -146,7 +146,7 @@ class DaemonStorage(daemon_base.DaemonBase): self.state_db.hset("{}|{}".format(STORAGE_DEVICE_TABLE,FSSTATS_SYNC_TIME_KEY), "successful_sync_time", str(self.fsio_sync_time)) except Exception as ex: - self.log_error("Unable to sync state_db to disk: {}".format(ex)) + self.log_error("Unable to sync state_db to disk: {}".format(str(ex))) # Run a sanity check on the state_db. If successful, get total, latest # FSIO reads and writes for each storage device from STATE_DB @@ -171,7 +171,7 @@ class DaemonStorage(daemon_base.DaemonBase): self.statedb_storage_info_loaded = True except Exception as e: - self.log_error("Reading STATE_DB failed with: {}".format(e)) + self.log_error("Reading STATE_DB failed with: {}".format(str(e))) def _determine_sot(self): From 9a94c0785fe182b1381e80198b864421ab581e5b Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Mon, 27 May 2024 16:29:18 +0000 Subject: [PATCH 21/31] Fixed an eror with _load_fsio_rw_statedb that would assign None values to fields --- sonic-stormond/scripts/stormond | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sonic-stormond/scripts/stormond b/sonic-stormond/scripts/stormond index 6901bfe03..baf52731a 100755 --- a/sonic-stormond/scripts/stormond +++ b/sonic-stormond/scripts/stormond @@ -167,7 +167,10 @@ class DaemonStorage(daemon_base.DaemonBase): # Get the total and latest procfs reads and writes from STATE_DB for field in self.statedb_json_sync_fields: - self.fsio_rw_statedb[storage_device][field] = self.state_db.hget('STORAGE_INFO|{}'.format(storage_device), field) + value = self.state_db.hget('STORAGE_INFO|{}'.format(storage_device), field) + self.fsio_rw_statedb[storage_device][field] = "0" if value is None else value + + if value is None: self.log_warning("{}:{} value = None in StateDB".format(storage_device, field)) self.statedb_storage_info_loaded = True except Exception as e: From 959c9466aaa01dbe3f73f46d3ff37ca59f97d134 Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Tue, 28 May 2024 20:37:29 +0000 Subject: [PATCH 22/31] Lowered severity for expected log when storage object isnt instantiated --- sonic-stormond/scripts/stormond | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonic-stormond/scripts/stormond b/sonic-stormond/scripts/stormond index baf52731a..28a8a16bd 100755 --- a/sonic-stormond/scripts/stormond +++ b/sonic-stormond/scripts/stormond @@ -250,7 +250,7 @@ class DaemonStorage(daemon_base.DaemonBase): try: # Unlikely scenario if storage_object is None: - self.log_error("{} does not have an instantiated object. Static Information cannot be gathered.".format(storage_device)) + self.log_info("{} does not have an instantiated object. Static Information cannot be gathered.".format(storage_device)) continue static_kvp_dict = {} From d89a8bdf1903e6f0ebdd201190a717ef0e09b244 Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Wed, 29 May 2024 01:14:51 +0000 Subject: [PATCH 23/31] Fixed potential bug where if dynamic_fields fails before populating statedb, JSON is populated with null values --- sonic-stormond/scripts/stormond | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sonic-stormond/scripts/stormond b/sonic-stormond/scripts/stormond index 28a8a16bd..c704dbbc6 100755 --- a/sonic-stormond/scripts/stormond +++ b/sonic-stormond/scripts/stormond @@ -121,6 +121,13 @@ class DaemonStorage(daemon_base.DaemonBase): self.fsio_rw_json = json.load(f) self.fsio_json_file_loaded = True + for storage_device in self.storage.devices: + for field in self.statedb_json_sync_fields: + + value = self.fsio_rw_json[storage_device][field] + + self.fsio_rw_json[storage_device][field] = "0" if value == "null" else value + except Exception as e: self.log_error("JSON file could not be loaded: {}".format(str(e))) From ccbe35049e4a65c7c0c19f6f74650faa55498258 Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Wed, 29 May 2024 01:20:18 +0000 Subject: [PATCH 24/31] Fixed indentation error --- sonic-stormond/scripts/stormond | 2 +- .../proto_out/linkmgr_grpc_driver_pb2.py | 53 ++++ .../proto_out/linkmgr_grpc_driver_pb2_grpc.py | 259 ++++++++++++++++++ 3 files changed, 313 insertions(+), 1 deletion(-) create mode 100644 sonic-ycabled/proto_out/linkmgr_grpc_driver_pb2.py create mode 100644 sonic-ycabled/proto_out/linkmgr_grpc_driver_pb2_grpc.py diff --git a/sonic-stormond/scripts/stormond b/sonic-stormond/scripts/stormond index c704dbbc6..023006859 100755 --- a/sonic-stormond/scripts/stormond +++ b/sonic-stormond/scripts/stormond @@ -121,7 +121,7 @@ class DaemonStorage(daemon_base.DaemonBase): self.fsio_rw_json = json.load(f) self.fsio_json_file_loaded = True - for storage_device in self.storage.devices: + for storage_device in self.storage.devices: for field in self.statedb_json_sync_fields: value = self.fsio_rw_json[storage_device][field] diff --git a/sonic-ycabled/proto_out/linkmgr_grpc_driver_pb2.py b/sonic-ycabled/proto_out/linkmgr_grpc_driver_pb2.py new file mode 100644 index 000000000..d7ff19e1b --- /dev/null +++ b/sonic-ycabled/proto_out/linkmgr_grpc_driver_pb2.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: proto_out/linkmgr_grpc_driver.proto +"""Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#proto_out/linkmgr_grpc_driver.proto\"-\n\x0c\x41\x64minRequest\x12\x0e\n\x06portid\x18\x01 \x03(\x05\x12\r\n\x05state\x18\x02 \x03(\x08\"+\n\nAdminReply\x12\x0e\n\x06portid\x18\x01 \x03(\x05\x12\r\n\x05state\x18\x02 \x03(\x08\"\"\n\x10OperationRequest\x12\x0e\n\x06portid\x18\x01 \x03(\x05\"/\n\x0eOperationReply\x12\x0e\n\x06portid\x18\x01 \x03(\x05\x12\r\n\x05state\x18\x02 \x03(\x08\"\"\n\x10LinkStateRequest\x12\x0e\n\x06portid\x18\x01 \x03(\x05\"/\n\x0eLinkStateReply\x12\x0e\n\x06portid\x18\x01 \x03(\x05\x12\r\n\x05state\x18\x02 \x03(\x08\"\'\n\x14ServerVersionRequest\x12\x0f\n\x07version\x18\x01 \x01(\t\"%\n\x12ServerVersionReply\x12\x0f\n\x07version\x18\x01 \x01(\t\"-\n\x14GracefulAdminRequest\x12\x15\n\x03tor\x18\x01 \x01(\x0e\x32\x08.ToRSide\"\x8f\x01\n\x15GracefulAdminResponse\x12(\n\x07msgtype\x18\x01 \x01(\x0e\x32\x17.GracefulRestartMsgType\x12.\n\nnotifytype\x18\x02 \x01(\x0e\x32\x1a.GracefulRestartNotifyType\x12\x0c\n\x04guid\x18\x03 \x01(\t\x12\x0e\n\x06period\x18\x04 \x01(\x05*\'\n\x07ToRSide\x12\r\n\tLOWER_TOR\x10\x00\x12\r\n\tUPPER_TOR\x10\x01*<\n\x16GracefulRestartMsgType\x12\x11\n\rSERVICE_BEGIN\x10\x00\x12\x0f\n\x0bSERVICE_END\x10\x01*H\n\x19GracefulRestartNotifyType\x12\x11\n\rCONTROL_PLANE\x10\x00\x12\x0e\n\nDATA_PLANE\x10\x01\x12\x08\n\x04\x42OTH\x10\x02\x32\xc8\x02\n\rDualToRActive\x12=\n\x1dQueryAdminForwardingPortState\x12\r.AdminRequest\x1a\x0b.AdminReply\"\x00\x12;\n\x1bSetAdminForwardingPortState\x12\r.AdminRequest\x1a\x0b.AdminReply\"\x00\x12?\n\x17QueryOperationPortState\x12\x11.OperationRequest\x1a\x0f.OperationReply\"\x00\x12\x36\n\x0eQueryLinkState\x12\x11.LinkStateRequest\x1a\x0f.LinkStateReply\"\x00\x12\x42\n\x12QueryServerVersion\x12\x15.ServerVersionRequest\x1a\x13.ServerVersionReply\"\x00\x32\x62\n\x0fGracefulRestart\x12O\n\x1aNotifyGracefulRestartStart\x12\x15.GracefulAdminRequest\x1a\x16.GracefulAdminResponse\"\x00\x30\x01\x62\x06proto3') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'proto_out.linkmgr_grpc_driver_pb2', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + _TORSIDE._serialized_start=574 + _TORSIDE._serialized_end=613 + _GRACEFULRESTARTMSGTYPE._serialized_start=615 + _GRACEFULRESTARTMSGTYPE._serialized_end=675 + _GRACEFULRESTARTNOTIFYTYPE._serialized_start=677 + _GRACEFULRESTARTNOTIFYTYPE._serialized_end=749 + _ADMINREQUEST._serialized_start=39 + _ADMINREQUEST._serialized_end=84 + _ADMINREPLY._serialized_start=86 + _ADMINREPLY._serialized_end=129 + _OPERATIONREQUEST._serialized_start=131 + _OPERATIONREQUEST._serialized_end=165 + _OPERATIONREPLY._serialized_start=167 + _OPERATIONREPLY._serialized_end=214 + _LINKSTATEREQUEST._serialized_start=216 + _LINKSTATEREQUEST._serialized_end=250 + _LINKSTATEREPLY._serialized_start=252 + _LINKSTATEREPLY._serialized_end=299 + _SERVERVERSIONREQUEST._serialized_start=301 + _SERVERVERSIONREQUEST._serialized_end=340 + _SERVERVERSIONREPLY._serialized_start=342 + _SERVERVERSIONREPLY._serialized_end=379 + _GRACEFULADMINREQUEST._serialized_start=381 + _GRACEFULADMINREQUEST._serialized_end=426 + _GRACEFULADMINRESPONSE._serialized_start=429 + _GRACEFULADMINRESPONSE._serialized_end=572 + _DUALTORACTIVE._serialized_start=752 + _DUALTORACTIVE._serialized_end=1080 + _GRACEFULRESTART._serialized_start=1082 + _GRACEFULRESTART._serialized_end=1180 +# @@protoc_insertion_point(module_scope) diff --git a/sonic-ycabled/proto_out/linkmgr_grpc_driver_pb2_grpc.py b/sonic-ycabled/proto_out/linkmgr_grpc_driver_pb2_grpc.py new file mode 100644 index 000000000..d8ec9f358 --- /dev/null +++ b/sonic-ycabled/proto_out/linkmgr_grpc_driver_pb2_grpc.py @@ -0,0 +1,259 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from proto_out import linkmgr_grpc_driver_pb2 as proto__out_dot_linkmgr__grpc__driver__pb2 + + +class DualToRActiveStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.QueryAdminForwardingPortState = channel.unary_unary( + '/DualToRActive/QueryAdminForwardingPortState', + request_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.AdminRequest.SerializeToString, + response_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.AdminReply.FromString, + ) + self.SetAdminForwardingPortState = channel.unary_unary( + '/DualToRActive/SetAdminForwardingPortState', + request_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.AdminRequest.SerializeToString, + response_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.AdminReply.FromString, + ) + self.QueryOperationPortState = channel.unary_unary( + '/DualToRActive/QueryOperationPortState', + request_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.OperationRequest.SerializeToString, + response_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.OperationReply.FromString, + ) + self.QueryLinkState = channel.unary_unary( + '/DualToRActive/QueryLinkState', + request_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.LinkStateRequest.SerializeToString, + response_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.LinkStateReply.FromString, + ) + self.QueryServerVersion = channel.unary_unary( + '/DualToRActive/QueryServerVersion', + request_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.ServerVersionRequest.SerializeToString, + response_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.ServerVersionReply.FromString, + ) + + +class DualToRActiveServicer(object): + """Missing associated documentation comment in .proto file.""" + + def QueryAdminForwardingPortState(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetAdminForwardingPortState(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def QueryOperationPortState(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def QueryLinkState(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def QueryServerVersion(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_DualToRActiveServicer_to_server(servicer, server): + rpc_method_handlers = { + 'QueryAdminForwardingPortState': grpc.unary_unary_rpc_method_handler( + servicer.QueryAdminForwardingPortState, + request_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.AdminRequest.FromString, + response_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.AdminReply.SerializeToString, + ), + 'SetAdminForwardingPortState': grpc.unary_unary_rpc_method_handler( + servicer.SetAdminForwardingPortState, + request_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.AdminRequest.FromString, + response_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.AdminReply.SerializeToString, + ), + 'QueryOperationPortState': grpc.unary_unary_rpc_method_handler( + servicer.QueryOperationPortState, + request_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.OperationRequest.FromString, + response_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.OperationReply.SerializeToString, + ), + 'QueryLinkState': grpc.unary_unary_rpc_method_handler( + servicer.QueryLinkState, + request_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.LinkStateRequest.FromString, + response_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.LinkStateReply.SerializeToString, + ), + 'QueryServerVersion': grpc.unary_unary_rpc_method_handler( + servicer.QueryServerVersion, + request_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.ServerVersionRequest.FromString, + response_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.ServerVersionReply.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'DualToRActive', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class DualToRActive(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def QueryAdminForwardingPortState(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/DualToRActive/QueryAdminForwardingPortState', + proto__out_dot_linkmgr__grpc__driver__pb2.AdminRequest.SerializeToString, + proto__out_dot_linkmgr__grpc__driver__pb2.AdminReply.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def SetAdminForwardingPortState(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/DualToRActive/SetAdminForwardingPortState', + proto__out_dot_linkmgr__grpc__driver__pb2.AdminRequest.SerializeToString, + proto__out_dot_linkmgr__grpc__driver__pb2.AdminReply.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def QueryOperationPortState(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/DualToRActive/QueryOperationPortState', + proto__out_dot_linkmgr__grpc__driver__pb2.OperationRequest.SerializeToString, + proto__out_dot_linkmgr__grpc__driver__pb2.OperationReply.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def QueryLinkState(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/DualToRActive/QueryLinkState', + proto__out_dot_linkmgr__grpc__driver__pb2.LinkStateRequest.SerializeToString, + proto__out_dot_linkmgr__grpc__driver__pb2.LinkStateReply.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def QueryServerVersion(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/DualToRActive/QueryServerVersion', + proto__out_dot_linkmgr__grpc__driver__pb2.ServerVersionRequest.SerializeToString, + proto__out_dot_linkmgr__grpc__driver__pb2.ServerVersionReply.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + +class GracefulRestartStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.NotifyGracefulRestartStart = channel.unary_stream( + '/GracefulRestart/NotifyGracefulRestartStart', + request_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.GracefulAdminRequest.SerializeToString, + response_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.GracefulAdminResponse.FromString, + ) + + +class GracefulRestartServicer(object): + """Missing associated documentation comment in .proto file.""" + + def NotifyGracefulRestartStart(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_GracefulRestartServicer_to_server(servicer, server): + rpc_method_handlers = { + 'NotifyGracefulRestartStart': grpc.unary_stream_rpc_method_handler( + servicer.NotifyGracefulRestartStart, + request_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.GracefulAdminRequest.FromString, + response_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.GracefulAdminResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'GracefulRestart', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class GracefulRestart(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def NotifyGracefulRestartStart(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_stream(request, target, '/GracefulRestart/NotifyGracefulRestartStart', + proto__out_dot_linkmgr__grpc__driver__pb2.GracefulAdminRequest.SerializeToString, + proto__out_dot_linkmgr__grpc__driver__pb2.GracefulAdminResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) From 4e2f48dacb21da2903681b885d96708e7a23cbff Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Wed, 29 May 2024 01:45:47 +0000 Subject: [PATCH 25/31] Removed untracked ycabled files that were committed by mistake --- .../proto_out/linkmgr_grpc_driver_pb2.py | 53 ---- .../proto_out/linkmgr_grpc_driver_pb2_grpc.py | 259 ------------------ 2 files changed, 312 deletions(-) delete mode 100644 sonic-ycabled/proto_out/linkmgr_grpc_driver_pb2.py delete mode 100644 sonic-ycabled/proto_out/linkmgr_grpc_driver_pb2_grpc.py diff --git a/sonic-ycabled/proto_out/linkmgr_grpc_driver_pb2.py b/sonic-ycabled/proto_out/linkmgr_grpc_driver_pb2.py deleted file mode 100644 index d7ff19e1b..000000000 --- a/sonic-ycabled/proto_out/linkmgr_grpc_driver_pb2.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: proto_out/linkmgr_grpc_driver.proto -"""Generated protocol buffer code.""" -from google.protobuf.internal import builder as _builder -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#proto_out/linkmgr_grpc_driver.proto\"-\n\x0c\x41\x64minRequest\x12\x0e\n\x06portid\x18\x01 \x03(\x05\x12\r\n\x05state\x18\x02 \x03(\x08\"+\n\nAdminReply\x12\x0e\n\x06portid\x18\x01 \x03(\x05\x12\r\n\x05state\x18\x02 \x03(\x08\"\"\n\x10OperationRequest\x12\x0e\n\x06portid\x18\x01 \x03(\x05\"/\n\x0eOperationReply\x12\x0e\n\x06portid\x18\x01 \x03(\x05\x12\r\n\x05state\x18\x02 \x03(\x08\"\"\n\x10LinkStateRequest\x12\x0e\n\x06portid\x18\x01 \x03(\x05\"/\n\x0eLinkStateReply\x12\x0e\n\x06portid\x18\x01 \x03(\x05\x12\r\n\x05state\x18\x02 \x03(\x08\"\'\n\x14ServerVersionRequest\x12\x0f\n\x07version\x18\x01 \x01(\t\"%\n\x12ServerVersionReply\x12\x0f\n\x07version\x18\x01 \x01(\t\"-\n\x14GracefulAdminRequest\x12\x15\n\x03tor\x18\x01 \x01(\x0e\x32\x08.ToRSide\"\x8f\x01\n\x15GracefulAdminResponse\x12(\n\x07msgtype\x18\x01 \x01(\x0e\x32\x17.GracefulRestartMsgType\x12.\n\nnotifytype\x18\x02 \x01(\x0e\x32\x1a.GracefulRestartNotifyType\x12\x0c\n\x04guid\x18\x03 \x01(\t\x12\x0e\n\x06period\x18\x04 \x01(\x05*\'\n\x07ToRSide\x12\r\n\tLOWER_TOR\x10\x00\x12\r\n\tUPPER_TOR\x10\x01*<\n\x16GracefulRestartMsgType\x12\x11\n\rSERVICE_BEGIN\x10\x00\x12\x0f\n\x0bSERVICE_END\x10\x01*H\n\x19GracefulRestartNotifyType\x12\x11\n\rCONTROL_PLANE\x10\x00\x12\x0e\n\nDATA_PLANE\x10\x01\x12\x08\n\x04\x42OTH\x10\x02\x32\xc8\x02\n\rDualToRActive\x12=\n\x1dQueryAdminForwardingPortState\x12\r.AdminRequest\x1a\x0b.AdminReply\"\x00\x12;\n\x1bSetAdminForwardingPortState\x12\r.AdminRequest\x1a\x0b.AdminReply\"\x00\x12?\n\x17QueryOperationPortState\x12\x11.OperationRequest\x1a\x0f.OperationReply\"\x00\x12\x36\n\x0eQueryLinkState\x12\x11.LinkStateRequest\x1a\x0f.LinkStateReply\"\x00\x12\x42\n\x12QueryServerVersion\x12\x15.ServerVersionRequest\x1a\x13.ServerVersionReply\"\x00\x32\x62\n\x0fGracefulRestart\x12O\n\x1aNotifyGracefulRestartStart\x12\x15.GracefulAdminRequest\x1a\x16.GracefulAdminResponse\"\x00\x30\x01\x62\x06proto3') - -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'proto_out.linkmgr_grpc_driver_pb2', globals()) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - _TORSIDE._serialized_start=574 - _TORSIDE._serialized_end=613 - _GRACEFULRESTARTMSGTYPE._serialized_start=615 - _GRACEFULRESTARTMSGTYPE._serialized_end=675 - _GRACEFULRESTARTNOTIFYTYPE._serialized_start=677 - _GRACEFULRESTARTNOTIFYTYPE._serialized_end=749 - _ADMINREQUEST._serialized_start=39 - _ADMINREQUEST._serialized_end=84 - _ADMINREPLY._serialized_start=86 - _ADMINREPLY._serialized_end=129 - _OPERATIONREQUEST._serialized_start=131 - _OPERATIONREQUEST._serialized_end=165 - _OPERATIONREPLY._serialized_start=167 - _OPERATIONREPLY._serialized_end=214 - _LINKSTATEREQUEST._serialized_start=216 - _LINKSTATEREQUEST._serialized_end=250 - _LINKSTATEREPLY._serialized_start=252 - _LINKSTATEREPLY._serialized_end=299 - _SERVERVERSIONREQUEST._serialized_start=301 - _SERVERVERSIONREQUEST._serialized_end=340 - _SERVERVERSIONREPLY._serialized_start=342 - _SERVERVERSIONREPLY._serialized_end=379 - _GRACEFULADMINREQUEST._serialized_start=381 - _GRACEFULADMINREQUEST._serialized_end=426 - _GRACEFULADMINRESPONSE._serialized_start=429 - _GRACEFULADMINRESPONSE._serialized_end=572 - _DUALTORACTIVE._serialized_start=752 - _DUALTORACTIVE._serialized_end=1080 - _GRACEFULRESTART._serialized_start=1082 - _GRACEFULRESTART._serialized_end=1180 -# @@protoc_insertion_point(module_scope) diff --git a/sonic-ycabled/proto_out/linkmgr_grpc_driver_pb2_grpc.py b/sonic-ycabled/proto_out/linkmgr_grpc_driver_pb2_grpc.py deleted file mode 100644 index d8ec9f358..000000000 --- a/sonic-ycabled/proto_out/linkmgr_grpc_driver_pb2_grpc.py +++ /dev/null @@ -1,259 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from proto_out import linkmgr_grpc_driver_pb2 as proto__out_dot_linkmgr__grpc__driver__pb2 - - -class DualToRActiveStub(object): - """Missing associated documentation comment in .proto file.""" - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.QueryAdminForwardingPortState = channel.unary_unary( - '/DualToRActive/QueryAdminForwardingPortState', - request_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.AdminRequest.SerializeToString, - response_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.AdminReply.FromString, - ) - self.SetAdminForwardingPortState = channel.unary_unary( - '/DualToRActive/SetAdminForwardingPortState', - request_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.AdminRequest.SerializeToString, - response_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.AdminReply.FromString, - ) - self.QueryOperationPortState = channel.unary_unary( - '/DualToRActive/QueryOperationPortState', - request_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.OperationRequest.SerializeToString, - response_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.OperationReply.FromString, - ) - self.QueryLinkState = channel.unary_unary( - '/DualToRActive/QueryLinkState', - request_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.LinkStateRequest.SerializeToString, - response_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.LinkStateReply.FromString, - ) - self.QueryServerVersion = channel.unary_unary( - '/DualToRActive/QueryServerVersion', - request_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.ServerVersionRequest.SerializeToString, - response_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.ServerVersionReply.FromString, - ) - - -class DualToRActiveServicer(object): - """Missing associated documentation comment in .proto file.""" - - def QueryAdminForwardingPortState(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SetAdminForwardingPortState(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def QueryOperationPortState(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def QueryLinkState(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def QueryServerVersion(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_DualToRActiveServicer_to_server(servicer, server): - rpc_method_handlers = { - 'QueryAdminForwardingPortState': grpc.unary_unary_rpc_method_handler( - servicer.QueryAdminForwardingPortState, - request_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.AdminRequest.FromString, - response_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.AdminReply.SerializeToString, - ), - 'SetAdminForwardingPortState': grpc.unary_unary_rpc_method_handler( - servicer.SetAdminForwardingPortState, - request_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.AdminRequest.FromString, - response_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.AdminReply.SerializeToString, - ), - 'QueryOperationPortState': grpc.unary_unary_rpc_method_handler( - servicer.QueryOperationPortState, - request_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.OperationRequest.FromString, - response_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.OperationReply.SerializeToString, - ), - 'QueryLinkState': grpc.unary_unary_rpc_method_handler( - servicer.QueryLinkState, - request_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.LinkStateRequest.FromString, - response_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.LinkStateReply.SerializeToString, - ), - 'QueryServerVersion': grpc.unary_unary_rpc_method_handler( - servicer.QueryServerVersion, - request_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.ServerVersionRequest.FromString, - response_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.ServerVersionReply.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'DualToRActive', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class DualToRActive(object): - """Missing associated documentation comment in .proto file.""" - - @staticmethod - def QueryAdminForwardingPortState(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DualToRActive/QueryAdminForwardingPortState', - proto__out_dot_linkmgr__grpc__driver__pb2.AdminRequest.SerializeToString, - proto__out_dot_linkmgr__grpc__driver__pb2.AdminReply.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def SetAdminForwardingPortState(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DualToRActive/SetAdminForwardingPortState', - proto__out_dot_linkmgr__grpc__driver__pb2.AdminRequest.SerializeToString, - proto__out_dot_linkmgr__grpc__driver__pb2.AdminReply.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def QueryOperationPortState(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DualToRActive/QueryOperationPortState', - proto__out_dot_linkmgr__grpc__driver__pb2.OperationRequest.SerializeToString, - proto__out_dot_linkmgr__grpc__driver__pb2.OperationReply.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def QueryLinkState(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DualToRActive/QueryLinkState', - proto__out_dot_linkmgr__grpc__driver__pb2.LinkStateRequest.SerializeToString, - proto__out_dot_linkmgr__grpc__driver__pb2.LinkStateReply.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def QueryServerVersion(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/DualToRActive/QueryServerVersion', - proto__out_dot_linkmgr__grpc__driver__pb2.ServerVersionRequest.SerializeToString, - proto__out_dot_linkmgr__grpc__driver__pb2.ServerVersionReply.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - -class GracefulRestartStub(object): - """Missing associated documentation comment in .proto file.""" - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.NotifyGracefulRestartStart = channel.unary_stream( - '/GracefulRestart/NotifyGracefulRestartStart', - request_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.GracefulAdminRequest.SerializeToString, - response_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.GracefulAdminResponse.FromString, - ) - - -class GracefulRestartServicer(object): - """Missing associated documentation comment in .proto file.""" - - def NotifyGracefulRestartStart(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_GracefulRestartServicer_to_server(servicer, server): - rpc_method_handlers = { - 'NotifyGracefulRestartStart': grpc.unary_stream_rpc_method_handler( - servicer.NotifyGracefulRestartStart, - request_deserializer=proto__out_dot_linkmgr__grpc__driver__pb2.GracefulAdminRequest.FromString, - response_serializer=proto__out_dot_linkmgr__grpc__driver__pb2.GracefulAdminResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'GracefulRestart', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class GracefulRestart(object): - """Missing associated documentation comment in .proto file.""" - - @staticmethod - def NotifyGracefulRestartStart(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream(request, target, '/GracefulRestart/NotifyGracefulRestartStart', - proto__out_dot_linkmgr__grpc__driver__pb2.GracefulAdminRequest.SerializeToString, - proto__out_dot_linkmgr__grpc__driver__pb2.GracefulAdminResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) From 7c5c88f79efab37006a29c90cc9542e9b8399afd Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Wed, 29 May 2024 15:41:21 +0000 Subject: [PATCH 26/31] Swapped logger for syslogger per prgeor review --- sonic-stormond/scripts/stormond | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sonic-stormond/scripts/stormond b/sonic-stormond/scripts/stormond index 023006859..81c706868 100755 --- a/sonic-stormond/scripts/stormond +++ b/sonic-stormond/scripts/stormond @@ -14,7 +14,7 @@ import shutil import json import time -from sonic_py_common import daemon_base, device_info, logger +from sonic_py_common import daemon_base, device_info, syslogger from swsscommon import swsscommon from sonic_platform_base.sonic_storage.storage_devices import StorageDevices, BLKDEV_BASE_PATH @@ -39,7 +39,7 @@ STORMOND_SYNC_TO_DISK_SECS = 86400 #one day STORAGEUTIL_LOAD_ERROR = 127 -log = logger.Logger(SYSLOG_IDENTIFIER) +log = syslogger.SysLogger(SYSLOG_IDENTIFIER) exit_code = 0 From 7b84cc1895927211d94f327a031bc2254ac1dad8 Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Wed, 29 May 2024 21:36:14 +0000 Subject: [PATCH 27/31] log_error --> log_info per review comment --- sonic-stormond/scripts/stormond | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonic-stormond/scripts/stormond b/sonic-stormond/scripts/stormond index 81c706868..17120da89 100755 --- a/sonic-stormond/scripts/stormond +++ b/sonic-stormond/scripts/stormond @@ -280,7 +280,7 @@ class DaemonStorage(daemon_base.DaemonBase): for storage_device, storage_object in self.storage.devices.items(): try: if storage_object is None: - self.log_error("Storage device '{}' does not have an instantiated object. Dynamic Information cannot be gathered.".format(storage_device)) + self.log_info("Storage device '{}' does not have an instantiated object. Dynamic Information cannot be gathered.".format(storage_device)) continue # Fetch the latest dynamic info From b31f591662ed371229a1a00889f805c469bb01c7 Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Thu, 30 May 2024 16:42:01 +0000 Subject: [PATCH 28/31] Made changes per prgeor review comments --- sonic-stormond/scripts/stormond | 20 ++++++++------------ sonic-stormond/setup.py | 5 ++--- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/sonic-stormond/scripts/stormond b/sonic-stormond/scripts/stormond index 17120da89..815c91ccb 100755 --- a/sonic-stormond/scripts/stormond +++ b/sonic-stormond/scripts/stormond @@ -22,19 +22,15 @@ from sonic_platform_base.sonic_storage.storage_devices import StorageDevices, BL # Constants ==================================================================== # -# TODO: Once we no longer support Python 2, we can eliminate this and get the -# name using the 'name' field (e.g., `signal.SIGINT.name`) starting with Python 3.5 -SIGNALS_TO_NAMES_DICT = dict((getattr(signal, n), n) - for n in dir(signal) if n.startswith('SIG') and '_' not in n) - SYSLOG_IDENTIFIER = "stormond" STORAGE_DEVICE_TABLE = "STORAGE_INFO" FSSTATS_SYNC_TIME_KEY = "FSSTATS_SYNC" +# This directory binds to /host/pmon/stormond/ on the host FSIO_RW_JSON_FILE = "/usr/share/stormond/fsio-rw-stats.json" -STORMOND_MAIN_THREAD_SLEEP_SECS = 3600 #one hour +STORMOND_PERIODIC_STATEDB_SYNC_SECS = 3600 #one hour STORMOND_SYNC_TO_DISK_SECS = 86400 #one day STORAGEUTIL_LOAD_ERROR = 127 @@ -53,7 +49,7 @@ class DaemonStorage(daemon_base.DaemonBase): def __init__(self, log_identifier): super(DaemonStorage, self).__init__(log_identifier) - self.timeout = STORMOND_MAIN_THREAD_SLEEP_SECS + self.timeout = STORMOND_PERIODIC_STATEDB_SYNC_SECS self.fsstats_sync_interval = STORMOND_SYNC_TO_DISK_SECS self.stop_event = threading.Event() self.state_db = None @@ -103,7 +99,7 @@ class DaemonStorage(daemon_base.DaemonBase): def _get_configdb_intervals(self): self.config_db = daemon_base.db_connect("CONFIG_DB") config_info = dict(self.config_db.hgetall('STORMOND_CONFIG|INTERVALS')) - self.timeout = int(config_info.get('daemon_polling_interval', STORMOND_MAIN_THREAD_SLEEP_SECS)) + self.timeout = int(config_info.get('daemon_polling_interval', STORMOND_PERIODIC_STATEDB_SYNC_SECS)) self.fsstats_sync_interval = int(config_info.get('fsstats_sync_interval', STORMOND_SYNC_TO_DISK_SECS)) self.log_info("Polling Interval set to {} seconds".format(self.timeout)) @@ -321,20 +317,20 @@ class DaemonStorage(daemon_base.DaemonBase): global exit_code if sig in FATAL_SIGNALS: - self.log_info("Caught signal '{}'".format(SIGNALS_TO_NAMES_DICT[sig])) + self.log_info("Caught signal '{}'".format(signal.Signals(sig).name)) self.log_info("Syncing latest procfs reads and writes to disk") self._sync_fsio_rw_json() - self.log_info("Exiting with {}".format(SIGNALS_TO_NAMES_DICT[sig])) + self.log_info("Exiting with {}".format(signal.Signals(sig).name)) # Make sure we exit with a non-zero code so that supervisor will try to restart us exit_code = 128 + sig self.stop_event.set() elif sig in NONFATAL_SIGNALS: - self.log_info("Caught signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) + self.log_info("Caught signal '{}' - ignoring...".format(signal.Signals(sig).name)) else: - self.log_warning("Caught unhandled signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) + self.log_warning("Caught unhandled signal '{}' - ignoring...".format(signal.Signals(sig).name)) # Main daemon logic def run(self): diff --git a/sonic-stormond/setup.py b/sonic-stormond/setup.py index 5ea252bca..0e2013829 100644 --- a/sonic-stormond/setup.py +++ b/sonic-stormond/setup.py @@ -18,11 +18,11 @@ 'wheel' ], install_requires=[ - 'enum34; python_version < "3.4"', + 'enum34', 'sonic-py-common', ], tests_require=[ - 'mock>=2.0.0; python_version < "3.3"', + 'mock>=2.0.0', 'pytest', 'pytest-cov', ], @@ -35,7 +35,6 @@ 'License :: OSI Approved :: Apache Software License', 'Natural Language :: English', 'Operating System :: POSIX :: Linux', - 'Programming Language :: Python :: 2.7', 'Topic :: System :: Hardware', ], keywords='sonic SONiC ssd Ssd SSD ssdmond storage stormond storagemond', From c83d0241b87d88eb6238ed8d78b66c9ab4a55344 Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Thu, 30 May 2024 21:44:32 +0000 Subject: [PATCH 29/31] Made changes per prgeor walkthrough review --- sonic-stormond/scripts/stormond | 101 +++++++++++++++++++------------- 1 file changed, 61 insertions(+), 40 deletions(-) diff --git a/sonic-stormond/scripts/stormond b/sonic-stormond/scripts/stormond index 815c91ccb..dd1834156 100755 --- a/sonic-stormond/scripts/stormond +++ b/sonic-stormond/scripts/stormond @@ -35,8 +35,6 @@ STORMOND_SYNC_TO_DISK_SECS = 86400 #one day STORAGEUTIL_LOAD_ERROR = 127 -log = syslogger.SysLogger(SYSLOG_IDENTIFIER) - exit_code = 0 # @@ -47,6 +45,8 @@ exit_code = 0 class DaemonStorage(daemon_base.DaemonBase): def __init__(self, log_identifier): + + self.log = syslogger.SysLogger(SYSLOG_IDENTIFIER) super(DaemonStorage, self).__init__(log_identifier) self.timeout = STORMOND_PERIODIC_STATEDB_SYNC_SECS @@ -59,8 +59,9 @@ class DaemonStorage(daemon_base.DaemonBase): # These booleans are for FSIO RW information reconciliation self.fsio_json_file_loaded = False - self.use_fsio_json_baseline = False self.statedb_storage_info_loaded = False + + self.use_fsio_json_baseline = False self.use_statedb_baseline = False # These dicts are to load info from disk/database into memory, respectively @@ -96,7 +97,7 @@ class DaemonStorage(daemon_base.DaemonBase): self._load_fsio_rw_json() self._determine_sot() - def _get_configdb_intervals(self): + def get_configdb_intervals(self): self.config_db = daemon_base.db_connect("CONFIG_DB") config_info = dict(self.config_db.hgetall('STORMOND_CONFIG|INTERVALS')) self.timeout = int(config_info.get('daemon_polling_interval', STORMOND_PERIODIC_STATEDB_SYNC_SECS)) @@ -109,20 +110,23 @@ class DaemonStorage(daemon_base.DaemonBase): # Get the total and latest FSIO reads and writes from JSON file def _load_fsio_rw_json(self): try: - if not os.path.exists(FSIO_RW_JSON_FILE): - self.log_info("{} not present.".format(FSIO_RW_JSON_FILE)) - return + if not os.path.exists(FSIO_RW_JSON_FILE): + self.log_info("{} not present.".format(FSIO_RW_JSON_FILE)) + return - with open(FSIO_RW_JSON_FILE, 'r') as f: - self.fsio_rw_json = json.load(f) - self.fsio_json_file_loaded = True + # Load JSON file + with open(FSIO_RW_JSON_FILE, 'r') as f: + self.fsio_rw_json = json.load(f) - for storage_device in self.storage.devices: - for field in self.statedb_json_sync_fields: + # Verify that none of the values in the JSON file are None + for storage_device in self.storage.devices: + for field in self.statedb_json_sync_fields: - value = self.fsio_rw_json[storage_device][field] + if self.fsio_rw_json[storage_device][field] == None: + self.log_warning("{}:{} value = None in JSON file".format(storage_device, field)) + return - self.fsio_rw_json[storage_device][field] = "0" if value == "null" else value + self.fsio_json_file_loaded = True except Exception as e: self.log_error("JSON file could not be loaded: {}".format(str(e))) @@ -131,7 +135,7 @@ class DaemonStorage(daemon_base.DaemonBase): # Sync the total and latest procfs reads and writes from STATE_DB to JSON file on disk - def _sync_fsio_rw_json(self): + def sync_fsio_rw_json(self): self.log_info("Syncing total and latest procfs reads and writes from STATE_DB to JSON file") @@ -146,10 +150,17 @@ class DaemonStorage(daemon_base.DaemonBase): with open(FSIO_RW_JSON_FILE, 'w+') as f: json.dump(json_file_dict, f) - self.state_db.hset("{}|{}".format(STORAGE_DEVICE_TABLE,FSSTATS_SYNC_TIME_KEY), "successful_sync_time", str(self.fsio_sync_time)) + + return True except Exception as ex: self.log_error("Unable to sync state_db to disk: {}".format(str(ex))) + return False + + + # Update the successful sync time to STATE_DB + def write_sync_time_statedb(self): + self.state_db.hset("{}|{}".format(STORAGE_DEVICE_TABLE,FSSTATS_SYNC_TIME_KEY), "successful_sync_time", str(self.fsio_sync_time)) # Run a sanity check on the state_db. If successful, get total, latest # FSIO reads and writes for each storage device from STATE_DB @@ -173,7 +184,9 @@ class DaemonStorage(daemon_base.DaemonBase): value = self.state_db.hget('STORAGE_INFO|{}'.format(storage_device), field) self.fsio_rw_statedb[storage_device][field] = "0" if value is None else value - if value is None: self.log_warning("{}:{} value = None in StateDB".format(storage_device, field)) + if value is None: + self.log_warning("{}:{} value = None in StateDB".format(storage_device, field)) + return self.statedb_storage_info_loaded = True except Exception as e: @@ -183,21 +196,24 @@ class DaemonStorage(daemon_base.DaemonBase): def _determine_sot(self): # This daemon considers the storage information values held in the STATE_DB to be its - # Source of Truth. + # Source of Truth. + + # If stormond is coming back up after a daemon crash, storage information would be saved in the + # STATE_DB. In that scenario, we use the STATE_DB information as the SoT to reconcile the FSIO + # reads and writes values. + if self.statedb_storage_info_loaded == True: + self.use_fsio_json_baseline = False + self.use_statedb_baseline = True # If the state_db information did not load successfully but the JSON file did, # we consider the JSON file to be the SoT. - if self.statedb_storage_info_loaded == False and self.fsio_json_file_loaded == True: + elif self.statedb_storage_info_loaded == False and self.fsio_json_file_loaded == True: self.use_fsio_json_baseline = True self.use_statedb_baseline = False - # If stormond is coming back up after a daemon crash, storage information would be saved in the - # STATE_DB. In that scenario, we use the STATE_DB information as the SoT and reconcile the FSIO - # reads and writes values. - elif self.statedb_storage_info_loaded == True: - self.use_fsio_json_baseline = False - self.use_statedb_baseline = True + # If neither the STATE_DB nor the JSON file information was loaded, we consider + # that akin to an INIT state. def _reconcile_fsio_rw_values(self, fsio_dict, device): @@ -239,14 +255,14 @@ class DaemonStorage(daemon_base.DaemonBase): # Update the Storage device info to State DB - def update_storage_info_status_db(self, disk_device, disk_fields, kvp_dict): + def update_storage_info_status_db(self, disk_device, kvp_dict): fvp = swsscommon.FieldValuePairs([(field, str(value)) for field, value in kvp_dict.items()]) self.device_table.set(disk_device, fvp) # Get Static attributes and update the State DB, once - def get_static_fields(self): + def get_static_fields_update_state_db(self): # Get relevant information about each Storage Device on the switch for storage_device, storage_object in self.storage.devices.items(): @@ -264,13 +280,13 @@ class DaemonStorage(daemon_base.DaemonBase): self.log_info("Storage Device: {}, Device Model: {}, Serial: {}".format(storage_device, static_kvp_dict["device_model"], static_kvp_dict["serial"])) # update Storage Device Status to DB - self.update_storage_info_status_db(storage_device, self.static_fields, static_kvp_dict) + self.update_storage_info_status_db(storage_device, static_kvp_dict) except Exception as ex: - self.log_error("get_static_fields() failed with: {}".format(str(ex))) + self.log_error("get_static_fields_update_state_db() failed with: {}".format(str(ex))) # Get Dynamic attributes and update the State DB - def get_dynamic_fields(self): + def get_dynamic_fields_update_state_db(self): # Get relevant information about each storage disk on the device for storage_device, storage_object in self.storage.devices.items(): @@ -302,11 +318,11 @@ class DaemonStorage(daemon_base.DaemonBase): self.log_info("Disk IO Reads: {}, Disk IO Writes: {}, Reserved Blocks: {}".format(dynamic_kvp_dict["disk_io_reads"], dynamic_kvp_dict["disk_io_writes"], \ dynamic_kvp_dict["reserved_blocks"])) - # update Storage Device Status to DB - self.update_storage_info_status_db(storage_device, self.dynamic_fields, dynamic_kvp_dict) + # Update storage device statistics to STATE_DB + self.update_storage_info_status_db(storage_device, dynamic_kvp_dict) except Exception as ex: - self.log_info("get_dynamic_fields() failed with: {}".format(str(ex))) + self.log_info("get_dynamic_fields_update_state_db() failed with: {}".format(str(ex))) # Override signal handler from DaemonBase @@ -318,9 +334,11 @@ class DaemonStorage(daemon_base.DaemonBase): if sig in FATAL_SIGNALS: self.log_info("Caught signal '{}'".format(signal.Signals(sig).name)) - - self.log_info("Syncing latest procfs reads and writes to disk") - self._sync_fsio_rw_json() + + if self.sync_fsio_rw_json(): + self.write_sync_time_statedb() + else: + self.log_warning("Unable to sync latest and total procfs RW to disk") self.log_info("Exiting with {}".format(signal.Signals(sig).name)) @@ -337,10 +355,10 @@ class DaemonStorage(daemon_base.DaemonBase): # Connect to CONFIG_DB and get polling and sync intervals -- # this is to be able to dynamically configure the polling and sync times. - self._get_configdb_intervals() + self.get_configdb_intervals() # Repeatedly read and update Dynamic Fields to the StateDB - self.get_dynamic_fields() + self.get_dynamic_fields_update_state_db() if self.stop_event.wait(self.timeout): # We received a fatal signal @@ -353,7 +371,10 @@ class DaemonStorage(daemon_base.DaemonBase): elapsed_time = time.time() - self.fsio_sync_time if (elapsed_time > self.fsstats_sync_interval) or ((self.fsstats_sync_interval - elapsed_time) < self.timeout): - self._sync_fsio_rw_json() + if self.sync_fsio_rw_json(): + self.write_sync_time_statedb() + else: + self.log_warning("Unable to sync latest and total procfs RW to disk") return True # @@ -367,7 +388,7 @@ def main(): stormon.log_info("Starting Storage Monitoring Daemon") # Read and update Static Fields to the StateDB once - stormon.get_static_fields() + stormon.get_static_fields_update_state_db() while stormon.run(): pass From ea45fae2121ea6dbcbcc0639014a167c4c7e62dd Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Thu, 30 May 2024 22:09:34 +0000 Subject: [PATCH 30/31] Test mods corresponding to daemon nunction name mods --- sonic-stormond/tests/test_DaemonStorage.py | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/sonic-stormond/tests/test_DaemonStorage.py b/sonic-stormond/tests/test_DaemonStorage.py index f973f0781..5c1eed33e 100644 --- a/sonic-stormond/tests/test_DaemonStorage.py +++ b/sonic-stormond/tests/test_DaemonStorage.py @@ -72,14 +72,14 @@ def new_mock_factory(self, key): assert(list(stormon_daemon.storage.devices.keys()) == ['sda']) @patch('os.path.exists', MagicMock(return_value=True)) - @patch('json.load', MagicMock(return_value={})) + @patch('json.load', MagicMock(return_value={'sda':{}})) @patch('sonic_py_common.daemon_base.db_connect', MagicMock()) def test_load_fsio_rw_json(self): with patch('builtins.open', new_callable=mock_open, read_data='{}') as mock_fd: stormon_daemon = stormond.DaemonStorage(log_identifier) - assert stormon_daemon.fsio_json_file_loaded == True + assert stormon_daemon.fsio_json_file_loaded == False @patch('os.path.exists', MagicMock(return_value=True)) @@ -93,12 +93,12 @@ def test_load_fsio_rw_json_exception(self): assert stormon_daemon.fsio_json_file_loaded == False @patch('sonic_py_common.daemon_base.db_connect') - def test_get_configdb_intervals(self, mock_daemon_base): + def testget_configdb_intervals(self, mock_daemon_base): mock_daemon_base = MagicMock() stormon_daemon = stormond.DaemonStorage(log_identifier) - stormon_daemon._get_configdb_intervals() + stormon_daemon.get_configdb_intervals() assert mock_daemon_base.call_count == 0 @@ -109,7 +109,7 @@ def test_sync_fsio_rw_json_exception(self): stormon_daemon = stormond.DaemonStorage(log_identifier) with patch('builtins.open', new_callable=mock_open, read_data='{}') as mock_fd: - stormon_daemon._sync_fsio_rw_json() + stormon_daemon.sync_fsio_rw_json() assert stormon_daemon.state_db.call_count == 0 @@ -120,7 +120,7 @@ def test_sync_fsio_rw_json_happy(self): stormon_daemon = stormond.DaemonStorage(log_identifier) with patch('builtins.open', new_callable=mock_open, read_data='{}') as mock_fd: - stormon_daemon._sync_fsio_rw_json() + stormon_daemon.sync_fsio_rw_json() assert stormon_daemon.state_db.call_count == 0 @@ -168,7 +168,7 @@ def test_reconcile_fsio_rw_values_daemon_crash(self): def test_update_storage_info_status_db(self): stormon_daemon = stormond.DaemonStorage(log_identifier) - stormon_daemon.update_storage_info_status_db('sda', 'mock_field', fsio_json_dict['sda']) + stormon_daemon.update_storage_info_status_db('sda', fsio_json_dict['sda']) assert stormon_daemon.device_table.getKeys() == ['sda'] @@ -182,7 +182,7 @@ def test_get_static_fields(self): mock_storage_device_object.get_serial.return_value = "T1000" stormon_daemon.storage.devices = {'sda' : mock_storage_device_object} - stormon_daemon.get_static_fields() + stormon_daemon.get_static_fields_update_state_db() assert stormon_daemon.device_table.getKeys() == ['sda'] assert stormon_daemon.device_table.get('sda') == {'device_model': 'Skynet', 'serial': 'T1000'} @@ -203,7 +203,7 @@ def test_get_dynamic_fields(self): mock_storage_device_object.get_reserved_blocks.return_value = "3" stormon_daemon.storage.devices = {'sda' : mock_storage_device_object} - stormon_daemon.get_dynamic_fields() + stormon_daemon.get_dynamic_fields_update_state_db() assert stormon_daemon.device_table.getKeys() == ['sda'] for field, value in dynamic_dict.items(): @@ -213,7 +213,7 @@ def test_get_dynamic_fields(self): @patch('sonic_py_common.daemon_base.db_connect', MagicMock()) def test_signal_handler(self): stormon_daemon = stormond.DaemonStorage(log_identifier) - stormon_daemon._sync_fsio_rw_json = MagicMock() + stormon_daemon.sync_fsio_rw_json = MagicMock() stormon_daemon.stop_event.set = MagicMock() stormon_daemon.log_info = MagicMock() @@ -235,7 +235,7 @@ def test_signal_handler(self): # Test SIGINT test_signal = stormond.signal.SIGINT stormon_daemon.signal_handler(test_signal, None) - assert stormon_daemon.log_info.call_count == 3 + assert stormon_daemon.log_info.call_count == 2 stormon_daemon.log_info.assert_called_with("Exiting with SIGINT") assert stormon_daemon.log_warning.call_count == 0 assert stormon_daemon.stop_event.set.call_count == 1 @@ -249,7 +249,7 @@ def test_signal_handler(self): # Test SIGTERM test_signal = stormond.signal.SIGTERM stormon_daemon.signal_handler(test_signal, None) - assert stormon_daemon.log_info.call_count == 3 + assert stormon_daemon.log_info.call_count == 2 stormon_daemon.log_info.assert_called_with("Exiting with SIGTERM") assert stormon_daemon.log_warning.call_count == 0 assert stormon_daemon.stop_event.set.call_count == 1 @@ -273,15 +273,15 @@ def test_signal_handler(self): @patch('sonic_py_common.daemon_base.db_connect', MagicMock()) def test_run(self): stormon_daemon = stormond.DaemonStorage(log_identifier) - stormon_daemon.get_dynamic_fields = MagicMock() + stormon_daemon.get_dynamic_fields_update_state_db = MagicMock() def mock_intervals(): stormon_daemon.timeout = 10 stormon_daemon.fsstats_sync_interval = 30 - with patch.object(stormon_daemon, '_get_configdb_intervals', new=mock_intervals): + with patch.object(stormon_daemon, 'get_configdb_intervals', new=mock_intervals): stormon_daemon.run() - assert stormon_daemon.get_dynamic_fields.call_count == 1 + assert stormon_daemon.get_dynamic_fields_update_state_db.call_count == 1 From c36f40d2b37f207c3cadc540a7e62b27b57dfc15 Mon Sep 17 00:00:00 2001 From: Ashwin Srinivasan Date: Thu, 30 May 2024 23:56:21 +0000 Subject: [PATCH 31/31] More Uts to pass coverage --- sonic-stormond/tests/test_DaemonStorage.py | 25 ++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/sonic-stormond/tests/test_DaemonStorage.py b/sonic-stormond/tests/test_DaemonStorage.py index 5c1eed33e..dafb05fd9 100644 --- a/sonic-stormond/tests/test_DaemonStorage.py +++ b/sonic-stormond/tests/test_DaemonStorage.py @@ -41,6 +41,7 @@ fsio_dict = {"total_fsio_reads": "", "total_fsio_writes": "", "latest_fsio_reads": "1000", "latest_fsio_writes": "2000"} fsio_json_dict = { 'sda' : {"total_fsio_reads": "10500", "total_fsio_writes": "21000", "latest_fsio_reads": "1000", "latest_fsio_writes": "2000"}} +bad_fsio_json_dict = { 'sda' : {"total_fsio_reads": None, "total_fsio_writes": "21000", "latest_fsio_reads": "1000", "latest_fsio_writes": "2000"}} fsio_statedb_dict = { 'sda' : {"total_fsio_reads": "10500", "total_fsio_writes": "21000", "latest_fsio_reads": "200", "latest_fsio_writes": "400"}} dynamic_dict = {'firmware': 'ILLBBK', 'health': '40', 'temperature': '5000', 'latest_fsio_reads': '150', 'latest_fsio_writes': '270', 'disk_io_reads': '1000', 'disk_io_writes': '2000', 'reserved_blocks': '3'} @@ -72,14 +73,24 @@ def new_mock_factory(self, key): assert(list(stormon_daemon.storage.devices.keys()) == ['sda']) @patch('os.path.exists', MagicMock(return_value=True)) - @patch('json.load', MagicMock(return_value={'sda':{}})) + @patch('json.load', MagicMock(return_value=bad_fsio_json_dict)) @patch('sonic_py_common.daemon_base.db_connect', MagicMock()) - def test_load_fsio_rw_json(self): + def test_load_fsio_rw_json_false(self): with patch('builtins.open', new_callable=mock_open, read_data='{}') as mock_fd: stormon_daemon = stormond.DaemonStorage(log_identifier) assert stormon_daemon.fsio_json_file_loaded == False + + @patch('os.path.exists', MagicMock(return_value=True)) + @patch('json.load', MagicMock(return_value=fsio_json_dict)) + @patch('sonic_py_common.daemon_base.db_connect', MagicMock()) + def test_load_fsio_rw_json_true(self): + + with patch('builtins.open', new_callable=mock_open, read_data='{}') as mock_fd: + stormon_daemon = stormond.DaemonStorage(log_identifier) + + assert stormon_daemon.fsio_json_file_loaded == True @patch('os.path.exists', MagicMock(return_value=True)) @@ -209,6 +220,16 @@ def test_get_dynamic_fields(self): for field, value in dynamic_dict.items(): assert stormon_daemon.device_table.get('sda')[field] == value + @patch('sonic_py_common.daemon_base.db_connect', MagicMock()) + @patch('json.dump', MagicMock()) + @patch('time.time', MagicMock(return_value=1000)) + def test_write_sync_time_statedb(self): + stormon_daemon = stormond.DaemonStorage(log_identifier) + stormon_daemon.sync_fsio_rw_json = MagicMock(return_value=True) + + stormon_daemon.write_sync_time_statedb() + assert stormon_daemon.state_db.call_count == 0 + @patch('sonic_py_common.daemon_base.db_connect', MagicMock()) def test_signal_handler(self):