]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Fix Cisco nexus plugin failure for first VLAN on phy interface
authorDane LeBlanc <leblancd@cisco.com>
Fri, 10 May 2013 21:54:04 +0000 (17:54 -0400)
committerDane LeBlanc <leblancd@cisco.com>
Thu, 30 May 2013 17:08:07 +0000 (13:08 -0400)
Fixes bug 1174852

This fix changes the command being sent to the Nexus switch for
enabling a VLAN on an interface such that the 'add' keyword is
included only when there are prior VLANs already enabled on that
interface.

Change-Id: I466bcb839912b164b84be0e6721d87206c0e53eb

quantum/plugins/cisco/db/nexus_db_v2.py
quantum/plugins/cisco/nexus/cisco_nexus_network_driver_v2.py
quantum/plugins/cisco/nexus/cisco_nexus_snippets.py
quantum/tests/unit/cisco/test_network_plugin.py

index a48a82795d62f6b04b737c36b5472544e50f81d4..4fd94499301755417307781dcb04d1d3b554a6e1 100644 (file)
@@ -133,3 +133,18 @@ def get_port_vlan_switch_binding(port_id, vlan_id, switch_ip):
         raise c_exc.NexusPortBindingNotFound(**filters)
 
     return bindings
+
+
+def get_port_switch_bindings(port_id, switch_ip):
+    """List all vm/vlan bindings on a Nexus switch port."""
+    LOG.debug(_("get_port_switch_bindings() called, "
+                "port:'%(port_id)s', switch:'%(switch_ip)s'"),
+              {'port_id': port_id, 'switch_ip': switch_ip})
+    session = db.get_session()
+    try:
+        binding = (session.query(nexus_models_v2.NexusPortBinding).
+                   filter_by(port_id=port_id).
+                   filter_by(switch_ip=switch_ip).all())
+        return binding
+    except exc.NoResultFound:
+        return
index f7a162e38afc03b45c43ec8ba75b88aa5ecdd147..a620afa975de540a1264e0ed706790059f1962df 100644 (file)
@@ -27,6 +27,7 @@ from ncclient import manager
 
 from quantum.plugins.cisco.common import cisco_exceptions as cexc
 from quantum.plugins.cisco.db import network_db_v2 as cdb
+from quantum.plugins.cisco.db import nexus_db_v2
 from quantum.plugins.cisco.nexus import cisco_nexus_snippets as snipp
 
 LOG = logging.getLogger(__name__)
@@ -135,13 +136,19 @@ class CiscoNEXUSDriver():
         LOG.debug(_("NexusDriver: %s"), confstr)
         self._edit_config(mgr, target='running', config=confstr)
 
-    def enable_vlan_on_trunk_int(self, mgr, interface, vlanid):
+    def enable_vlan_on_trunk_int(self, mgr, nexus_switch, interface, vlanid):
         """Enable vlan in trunk interface.
 
         Enables trunk mode vlan access an interface on Nexus Switch given
         VLANID.
         """
-        confstr = snipp.CMD_VLAN_INT_SNIPPET % (interface, vlanid)
+        # If one or more VLANs are already configured on this interface,
+        # include the 'add' keyword.
+        if nexus_db_v2.get_port_switch_bindings(interface, nexus_switch):
+            snippet = snipp.CMD_INT_VLAN_ADD_SNIPPET
+        else:
+            snippet = snipp.CMD_INT_VLAN_SNIPPET
+        confstr = snippet % (interface, vlanid)
         confstr = self.create_xml_snippet(confstr)
         LOG.debug(_("NexusDriver: %s"), confstr)
         self._edit_config(mgr, target='running', config=confstr)
@@ -172,7 +179,7 @@ class CiscoNEXUSDriver():
             vlan_ids = self.build_vlans_cmd()
         LOG.debug(_("NexusDriver VLAN IDs: %s"), vlan_ids)
         for ports in nexus_ports:
-            self.enable_vlan_on_trunk_int(man, ports, vlan_ids)
+            self.enable_vlan_on_trunk_int(man, nexus_host, ports, vlan_ids)
 
     def delete_vlan(self, vlan_id, nexus_host, nexus_user, nexus_password,
                     nexus_ports, nexus_ssh_port):
@@ -208,7 +215,7 @@ class CiscoNEXUSDriver():
         if not vlan_ids:
             vlan_ids = self.build_vlans_cmd()
         for ports in nexus_ports:
