]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Change nexus_dict to accept port lists
authorBritt Houser <bhouser@cisco.com>
Fri, 8 Aug 2014 19:45:51 +0000 (15:45 -0400)
committerBritt Houser <bhouser@cisco.com>
Sun, 17 Aug 2014 18:33:29 +0000 (14:33 -0400)
When users configured a server to have two logical connections to a
single switch, the nexus driver would never know about the second
connection because the nexus_dict stored the interface as a single
value, which would just get overwritten.  This has been fixed by
allowing the port value to be a comma seperated list.  All
operations on ports have been update to loop over multiple ports
per switch. New test added for host with dual connections to one
switch.

Change-Id: Iefb30452083747b45496600c81f8d0a6f378bd08
Closes-Bug: 1288393

neutron/plugins/ml2/drivers/cisco/nexus/mech_cisco_nexus.py
neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_config.py
neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus.py

index e77212ffbae65b32eeff0486780067f4294e1a46..a1bf1ad1d5ec53108e2483d5fbbf0da6d014bfff 100644 (file)
@@ -66,12 +66,13 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
         host_connections = []
         for switch_ip, attr in self._nexus_switches:
             if str(attr) == str(host_id):
-                port_id = self._nexus_switches[switch_ip, attr]
-                if ':' in port_id:
-                    intf_type, port = port_id.split(':')
-                else:
-                    intf_type, port = 'ethernet', port_id
-                host_connections.append((switch_ip, intf_type, port))
+                for port_id in (
+                    self._nexus_switches[switch_ip, attr].split(',')):
+                    if ':' in port_id:
+                        intf_type, port = port_id.split(':')
+                    else:
+                        intf_type, port = 'ethernet', port_id
+                    host_connections.append((switch_ip, intf_type, port))
 
         if host_connections:
             return host_connections
@@ -100,18 +101,30 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
         vlan_name = cfg.CONF.ml2_cisco.vlan_name_prefix + str(vlan_id)
         host_connections = self._get_switch_info(host_id)
 
+        # (nexus_port,switch_ip) will be unique in each iteration.
+        # But switch_ip will repeat if host has >1 connection to same switch.
+        # So track which switch_ips already have vlan created in this loop.
+        vlan_already_created = []
         for switch_ip, intf_type, nexus_port in host_connections:
-            # Check to see if this is the first binding to use this vlan on the
-            # switch/port. Configure switch accordingly.
-            bindings = nxos_db.get_nexusvlan_binding(vlan_id, switch_ip)
-            if len(bindings) == 1:
-                LOG.debug(_("Nexus: create & trunk vlan %s"), vlan_name)
-                self.driver.create_and_trunk_vlan(
-                    switch_ip, vlan_id, vlan_name, intf_type, nexus_port)
-            else:
-                LOG.debug(_("Nexus: trunk vlan %s"), vlan_name)
+
+            # The VLAN needs to be created on the switch if no other
+            # instance has been placed in this VLAN on a different host
+            # attached to this switch.  Search the existing bindings in the
+            # database.  If all the instance_id in the database match the
+            # current device_id, then create the VLAN, but only once per
+            # switch_ip.  Otherwise, just trunk.
+            all_bindings = nxos_db.get_nexusvlan_binding(vlan_id, switch_ip)
+            previous_bindings = [row for row in all_bindings
+                    if row.instance_id != device_id]
+            if previous_bindings or (switch_ip in vlan_already_created):
+                LOG.debug("Nexus: trunk vlan %s"), vlan_name
                 self.driver.enable_vlan_on_trunk_int(switch_ip, vlan_id,
                                                      intf_type, nexus_port)
+            else:
+                vlan_already_created.append(switch_ip)
+                LOG.debug("Nexus: create & trunk vlan %s"), vlan_name
+                self.driver.create_and_trunk_vlan(
+                    switch_ip, vlan_id, vlan_name, intf_type, nexus_port)
 
     def _delete_nxos_db(self, vlan_id, device_id, host_id):
         """Delete the nexus database entry.
@@ -135,7 +148,13 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
         Called during update postcommit port event.
         """
         host_connections = self._get_switch_info(host_id)
+
+        # (nexus_port,switch_ip) will be unique in each iteration.
+        # But switch_ip will repeat if host has >1 connection to same switch.
+        # So track which switch_ips already have vlan removed in this loop.
+        vlan_already_removed = []
         for switch_ip, intf_type, nexus_port in host_connections:
+
             # if there are no remaining db entries using this vlan on this
             # nexus switch port then remove vlan from the switchport trunk.
             port_id = '%s:%s' % (intf_type, nexus_port)
@@ -151,7 +170,11 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
                 try:
                     nxos_db.get_nexusvlan_binding(vlan_id, switch_ip)
                 except excep.NexusPortBindingNotFound:
-                    self.driver.delete_vlan(switch_ip, vlan_id)
+
+                    # Do not perform a second time on same switch
+                    if switch_ip not in vlan_already_removed:
+                        self.driver.delete_vlan(switch_ip, vlan_id)
+                        vlan_already_removed.append(switch_ip)
 
     def _is_vm_migration(self, context):
         if not context.bound_segment and context.original_bound_segment:
index 55f0db3daca3ddd7dbe5b19ee7a882c11335fa07..6dd4873bea6924ea4bdfb2e4151540e3bcd48edc 100644 (file)
@@ -42,6 +42,7 @@ class TestCiscoNexusPluginConfig(base.BaseTestCase):
                 'ssh_port': [22],
                 'compute1': ['1/1'],
                 'compute2': ['1/2'],
