Adding SVI support to the Cisco Nexus plugin
Adding support to create vlan SVI gateways on Cisco Nexus hardware switches. blueprint cisco-plugin-svi Change-Id: I88516f3e67d51d213fa60f6ec9aee23f9ca4be97
This commit is contained in:
parent
08561f35ac
commit
bcb9ea7ba4
@ -12,6 +12,7 @@
|
||||
#model_class=quantum.plugins.cisco.models.virt_phy_sw_v2.VirtualPhysicalSwitchModelV2
|
||||
#manager_class=quantum.plugins.cisco.segmentation.l2network_vlan_mgr_v2.L2NetworkVLANMgr
|
||||
#nexus_driver=quantum.plugins.cisco.tests.unit.v2.nexus.fake_nexus_driver.CiscoNEXUSFakeDriver
|
||||
#svi_round_robin=False
|
||||
|
||||
# IMPORTANT: Comment out the following two lines for production deployments
|
||||
[CISCO_TEST]
|
||||
|
@ -103,6 +103,11 @@ class NexusPortBindingNotFound(exceptions.QuantumException):
|
||||
super(NexusPortBindingNotFound, self).__init__(filters=filters)
|
||||
|
||||
|
||||
class NoNexusSviSwitch(exceptions.QuantumException):
|
||||
"""No usable nexus switch found."""
|
||||
message = _("No usable Nexus switch found to create SVI interface")
|
||||
|
||||
|
||||
class PortVnicBindingAlreadyExists(exceptions.QuantumException):
|
||||
"""PortVnic Binding already exists."""
|
||||
message = _("PortVnic Binding %(port_id)s already exists")
|
||||
@ -111,3 +116,18 @@ class PortVnicBindingAlreadyExists(exceptions.QuantumException):
|
||||
class PortVnicNotFound(exceptions.QuantumException):
|
||||
"""PortVnic Binding is not present."""
|
||||
message = _("PortVnic Binding %(port_id)s is not present")
|
||||
|
||||
|
||||
class SubnetNotSpecified(exceptions.QuantumException):
|
||||
"""Subnet id not specified."""
|
||||
message = _("No subnet_id specified for router gateway")
|
||||
|
||||
|
||||
class SubnetInterfacePresent(exceptions.QuantumException):
|
||||
"""Subnet SVI interface already exists."""
|
||||
message = _("Subnet %(subnet_id)s has an interface on %(router_id)s")
|
||||
|
||||
|
||||
class PortIdForNexusSvi(exceptions.QuantumException):
|
||||
"""Port Id specified for Nexus SVI."""
|
||||
message = _('Nexus hardware router gateway only uses Subnet Ids')
|
||||
|
@ -44,6 +44,8 @@ cisco_opts = [
|
||||
help=_("Maximum Port Profile value")),
|
||||
cfg.StrOpt('max_networks', default='65568',
|
||||
help=_("Maximum Network value")),
|
||||
cfg.BoolOpt('svi_round_robin', default=False,
|
||||
help=_("Distribute SVI interfaces over all switches")),
|
||||
cfg.StrOpt('model_class',
|
||||
default='quantum.plugins.cisco.models.virt_phy_sw_v2.'
|
||||
'VirtualPhysicalSwitchModelV2',
|
||||
|
@ -148,3 +148,17 @@ def get_port_switch_bindings(port_id, switch_ip):
|
||||
return binding
|
||||
except exc.NoResultFound:
|
||||
return
|
||||
|
||||
|
||||
def get_nexussvi_bindings():
|
||||
"""Lists nexus svi bindings."""
|
||||
LOG.debug(_("get_nexussvi_bindings() called"))
|
||||
session = db.get_session()
|
||||
|
||||
filters = {'port_id': 'router'}
|
||||
bindings = (session.query(nexus_models_v2.NexusPortBinding).
|
||||
filter_by(**filters).all())
|
||||
if not bindings:
|
||||
raise c_exc.NexusPortBindingNotFound(**filters)
|
||||
|
||||
return bindings
|
||||
|
@ -392,6 +392,69 @@ class VirtualPhysicalSwitchModelV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
||||
|
||||
return ovs_output[0]
|
||||
|
||||
def add_router_interface(self, context, router_id, interface_info):
|
||||
"""Add a router interface on a subnet.
|
||||
|
||||
Only invoke the Nexus plugin to create SVI if a Nexus
|
||||
plugin is loaded, otherwise send it to the vswitch plugin
|
||||
"""
|
||||
nexus_driver = cfg.CONF.CISCO.nexus_driver
|
||||
if nexus_driver.endswith('CiscoNEXUSDriver'):
|
||||
LOG.debug(_("Nexus plugin loaded, creating SVI on switch"))
|
||||
if 'subnet_id' not in interface_info:
|
||||
raise cexc.SubnetNotSpecified()
|
||||
if 'port_id' in interface_info:
|
||||
raise cexc.PortIdForNexusSvi()
|
||||
subnet = self.get_subnet(context, interface_info['subnet_id'])
|
||||
gateway_ip = subnet['gateway_ip']
|
||||
# Get gateway IP address and netmask
|
||||
cidr = subnet['cidr']
|
||||
netmask = cidr.split('/', 1)[1]
|
||||
gateway_ip = gateway_ip + '/' + netmask
|
||||
network_id = subnet['network_id']
|
||||
vlan_id = self._get_segmentation_id(network_id)
|
||||
vlan_name = conf.CISCO.vlan_name_prefix + str(vlan_id)
|
||||
|
||||
n_args = [vlan_name, vlan_id, subnet['id'], gateway_ip, router_id]
|
||||
nexus_output = self._invoke_plugin_per_device(const.NEXUS_PLUGIN,
|
||||
self._func_name(),
|
||||
n_args)
|
||||
return nexus_output
|
||||
else:
|
||||
LOG.debug(_("No Nexus plugin, sending to vswitch"))
|
||||
n_args = [context, router_id, interface_info]
|
||||
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
|
||||
self._func_name(),
|
||||
n_args)
|
||||
return ovs_output
|
||||
|
||||
def remove_router_interface(self, context, router_id, interface_info):
|
||||
"""Remove a router interface.
|
||||
|
||||
Only invoke the Nexus plugin to delete SVI if a Nexus
|
||||
plugin is loaded, otherwise send it to the vswitch plugin
|
||||
"""
|
||||
nexus_driver = cfg.CONF.CISCO.nexus_driver
|
||||
if nexus_driver.endswith('CiscoNEXUSDriver'):
|
||||
LOG.debug(_("Nexus plugin loaded, deleting SVI from switch"))
|
||||
|
||||
subnet = self.get_subnet(context, interface_info['subnet_id'])
|
||||
network_id = subnet['network_id']
|
||||
vlan_id = self._get_segmentation_id(network_id)
|
||||
n_args = [vlan_id, router_id]
|
||||
|
||||
nexus_output = self._invoke_plugin_per_device(const.NEXUS_PLUGIN,
|
||||
self._func_name(),
|
||||
n_args)
|
||||
return nexus_output
|
||||
else:
|
||||
LOG.debug(_("No Nexus plugin, sending to vswitch"))
|
||||
n_args = [context, router_id, interface_info]
|
||||
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
|
||||
self._func_name(),
|
||||
n_args)
|
||||
return ovs_output
|
||||
|
||||
def create_subnet(self, context, subnet):
|
||||
"""For this model this method will be delegated to vswitch plugin."""
|
||||
pass
|
||||
|
@ -36,7 +36,7 @@ LOG = logging.getLogger(__name__)
|
||||
class CiscoNEXUSDriver():
|
||||
"""Nexus Driver Main Class."""
|
||||
def __init__(self):
|
||||
pass
|
||||
self.connections = {}
|
||||
|
||||
def _edit_config(self, mgr, target='running', config='',
|
||||
allowed_exc_strs=None):
|
||||
@ -68,16 +68,21 @@ class CiscoNEXUSDriver():
|
||||
def nxos_connect(self, nexus_host, nexus_ssh_port, nexus_user,
|
||||
nexus_password):
|
||||
"""Make SSH connection to the Nexus Switch."""
|
||||
if getattr(self.connections.get(nexus_host), 'connected', None):
|
||||
return self.connections[nexus_host]
|
||||
|
||||
try:
|
||||
man = manager.connect(host=nexus_host, port=nexus_ssh_port,
|
||||
man = manager.connect(host=nexus_host,
|
||||
port=nexus_ssh_port,
|
||||
username=nexus_user,
|
||||
password=nexus_password)
|
||||
self.connections[nexus_host] = man
|
||||
except Exception as e:
|
||||
# Raise a Quantum exception. Include a description of
|
||||
# the original ncclient exception.
|
||||
raise cexc.NexusConnectFailed(nexus_host=nexus_host, exc=e)
|
||||
|
||||
return man
|
||||
return self.connections[nexus_host]
|
||||
|
||||
def create_xml_snippet(self, cutomized_config):
|
||||
"""Create XML snippet.
|
||||
@ -227,3 +232,23 @@ class CiscoNEXUSDriver():
|
||||
nexus_user, nexus_password)
|
||||
for ports in nexus_ports:
|
||||
self.disable_vlan_on_trunk_int(man, ports, vlan_id)
|
||||
|
||||
def create_vlan_svi(self, vlan_id, nexus_host, nexus_user, nexus_password,
|
||||
nexus_ssh_port, gateway_ip):
|
||||
man = self.nxos_connect(nexus_host, int(nexus_ssh_port),
|
||||
nexus_user, nexus_password)
|
||||
|
||||
confstr = snipp.CMD_VLAN_SVI_SNIPPET % (vlan_id, gateway_ip)
|
||||
confstr = self.create_xml_snippet(confstr)
|
||||
LOG.debug(_("NexusDriver: %s"), confstr)
|
||||
man.edit_config(target='running', config=confstr)
|
||||
|
||||
def delete_vlan_svi(self, vlan_id, nexus_host, nexus_user, nexus_password,
|
||||
nexus_ssh_port):
|
||||
man = self.nxos_connect(nexus_host, int(nexus_ssh_port),
|
||||
nexus_user, nexus_password)
|
||||
|
||||
confstr = snipp.CMD_NO_VLAN_SVI_SNIPPET % vlan_id
|
||||
confstr = self.create_xml_snippet(confstr)
|
||||
LOG.debug(_("NexusDriver: %s"), confstr)
|
||||
man.edit_config(target='running', config=confstr)
|
||||
|
@ -117,6 +117,7 @@ class NexusPlugin(L2DevicePluginBase):
|
||||
_nexus_username,
|
||||
_nexus_password)
|
||||
self._client.enable_vlan_on_trunk_int(man,
|
||||
_nexus_ip,
|
||||
port_id,
|
||||
vlan_id)
|
||||
vlan_enabled = True
|
||||
@ -148,6 +149,91 @@ class NexusPlugin(L2DevicePluginBase):
|
||||
self._networks[net_id] = new_net_dict
|
||||
return new_net_dict
|
||||
|
||||
def add_router_interface(self, vlan_name, vlan_id, subnet_id,
|
||||
gateway_ip, router_id):
|
||||
"""Create VLAN SVI on the Nexus switch."""
|
||||
# Find a switch to create the SVI on
|
||||
switch_ip = self._find_switch_for_svi()
|
||||
if not switch_ip:
|
||||
raise cisco_exc.NoNexusSwitch()
|
||||
|
||||
_nexus_ip = switch_ip
|
||||
_nexus_ssh_port = self._nexus_switches[switch_ip, 'ssh_port']
|
||||
_nexus_creds = self.get_credential(_nexus_ip)
|
||||
_nexus_username = _nexus_creds['username']
|
||||
_nexus_password = _nexus_creds['password']
|
||||
|
||||
# Check if this vlan exists on the switch already
|
||||
try:
|
||||
nxos_db.get_nexusvlan_binding(vlan_id, switch_ip)
|
||||
except cisco_exc.NexusPortBindingNotFound:
|
||||
# Create vlan and trunk vlan on the port
|
||||
self._client.create_vlan(
|
||||
vlan_name, str(vlan_id), _nexus_ip,
|
||||
_nexus_username, _nexus_password,
|
||||
[], _nexus_ssh_port, vlan_id)
|
||||
|
||||
# Check if a router interface has already been created
|
||||
try:
|
||||
nxos_db.get_nexusvm_binding(vlan_id, router_id)
|
||||
raise cisco_exc.SubnetInterfacePresent(subnet_id=subnet_id,
|
||||
router_id=router_id)
|
||||
except cisco_exc.NexusPortBindingNotFound:
|
||||
self._client.create_vlan_svi(vlan_id, _nexus_ip, _nexus_username,
|
||||
_nexus_password, _nexus_ssh_port,
|
||||
gateway_ip)
|
||||
nxos_db.add_nexusport_binding('router', str(vlan_id),
|
||||
switch_ip, router_id)
|
||||
|
||||
return True
|
||||
|
||||
def remove_router_interface(self, vlan_id, router_id):
|
||||
"""Remove VLAN SVI from the Nexus Switch."""
|
||||
# Grab switch_ip from database
|
||||
row = nxos_db.get_nexusvm_binding(vlan_id, router_id)
|
||||
|
||||
# Delete the SVI interface from the switch
|
||||
_nexus_ip = row['switch_ip']
|
||||
_nexus_ssh_port = self._nexus_switches[_nexus_ip, 'ssh_port']
|
||||
_nexus_creds = self.get_credential(_nexus_ip)
|
||||
_nexus_username = _nexus_creds['username']
|
||||
_nexus_password = _nexus_creds['password']
|
||||
|
||||
self._client.delete_vlan_svi(vlan_id, _nexus_ip, _nexus_username,
|
||||
_nexus_password, _nexus_ssh_port)
|
||||
|
||||
# Invoke delete_port to delete this row
|
||||
# And delete vlan if required
|
||||
return self.delete_port(router_id, vlan_id)
|
||||
|
||||
def _find_switch_for_svi(self):
|
||||
"""Get a switch to create the SVI on."""
|
||||
LOG.debug(_("Grabbing a switch to create SVI"))
|
||||
if conf.CISCO.svi_round_robin:
|
||||
LOG.debug(_("Using round robin to create SVI"))
|
||||
switch_dict = dict(
|
||||
(switch_ip, 0) for switch_ip, _ in self._nexus_switches)
|
||||
try:
|
||||
bindings = nxos_db.get_nexussvi_bindings()
|
||||
# Build a switch dictionary with weights
|
||||
for binding in bindings:
|
||||
switch_ip = binding.switch_ip
|
||||
if switch_ip not in switch_dict:
|
||||
switch_dict[switch_ip] = 1
|
||||
else:
|
||||
switch_dict[switch_ip] += 1
|
||||
# Search for the lowest value in the dict
|
||||
if switch_dict:
|
||||
switch_ip = min(switch_dict.items(), key=switch_dict.get)
|
||||
return switch_ip[0]
|
||||
except cisco_exc.NexusPortBindingNotFound:
|
||||
pass
|
||||
|
||||
LOG.debug(_("No round robin or zero weights, using first switch"))
|
||||
# Return the first switch in the config
|
||||
for switch_ip, attr in self._nexus_switches:
|
||||
return switch_ip
|
||||
|
||||
def delete_network(self, tenant_id, net_id, **kwargs):
|
||||
"""Delete network.
|
||||
|
||||
@ -205,7 +291,9 @@ class NexusPlugin(L2DevicePluginBase):
|
||||
try:
|
||||
# Delete this vlan from this switch
|
||||
_nexus_ip = row['switch_ip']
|
||||
_nexus_ports = (row['port_id'],)
|
||||
_nexus_ports = ()
|
||||
if row['port_id'] != 'router':
|
||||
_nexus_ports = (row['port_id'],)
|
||||
_nexus_ssh_port = (self._nexus_switches[_nexus_ip,
|
||||
'ssh_port'])
|
||||
_nexus_creds = self.get_credential(_nexus_ip)
|
||||
|
@ -183,3 +183,32 @@ FILTER_SHOW_VLAN_BRIEF_SNIPPET = """
|
||||
</vlan>
|
||||
</show>
|
||||
"""
|
||||
|
||||
|
||||
CMD_VLAN_SVI_SNIPPET = """
|
||||
<interface>
|
||||
<vlan>
|
||||
<vlan>%s</vlan>
|
||||
<__XML__MODE_vlan>
|
||||
<no>
|
||||
<shutdown/>
|
||||
</no>
|
||||
<ip>
|
||||
<address>
|
||||
<address>%s</address>
|
||||
</address>
|
||||
</ip>
|
||||
</__XML__MODE_vlan>
|
||||
</vlan>
|
||||
</interface>
|
||||
"""
|
||||
|
||||
CMD_NO_VLAN_SVI_SNIPPET = """
|
||||
<no>
|
||||
<interface>
|
||||
<vlan>
|
||||
<vlan>%s</vlan>
|
||||
</vlan>
|
||||
</interface>
|
||||
</no>
|
||||
"""
|
||||
|
@ -18,6 +18,7 @@ import mock
|
||||
from quantum.db import api as db
|
||||
from quantum.openstack.common import importutils
|
||||
from quantum.plugins.cisco.common import cisco_constants as const
|
||||
from quantum.plugins.cisco.common import cisco_exceptions as cisco_exc
|
||||
from quantum.plugins.cisco.nexus import cisco_nexus_plugin_v2
|
||||
from quantum.tests import base
|
||||
|
||||
@ -122,3 +123,40 @@ class TestCiscoNexusPlugin(base.BaseTestCase):
|
||||
INSTANCE, self.vlan_id)
|
||||
|
||||
self.assertEqual(expected_instance_id, INSTANCE)
|
||||
|
||||
def test_nexus_add_remove_router_interface(self):
|
||||
"""Tests addition of a router interface."""
|
||||
vlan_name = self.vlan_name
|
||||
vlan_id = self.vlan_id
|
||||
gateway_ip = '10.0.0.1/24'
|
||||
router_id = '00000R1'
|
||||
subnet_id = '00001'
|
||||
|
||||
result = self._cisco_nexus_plugin.add_router_interface(vlan_name,
|
||||
vlan_id,
|
||||
subnet_id,
|
||||
gateway_ip,
|
||||
router_id)
|
||||
self.assertTrue(result)
|
||||
result = self._cisco_nexus_plugin.remove_router_interface(vlan_id,
|
||||
router_id)
|
||||
self.assertEqual(result, router_id)
|
||||
|
||||
def test_nexus_add_router_interface_fail(self):
|
||||
"""Tests deletion of a router interface."""
|
||||
vlan_name = self.vlan_name
|
||||
vlan_id = self.vlan_id
|
||||
gateway_ip = '10.0.0.1/24'
|
||||
router_id = '00000R1'
|
||||
subnet_id = '00001'
|
||||
|
||||
self._cisco_nexus_plugin.add_router_interface(vlan_name,
|
||||
vlan_id,
|
||||
subnet_id,
|
||||
gateway_ip,
|
||||
router_id)
|
||||
|
||||
self.assertRaises(
|
||||
cisco_exc.SubnetInterfacePresent,
|
||||
self._cisco_nexus_plugin.add_router_interface,
|
||||
vlan_name, vlan_id, subnet_id, gateway_ip, router_id)
|
||||
|
Loading…
Reference in New Issue
Block a user