From 1fa3e47396930caa47be8c57ed34849c4aa666fa Mon Sep 17 00:00:00 2001 From: Guohan Lu Date: Wed, 5 Aug 2020 12:20:20 +0000 Subject: [PATCH 1/3] split feature from the config and show Signed-off-by: Guohan Lu --- config/feature.py | 48 ++++++++++++++++++++++ config/main.py | 90 ++--------------------------------------- show/feature.py | 56 +++++++++++++++++++++++++ show/main.py | 55 ++----------------------- utilities_common/cli.py | 43 ++++++++++++++++++++ 5 files changed, 153 insertions(+), 139 deletions(-) create mode 100644 config/feature.py create mode 100644 show/feature.py create mode 100644 utilities_common/cli.py diff --git a/config/feature.py b/config/feature.py new file mode 100644 index 0000000000..55bbaaf921 --- /dev/null +++ b/config/feature.py @@ -0,0 +1,48 @@ +import click + +from utilities_common.cli import AbbreviationGroup, pass_db + +# +# 'feature' group ('config feature ...') +# +@click.group(cls=AbbreviationGroup, name='feature', invoke_without_command=False) +def feature(): + """Configure features""" + pass + +# +# 'state' command ('config feature state ...') +# +@feature.command('state', short_help="Enable/disable a feature") +@click.argument('name', metavar='', required=True) +@click.argument('state', metavar='', required=True, type=click.Choice(["enabled", "disabled"])) +@pass_db +def feature_state(db, name, state): + """Enable/disable a feature""" + state_data = db.cfgdb.get_entry('FEATURE', name) + + if not state_data: + click.echo("Feature '{}' doesn't exist".format(name)) + sys.exit(1) + + db.cfgdb.mod_entry('FEATURE', name, {'state': state}) + +# +# 'autorestart' command ('config feature autorestart ...') +# +@feature.command(name='autorestart', short_help="Enable/disable autosrestart of a feature") +@click.argument('name', metavar='', required=True) +@click.argument('autorestart', metavar='', required=True, type=click.Choice(["enabled", "disabled"])) +@pass_db +def feature_autorestart(db, name, autorestart): + """Enable/disable autorestart of a feature""" + feature_table = db.cfgdb.get_table('FEATURE') + if not feature_table: + click.echo("Unable to retrieve feature table from Config DB.") + sys.exit(1) + + if not feature_table.has_key(name): + click.echo("Unable to retrieve feature '{}'".format(name)) + sys.exit(1) + + db.cfgdb.mod_entry('FEATURE', name, {'auto_restart': autorestart}) diff --git a/config/main.py b/config/main.py index 43f07a0bd2..5bdd908a29 100755 --- a/config/main.py +++ b/config/main.py @@ -18,10 +18,12 @@ from swsssdk import ConfigDBConnector, SonicV2Connector, SonicDBConfig from utilities_common.db import Db from utilities_common.intf_filter import parse_interface_in_filter +from utilities_common.cli import AbbreviationGroup, pass_db import aaa import mlnx import nat +import feature from config_mgmt import ConfigMgmtDPB CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help', '-?']) @@ -54,46 +56,6 @@ asic_type = None - -class AbbreviationGroup(click.Group): - """This subclass of click.Group supports abbreviated subgroup/subcommand names - """ - - def get_command(self, ctx, cmd_name): - # Try to get builtin commands as normal - rv = click.Group.get_command(self, ctx, cmd_name) - if rv is not None: - return rv - - # Allow automatic abbreviation of the command. "status" for - # instance will match "st". We only allow that however if - # there is only one command. - # If there are multiple matches and the shortest one is the common prefix of all the matches, return - # the shortest one - matches = [] - shortest = None - for x in self.list_commands(ctx): - if x.lower().startswith(cmd_name.lower()): - matches.append(x) - if not shortest: - shortest = x - elif len(shortest) > len(x): - shortest = x - - if not matches: - return None - elif len(matches) == 1: - return click.Group.get_command(self, ctx, matches[0]) - else: - for x in matches: - if not x.startswith(shortest): - break - else: - return click.Group.get_command(self, ctx, shortest) - - ctx.fail('Too many matches: %s' % ', '.join(sorted(matches))) - - # # Load breakout config file for Dynamic Port Breakout # @@ -922,8 +884,7 @@ def config(ctx): ctx.obj = Db() -pass_db = click.make_pass_decorator(Db, ensure=True) - +config.add_command(feature.feature) config.add_command(aaa.aaa) config.add_command(aaa.tacacs) # === Add NAT Configuration ========== @@ -3838,50 +3799,5 @@ def delete(ctx): sflow_tbl['global'].pop('agent_id') config_db.set_entry('SFLOW', 'global', sflow_tbl['global']) -# -# 'feature' group ('config feature ...') -# -@config.group(cls=AbbreviationGroup, name='feature', invoke_without_command=False) -def feature(): - """Modify configuration of features""" - pass - -# -# 'state' command ('config feature state ...') -# -@feature.command('state', short_help="Enable/disable a feature") -@click.argument('name', metavar='', required=True) -@click.argument('state', metavar='', required=True, type=click.Choice(["enabled", "disabled"])) -@pass_db -def feature_state(db, name, state): - """Enable/disable a feature""" - state_data = db.cfgdb.get_entry('FEATURE', name) - - if not state_data: - click.echo("Feature '{}' doesn't exist".format(name)) - sys.exit(1) - - db.cfgdb.mod_entry('FEATURE', name, {'state': state}) - -# -# 'autorestart' command ('config feature autorestart ...') -# -@feature.command(name='autorestart', short_help="Enable/disable autosrestart of a feature") -@click.argument('name', metavar='', required=True) -@click.argument('autorestart', metavar='', required=True, type=click.Choice(["enabled", "disabled"])) -@pass_db -def feature_autorestart(db, name, autorestart): - """Enable/disable autorestart of a feature""" - feature_table = db.cfgdb.get_table('FEATURE') - if not feature_table: - click.echo("Unable to retrieve feature table from Config DB.") - sys.exit(1) - - if not feature_table.has_key(name): - click.echo("Unable to retrieve feature '{}'".format(name)) - sys.exit(1) - - db.cfgdb.mod_entry('FEATURE', name, {'auto_restart': autorestart}) - if __name__ == '__main__': config() diff --git a/show/feature.py b/show/feature.py new file mode 100644 index 0000000000..84d54a1299 --- /dev/null +++ b/show/feature.py @@ -0,0 +1,56 @@ +import click +from natsort import natsorted +from tabulate import tabulate + +from utilities_common.cli import AbbreviationGroup, pass_db + +# +# 'feature' group (show feature ...) +# +@click.group(name='feature', invoke_without_command=False) +def feature(): + """Show feature status""" + pass + +# +# 'status' subcommand (show feature status) +# +@feature.command('status', short_help="Show feature state") +@click.argument('feature_name', required=False) +@pass_db +def feature_status(db, feature_name): + header = ['Feature', 'State', 'AutoRestart'] + body = [] + feature_table = db.cfgdb.get_table('FEATURE') + if feature_name: + if feature_table and feature_table.has_key(feature_name): + body.append([feature_name, feature_table[feature_name]['state'], \ + feature_table[feature_name]['auto_restart']]) + else: + click.echo("Can not find feature {}".format(feature_name)) + sys.exit(1) + else: + for key in natsorted(feature_table.keys()): + body.append([key, feature_table[key]['state'], feature_table[key]['auto_restart']]) + click.echo(tabulate(body, header)) + +# +# 'autorestart' subcommand (show feature autorestart) +# +@feature.command('autorestart', short_help="Show auto-restart state for a feature") +@click.argument('feature_name', required=False) +@pass_db +def feature_autorestart(db, feature_name): + header = ['Feature', 'AutoRestart'] + body = [] + feature_table = db.cfgdb.get_table('FEATURE') + if feature_name: + if feature_table and feature_table.has_key(feature_name): + body.append([feature_name, feature_table[feature_name]['auto_restart']]) + else: + click.echo("Can not find feature {}".format(feature_name)) + sys.exit(1) + else: + for name in natsorted(feature_table.keys()): + body.append([name, feature_table[name]['auto_restart']]) + click.echo(tabulate(body, header)) diff --git a/show/main.py b/show/main.py index 0158706cb2..4b85aa3ca5 100755 --- a/show/main.py +++ b/show/main.py @@ -19,7 +19,9 @@ from swsssdk import SonicV2Connector from tabulate import tabulate from utilities_common.db import Db +from utilities_common.cli import AbbreviationGroup, pass_db +import feature import mlnx # Global Variables @@ -559,7 +561,7 @@ def cli(ctx): ctx.obj = Db() -pass_db = click.make_pass_decorator(Db, ensure=True) +cli.add_command(feature.feature) # # 'vrf' command ("show vrf") @@ -3040,57 +3042,6 @@ def ztp(status, verbose): cmd = cmd + " --verbose" run_command(cmd, display_cmd=verbose) -# -# 'feature' group (show feature ...) -# -@cli.group(name='feature', invoke_without_command=False) -def feature(): - """Show feature status""" - pass - -# -# 'status' subcommand (show feature status) -# -@feature.command('status', short_help="Show feature state") -@click.argument('feature_name', required=False) -@pass_db -def feature_status(db, feature_name): - header = ['Feature', 'State', 'AutoRestart'] - body = [] - feature_table = db.cfgdb.get_table('FEATURE') - if feature_name: - if feature_table and feature_table.has_key(feature_name): - body.append([feature_name, feature_table[feature_name]['state'], \ - feature_table[feature_name]['auto_restart']]) - else: - click.echo("Can not find feature {}".format(feature_name)) - sys.exit(1) - else: - for key in natsorted(feature_table.keys()): - body.append([key, feature_table[key]['state'], feature_table[key]['auto_restart']]) - click.echo(tabulate(body, header)) - -# -# 'autorestart' subcommand (show feature autorestart) -# -@feature.command('autorestart', short_help="Show auto-restart state for a feature") -@click.argument('feature_name', required=False) -@pass_db -def feature_autorestart(db, feature_name): - header = ['Feature', 'AutoRestart'] - body = [] - feature_table = db.cfgdb.get_table('FEATURE') - if feature_name: - if feature_table and feature_table.has_key(feature_name): - body.append([feature_name, feature_table[feature_name]['auto_restart']]) - else: - click.echo("Can not find feature {}".format(feature_name)) - sys.exit(1) - else: - for name in natsorted(feature_table.keys()): - body.append([name, feature_table[name]['auto_restart']]) - click.echo(tabulate(body, header)) - # # 'vnet' command ("show vnet") # diff --git a/utilities_common/cli.py b/utilities_common/cli.py new file mode 100644 index 0000000000..dd09a444c5 --- /dev/null +++ b/utilities_common/cli.py @@ -0,0 +1,43 @@ +import click + +from utilities_common.db import Db + +pass_db = click.make_pass_decorator(Db, ensure=True) + +class AbbreviationGroup(click.Group): + """This subclass of click.Group supports abbreviated subgroup/subcommand names + """ + + def get_command(self, ctx, cmd_name): + # Try to get builtin commands as normal + rv = click.Group.get_command(self, ctx, cmd_name) + if rv is not None: + return rv + + # Allow automatic abbreviation of the command. "status" for + # instance will match "st". We only allow that however if + # there is only one command. + # If there are multiple matches and the shortest one is the common prefix of all the matches, return + # the shortest one + matches = [] + shortest = None + for x in self.list_commands(ctx): + if x.lower().startswith(cmd_name.lower()): + matches.append(x) + if not shortest: + shortest = x + elif len(shortest) > len(x): + shortest = x + + if not matches: + return None + elif len(matches) == 1: + return click.Group.get_command(self, ctx, matches[0]) + else: + for x in matches: + if not x.startswith(shortest): + break + else: + return click.Group.get_command(self, ctx, shortest) + + ctx.fail('Too many matches: %s' % ', '.join(sorted(matches))) From cba2661f3eeb75e4ff164a379dfdc9908c936b09 Mon Sep 17 00:00:00 2001 From: Guohan Lu Date: Fri, 7 Aug 2020 05:32:21 -0700 Subject: [PATCH 2/3] fix lgtm warnings Signed-off-by: Guohan Lu --- show/feature.py | 2 +- show/main.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/show/feature.py b/show/feature.py index 84d54a1299..05ff1b023f 100644 --- a/show/feature.py +++ b/show/feature.py @@ -2,7 +2,7 @@ from natsort import natsorted from tabulate import tabulate -from utilities_common.cli import AbbreviationGroup, pass_db +from utilities_common.cli import pass_db # # 'feature' group (show feature ...) diff --git a/show/main.py b/show/main.py index 4b85aa3ca5..ba7e912dde 100755 --- a/show/main.py +++ b/show/main.py @@ -19,7 +19,6 @@ from swsssdk import SonicV2Connector from tabulate import tabulate from utilities_common.db import Db -from utilities_common.cli import AbbreviationGroup, pass_db import feature import mlnx From 76d9608c6774f1273ba855bf1f1e396b91fd3b31 Mon Sep 17 00:00:00 2001 From: Guohan Lu Date: Sat, 8 Aug 2020 09:29:30 -0700 Subject: [PATCH 3/3] address comments Signed-off-by: Guohan Lu --- config/main.py | 2 +- show/feature.py | 4 ++-- tests/feature_test.py | 9 +++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/config/main.py b/config/main.py index 5bdd908a29..2d917dd417 100755 --- a/config/main.py +++ b/config/main.py @@ -884,9 +884,9 @@ def config(ctx): ctx.obj = Db() -config.add_command(feature.feature) config.add_command(aaa.aaa) config.add_command(aaa.tacacs) +config.add_command(feature.feature) # === Add NAT Configuration ========== config.add_command(nat.nat) diff --git a/show/feature.py b/show/feature.py index 05ff1b023f..c3b13abaf8 100644 --- a/show/feature.py +++ b/show/feature.py @@ -2,12 +2,12 @@ from natsort import natsorted from tabulate import tabulate -from utilities_common.cli import pass_db +from utilities_common.cli import AbbreviationGroup, pass_db # # 'feature' group (show feature ...) # -@click.group(name='feature', invoke_without_command=False) +@click.group(cls=AbbreviationGroup, name='feature', invoke_without_command=False) def feature(): """Show feature status""" pass diff --git a/tests/feature_test.py b/tests/feature_test.py index c928b9657e..cf54b89d7b 100644 --- a/tests/feature_test.py +++ b/tests/feature_test.py @@ -79,6 +79,15 @@ def test_show_feature_status(self, get_cmd_module): assert result.exit_code == 0 assert result.output == show_feature_status_output + def test_show_feature_status_abbrev_cmd(self, get_cmd_module): + (config, show) = get_cmd_module + runner = CliRunner() + result = runner.invoke(show.cli.commands["feature"], ["st"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_feature_status_output + def test_show_bgp_feature_status(self, get_cmd_module): (config, show) = get_cmd_module runner = CliRunner()