Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce chassisd to monitor status of cards on chassis #97

Merged
merged 12 commits into from
Nov 10, 2020
224 changes: 224 additions & 0 deletions sonic-chassisd/scripts/chassisd
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
#!/usr/bin/env python2
mprabhu-nokia marked this conversation as resolved.
Show resolved Hide resolved

"""
chassisd
Module information update daemon for SONiC
This daemon will loop to collect all modules related information and then write the information to state DB.
The loop interval is CHASSIS_INFO_UPDATE_PERIOD_SECS in seconds.
"""

try:
import sys
import time
import signal
import threading
import os
from swsscommon import swsscommon
from sonic_py_common import daemon_base, logger
from sonic_platform_base.module_base import ModuleBase
from sonic_py_common.task_base import ProcessTaskBase
except ImportError, e:
raise ImportError (str(e) + " - required module not found")

#
# Constants ====================================================================
#

SYSLOG_IDENTIFIER = "chassisd"

CHASSIS_CFG_TABLE = 'CHASSIS_MODULE'

CHASSIS_INFO_TABLE = 'CHASSIS_TABLE'
CHASSIS_INFO_KEY_TEMPLATE = 'CHASSIS {}'
CHASSIS_INFO_CARD_NUM_FIELD = 'module_num'

CHASSIS_MODULE_INFO_TABLE = 'CHASSIS_MODULE_TABLE'
CHASSIS_MODULE_INFO_KEY_TEMPLATE = 'CHASSIS_MODULE {}'
CHASSIS_MODULE_INFO_NAME_FIELD = 'name'
CHASSIS_MODULE_INFO_DESC_FIELD = 'desc'
CHASSIS_MODULE_INFO_SLOT_FIELD = 'slot'
CHASSIS_MODULE_INFO_OPERSTATUS_FIELD = 'oper_status'

CHASSIS_INFO_UPDATE_PERIOD_SECS = 10

CHASSIS_LOAD_ERROR = 1
CHASSIS_NOT_AVAIL = 2

platform_chassis = None

SELECT_TIMEOUT = 1000
#
# Helper functions =============================================================
#
info_dict_keys = [CHASSIS_MODULE_INFO_NAME_FIELD,
CHASSIS_MODULE_INFO_DESC_FIELD,
CHASSIS_MODULE_INFO_SLOT_FIELD,
CHASSIS_MODULE_INFO_OPERSTATUS_FIELD]

def get_module_info(module_index):
"""
Retrieves module info of this module
"""
module_info_dict = {}
module_info_dict = dict.fromkeys(info_dict_keys, 'N/A')
module_info_dict[CHASSIS_MODULE_INFO_NAME_FIELD] = str(platform_chassis.get_module(module_index - 1).get_name())
module_info_dict[CHASSIS_MODULE_INFO_DESC_FIELD] = str(platform_chassis.get_module(module_index - 1).get_description())
module_info_dict[CHASSIS_MODULE_INFO_SLOT_FIELD] = str(platform_chassis.get_module(module_index - 1).get_slot())
module_info_dict[CHASSIS_MODULE_INFO_OPERSTATUS_FIELD] = str(platform_chassis.get_module(module_index - 1).get_status())
return module_info_dict

def module_db_update(config_tbl, module_tbl, num_modules):
for module_index in range(1, num_modules + 1):
module_info_dict = get_module_info(module_index)
if module_info_dict is not None:
fvs = swsscommon.FieldValuePairs([(CHASSIS_MODULE_INFO_DESC_FIELD, module_info_dict[CHASSIS_MODULE_INFO_DESC_FIELD]),
(CHASSIS_MODULE_INFO_SLOT_FIELD, module_info_dict[CHASSIS_MODULE_INFO_SLOT_FIELD]),
(CHASSIS_MODULE_INFO_OPERSTATUS_FIELD, module_info_dict[CHASSIS_MODULE_INFO_OPERSTATUS_FIELD])])
module_tbl.set(module_info_dict[CHASSIS_MODULE_INFO_NAME_FIELD], fvs)

#
# Config Handling task ========================================================
#
class config_handling_task(ProcessTaskBase):
def __init__(self):
ProcessTaskBase.__init__(self)

# TODO: Refactor to eliminate the need for this Logger instance
self.logger = logger.Logger(SYSLOG_IDENTIFIER)

def task_worker(self):
config_db = daemon_base.db_connect("CONFIG_DB")

#Subscribe to CHASSIS_MODULE table notifications in the Config DB
sel = swsscommon.Select()
sst = swsscommon.SubscriberStateTable(config_db, CHASSIS_CFG_TABLE)
sel.addSelectable(sst)

# Listen indefinitely for changes to the CFG_CHASSIS_MODULE_TABLE table in the Config DB
while True:
# Use timeout to prevent ignoring the signals we want to handle
# in signal_handler() (e.g. SIGTERM for graceful shutdown)
(state, c) = sel.select(SELECT_TIMEOUT)

