Skip to content

Commit dba8fcb

Browse files
Validations checks while adding a member to PortChannel and removing a member from a Portchannel (sonic-net#1328)
* Validations checks while adding a member to PortChannel and removing a member from a Portchannel Added below validation checks when user the configures a port as member a portchannel: 1. Check if the given portchannel name is valid or not 2. Check if the given portchannel name exists in CONFIG_DB 3. Check if the given port is configured with an IP address or not 4. Check if the given port is configured with with VLAN membership or not 5. Check if the given port is already member of a portchannel 6. Check if the given port speed matches the speed of existing members 7. Check if the given port MTU matches the speed of existing members Added below validation checks when user the removes member port from a portchannel: 1. Check if the given portchannel name is valid or not 2. Check if the given portchannel name exists in exists in CONFIG_DB or not 3. Check if the given port is member of the given portchannel or not Signed-off-by: Akhilesh Samineni <[email protected]>
1 parent b18ef5a commit dba8fcb

File tree

3 files changed

+241
-1
lines changed

3 files changed

+241
-1
lines changed

config/main.py

+120-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@
6060
CFG_LOOPBACK_ID_MAX_VAL = 999
6161
CFG_LOOPBACK_NO="<0-999>"
6262

63+
CFG_PORTCHANNEL_PREFIX = "PortChannel"
64+
CFG_PORTCHANNEL_PREFIX_LEN = 11
65+
CFG_PORTCHANNEL_NAME_TOTAL_LEN_MAX = 15
66+
CFG_PORTCHANNEL_MAX_VAL = 9999
67+
CFG_PORTCHANNEL_NO="<0-9999>"
68+
69+
PORT_MTU = "mtu"
70+
PORT_SPEED = "speed"
6371

6472
asic_type = None
6573

@@ -390,6 +398,45 @@ def is_interface_bind_to_vrf(config_db, interface_name):
390398
return True
391399
return False
392400

401+
def is_portchannel_name_valid(portchannel_name):
402+
"""Port channel name validation
403+
"""
404+
405+
# Return True if Portchannel name is PortChannelXXXX (XXXX can be 0-9999)
406+
if portchannel_name[:CFG_PORTCHANNEL_PREFIX_LEN] != CFG_PORTCHANNEL_PREFIX :
407+
return False
408+
if (portchannel_name[CFG_PORTCHANNEL_PREFIX_LEN:].isdigit() is False or
409+
int(portchannel_name[CFG_PORTCHANNEL_PREFIX_LEN:]) > CFG_PORTCHANNEL_MAX_VAL) :
410+
return False
411+
if len(portchannel_name) > CFG_PORTCHANNEL_NAME_TOTAL_LEN_MAX:
412+
return False
413+
return True
414+
415+
def is_portchannel_present_in_db(db, portchannel_name):
416+
"""Check if Portchannel is present in Config DB
417+
"""
418+
419+
# Return True if Portchannel name exists in the CONFIG_DB
420+
portchannel_list = db.get_table(CFG_PORTCHANNEL_PREFIX)
421+
if portchannel_list is None:
422+
return False
423+
if portchannel_name in portchannel_list:
424+
return True
425+
return False
426+
427+
def is_port_member_of_this_portchannel(db, port_name, portchannel_name):
428+
"""Check if a port is member of given portchannel
429+
"""
430+
portchannel_list = db.get_table(CFG_PORTCHANNEL_PREFIX)
431+
if portchannel_list is None:
432+
return False
433+
434+
for k,v in db.get_table('PORTCHANNEL_MEMBER'):
435+
if (k == portchannel_name) and (v == port_name):
436+
return True
437+
438+
return False
439+
393440
# Return the namespace where an interface belongs
394441
# The port name input could be in default mode or in alias mode.
395442
def get_port_namespace(port):
@@ -1330,9 +1377,64 @@ def add_portchannel_member(ctx, portchannel_name, port_name):
13301377
ctx.fail("{} is configured as mirror destination port".format(port_name))
13311378

13321379
# Check if the member interface given by user is valid in the namespace.
1333-
if interface_name_is_valid(db, port_name) is False:
1380+
if port_name.startswith("Ethernet") is False or interface_name_is_valid(db, port_name) is False:
13341381
ctx.fail("Interface name is invalid. Please enter a valid interface name!!")
13351382

1383+
# Dont proceed if the port channel name is not valid
1384+
if is_portchannel_name_valid(portchannel_name) is False:
1385+
ctx.fail("{} is invalid!, name should have prefix '{}' and suffix '{}'"
1386+
.format(portchannel_name, CFG_PORTCHANNEL_PREFIX, CFG_PORTCHANNEL_NO))
1387+
1388+
# Dont proceed if the port channel does not exist
1389+
if is_portchannel_present_in_db(db, portchannel_name) is False:
1390+
ctx.fail("{} is not present.".format(portchannel_name))
1391+
1392+
# Dont allow a port to be member of port channel if it is configured with an IP address
1393+
for key in db.get_table('INTERFACE').keys():
1394+
if type(key) != tuple:
1395+
continue
1396+
if key[0] == port_name:
1397+
ctx.fail(" {} has ip address {} configured".format(port_name, key[1]))
1398+
return
1399+
1400+
# Dont allow a port to be member of port channel if it is configured as a VLAN member
1401+
for k,v in db.get_table('VLAN_MEMBER'):
1402+
if v == port_name:
1403+
ctx.fail("%s Interface configured as VLAN_MEMBER under vlan : %s" %(port_name,str(k)))
1404+
return
1405+
1406+
# Dont allow a port to be member of port channel if it is already member of a port channel
1407+
for k,v in db.get_table('PORTCHANNEL_MEMBER'):
1408+
if v == port_name:
1409+
ctx.fail("{} Interface is already member of {} ".format(v,k))
1410+
1411+
# Dont allow a port to be member of port channel if its speed does not match with existing members
1412+
for k,v in db.get_table('PORTCHANNEL_MEMBER'):
1413+
if k == portchannel_name:
1414+
member_port_entry = db.get_entry('PORT', v)
1415+
port_entry = db.get_entry('PORT', port_name)
1416+
1417+
if member_port_entry is not None and port_entry is not None:
1418+
member_port_speed = member_port_entry.get(PORT_SPEED)
1419+
1420+
port_speed = port_entry.get(PORT_SPEED)
1421+
if member_port_speed != port_speed:
1422+
ctx.fail("Port speed of {} is different than the other members of the portchannel {}"
1423+
.format(port_name, portchannel_name))
1424+
1425+
# Dont allow a port to be member of port channel if its MTU does not match with portchannel
1426+
portchannel_entry = db.get_entry('PORTCHANNEL', portchannel_name)
1427+
if portchannel_entry and portchannel_entry.get(PORT_MTU) is not None :
1428+
port_entry = db.get_entry('PORT', port_name)
1429+
1430+
if port_entry and port_entry.get(PORT_MTU) is not None:
1431+
port_mtu = port_entry.get(PORT_MTU)
1432+
1433+
portchannel_mtu = portchannel_entry.get(PORT_MTU)
1434+
if portchannel_mtu != port_mtu:
1435+
ctx.fail("Port MTU of {} is different than the {} MTU size"
1436+
.format(port_name, portchannel_name))
1437+
13361438
db.set_entry('PORTCHANNEL_MEMBER', (portchannel_name, port_name),
13371439
{'NULL': 'NULL'})
13381440

@@ -1342,12 +1444,25 @@ def add_portchannel_member(ctx, portchannel_name, port_name):
13421444
@click.pass_context
13431445
def del_portchannel_member(ctx, portchannel_name, port_name):
13441446
"""Remove member from portchannel"""
1447+
# Dont proceed if the port channel name is not valid
1448+
if is_portchannel_name_valid(portchannel_name) is False:
1449+
ctx.fail("{} is invalid!, name should have prefix '{}' and suffix '{}'"
1450+
.format(portchannel_name, CFG_PORTCHANNEL_PREFIX, CFG_PORTCHANNEL_NO))
1451+
13451452
db = ctx.obj['db']
13461453

13471454
# Check if the member interface given by user is valid in the namespace.
13481455
if interface_name_is_valid(db, port_name) is False:
13491456
ctx.fail("Interface name is invalid. Please enter a valid interface name!!")
13501457

1458+
# Dont proceed if the port channel does not exist
1459+
if is_portchannel_present_in_db(db, portchannel_name) is False:
1460+
ctx.fail("{} is not present.".format(portchannel_name))
1461+
1462+
# Dont proceed if the the port is not an existing member of the port channel
1463+
if not is_port_member_of_this_portchannel(db, port_name, portchannel_name):
1464+
ctx.fail("{} is not a member of portchannel {}".format(port_name, portchannel_name))
1465+
13511466
db.set_entry('PORTCHANNEL_MEMBER', (portchannel_name, port_name), None)
13521467
db.set_entry('PORTCHANNEL_MEMBER', portchannel_name + '|' + port_name, None)
13531468

@@ -2410,6 +2525,10 @@ def mtu(ctx, interface_name, interface_mtu, verbose):
24102525
if interface_name is None:
24112526
ctx.fail("'interface_name' is None!")
24122527

2528+
portchannel_member_table = config_db.get_table('PORTCHANNEL_MEMBER')
2529+
if interface_is_in_portchannel(portchannel_member_table, interface_name):
2530+
ctx.fail("'interface_name' is in portchannel!")
2531+
24132532
if ctx.obj['namespace'] is DEFAULT_NAMESPACE:
24142533
command = "portconfig -p {} -m {}".format(interface_name, interface_mtu)
24152534
else:

tests/mock_tables/config_db.json

+6
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,12 @@
546546
"PORTCHANNEL_INTERFACE|PortChannel0004|FC00::7D/126": {
547547
"NULL": "NULL"
548548
},
549+
"INTERFACE|Ethernet0": {
550+
"NULL": "NULL"
551+
},
552+
"INTERFACE|Ethernet0|14.14.0.1/24": {
553+
"NULL": "NULL"
554+
},
549555
"DEBUG_COUNTER|DEBUG_0": {
550556
"type": "PORT_INGRESS_DROPS"
551557
},

tests/portchannel_test.py

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import os
2+
import traceback
3+
4+
from click.testing import CliRunner
5+
6+
import config.main as config
7+
import show.main as show
8+
from utilities_common.db import Db
9+
10+
class TestPortChannel(object):
11+
@classmethod
12+
def setup_class(cls):
13+
os.environ['UTILITIES_UNIT_TESTING'] = "1"
14+
print("SETUP")
15+
16+
def test_add_portchannel_member_with_invalid_name(self):
17+
runner = CliRunner()
18+
db = Db()
19+
obj = {'db':db.cfgdb}
20+
21+
# add a portchannel member with invalid portchannel name
22+
result = runner.invoke(config.config.commands["portchannel"].commands["member"].commands["add"], ["PortChan005", "Ethernet0"], obj=obj)
23+
print(result.exit_code)
24+
print(result.output)
25+
assert result.exit_code != 0
26+
assert "Error: PortChan005 is invalid!, name should have prefix 'PortChannel' and suffix '<0-9999>'" in result.output
27+
28+
def test_delete_portchannel_member_with_invalid_name(self):
29+
runner = CliRunner()
30+
db = Db()
31+
obj = {'db':db.cfgdb}
32+
33+
# delete a portchannel member with invalid portchannel name
34+
result = runner.invoke(config.config.commands["portchannel"].commands["member"].commands["del"], ["PortChan005", "Ethernet0"], obj=obj)
35+
print(result.exit_code)
36+
print(result.output)
37+
assert result.exit_code != 0
38+
assert "Error: PortChan005 is invalid!, name should have prefix 'PortChannel' and suffix '<0-9999>'" in result.output
39+
40+
def test_add_non_existing_portchannel_member(self):
41+
runner = CliRunner()
42+
db = Db()
43+
obj = {'db':db.cfgdb}
44+
45+
# add a portchannel member with portchannel is not yet created
46+
result = runner.invoke(config.config.commands["portchannel"].commands["member"].commands["add"], ["PortChannel0005", "Ethernet0"], obj=obj)
47+
print(result.exit_code)
48+
print(result.output)
49+
assert result.exit_code != 0
50+
assert "Error: PortChannel0005 is not present." in result.output
51+
52+
def test_delete_non_existing_portchannel_member(self):
53+
runner = CliRunner()
54+
db = Db()
55+
obj = {'db':db.cfgdb}
56+
57+
# delete a portchannel member with portchannel is not yet created
58+
result = runner.invoke(config.config.commands["portchannel"].commands["member"].commands["del"], ["PortChannel0005", "Ethernet0"], obj=obj)
59+
print(result.exit_code)
60+
print(result.output)
61+
assert result.exit_code != 0
62+
assert "Error: PortChannel0005 is not present." in result.output
63+
64+
def test_add_portchannel_member_which_has_ipaddress(self):
65+
runner = CliRunner()
66+
db = Db()
67+
obj = {'db':db.cfgdb}
68+
69+
# add a portchannel member with port which has ip-address
70+
result = runner.invoke(config.config.commands["portchannel"].commands["member"].commands["add"], ["PortChannel1001", "Ethernet0"], obj=obj)
71+
print(result.exit_code)
72+
print(result.output)
73+
assert result.exit_code != 0
74+
assert "Error: Ethernet0 has ip address 14.14.0.1/24 configured" in result.output
75+
76+
def test_add_portchannel_member_which_is_member_of_vlan(self):
77+
runner = CliRunner()
78+
db = Db()
79+
obj = {'db':db.cfgdb}
80+
81+
# add a portchannel member with port which is member of Vlan
82+
result = runner.invoke(config.config.commands["portchannel"].commands["member"].commands["add"], ["PortChannel1001", "Ethernet24"], obj=obj)
83+
print(result.exit_code)
84+
print(result.output)
85+
assert result.exit_code != 0
86+
assert "Error: Ethernet24 Interface configured as VLAN_MEMBER under vlan : Vlan2000" in result.output
87+
88+
def test_add_portchannel_member_which_is_member_of_another_po(self):
89+
runner = CliRunner()
90+
db = Db()
91+
obj = {'db':db.cfgdb}
92+
93+
# add a portchannel member with port which is member of another PO
94+
result = runner.invoke(config.config.commands["portchannel"].commands["member"].commands["add"], ["PortChannel1001", "Ethernet116"], obj=obj)
95+
print(result.exit_code)
96+
print(result.output)
97+
assert result.exit_code != 0
98+
assert "Error: Ethernet116 Interface is already member of PortChannel0002 " in result.output
99+
100+
def test_delete_portchannel_member_which_is_member_of_another_po(self):
101+
runner = CliRunner()
102+
db = Db()
103+
obj = {'db':db.cfgdb}
104+
105+
# delete a portchannel member with port which is member of another PO
106+
result = runner.invoke(config.config.commands["portchannel"].commands["member"].commands["del"], ["PortChannel1001", "Ethernet116"], obj=obj)
107+
print(result.exit_code)
108+
print(result.output)
109+
assert result.exit_code != 0
110+
assert "Error: Ethernet116 is not a member of portchannel PortChannel1001" in result.output
111+
112+
@classmethod
113+
def teardown_class(cls):
114+
os.environ['UTILITIES_UNIT_TESTING'] = "0"
115+
print("TEARDOWN")

0 commit comments

Comments
 (0)