From a8c57f453f5a2cc9bc7a4feb7c2f82ef3500a3a3 Mon Sep 17 00:00:00 2001 From: Riccardo Pittau Date: Mon, 2 Aug 2021 16:14:33 +0200 Subject: [PATCH 1/2] Increase version of hacking and pycodestyle Fix code and tests Change-Id: Id6a4ac7293a6108a353d6ec639fc070e4e6562a8 (cherry picked from commit 1ab2f8d7422cfcc78d152273984dd7ff3e1f17d2) --- .../generic_switch_mech.py | 18 +++++------ .../tests/unit/netmiko/test_dell.py | 4 +-- .../tests/unit/netmiko/test_juniper.py | 32 +++++++++---------- .../tests/unit/netmiko/test_netmiko_base.py | 4 +-- .../tests/unit/test_devices.py | 2 +- .../tests/unit/test_generic_switch_mech.py | 16 +++++----- tox.ini | 4 +-- 7 files changed, 39 insertions(+), 41 deletions(-) diff --git a/networking_generic_switch/generic_switch_mech.py b/networking_generic_switch/generic_switch_mech.py index 4c0c257d..03258c91 100644 --- a/networking_generic_switch/generic_switch_mech.py +++ b/networking_generic_switch/generic_switch_mech.py @@ -458,11 +458,10 @@ def bind_port(self, context): provisioning_blocks.add_provisioning_component( context._plugin_context, port['id'], resources.PORT, GENERIC_SWITCH_ENTITY) - LOG.debug("Putting port {port} on {switch_info} to vlan: " - "{segmentation_id}".format( - port=port_id, - switch_info=switch_info, - segmentation_id=segmentation_id)) + LOG.debug("Putting port %(port_id)s on %(switch_info)s to vlan: " + "%(segmentation_id)s", + {'port_id': port_id, 'switch_info': switch_info, + 'segmentation_id': segmentation_id}) # Move port to network switch.plug_port_to_network(port_id, segmentation_id) LOG.info("Successfully bound port %(port_id)s in segment " @@ -523,11 +522,10 @@ def _unplug_port_from_network(self, port, network): port_id = local_link_information[0].get('port_id') # If segmentation ID is None, set vlan 1 segmentation_id = network.get('provider:segmentation_id') or 1 - LOG.debug("Unplugging port {port} on {switch_info} from vlan: " - "{segmentation_id}".format( - port=port_id, - switch_info=switch_info, - segmentation_id=segmentation_id)) + LOG.debug("Unplugging port %(port)s on %(switch_info)s from vlan: " + "%(segmentation_id)s", + {'port': port_id, 'switch_info': switch_info, + 'segmentation_id': segmentation_id}) try: switch.delete_port(port_id, segmentation_id) except Exception as e: diff --git a/networking_generic_switch/tests/unit/netmiko/test_dell.py b/networking_generic_switch/tests/unit/netmiko/test_dell.py index 7f1e8d21..81f47010 100644 --- a/networking_generic_switch/tests/unit/netmiko/test_dell.py +++ b/networking_generic_switch/tests/unit/netmiko/test_dell.py @@ -256,8 +256,8 @@ def test_check_output(self): def _test_check_output_error(self, output): msg = ("Found invalid configuration in device response. Operation: " "fake op. Output: %s" % output) - self.assertRaisesRegexp(exc.GenericSwitchNetmikoConfigError, msg, - self.switch.check_output, output, 'fake op') + self.assertRaisesRegex(exc.GenericSwitchNetmikoConfigError, msg, + self.switch.check_output, output, 'fake op') def test_check_output_incomplete_command(self): output = """ diff --git a/networking_generic_switch/tests/unit/netmiko/test_juniper.py b/networking_generic_switch/tests/unit/netmiko/test_juniper.py index 0e1b36e6..4a6d6653 100644 --- a/networking_generic_switch/tests/unit/netmiko/test_juniper.py +++ b/networking_generic_switch/tests/unit/netmiko/test_juniper.py @@ -166,10 +166,10 @@ def test_save_configuration_db_locked_timeout(self, m_stop, m_wait): mock_connection.commit.side_effect = ValueError( "Commit failed with the following errors:\n\n{0}".format(output)) - self.assertRaisesRegexp(exc.GenericSwitchNetmikoConfigError, - "Reached timeout waiting for", - self.switch.save_configuration, - mock_connection) + self.assertRaisesRegex(exc.GenericSwitchNetmikoConfigError, + "Reached timeout waiting for", + self.switch.save_configuration, + mock_connection) self.assertGreater(mock_connection.commit.call_count, 1) m_stop.assert_called_once_with(60) m_wait.assert_called_once_with(5) @@ -214,10 +214,10 @@ def test_save_configuration_warn_already_exists_timeout( mock_connection.commit.side_effect = ValueError( "Commit failed with the following errors:\n\n{0}".format(output)) - self.assertRaisesRegexp(exc.GenericSwitchNetmikoConfigError, - "Reached timeout while attempting", - self.switch.save_configuration, - mock_connection) + self.assertRaisesRegex(exc.GenericSwitchNetmikoConfigError, + "Reached timeout while attempting", + self.switch.save_configuration, + mock_connection) self.assertGreater(mock_connection.commit.call_count, 1) m_stop.assert_called_once_with(60) m_wait.assert_called_once_with(5) @@ -262,10 +262,10 @@ def test_save_configuration_warn_does_not_exist_timeout( mock_connection.commit.side_effect = ValueError( "Commit failed with the following errors:\n\n{0}".format(output)) - self.assertRaisesRegexp(exc.GenericSwitchNetmikoConfigError, - "Reached timeout while attempting", - self.switch.save_configuration, - mock_connection) + self.assertRaisesRegex(exc.GenericSwitchNetmikoConfigError, + "Reached timeout while attempting", + self.switch.save_configuration, + mock_connection) self.assertGreater(mock_connection.commit.call_count, 1) m_stop.assert_called_once_with(60) m_wait.assert_called_once_with(5) @@ -284,10 +284,10 @@ def test_save_configuration_error(self): mock_connection.commit.side_effect = ValueError( "Commit failed with the following errors:\n\n{0}".format(output)) - self.assertRaisesRegexp(exc.GenericSwitchNetmikoConfigError, - "Failed to commit configuration", - self.switch.save_configuration, - mock_connection) + self.assertRaisesRegex(exc.GenericSwitchNetmikoConfigError, + "Failed to commit configuration", + self.switch.save_configuration, + mock_connection) mock_connection.commit.assert_called_once_with() @mock.patch.object(netmiko_devices.tenacity, 'wait_fixed', diff --git a/networking_generic_switch/tests/unit/netmiko/test_netmiko_base.py b/networking_generic_switch/tests/unit/netmiko/test_netmiko_base.py index 33aa281c..4a740989 100644 --- a/networking_generic_switch/tests/unit/netmiko/test_netmiko_base.py +++ b/networking_generic_switch/tests/unit/netmiko/test_netmiko_base.py @@ -382,5 +382,5 @@ def test_check_output_error(self): """ msg = ("Found invalid configuration in device response. Operation: " "fake op. Output: %s" % output) - self.assertRaisesRegexp(exc.GenericSwitchNetmikoConfigError, msg, - self.switch.check_output, output, 'fake op') + self.assertRaisesRegex(exc.GenericSwitchNetmikoConfigError, msg, + self.switch.check_output, output, 'fake op') diff --git a/networking_generic_switch/tests/unit/test_devices.py b/networking_generic_switch/tests/unit/test_devices.py index 23cece7d..57a22b72 100644 --- a/networking_generic_switch/tests/unit/test_devices.py +++ b/networking_generic_switch/tests/unit/test_devices.py @@ -156,7 +156,7 @@ def test__validate_network_name_format(self): def test__validate_network_name_format_failure(self): device_cfg = {'ngs_network_name_format': '{invalid}'} - self.assertRaisesRegexp( + self.assertRaisesRegex( exc.GenericSwitchNetworkNameFormatInvalid, r"Invalid value for 'ngs_network_name_format'", FakeDevice, device_cfg) diff --git a/networking_generic_switch/tests/unit/test_generic_switch_mech.py b/networking_generic_switch/tests/unit/test_generic_switch_mech.py index 50901f0b..834c9ea9 100644 --- a/networking_generic_switch/tests/unit/test_generic_switch_mech.py +++ b/networking_generic_switch/tests/unit/test_generic_switch_mech.py @@ -104,8 +104,8 @@ def test_create_network_postcommit_failure(self, m_log, m_list): 'provider:segmentation_id': 22, 'provider:physical_network': 'physnet1'} - self.assertRaisesRegexp(ValueError, "boom", - driver.create_network_postcommit, mock_context) + self.assertRaisesRegex(ValueError, "boom", + driver.create_network_postcommit, mock_context) self.switch_mock.add_network.assert_called_once_with(22, 22) self.assertEqual(1, m_log.error.call_count) self.assertIn('Failed to create network', m_log.error.call_args[0][0]) @@ -126,8 +126,8 @@ def test_create_network_postcommit_failure_multiple(self, m_log, m_list): 'provider:segmentation_id': 22, 'provider:physical_network': 'physnet1'} - self.assertRaisesRegexp(ValueError, "boom", - driver.create_network_postcommit, mock_context) + self.assertRaisesRegex(ValueError, "boom", + driver.create_network_postcommit, mock_context) self.switch_mock.add_network.assert_called_once_with(22, 22) self.assertEqual(1, m_log.error.call_count) self.assertIn('Failed to create network', m_log.error.call_args[0][0]) @@ -196,8 +196,8 @@ def test_delete_network_postcommit_failure(self, m_log, m_list): 'provider:segmentation_id': 22, 'provider:physical_network': 'physnet1'} - self.assertRaisesRegexp(ValueError, "boom", - driver.delete_network_postcommit, mock_context) + self.assertRaisesRegex(ValueError, "boom", + driver.delete_network_postcommit, mock_context) self.switch_mock.del_network.assert_called_once_with(22, 22) self.assertEqual(1, m_log.error.call_count) self.assertIn('Failed to delete network', m_log.error.call_args[0][0]) @@ -218,8 +218,8 @@ def test_delete_network_postcommit_failure_multiple(self, m_log, m_list): 'provider:segmentation_id': 22, 'provider:physical_network': 'physnet1'} - self.assertRaisesRegexp(ValueError, "boom", - driver.delete_network_postcommit, mock_context) + self.assertRaisesRegex(ValueError, "boom", + driver.delete_network_postcommit, mock_context) self.switch_mock.del_network.assert_called_with(22, 22) self.assertEqual(2, self.switch_mock.del_network.call_count) self.assertEqual(2, m_log.error.call_count) diff --git a/tox.ini b/tox.ini index a4607984..ffb8aa22 100644 --- a/tox.ini +++ b/tox.ini @@ -23,10 +23,10 @@ commands = [testenv:pep8] deps = - hacking>=3.1.0,<4.0.0 # Apache-2.0 + hacking>=4.1.0,<5.0.0 # Apache-2.0 flake8-import-order==0.17.1 # LGPLv3 bashate>=0.5.1 # Apache-2.0 - pycodestyle>=2.0.0,<2.7.0 # MIT + pycodestyle>=2.0.0,<3.0.0 # MIT doc8>=0.6.0 # Apache-2.0 whitelist_externals = bash commands = From e631a01825ac00e70052be636c72986d111730f5 Mon Sep 17 00:00:00 2001 From: James Denton Date: Mon, 15 Jun 2020 22:10:26 +0000 Subject: [PATCH 2/2] Support multiple links in link_local_information dict of portgroups This patch applies configuration to all links present in the local_link_information dictionary and not only to the first one. With these changes, bonding modes not requiring special switch configurations should be supported, including: Mode 1: Active/Backup Mode 5: Transmit Load Balancing (TLB) Mode 6: Adaptive Load Balancing (ALB) Change-Id: Ic3e10d19315b776662188f41c552fe0676a12782 Closes-Bug: #1759000 (cherry picked from commit a6d14ab12d217ecdc210a29d9ab50f9946882907) --- .../generic_switch_mech.py | 166 +++++++++++------- .../tests/unit/test_generic_switch_mech.py | 148 +++++++++++++++- ...-links-in-port-group-59a11c2c2da73065.yaml | 9 + 3 files changed, 261 insertions(+), 62 deletions(-) create mode 100644 releasenotes/notes/support-multiple-links-in-port-group-59a11c2c2da73065.yaml diff --git a/networking_generic_switch/generic_switch_mech.py b/networking_generic_switch/generic_switch_mech.py index 03258c91..a5e153d9 100644 --- a/networking_generic_switch/generic_switch_mech.py +++ b/networking_generic_switch/generic_switch_mech.py @@ -343,13 +343,14 @@ def update_port_postcommit(self, context): 'local_link_information') if not local_link_information: return - switch_info = local_link_information[0].get('switch_info') - switch_id = local_link_information[0].get('switch_id') - switch = device_utils.get_switch_device( - self.switches, switch_info=switch_info, - ngs_mac_address=switch_id) - if not switch: - return + for link in local_link_information: + switch_info = link.get('switch_info') + switch_id = link.get('switch_id') + switch = device_utils.get_switch_device( + self.switches, switch_info=switch_info, + ngs_mac_address=switch_id) + if not switch: + return provisioning_blocks.provisioning_complete( context._plugin_context, port['id'], resources.PORT, GENERIC_SWITCH_ENTITY) @@ -432,44 +433,86 @@ def bind_port(self, context): """ port = context.current + network = context.network.current binding_profile = port['binding:profile'] local_link_information = binding_profile.get('local_link_information') + if self._is_port_supported(port) and local_link_information: - switch_info = local_link_information[0].get('switch_info') - switch_id = local_link_information[0].get('switch_id') + # NOTE(jamesdenton): If any link of the port is invalid, none + # of the links should be processed. + if not self._is_link_valid(port, network): + return + else: + for link in local_link_information: + port_id = link.get('port_id') + switch_info = link.get('switch_info') + switch_id = link.get('switch_id') + switch = device_utils.get_switch_device( + self.switches, switch_info=switch_info, + ngs_mac_address=switch_id) + + segments = context.segments_to_bind + # If segmentation ID is None, set vlan 1 + segmentation_id = segments[0].get('segmentation_id') or 1 + LOG.debug("Putting port %(port_id)s on %(switch_info)s " + "to vlan: %(segmentation_id)s", + {'port_id': port_id, 'switch_info': switch_info, + 'segmentation_id': segmentation_id}) + # Move port to network + switch.plug_port_to_network(port_id, segmentation_id) + LOG.info("Successfully bound port %(port_id)s in segment " + "%(segment_id)s on device %(device)s", + {'port_id': port['id'], 'device': switch_info, + 'segment_id': segmentation_id}) + + context.set_binding(segments[0][api.ID], + portbindings.VIF_TYPE_OTHER, {}) + provisioning_blocks.add_provisioning_component( + context._plugin_context, port['id'], resources.PORT, + GENERIC_SWITCH_ENTITY) + + def _is_link_valid(self, port, network): + """Return whether a link references valid switch and physnet. + + If the local link information refers to a switch that is not + known to NGS or the switch is not associated with the respective + physnet, the port will not be processed and no exception will + be raised. + + :param port: The port to check + :param network: the network mapped to physnet + :returns: Whether the link refers to a configured switch and/or switch + is associated with physnet + """ + + binding_profile = port['binding:profile'] + local_link_information = binding_profile.get('local_link_information') + + for link in local_link_information: + switch_info = link.get('switch_info') + switch_id = link.get('switch_id') switch = device_utils.get_switch_device( self.switches, switch_info=switch_info, ngs_mac_address=switch_id) if not switch: - return - network = context.network.current + LOG.error("Cannot bind port %(port)s as device %(device)s " + "is not configured. Check baremetal port link " + "configuration.", + {'port': port['id'], + 'device': switch_info}) + return False + physnet = network['provider:physical_network'] switch_physnets = switch._get_physical_networks() + if switch_physnets and physnet not in switch_physnets: - LOG.error("Cannot bind port %(port)s as device %(device)s is " - "not on physical network %(physnet)", - {'port_id': port['id'], 'device': switch_info, + LOG.error("Cannot bind port %(port)s as device %(device)s " + "is not on physical network %(physnet)s. Check " + "baremetal port link configuration.", + {'port': port['id'], 'device': switch_info, 'physnet': physnet}) - return - port_id = local_link_information[0].get('port_id') - segments = context.segments_to_bind - # If segmentation ID is None, set vlan 1 - segmentation_id = segments[0].get('segmentation_id') or 1 - provisioning_blocks.add_provisioning_component( - context._plugin_context, port['id'], resources.PORT, - GENERIC_SWITCH_ENTITY) - LOG.debug("Putting port %(port_id)s on %(switch_info)s to vlan: " - "%(segmentation_id)s", - {'port_id': port_id, 'switch_info': switch_info, - 'segmentation_id': segmentation_id}) - # Move port to network - switch.plug_port_to_network(port_id, segmentation_id) - LOG.info("Successfully bound port %(port_id)s in segment " - "%(segment_id)s on device %(device)s", - {'port_id': port['id'], 'device': switch_info, - 'segment_id': segmentation_id}) - context.set_binding(segments[0][api.ID], - portbindings.VIF_TYPE_OTHER, {}) + return False + return True @staticmethod def _is_port_supported(port): @@ -512,33 +555,34 @@ def _unplug_port_from_network(self, port, network): local_link_information = binding_profile.get('local_link_information') if not local_link_information: return - switch_info = local_link_information[0].get('switch_info') - switch_id = local_link_information[0].get('switch_id') - switch = device_utils.get_switch_device( - self.switches, switch_info=switch_info, - ngs_mac_address=switch_id) - if not switch: - return - port_id = local_link_information[0].get('port_id') - # If segmentation ID is None, set vlan 1 - segmentation_id = network.get('provider:segmentation_id') or 1 - LOG.debug("Unplugging port %(port)s on %(switch_info)s from vlan: " - "%(segmentation_id)s", - {'port': port_id, 'switch_info': switch_info, - 'segmentation_id': segmentation_id}) - try: - switch.delete_port(port_id, segmentation_id) - except Exception as e: - LOG.error("Failed to unplug port %(port_id)s " - "on device: %(switch)s from network %(net_id)s " - "reason: %(exc)s", - {'port_id': port['id'], 'net_id': network['id'], - 'switch': switch_info, 'exc': e}) - raise e - LOG.info('Port %(port_id)s has been unplugged from network ' - '%(net_id)s on device %(device)s', - {'port_id': port['id'], 'net_id': network['id'], - 'device': switch_info}) + for link in local_link_information: + switch_info = link.get('switch_info') + switch_id = link.get('switch_id') + switch = device_utils.get_switch_device( + self.switches, switch_info=switch_info, + ngs_mac_address=switch_id) + if not switch: + continue + port_id = link.get('port_id') + # If segmentation ID is None, set vlan 1 + segmentation_id = network.get('provider:segmentation_id') or 1 + LOG.debug("Unplugging port %(port)s on %(switch_info)s from vlan: " + "%(segmentation_id)s", + {'port': port_id, 'switch_info': switch_info, + 'segmentation_id': segmentation_id}) + try: + switch.delete_port(port_id, segmentation_id) + except Exception as e: + LOG.error("Failed to unplug port %(port_id)s " + "on device: %(switch)s from network %(net_id)s " + "reason: %(exc)s", + {'port_id': port['id'], 'net_id': network['id'], + 'switch': switch_info, 'exc': e}) + raise e + LOG.info('Port %(port_id)s has been unplugged from network ' + '%(net_id)s on device %(device)s', + {'port_id': port['id'], 'net_id': network['id'], + 'device': switch_info}) def _get_devices_by_physnet(self, physnet): """Generator yielding switches on a particular physical network. diff --git a/networking_generic_switch/tests/unit/test_generic_switch_mech.py b/networking_generic_switch/tests/unit/test_generic_switch_mech.py index 834c9ea9..549b09f0 100644 --- a/networking_generic_switch/tests/unit/test_generic_switch_mech.py +++ b/networking_generic_switch/tests/unit/test_generic_switch_mech.py @@ -250,6 +250,36 @@ def test_delete_port_postcommit(self, m_list): self.switch_mock.delete_port.assert_called_once_with( '2222', 123) + def test_delete_portgroup_postcommit(self, m_list): + driver = gsm.GenericSwitchDriver() + driver.initialize() + mock_context = mock.create_autospec(driver_context.PortContext) + mock_context.current = {'binding:profile': + {'local_link_information': + [ + { + 'switch_info': 'foo', + 'port_id': 2222 + }, + { + 'switch_info': 'foo', + 'port_id': 3333 + }, + ] + }, + 'binding:vnic_type': 'baremetal', + 'binding:vif_type': 'other', + 'id': 'aaaa-bbbb-cccc'} + mock_context.network = mock.Mock() + mock_context.network.current = {'provider:segmentation_id': 123, + 'id': 'aaaa-bbbb-cccc'} + mock_context.segments_to_bind = [mock_context.network.current] + + driver.delete_port_postcommit(mock_context) + self.switch_mock.delete_port.assert_has_calls( + [mock.call(2222, 123), + mock.call(3333, 123)]) + def test_delete_port_postcommit_failure(self, m_list): driver = gsm.GenericSwitchDriver() driver.initialize() @@ -262,7 +292,7 @@ def test_delete_port_postcommit_failure(self, m_list): [ { 'switch_info': 'foo', - 'port_id': '2222' + 'port_id': 2222 } ] }, @@ -448,6 +478,41 @@ def test_update_port_postcommit_complete_provisioning(self, m_pc, m_list): resources.PORT, 'GENERICSWITCH') + @mock.patch.object(provisioning_blocks, 'provisioning_complete') + def test_update_portgroup_postcommit_complete_provisioning(self, + m_pc, + m_list): + driver = gsm.GenericSwitchDriver() + driver.initialize() + mock_context = mock.create_autospec(driver_context.PortContext) + mock_context._plugin_context = mock.MagicMock() + mock_context.current = {'binding:profile': + {'local_link_information': + [ + { + 'switch_info': 'foo', + 'port_id': 2222 + }, + { + 'switch_info': 'foo', + 'port_id': 3333 + }, + ] + }, + 'binding:vnic_type': 'baremetal', + 'id': '123', + 'binding:vif_type': 'other'} + mock_context.original = {'binding:profile': {}, + 'binding:vnic_type': 'baremetal', + 'id': '123', + 'binding:vif_type': 'unbound'} + driver.update_port_postcommit(mock_context) + self.switch_mock.plug_port_to_network.assert_not_called() + m_pc.assert_has_calls([mock.call(mock_context._plugin_context, + mock_context.current['id'], + resources.PORT, + 'GENERICSWITCH')]) + @mock.patch.object(provisioning_blocks, 'provisioning_complete') def test_update_port_postcommit_unbind_not_bound(self, m_pc, m_list): driver = gsm.GenericSwitchDriver() @@ -572,6 +637,43 @@ def test_update_port_postcommit_unbind(self, m_pc, m_list): self.switch_mock.delete_port.assert_called_once_with(2222, 123) m_pc.assert_not_called() + @mock.patch.object(provisioning_blocks, 'provisioning_complete') + def test_update_portgroup_postcommit_unbind(self, m_pc, m_list): + driver = gsm.GenericSwitchDriver() + driver.initialize() + mock_context = mock.create_autospec(driver_context.PortContext) + mock_context._plugin_context = mock.MagicMock() + mock_context.current = {'binding:profile': {}, + 'binding:vnic_type': 'baremetal', + 'id': '123', + 'binding:vif_type': 'unbound'} + mock_context.original = {'binding:profile': + {'local_link_information': + [ + { + 'switch_info': 'foo', + 'port_id': 2222 + }, + { + 'switch_info': 'foo', + 'port_id': 3333 + }, + ] + }, + 'binding:vnic_type': 'baremetal', + 'id': '123', + 'binding:vif_type': 'other'} + mock_context.network = mock.Mock() + mock_context.network.current = {'provider:segmentation_id': 123, + 'id': 'aaaa-bbbb-cccc'} + mock_context.segments_to_bind = [mock_context.network.current] + + driver.update_port_postcommit(mock_context) + self.switch_mock.delete_port.assert_has_calls( + [mock.call(2222, 123), + mock.call(3333, 123)]) + m_pc.assert_not_called() + @mock.patch.object(provisioning_blocks, 'add_provisioning_component') def test_bind_port(self, m_apc, m_list): driver = gsm.GenericSwitchDriver() @@ -608,6 +710,50 @@ def test_bind_port(self, m_apc, m_list): resources.PORT, 'GENERICSWITCH') + @mock.patch.object(provisioning_blocks, 'add_provisioning_component') + def test_bind_portgroup(self, m_apc, m_list): + driver = gsm.GenericSwitchDriver() + driver.initialize() + mock_context = mock.create_autospec(driver_context.PortContext) + mock_context._plugin_context = mock.MagicMock() + mock_context.current = {'binding:profile': + {'local_link_information': + [ + { + 'switch_info': 'foo', + 'port_id': 2222 + }, + { + 'switch_info': 'foo', + 'port_id': 3333 + }, + ] + }, + 'binding:vnic_type': 'baremetal', + 'id': '123'} + mock_context.network.current = { + 'provider:physical_network': 'physnet1' + } + mock_context.segments_to_bind = [ + { + 'segmentation_id': None, + 'id': 123 + } + ] + + driver.bind_port(mock_context) + self.switch_mock.plug_port_to_network.assert_has_calls( + [mock.call(2222, 1), + mock.call(3333, 1)] + ) + mock_context.set_binding.assert_has_calls( + [mock.call(123, 'other', {})] + ) + m_apc.assert_has_calls([mock.call(mock_context._plugin_context, + mock_context.current['id'], + resources.PORT, + 'GENERICSWITCH')]) + @mock.patch.object(provisioning_blocks, 'add_provisioning_component') def test_bind_port_with_physnet(self, m_apc, m_list): driver = gsm.GenericSwitchDriver() diff --git a/releasenotes/notes/support-multiple-links-in-port-group-59a11c2c2da73065.yaml b/releasenotes/notes/support-multiple-links-in-port-group-59a11c2c2da73065.yaml new file mode 100644 index 00000000..0285c3e1 --- /dev/null +++ b/releasenotes/notes/support-multiple-links-in-port-group-59a11c2c2da73065.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + NGS now attempts to configure the respective switchports of all links + of a port group. As a result, bonding modes not requiring special switch + configurations (e.g. active-passive, ALB, TLB) should be supported. MLAG + may be supported using pre-defined port-channel interfaces. See + `NGS Bug 1759000 `__ + for details.