Skip to content
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

mgmt_vrf_namespace: management vrf using namespace solution #422

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
161 changes: 160 additions & 1 deletion config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import json
import subprocess
import netaddr
import syslog
import logging
import logging.handlers
import re
from swsssdk import ConfigDBConnector
from natsort import natsorted
Expand Down Expand Up @@ -662,6 +665,96 @@ def del_vlan_member(ctx, vid, interface_name):
db.set_entry('VLAN_MEMBER', (vlan_name, interface_name), None)


def vrf_add_management_vrf():
"""Enable management vrf"""

cmd = 'sonic-cfggen -d --var-json "MGMT_VRF_CONFIG"'
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to read this address from the config DB?
Why do you read using sonic-cfggen, but write directly to DB?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@pavel-shirshov : Addressed your comment in #431. Instead of using cfggen to read, it now reads from config DB.

p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
res = p.communicate()
if p.returncode == 0 :
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
mvrf_dict = json.loads(p.stdout.read())

# if the mgmtVrfEnabled attribute is configured, check the value
# and modify only if it is changed.
if 'mgmtVrfEnabled' in mvrf_dict['vrf_global']:
if (mvrf_dict['vrf_global']['mgmtVrfEnabled'] == "true"):
click.echo("ManagementVRF is already Enabled.")
return None

config_db = ConfigDBConnector()
config_db.connect()
config_db.mod_entry('MGMT_VRF_CONFIG',"vrf_global",{"mgmtVrfEnabled": "true"})


def vrf_delete_management_vrf():
"""Disable management vrf"""

cmd = 'sonic-cfggen -d --var-json "MGMT_VRF_CONFIG"'
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
res = p.communicate()
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd extract subprocess.Popen chunk as a function, otherwise you have a lot of duplicate code

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@pavel-shirshov : Addressed your comment in #431. Instead of using cfggen to read, it now reads from config DB and hence there is no duplicate usage of Popen chunk.

if p.returncode == 0 :
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
mvrf_dict = json.loads(p.stdout.read())

# if the mgmtVrfEnabled attribute is configured, check the value
# and modify only if it is changed.
if 'mgmtVrfEnabled' in mvrf_dict['vrf_global']:
if (mvrf_dict['vrf_global']['mgmtVrfEnabled'] == "false"):
click.echo("ManagementVRF is already Disabled.")
return None
else:
click.echo("ManagementVRF is already Disabled.")
return None

config_db = ConfigDBConnector()
config_db.connect()
config_db.mod_entry('MGMT_VRF_CONFIG',"vrf_global",{"mgmtVrfEnabled": "false"})


#
# 'vrf' group ('config vrf ...')
#

@config.group('vrf')
def vrf():
"""VRF-related configuration tasks"""
pass


@vrf.command('add')
@click.argument('vrfname', metavar='<vrfname>. Type mgmt for management VRF', required=True)
@click.pass_context
def vrf_add (ctx, vrfname):
"""VRF ADD"""
if vrfname == 'mgmt' or vrfname == 'management':
Copy link
Contributor

@pavel-shirshov pavel-shirshov Dec 31, 2018

Choose a reason for hiding this comment

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

why two VRF names? Do we need to choose one only? Like 'mgmt' you defined in the help?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@pavel-shirshov : Replied to your comment in #431. Please check and followup in that PR.

vrf_add_management_vrf()
else:
click.echo("Creation of data vrf={} is not yet supported".format(vrfname))


@vrf.command('del')
@click.argument('vrfname', metavar='<vrfname>. Type mgmt for management VRF', required=False)
@click.pass_context
def vrf_del (ctx, vrfname):
"""VRF Delete"""
if vrfname == 'mgmt' or vrfname == 'management':
vrf_delete_management_vrf()
else:
click.echo("Deletion of data vrf={} is not yet supported".format(vrfname))


