[macsec] cli multi-namespace support (#11285)

Enable multi-asic platform support for macsec cli
This commit is contained in:
Junhua Zhai 2022-07-22 01:52:46 +00:00 committed by GitHub
parent ff605808f9
commit f01749de99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 193 additions and 83 deletions

View File

@ -1,5 +1,6 @@
import pytest
import mock_tables # lgtm [py/unused-import]
import mock_single_asic # lgtm[py/unused-import]
from unittest import mock

View File

@ -0,0 +1,81 @@
# MONKEY PATCH!!!
from unittest import mock
from sonic_py_common import multi_asic
from utilities_common import multi_asic as multi_asic_util
mock_intf_table = {
'': {
'eth0': {
2: [{'addr': '10.1.1.1', 'netmask': '255.255.255.0', 'broadcast': '10.1.1.1'}],
10: [{'addr': '3100::1', 'netmask': 'ffff:ffff:ffff:ffff::/64'}]
},
'Ethernet0': {
17: [{'addr': '82:fd:d1:5b:45:2f', 'broadcast': 'ff:ff:ff:ff:ff:ff'}],
2: [
{'addr': '20.1.1.1', 'netmask': '255.255.255.0', 'broadcast': '20.1.1.1'},
{'addr': '21.1.1.1', 'netmask': '255.255.255.0', 'broadcast': '21.1.1.1'}
],
10: [
{'addr': 'aa00::1', 'netmask': 'ffff:ffff:ffff:ffff::/64'},
{'addr': '2100::1', 'netmask': 'ffff:ffff:ffff:ffff::/64'},
{'addr': 'fe80::64be:a1ff:fe85:c6c4%Ethernet0', 'netmask': 'ffff:ffff:ffff:ffff::/64'}
]
},
'PortChannel0001': {
17: [{'addr': '82:fd:d1:5b:45:2f', 'broadcast': 'ff:ff:ff:ff:ff:ff'}],
2: [{'addr': '30.1.1.1', 'netmask': '255.255.255.0', 'broadcast': '30.1.1.1'}],
10: [
{'addr': 'ab00::1', 'netmask': 'ffff:ffff:ffff:ffff::/64'},
{'addr': 'fe80::cc8d:60ff:fe08:139f%PortChannel0001', 'netmask': 'ffff:ffff:ffff:ffff::/64'}
]
},
'Vlan100': {
17: [{'addr': '82:fd:d1:5b:45:2f', 'broadcast': 'ff:ff:ff:ff:ff:ff'}],
2: [{'addr': '40.1.1.1', 'netmask': '255.255.255.0', 'broadcast': '30.1.1.1'}],
10: [
{'addr': 'cc00::1', 'netmask': 'ffff:ffff:ffff:ffff::/64'},
{'addr': 'fe80::c029:3fff:fe41:cf56%Vlan100', 'netmask': 'ffff:ffff:ffff:ffff::/64'}
]
},
'lo': {
2: [{'addr': '127.0.0.1', 'netmask': '255.0.0.0', 'broadcast': '127.255.255.255'}],
10: [{'addr': '::1', 'netmask':'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128'}]
}
}
}
def mock_get_num_asics():
return 1
def mock_is_multi_asic():
return False
def mock_get_namespace_list(namespace=None):
return ['']
def mock_single_asic_get_ip_intf_from_ns(namespace):
interfaces = []
try:
interfaces = list(mock_intf_table[namespace].keys())
except KeyError:
pass
return interfaces
def mock_single_asic_get_ip_intf_addr_from_ns(namespace, iface):
ipaddresses = []
try:
ipaddresses = mock_intf_table[namespace][iface]
except KeyError:
pass
return ipaddresses
multi_asic.is_multi_asic = mock_is_multi_asic
multi_asic.get_num_asics = mock_get_num_asics
multi_asic.get_namespace_list = mock_get_namespace_list
multi_asic_util.multi_asic_get_ip_intf_from_ns = mock_single_asic_get_ip_intf_from_ns
multi_asic_util.multi_asic_get_ip_intf_addr_from_ns = mock_single_asic_get_ip_intf_addr_from_ns

View File

@ -125,7 +125,10 @@ class CounterTable:
def get(self, macsec, name):
key = self.db.hget("COUNTERS_MACSEC_NAME_MAP", name)
return self.db.get("COUNTERS:" + key)
if key:
fvs = self.db.get("COUNTERS:" + key)
if fvs: return True, fvs
return False, ()
swsssdk.interface.DBInterface._subscribe_keyspace_notification = _subscribe_keyspace_notification

View File

@ -2,7 +2,6 @@ import sys
from unittest import mock
from click.testing import CliRunner
from utilities_common.db import Db
sys.path.append('../cli/config/plugins/')
import macsec
@ -20,14 +19,13 @@ class TestConfigMACsec(object):
cli.add_command.assert_called_once_with(macsec.macsec)
def test_default_profile(self, mock_cfgdb):
cfgdb = mock_cfgdb
runner = CliRunner()
db = Db()
db.cfgdb = mock_cfgdb
result = runner.invoke(macsec.macsec.commands["profile"].commands["add"],
[profile_name, "--primary_cak=" + primary_cak,"--primary_ckn=" + primary_ckn],
obj=db)
result = runner.invoke(macsec.macsec,
["profile", "add", profile_name, "--primary_cak=" + primary_cak,"--primary_ckn=" + primary_ckn],
obj=cfgdb)
assert result.exit_code == 0
profile_table = db.cfgdb.get_entry("MACSEC_PROFILE", profile_name)
profile_table = cfgdb.get_entry("MACSEC_PROFILE", profile_name)
assert profile_table
assert profile_table["priority"] == "255"
assert profile_table["cipher_suite"] == "GCM-AES-128"
@ -39,15 +37,14 @@ class TestConfigMACsec(object):
assert profile_table["send_sci"] == "true"
assert "rekey_period" not in profile_table
result = runner.invoke(macsec.macsec.commands["profile"].commands["del"], [profile_name], obj=db)
result = runner.invoke(macsec.macsec, ["profile", "del", profile_name], obj=cfgdb)
assert result.exit_code == 0, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
profile_table = db.cfgdb.get_entry("MACSEC_PROFILE", profile_name)
profile_table = cfgdb.get_entry("MACSEC_PROFILE", profile_name)
assert not profile_table
def test_macsec_valid_profile(self, mock_cfgdb):
cfgdb = mock_cfgdb
runner = CliRunner()
db = Db()
db.cfgdb = mock_cfgdb
profile_name = "test"
profile_map = {
@ -67,9 +64,9 @@ class TestConfigMACsec(object):
if v is not None:
options[-1] += "=" + str(v)
result = runner.invoke(macsec.macsec.commands["profile"].commands["add"], options, obj=db)
result = runner.invoke(macsec.macsec, ["profile", "add"] + options, obj=cfgdb)
assert result.exit_code == 0, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
profile_table = db.cfgdb.get_entry("MACSEC_PROFILE", profile_name)
profile_table = cfgdb.get_entry("MACSEC_PROFILE", profile_name)
assert profile_table
assert profile_table["priority"] == str(profile_map["priority"])
assert profile_table["cipher_suite"] == profile_map["cipher_suite"]
@ -87,62 +84,65 @@ class TestConfigMACsec(object):
assert profile_table["rekey_period"] == str(profile_map["rekey_period"])
def test_macsec_invalid_profile(self, mock_cfgdb):
cfgdb = mock_cfgdb
runner = CliRunner()
db = Db()
db.cfgdb = mock_cfgdb
# Loss primary cak and primary ckn
result = runner.invoke(macsec.macsec.commands["profile"].commands["add"], ["test"], obj=db)
result = runner.invoke(macsec.macsec, ["profile", "add", "test"], obj=cfgdb)
assert result.exit_code != 0
# Invalid primary cak
result = runner.invoke(macsec.macsec.commands["profile"].commands["add"], ["test", "--primary_cak=abcdfghjk90123456789012345678912","--primary_ckn=01234567890123456789012345678912", "--cipher_suite=GCM-AES-128"], obj=db)
result = runner.invoke(macsec.macsec, ["profile", "add", "test",
"--primary_cak=abcdfghjk90123456789012345678912","--primary_ckn=01234567890123456789012345678912",
"--cipher_suite=GCM-AES-128"], obj=cfgdb)
assert result.exit_code != 0
# Invalid primary cak length
result = runner.invoke(macsec.macsec.commands["profile"].commands["add"], ["test", "--primary_cak=01234567890123456789012345678912","--primary_ckn=01234567890123456789012345678912", "--cipher_suite=GCM-AES-256"], obj=db)
result = runner.invoke(macsec.macsec, ["profile", "add", "test",
"--primary_cak=01234567890123456789012345678912","--primary_ckn=01234567890123456789012345678912",
"--cipher_suite=GCM-AES-256"], obj=cfgdb)
assert result.exit_code != 0
def test_macsec_port(self, mock_cfgdb):
cfgdb = mock_cfgdb
runner = CliRunner()
db = Db()
db.cfgdb = mock_cfgdb
result = runner.invoke(macsec.macsec.commands["profile"].commands["add"], ["test", "--primary_cak=01234567890123456789012345678912","--primary_ckn=01234567890123456789012345678912"], obj=db)
result = runner.invoke(macsec.macsec, ["profile", "add", "test",
"--primary_cak=01234567890123456789012345678912","--primary_ckn=01234567890123456789012345678912"],
obj=cfgdb)
assert result.exit_code == 0, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
result = runner.invoke(macsec.macsec.commands["port"].commands["add"], ["Ethernet0", "test"], obj=db)
result = runner.invoke(macsec.macsec, ["port", "add", "Ethernet0", "test"], obj=cfgdb)
assert result.exit_code == 0, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
port_table = db.cfgdb.get_entry("PORT", "Ethernet0")
port_table = cfgdb.get_entry("PORT", "Ethernet0")
assert port_table
assert port_table["macsec"] == "test"
assert port_table["admin_status"] == "up"
result = runner.invoke(macsec.macsec.commands["profile"].commands["del"], ["test"], obj=db)
result = runner.invoke(macsec.macsec, ["profile", "del", "test"], obj=cfgdb)
assert result.exit_code != 0
result = runner.invoke(macsec.macsec.commands["port"].commands["del"], ["Ethernet0"], obj=db)
result = runner.invoke(macsec.macsec, ["port", "del", "Ethernet0"], obj=cfgdb)
assert result.exit_code == 0, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
port_table = db.cfgdb.get_entry("PORT", "Ethernet0")
port_table = cfgdb.get_entry("PORT", "Ethernet0")
assert "macsec" not in port_table or not port_table["macsec"]
assert port_table["admin_status"] == "up"
def test_macsec_invalid_operation(self, mock_cfgdb):
cfgdb = mock_cfgdb
runner = CliRunner()
db = Db()
db.cfgdb = mock_cfgdb
# Enable nonexisted profile
result = runner.invoke(macsec.macsec.commands["port"].commands["add"], ["Ethernet0", "test"], obj=db)
result = runner.invoke(macsec.macsec, ["port", "add", "Ethernet0", "test"], obj=cfgdb)
assert result.exit_code != 0
# Delete nonexisted profile
result = runner.invoke(macsec.macsec.commands["profile"].commands["del"], ["test"], obj=db)
result = runner.invoke(macsec.macsec, ["profile", "del", "test"], obj=cfgdb)
assert result.exit_code != 0
result = runner.invoke(macsec.macsec.commands["profile"].commands["add"], ["test", "--primary_cak=01234567890123456789012345678912","--primary_ckn=01234567890123456789012345678912"], obj=db)
result = runner.invoke(macsec.macsec, ["profile", "add", "test", "--primary_cak=01234567890123456789012345678912","--primary_ckn=01234567890123456789012345678912"], obj=cfgdb)
assert result.exit_code == 0, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
# Repeat add profile
result = runner.invoke(macsec.macsec.commands["profile"].commands["add"], ["test", "--primary_cak=01234567890123456789012345678912","--primary_ckn=01234567890123456789012345678912"], obj=db)
result = runner.invoke(macsec.macsec, ["profile", "add", "test", "--primary_cak=01234567890123456789012345678912","--primary_ckn=01234567890123456789012345678912"], obj=cfgdb)
assert result.exit_code != 0

View File

@ -1,13 +1,27 @@
import click
import utilities_common.cli as clicommon
from sonic_py_common import multi_asic
from swsscommon.swsscommon import ConfigDBConnector
from utilities_common.constants import DEFAULT_NAMESPACE
from utilities_common.db import Db
#
# 'macsec' group ('config macsec ...')
#
@click.group(cls=clicommon.AbbreviationGroup, name='macsec')
def macsec():
# TODO add "hidden=True if this is a single ASIC platform, once we have click 7.0 in all branches.
@click.option('-n', '--namespace', help='Namespace name',
required=True if multi_asic.is_multi_asic() else False, type=click.Choice(multi_asic.get_namespace_list()))
@click.pass_context
def macsec(ctx, namespace):
"""MACsec-related configuration tasks"""
pass
if not ctx.obj or isinstance(ctx.obj, Db):
# Set namespace to default_namespace if it is None.
if namespace is None:
namespace = DEFAULT_NAMESPACE
config_db = ConfigDBConnector(use_unix_socket_path=True, namespace=str(namespace))
config_db.connect()
ctx.obj = config_db
#
@ -24,31 +38,29 @@ def macsec_port():
@macsec_port.command('add')
@click.argument('port', metavar='<port_name>', required=True)
@click.argument('profile', metavar='<profile_name>', required=True)
@clicommon.pass_db
def add_port(db, port, profile):
def add_port(port, profile):
"""
Add MACsec port
"""
ctx = click.get_current_context()
config_db = ctx.obj
if clicommon.get_interface_naming_mode() == "alias":
alias = port
iface_alias_converter = clicommon.InterfaceAliasConverter(db)
port = iface_alias_converter.alias_to_name(alias)
port = interface_alias_to_name(config_db, port)
if port is None:
ctx.fail("cannot find port name for alias {}".format(alias))
ctx.fail("cannot find port name for alias {}".format(port))
profile_entry = db.cfgdb.get_entry('MACSEC_PROFILE', profile)
profile_entry = config_db.get_entry('MACSEC_PROFILE', profile)
if len(profile_entry) == 0:
ctx.fail("profile {} doesn't exist".format(profile))
port_entry = db.cfgdb.get_entry('PORT', port)
port_entry = config_db.get_entry('PORT', port)
if len(port_entry) == 0:
ctx.fail("port {} doesn't exist".format(port))
port_entry['macsec'] = profile
db.cfgdb.set_entry("PORT", port, port_entry)
config_db.set_entry("PORT", port, port_entry)
#
@ -56,27 +68,25 @@ def add_port(db, port, profile):
#
@macsec_port.command('del')
@click.argument('port', metavar='<port_name>', required=True)
@clicommon.pass_db
def del_port(db, port):
def del_port(port):
"""
Delete MACsec port
"""
ctx = click.get_current_context()
config_db = ctx.obj
if clicommon.get_interface_naming_mode() == "alias":
alias = port
iface_alias_converter = clicommon.InterfaceAliasConverter(db)
port = iface_alias_converter.alias_to_name(alias)
port = interface_alias_to_name(config_db, port)
if port is None:
ctx.fail("cannot find port name for alias {}".format(alias))
ctx.fail("cannot find port name for alias {}".format(port))
port_entry = db.cfgdb.get_entry('PORT', port)
port_entry = config_db.get_entry('PORT', port)
if len(port_entry) == 0:
ctx.fail("port {} doesn't exist".format(port))
del port_entry['macsec']
db.cfgdb.set_entry("PORT", port, port_entry)
config_db.set_entry("PORT", port, port_entry)
#
@ -109,13 +119,14 @@ def is_hexstring(hexstring: str):
@click.option('--replay_window', metavar='<enable_replay_protect>', required=False, default=0, show_default=True, type=click.IntRange(0, 2**32), help="Replay window size that is the number of packets that could be out of order. This field works only if ENABLE_REPLAY_PROTECT is true.")
@click.option('--send_sci/--no_send_sci', metavar='<send_sci>', required=False, default=True, show_default=True, is_flag=True, help="Send SCI in SecTAG field of MACsec header.")
@click.option('--rekey_period', metavar='<rekey_period>', required=False, default=0, show_default=True, type=click.IntRange(min=0), help="The period of proactively refresh (Unit second).")
@clicommon.pass_db
def add_profile(db, profile, priority, cipher_suite, primary_cak, primary_ckn, policy, enable_replay_protect, replay_window, send_sci, rekey_period):
def add_profile(profile, priority, cipher_suite, primary_cak, primary_ckn, policy, enable_replay_protect, replay_window, send_sci, rekey_period):
"""
Add MACsec profile
"""
ctx = click.get_current_context()
profile_entry = db.cfgdb.get_entry('MACSEC_PROFILE', profile)
config_db = ctx.obj
profile_entry = config_db.get_entry('MACSEC_PROFILE', profile)
if not len(profile_entry) == 0:
ctx.fail("{} already exists".format(profile))
@ -157,7 +168,7 @@ def add_profile(db, profile, priority, cipher_suite, primary_cak, primary_ckn, p
profile_table[k] = "false"
else:
profile_table[k] = str(v)
db.cfgdb.set_entry("MACSEC_PROFILE", profile, profile_table)
config_db.set_entry("MACSEC_PROFILE", profile, profile_table)
#
@ -165,24 +176,24 @@ def add_profile(db, profile, priority, cipher_suite, primary_cak, primary_ckn, p
#
@macsec_profile.command('del')
@click.argument('profile', metavar='<profile_name>', required=True)
@clicommon.pass_db
def del_profile(db, profile):
def del_profile( profile):
"""
Delete MACsec profile
"""
ctx = click.get_current_context()
config_db = ctx.obj
profile_entry = db.cfgdb.get_entry('MACSEC_PROFILE', profile)
profile_entry = config_db.get_entry('MACSEC_PROFILE', profile)
if len(profile_entry) == 0:
ctx.fail("{} doesn't exist".format(profile))
# Check if the profile is being used by any port
for port in db.cfgdb.get_keys('PORT'):
attr = db.cfgdb.get_entry('PORT', port)
for port in config_db.get_keys('PORT'):
attr = config_db.get_entry('PORT', port)
if 'macsec' in attr and attr['macsec'] == profile:
ctx.fail("{} is being used by port {}, Please remove the MACsec from the port firstly".format(profile, port))
db.cfgdb.set_entry("MACSEC_PROFILE", profile, None)
config_db.set_entry("MACSEC_PROFILE", profile, None)
def register(cli):

View File

@ -4,22 +4,19 @@ from natsort import natsorted
import click
from tabulate import tabulate
from swsscommon.swsscommon import SonicV2Connector
import utilities_common.multi_asic as multi_asic_util
from swsscommon.swsscommon import CounterTable, MacsecCounter
DB_CONNECTOR = SonicV2Connector(use_unix_socket_path=False)
DB_CONNECTOR.connect(DB_CONNECTOR.APPL_DB)
DB_CONNECTOR.connect(DB_CONNECTOR.COUNTERS_DB)
COUNTER_TABLE = CounterTable(DB_CONNECTOR.get_redis_client(DB_CONNECTOR.COUNTERS_DB))
DB_CONNECTOR = None
COUNTER_TABLE = None
class MACsecAppMeta(object):
SEPARATOR = DB_CONNECTOR.get_db_separator(DB_CONNECTOR.APPL_DB)
def __init__(self, *args) -> None:
key = self.__class__.get_appl_table_name() + MACsecAppMeta.SEPARATOR + \
MACsecAppMeta.SEPARATOR.join(args)
SEPARATOR = DB_CONNECTOR.get_db_separator(DB_CONNECTOR.APPL_DB)
key = self.__class__.get_appl_table_name() + SEPARATOR + \
SEPARATOR.join(args)
self.meta = DB_CONNECTOR.get_all(
DB_CONNECTOR.APPL_DB, key)
if len(self.meta) == 0:
@ -184,19 +181,36 @@ def create_macsec_objs(interface_name: str) -> typing.List[MACsecAppMeta]:
@click.command()
@click.argument('interface_name', required=False)
def macsec(interface_name):
ctx = click.get_current_context()
objs = []
interface_names = [name.split(":")[1] for name in DB_CONNECTOR.keys(DB_CONNECTOR.APPL_DB, "MACSEC_PORT*")]
if interface_name is not None:
if interface_name not in interface_names:
ctx.fail("Cannot find the port {} in MACsec port lists {}".format(interface_name, interface_names))
else:
@multi_asic_util.multi_asic_click_options
def macsec(interface_name, namespace, display):
MacsecContext(namespace, display).show(interface_name)
class MacsecContext(object):
def __init__(self, namespace_option, display_option):
self.db = None
self.multi_asic = multi_asic_util.MultiAsic(
display_option, namespace_option)
@multi_asic_util.run_on_multi_asic
def show(self, interface_name):
global DB_CONNECTOR
global COUNTER_TABLE
DB_CONNECTOR = self.db
COUNTER_TABLE = CounterTable(self.db.get_redis_client(self.db.COUNTERS_DB))
interface_names = [name.split(":")[1] for name in self.db.keys(self.db.APPL_DB, "MACSEC_PORT*")]
if interface_name is not None:
if interface_name not in interface_names:
return
interface_names = [interface_name]
for interface_name in natsorted(interface_names):
objs += create_macsec_objs(interface_name)
for obj in objs:
print(obj.dump_str())
objs = []
for interface_name in natsorted(interface_names):
objs += create_macsec_objs(interface_name)
for obj in objs:
print(obj.dump_str())
def register(cli):