From af75845b64b9f2ab12e4f49106055e3a64468cf0 Mon Sep 17 00:00:00 2001 From: Rich Curran Date: Thu, 27 Mar 2014 15:34:08 -0400 Subject: [PATCH] ML2 Cisco Nexus MD: Support portchannel interfaces Port of port-channel interface support that was implemented for the cisco core plugin - https://review.openstack.org/#/c/42037 Note that additional port-channel UT already existed. (Created during initial cisco core nexus subplugin -> ml2 cisco nexus md port.) See test_cisco_nexus.py module, NEXUS_PORTCHANNELS references. Closes-Bug: 1294900 Change-Id: Ifc64f605e5783ee1e85d66c87f422f64b47fa996 --- etc/neutron/plugins/ml2/ml2_conf_cisco.ini | 8 +- .../drivers/cisco/nexus/mech_cisco_nexus.py | 78 +++++++++++-------- .../ml2/drivers/cisco/nexus/nexus_db_v2.py | 8 +- .../cisco/nexus/nexus_network_driver.py | 40 ++++------ .../ml2/drivers/cisco/nexus/nexus_snippets.py | 16 ++-- .../drivers/cisco/nexus/test_cisco_mech.py | 14 ++-- .../drivers/cisco/nexus/test_cisco_nexus.py | 4 +- .../cisco/nexus/test_cisco_nexus_db.py | 6 +- 8 files changed, 95 insertions(+), 79 deletions(-) diff --git a/etc/neutron/plugins/ml2/ml2_conf_cisco.ini b/etc/neutron/plugins/ml2/ml2_conf_cisco.ini index 6b6f5a76d..927c6f5be 100644 --- a/etc/neutron/plugins/ml2/ml2_conf_cisco.ini +++ b/etc/neutron/plugins/ml2/ml2_conf_cisco.ini @@ -23,13 +23,16 @@ # # Cisco Nexus Switch Format. # [ml2_mech_cisco_nexus:] -# = (1) +# = (1) # ssh_port= (2) # username= (3) # password= (4) # # (1) For each host connected to a port on the switch, specify the hostname # and the Nexus physical port (interface) it is connected to. +# Valid intf_type's are 'ethernet' and 'port-channel'. +# The default setting for is 'ethernet' and need not be +# added to this setting. # (2) The TCP port for connecting via SSH to manage the switch. This is # port number 22 unless the switch has been configured otherwise. # (3) The username for logging into the switch to manage it. @@ -38,7 +41,8 @@ # Example: # [ml2_mech_cisco_nexus:1.1.1.1] # compute1=1/1 -# compute2=1/2 +# compute2=ethernet:1/2 +# compute3=port-channel:1 # ssh_port=22 # username=admin # password=mySecretPassword 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 8f98f9fca..3514017cc 100644 --- a/neutron/plugins/ml2/drivers/cisco/nexus/mech_cisco_nexus.py +++ b/neutron/plugins/ml2/drivers/cisco/nexus/mech_cisco_nexus.py @@ -68,10 +68,18 @@ class CiscoNexusMechanismDriver(api.MechanismDriver): return port['status'] == n_const.PORT_STATUS_ACTIVE def _get_switch_info(self, host_id): + host_connections = [] for switch_ip, attr in self._nexus_switches: if str(attr) == str(host_id): port_id = self._nexus_switches[switch_ip, attr] - return port_id, switch_ip + 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 else: raise excep.NexusComputeHostNotConfigured(host=host_id) @@ -80,9 +88,11 @@ class CiscoNexusMechanismDriver(api.MechanismDriver): Called during update precommit port event. """ - port_id, switch_ip = self._get_switch_info(host_id) - nxos_db.add_nexusport_binding(port_id, str(vlan_id), switch_ip, - device_id) + host_connections = self._get_switch_info(host_id) + for switch_ip, intf_type, nexus_port in host_connections: + port_id = '%s:%s' % (intf_type, nexus_port) + nxos_db.add_nexusport_binding(port_id, str(vlan_id), switch_ip, + device_id) def _configure_switch_entry(self, vlan_id, device_id, host_id): """Create a nexus switch entry. @@ -92,19 +102,21 @@ class CiscoNexusMechanismDriver(api.MechanismDriver): Called during update postcommit port event. """ - port_id, switch_ip = self._get_switch_info(host_id) vlan_name = cfg.CONF.ml2_cisco.vlan_name_prefix + str(vlan_id) - - # 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, - port_id) - else: - LOG.debug(_("Nexus: trunk vlan %s"), vlan_name) - self.driver.enable_vlan_on_trunk_int(switch_ip, vlan_id, port_id) + host_connections = self._get_switch_info(host_id) + + 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) + self.driver.enable_vlan_on_trunk_int(switch_ip, vlan_id, + intf_type, nexus_port) def _delete_nxos_db(self, vlan_id, device_id, host_id): """Delete the nexus database entry. @@ -112,9 +124,10 @@ class CiscoNexusMechanismDriver(api.MechanismDriver): Called during delete precommit port event. """ try: - row = nxos_db.get_nexusvm_binding(vlan_id, device_id) - nxos_db.remove_nexusport_binding(row.port_id, row.vlan_id, - row.switch_ip, row.instance_id) + rows = nxos_db.get_nexusvm_bindings(vlan_id, device_id) + for row in rows: + nxos_db.remove_nexusport_binding( + row.port_id, row.vlan_id, row.switch_ip, row.instance_id) except excep.NexusPortBindingNotFound: return @@ -126,21 +139,24 @@ class CiscoNexusMechanismDriver(api.MechanismDriver): Called during update postcommit port event. """ - port_id, switch_ip = self._get_switch_info(host_id) - - # if there are no remaining db entries using this vlan on this nexus - # switch port then remove vlan from the switchport trunk. - try: - nxos_db.get_port_vlan_switch_binding(port_id, vlan_id, switch_ip) - except excep.NexusPortBindingNotFound: - self.driver.disable_vlan_on_trunk_int(switch_ip, vlan_id, port_id) - + host_connections = self._get_switch_info(host_id) + for switch_ip, intf_type, nexus_port in host_connections: # if there are no remaining db entries using this vlan on this - # nexus switch then remove the vlan. + # nexus switch port then remove vlan from the switchport trunk. + port_id = '%s:%s' % (intf_type, nexus_port) try: - nxos_db.get_nexusvlan_binding(vlan_id, switch_ip) + nxos_db.get_port_vlan_switch_binding(port_id, vlan_id, + switch_ip) except excep.NexusPortBindingNotFound: - self.driver.delete_vlan(switch_ip, vlan_id) + self.driver.disable_vlan_on_trunk_int(switch_ip, vlan_id, + intf_type, nexus_port) + + # if there are no remaining db entries using this vlan on this + # nexus switch then remove the vlan. + try: + nxos_db.get_nexusvlan_binding(vlan_id, switch_ip) + except excep.NexusPortBindingNotFound: + self.driver.delete_vlan(switch_ip, vlan_id) def _is_vm_migration(self, context): if not context.bound_segment and context.original_bound_segment: diff --git a/neutron/plugins/ml2/drivers/cisco/nexus/nexus_db_v2.py b/neutron/plugins/ml2/drivers/cisco/nexus/nexus_db_v2.py index a9194379d..141040e58 100644 --- a/neutron/plugins/ml2/drivers/cisco/nexus/nexus_db_v2.py +++ b/neutron/plugins/ml2/drivers/cisco/nexus/nexus_db_v2.py @@ -82,11 +82,11 @@ def update_nexusport_binding(port_id, new_vlan_id): return binding -def get_nexusvm_binding(vlan_id, instance_id): +def get_nexusvm_bindings(vlan_id, instance_id): """Lists nexusvm bindings.""" - LOG.debug(_("get_nexusvm_binding() called")) - return _lookup_first_nexus_binding(instance_id=instance_id, - vlan_id=vlan_id) + LOG.debug(_("get_nexusvm_bindings() called")) + return _lookup_all_nexus_bindings(instance_id=instance_id, + vlan_id=vlan_id) def get_port_vlan_switch_binding(port_id, vlan_id, switch_ip): diff --git a/neutron/plugins/ml2/drivers/cisco/nexus/nexus_network_driver.py b/neutron/plugins/ml2/drivers/cisco/nexus/nexus_network_driver.py index 4be4ab88f..8670c5534 100644 --- a/neutron/plugins/ml2/drivers/cisco/nexus/nexus_network_driver.py +++ b/neutron/plugins/ml2/drivers/cisco/nexus/nexus_network_driver.py @@ -154,54 +154,46 @@ class CiscoNexusDriver(object): confstr = self.create_xml_snippet(confstr) self._edit_config(nexus_host, target='running', config=confstr) - def enable_port_trunk(self, nexus_host, interface): - """Enable trunk mode an interface on Nexus Switch.""" - confstr = snipp.CMD_PORT_TRUNK % (interface) - confstr = self.create_xml_snippet(confstr) - LOG.debug(_("NexusDriver: %s"), confstr) - self._edit_config(nexus_host, target='running', config=confstr) - - def disable_switch_port(self, nexus_host, interface): - """Disable trunk mode an interface on Nexus Switch.""" - confstr = snipp.CMD_NO_SWITCHPORT % (interface) - confstr = self.create_xml_snippet(confstr) - LOG.debug(_("NexusDriver: %s"), confstr) - self._edit_config(nexus_host, target='running', config=confstr) - - def enable_vlan_on_trunk_int(self, nexus_host, vlanid, interface): + def enable_vlan_on_trunk_int(self, nexus_host, vlanid, intf_type, + interface): """Enable a VLAN on a trunk interface.""" # If more than one VLAN is configured on this interface then # include the 'add' keyword. - if len(nexus_db_v2.get_port_switch_bindings(interface, - nexus_host)) == 1: + if len(nexus_db_v2.get_port_switch_bindings( + '%s:%s' % (intf_type, interface), nexus_host)) == 1: snippet = snipp.CMD_INT_VLAN_SNIPPET else: snippet = snipp.CMD_INT_VLAN_ADD_SNIPPET - confstr = snippet % (interface, vlanid) + confstr = snippet % (intf_type, interface, vlanid, intf_type) confstr = self.create_xml_snippet(confstr) LOG.debug(_("NexusDriver: %s"), confstr) self._edit_config(nexus_host, target='running', config=confstr) - def disable_vlan_on_trunk_int(self, nexus_host, vlanid, interface): + def disable_vlan_on_trunk_int(self, nexus_host, vlanid, intf_type, + interface): """Disable a VLAN on a trunk interface.""" - confstr = snipp.CMD_NO_VLAN_INT_SNIPPET % (interface, vlanid) + confstr = (snipp.CMD_NO_VLAN_INT_SNIPPET % + (intf_type, interface, vlanid, intf_type)) confstr = self.create_xml_snippet(confstr) LOG.debug(_("NexusDriver: %s"), confstr) self._edit_config(nexus_host, target='running', config=confstr) def create_and_trunk_vlan(self, nexus_host, vlan_id, vlan_name, - nexus_port): + intf_type, nexus_port): """Create VLAN and trunk it on the specified ports.""" self.create_vlan(nexus_host, vlan_id, vlan_name) LOG.debug(_("NexusDriver created VLAN: %s"), vlan_id) if nexus_port: - self.enable_vlan_on_trunk_int(nexus_host, vlan_id, nexus_port) + self.enable_vlan_on_trunk_int(nexus_host, vlan_id, intf_type, + nexus_port) - def delete_and_untrunk_vlan(self, nexus_host, vlan_id, nexus_port): + def delete_and_untrunk_vlan(self, nexus_host, vlan_id, intf_type, + nexus_port): """Delete VLAN and untrunk it from the specified ports.""" self.delete_vlan(nexus_host, vlan_id) if nexus_port: - self.disable_vlan_on_trunk_int(nexus_host, vlan_id, nexus_port) + self.disable_vlan_on_trunk_int(nexus_host, vlan_id, intf_type, + nexus_port) def create_vlan_svi(self, nexus_host, vlan_id, gateway_ip): confstr = snipp.CMD_VLAN_SVI_SNIPPET % (vlan_id, gateway_ip) diff --git a/neutron/plugins/ml2/drivers/cisco/nexus/nexus_snippets.py b/neutron/plugins/ml2/drivers/cisco/nexus/nexus_snippets.py index b30c7e638..fb38e4199 100644 --- a/neutron/plugins/ml2/drivers/cisco/nexus/nexus_snippets.py +++ b/neutron/plugins/ml2/drivers/cisco/nexus/nexus_snippets.py @@ -85,7 +85,7 @@ CMD_NO_VLAN_CONF_SNIPPET = """ CMD_INT_VLAN_HEADER = """ - + <%s> %s <__XML__MODE_if-ethernet-switch> @@ -106,7 +106,7 @@ CMD_INT_VLAN_TRAILER = """ - + """ @@ -120,7 +120,7 @@ CMD_INT_VLAN_ADD_SNIPPET = (CMD_INT_VLAN_HEADER + CMD_PORT_TRUNK = """ - + <%s> %s <__XML__MODE_if-ethernet-switch> @@ -131,13 +131,13 @@ CMD_PORT_TRUNK = """ - + """ CMD_NO_SWITCHPORT = """ - + <%s> %s <__XML__MODE_if-ethernet-switch> @@ -145,13 +145,13 @@ CMD_NO_SWITCHPORT = """ - + """ CMD_NO_VLAN_INT_SNIPPET = """ - + <%s> %s <__XML__MODE_if-ethernet-switch> @@ -167,7 +167,7 @@ CMD_NO_VLAN_INT_SNIPPET = """ - + """ diff --git a/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_mech.py b/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_mech.py index 51a4f1d32..c43315f5a 100644 --- a/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_mech.py +++ b/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_mech.py @@ -418,8 +418,10 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase, with self._create_resources() as result: # Verify initial database entry. # Use port_id to verify that 1st host name was used. - binding = nexus_db_v2.get_nexusvm_binding(VLAN_START, DEVICE_ID_1) - self.assertEqual(binding.port_id, NEXUS_INTERFACE) + binding = nexus_db_v2.get_nexusvm_bindings(VLAN_START, + DEVICE_ID_1)[0] + intf_type, nexus_port = binding.port_id.split(':') + self.assertEqual(nexus_port, NEXUS_INTERFACE) port = self.deserialize(self.fmt, result) port_id = port['port']['id'] @@ -434,7 +436,7 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase, # Verify that port entry has been deleted. self.assertRaises(c_exc.NexusPortBindingNotFound, - nexus_db_v2.get_nexusvm_binding, + nexus_db_v2.get_nexusvm_bindings, VLAN_START, DEVICE_ID_1) # Trigger update event to bind segment with new host. @@ -445,8 +447,10 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase, # Verify that port entry has been added using new host name. # Use port_id to verify that 2nd host name was used. - binding = nexus_db_v2.get_nexusvm_binding(VLAN_START, DEVICE_ID_1) - self.assertEqual(binding.port_id, NEXUS_INTERFACE_2) + binding = nexus_db_v2.get_nexusvm_bindings(VLAN_START, + DEVICE_ID_1)[0] + intf_type, nexus_port = binding.port_id.split(':') + self.assertEqual(nexus_port, NEXUS_INTERFACE_2) def test_nexus_config_fail(self): """Test a Nexus switch configuration failure. 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 dfa74c07f..390477e1d 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 @@ -38,8 +38,8 @@ HOST_NAME_PC = 'testpchost' INSTANCE_1 = 'testvm1' INSTANCE_2 = 'testvm2' INSTANCE_PC = 'testpcvm' -NEXUS_PORT_1 = '1/10' -NEXUS_PORT_2 = '1/20' +NEXUS_PORT_1 = 'ethernet:1/10' +NEXUS_PORT_2 = 'ethernet:1/20' NEXUS_PORTCHANNELS = 'portchannel:2' VLAN_ID_1 = 267 VLAN_ID_2 = 265 diff --git a/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_db.py b/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_db.py index b66514c54..72d36ca49 100644 --- a/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_db.py +++ b/neutron/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_db.py @@ -76,8 +76,8 @@ class CiscoNexusDbTest(base.BaseTestCase): return nexus_db_v2.get_nexusvlan_binding(npb.vlan, npb.switch) def _get_nexusvm_binding(self, npb): - """Gets port bindings based on vlan and instance.""" - return nexus_db_v2.get_nexusvm_binding(npb.vlan, npb.instance) + """Gets port binding based on vlan and instance.""" + return nexus_db_v2.get_nexusvm_bindings(npb.vlan, npb.instance)[0] def _get_port_vlan_switch_binding(self, npb): """Gets port bindings based on port, vlan, and switch.""" @@ -149,7 +149,7 @@ class CiscoNexusDbTest(base.BaseTestCase): self._assert_bindings_match(npb, npb22) with testtools.ExpectedException(exceptions.NexusPortBindingNotFound): - nexus_db_v2.get_nexusvm_binding(npb21.vlan, "dummyInstance") + nexus_db_v2.get_nexusvm_bindings(npb21.vlan, "dummyInstance")[0] def test_nexusportvlanswitchbinding_get(self): """Tests get of port bindings based on port, vlan, and switch.""" -- 2.45.2