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
|
#model_class=quantum.plugins.cisco.models.virt_phy_sw_v2.VirtualPhysicalSwitchModelV2
|
||||||
#manager_class=quantum.plugins.cisco.segmentation.l2network_vlan_mgr_v2.L2NetworkVLANMgr
|
#manager_class=quantum.plugins.cisco.segmentation.l2network_vlan_mgr_v2.L2NetworkVLANMgr
|
||||||
#nexus_driver=quantum.plugins.cisco.tests.unit.v2.nexus.fake_nexus_driver.CiscoNEXUSFakeDriver
|
#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
|
# IMPORTANT: Comment out the following two lines for production deployments
|
||||||
[CISCO_TEST]
|
[CISCO_TEST]
|
||||||
|
@ -103,6 +103,11 @@ class NexusPortBindingNotFound(exceptions.QuantumException):
|
|||||||
super(NexusPortBindingNotFound, self).__init__(filters=filters)
|
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):
|
class PortVnicBindingAlreadyExists(exceptions.QuantumException):
|
||||||
"""PortVnic Binding already exists."""
|
"""PortVnic Binding already exists."""
|
||||||
message = _("PortVnic Binding %(port_id)s already exists")
|
message = _("PortVnic Binding %(port_id)s already exists")
|
||||||
@ -111,3 +116,18 @@ class PortVnicBindingAlreadyExists(exceptions.QuantumException):
|
|||||||
class PortVnicNotFound(exceptions.QuantumException):
|
class PortVnicNotFound(exceptions.QuantumException):
|
||||||
"""PortVnic Binding is not present."""
|
"""PortVnic Binding is not present."""
|
||||||
message = _("PortVnic Binding %(port_id)s 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")),
|
help=_("Maximum Port Profile value")),
|
||||||
cfg.StrOpt('max_networks', default='65568',
|
cfg.StrOpt('max_networks', default='65568',
|
||||||
help=_("Maximum Network value")),
|
help=_("Maximum Network value")),
|
||||||
|
cfg.BoolOpt('svi_round_robin', default=False,
|
||||||
|
help=_("Distribute SVI interfaces over all switches")),
|
||||||
cfg.StrOpt('model_class',
|
cfg.StrOpt('model_class',
|
||||||
default='quantum.plugins.cisco.models.virt_phy_sw_v2.'
|
default='quantum.plugins.cisco.models.virt_phy_sw_v2.'
|
||||||
'VirtualPhysicalSwitchModelV2',
|
'VirtualPhysicalSwitchModelV2',
|
||||||
|
@ -148,3 +148,17 @@ def get_port_switch_bindings(port_id, switch_ip):
|
|||||||
return binding
|
return binding
|
||||||
except exc.NoResultFound:
|
except exc.NoResultFound:
|
||||||
return
|
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]
|
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):
|
def create_subnet(self, context, subnet):
|
||||||
"""For this model this method will be delegated to vswitch plugin."""
|
"""For this model this method will be delegated to vswitch plugin."""
|
||||||
pass
|
pass
|
||||||
|
@ -36,7 +36,7 @@ LOG = logging.getLogger(__name__)
|
|||||||
class CiscoNEXUSDriver():
|
class CiscoNEXUSDriver():
|
||||||
"""Nexus Driver Main Class."""
|
"""Nexus Driver Main Class."""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
self.connections = {}
|
||||||
|
|
||||||
def _edit_config(self, mgr, target='running', config='',
|
def _edit_config(self, mgr, target='running', config='',
|
||||||
allowed_exc_strs=None):
|
allowed_exc_strs=None):
|
||||||
@ -68,16 +68,21 @@ class CiscoNEXUSDriver():
|
|||||||
def nxos_connect(self, nexus_host, nexus_ssh_port, nexus_user,
|
def nxos_connect(self, nexus_host, nexus_ssh_port, nexus_user,
|
||||||
nexus_password):
|
nexus_password):
|
||||||
"""Make SSH connection to the Nexus Switch."""
|
"""Make SSH connection to the Nexus Switch."""
|
||||||
|
if getattr(self.connections.get(nexus_host), 'connected', None):
|
||||||
|
return self.connections[nexus_host]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
man = manager.connect(host=nexus_host, port=nexus_ssh_port,
|
man = manager.connect(host=nexus_host,
|
||||||
|
port=nexus_ssh_port,
|
||||||
username=nexus_user,
|
username=nexus_user,
|
||||||
password=nexus_password)
|
password=nexus_password)
|
||||||
|
self.connections[nexus_host] = man
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Raise a Quantum exception. Include a description of
|
# Raise a Quantum exception. Include a description of
|
||||||
# the original ncclient exception.
|
# the original ncclient exception.
|
||||||
raise cexc.NexusConnectFailed(nexus_host=nexus_host, exc=e)
|
raise cexc.NexusConnectFailed(nexus_host=nexus_host, exc=e)
|
||||||
|
|
||||||
return man
|
return self.connections[nexus_host]
|
||||||
|
|
||||||
def create_xml_snippet(self, cutomized_config):
|
def create_xml_snippet(self, cutomized_config):
|
||||||
"""Create XML snippet.
|
"""Create XML snippet.
|
||||||
@ -227,3 +232,23 @@ class CiscoNEXUSDriver():
|
|||||||
nexus_user, nexus_password)
|
nexus_user, nexus_password)
|
||||||
for ports in nexus_ports:
|
for ports in nexus_ports:
|
||||||
self.disable_vlan_on_trunk_int(man, ports, vlan_id)
|
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_username,
|
||||||
_nexus_password)
|
_nexus_password)
|
||||||
self._client.enable_vlan_on_trunk_int(man,
|
self._client.enable_vlan_on_trunk_int(man,
|
||||||
|
_nexus_ip,
|
||||||
port_id,
|
port_id,
|
||||||
vlan_id)
|
vlan_id)
|
||||||
vlan_enabled = True
|
vlan_enabled = True
|
||||||
@ -148,6 +149,91 @@ class NexusPlugin(L2DevicePluginBase):
|
|||||||
self._networks[net_id] = new_net_dict
|
self._networks[net_id] = new_net_dict
|
||||||
return 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):
|
def delete_network(self, tenant_id, net_id, **kwargs):
|
||||||
"""Delete network.
|
"""Delete network.
|
||||||
|
|
||||||
@ -205,6 +291,8 @@ class NexusPlugin(L2DevicePluginBase):
|
|||||||
try:
|
try:
|
||||||
# Delete this vlan from this switch
|
# Delete this vlan from this switch
|
||||||
_nexus_ip = row['switch_ip']
|
_nexus_ip = row['switch_ip']
|
||||||
|
_nexus_ports = ()
|
||||||
|
if row['port_id'] != 'router':
|
||||||
_nexus_ports = (row['port_id'],)
|
_nexus_ports = (row['port_id'],)
|
||||||
_nexus_ssh_port = (self._nexus_switches[_nexus_ip,
|
_nexus_ssh_port = (self._nexus_switches[_nexus_ip,
|
||||||
'ssh_port'])
|
'ssh_port'])
|
||||||
|
@ -183,3 +183,32 @@ FILTER_SHOW_VLAN_BRIEF_SNIPPET = """
|
|||||||
</vlan>
|
</vlan>
|
||||||
</show>
|
</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.db import api as db
|
||||||
from quantum.openstack.common import importutils
|
from quantum.openstack.common import importutils
|
||||||
from quantum.plugins.cisco.common import cisco_constants as const
|
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.plugins.cisco.nexus import cisco_nexus_plugin_v2
|
||||||
from quantum.tests import base
|
from quantum.tests import base
|
||||||
|
|
||||||
@ -122,3 +123,40 @@ class TestCiscoNexusPlugin(base.BaseTestCase):
|
|||||||
INSTANCE, self.vlan_id)
|
INSTANCE, self.vlan_id)
|
||||||
|
|
||||||
self.assertEqual(expected_instance_id, INSTANCE)
|
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