From 1adf47b20c86b37a60c64a25ecacdc8eb49c3903 Mon Sep 17 00:00:00 2001 From: Alexander Allen Date: Tue, 18 May 2021 12:18:51 -0400 Subject: [PATCH] [chassisd] Add script to initialize chassis info in STATE_DB (#183) #### Description I added a new script `chassis_db_init` that uploads chassis hardware information such as serial number, model number and hardware revision to the STATE_DB under the CHASSIS_INFO table. #### Motivation and Context I made this change in order to expose the chassis hardware information to SONiC user space and allow CLI utilities to access it in order to expose it to the user. --- sonic-chassisd/scripts/chassis_db_init | 105 +++++++++++++++++++ sonic-chassisd/setup.py | 1 + sonic-chassisd/tests/mock_platform.py | 9 ++ sonic-chassisd/tests/test_chassis_db_init.py | 38 +++++++ 4 files changed, 153 insertions(+) create mode 100644 sonic-chassisd/scripts/chassis_db_init create mode 100644 sonic-chassisd/tests/test_chassis_db_init.py diff --git a/sonic-chassisd/scripts/chassis_db_init b/sonic-chassisd/scripts/chassis_db_init new file mode 100644 index 000000000000..b1aafb8817f4 --- /dev/null +++ b/sonic-chassisd/scripts/chassis_db_init @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 + +""" + chassis_db_init + Chassis information update tool for SONiC + This tool runs one time at the launch of the platform monitor in order to populate STATE_DB with chassis information such as model, serial number, and revision. +""" + +try: + import os + import sys + + from sonic_py_common import daemon_base, logger + + # If unit testing is occurring, mock swsscommon and module_base + if os.getenv("CHASSIS_DB_INIT_UNIT_TESTING") == "1": + from tests import mock_swsscommon as swsscommon + else: + from swsscommon import swsscommon +except ImportError as e: + raise ImportError(str(e) + " - required module not found") + +# +# Constants +# + +SYSLOG_IDENTIFIER = "chassis_db_init" + +CHASSIS_INFO_TABLE = 'CHASSIS_INFO' +CHASSIS_INFO_KEY_TEMPLATE = 'chassis {}' +CHASSIS_INFO_CARD_NUM_FIELD = 'module_num' +CHASSIS_INFO_SERIAL_FIELD = 'serial' +CHASSIS_INFO_MODEL_FIELD = 'model' +CHASSIS_INFO_REV_FIELD = 'revision' + +CHASSIS_LOAD_ERROR = 1 + +NOT_AVAILABLE = 'N/A' + +# +# Helper functions ============================================================= +# + +# try get information from platform API and return a default value if caught NotImplementedError + + +def try_get(callback, *args, **kwargs): + """ + Handy function to invoke the callback and catch NotImplementedError + :param callback: Callback to be invoked + :param args: Arguments to be passed to callback + :param kwargs: Default return value if exception occur + :return: Default return value if exception occur else return value of the callback + """ + default = kwargs.get('default', NOT_AVAILABLE) + try: + ret = callback(*args) + if ret is None: + ret = default + except NotImplementedError: + ret = default + + return ret + +# +# Functions +# + +def provision_db(platform_chassis, log): + # Init state db connection + state_db = daemon_base.db_connect("STATE_DB") + chassis_table = swsscommon.Table(state_db, CHASSIS_INFO_TABLE) + + # Populate DB with chassis hardware info + fvs = swsscommon.FieldValuePairs([ + (CHASSIS_INFO_SERIAL_FIELD, try_get(platform_chassis.get_serial)), + (CHASSIS_INFO_MODEL_FIELD, try_get(platform_chassis.get_model)), + (CHASSIS_INFO_REV_FIELD, try_get(platform_chassis.get_revision)) + ]) + chassis_table.set(CHASSIS_INFO_KEY_TEMPLATE.format(1), fvs) + log.log_info("STATE_DB provisioned with chassis info.") + + return chassis_table + + +# +# Main +# + +def main(): + log = logger.Logger(SYSLOG_IDENTIFIER) + log.log_info("Provisioning Database with Chassis Info...") + + # Load platform api class + try: + import sonic_platform.platform + platform_chassis = sonic_platform.platform.Platform().get_chassis() + except Exception as e: + log.log_error("Failed to load chassis due to {}".format(repr(e))) + sys.exit(CHASSIS_LOAD_ERROR) + + provision_db(platform_chassis, log) + +if __name__ == '__main__': + main() diff --git a/sonic-chassisd/setup.py b/sonic-chassisd/setup.py index 271c7a7942df..6fcfb51f3ca2 100644 --- a/sonic-chassisd/setup.py +++ b/sonic-chassisd/setup.py @@ -15,6 +15,7 @@ ], scripts=[ 'scripts/chassisd', + 'scripts/chassis_db_init' ], setup_requires=[ 'pytest-runner', diff --git a/sonic-chassisd/tests/mock_platform.py b/sonic-chassisd/tests/mock_platform.py index c878fafd6b86..f13356c52637 100644 --- a/sonic-chassisd/tests/mock_platform.py +++ b/sonic-chassisd/tests/mock_platform.py @@ -92,3 +92,12 @@ def get_module_index(self, module_name): def init_midplane_switch(self): return True + + def get_serial(self): + return "Serial No" + + def get_model(self): + return "Model A" + + def get_revision(self): + return "Rev C" diff --git a/sonic-chassisd/tests/test_chassis_db_init.py b/sonic-chassisd/tests/test_chassis_db_init.py new file mode 100644 index 000000000000..e9a9560bae63 --- /dev/null +++ b/sonic-chassisd/tests/test_chassis_db_init.py @@ -0,0 +1,38 @@ +import os +import sys +from imp import load_source + +from mock import Mock, MagicMock, patch +from sonic_py_common import daemon_base + +from .mock_platform import MockChassis, MockModule +from .mock_module_base import ModuleBase + +SYSLOG_IDENTIFIER = 'chassis_db_init_test' +NOT_AVAILABLE = 'N/A' + +daemon_base.db_connect = MagicMock() + +test_path = os.path.dirname(os.path.abspath(__file__)) +modules_path = os.path.dirname(test_path) +scripts_path = os.path.join(modules_path, "scripts") +sys.path.insert(0, modules_path) + +os.environ["CHASSIS_DB_INIT_UNIT_TESTING"] = "1" +load_source('chassis_db_init', scripts_path + '/chassis_db_init') +from chassis_db_init import * + + +def test_provision_db(): + chassis = MockChassis() + log = MagicMock() + serial = "Serial No" + model = "Model A" + revision = "Rev C" + + chassis_table = provision_db(chassis, log) + + fvs = chassis_table.get(CHASSIS_INFO_KEY_TEMPLATE.format(1)) + assert serial == fvs[CHASSIS_INFO_SERIAL_FIELD] + assert model == fvs[CHASSIS_INFO_MODEL_FIELD] + assert revision == fvs[CHASSIS_INFO_REV_FIELD]