Skip to content

Commit 899ba12

Browse files
Subinterface vrf bind issue fix (sonic-net#2211)
* Add support to configure routed subinterface in short name and long name format * Update "show subniterface status" to reflect subinterface in user configured long name and short name format.
1 parent e45b47a commit 899ba12

File tree

8 files changed

+433
-3
lines changed

8 files changed

+433
-3
lines changed

config/main.py

+29-2
Original file line numberDiff line numberDiff line change
@@ -4914,6 +4914,11 @@ def bind(ctx, interface_name, vrf_name):
49144914
interface_addresses = get_interface_ipaddresses(config_db, interface_name)
49154915
for ipaddress in interface_addresses:
49164916
remove_router_interface_ip_address(config_db, interface_name, ipaddress)
4917+
if table_name == "VLAN_SUB_INTERFACE":
4918+
subintf_entry = config_db.get_entry(table_name, interface_name)
4919+
if 'vrf_name' in subintf_entry:
4920+
subintf_entry.pop('vrf_name')
4921+
49174922
config_db.set_entry(table_name, interface_name, None)
49184923
# When config_db del entry and then add entry with same key, the DEL will lost.
49194924
if ctx.obj['namespace'] is DEFAULT_NAMESPACE:
@@ -4925,7 +4930,11 @@ def bind(ctx, interface_name, vrf_name):
49254930
while state_db.exists(state_db.STATE_DB, _hash):
49264931
time.sleep(0.01)
49274932
state_db.close(state_db.STATE_DB)
4928-
config_db.set_entry(table_name, interface_name, {"vrf_name": vrf_name})
4933+
if table_name == "VLAN_SUB_INTERFACE":
4934+
subintf_entry['vrf_name'] = vrf_name
4935+
config_db.set_entry(table_name, interface_name, subintf_entry)
4936+
else:
4937+
config_db.set_entry(table_name, interface_name, {"vrf_name": vrf_name})
49294938

49304939
#
49314940
# 'unbind' subcommand
@@ -4947,12 +4956,21 @@ def unbind(ctx, interface_name):
49474956
table_name = get_interface_table_name(interface_name)
49484957
if table_name == "":
49494958
ctx.fail("'interface_name' is not valid. Valid names [Ethernet/PortChannel/Vlan/Loopback]")
4959+
49504960
if is_interface_bind_to_vrf(config_db, interface_name) is False:
49514961
return
4962+
if table_name == "VLAN_SUB_INTERFACE":
4963+
subintf_entry = config_db.get_entry(table_name, interface_name)
4964+
if 'vrf_name' in subintf_entry:
4965+
subintf_entry.pop('vrf_name')
4966+
49524967
interface_ipaddresses = get_interface_ipaddresses(config_db, interface_name)
49534968
for ipaddress in interface_ipaddresses:
49544969
remove_router_interface_ip_address(config_db, interface_name, ipaddress)
4955-
config_db.set_entry(table_name, interface_name, None)
4970+
if table_name == "VLAN_SUB_INTERFACE":
4971+
config_db.set_entry(table_name, interface_name, subintf_entry)
4972+
else:
4973+
config_db.set_entry(table_name, interface_name, None)
49564974

49574975
#
49584976
# 'ipv6' subgroup ('config interface ipv6 ...')
@@ -6677,6 +6695,13 @@ def subintf_vlan_check(config_db, parent_intf, vlan):
66776695
return True
66786696
return False
66796697

6698+
def is_subintf_shortname(intf):
6699+
if VLAN_SUB_INTERFACE_SEPARATOR in intf:
6700+
if intf.startswith("Ethernet") or intf.startswith("PortChannel"):
6701+
return False
6702+
return True
6703+
return False
6704+
66806705
@subinterface.command('add')
66816706
@click.argument('subinterface_name', metavar='<subinterface_name>', required=True)
66826707
@click.argument('vid', metavar='<vid>', required=False, type=click.IntRange(1,4094))
@@ -6722,6 +6747,8 @@ def add_subinterface(ctx, subinterface_name, vid):
67226747
subintf_dict = {}
67236748
if vid is not None:
67246749
subintf_dict.update({"vlan" : vid})
6750+
elif is_subintf_shortname(subinterface_name):
6751+
ctx.fail("{} Encap vlan is mandatory for short name subinterfaces".format(subinterface_name))
67256752

67266753
if subintf_vlan_check(config_db, get_intf_longname(interface_alias), vid) is True:
67276754
ctx.fail("Vlan {} encap already configured on other subinterface on {}".format(vid, interface_alias))

tests/intfutil_test.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,8 @@ def test_subintf_status(self):
210210
"Sub port interface Speed MTU Vlan Admin Type\n"
211211
"-------------------- ------- ----- ------ ------- --------------------\n"
212212
" Eth32.10 40G 9100 100 up 802.1q-encapsulation\n"
213-
" Ethernet0.10 25G 9100 10 up 802.1q-encapsulation"
213+
" Ethernet0.10 25G 9100 10 up 802.1q-encapsulation\n"
214+
" Po0001.10 40G 9100 100 up 802.1q-encapsulation"
214215
)
215216
self.assertEqual(result.output.strip(), expected_output)
216217