-            self.enable_vlan_on_trunk_int(man, ports, vlan_ids)
+            self.enable_vlan_on_trunk_int(man, nexus_host, ports, vlan_ids)
 
     def remove_vlan_int(self, vlan_id, nexus_host, nexus_user, nexus_password,
                         nexus_ports, nexus_ssh_port):
index aeb7d43c903bfa09570be0ff442a43c912cf92fc..a6a5018538cd71d961a01a3058b186fe94b88791 100644 (file)
@@ -86,7 +86,7 @@ CMD_NO_VLAN_CONF_SNIPPET = """
           </no>
 """
 
-CMD_VLAN_INT_SNIPPET = """
+CMD_INT_VLAN_HEADER = """
           <interface>
             <ethernet>
               <interface>%s</interface>
@@ -94,10 +94,16 @@ CMD_VLAN_INT_SNIPPET = """
                 <switchport>
                   <trunk>
                     <allowed>
-                      <vlan>
-                        <add>
-                          <add_vlans>%s</add_vlans>
-                        </add>
+                      <vlan>"""
+
+CMD_VLAN_ID = """
+                          <vlan_id>%s</vlan_id>"""
+
+CMD_VLAN_ADD_ID = """
+                        <add>%s
+                        </add>""" % CMD_VLAN_ID
+
+CMD_INT_VLAN_TRAILER = """
                       </vlan>
                     </allowed>
                   </trunk>
@@ -107,6 +113,14 @@ CMD_VLAN_INT_SNIPPET = """
           </interface>
 """
 
+CMD_INT_VLAN_SNIPPET = (CMD_INT_VLAN_HEADER +
+                        CMD_VLAN_ID +
+                        CMD_INT_VLAN_TRAILER)
+
+CMD_INT_VLAN_ADD_SNIPPET = (CMD_INT_VLAN_HEADER +
+                            CMD_VLAN_ADD_ID +
+                            CMD_INT_VLAN_TRAILER)
+
 CMD_PORT_TRUNK = """
           <interface>
             <ethernet>
index a2a239066fc2da121aa156cb8158855fc4390fa5..d1b8de8f89f339744c0a4d1c68756be5a355a4eb 100644 (file)
@@ -155,28 +155,29 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
         self.mock_ncclient.configure_mock(**config)
 
     @contextlib.contextmanager
-    def _create_port_res(self, fmt=None, no_delete=False,
-                         **kwargs):
+    def _create_port_res(self, name='myname', cidr='1.0.0.0/24',
+                         do_delete=True):
         """Create a network, subnet, and port and yield the result.
 
         Create a network, subnet, and port, yield the result,
         then delete the port, subnet, and network.
 
-        :param fmt: Format to be used for API requests.
-        :param no_delete: If set to True, don't delete the port at the
-                          end of testing.
-        :param kwargs: Keyword args to be passed to self._create_port.
+        :param name: Name of network to be created
+        :param cidr: cidr address of subnetwork to be created
+        :param do_delete: If set to True, delete the port at the
+                          end of testing
 
         """
-        with self.subnet() as subnet:
-            net_id = subnet['subnet']['network_id']
-            res = self._create_port(fmt, net_id, **kwargs)
-            port = self.deserialize(fmt, res)
-            try:
-                yield res
-            finally:
-                if not no_delete:
-                    self._delete('ports', port['port']['id'])
+        with self.network(name=name) as network:
+            with self.subnet(network=network, cidr=cidr) as subnet:
+                net_id = subnet['subnet']['network_id']
+                res = self._create_port(self.fmt, net_id)
+                port = self.deserialize(self.fmt, res)
+                try:
+                    yield res
+                finally:
+                    if do_delete:
+                        self._delete('ports', port['port']['id'])
 
     def _assertExpectedHTTP(self, status, exc):
         """Confirm that an HTTP status corresponds to an expected exception.
@@ -255,6 +256,17 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
                     'ports',
                     wexc.HTTPInternalServerError.code)
 
+    def test_nexus_enable_vlan_cmd(self):
+        """Verify the syntax of the command to enable a vlan on an intf."""
+        # First vlan should be configured without 'add' keyword
+        with self._create_port_res(name='net1', cidr='1.0.0.0/24'):
+            self.assertTrue(self._is_in_last_nexus_cfg(['allowed', 'vlan']))
+            self.assertFalse(self._is_in_last_nexus_cfg(['add']))
+            # Second vlan should be configured with 'add' keyword
+            with self._create_port_res(name='net2', cidr='1.0.1.0/24'):
+                self.assertTrue(
+                    self._is_in_last_nexus_cfg(['allowed', 'vlan', 'add']))
+
     def test_nexus_connect_fail(self):
         """Test failure to connect to a Nexus switch.
 
