Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve show acl commands #2667

Merged
merged 7 commits into from
Mar 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 62 additions & 13 deletions acl_loader/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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"] = {}
Expand All @@ -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

Expand Down Expand Up @@ -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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra space?

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=""))

Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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])

Expand Down
7 changes: 5 additions & 2 deletions tests/aclshow_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
----------- ------------ ------ --------------- -------------
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
11 changes: 11 additions & 0 deletions tests/mock_tables/asic0/config_db.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
6 changes: 6 additions & 0 deletions tests/mock_tables/asic0/state_db.json
Original file line number Diff line number Diff line change
Expand Up @@ -249,5 +249,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"
}
}
11 changes: 11 additions & 0 deletions tests/mock_tables/asic2/config_db.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
6 changes: 6 additions & 0 deletions tests/mock_tables/asic2/state_db.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
11 changes: 11 additions & 0 deletions tests/mock_tables/config_db.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
6 changes: 6 additions & 0 deletions tests/mock_tables/state_db.json
Original file line number Diff line number Diff line change
Expand Up @@ -1155,5 +1155,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"
}
}
95 changes: 95 additions & 0 deletions tests/show_acl_test.py
Original file line number Diff line number Diff line change
@@ -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