@@ -254,6 +255,16 @@ def test_single_subintf_status(self):
254255
print(output, file=sys.stderr)
255256
self.assertEqual(output.strip(), expected_output)
256257

258+
expected_output = (
259+
"Sub port interface Speed MTU Vlan Admin Type\n"
260+
"-------------------- ------- ----- ------ ------- --------------------\n"
261+
" Po0001.10 40G 9100 100 up 802.1q-encapsulation"
262+
)
263+
# Test 'intfutil status Po0001.10'
264+
output = subprocess.check_output('intfutil -c status -i Po0001.10', stderr=subprocess.STDOUT, shell=True, text=True)
265+
print(output, file=sys.stderr)
266+
self.assertEqual(output.strip(), expected_output)
267+
257268
# Test '--verbose' status of single sub interface
258269
def test_single_subintf_status_verbose(self):
259270
result = self.runner.invoke(show.cli.commands["subinterfaces"].commands["status"], ["Ethernet0.10", "--verbose"])
@@ -266,6 +277,11 @@ def test_single_subintf_status_verbose(self):
266277
expected_output = "Command: intfutil -c status -i Eth32.10"
267278
self.assertEqual(result.output.split('\n')[0], expected_output)
268279

280+
result = self.runner.invoke(show.cli.commands["subinterfaces"].commands["status"], ["Po0001.10", "--verbose"])
281+
print(result.output, file=sys.stderr)
282+
expected_output = "Command: intfutil -c status -i Po0001.10"
283+
self.assertEqual(result.output.split('\n')[0], expected_output)
284+
269285
# Test status of single sub interface in alias naming mode
270286
def test_single_subintf_status_alias_mode(self):
271287
os.environ["SONIC_CLI_IFACE_MODE"] = "alias"

tests/ip_config_test.py

+44
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,36 @@ def test_add_del_interface_valid_ipv4(self):
4848
assert result.exit_code == 0
4949
assert ('Ethernet64', '10.10.10.1/24') in db.cfgdb.get_table('INTERFACE')
5050

51+
# config int ip add Ethernet0.10 10.11.10.1/24
52+
result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Ethernet0.10", "10.11.10.1/24"], obj=obj)
53+
print(result.exit_code, result.output)
54+
assert result.exit_code == 0
55+
assert ('Ethernet0.10', '10.11.10.1/24') in db.cfgdb.get_table('VLAN_SUB_INTERFACE')
56+
57+
# config int ip add Eth32.10 32.11.10.1/24
58+
result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Eth32.10", "32.11.10.1/24"], obj=obj)
59+
print(result.exit_code, result.output)
60+
assert result.exit_code == 0
61+
assert ('Eth32.10', '32.11.10.1/24') in db.cfgdb.get_table('VLAN_SUB_INTERFACE')
62+
5163
# config int ip remove Ethernet64 10.10.10.1/24
5264
result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["remove"], ["Ethernet64", "10.10.10.1/24"], obj=obj)
5365
print(result.exit_code, result.output)
5466
assert result.exit_code != 0
5567
assert ('Ethernet64', '10.10.10.1/24') not in db.cfgdb.get_table('INTERFACE')
5668

69+
# config int ip remove Ethernet0.10 10.11.10.1/24
70+
result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["remove"], ["Ethernet0.10", "10.11.10.1/24"], obj=obj)
71+
print(result.exit_code, result.output)
72+
assert result.exit_code != 0
73+
assert ('Ethernet0.10', '10.11.10.1/24') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE')
74+
75+
# config int ip remove Eth32.10 32.11.10.1/24
76+
result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["remove"], ["Eth32.10", "32.11.10.1/24"], obj=obj)
77+
print(result.exit_code, result.output)
78+
assert result.exit_code != 0
79+
assert ('Eth32.10', '32.11.10.1/24') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE')
80+
5781
def test_add_interface_invalid_ipv4(self):
5882
db = Db()
5983
runner = CliRunner()
@@ -100,12 +124,32 @@ def test_add_del_interface_valid_ipv6(self):
100124
assert result.exit_code == 0
101125
assert ('Ethernet72', '2001:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34') in db.cfgdb.get_table('INTERFACE')
102126

