diff --git a/scripts/sysreadyshow b/scripts/sysreadyshow new file mode 100755 index 00000000000..e26f0b2f3b9 --- /dev/null +++ b/scripts/sysreadyshow @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 + +""" + Script to show system ready status. +""" + +import os +import sys +import argparse +from tabulate import tabulate +from natsort import natsorted + +from swsscommon.swsscommon import SonicV2Connector + +header = ['Service-Name', 'Service-Status', 'App-Ready-Status', 'Down-Reason'] +header_detail = ['Service-Name', 'Service-Status', 'App-Ready-Status', 'Down-Reason', 'AppStatus-UpdateTime'] + +ALL_TABLE_NAME = 'ALL_SERVICE_STATUS' +SERVICE_STATUS = 'service_status' +APP_READY_STATUS = 'app_ready_status' +FAIL_REASON = 'fail_reason' +UPDATE_TIME = 'update_time' + +class SysreadyShow(object): + def __init__(self): + self.db = SonicV2Connector(host="127.0.0.1") + self.db.connect(self.db.STATE_DB) + + def show(self,type): + TABLE_NAME = ALL_TABLE_NAME + SYSREADY_TABLE = "SYSTEM_READY|SYSTEM_STATE" + + keys = self.db.keys(self.db.STATE_DB, TABLE_NAME + '*') + if not keys: + print('No info\n') + return + + table = [] + for key in natsorted(keys): + key_list = key.split('|') + if len(key_list) != 2: # error data in DB, log it and ignore + print('Warn: Invalid key in table {}: {}'.format(TABLE_NAME, key)) + continue + + name = key_list[1] + data_dict = self.db.get_all(self.db.STATE_DB, key) + try: + service_status = data_dict[SERVICE_STATUS] + app_ready_status = data_dict[APP_READY_STATUS] + fail_reason = data_dict[FAIL_REASON] + update_time = data_dict[UPDATE_TIME] + except ValueError as e: + print('Error in data_dict') + + if type == "alldetail": + table.append((name, service_status, app_ready_status, fail_reason, update_time)) + header_info = header_detail + else: + table.append((name, service_status, app_ready_status, fail_reason)) + header_info = header + + sysready_state = self.db.get(self.db.STATE_DB, SYSREADY_TABLE, "Status") + if sysready_state == "UP": + print("System is ready\n") + else: + print("System is not ready - one or more services are not up\n") + + + if type == "allbrief": + return + + + if table: + print(tabulate(table, header_info, tablefmt='simple', stralign='left')) + else: + print('No sysready status data available\n') + + +def main(): + parser = argparse.ArgumentParser(description='Display the System Ready status', + formatter_class=argparse.RawTextHelpFormatter, + epilog=""" + Examples: + sysreadyshow --all + sysreadyshow --allbrief + sysreadyshow --alldetail + """) + + parser.add_argument('-a', '--all', action='store_true', help='all service status', default=True) + parser.add_argument('-b', '--allbrief', action='store_true', help='all service status brief', default=False) + parser.add_argument('-d', '--alldetail', action='store_true', help='all service status detail', default=False) + args = parser.parse_args() + + try: + sysready = SysreadyShow() + if args.alldetail: + sysready.show("alldetail") + elif args.allbrief: + sysready.show("allbrief") + else: + sysready.show("all") + except Exception as e: + print(str(e), file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/setup.py b/setup.py index a8bf39170c4..340a326cb51 100644 --- a/setup.py +++ b/setup.py @@ -146,7 +146,8 @@ 'scripts/null_route_helper', 'scripts/coredump_gen_handler.py', 'scripts/techsupport_cleanup.py', - 'scripts/check_db_integrity.py' + 'scripts/check_db_integrity.py', + 'scripts/sysreadyshow' ], entry_points={ 'console_scripts': [ diff --git a/show/system_health.py b/show/system_health.py index 30d9d74114c..f6140fcdbaf 100644 --- a/show/system_health.py +++ b/show/system_health.py @@ -198,3 +198,34 @@ def monitor_list(): entry.append(element[1]['type']) table.append(entry) click.echo(tabulate(table, header)) + + +@system_health.group('sysready-status',invoke_without_command=True) +@click.pass_context +def sysready_status(ctx): + """Show system-health system ready status""" + + if ctx.invoked_subcommand is None: + try: + cmd = "sudo sysreadyshow --all" + clicommon.run_command(cmd, display_cmd=False) + except Exception as e: + click.echo("Exception: {}".format(str(e))) + + +@sysready_status.command('brief') +def sysready_status_brief(): + try: + cmd = "sudo sysreadyshow --allbrief" + clicommon.run_command(cmd, display_cmd=False) + except Exception as e: + click.echo("Exception: {}".format(str(e))) + + +@sysready_status.command('detail') +def sysready_status_detail(): + try: + cmd = "sudo sysreadyshow --alldetail" + clicommon.run_command(cmd, display_cmd=False) + except Exception as e: + click.echo("Exception: {}".format(str(e))) diff --git a/sonic_package_manager/manifest.py b/sonic_package_manager/manifest.py index 2d9f3514e78..94e00dec327 100644 --- a/sonic_package_manager/manifest.py +++ b/sonic_package_manager/manifest.py @@ -177,6 +177,7 @@ def unmarshal(self, value): ManifestField('asic-service', DefaultMarshaller(bool), False), ManifestField('host-service', DefaultMarshaller(bool), True), ManifestField('delayed', DefaultMarshaller(bool), False), + ManifestField('check_up_status', DefaultMarshaller(bool), False), ManifestRoot('warm-shutdown', [ ManifestArray('after', DefaultMarshaller(str)), ManifestArray('before', DefaultMarshaller(str)), diff --git a/sonic_package_manager/service_creator/feature.py b/sonic_package_manager/service_creator/feature.py index eb8e1a0710e..9392db0c844 100644 --- a/sonic_package_manager/service_creator/feature.py +++ b/sonic_package_manager/service_creator/feature.py @@ -143,4 +143,5 @@ def get_non_configurable_feature_entries(manifest) -> Dict[str, str]: 'has_per_asic_scope': str(manifest['service']['asic-service']), 'has_global_scope': str(manifest['service']['host-service']), 'has_timer': str(manifest['service']['delayed']), + 'check_up_status': str(manifest['service']['check_up_status']), } diff --git a/tests/sonic_package_manager/test_service_creator.py b/tests/sonic_package_manager/test_service_creator.py index 295e80dc52c..2645fba10f4 100644 --- a/tests/sonic_package_manager/test_service_creator.py +++ b/tests/sonic_package_manager/test_service_creator.py @@ -215,6 +215,7 @@ def test_feature_registration(mock_sonic_db, manifest): 'has_per_asic_scope': 'False', 'has_global_scope': 'True', 'has_timer': 'False', + 'check_up_status': 'False', }) @@ -227,6 +228,7 @@ def test_feature_update(mock_sonic_db, manifest): 'has_per_asic_scope': 'False', 'has_global_scope': 'True', 'has_timer': 'False', + 'check_up_status': 'False', } mock_connector = Mock() mock_connector.get_entry = Mock(return_value=curr_feature_config) @@ -249,6 +251,7 @@ def test_feature_update(mock_sonic_db, manifest): 'has_per_asic_scope': 'False', 'has_global_scope': 'True', 'has_timer': 'True', + 'check_up_status': 'False', }), ], any_order=True) @@ -268,6 +271,7 @@ def test_feature_registration_with_timer(mock_sonic_db, manifest): 'has_per_asic_scope': 'False', 'has_global_scope': 'True', 'has_timer': 'True', + 'check_up_status': 'False', }) @@ -285,4 +289,5 @@ def test_feature_registration_with_non_default_owner(mock_sonic_db, manifest): 'has_per_asic_scope': 'False', 'has_global_scope': 'True', 'has_timer': 'False', + 'check_up_status': 'False', })