if state == swsscommon.Select.TIMEOUT:
# Do not flood log when select times out
continue
if state != swsscommon.Select.OBJECT:
self.logger.log_warning("sel.select() did not return swsscommon.Select.OBJECT")
continue

(key, op, fvp) = sst.pop()

if not key.startswith(ModuleBase.MODULE_TYPE_CONTROL) and not key.startswith(ModuleBase.MODULE_TYPE_LINE) and not key.startswith(ModuleBase.MODULE_TYPE_FABRIC):
self.logger.log_warning("Incorrect module-name {}. Should start with {} or {} or {}".format(key, ModuleBase.MODULE_TYPE_CONTROL, ModuleBase.MODULE_TYPE_LINE, ModuleBase.MODULE_TYPE_FABRIC))
continue

fvp_dict = dict(fvp)

module_index = platform_chassis.get_module_index(key)

#Continue if the index is invalid
if module_index < 0:
self.logger.log_error("Unable to get module-index for key {} during op {}". format(key, op))
continue

if op == "SET":
#Setting the module to administratively down state
self.logger.log_info("Changing module {} to admin DOWN state".format(key))
platform_chassis.get_module(module_index).set_admin_state(0)
elif op == "DEL":
#Setting the module to administratively up state
self.logger.log_info("Changing module {} to admin UP state".format(key))
platform_chassis.get_module(module_index).set_admin_state(1)

return 1

#
# Daemon =======================================================================
#

class ChassisdDaemon(daemon_base.DaemonBase):
def __init__(self, log_identifier):
super(ChassisdDaemon, self).__init__(log_identifier)

self.logger = logger.Logger(SYSLOG_IDENTIFIER)
self.stop = threading.Event()

# Signal handler
def signal_handler(self, sig, frame):
if sig == signal.SIGHUP:
self.log_info("Caught SIGHUP - ignoring...")
elif sig == signal.SIGINT:
self.log_info("Caught SIGINT - exiting...")
self.stop.set()
elif sig == signal.SIGTERM:
self.log_info("Caught SIGTERM - exiting...")
self.stop.set()
else:
self.log_warning("Caught unhandled signal '" + sig + "'")

# Run daemon
def run(self):
global platform_chassis
mprabhu-nokia marked this conversation as resolved.
Show resolved Hide resolved

self.log_info("Starting up...")

# Load new platform api class
try:
import sonic_platform.platform
platform_chassis = sonic_platform.platform.Platform().get_chassis()
except Exception as e:
self.log_error("Failed to load chassis due to {}".format(repr(e)))
sys.exit(CHASSIS_LOAD_ERROR)

# Connect to STATE_DB and create chassis module/chassis info tables
state_db = daemon_base.db_connect("STATE_DB")
chassis_tbl = swsscommon.Table(state_db, CHASSIS_INFO_TABLE)
module_tbl = swsscommon.Table(state_db, CHASSIS_MODULE_INFO_TABLE)

# Connect to CONFIG_DB and check with chassis cfg table
config_db = daemon_base.db_connect("CONFIG_DB")
config_tbl = swsscommon.Table(config_db, CHASSIS_CFG_TABLE)

# Post number-of-modules info to STATE_DB
num_modules = platform_chassis.get_num_modules()
fvs = swsscommon.FieldValuePairs([(CHASSIS_INFO_CARD_NUM_FIELD, str(num_modules))])
chassis_tbl.set(CHASSIS_INFO_KEY_TEMPLATE.format(1), fvs)

if num_modules == 0:
self.log_error("Chassisd has no modules available")

#Start configuration handling task on control module
if platform_chassis.get_controlcard_slot() == platform_chassis.get_my_slot():
config_update = config_handling_task()
config_update.task_run()

# Start main loop
self.log_info("Start daemon main loop")

while not self.stop.wait(CHASSIS_INFO_UPDATE_PERIOD_SECS):
module_db_update(config_tbl, module_tbl, num_modules)

self.log_info("Stop daemon main loop")

if config_update is not None:
config_update.task_stop()

# Delete all the information from DB and then exit
for module_index in range(1, num_modules + 1):
module_tbl._del(CHASSIS_MODULE_INFO_KEY_TEMPLATE.format(module_index))

chassis_tbl._del(CHASSIS_INFO_KEY_TEMPLATE.format(1))

self.log_info("Shutting down...")

mprabhu-nokia marked this conversation as resolved.
Show resolved Hide resolved
#
# Main =========================================================================
#

def main():
chassisd = ChassisdDaemon(SYSLOG_IDENTIFIER)
chassisd.run()

if __name__ == '__main__':
main()
29 changes: 29 additions & 0 deletions sonic-chassisd/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from setuptools import setup

setup(
name='sonic-chassisd',
version='1.0',
description='Chassis daemon for SONiC',
license='Apache 2.0',
author='SONiC Team',
author_email='linuxnetdev@microsoft.com',
url='https://github.com/Azure/sonic-platform-daemons',
maintainer='Manju Prabhu',
maintainer_email='manjunath.prabhu@nokia.com',
scripts=[
'scripts/chassisd',
],
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 chassis Chassis daemon chassisd',
)