@@ -265,8 +277,7 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
         """
         with self._patch_ncclient('manager.connect.side_effect',
                                   AttributeError):
-            with self._create_port_res(self.fmt, no_delete=True,
-                                       name='myname') as res:
+            with self._create_port_res(do_delete=False) as res:
                 self._assertExpectedHTTP(res.status_int,
                                          c_exc.NexusConnectFailed)
 
@@ -281,8 +292,7 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
         with self._patch_ncclient(
             'manager.connect.return_value.edit_config.side_effect',
             AttributeError):
-            with self._create_port_res(self.fmt, no_delete=True,
-                                       name='myname') as res:
+            with self._create_port_res(do_delete=False) as res:
                 self._assertExpectedHTTP(res.status_int,
                                          c_exc.NexusConfigFailed)
 
@@ -302,7 +312,7 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
         with self._patch_ncclient(
             'manager.connect.return_value.edit_config.side_effect',
             mock_edit_config_a):
-            with self._create_port_res(self.fmt, name='myname') as res:
+            with self._create_port_res(name='myname') as res:
                 self.assertEqual(res.status_int, wexc.HTTPCreated.code)
 
         def mock_edit_config_b(target, config):
@@ -312,7 +322,7 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
         with self._patch_ncclient(
             'manager.connect.return_value.edit_config.side_effect',
             mock_edit_config_b):
-            with self._create_port_res(self.fmt, name='myname') as res:
+            with self._create_port_res(name='myname') as res:
                 self.assertEqual(res.status_int, wexc.HTTPCreated.code)
 
     def test_nexus_vlan_config_rollback(self):
@@ -330,8 +340,7 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
         with self._patch_ncclient(
             'manager.connect.return_value.edit_config.side_effect',
             mock_edit_config):
-            with self._create_port_res(self.fmt, no_delete=True,
-                                       name='myname') as res:
+            with self._create_port_res(name='myname', do_delete=False) as res:
                 # Confirm that the last configuration sent to the Nexus
                 # switch was deletion of the VLAN.
                 self.assertTrue(
@@ -362,8 +371,7 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
 
         with mock.patch.object(ovs_db_v2, 'get_network_binding',
                                new=_return_none_if_nexus_caller):
-            with self._create_port_res(self.fmt, no_delete=True,
-                                       name='myname') as res:
+            with self._create_port_res(do_delete=False) as res:
                 self._assertExpectedHTTP(res.status_int,
                                          c_exc.NetworkSegmentIDNotFound)
 
@@ -377,8 +385,7 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
         with mock.patch.object(virt_phy_sw_v2.VirtualPhysicalSwitchModelV2,
                                '_get_instance_host') as mock_get_instance:
             mock_get_instance.return_value = 'fictitious_host'
-            with self._create_port_res(self.fmt, no_delete=True,
-                                       name='myname') as res:
+            with self._create_port_res(do_delete=False) as res:
                 self._assertExpectedHTTP(res.status_int,
                                          c_exc.NexusComputeHostNotConfigured)
 
@@ -392,8 +399,7 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
         """
         with mock.patch.object(nexus_db_v2, 'add_nexusport_binding',
                                side_effect=KeyError):
-            with self._create_port_res(self.fmt, no_delete=True,
-                                       name='myname') as res:
+            with self._create_port_res(do_delete=False) as res:
                 # Confirm that the last configuration sent to the Nexus
                 # switch was a removal of vlan from the test interface.
                 self.assertTrue(
@@ -409,7 +415,7 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
         plugin for a delete port operation.
 
         """
-        with self._create_port_res(self.fmt, name='myname') as res:
+        with self._create_port_res() as res:
 
             # After port is created, we should have one binding for this
             # vlan/nexus switch.
@@ -442,7 +448,7 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
         nexus switch during a delete_port operation.
 
         """
-        with self._create_port_res(self.fmt, name='myname') as res:
+        with self._create_port_res() as res:
 
             port = self.deserialize(self.fmt, res)