Skip to content

Commit 290ff5f

Browse files
Routed subinterface enhancements (sonic-net#1821)
*CLI based on Routed subinterface enhancements HLD sonic-net#833 *Added support for configuring routed subinterface in short name and long name format *Updated show command to display user configured subinterfaces in correct format.
1 parent 1ea88e2 commit 290ff5f

File tree

6 files changed

+204
-4
lines changed

6 files changed

+204
-4
lines changed

config/main.py

+118-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from portconfig import get_child_ports
2121
from socket import AF_INET, AF_INET6
2222
from sonic_py_common import device_info, multi_asic
23-
from sonic_py_common.interface import get_interface_table_name, get_port_table_name
23+
from sonic_py_common.interface import get_interface_table_name, get_port_table_name, get_intf_longname
2424
from utilities_common import util_base
2525
from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector
2626
from utilities_common.db import Db
@@ -5971,6 +5971,123 @@ def smoothing_interval(interval, rates_type):
59715971
helper = util_base.UtilHelper()
59725972
helper.load_and_register_plugins(plugins, config)
59735973

5974+
#
5975+
# 'subinterface' group ('config subinterface ...')
5976+
#
5977+
@config.group()
5978+
@click.pass_context
5979+
@click.option('-s', '--redis-unix-socket-path', help='unix socket path for redis connection')
5980+
def subinterface(ctx, redis_unix_socket_path):
5981+
"""subinterface-related configuration tasks"""
5982+
kwargs = {}
5983+
if redis_unix_socket_path:
5984+
kwargs['unix_socket_path'] = redis_unix_socket_path
5985+
config_db = ConfigDBConnector(**kwargs)
5986+
config_db.connect(wait_for_init=False)
5987+
ctx.obj = {'db': config_db}
5988+
5989+
def subintf_vlan_check(config_db, parent_intf, vlan):
5990+
subintf_db = config_db.get_table('VLAN_SUB_INTERFACE')
5991+
subintf_names = [k for k in subintf_db if type(k) != tuple]
5992+
for subintf in subintf_names:
5993+
sub_intf_sep_idx = subintf.find(VLAN_SUB_INTERFACE_SEPARATOR)
5994+
if sub_intf_sep_idx == -1:
5995+
continue
5996+
if parent_intf == subintf[:sub_intf_sep_idx]:
5997+
if 'vlan' in subintf_db[subintf]:
5998+
if str(vlan) == subintf_db[subintf]['vlan']:
5999+
return True
6000+
else:
6001+
vlan_id = subintf[sub_intf_sep_idx + 1:]
6002+
if str(vlan) == vlan_id:
6003+
return True
6004+
return False
6005+
6006+
@subinterface.command('add')
6007+
@click.argument('subinterface_name', metavar='<subinterface_name>', required=True)
6008+
@click.argument('vid', metavar='<vid>', required=False, type=click.IntRange(1,4094))
6009+
@click.pass_context
6010+
def add_subinterface(ctx, subinterface_name, vid):
6011+
sub_intf_sep_idx = subinterface_name.find(VLAN_SUB_INTERFACE_SEPARATOR)
6012+
if sub_intf_sep_idx == -1:
6013+
ctx.fail("{} is invalid vlan subinterface".format(subinterface_name))
6014+
6015+
interface_alias = subinterface_name[:sub_intf_sep_idx]
6016+
if interface_alias is None:
6017+
ctx.fail("{} invalid subinterface".format(interface_alias))
6018+
6019+
if interface_alias.startswith("Po") is True:
6020+
intf_table_name = CFG_PORTCHANNEL_PREFIX
6021+
elif interface_alias.startswith("Eth") is True:
6022+
intf_table_name = 'PORT'
6023+
6024+
config_db = ctx.obj['db']
6025+
port_dict = config_db.get_table(intf_table_name)
6026+
if interface_alias is not None:
6027+
if not port_dict:
6028+
ctx.fail("{} parent interface not found. {} table none".format(interface_alias, intf_table_name))
6029+
if get_intf_longname(interface_alias) not in port_dict.keys():
6030+
ctx.fail("{} parent interface not found".format(subinterface_name))
6031+
6032+
# Validate if parent is portchannel member
6033+
portchannel_member_table = config_db.get_table('PORTCHANNEL_MEMBER')
6034+
if interface_is_in_portchannel(portchannel_member_table, interface_alias):
6035+
ctx.fail("{} is configured as a member of portchannel. Cannot configure subinterface"
6036+
.format(interface_alias))
6037+
6038+
# Validate if parent is vlan member
6039+
vlan_member_table = config_db.get_table('VLAN_MEMBER')
6040+
if interface_is_in_vlan(vlan_member_table, interface_alias):
6041+
ctx.fail("{} is configured as a member of vlan. Cannot configure subinterface"
6042+
.format(interface_alias))
6043+
6044+
sub_intfs = [k for k,v in config_db.get_table('VLAN_SUB_INTERFACE').items() if type(k) != tuple]
6045+
if subinterface_name in sub_intfs:
6046+
ctx.fail("{} already exists".format(subinterface_name))
6047+
6048+
subintf_dict = {}
6049+
if vid is not None:
6050+
subintf_dict.update({"vlan" : vid})
6051+
6052+
if subintf_vlan_check(config_db, get_intf_longname(interface_alias), vid) is True:
6053+
ctx.fail("Vlan {} encap already configured on other subinterface on {}".format(vid, interface_alias))
6054+
6055+
subintf_dict.update({"admin_status" : "up"})
6056+
config_db.set_entry('VLAN_SUB_INTERFACE', subinterface_name, subintf_dict)
6057+
6058+
@subinterface.command('del')
6059+
@click.argument('subinterface_name', metavar='<subinterface_name>', required=True)
6060+
@click.pass_context
6061+
def del_subinterface(ctx, subinterface_name):
6062+
sub_intf_sep_idx = subinterface_name.find(VLAN_SUB_INTERFACE_SEPARATOR)
6063+
if sub_intf_sep_idx == -1:
6064+
ctx.fail("{} is invalid vlan subinterface".format(subinterface_name))
6065+
6066+
config_db = ctx.obj['db']
6067+
#subinterface_name = subintf_get_shortname(subinterface_name)
6068+
if interface_name_is_valid(config_db, subinterface_name) is False:
6069+
ctx.fail("{} is invalid ".format(subinterface_name))
6070+
6071+
subintf_config_db = config_db.get_table('VLAN_SUB_INTERFACE')
6072+
sub_intfs = [k for k,v in subintf_config_db.items() if type(k) != tuple]
6073+
if subinterface_name not in sub_intfs:
6074+
ctx.fail("{} does not exists".format(subinterface_name))
6075+
6076+
ips = {}
6077+
ips = [ k[1] for k in config_db.get_table('VLAN_SUB_INTERFACE') if type(k) == tuple and k[0] == subinterface_name ]
6078+
for ip in ips:
6079+
try:
6080+
ipaddress.ip_network(ip, strict=False)
6081+
config_db.set_entry('VLAN_SUB_INTERFACE', (subinterface_name, ip), None)
6082+
except ValueError:
6083+
ctx.fail("Invalid ip {} found on interface {}".format(ip, subinterface_name))
6084+
6085+
subintf_config_db = config_db.get_table('INTERFACE')
6086+
ips = [ k[1] for k in subintf_config_db if type(k) == tuple and k[0] == subinterface_name ]
6087+
for ip in ips:
6088+
config_db.set_entry('INTERFACE', (subinterface_name, ip), None)
6089+
6090+
config_db.set_entry('VLAN_SUB_INTERFACE', subinterface_name, None)
59746091

59756092
if __name__ == '__main__':
59766093
config()

doc/Command-Reference.md

+58
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@
136136
* [Startup Configuration](#startup-configuration)
137137
* [Running Configuration](#running-configuration)
138138
* [Static routing](#static-routing)
139+
* [Subinterfaces](#subinterfaces)
140+
* [Subinterfaces Show Commands](#subinterfaces-show-commands)
141+
* [Subinterfaces Config Commands](#subinterfaces-config-commands)
139142
* [Syslog](#syslog)
140143
* [Syslog config commands](#syslog-config-commands)
141144
* [System State](#system-state)
@@ -391,6 +394,7 @@ This command displays the full list of show commands available in the software;
391394
runningconfiguration Show current running configuration...
392395
services Show all daemon services
393396
startupconfiguration Show startup configuration information
397+
subinterfaces Show details of the sub port interfaces
394398
system-memory Show memory information
395399
tacacs Show TACACS+ configuration
396400
techsupport Gather information for troubleshooting
@@ -8128,6 +8132,60 @@ This sub-section explains of command is used to show current routes.
81288132
81298133
Go Back To [Beginning of the document](#) or [Beginning of this section](#static-routing)
81308134
8135+
## Subinterfaces
8136+
8137+
### Subinterfaces Show Commands
8138+
8139+
**show subinterfaces status**
8140+
8141+
This command displays all the subinterfaces that are configured on the device and its current status.
8142+
8143+
- Usage:
8144+
```
8145+
show subinterfaces status
8146+
```
8147+
8148+
- Example:
8149+
```
8150+
admin@sonic:~$ show subinterfaces status
8151+
Sub port interface Speed MTU Vlan Admin Type
8152+
------------------ ------- ----- ------ ------- -------------------
8153+
Eth64.10 100G 9100 100 up dot1q-encapsulation
8154+
Ethernet0.100 100G 9100 100 up dot1q-encapsulation
8155+
```
8156+
8157+
### Subinterfaces Config Commands
8158+
8159+
This sub-section explains how to configure subinterfaces.
8160+
8161+
**config subinterface**
8162+
8163+
- Usage:
8164+
```
8165+
config subinterface (add | del) <subinterface_name> [vlan <1-4094>]
8166+
```
8167+
8168+
- Example (Create the subinterfces with name "Ethernet0.100"):
8169+
```
8170+
admin@sonic:~$ sudo config subinterface add Ethernet0.100
8171+
```
8172+
8173+
- Example (Create the subinterfces with name "Eth64.100"):
8174+
```
8175+
admin@sonic:~$ sudo config subinterface add Eth64.100 100
8176+
```
8177+
8178+
- Example (Delete the subinterfces with name "Ethernet0.100"):
8179+
```
8180+
admin@sonic:~$ sudo config subinterface del Ethernet0.100
8181+
```
8182+
8183+
- Example (Delete the subinterfces with name "Eth64.100"):
8184+
```
8185+
admin@sonic:~$ sudo config subinterface del Eth64.100 100
8186+
```
8187+
8188+
Go Back To [Beginning of the document](#) or [Beginning of this section](#static-routing)
81318189
81328190
## Syslog
81338191

scripts/intfutil

+3-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ from tabulate import tabulate
1010
from utilities_common import constants
1111
from utilities_common import multi_asic as multi_asic_util
1212
from utilities_common.intf_filter import parse_interface_in_filter
13+
from sonic_py_common.interface import get_intf_longname
1314

1415
# mock the redis for unit test purposes #
1516
try:
@@ -313,12 +314,12 @@ def appl_db_portchannel_status_get(appl_db, config_db, po_name, status_type, por
313314
def appl_db_sub_intf_status_get(appl_db, config_db, front_panel_ports_list, portchannel_speed_dict, sub_intf_name, status_type):
314315
sub_intf_sep_idx = sub_intf_name.find(VLAN_SUB_INTERFACE_SEPARATOR)
315316
if sub_intf_sep_idx != -1:
316-
parent_port_name = sub_intf_name[:sub_intf_sep_idx]
317-
vlan_id = sub_intf_name[sub_intf_sep_idx + 1:]
317+
parent_port_name = get_intf_longname(sub_intf_name[:sub_intf_sep_idx])
318318

319319
full_intf_table_name = "INTF_TABLE" + ":" + sub_intf_name
320320

321321
if status_type == "vlan":
322+
vlan_id = appl_db.get(appl_db.APPL_DB, full_intf_table_name, status_type)
322323
return vlan_id
323324

324325
if status_type == "admin_status":

tests/intfutil_test.py

+15
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ def test_subintf_status(self):
184184
expected_output = (
185185
"Sub port interface Speed MTU Vlan Admin Type\n"
186186
"-------------------- ------- ----- ------ ------- --------------------\n"
187+
" Eth32.10 40G 9100 100 up 802.1q-encapsulation\n"
187188
" Ethernet0.10 25G 9100 10 up 802.1q-encapsulation"
188189
)
189190
self.assertEqual(result.output.strip(), expected_output)
@@ -218,13 +219,27 @@ def test_single_subintf_status(self):
218219
print(output, file=sys.stderr)
219220
self.assertEqual(output.strip(), expected_output)
220221

222+
expected_output = (
223+
"Sub port interface Speed MTU Vlan Admin Type\n"
224+
"-------------------- ------- ----- ------ ------- --------------------\n"
225+
" Eth32.10 40G 9100 100 up 802.1q-encapsulation"
226+
)
227+
# Test 'intfutil status Eth32.10'
228+
output = subprocess.check_output('intfutil -c status -i Eth32.10', stderr=subprocess.STDOUT, shell=True, text=True)
229+
print(output, file=sys.stderr)
230+
self.assertEqual(output.strip(), expected_output)
231+
221232
# Test '--verbose' status of single sub interface
222233
def test_single_subintf_status_verbose(self):
223234
result = self.runner.invoke(show.cli.commands["subinterfaces"].commands["status"], ["Ethernet0.10", "--verbose"])
224235
print(result.output, file=sys.stderr)
225236
expected_output = "Command: intfutil -c status -i Ethernet0.10"
226237
self.assertEqual(result.output.split('\n')[0], expected_output)
227238

239+
result = self.runner.invoke(show.cli.commands["subinterfaces"].commands["status"], ["Eth32.10", "--verbose"])
240+
print(result.output, file=sys.stderr)
241+
expected_output = "Command: intfutil -c status -i Eth32.10"
242+
self.assertEqual(result.output.split('\n')[0], expected_output)
228243

229244
# Test status of single sub interface in alias naming mode
230245
def test_single_subintf_status_alias_mode(self):

tests/mock_tables/appl_db.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,12 @@
136136
"speed": "100000"
137137
},
138138
"INTF_TABLE:Ethernet0.10": {
139-
"admin_status": "up"
139+
"admin_status": "up",
140+
"vlan": "10"
141+
},
142+
"INTF_TABLE:Eth32.10": {
143+
"admin_status": "up",
144+
"vlan": "100"
140145
},
141146
"_GEARBOX_TABLE:phy:1": {
142147
"name": "sesto-1",

tests/mock_tables/config_db.json

+4
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,10 @@
373373
"VLAN_SUB_INTERFACE|Ethernet0.10": {
374374
"admin_status": "up"
375375
},
376+
"VLAN_SUB_INTERFACE|Eth32.10": {
377+
"admin_status": "up",
378+
"vlan": "100"
379+
},
376380
"ACL_RULE|NULL_ROUTE_V4|DEFAULT_RULE": {
377381
"PACKET_ACTION": "DROP",
378382
"PRIORITY": "1"

0 commit comments

Comments
 (0)