Skip to content

Commit

Permalink
Add status for ACL_TABLE and ACL_RULE in STATE_DB (#2749)
Browse files Browse the repository at this point in the history
What I did
This PR is to backport changes in PR #2667 into 202211 branch.
HLD sonic-net/SONiC#1261
This PR is to enhance show acl table and show acl rule commands.
Currently, show acl table and show acl rule commands read ACL table/rule configuration from CONFIG_DB directly. We don't know whether the ACL table or rule is created successfully.
We improved swss to write the status of ACL table/rule into a STATE_DB table. In this PR, the show command is enhanced to read the status from STATE_DB table.

How I did it
Introduce two tables in STATE_DB
orchgent writes the status to STATE_DB
show commands read the status from STATE_DB.
How to verify it
Verified by copying the new script to a testbed, and check the output.

Previous command output (if the output of a command-line utility has changed)
$ show acl table DATAACL
Name     Type    Binding      Description    Stage     
-------  ------  -----------  -------------  -------   
DATAACL  L3      Ethernet0    DATAACL        ingress   
                 Ethernet4
                 Ethernet8
                 Ethernet12
show acl rule
Table    Rule          Priority    Action    Match               
-------  ------------  ----------  --------  ------------------- 
DATAACL  RULE_1        9999        DROP      DST_IP: 9.5.9.3/32  
                                             ETHER_TYPE: 2048
DATAACL  RULE_2        9998        FORWARD   DST_IP: 10.2.1.2/32 
                                             ETHER_TYPE: 2048
                                             IP_PROTOCOL: 6
                                             L4_DST_PORT: 22
New command output (if the output of a command-line utility has changed)
$ show acl table DATAACL
Name     Type    Binding      Description    Stage      Status
-------  ------  -----------  -------------  -------    -------
DATAACL  L3      Ethernet0    DATAACL        ingress    Active
                 Ethernet4
                 Ethernet8
                 Ethernet12
show acl rule
Table    Rule          Priority    Action    Match                Status
-------  ------------  ----------  --------  -------------------  --------
DATAACL  RULE_1        9999        DROP      DST_IP: 9.5.9.3/32   Active
                                             ETHER_TYPE: 2048
DATAACL  RULE_2        9998        FORWARD   DST_IP: 10.2.1.2/32  Active
                                             ETHER_TYPE: 2048
                                             IP_PROTOCOL: 6
                                             L4_DST_PORT: 22
  • Loading branch information
bingwang-ms authored Mar 24, 2023
1 parent b03d0b9 commit 721e26f
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 15 deletions.
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

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 @@ -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"
}
}
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 @@ -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"
}
}
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


0 comments on commit 721e26f

Please sign in to comment.