127+
result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Ethernet0.10", "1010:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34"], obj=obj)
128+
print(result.exit_code, result.output)
129+
assert result.exit_code == 0
130+
assert ('Ethernet0.10', '1010:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34') in db.cfgdb.get_table('VLAN_SUB_INTERFACE')
131+
132+
result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Eth32.10", "3210:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34"], obj=obj)
133+
print(result.exit_code, result.output)
134+
assert result.exit_code == 0
135+
assert ('Eth32.10', '3210:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34') in db.cfgdb.get_table('VLAN_SUB_INTERFACE')
136+
103137
# config int ip remove Ethernet72 2001:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34
104138
result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["remove"], ["Ethernet72", "2001:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34"], obj=obj)
105139
print(result.exit_code, result.output)
106140
assert result.exit_code != 0
107141
assert ('Ethernet72', '2001:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34') not in db.cfgdb.get_table('INTERFACE')
108142

143+
result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["remove"], ["Ethernet0.10", "1010:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34"], obj=obj)
144+
print(result.exit_code, result.output)
145+
assert result.exit_code != 0
146+
assert ('Ethernet0.10', '1010:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE')
147+
148+
result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["remove"], ["Eth32.10", "3210:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34"], obj=obj)
149+
print(result.exit_code, result.output)
150+
assert result.exit_code != 0
151+
assert ('Eth32.10', '3210:1db8:11a3:19d7:1f34:8a2e:17a0:765d/34') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE')
152+
109153
def test_del_interface_case_sensitive_ipv6(self):
110154
db = Db()
111155
runner = CliRunner()

tests/mock_tables/appl_db.json

