Skip to content

Commit 29503ab

Browse files
authored
[portchannel] Added ACL/PBH binding checks to the port before getting added to portchannel (#2151)
- What I did The change to handle it in oA is already added. When this check is not performed when adding the config, the portchannel configuration will be inconsistent b/w Kernel and ASIC - How I did it Utilize the match infra to implement methods to check for ACL/PBH bindings to a port - How to verify it Unit tests Signed-off-by: Vivek Reddy Karri <[email protected]>
1 parent ac89489 commit 29503ab

File tree

7 files changed

+137
-6
lines changed

7 files changed

+137
-6
lines changed

config/main.py

+20-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from utilities_common.intf_filter import parse_interface_in_filter
2828
from utilities_common import bgp_util
2929
import utilities_common.cli as clicommon
30+
from utilities_common.helper import get_port_pbh_binding, get_port_acl_binding
3031
from utilities_common.general import load_db_config, load_module_from_source
3132

3233
from .utils import log
@@ -1802,14 +1803,15 @@ def synchronous_mode(sync_mode):
18021803
@click.option('-n', '--namespace', help='Namespace name',
18031804
required=True if multi_asic.is_multi_asic() else False, type=click.Choice(multi_asic.get_namespace_list()))
18041805
@click.pass_context
1805-
def portchannel(ctx, namespace):
1806+
@clicommon.pass_db
1807+
def portchannel(db, ctx, namespace):
18061808
# Set namespace to default_namespace if it is None.
18071809
if namespace is None:
18081810
namespace = DEFAULT_NAMESPACE
18091811

18101812
config_db = ConfigDBConnector(use_unix_socket_path=True, namespace=str(namespace))
18111813
config_db.connect()
1812-
ctx.obj = {'db': config_db, 'namespace': str(namespace)}
1814+
ctx.obj = {'db': config_db, 'namespace': str(namespace), 'db_wrap': db}
18131815

18141816
@portchannel.command('add')
18151817
@click.argument('portchannel_name', metavar='<portchannel_name>', required=True)
@@ -1939,6 +1941,22 @@ def add_portchannel_member(ctx, portchannel_name, port_name):
19391941
if port_tpid != DEFAULT_TPID:
19401942
ctx.fail("Port TPID of {}: {} is not at default 0x8100".format(port_name, port_tpid))
19411943

1944+
# Don't allow a port to be a member of portchannel if already has ACL bindings
1945+
try:
1946+
acl_bindings = get_port_acl_binding(ctx.obj['db_wrap'], port_name, ctx.obj['namespace'])
1947+
if acl_bindings:
1948+
ctx.fail("Port {} is already bound to following ACL_TABLES: {}".format(port_name, acl_bindings))
1949+
except Exception as e:
1950+
ctx.fail(str(e))
1951+
1952+
# Don't allow a port to be a member of portchannel if already has PBH bindings
1953+
try:
1954+
pbh_bindings = get_port_pbh_binding(ctx.obj['db_wrap'], port_name, DEFAULT_NAMESPACE)
1955+
if pbh_bindings:
1956+
ctx.fail("Port {} is already bound to following PBH_TABLES: {}".format(port_name, pbh_bindings))
1957+
except Exception as e:
1958+
ctx.fail(str(e))
1959+
19421960
db.set_entry('PORTCHANNEL_MEMBER', (portchannel_name, port_name),
19431961
{'NULL': 'NULL'})
19441962

dump/match_helper.py

+17
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
11
from dump.match_infra import MatchRequest
22
from dump.helper import handle_multiple_keys_matched_error
33

4+
# Return dict helper methods
5+
6+
def check_error(ret):
7+
""" Check if the match request failed """
8+
if ret["error"]:
9+
return True, ret["error"]
10+
else:
11+
return False, ""
12+
13+
def get_matched_keys(ret):
14+
""" Return Matched Keys """
15+
failed, err_str = check_error(ret)
16+
if not failed:
17+
return ret["keys"], ""
18+
else:
19+
return [], err_str
20+
421
# Port Helper Methods
522

623
def fetch_port_oid(match_engine, port_name, ns):

dump/match_infra.py

+4
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,10 @@ def clear(self, namespace=None):
250250
elif namespace in self.cache:
251251
del self.cache[namespace]
252252

253+
def fill(self, ns, conn, connected_to):
254+
""" Update internal cache """
255+
self.cache[ns] = {'conn': conn, 'connected_to': set(connected_to)}
256+
253257

254258
class MatchEngine:
255259
"""

tests/mock_tables/config_db.json

+4
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,10 @@
532532
"services@": "SSH",
533533
"type": "CTRLPLANE"
534534
},
535+
"PBH_TABLE|pbh_table1": {
536+
"description": "NVGRE",
537+
"interface_list@": "Ethernet8,Ethernet60"
538+
},
535539
"VLAN|Vlan1000": {
536540
"dhcp_servers@": "192.0.0.1,192.0.0.2,192.0.0.3,192.0.0.4",
537541
"vlanid": "1000"

tests/portchannel_test.py

+22
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,28 @@ def test_delete_portchannel_member_which_is_member_of_another_po(self):
157157
assert result.exit_code != 0
158158
assert "Error: Ethernet116 is not a member of portchannel PortChannel1001" in result.output
159159

160+
def test_add_portchannel_member_with_acl_bindngs(self):
161+
runner = CliRunner()
162+
db = Db()
163+
obj = {'db':db.cfgdb, 'db_wrap':db, 'namespace':''}
164+
165+
result = runner.invoke(config.config.commands["portchannel"].commands["member"].commands["add"], ["PortChannel0002", "Ethernet100"], obj=obj)
166+
print(result.exit_code)
167+
print(result.output)
168+
assert result.exit_code != 0
169+
assert "Error: Port Ethernet100 is already bound to following ACL_TABLES:" in result.output
170+
171+
def test_add_portchannel_member_with_pbh_bindngs(self):
172+
runner = CliRunner()
173+
db = Db()
174+
obj = {'db':db.cfgdb, 'db_wrap':db, 'namespace':''}
175+
176+
result = runner.invoke(config.config.commands["portchannel"].commands["member"].commands["add"], ["PortChannel0002", "Ethernet60"], obj=obj)
177+
print(result.exit_code)
178+
print(result.output)
179+
assert result.exit_code != 0
180+
assert "Error: Port Ethernet60 is already bound to following PBH_TABLES:" in result.output
181+
160182
@classmethod
161183
def teardown_class(cls):
162184
os.environ['UTILITIES_UNIT_TESTING'] = "0"

utilities_common/db.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ def __init__(self):
1515
self.db = SonicV2Connector(host="127.0.0.1")
1616

1717
# Skip connecting to chassis databases in line cards
18-
db_list = list(self.db.get_db_list())
18+
self.db_list = list(self.db.get_db_list())
1919
if not device_info.is_supervisor():
2020
try:
21-
db_list.remove('CHASSIS_APP_DB')
22-
db_list.remove('CHASSIS_STATE_DB')
21+
self.db_list.remove('CHASSIS_APP_DB')
22+
self.db_list.remove('CHASSIS_STATE_DB')
2323
except Exception:
2424
pass
2525

26-
for db_id in db_list:
26+
for db_id in self.db_list:
2727
self.db.connect(db_id)
2828

2929
self.cfgdb_clients[constants.DEFAULT_NAMESPACE] = self.cfgdb

utilities_common/helper.py

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from dump.match_infra import MatchEngine, MatchRequest, ConnectionPool
2+
from dump.match_helper import get_matched_keys
3+
from .db import Db
4+
5+
def get_port_acl_binding(db_wrap, port, ns):
6+
"""
7+
Verify if the port is not bound to any ACL Table
8+
9+
Args:
10+
db_wrap: utilities_common.Db() object
11+
port: Iface name
12+
ns: namespace
13+
14+
Returns:
15+
list: ACL_TABLE names if found,
16+
otherwise empty
17+
"""
18+
ACL = "ACL_TABLE" # Table to look for port bindings
19+
if not isinstance(db_wrap, Db):
20+
raise Exception("db_wrap object is not of type utilities_common.Db")
21+
22+
conn_pool = ConnectionPool()
23+
conn_pool.fill(ns, db_wrap.db_clients[ns], db_wrap.db_list)
24+
m_engine = MatchEngine(conn_pool)
25+
req = MatchRequest(db="CONFIG_DB",
26+
table=ACL,
27+
key_pattern="*",
28+
field="ports@",
29+
value=port,
30+
ns=ns,
31+
match_entire_list=False)
32+
ret = m_engine.fetch(req)
33+
acl_tables, _ = get_matched_keys(ret)
34+
return acl_tables
35+
36+
37+
def get_port_pbh_binding(db_wrap, port, ns):
38+
"""
39+
Verify if the port is not bound to any PBH Table
40+
41+
Args:
42+
db_wrap: Db() object
43+
port: Iface name
44+
ns: namespace
45+
46+
Returns:
47+
list: PBH_TABLE names if found,
48+
otherwise empty
49+
"""
50+
PBH = "PBH_TABLE" # Table to look for port bindings
51+
if not isinstance(db_wrap, Db):
52+
raise Exception("db_wrap object is not of type utilities_common.Db")
53+
54+
conn_pool = ConnectionPool()
55+
conn_pool.fill(ns, db_wrap.db_clients[ns], db_wrap.db_list)
56+
m_engine = MatchEngine(conn_pool)
57+
req = MatchRequest(db="CONFIG_DB",
58+
table=PBH,
59+
key_pattern="*",
60+
field="interface_list@",
61+
value=port,
62+
ns=ns,
63+
match_entire_list=False)
64+
ret = m_engine.fetch(req)
65+
pbh_tables, _ = get_matched_keys(ret)
66+
return pbh_tables

0 commit comments

Comments
 (0)