@config.command('clear_mgmt')
@click.pass_context
def clear_mgmt(ctx):
MGMT_TABLE_NAMES = [
'MGMT_INTERFACE',
'MGMT_VRF_CONFIG']
config_db = ConfigDBConnector()
config_db.connect()
for mgmt_table in MGMT_TABLE_NAMES:
config_db.delete_table(mgmt_table)

#
# 'bgp' group
#
Expand Down Expand Up @@ -787,6 +880,42 @@ def speed(ctx, verbose):
command += " -vv"
run_command(command, display_cmd=verbose)

def _get_all_mgmtinterface_keys():
"""Returns list of strings containing mgmt interface keys
"""
config_db = ConfigDBConnector()
config_db.connect()
return config_db.get_table('MGMT_INTERFACE').keys()

def is_address_in_network(network, address):
"""
Determine whether the provided address is within a network range.

:param network (str): CIDR presentation format. For example,
'192.168.1.0/24'.
:param address: An individual IPv4 or IPv6 address without a net
mask or subnet prefix. For example, '192.168.1.1'.
:returns boolean: Flag indicating whether address is in network.
"""
try:
network = netaddr.IPNetwork(network)
# There wont be any exception if the IPNetwork is valid
except (netaddr.core.AddrFormatError, ValueError):
raise ValueError("Network (%s) is not in CIDR presentation format" %
network)

try:
address = netaddr.IPAddress(address)
# There wont be any exception if the IPAddress is valid
except (netaddr.core.AddrFormatError, ValueError):
raise ValueError("Address (%s) is not in correct presentation format" %
address)

if address in network:
return True
else:
return False
Copy link
Contributor

Choose a reason for hiding this comment

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

return address in network ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@pavel-shirshov : Replied to your comment in #431. Please check and followup in that PR.


#
# 'ip' subgroup
#
Expand All @@ -803,14 +932,42 @@ def ip(ctx):

@ip.command()
@click.argument("ip_addr", metavar="<ip_addr>", required=True)
@click.argument('gw1', metavar='<default gateway IP address>', required=False)
Copy link
Contributor

Choose a reason for hiding this comment

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

should we use gw here? Why gw1?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@pavel-shirshov : Addressed your comment in #431. Changed gw1 to gw.

@click.pass_context
def add(ctx, ip_addr):
def add(ctx, ip_addr, gw1):
"""Add an IP address towards the interface"""
config_db = ctx.obj["config_db"]
interface_name = ctx.obj["interface_name"]

if interface_name.startswith("Ethernet"):
config_db.set_entry("INTERFACE", (interface_name, ip_addr), {"NULL": "NULL"})
elif interface_name.startswith("eth0"):
Copy link
Contributor

Choose a reason for hiding this comment

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

It could match interface 'eth01'. I think it's better to compare to 'eth' here

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@pavel-shirshov : Addressed your comment in #431. Changed "startswith eth0" and checked for exact match with "eth0".

# Validate the ip/mask
ipaddress= ip_addr.split("/")
if is_address_in_network(ip_addr, ipaddress[0]) == False:
click.echo ("Its an invalid ip/mask value")
return

# Configuring more than 1 IPv4 or more than 1 IPv6 address fails.
# Allow only one IPv4 and only one IPv6 address to be configured for IPv6.
# If a row already exist, overwrite it (by doing delete and add).
mgmtintf_key_list = _get_all_mgmtinterface_keys()

for key in mgmtintf_key_list:
# For loop runs for max 2 rows, once for IPv4 and once for IPv6.
if ':' in ip_addr and ':' in key[1]:
# If user has configured IPv6 address and the already available row is also IPv6, delete it here.
config_db.set_entry("MGMT_INTERFACE", ("eth0", key[1]), None)
elif ':' not in ip_addr and ':' not in key[1]:
# If user has configured IPv4 address and the already available row is also IPv6, delete it here.
config_db.set_entry("MGMT_INTERFACE", ("eth0", key[1]), None)