+26
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,34 @@
190190
},
191191
"INTF_TABLE:Eth32.10": {
192192
"admin_status": "up",
193+
"vrf_name": "Vrf1",
193194
"vlan": "100"
194195
},
196+
"INTF_TABLE:Po0001.10": {
197+
"admin_status": "up",
198+
"vrf_name": "Vrf1",
199+
"vlan": "100"
200+
},
201+
"INTF_TABLE:Ethernet0.10|10.11.12.13/24": {
202+
"family": "IPv4",
203+
"scope": "global"
204+
},
205+
"INTF_TABLE:Eth32.10|32.10.11.12/24": {
206+
"family": "IPv4",
207+
"scope": "global"
208+
},
209+
"INTF_TABLE:Po0001.10|10.10.11.12/24": {
210+
"family": "IPv4",
211+
"scope": "global"
212+
},
213+
"INTF_TABLE:Eth32.10|3210::12/126": {
214+
"family": "IPv6",
215+
"scope": "global"
216+
},
217+
"INTF_TABLE:Po0001.10|1010::12/126": {
218+
"family": "IPv6",
219+
"scope": "global"
220+
},
195221
"_GEARBOX_TABLE:phy:1": {
196222
"name": "sesto-1",
197223
"phy_id": "1",

tests/mock_tables/config_db.json

+15
Original file line numberDiff line numberDiff line change
@@ -379,11 +379,26 @@
379379
"VLAN_SUB_INTERFACE|Eth32.10": {
380380
"admin_status": "up",
381381
"loopback_action": "drop",
382+
"vrf_name": "Vrf1",
382383
"vlan": "100"
383384
},
384385
"VLAN_SUB_INTERFACE|Eth32.10|32.10.11.12/24": {
385386
"NULL" : "NULL"
386387
},
388+
"VLAN_SUB_INTERFACE|Eth32.10|3210::12/126": {
389+
"NULL" : "NULL"
390+
},
391+
"VLAN_SUB_INTERFACE|Po0001.10": {
392+
"admin_status": "up",
393+
"vrf_name": "Vrf1",
394+
"vlan": "100"
395+
},
396+
"VLAN_SUB_INTERFACE|Po0001.10|10.10.11.12/24": {
397+
"NULL" : "NULL"
398+
},
399+
"VLAN_SUB_INTERFACE|Po0001.10|1010::12/126": {
400+
"NULL" : "NULL"
401+
},
387402
"ACL_RULE|NULL_ROUTE_V4|DEFAULT_RULE": {
388403
"PACKET_ACTION": "DROP",
389404
"PRIORITY": "1"

tests/show_vrf_test.py

+87
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from swsscommon.swsscommon import SonicV2Connector
55
from utilities_common.db import Db
66

7+
import config.main as config
78
import show.main as show
89

910
test_path = os.path.dirname(os.path.abspath(__file__))
@@ -31,6 +32,92 @@ def test_vrf_show(self):
3132
Eth32.10
3233
Vrf103 Ethernet4
3334
Loopback0
35+
Po0002.101
36+
"""
37+
38+
result = runner.invoke(show.cli.commands['vrf'], [], obj=db)
39+
dbconnector.dedicated_dbs = {}
40+
assert result.exit_code == 0
41+
assert result.output == expected_output
42+
43+
def test_vrf_bind_unbind(self):
44+
from .mock_tables import dbconnector
45+
jsonfile_config = os.path.join(mock_db_path, "config_db")
46+
dbconnector.dedicated_dbs['CONFIG_DB'] = jsonfile_config
47+
runner = CliRunner()
48+
db = Db()
49+
expected_output = """\
50+
VRF Interfaces
51+
------ ---------------
52+
Vrf1
53+
Vrf101 Ethernet0.10
54+
Vrf102 PortChannel0002
55+
Vlan40
56+
Eth32.10
57+
Vrf103 Ethernet4
58+
Loopback0
59+
Po0002.101
60+
"""
61+
62+
result = runner.invoke(show.cli.commands['vrf'], [], obj=db)
63+
dbconnector.dedicated_dbs = {}
64+
assert result.exit_code == 0
65+
assert result.output == expected_output
66+
67+
obj = {'config_db':db.cfgdb}
68+
69+
result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["unbind"], ["Ethernet4"], obj=obj)
70+
print(result.exit_code, result.output)
71+
assert result.exit_code == 0
72+
assert 'Ethernet4' not in db.cfgdb.get_table('INTERFACE')
73+
74+
result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["unbind"], ["Loopback0"], obj=obj)
75+
print(result.exit_code, result.output)
76+
assert result.exit_code == 0
77+
assert 'Loopback0' not in db.cfgdb.get_table('LOOPBACK_INTERFACE')
78+
79+
result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["unbind"], ["Vlan40"], obj=obj)
80+
print(result.exit_code, result.output)
81+
assert result.exit_code == 0
82+
assert 'Vlan40' not in db.cfgdb.get_table('VLAN_INTERFACE')
83+
84+
result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["unbind"], ["PortChannel0002"], obj=obj)
85+
print(result.exit_code, result.output)
86+
assert result.exit_code == 0
87+
assert 'PortChannel002' not in db.cfgdb.get_table('PORTCHANNEL_INTERFACE')
88+
89+
result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["unbind"], ["Eth32.10"], obj=obj)
90+
print(result.exit_code, result.output)
91+
assert result.exit_code == 0
92+
assert ('vrf_name', 'Vrf102') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE')['Eth32.10']
93+
94+
result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["unbind"], ["Ethernet0.10"], obj=obj)
95+
print(result.exit_code, result.output)
96+
assert result.exit_code == 0
97+
assert ('vrf_name', 'Vrf101') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE')['Ethernet0.10']
98+
99+
result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["unbind"], ["Po0002.101"], obj=obj)
100+
print(result.exit_code, result.output)
101+
assert result.exit_code == 0
102+
assert ('vrf_name', 'Vrf103') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE')['Po0002.101']
103+
104+
105+
#Bind click CLI cannot be tested as it tries to connecte to statedb
106+
#for verification of all IP address delete before applying new vrf configuration
107+
jsonfile_config = os.path.join(mock_db_path, "config_db")
108+
dbconnector.dedicated_dbs['CONFIG_DB'] = jsonfile_config
109+
110+
expected_output = """\
111+
VRF Interfaces
112+
------ ---------------
113+
Vrf1
114+
Vrf101 Ethernet0.10
115+
Vrf102 PortChannel0002
116+
Vlan40
117+
Eth32.10
118+
Vrf103 Ethernet4
119+
Loopback0
120+
Po0002.101
34121
"""
35122

36123
result = runner.invoke(show.cli.commands['vrf'], [], obj=db)

0 commit comments

Comments
 (0)