+                'compute5': ['1/3,1/4']
             },
             'ml2_mech_cisco_nexus:2.2.2.2': {
                 'username': ['admin'],
@@ -49,6 +50,7 @@ class TestCiscoNexusPluginConfig(base.BaseTestCase):
                 'ssh_port': [22],
                 'compute3': ['1/1'],
                 'compute4': ['1/2'],
+                'compute5': ['portchannel:20,portchannel:30']
             },
         }
         expected_dev_dict = {
@@ -57,11 +59,13 @@ class TestCiscoNexusPluginConfig(base.BaseTestCase):
             ('1.1.1.1', 'ssh_port'): 22,
             ('1.1.1.1', 'compute1'): '1/1',
             ('1.1.1.1', 'compute2'): '1/2',
+            ('1.1.1.1', 'compute5'): '1/3,1/4',
             ('2.2.2.2', 'username'): 'admin',
             ('2.2.2.2', 'password'): 'mySecretPassword',
             ('2.2.2.2', 'ssh_port'): 22,
             ('2.2.2.2', 'compute3'): '1/1',
             ('2.2.2.2', 'compute4'): '1/2',
+            ('2.2.2.2', 'compute5'): 'portchannel:20,portchannel:30',
         }
         with mock.patch.object(cfg, 'MultiConfigParser') as parser:
             parser.return_value.read.return_value = cfg.CONF.config_file
index 482c4500b1890ff2bc57d5e867b778b41f599009..51a3afb3f32f7dcd5a56d55479ddeb0d99c05615 100644 (file)
@@ -32,18 +32,23 @@ from neutron.tests import base
 
 NEXUS_IP_ADDRESS = '1.1.1.1'
 NEXUS_IP_ADDRESS_PC = '2.2.2.2'
+NEXUS_IP_ADDRESS_DUAL = '3.3.3.3'
 HOST_NAME_1 = 'testhost1'
 HOST_NAME_2 = 'testhost2'
 HOST_NAME_PC = 'testpchost'
+HOST_NAME_DUAL = 'testdualhost'
 INSTANCE_1 = 'testvm1'
 INSTANCE_2 = 'testvm2'
 INSTANCE_PC = 'testpcvm'
+INSTANCE_DUAL = 'testdualvm'
 NEXUS_PORT_1 = 'ethernet:1/10'
 NEXUS_PORT_2 = 'ethernet:1/20'
 NEXUS_PORTCHANNELS = 'portchannel:2'
+NEXUS_DUAL = 'ethernet:1/3,portchannel:2'
 VLAN_ID_1 = 267
 VLAN_ID_2 = 265
 VLAN_ID_PC = 268
+VLAN_ID_DUAL = 269
 DEVICE_OWNER = 'compute:test'
 NEXUS_SSH_PORT = '22'
 PORT_STATE = n_const.PORT_STATUS_ACTIVE
@@ -120,6 +125,12 @@ class TestCiscoNexusDevice(base.BaseTestCase):
             NEXUS_PORTCHANNELS,
             INSTANCE_PC,
             VLAN_ID_PC),
+        'test_config_dual': TestConfigObj(
+            NEXUS_IP_ADDRESS_DUAL,
+            HOST_NAME_DUAL,
+            NEXUS_DUAL,
+            INSTANCE_DUAL,
+            VLAN_ID_DUAL),
     }
 
     def setUp(self):
@@ -174,19 +185,22 @@ class TestCiscoNexusDevice(base.BaseTestCase):
 
         self._cisco_mech_driver.update_port_precommit(port_context)
         self._cisco_mech_driver.update_port_postcommit(port_context)
-        bindings = nexus_db_v2.get_nexusport_binding(nexus_port,
-                                                     vlan_id,
-                                                     nexus_ip_addr,
-                                                     instance_id)
-        self.assertEqual(len(bindings), 1)
+        for port_id in nexus_port.split(','):
+            bindings = nexus_db_v2.get_nexusport_binding(port_id,
+                                                         vlan_id,
+                                                         nexus_ip_addr,
+                                                         instance_id)
+            self.assertEqual(len(bindings), 1)
 
         self._cisco_mech_driver.delete_port_precommit(port_context)
         self._cisco_mech_driver.delete_port_postcommit(port_context)
-        with testtools.ExpectedException(exceptions.NexusPortBindingNotFound):
-            nexus_db_v2.get_nexusport_binding(nexus_port,
-                                              vlan_id,
-                                              nexus_ip_addr,
-                                              instance_id)
+        for port_id in nexus_port.split(','):
+            with testtools.ExpectedException(
+                    exceptions.NexusPortBindingNotFound):
+                nexus_db_v2.get_nexusport_binding(port_id,
+                                                  vlan_id,
+                                                  nexus_ip_addr,
+                                                  instance_id)
 
     def test_create_delete_ports(self):
         """Tests creation and deletion of two new virtual Ports."""
@@ -200,3 +214,8 @@ class TestCiscoNexusDevice(base.BaseTestCase):
         """Tests creation of a port over a portchannel."""
         self._create_delete_port(
             TestCiscoNexusDevice.test_configs['test_config_portchannel'])
+
+    def test_create_delete_dual(self):
+        """Tests creation and deletion of dual ports for single server"""
+        self._create_delete_port(
+            TestCiscoNexusDevice.test_configs['test_config_dual'])