Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
240 changes: 150 additions & 90 deletions config/main.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import subprocess
import sys
import time
import itertools

from socket import AF_INET, AF_INET6
from minigraph import parse_device_desc_xml
Expand Down Expand Up @@ -783,6 +784,66 @@ def validate_mirror_session_config(config_db, session_name, dst_port, src_port,

return True

def cli_sroute_to_config(ctx, command_str, strict_nh = True):
if len(command_str) < 2 or len(command_str) > 9:
ctx.fail("argument is not in pattern prefix [vrf <vrf_name>] <A.B.C.D/M> nexthop <[vrf <vrf_name>] <A.B.C.D>>|<dev <dev_name>>!")
if "prefix" not in command_str:
ctx.fail("argument is incomplete, prefix not found!")
if "nexthop" not in command_str and strict_nh:
ctx.fail("argument is incomplete, nexthop not found!")

nexthop_str = None
config_entry = {}
vrf_name = ""

if "nexthop" in command_str:
idx = command_str.index("nexthop")
prefix_str = command_str[:idx]
nexthop_str = command_str[idx:]
else:
prefix_str = command_str[:]

if prefix_str:
if 'prefix' in prefix_str and 'vrf' in prefix_str:
# prefix_str: ['prefix', 'vrf', Vrf-name, ip]
vrf_name = prefix_str[2]
ip_prefix = prefix_str[3]
elif 'prefix' in prefix_str:
# prefix_str: ['prefix', ip]
ip_prefix = prefix_str[1]
else:
ctx.fail("prefix is not in pattern!")

if nexthop_str:
if 'nexthop' in nexthop_str and 'vrf' in nexthop_str:
# nexthop_str: ['nexthop', 'vrf', Vrf-name, ip]
config_entry["nexthop"] = nexthop_str[3]
config_entry["nexthop-vrf"] = nexthop_str[2]
elif 'nexthop' in nexthop_str:
# nexthop_str: ['nexthop', ip]
config_entry["nexthop"] = nexthop_str[1]
elif 'dev' in nexthop_str:
# nexthop_str: ['nexthop', 'dev', ifname]
config_entry["ifname"] = nexthop_str[2]
else:
ctx.fail("nexthop is not in pattern!")

try:
ipaddress.ip_network(ip_prefix)
if 'nexthop' in config_entry:
nh = config_entry['nexthop'].split(',')
for ip in nh:
ipaddress.ip_address(ip)
except ValueError:
ctx.fail("ip address is not valid.")

if not vrf_name == "":
key = vrf_name + "|" + ip_prefix
else:
key = ip_prefix

return key, config_entry

def update_sonic_environment():
"""Prepare sonic environment variable using SONiC environment template file.
"""
Expand Down Expand Up @@ -3153,111 +3214,110 @@ def del_vrf_vni_map(ctx, vrfname):
@click.pass_context
def route(ctx):
"""route-related configuration tasks"""
pass
config_db = ConfigDBConnector()
config_db.connect()
ctx.obj = {}
ctx.obj['config_db'] = config_db
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add the support for multi asic ?
Similar to how it is done for interface command https://github.com/Azure/sonic-utilities/blob/08337aa7637b290bb8407c38b2a5dbe3e8383b3e/config/main.py#L2359

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arlakshm, can we add multiasic as another enhancement PR?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@prsunny, Sure. Can we create an issue or task to track this?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created issue - #1608


@route.command('add', context_settings={"ignore_unknown_options":True})
@click.argument('command_str', metavar='prefix [vrf <vrf_name>] <A.B.C.D/M> nexthop <[vrf <vrf_name>] <A.B.C.D>>|<dev <dev_name>>', nargs=-1, type=click.Path())
@click.pass_context
def add_route(ctx, command_str):
"""Add route command"""
if len(command_str) < 4 or len(command_str) > 9:
ctx.fail("argument is not in pattern prefix [vrf <vrf_name>] <A.B.C.D/M> nexthop <[vrf <vrf_name>] <A.B.C.D>>|<dev <dev_name>>!")
if "prefix" not in command_str:
ctx.fail("argument is incomplete, prefix not found!")
if "nexthop" not in command_str:
ctx.fail("argument is incomplete, nexthop not found!")
for i in range(0, len(command_str)):
if "nexthop" == command_str[i]:
prefix_str = command_str[:i]
nexthop_str = command_str[i:]
vrf_name = ""
cmd = 'sudo vtysh -c "configure terminal" -c "ip route'
if prefix_str:
if len(prefix_str) == 2:
prefix_mask = prefix_str[1]
cmd += ' {}'.format(prefix_mask)
elif len(prefix_str) == 4:
vrf_name = prefix_str[2]
prefix_mask = prefix_str[3]
cmd += ' {}'.format(prefix_mask)
else:
ctx.fail("prefix is not in pattern!")
if nexthop_str:
if len(nexthop_str) == 2:
ip = nexthop_str[1]
if vrf_name == "":
cmd += ' {}'.format(ip)
else:
cmd += ' {} vrf {}'.format(ip, vrf_name)
elif len(nexthop_str) == 3:
dev_name = nexthop_str[2]
if vrf_name == "":
cmd += ' {}'.format(dev_name)
else:
cmd += ' {} vrf {}'.format(dev_name, vrf_name)
elif len(nexthop_str) == 4:
vrf_name_dst = nexthop_str[2]
ip = nexthop_str[3]
if vrf_name == "":
cmd += ' {} nexthop-vrf {}'.format(ip, vrf_name_dst)
config_db = ctx.obj['config_db']
key, route = cli_sroute_to_config(ctx, command_str)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we are writing to DB, lets check the correctness of key (like a valid ip prefix)


# If defined intf name, check if it exists
if 'ifname' in route:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As commented by @arlakshm for PR #1535 , please check if ifname belongs to VLAN_INTERFACE, INTERFACE and PORTCHANNEL_INTERFACE. If not return failure as "IP interface doesn't exist"

if (not route['ifname'] in config_db.get_keys('VLAN') and
not route['ifname'] in config_db.get_keys('INTERFACE') and
not route['ifname'] in config_db.get_keys('PORTCHANNEL')):
ctx.fail('interface {} doesn`t exist'.format(route['ifname']))

# Check if exist entry with key
keys = config_db.get_keys('STATIC_ROUTE')
if key in keys:
# If exist update current entry
current_entry = config_db.get_entry('STATIC_ROUTE', key)

for entry in ['nexthop', 'nexthop-vrf', 'ifname']:
if not entry in current_entry:
current_entry[entry] = ''
if entry in route:
current_entry[entry] += ',' + route[entry]
else:
cmd += ' {} vrf {} nexthop-vrf {}'.format(ip, vrf_name, vrf_name_dst)
else:
ctx.fail("nexthop is not in pattern!")
cmd += '"'
clicommon.run_command(cmd)
current_entry[entry] += ','

config_db.set_entry("STATIC_ROUTE", key, current_entry)
else:
config_db.set_entry("STATIC_ROUTE", key, route)

@route.command('del', context_settings={"ignore_unknown_options":True})
@click.argument('command_str', metavar='prefix [vrf <vrf_name>] <A.B.C.D/M> nexthop <[vrf <vrf_name>] <A.B.C.D>>|<dev <dev_name>>', nargs=-1, type=click.Path())
@click.pass_context
def del_route(ctx, command_str):
"""Del route command"""
if len(command_str) < 4 or len(command_str) > 9:
ctx.fail("argument is not in pattern prefix [vrf <vrf_name>] <A.B.C.D/M> nexthop <[vrf <vrf_name>] <A.B.C.D>>|<dev <dev_name>>!")
if "prefix" not in command_str:
ctx.fail("argument is incomplete, prefix not found!")
if "nexthop" not in command_str:
ctx.fail("argument is incomplete, nexthop not found!")
for i in range(0, len(command_str)):
if "nexthop" == command_str[i]:
prefix_str = command_str[:i]
nexthop_str = command_str[i:]
vrf_name = ""
cmd = 'sudo vtysh -c "configure terminal" -c "no ip route'
if prefix_str:
if len(prefix_str) == 2:
prefix_mask = prefix_str[1]
cmd += ' {}'.format(prefix_mask)
elif len(prefix_str) == 4:
vrf_name = prefix_str[2]
prefix_mask = prefix_str[3]
cmd += ' {}'.format(prefix_mask)
else:
ctx.fail("prefix is not in pattern!")
if nexthop_str:
if len(nexthop_str) == 2:
ip = nexthop_str[1]
if vrf_name == "":
cmd += ' {}'.format(ip)
else:
cmd += ' {} vrf {}'.format(ip, vrf_name)
elif len(nexthop_str) == 3:
dev_name = nexthop_str[2]
if vrf_name == "":
cmd += ' {}'.format(dev_name)
else:
cmd += ' {} vrf {}'.format(dev_name, vrf_name)
elif len(nexthop_str) == 4:
vrf_name_dst = nexthop_str[2]
ip = nexthop_str[3]
if vrf_name == "":
cmd += ' {} nexthop-vrf {}'.format(ip, vrf_name_dst)
config_db = ctx.obj['config_db']
key, route = cli_sroute_to_config(ctx, command_str, strict_nh=False)
keys = config_db.get_keys('STATIC_ROUTE')
prefix_tuple = tuple(key.split('|'))
if not key in keys and not prefix_tuple in keys:
ctx.fail('Route {} doesnt exist'.format(key))
else:
# If not defined nexthop or intf name remove entire route
if not 'nexthop' in route and not 'ifname' in route:
config_db.set_entry("STATIC_ROUTE", key, None)
return

current_entry = config_db.get_entry('STATIC_ROUTE', key)

nh = ['']
nh_vrf = ['']
ifname = ['']
if 'nexthop' in current_entry:
nh = current_entry['nexthop'].split(',')
if 'nexthop-vrf' in current_entry:
nh_vrf = current_entry['nexthop-vrf'].split(',')
if 'ifname' in current_entry:
ifname = current_entry['ifname'].split(',')

# Zip data from config_db into tuples
# {'nexthop': '10.0.0.2,20.0.0.2', 'vrf-nexthop': ',Vrf-RED', 'ifname': ','}
# [('10.0.0.2', '', ''), ('20.0.0.2', 'Vrf-RED', '')]
nh_zip = list(itertools.zip_longest(nh, nh_vrf, ifname, fillvalue=''))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add some comments to understand the logic in this section?

cli_tuple = ()

# Create tuple from CLI argument
# config route add prefix 1.4.3.4/32 nexthop vrf Vrf-RED 20.0.0.2
# ('20.0.0.2', 'Vrf-RED', '')
for entry in ['nexthop', 'nexthop-vrf', 'ifname']:
if entry in route:
cli_tuple += (route[entry],)
else:
cmd += ' {} vrf {} nexthop-vrf {}'.format(ip, vrf_name, vrf_name_dst)
cli_tuple += ('',)

if cli_tuple in nh_zip:
# If cli tuple is in config_db find its index and delete from lists
idx = nh_zip.index(cli_tuple)
if len(nh) - 1 >= idx:
del nh[idx]
if len(nh_vrf) - 1 >= idx:
del nh_vrf[idx]
if len(ifname) - 1 >= idx:
del ifname[idx]
else:
ctx.fail("nexthop is not in pattern!")
cmd += '"'
clicommon.run_command(cmd)
ctx.fail('Not found {} in {}'.format(cli_tuple, key))

if (len(nh) == 0 or (len(nh) == 1 and nh[0] == '')) and \
(len(ifname) == 0 or (len(ifname) == 1 and ifname[0] == '')):
# If there are no nexthop and ifname fields in the current record, delete it
config_db.set_entry("STATIC_ROUTE", key, None)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if user just deletes the route without mentioning any nexthops, then the route is expected to be fully deleted.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the user tries to delete a route without nexthop (for example, 'сonfig del config route del prefix 1.2.3.4/32'), he will receive an error: 'argument is not in pattern'. So if the user has previously added ECMP routes, he must remove them one by one.
This condition checks if the record still has any nexthop, if so, it just updates this record, if it doesn't have the nexthop, it removes the key. Is this the correct behavior or should user be able to delete the entire entry at once?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, user should be able to delete the entire route at once.

else:
# Otherwise it still has ECMP nexthop or ifname fields, so compose it from the lists in db
current_entry['nexthop'] = ','.join((str(e)) for e in nh)
current_entry['nexthop-vrf'] = ','.join((str(e)) for e in nh_vrf)
current_entry['ifname'] = ','.join((str(e)) for e in ifname)
config_db.set_entry("STATIC_ROUTE", key, current_entry)

#
# 'acl' group ('config acl ...')
Expand Down
78 changes: 78 additions & 0 deletions doc/Command-Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
* [Startup & Running Configuration](#startup--running-configuration)
* [Startup Configuration](#startup-configuration)
* [Running Configuration](#running-configuration)
* [Static routing](#static-routing)
* [Syslog](#syslog)
* [Syslog config commands](#syslog-config-commands)
* [System State](#system-state)
Expand Down Expand Up @@ -6746,6 +6747,83 @@ This command displays the running configuration of the snmp module.
Go Back To [Beginning of the document](#) or [Beginning of this section](#Startup--Running-Configuration)


## Static routing

### Static routing Config Commands

This sub-section explains of commands is used to add or remove the static route.

**config route add**

This command is used to add a static route. Note that prefix /nexthop vrf`s and interface name are optional.

- Usage:

```
config route add prefix [vrf <vrf>] <A.B.C.D/M> nexthop [vrf <vrf>] <A.B.C.D> dev <interface name>
```

- Example:

```
admin@sonic:~$ config route add prefix 2.2.3.4/32 nexthop 30.0.0.9
```

It also supports ECMP, and adding a new nexthop to the existing prefix will complement it and not overwrite them.

- Example:

```
admin@sonic:~$ sudo config route add prefix 2.2.3.4/32 nexthop vrf Vrf-RED 30.0.0.9
admin@sonic:~$ sudo config route add prefix 2.2.3.4/32 nexthop vrf Vrf-BLUE 30.0.0.10
```

**config route del**

This command is used to remove a static route. Note that prefix /nexthop vrf`s and interface name are optional.

- Usage:

```
config route del prefix [vrf <vrf>] <A.B.C.D/M> nexthop [vrf <vrf>] <A.B.C.D> dev <interface name>
```

- Example:

```
admin@sonic:~$ sudo config route del prefix 2.2.3.4/32 nexthop vrf Vrf-RED 30.0.0.9
admin@sonic:~$ sudo config route del prefix 2.2.3.4/32 nexthop vrf Vrf-BLUE 30.0.0.10
```

This sub-section explains of command is used to show current routes.

**show ip route**

- Usage:

```
show ip route
```

- Example:

```
admin@sonic:~$ show ip route
Codes: K - kernel route, C - connected, S - static, R - RIP,
O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP,
F - PBR, f - OpenFabric,
> - selected route, * - FIB route, q - queued, r - rejected, b - backup

S>* 0.0.0.0/0 [200/0] via 192.168.111.3, eth0, weight 1, 3d03h58m
S> 1.2.3.4/32 [1/0] via 30.0.0.7, weight 1, 00:00:06
C>* 10.0.0.18/31 is directly connected, Ethernet36, 3d03h57m
C>* 10.0.0.20/31 is directly connected, Ethernet40, 3d03h57m
```

Go Back To [Beginning of the document](#) or [Beginning of this section](#static-routing)


## Syslog

### Syslog Config Commands
Expand Down
Loading