]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Adding SVI support to the Cisco Nexus plugin
authorArvind Somya <asomya@cisco.com>
Wed, 22 May 2013 17:27:48 +0000 (10:27 -0700)
committerArvind Somya <asomya@cisco.com>
Wed, 12 Jun 2013 08:18:29 +0000 (04:18 -0400)
Adding support to create vlan SVI gateways on Cisco Nexus hardware switches.

blueprint cisco-plugin-svi

Change-Id: I88516f3e67d51d213fa60f6ec9aee23f9ca4be97

etc/quantum/plugins/cisco/cisco_plugins.ini
quantum/plugins/cisco/common/cisco_exceptions.py
quantum/plugins/cisco/common/config.py
quantum/plugins/cisco/db/nexus_db_v2.py
quantum/plugins/cisco/models/virt_phy_sw_v2.py
quantum/plugins/cisco/nexus/cisco_nexus_network_driver_v2.py
quantum/plugins/cisco/nexus/cisco_nexus_plugin_v2.py
quantum/plugins/cisco/nexus/cisco_nexus_snippets.py
quantum/tests/unit/cisco/test_nexus_plugin.py

index 2fbf99cd9c79ab32728a57519305dedbfcecba25..f9d8fdb3df4989adc043f5c51f1401fd2b93ce59 100644 (file)
@@ -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]
index f50e6050c59cc2d4baf147fac953c047e9335bac..e33fa65e69f62a31159197d88a3f8aa4ec7e4c0d 100644 (file)
@@ -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')
index af285a5ab727a8b1e0ccd9824b277f064aa119ed..f8c94df8cc7a715521ba5103eed67de470f3a15a 100644 (file)
@@ -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',
index 4fd94499301755417307781dcb04d1d3b554a6e1..a268937daf7d9afe0cade1723eab5f5ed6c54266 100644 (file)
@@ -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
index fe0bd91782aa4439170d2c15b5788a3986067f2f..3642cd7d687c6d8628d2ee5fdfdfddaf95f606dd 100644 (file)
@@ -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
index a620afa975de540a1264e0ed706790059f1962df..b6d9f42d020cb769a0f576b43e3ce038d228a152 100644 (file)
@@ -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)
index b0c17366b9cccf72b9836a5b2a807bced94642a6..2ecf381977e853d475877871370de580afd65d2c 100644 (file)
@@ -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)
index a6a5018538cd71d961a01a3058b186fe94b88791..cf7f6332fdca25fb76d96e79b5147b7fcf37d02f 100644 (file)
@@ -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>
+"""
index 0e56c4fa65cc453e1100bcd4126a58324d57a609..8c1f07d9ecfcb073e5b21c1fcd398123147fb6ec 100644 (file)
@@ -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)