diff --git a/acl_loader/main.py b/acl_loader/main.py index c50efec032..2eab089c21 100644 --- a/acl_loader/main.py +++ b/acl_loader/main.py @@ -72,6 +72,10 @@ class AclLoader(object): ACL_TABLE = "ACL_TABLE" ACL_RULE = "ACL_RULE" + CFG_ACL_TABLE = "ACL_TABLE" + STATE_ACL_TABLE = "ACL_TABLE_TABLE" + CFG_ACL_RULE = "ACL_RULE" + STATE_ACL_RULE = "ACL_RULE_TABLE" ACL_TABLE_TYPE_MIRROR = "MIRROR" ACL_TABLE_TYPE_CTRLPLANE = "CTRLPLANE" CFG_MIRROR_SESSION_TABLE = "MIRROR_SESSION" @@ -117,11 +121,16 @@ def __init__(self): self.tables_db_info = {} self.rules_db_info = {} self.rules_info = {} + self.tables_state_info = None + self.rules_state_info = None # Load database config files load_db_config() self.sessions_db_info = {} + self.acl_table_status = {} + self.acl_rule_status = {} + self.configdb = ConfigDBConnector() self.configdb.connect() self.statedb = SonicV2Connector(host="127.0.0.1") @@ -156,6 +165,8 @@ def __init__(self): self.read_rules_info() self.read_sessions_info() self.read_policers_info() + self.acl_table_status = self.read_acl_object_status_info(self.CFG_ACL_TABLE, self.STATE_ACL_TABLE) + self.acl_rule_status = self.read_acl_object_status_info(self.CFG_ACL_RULE, self.STATE_ACL_RULE) def read_tables_info(self): """ @@ -210,7 +221,7 @@ def read_sessions_info(self): for key in self.sessions_db_info: if self.per_npu_statedb: # For multi-npu platforms we will read from all front asic name space - # statedb as the monitor port will be differnt for each asic + # statedb as the monitor port will be different for each asic # and it's status also might be different (ideally should not happen) # We will store them as dict of 'asic' : value self.sessions_db_info[key]["status"] = {} @@ -224,6 +235,35 @@ def read_sessions_info(self): self.sessions_db_info[key]["status"] = state_db_info.get("status", "inactive") if state_db_info else "error" self.sessions_db_info[key]["monitor_port"] = state_db_info.get("monitor_port", "") if state_db_info else "" + def read_acl_object_status_info(self, cfg_db_table_name, state_db_table_name): + """ + Read ACL_TABLE status or ACL_RULE status from STATE_DB + """ + if self.per_npu_configdb: + namespace_configdb = list(self.per_npu_configdb.values())[0] + keys = namespace_configdb.get_table(cfg_db_table_name).keys() + else: + keys = self.configdb.get_table(cfg_db_table_name).keys() + + status = {} + for key in keys: + # For ACL_RULE, the key is (acl_table_name, acl_rule_name) + if isinstance(key, tuple): + state_db_key = key[0] + "|" + key[1] + else: + state_db_key = key + status[key] = {} + if self.per_npu_statedb: + status[key]['status'] = {} + for namespace_key, namespace_statedb in self.per_npu_statedb.items(): + state_db_info = namespace_statedb.get_all(self.statedb.STATE_DB, "{}|{}".format(state_db_table_name, state_db_key)) + status[key]['status'][namespace_key] = state_db_info.get("status", "N/A") if state_db_info else "N/A" + else: + state_db_info = self.statedb.get_all(self.statedb.STATE_DB, "{}|{}".format(state_db_table_name, state_db_key)) + status[key]['status'] = state_db_info.get("status", "N/A") if state_db_info else "N/A" + + return status + def get_sessions_db_info(self): return self.sessions_db_info @@ -786,32 +826,36 @@ def show_table(self, table_name): :param table_name: Optional. ACL table name. Filter tables by specified name. :return: """ - header = ("Name", "Type", "Binding", "Description", "Stage") + header = ("Name", "Type", "Binding", "Description", "Stage", "Status") data = [] for key, val in self.get_tables_db_info().items(): if table_name and key != table_name: continue - + stage = val.get("stage", Stage.INGRESS).lower() - + # Get ACL table status from STATE_DB + if key in self.acl_table_status: + status = self.acl_table_status[key]['status'] + else: + status = 'N/A' if val["type"] == AclLoader.ACL_TABLE_TYPE_CTRLPLANE: services = natsorted(val["services"]) - data.append([key, val["type"], services[0], val["policy_desc"], stage]) + data.append([key, val["type"], services[0], val["policy_desc"], stage, status]) if len(services) > 1: for service in services[1:]: - data.append(["", "", service, "", ""]) + data.append(["", "", service, "", "", ""]) else: if not val["ports"]: - data.append([key, val["type"], "", val["policy_desc"], stage]) + data.append([key, val["type"], "", val["policy_desc"], stage, status]) else: ports = natsorted(val["ports"]) - data.append([key, val["type"], ports[0], val["policy_desc"], stage]) + data.append([key, val["type"], ports[0], val["policy_desc"], stage, status]) if len(ports) > 1: for port in ports[1:]: - data.append(["", "", port, "", ""]) + data.append(["", "", port, "", "", ""]) print(tabulate.tabulate(data, headers=header, tablefmt="simple", missingval="")) @@ -873,7 +917,7 @@ def show_rule(self, table_name, rule_id): :param rule_id: Optional. ACL rule name. Filter rule by specified rule name. :return: """ - header = ("Table", "Rule", "Priority", "Action", "Match") + header = ("Table", "Rule", "Priority", "Action", "Match", "Status") def pop_priority(val): priority = "N/A" @@ -919,11 +963,16 @@ def pop_matches(val): priority = pop_priority(val) action = pop_action(val) matches = pop_matches(val) - - rule_data = [[tname, rid, priority, action, matches[0]]] + # Get ACL rule status from STATE_DB + status_key = (tname, rid) + if status_key in self.acl_rule_status: + status = self.acl_rule_status[status_key]['status'] + else: + status = "N/A" + rule_data = [[tname, rid, priority, action, matches[0], status]] if len(matches) > 1: for m in matches[1:]: - rule_data.append(["", "", "", "", m]) + rule_data.append(["", "", "", "", m, ""]) raw_data.append([priority, rule_data]) diff --git a/tests/aclshow_test.py b/tests/aclshow_test.py index 90fe46f683..0abe509aad 100644 --- a/tests/aclshow_test.py +++ b/tests/aclshow_test.py @@ -46,6 +46,7 @@ RULE_9 DATAACL 9991 901 900 RULE_10 DATAACL 9989 1001 1000 DEFAULT_RULE DATAACL 1 2 1 +RULE_1 DATAACL_5 9999 N/A N/A RULE_NO_COUNTER DATAACL_NO_COUNTER 9995 N/A N/A RULE_6 EVERFLOW 9994 601 600 RULE_08 EVERFLOW 9992 0 0 @@ -89,8 +90,8 @@ # Expected output for aclshow -r RULE_4,RULE_6 -vv rule4_rule6_verbose_output = '' + \ """Reading ACL info... -Total number of ACL Tables: 11 -Total number of ACL Rules: 20 +Total number of ACL Tables: 12 +Total number of ACL Rules: 21 RULE NAME TABLE NAME PRIO PACKETS COUNT BYTES COUNT ----------- ------------ ------ --------------- ------------- @@ -136,6 +137,7 @@ RULE_9 DATAACL 9991 0 0 RULE_10 DATAACL 9989 0 0 DEFAULT_RULE DATAACL 1 0 0 +RULE_1 DATAACL_5 9999 N/A N/A RULE_NO_COUNTER DATAACL_NO_COUNTER 9995 N/A N/A RULE_6 EVERFLOW 9994 0 0 RULE_08 EVERFLOW 9992 0 0 @@ -161,6 +163,7 @@ RULE_9 DATAACL 9991 0 0 RULE_10 DATAACL 9989 0 0 DEFAULT_RULE DATAACL 1 0 0 +RULE_1 DATAACL_5 9999 N/A N/A RULE_NO_COUNTER DATAACL_NO_COUNTER 9995 100 100 RULE_6 EVERFLOW 9994 0 0 RULE_08 EVERFLOW 9992 0 0 diff --git a/tests/mock_tables/asic0/config_db.json b/tests/mock_tables/asic0/config_db.json index 66b51f4ccb..de20194a64 100644 --- a/tests/mock_tables/asic0/config_db.json +++ b/tests/mock_tables/asic0/config_db.json @@ -246,5 +246,16 @@ "holdtime": "10", "asn": "65200", "keepalive": "3" + }, + "ACL_RULE|DATAACL_5|RULE_1": { + "IP_PROTOCOL": "126", + "PACKET_ACTION": "FORWARD", + "PRIORITY": "9999" + }, + "ACL_TABLE|DATAACL_5": { + "policy_desc": "DATAACL_5", + "ports@": "Ethernet124", + "type": "L3", + "stage": "ingress" } } diff --git a/tests/mock_tables/asic0/state_db.json b/tests/mock_tables/asic0/state_db.json index 2756404971..559af04826 100644 --- a/tests/mock_tables/asic0/state_db.json +++ b/tests/mock_tables/asic0/state_db.json @@ -286,5 +286,11 @@ "STATUS": "up", "REMOTE_MOD": "0", "REMOTE_PORT": "93" + }, + "ACL_TABLE_TABLE|DATAACL_5" : { + "status": "Active" + }, + "ACL_RULE_TABLE|DATAACL_5|RULE_1" : { + "status": "Active" } } diff --git a/tests/mock_tables/asic2/config_db.json b/tests/mock_tables/asic2/config_db.json index 532d85bcbb..bfda10a0d5 100644 --- a/tests/mock_tables/asic2/config_db.json +++ b/tests/mock_tables/asic2/config_db.json @@ -124,5 +124,16 @@ "state": "disabled", "auto_restart": "disabled", "high_mem_alert": "disabled" + }, + "ACL_RULE|DATAACL_5|RULE_1": { + "IP_PROTOCOL": "126", + "PACKET_ACTION": "FORWARD", + "PRIORITY": "9999" + }, + "ACL_TABLE|DATAACL_5": { + "policy_desc": "DATAACL_5", + "ports@": "Ethernet124", + "type": "L3", + "stage": "ingress" } } diff --git a/tests/mock_tables/asic2/state_db.json b/tests/mock_tables/asic2/state_db.json index f6e3eee4cf..c6c8c88898 100644 --- a/tests/mock_tables/asic2/state_db.json +++ b/tests/mock_tables/asic2/state_db.json @@ -207,5 +207,11 @@ "speed_target": "50", "led_status": "green", "timestamp": "20200813 01:32:30" + }, + "ACL_TABLE_TABLE|DATAACL_5" : { + "status": "Active" + }, + "ACL_RULE_TABLE|DATAACL_5|RULE_1" : { + "status": "Active" } } diff --git a/tests/mock_tables/config_db.json b/tests/mock_tables/config_db.json index 899dada260..22744365f1 100644 --- a/tests/mock_tables/config_db.json +++ b/tests/mock_tables/config_db.json @@ -496,6 +496,11 @@ "PACKET_ACTION": "FORWARD", "PRIORITY": "9995" }, + "ACL_RULE|DATAACL_5|RULE_1": { + "IP_PROTOCOL": "126", + "PACKET_ACTION": "FORWARD", + "PRIORITY": "9999" + }, "ACL_TABLE|NULL_ROUTE_V4": { "policy_desc": "DATAACL", "ports@": "PortChannel0002,PortChannel0005,PortChannel0008,PortChannel0011,PortChannel0014,PortChannel0017,PortChannel0020,PortChannel0023", @@ -533,6 +538,12 @@ "type": "L3V6", "stage": "egress" }, + "ACL_TABLE|DATAACL_5": { + "policy_desc": "DATAACL_5", + "ports@": "Ethernet124", + "type": "L3", + "stage": "ingress" + }, "ACL_TABLE|EVERFLOW": { "policy_desc": "EVERFLOW", "ports@": "PortChannel0002,PortChannel0005,PortChannel0008,PortChannel0011,PortChannel0014,PortChannel0017,PortChannel0020,PortChannel0023,Ethernet100,Ethernet104,Ethernet92,Ethernet96,Ethernet84,Ethernet88,Ethernet76,Ethernet80,Ethernet108,Ethernet112,Ethernet64,Ethernet120,Ethernet116,Ethernet124,Ethernet72,Ethernet68", diff --git a/tests/mock_tables/state_db.json b/tests/mock_tables/state_db.json index 92af0858b1..45df6cfe23 100644 --- a/tests/mock_tables/state_db.json +++ b/tests/mock_tables/state_db.json @@ -1176,5 +1176,11 @@ }, "ADVERTISE_NETWORK_TABLE|fccc:a250:a251::a6:1/128": { "profile": "" + }, + "ACL_TABLE_TABLE|DATAACL_5" : { + "status": "Active" + }, + "ACL_RULE_TABLE|DATAACL_5|RULE_1" : { + "status": "Active" } } diff --git a/tests/show_acl_test.py b/tests/show_acl_test.py new file mode 100644 index 0000000000..1b2cdc60a9 --- /dev/null +++ b/tests/show_acl_test.py @@ -0,0 +1,95 @@ +import os +import pytest +from click.testing import CliRunner + +import acl_loader.main as acl_loader_show +from acl_loader import * +from acl_loader.main import * +from importlib import reload + +root_path = os.path.dirname(os.path.abspath(__file__)) +modules_path = os.path.dirname(root_path) +scripts_path = os.path.join(modules_path, "scripts") + + +@pytest.fixture() +def setup_teardown_single_asic(): + os.environ["PATH"] += os.pathsep + scripts_path + os.environ["UTILITIES_UNIT_TESTING"] = "2" + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "" + yield + os.environ["UTILITIES_UNIT_TESTING"] = "0" + + +@pytest.fixture(scope="class") +def setup_teardown_multi_asic(): + os.environ["PATH"] += os.pathsep + scripts_path + os.environ["UTILITIES_UNIT_TESTING"] = "2" + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "multi_asic" + from .mock_tables import mock_multi_asic_3_asics + reload(mock_multi_asic_3_asics) + from .mock_tables import dbconnector + dbconnector.load_namespace_config() + yield + os.environ["UTILITIES_UNIT_TESTING"] = "0" + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "" + from .mock_tables import mock_single_asic + reload(mock_single_asic) + + +class TestShowACLSingleASIC(object): + def test_show_acl_table(self, setup_teardown_single_asic): + runner = CliRunner() + aclloader = AclLoader() + context = { + "acl_loader": aclloader + } + result = runner.invoke(acl_loader_show.cli.commands['show'].commands['table'], ['DATAACL_5'], obj=context) + assert result.exit_code == 0 + # We only care about the third line, which contains the 'Active' + result_top = result.output.split('\n')[2] + expected_output = "DATAACL_5 L3 Ethernet124 DATAACL_5 ingress Active" + assert result_top == expected_output + + def test_show_acl_rule(self, setup_teardown_single_asic): + runner = CliRunner() + aclloader = AclLoader() + context = { + "acl_loader": aclloader + } + result = runner.invoke(acl_loader_show.cli.commands['show'].commands['rule'], ['DATAACL_5'], obj=context) + assert result.exit_code == 0 + # We only care about the third line, which contains the 'Active' + result_top = result.output.split('\n')[2] + expected_output = "DATAACL_5 RULE_1 9999 FORWARD IP_PROTOCOL: 126 Active" + assert result_top == expected_output + + +class TestShowACLMultiASIC(object): + def test_show_acl_table(self, setup_teardown_multi_asic): + runner = CliRunner() + aclloader = AclLoader() + context = { + "acl_loader": aclloader + } + result = runner.invoke(acl_loader_show.cli.commands['show'].commands['table'], ['DATAACL_5'], obj=context) + assert result.exit_code == 0 + # We only care about the third line, which contains the 'Active' + result_top = result.output.split('\n')[2] + expected_output = "DATAACL_5 L3 Ethernet124 DATAACL_5 ingress {'asic0': 'Active', 'asic2': 'Active'}" + assert result_top == expected_output + + def test_show_acl_rule(self, setup_teardown_multi_asic): + runner = CliRunner() + aclloader = AclLoader() + context = { + "acl_loader": aclloader + } + result = runner.invoke(acl_loader_show.cli.commands['show'].commands['rule'], ['DATAACL_5'], obj=context) + assert result.exit_code == 0 + # We only care about the third line, which contains the 'Active' + result_top = result.output.split('\n')[2] + expected_output = "DATAACL_5 RULE_1 9999 FORWARD IP_PROTOCOL: 126 {'asic0': 'Active', 'asic2': 'Active'}" + assert result_top == expected_output + +