diff --git a/sonic-xcvrd/scripts/xcvrd b/sonic-xcvrd/scripts/xcvrd new file mode 100644 index 0000000000..f9d7b47f79 --- /dev/null +++ b/sonic-xcvrd/scripts/xcvrd @@ -0,0 +1,449 @@ +#!/usr/bin/env python2 + +""" + Xcvrd + Transceiver information update daemon for SONiC +""" + +try: + import getopt + import os + import imp + import signal + import subprocess + import sys + import syslog + import time + import threading + from swsscommon import swsscommon +except ImportError, e: + raise ImportError (str(e) + " - required module not found") + +#============================= Constants ============================= + +VERSION = '1.0' + +SYSLOG_IDENTIFIER = os.path.basename(__file__) + +PLATFORM_ROOT_PATH = '/usr/share/sonic/device' +SONIC_CFGGEN_PATH = '/usr/local/bin/sonic-cfggen' +MINIGRAPH_PATH = '/etc/sonic/minigraph.xml' +HWSKU_KEY = 'DEVICE_METADATA.localhost.hwsku' +PLATFORM_KEY = 'DEVICE_METADATA.localhost.platform' + +PLATFORM_SPECIFIC_MODULE_NAME = "sfputil" +PLATFORM_SPECIFIC_CLASS_NAME = "SfpUtil" + +# Global platform-specific sfputil class instance +platform_sfputil = None + +# platform directory in base image +PLATFORM_ROOT = "/usr/share/sonic/device" + +# platform root directory inside docker +PLATFORM_ROOT_DOCKER = "/usr/share/sonic/platform" + +REDIS_HOSTNAME = "localhost" +REDIS_PORT = 6379 +REDIS_TIMEOUT_MSECS = 0 +SELECT_TIMEOUT_MSECS = 1000 + +DOM_INFO_UPDATE_PERIOD_SECS = 60 +TIME_FOR_SFP_READY_SECS = 1 + +SFP_STATUS_INSERTED = '1' +SFP_STATUS_REMOVED = '0' + +PHYSICAL_PORT_NOT_EXIST = -1 +SFP_EEPROM_NOT_READY = -2 + +TEMP_UNIT = 'C' +VOLT_UNIT = 'Volts' +POWER_UNIT = 'dBm' +BIAS_UNIT = 'mA' + +#========================== Syslog wrappers ========================== + +def log_info(msg, also_print_to_console=False): + syslog.openlog(SYSLOG_IDENTIFIER) + syslog.syslog(syslog.LOG_INFO, msg) + syslog.closelog() + + if also_print_to_console: + print msg + +def log_warning(msg, also_print_to_console=False): + syslog.openlog(SYSLOG_IDENTIFIER) + syslog.syslog(syslog.LOG_WARNING, msg) + syslog.closelog() + + if also_print_to_console: + print msg + +def log_error(msg, also_print_to_console=False): + syslog.openlog(SYSLOG_IDENTIFIER) + syslog.syslog(syslog.LOG_ERR, msg) + syslog.closelog() + + if also_print_to_console: + print msg + +#========================== Signal Handling ========================== + +def signal_handler(sig, frame): + if sig == signal.SIGHUP: + log_info("Caught SIGHUP - ignoring...") + return + elif sig == signal.SIGINT: + log_info("Caught SIGINT - exiting...") + sys.exit(128 + sig) + elif sig == signal.SIGTERM: + log_info("Caught SIGTERM - exiting...") + sys.exit(128 + sig) + else: + log_warning("Caught unhandled signal '" + sig + "'") + return + + +#============ Functions to load platform-specific classes ============ + +# Returns platform and HW SKU +def get_platform_and_hwsku(): + proc = subprocess.Popen([SONIC_CFGGEN_PATH, '-H', '-v', PLATFORM_KEY], + stdout=subprocess.PIPE, + shell=False, + stderr=subprocess.STDOUT) + stdout = proc.communicate()[0] + proc.wait() + platform = stdout.rstrip('\n') + + proc = subprocess.Popen([SONIC_CFGGEN_PATH, '-m', MINIGRAPH_PATH, '-v', HWSKU_KEY], + stdout=subprocess.PIPE, + shell=False, + stderr=subprocess.STDOUT) + stdout = proc.communicate()[0] + proc.wait() + hwsku = stdout.rstrip('\n') + + return (platform, hwsku) + +# Loads platform specific sfputil module from source +def load_platform_sfputil(): + global platform_sfputil + + # Get platform and hwsku + (platform, hwsku) = get_platform_and_hwsku() + + # Load platform module from source + platform_path = PLATFORM_ROOT_DOCKER + hwsku_path = "/".join([platform_path, hwsku]) + + try: + module_file = "/".join([platform_path, "plugins", PLATFORM_SPECIFIC_MODULE_NAME + ".py"]) + module = imp.load_source(PLATFORM_SPECIFIC_MODULE_NAME, module_file) + except IOError, e: + log_error("Failed to load platform module '%s': %s" % (PLATFORM_SPECIFIC_MODULE_NAME, str(e)), True) + return -1 + + try: + platform_sfputil_class = getattr(module, PLATFORM_SPECIFIC_CLASS_NAME) + platform_sfputil = platform_sfputil_class() + except AttributeError, e: + log_error("Failed to instantiate '%s' class: %s" % (PLATFORM_SPECIFIC_CLASS_NAME, str(e)), True) + return -2 + + return 0 + +# Returns path to port config file +def get_path_to_port_config_file(): + # Get platform and hwsku + (platform, hwsku) = get_platform_and_hwsku() + + # Load platform module from source + platform_path = PLATFORM_ROOT_DOCKER + hwsku_path = "/".join([platform_path, hwsku]) + + # First check for the presence of the new 'port_config.ini' file + port_config_file_path = "/".join([hwsku_path, "port_config.ini"]) + if not os.path.isfile(port_config_file_path): + # port_config.ini doesn't exist. Try loading the legacy 'portmap.ini' file + port_config_file_path = "/".join([hwsku_path, "portmap.ini"]) + + return port_config_file_path + +# find out the underneath physical port list by logical name +def logical_port_name_to_physical_port_list(port_name): + if port_name.startswith("Ethernet"): + if platform_sfputil.is_logical_port(port_name): + return platform_sfputil.get_logical_to_physical(port_name) + else: + print "Error: Invalid port '%s'" % port_name + return None + else: + return [int(port_name)] + +# Returns, +# port_num if physical +# logical_port:port_num if logical port and is a ganged port +# logical_port if logical and not ganged +def get_physical_port_name(logical_port, physical_port, ganged): + if logical_port == physical_port: + return logical_port + elif ganged: + return logical_port + ":%d (ganged)" % physical_port + else: + return logical_port + +def strip_unit_and_beautify(value, unit): + # Strip unit from raw data + width = len(unit) + if value[-width:] == unit: + value = value[:-width] + + return value + +# Remove unnecessary unit from the raw data +def beautify_dom_info_dict(dom_info_dict): + dom_info_dict['temperature'] = strip_unit_and_beautify(dom_info_dict['temperature'], TEMP_UNIT) + dom_info_dict['voltage'] = strip_unit_and_beautify(dom_info_dict['voltage'], VOLT_UNIT) + dom_info_dict['rx1power'] = strip_unit_and_beautify(dom_info_dict['rx1power'], POWER_UNIT) + dom_info_dict['rx2power'] = strip_unit_and_beautify(dom_info_dict['rx2power'], POWER_UNIT) + dom_info_dict['rx3power'] = strip_unit_and_beautify(dom_info_dict['rx3power'], POWER_UNIT) + dom_info_dict['rx4power'] = strip_unit_and_beautify(dom_info_dict['rx4power'], POWER_UNIT) + dom_info_dict['tx1bias'] = strip_unit_and_beautify(dom_info_dict['tx1bias'], BIAS_UNIT) + dom_info_dict['tx2bias'] = strip_unit_and_beautify(dom_info_dict['tx2bias'], BIAS_UNIT) + dom_info_dict['tx3bias'] = strip_unit_and_beautify(dom_info_dict['tx3bias'], BIAS_UNIT) + dom_info_dict['tx4bias'] = strip_unit_and_beautify(dom_info_dict['tx4bias'], BIAS_UNIT) + dom_info_dict['tx1power'] = strip_unit_and_beautify(dom_info_dict['tx1power'], POWER_UNIT) + dom_info_dict['tx2power'] = strip_unit_and_beautify(dom_info_dict['tx2power'], POWER_UNIT) + dom_info_dict['tx3power'] = strip_unit_and_beautify(dom_info_dict['tx3power'], POWER_UNIT) + dom_info_dict['tx4power'] = strip_unit_and_beautify(dom_info_dict['tx4power'], POWER_UNIT) + +# update all the sfp info to db +def post_port_sfp_info_to_db(logical_port_name, table): + ganged_port = False + ganged_member_num = 1 + + physical_port_list = logical_port_name_to_physical_port_list(logical_port_name) + if physical_port_list is None: + log_error("Error: No physical ports found for logical port '%s'" % logical_port_name) + return PHYSICAL_PORT_NOT_EXIST + + if len(physical_port_list) > 1: + ganged_port = True + + for physical_port in physical_port_list: + port_name = get_physical_port_name(logical_port_name, ganged_member_num, ganged_port) + ganged_member_num += 1 + + try: + port_info_dict = platform_sfputil.get_transceiver_info_dict(physical_port) + if port_info_dict is not None: + fvs = swsscommon.FieldValuePairs([('type', port_info_dict['type']), + ('hardwarerev', port_info_dict['hardwarerev']), + ('serialnum', port_info_dict['serialnum']), + ('manufacturename', port_info_dict['manufacturename']), + ('modelname', port_info_dict['modelname'])]) + table.set(port_name, fvs) + else: + return SFP_EEPROM_NOT_READY + + except NotImplementedError: + log_error("This functionality is currently not implemented for this platform") + sys.exit(3) + +# update dom sensor info to db +def post_port_dom_info_to_db(logical_port_name, table): + ganged_port = False + ganged_member_num = 1 + + physical_port_list = logical_port_name_to_physical_port_list(logical_port_name) + if physical_port_list is None: + log_error("Error: No physical ports found for logical port '%s'" % logical_port_name) + return PHYSICAL_PORT_NOT_EXIST + + if len(physical_port_list) > 1: + ganged_port = True + + for physical_port in physical_port_list: + port_name = get_physical_port_name(logical_port_name, ganged_member_num, ganged_port) + ganged_member_num += 1 + + try: + dom_info_dict = platform_sfputil.get_transceiver_dom_info_dict(physical_port) + if dom_info_dict is not None: + beautify_dom_info_dict(dom_info_dict) + fvs = swsscommon.FieldValuePairs([('temperature', dom_info_dict['temperature']), + ('voltage', dom_info_dict['voltage']), + ('rx1power', dom_info_dict['rx1power']), + ('rx2power', dom_info_dict['rx2power']), + ('rx3power', dom_info_dict['rx3power']), + ('rx4power', dom_info_dict['rx4power']), + ('tx1bias', dom_info_dict['tx1bias']), + ('tx2bias', dom_info_dict['tx2bias']), + ('tx3bias', dom_info_dict['tx3bias']), + ('tx4bias', dom_info_dict['tx4bias']), + ('tx1power', dom_info_dict['tx1power']), + ('tx2power', dom_info_dict['tx2power']), + ('tx3power', dom_info_dict['tx3power']), + ('tx4power', dom_info_dict['tx4power'])]) + table.set(port_name, fvs) + else: + return SFP_EEPROM_NOT_READY + + except NotImplementedError: + log_error("This functionality is currently not implemented for this platform") + sys.exit(3) + +# del sfp and dom info from db +def del_port_sfp_dom_info_to_db(logical_port_name, int_tbl, dom_tbl): + ganged_port = False + ganged_member_num = 1 + + physical_port_list = logical_port_name_to_physical_port_list(logical_port_name) + if physical_port_list is None: + log_error("Error: No physical ports found for logical port '%s'" % logical_port_name) + return PHYSICAL_PORT_NOT_EXIST + + if len(physical_port_list) > 1: + ganged_port = True + + for physical_port in physical_port_list: + port_name = get_physical_port_name(logical_port_name, ganged_member_num, ganged_port) + ganged_member_num += 1 + + try: + int_tbl._del(port_name) + dom_tbl._del(port_name) + + except NotImplementedError: + log_error("This functionality is currently not implemented for this platform") + sys.exit(3) + +# Timer thread wrapper class to update dom info to DB periodically +class dom_info_update_task: + def __init__(self, table): + self.task_stopping_event = threading.Event() + self.task_timer = None + self.dom_table = table + + def task_run(self): + if self.task_stopping_event.isSet(): + log_error("Error: dom info update thread received stop event, exiting...") + return + + logical_port_list = platform_sfputil.logical + for logical_port_name in logical_port_list: + post_port_dom_info_to_db(logical_port_name, self.dom_table) + + self.task_timer = threading.Timer(DOM_INFO_UPDATE_PERIOD_SECS, self.task_run) + self.task_timer.start() + + def task_stop(self): + self.task_stopping_event.set() + self.task_timer.join() + +#=============================== Main ================================ + +def main(): + log_info("Starting up...") + + # Register our signal handlers + signal.signal(signal.SIGHUP, signal_handler) + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + # Load platform-specific sfputil class + err = load_platform_sfputil() + if err != 0: + log_error("failed to load sfputil") + sys.exit(1) + + # Load port info + try: + port_config_file_path = get_path_to_port_config_file() + platform_sfputil.read_porttab_mappings(port_config_file_path) + # platform_sfputil.read_port_mappings() + except Exception, e: + log_error("Error reading port info (%s)" % str(e), True) + sys.exit(2) + + # Connect to STATE_DB and create transceiver info/dom info table + state_db = swsscommon.DBConnector(swsscommon.STATE_DB, + REDIS_HOSTNAME, + REDIS_PORT, + REDIS_TIMEOUT_MSECS) + int_tbl = swsscommon.Table(state_db, "TRANSCEIVER_INFO") + dom_tbl = swsscommon.Table(state_db, "TRANSCEIVER_DOM_SENSOR") + + # Connect to APPL_DB abd subscribe to PORT table notifications + appl_db = swsscommon.DBConnector(swsscommon.APPL_DB, + REDIS_HOSTNAME, + REDIS_PORT, + REDIS_TIMEOUT_MSECS) + + sel = swsscommon.Select() + sst = swsscommon.SubscriberStateTable(appl_db, swsscommon.APP_PORT_TABLE_NAME) + sel.addSelectable(sst) + + # Make sure this daemon started after all port configured. + while True: + (state, c) = sel.select(SELECT_TIMEOUT_MSECS) + if state == swsscommon.Select.TIMEOUT: + continue + if state != swsscommon.Select.OBJECT: + log_warning("sel.select() did not return swsscommon.Select.OBJECT") + continue + + (key, op, fvp) = sst.pop() + if key in ["PortConfigDone", "PortInitDone"]: + break + + # Post all the current interface SFP info to STATE_DB + logical_port_list = platform_sfputil.logical + for logical_port_name in logical_port_list: + post_port_sfp_info_to_db(logical_port_name, int_tbl) + post_port_dom_info_to_db(logical_port_name, dom_tbl) + + # Start the dom sensor info update timer thread + dom_info_update = dom_info_update_task(dom_tbl) + dom_info_update.task_run() + + # Start main loop to listen to the SFP change event. + log_info("Start main loop") + while True: + status, port_dict = platform_sfputil.get_transceiver_change_event() + if status: + for key, value in port_dict.iteritems(): + logical_port_list = platform_sfputil.get_physical_to_logical(int(key)) + for logical_port in logical_port_list: + if value == SFP_STATUS_INSERTED: + rc = post_port_sfp_info_to_db(logical_port, int_tbl) + # If we didn't get the sfp info, assuming the eeprom is not ready, give a try again. + if rc == SFP_EEPROM_NOT_READY: + time.sleep(TIME_FOR_SFP_READY_SECS) + post_port_sfp_info_to_db(logical_port, int_tbl) + post_port_dom_info_to_db(logical_port, dom_tbl) + + elif value == SFP_STATUS_REMOVED: + del_port_sfp_dom_info_to_db(logical_port, int_tbl, dom_tbl) + else: + # TODO, SFP return error code, need handle accordingly. + continue + else: + # If get_transceiver_change_event() return error, will clean up the DB and then exit + # TODO: next step need to define more error types to handle accordingly. + break + + # Stop the dom info update timer + dom_info_update.task_stop() + + # Clean all the information from DB and then exit + logical_port_list = platform_sfputil.logical + for logical_port_name in logical_port_list: + del_port_sfp_dom_info_to_db(logical_port_name, int_tbl, dom_tbl) + log_error("Error: return error from get_transceiver_change_event(), exiting...") + return 1 + +if __name__ == '__main__': + main() diff --git a/sonic-xcvrd/setup.py b/sonic-xcvrd/setup.py new file mode 100644 index 0000000000..2473319250 --- /dev/null +++ b/sonic-xcvrd/setup.py @@ -0,0 +1,29 @@ +from setuptools import setup + +setup( + name='sonic-xcvrd', + version='1.0', + description='Transceiver monitoring daemon for SONiC', + license='Apache 2.0', + author='SONiC Team', + author_email='linuxnetdev@microsoft.com', + url='https://github.com/Azure/sonic-platform-daemons', + maintainer='Kebo Liu', + maintainer_email='kebol@mellanox.com', + scripts=[ + 'scripts/xcvrd', + ], + 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 TRANSCEIVER transceiver daemon XCVRD xcvrd', +)