From: Dane LeBlanc Date: Fri, 10 May 2013 21:54:04 +0000 (-0400) Subject: Fix Cisco nexus plugin failure for first VLAN on phy interface X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=92ef293d02a7499850bc3becda413d6be22ae06c;p=openstack-build%2Fneutron-build.git Fix Cisco nexus plugin failure for first VLAN on phy interface 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 --- diff --git a/quantum/plugins/cisco/db/nexus_db_v2.py b/quantum/plugins/cisco/db/nexus_db_v2.py index a48a82795..4fd944993 100644 --- a/quantum/plugins/cisco/db/nexus_db_v2.py +++ b/quantum/plugins/cisco/db/nexus_db_v2.py @@ -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 diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_network_driver_v2.py b/quantum/plugins/cisco/nexus/cisco_nexus_network_driver_v2.py index f7a162e38..a620afa97 100644 --- a/quantum/plugins/cisco/nexus/cisco_nexus_network_driver_v2.py +++ b/quantum/plugins/cisco/nexus/cisco_nexus_network_driver_v2.py @@ -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): diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_snippets.py b/quantum/plugins/cisco/nexus/cisco_nexus_snippets.py index aeb7d43c9..a6a501853 100644 --- a/quantum/plugins/cisco/nexus/cisco_nexus_snippets.py +++ b/quantum/plugins/cisco/nexus/cisco_nexus_snippets.py @@ -86,7 +86,7 @@ CMD_NO_VLAN_CONF_SNIPPET = """ """ -CMD_VLAN_INT_SNIPPET = """ +CMD_INT_VLAN_HEADER = """ %s @@ -94,10 +94,16 @@ CMD_VLAN_INT_SNIPPET = """ - - - %s - + """ + +CMD_VLAN_ID = """ + %s""" + +CMD_VLAN_ADD_ID = """ + %s + """ % CMD_VLAN_ID + +CMD_INT_VLAN_TRAILER = """ @@ -107,6 +113,14 @@ CMD_VLAN_INT_SNIPPET = """ """ +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 = """ diff --git a/quantum/tests/unit/cisco/test_network_plugin.py b/quantum/tests/unit/cisco/test_network_plugin.py index a2a239066..d1b8de8f8 100644 --- a/quantum/tests/unit/cisco/test_network_plugin.py +++ b/quantum/tests/unit/cisco/test_network_plugin.py @@ -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)