-
Notifications
You must be signed in to change notification settings - Fork 711
[VRF]: submit vrf CLI #392 #558
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
72df947
ba45568
d4fe120
0be6eb1
96e7fa9
1599f62
c7702f8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
import netaddr | ||
import re | ||
import syslog | ||
import time | ||
|
||
import sonic_device_util | ||
import ipaddress | ||
|
@@ -127,6 +128,57 @@ def interface_name_to_alias(interface_name): | |
|
||
return None | ||
|
||
def get_interface_table_name(interface_name): | ||
"""Get table name by interface_name prefix | ||
""" | ||
if interface_name.startswith("Ethernet"): | ||
return "INTERFACE" | ||
elif interface_name.startswith("PortChannel"): | ||
return "PORTCHANNEL_INTERFACE" | ||
elif interface_name.startswith("Vlan"): | ||
return "VLAN_INTERFACE" | ||
elif interface_name.startswith("Loopback"): | ||
return "LOOPBACK_INTERFACE" | ||
else: | ||
return "" | ||
|
||
def interface_ipaddr_dependent_on_interface(config_db, interface_name): | ||
"""Get table keys including ipaddress | ||
""" | ||
data = [] | ||
table_name = get_interface_table_name(interface_name) | ||
if table_name == "": | ||
return data | ||
keys = config_db.get_keys(table_name) | ||
for key in keys: | ||
if interface_name in key and len(key) == 2: | ||
data.append(key) | ||
return data | ||
|
||
def is_interface_bind_to_vrf(config_db, interface_name): | ||
"""Get interface if bind to vrf or not | ||
""" | ||
table_name = get_interface_table_name(interface_name) | ||
if table_name == "": | ||
return False | ||
entry = config_db.get_entry(table_name, interface_name) | ||
if entry and entry.get("vrf_name"): | ||
return True | ||
return False | ||
|
||
def del_interface_bind_to_vrf(config_db, vrf_name): | ||
"""del interface bind to vrf | ||
""" | ||
tables = ['INTERFACE', 'PORTCHANNEL_INTERFACE', 'VLAN_INTERFACE', 'LOOPBACK_INTERFACE'] | ||
for table_name in tables: | ||
interface_dict = config_db.get_table(table_name) | ||
if interface_dict: | ||
for interface_name in interface_dict.keys(): | ||
if interface_dict[interface_name].has_key('vrf_name') and vrf_name == interface_dict[interface_name]['vrf_name']: | ||
interface_dependent = interface_ipaddr_dependent_on_interface(config_db, interface_name) | ||
for interface_del in interface_dependent: | ||
config_db.set_entry(table_name, interface_del, None) | ||
config_db.set_entry(table_name, interface_name, None) | ||
|
||
def set_interface_naming_mode(mode): | ||
"""Modify SONIC_CLI_IFACE_MODE env variable in user .bashrc | ||
|
@@ -953,16 +1005,13 @@ def add(ctx, interface_name, ip_addr): | |
|
||
try: | ||
ipaddress.ip_network(unicode(ip_addr), strict=False) | ||
if interface_name.startswith("Ethernet"): | ||
config_db.set_entry("INTERFACE", (interface_name, ip_addr), {"NULL": "NULL"}) | ||
elif interface_name.startswith("PortChannel"): | ||
config_db.set_entry("PORTCHANNEL_INTERFACE", (interface_name, ip_addr), {"NULL": "NULL"}) | ||
elif interface_name.startswith("Vlan"): | ||
config_db.set_entry("VLAN_INTERFACE", (interface_name, ip_addr), {"NULL": "NULL"}) | ||
elif interface_name.startswith("Loopback"): | ||
config_db.set_entry("LOOPBACK_INTERFACE", (interface_name, ip_addr), {"NULL": "NULL"}) | ||
else: | ||
table_name = get_interface_table_name(interface_name) | ||
if table_name == "": | ||
ctx.fail("'interface_name' is not valid. Valid names [Ethernet/PortChannel/Vlan/Loopback]") | ||
interface_entry = config_db.get_entry(table_name, interface_name) | ||
if len(interface_entry) == 0: | ||
config_db.set_entry(table_name, interface_name, {"NULL": "NULL"}) | ||
config_db.set_entry(table_name, (interface_name, ip_addr), {"NULL": "NULL"}) | ||
except ValueError: | ||
ctx.fail("'ip_addr' is not valid.") | ||
|
||
|
@@ -984,18 +1033,235 @@ def remove(ctx, interface_name, ip_addr): | |
|
||
try: | ||
ipaddress.ip_network(unicode(ip_addr), strict=False) | ||
if interface_name.startswith("Ethernet"): | ||
config_db.set_entry("INTERFACE", (interface_name, ip_addr), None) | ||
elif interface_name.startswith("PortChannel"): | ||
config_db.set_entry("PORTCHANNEL_INTERFACE", (interface_name, ip_addr), None) | ||
elif interface_name.startswith("Vlan"): | ||
config_db.set_entry("VLAN_INTERFACE", (interface_name, ip_addr), None) | ||
elif interface_name.startswith("Loopback"): | ||
config_db.set_entry("LOOPBACK_INTERFACE", (interface_name, ip_addr), None) | ||
else: | ||
table_name = get_interface_table_name(interface_name) | ||
if table_name == "": | ||
ctx.fail("'interface_name' is not valid. Valid names [Ethernet/PortChannel/Vlan/Loopback]") | ||
config_db.set_entry(table_name, (interface_name, ip_addr), None) | ||
interface_dependent = interface_ipaddr_dependent_on_interface(config_db, interface_name) | ||
if len(interface_dependent) == 0 and is_interface_bind_to_vrf(config_db, interface_name) is False: | ||
config_db.set_entry(table_name, interface_name, None) | ||
except ValueError: | ||
ctx.fail("'ip_addr' is not valid.") | ||
|
||
# | ||
# 'vrf' subgroup ('config interface vrf ...') | ||
# | ||
|
||
|
||
@interface.group() | ||
@click.pass_context | ||
def vrf(ctx): | ||
"""Bind or unbind VRF""" | ||
pass | ||
|
||
# | ||
# 'bind' subcommand | ||
# | ||
@vrf.command() | ||
@click.argument('interface_name', metavar='<interface_name>', required=True) | ||
@click.argument('vrf_name', metavar='<vrf_name>', required=True) | ||
@click.pass_context | ||
def bind(ctx, interface_name, vrf_name): | ||
"""Bind the interface to VRF""" | ||
config_db = ctx.obj["config_db"] | ||
if get_interface_naming_mode() == "alias": | ||
interface_name = interface_alias_to_name(interface_name) | ||
if interface_name is None: | ||
ctx.fail("'interface_name' is None!") | ||
|
||
table_name = get_interface_table_name(interface_name) | ||
if table_name == "": | ||
ctx.fail("'interface_name' is not valid. Valid names [Ethernet/PortChannel/Vlan/Loopback]") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to add any support for mgmt intf bind to mgmt VRF (if not present already) as part of this pull request or that should be handled separately? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now mgmt intf vrf has its own CLI. |
||
if is_interface_bind_to_vrf(config_db, interface_name) is True and \ | ||
config_db.get_entry(table_name, interface_name).get('vrf_name') == vrf_name: | ||
return | ||
interface_dependent = interface_ipaddr_dependent_on_interface(config_db, interface_name) | ||
for interface_del in interface_dependent: | ||
tylerlinp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
config_db.set_entry(table_name, interface_del, None) | ||
config_db.set_entry(table_name, interface_name, None) | ||
# When config_db del entry and then add entry with same key, the DEL will lost. | ||
state_db = SonicV2Connector(host='127.0.0.1') | ||
state_db.connect(state_db.STATE_DB, False) | ||
_hash = '{}{}'.format('INTERFACE_TABLE|', interface_name) | ||
while state_db.get(state_db.STATE_DB, _hash, "state") == "ok": | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need this handling? dont we handle this in VRF-mgrd? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In intfmgrd. It is a good question. The codes here is to assure DEL op has been handled, or else the new SET op will overwite DEL op and then we have to handle complex intf-move-vrf in intfsorch. |
||
time.sleep(0.01) | ||
state_db.close(state_db.STATE_DB) | ||
config_db.set_entry(table_name, interface_name, {"vrf_name": vrf_name}) | ||
|
||
# | ||
# 'unbind' subcommand | ||
# | ||
|
||
@vrf.command() | ||
@click.argument('interface_name', metavar='<interface_name>', required=True) | ||
@click.pass_context | ||
def unbind(ctx, interface_name): | ||
"""Unbind the interface to VRF""" | ||
config_db = ctx.obj["config_db"] | ||
if get_interface_naming_mode() == "alias": | ||
interface_name = interface_alias_to_name(interface_name) | ||
if interface_name is None: | ||
ctx.fail("interface is None!") | ||
|
||
table_name = get_interface_table_name(interface_name) | ||
if table_name == "": | ||
ctx.fail("'interface_name' is not valid. Valid names [Ethernet/PortChannel/Vlan/Loopback]") | ||
if is_interface_bind_to_vrf(config_db, interface_name) is False: | ||
return | ||
interface_dependent = interface_ipaddr_dependent_on_interface(config_db, interface_name) | ||
for interface_del in interface_dependent: | ||
config_db.set_entry(table_name, interface_del, None) | ||
config_db.set_entry(table_name, interface_name, None) | ||
|
||
|
||
# | ||
# 'vrf' group ('config vrf ...') | ||
# | ||
|
||
@config.group() | ||
@click.pass_context | ||
def vrf(ctx): | ||
"""VRF-related configuration tasks""" | ||
config_db = ConfigDBConnector() | ||
config_db.connect() | ||
ctx.obj = {} | ||
ctx.obj['config_db'] = config_db | ||
pass | ||
|
||
@vrf.command('add') | ||
@click.argument('vrf_name', metavar='<vrf_name>', required=True) | ||
@click.pass_context | ||
def add_vrf(ctx, vrf_name): | ||
"""Add vrf""" | ||
config_db = ctx.obj['config_db'] | ||
if not vrf_name.startswith("Vrf"): | ||
ctx.fail("'vrf_name' is not start with Vrf!") | ||
tylerlinp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
config_db.set_entry('VRF', vrf_name, {"NULL": "NULL"}) | ||
|
||
@vrf.command('del') | ||
@click.argument('vrf_name', metavar='<vrf_name>', required=True) | ||
@click.pass_context | ||
def del_vrf(ctx, vrf_name): | ||
"""Del vrf""" | ||
config_db = ctx.obj['config_db'] | ||
del_interface_bind_to_vrf(config_db, vrf_name) | ||
config_db.set_entry('VRF', vrf_name, None) | ||
|
||
|
||
# | ||
# 'route' group ('config route ...') | ||
# | ||
|
||
@config.group() | ||
@click.pass_context | ||
def route(ctx): | ||
"""route-related configuration tasks""" | ||
pass | ||
|
||
@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) | ||
else: | ||
cmd += ' {} vrf {} nexthop-vrf {}'.format(ip, vrf_name, vrf_name_dst) | ||
else: | ||
ctx.fail("nexthop is not in pattern!") | ||
cmd += '"' | ||
run_command(cmd) | ||
|
||
@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) | ||
else: | ||
cmd += ' {} vrf {} nexthop-vrf {}'.format(ip, vrf_name, vrf_name_dst) | ||
else: | ||
ctx.fail("nexthop is not in pattern!") | ||
cmd += '"' | ||
run_command(cmd) | ||
|
||
# | ||
# 'acl' group ('config acl ...') | ||
# | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hope we delete the dependent IP, VRF configs upon deleting the portchannel and Vlan...etc? or we expect the user the clean-up the dependent configs before deleting the interface?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deleting dependent configs implicitly can help user to simplify operation, actually it makes CLI implementation more complex.