Adding support to create vlan SVI gateways on Cisco Nexus hardware switches.
blueprint cisco-plugin-svi
Change-Id: I88516f3e67d51d213fa60f6ec9aee23f9ca4be97
#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]
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")
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')
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',
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
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
class CiscoNEXUSDriver():
"""Nexus Driver Main Class."""
def __init__(self):
- pass
+ self.connections = {}
def _edit_config(self, mgr, target='running', config='',
allowed_exc_strs=None):
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.
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)
_nexus_username,
_nexus_password)
self._client.enable_vlan_on_trunk_int(man,
+ _nexus_ip,
port_id,
vlan_id)
vlan_enabled = True
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.
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)
</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>
+"""
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
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)