Skip to content

Commit 9a2872d

Browse files
[cli] Dynamic cli extension via plugins (#1186)
Added a mechanism to extend command line interface via plugins. For every program - show, config, clear a new python package called 'plugins' is added. This package is used as a namespace for all plugins python modules. As an example mlnx.py is moved to the plugins package. In the fututre, this mechanism will be used by Application Extension infrastructure. Signed-off-by: Stepan Blyschak <[email protected]>
1 parent 6b51bcd commit 9a2872d

File tree

11 files changed

+82
-14
lines changed

11 files changed

+82
-14
lines changed

clear/main.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55

66
import click
77

8+
from utilities_common import util_base
9+
10+
from . import plugins
11+
812

913
# This is from the aliases example:
1014
# https://github.com/pallets/click/blob/57c6f09611fc47ca80db0bd010f05998b3c0aa95/examples/aliases/aliases.py
@@ -120,7 +124,6 @@ def cli():
120124
"""SONiC command line - 'Clear' command"""
121125
pass
122126

123-
124127
#
125128
# 'ip' group ###
126129
#
@@ -446,5 +449,12 @@ def translations():
446449
cmd = "natclear -t"
447450
run_command(cmd)
448451

452+
453+
# Load plugins and register them
454+
helper = util_base.UtilHelper()
455+
for plugin in helper.load_plugins(plugins):
456+
helper.register_plugin(plugin, cli)
457+
458+
449459
if __name__ == '__main__':
450460
cli()

clear/plugins/__init__.py

Whitespace-only changes.

config/main.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from portconfig import get_child_ports
1717
from sonic_py_common import device_info, multi_asic
1818
from sonic_py_common.interface import get_interface_table_name, get_port_table_name
19+
from utilities_common import util_base
1920
from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector, SonicDBConfig
2021
from utilities_common.db import Db
2122
from utilities_common.intf_filter import parse_interface_in_filter
@@ -28,11 +29,11 @@
2829
from . import feature
2930
from . import kdump
3031
from . import kube
31-
from . import mlnx
3232
from . import muxcable
3333
from . import nat
3434
from . import vlan
3535
from . import vxlan
36+
from . import plugins
3637
from .config_mgmt import ConfigMgmtDPB
3738

3839
# mock masic APIs for unit test
@@ -849,9 +850,6 @@ def config(ctx):
849850
except (KeyError, TypeError):
850851
raise click.Abort()
851852

852-
if asic_type == 'mellanox':
853-
platform.add_command(mlnx.mlnx)
854-
855853
# Load the global config file database_global.json once.
856854
num_asic = multi_asic.get_num_asics()
857855
if num_asic > 1:
@@ -4415,5 +4413,12 @@ def delete(ctx):
44154413
sflow_tbl['global'].pop('agent_id')
44164414
config_db.set_entry('SFLOW', 'global', sflow_tbl['global'])
44174415

4416+
4417+
# Load plugins and register them
4418+
helper = util_base.UtilHelper()
4419+
for plugin in helper.load_plugins(plugins):
4420+
helper.register_plugin(plugin, config)
4421+
4422+
44184423
if __name__ == '__main__':
44194424
config()

config/plugins/__init__.py

Whitespace-only changes.

config/mlnx.py config/plugins/mlnx.py

+6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import click
1313
from sonic_py_common import logger
14+
from sonic_py_common import device_info
1415
import utilities_common.cli as clicommon
1516
except ImportError as e:
1617
raise ImportError("%s - required module not found" % str(e))
@@ -229,5 +230,10 @@ def sdk_sniffer_disable():
229230
# pass
230231

231232

233+
def register(cli):
234+
version_info = device_info.get_sonic_version_info()
235+
if (version_info and version_info.get('asic_type') == 'mellanox'):
236+
cli.commands['platform'].add_command(mlnx)
237+
232238
if __name__ == '__main__':
233239
sniffer()

setup.py

+3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
packages=[
2323
'acl_loader',
2424
'clear',
25+
'clear.plugins',
2526
'config',
27+
'config.plugins',
2628
'connect',
2729
'consutil',
2830
'counterpoll',
@@ -42,6 +44,7 @@
4244
'pddf_ledutil',
4345
'show',
4446
'show.interfaces',
47+
'show.plugins',
4548
'sonic_installer',
4649
'sonic_installer.bootloader',
4750
'tests',

show/main.py

100644100755
+9-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from sonic_py_common import device_info, multi_asic
1212
from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector
1313
from tabulate import tabulate
14+
from utilities_common import util_base
1415
from utilities_common.db import Db
1516

1617
from . import acl
@@ -23,7 +24,6 @@
2324
from . import interfaces
2425
from . import kdump
2526
from . import kube
26-
from . import mlnx
2727
from . import muxcable
2828
from . import nat
2929
from . import platform
@@ -35,6 +35,7 @@
3535
from . import vxlan
3636
from . import system_health
3737
from . import warm_restart
38+
from . import plugins
3839

3940

4041
# Global Variables
@@ -1413,5 +1414,12 @@ def ztp(status, verbose):
14131414
cmd = cmd + " --verbose"
14141415
run_command(cmd, display_cmd=verbose)
14151416

1417+
1418+
# Load plugins and register them
1419+
helper = util_base.UtilHelper()
1420+
for plugin in helper.load_plugins(plugins):
1421+
helper.register_plugin(plugin, cli)
1422+
1423+
14161424
if __name__ == '__main__':
14171425
cli()

show/platform.py

-6
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,6 @@ def platform():
3333
pass
3434

3535

36-
version_info = device_info.get_sonic_version_info()
37-
if (version_info and version_info.get('asic_type') == 'mellanox'):
38-
from . import mlnx
39-
platform.add_command(mlnx.mlnx)
40-
41-
4236
# 'summary' subcommand ("show platform summary")
4337
@platform.command()
4438
@click.option('--json', is_flag=True, help="Output in JSON format")

show/plugins/__init__.py

Whitespace-only changes.

show/mlnx.py show/plugins/mlnx.py

+6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import subprocess
1111
import click
1212
import xml.etree.ElementTree as ET
13+
from sonic_py_common import device_info
1314
except ImportError as e:
1415
raise ImportError("%s - required module not found" % str(e))
1516

@@ -137,3 +138,8 @@ def issu_status():
137138

138139
click.echo('ISSU is enabled' if res else 'ISSU is disabled')
139140

141+
142+
def register(cli):
143+
version_info = device_info.get_sonic_version_info()
144+
if (version_info and version_info.get('asic_type') == 'mellanox'):
145+
cli.commands['platform'].add_command(mlnx)

utilities_common/util_base.py

+38-2
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,51 @@
1-
21
import os
3-
import sonic_platform
2+
import pkgutil
3+
import importlib
4+
5+
from sonic_py_common import logger
46

57
# Constants ====================================================================
68
PDDF_SUPPORT_FILE = '/usr/share/sonic/platform/pddf_support'
79

810
# Helper classs
911

12+
log = logger.Logger()
13+
1014

1115
class UtilHelper(object):
1216
def __init__(self):
1317
pass
1418

19+
def load_plugins(self, plugins_namespace):
20+
""" Discover and load CLI plugins. Yield a plugin module. """
21+
22+
def iter_namespace(ns_pkg):
23+
return pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + ".")
24+
25+
for _, module_name, ispkg in iter_namespace(plugins_namespace):
26+
if ispkg:
27+
continue
28+
log.log_debug('importing plugin: {}'.format(module_name))
29+
try:
30+
module = importlib.import_module(module_name)
31+
except Exception as err:
32+
log.log_error('failed to import plugin {}: {}'.format(module_name, err),
33+
also_print_to_console=True)
34+
continue
35+
36+
yield module
37+
38+
def register_plugin(self, plugin, root_command):
39+
""" Register plugin in top-level command root_command. """
40+
41+
name = plugin.__name__
42+
log.log_debug('registering plugin: {}'.format(name))
43+
try:
44+
plugin.register(root_command)
45+
except Exception as err:
46+
log.log_error('failed to import plugin {}: {}'.format(name, err),
47+
also_print_to_console=True)
48+
1549
# try get information from platform API and return a default value if caught NotImplementedError
1650
def try_get(self, callback, default=None):
1751
"""
@@ -35,6 +69,7 @@ def load_platform_chassis(self):
3569

3670
# Load 2.0 platform API chassis class
3771
try:
72+
import sonic_platform
3873
chassis = sonic_platform.platform.Platform().get_chassis()
3974
except Exception as e:
4075
raise Exception("Failed to load chassis due to {}".format(repr(e)))
@@ -47,3 +82,4 @@ def check_pddf_mode(self):
4782
return True
4883
else:
4984
return False
85+

0 commit comments

Comments
 (0)