# Set the new row with new value
if not gw1:
config_db.set_entry("MGMT_INTERFACE", (interface_name, ip_addr), {"NULL": "NULL"})
else:
config_db.set_entry("MGMT_INTERFACE", (interface_name, ip_addr), {"gwaddr": gw1})

elif interface_name.startswith("PortChannel"):
config_db.set_entry("PORTCHANNEL_INTERFACE", (interface_name, ip_addr), {"NULL": "NULL"})

Expand All @@ -828,6 +985,8 @@ def remove(ctx, ip_addr):

if interface_name.startswith("Ethernet"):
config_db.set_entry("INTERFACE", (interface_name, ip_addr), None)
elif interface_name.startswith("eth0"):
Copy link
Contributor

Choose a reason for hiding this comment

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

interface_name ==

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@pavel-shirshov : Addressed your comment in #431. Changed "startswith eth0" and checked for exact match with "eth0".

config_db.set_entry("MGMT_INTERFACE", (interface_name, ip_addr), None)
elif interface_name.startswith("PortChannel"):
config_db.set_entry("PORTCHANNEL_INTERFACE", (interface_name, ip_addr), None)
#
Expand Down
86 changes: 86 additions & 0 deletions show/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,92 @@ def ndp(ip6address, iface, verbose):

run_command(cmd, display_cmd=verbose)

#
# 'mgmt-vrf' group ("show mgmt-vrf ...")
#

@cli.group('mgmt-vrf', invoke_without_command=True)
@click.pass_context
def mgmt_vrf(ctx):

"""Show management VRF attributes"""

if ctx.invoked_subcommand is None:
cmd = 'sonic-cfggen -d --var-json "MGMT_VRF_CONFIG"'

p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
res = p.communicate()
if p.returncode == 0 :
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
mvrf_dict = json.loads(p.stdout.read())

# if the mgmtVrfEnabled attribute is configured, check the value
# and print Enabled or Disabled accordingly.
if 'mgmtVrfEnabled' in mvrf_dict['vrf_global']:
if (mvrf_dict['vrf_global']['mgmtVrfEnabled'] == "true"):
click.echo("\nManagementVRF : Enabled")
else:
click.echo("\nManagementVRF : Disabled")

click.echo("\nNameSpaces in Linux:")
cmd = "sudo ip netns list"
run_command(cmd)

@mgmt_vrf.command('interfaces')
def mgmt_vrf_interfaces ():
"""Show management VRF attributes"""

click.echo("\nInterfaces in Management VRF:")
cmd = "sudo ip netns exec mgmt ifconfig"
run_command(cmd)
return None

@mgmt_vrf.command('route')
def mgmt_vrf_route ():
"""Show management VRF routes"""

click.echo("\nRoutes in Management VRF Routing Table:")
cmd = "sudo ip netns exec mgmt ip route show"
run_command(cmd)
return None


@mgmt_vrf.command('addresses')
def mgmt_vrf_addresses ():
"""Show management VRF addresses"""

click.echo("\nIP Addresses for interfaces in Management VRF:")
cmd = "sudo ip netns exec mgmt ip address show"
run_command(cmd)
return None



#
# 'management_interface' group ("show management_interface ...")
#

@cli.group(cls=AliasedGroup, default_if_no_args=False)
def management_interface():
"""Show management interface parameters"""
pass

# 'address' subcommand ("show management_interface address")
@management_interface.command()
def address ():
"""Show IP address configured for management interface"""

config_db = ConfigDBConnector()
config_db.connect()
header = ['IFNAME', 'IP Address', 'PrefixLen',]
body = []

# Fetching data from config_db for MGMT_INTERFACE
mgmt_ip_data = config_db.get_table('MGMT_INTERFACE')
for key in natsorted(mgmt_ip_data.keys()):
click.echo("Management IP address = {0}".format(key[1]))
click.echo("Management NetWork Default Gateway = {0}".format(mgmt_ip_data[key]['gwaddr']))

#
# 'interfaces' group ("show interfaces ...")
#
Expand Down