From 2b325fbd20e1ead8ec713c2fd69fd970dab0b405 Mon Sep 17 00:00:00 2001 From: Britt Houser Date: Fri, 8 Aug 2014 15:45:51 -0400 Subject: [PATCH] Change nexus_dict to accept port lists 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 --- .../drivers/cisco/nexus/mech_cisco_nexus.py | 55 +++++++++++++------ .../drivers/cisco/nexus/test_cisco_config.py | 4 ++ .../drivers/cisco/nexus/test_cisco_nexus.py | 39 +++++++++---- 3 files changed, 72 insertions(+), 26 deletions(-) diff --git a/neutron/plugins/ml2/drivers/cisco/nexus/mech_cisco_nexus.py b/neutron/plugins/ml2/drivers/cisco/nexus/mech_cisco_nexus.py index e77212ffb..a1bf1ad1d 100644 --- a/neutron/plugins/ml2/drivers/cisco/nexus/mech_cisco_nexus.py +++ b/neutron/plugins/ml2/drivers/cisco/nexus/mech_cisco_nexus.py @@ -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: diff --git a/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_config.py b/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_config.py index 55f0db3da..6dd4873be 100644 --- a/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_config.py +++ b/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_config.py @@ -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 diff --git a/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus.py b/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus.py index 482c4500b..51a3afb3f 100644 --- a/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus.py +++ b/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus.py @@ -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']) -- 2.45.2