diff --git a/networking_generic_switch/devices/__init__.py b/networking_generic_switch/devices/__init__.py index 4eda1edb..3eee2918 100644 --- a/networking_generic_switch/devices/__init__.py +++ b/networking_generic_switch/devices/__init__.py @@ -46,6 +46,7 @@ {'name': 'ngs_network_name_format', 'default': '{network_id}'}, # If false, ngs will not add and delete VLANs from switches {'name': 'ngs_manage_vlans', 'default': True}, + {'name': 'vlan_translation_supported', 'default': False}, # If False, ngs will skip saving configuration on devices {'name': 'ngs_save_configuration', 'default': True}, # When true try to batch up in flight switch requests @@ -174,6 +175,10 @@ def add_network(self, segmentation_id, network_id): def del_network(self, segmentation_id, network_id): pass + def plug_port_to_network_trunk(self, port_id, segmentation_id, + trunk_details=None, vtr=False): + pass + @abc.abstractmethod def plug_port_to_network(self, port_id, segmentation_id): pass diff --git a/networking_generic_switch/devices/netmiko_devices/__init__.py b/networking_generic_switch/devices/netmiko_devices/__init__.py index 7f0e2c53..b50a6245 100644 --- a/networking_generic_switch/devices/netmiko_devices/__init__.py +++ b/networking_generic_switch/devices/netmiko_devices/__init__.py @@ -90,6 +90,10 @@ class NetmikoSwitch(devices.GenericSwitchDevice): SAVE_CONFIGURATION = None + SET_NATIVE_VLAN = None + + ALLOW_NETWORK_ON_TRUNK = None + ERROR_MSG_PATTERNS = () """Sequence of error message patterns. @@ -276,6 +280,28 @@ def del_network(self, segmentation_id, network_id): network_name=network_name) return self.send_commands_to_device(cmds) + @check_output('plug port trunk') + def plug_port_to_network_trunk(self, port, segmentation_id, + trunk_details=None, vtr=False): + cmd_set = [] + vts = self.ngs_config.get('vlan_translation_supported', False) + # NOTE(vsaienko) Always use vlan translation if it is supported. + if vts: + cmd_set.extend(self.get_trunk_port_cmds_vlan_translation( + port, segmentation_id, trunk_details)) + else: + if vtr: + msg = ("Cannot bind_port VLAN aware port as switch %s " + "doesn't support VLAN translation. " + "But it is required.") % self.config['ip'] + raise exc.GenericSwitchNotSupported(error=msg) + else: + cmd_set.extend( + self.get_trunk_port_cmds_no_vlan_translation( + port, segmentation_id, trunk_details)) + + self.send_commands_to_device(cmd_set) + @check_output('plug port') def plug_port_to_network(self, port, segmentation_id): cmds = [] @@ -409,3 +435,22 @@ def check_output(self, output, operation): raise exc.GenericSwitchNetmikoConfigError( config=device_utils.sanitise_config(self.config), error=msg) + + def get_trunk_port_cmds_no_vlan_translation(self, port_id, + segmentation_id, + trunk_details): + cmd_set = [] + cmd_set.extend( + self._format_commands(self.SET_NATIVE_VLAN, + port=port_id, + segmentation_id=segmentation_id)) + for sub_port in trunk_details.get('sub_ports'): + cmd_set.extend( + self._format_commands( + self.ALLOW_NETWORK_ON_TRUNK, port=port_id, + segmentation_id=sub_port['segmentation_id'])) + return cmd_set + + def get_trunk_port_cmds_vlan_translation(self, port_id, segmentation_id, + trunk_details): + pass diff --git a/networking_generic_switch/devices/netmiko_devices/arista.py b/networking_generic_switch/devices/netmiko_devices/arista.py index a9500ac9..c32e338b 100644 --- a/networking_generic_switch/devices/netmiko_devices/arista.py +++ b/networking_generic_switch/devices/netmiko_devices/arista.py @@ -37,3 +37,15 @@ class AristaEos(netmiko_devices.NetmikoSwitch): 'no switchport mode trunk', 'switchport trunk allowed vlan none' ) + + SET_NATIVE_VLAN = ( + 'interface {port}', + 'switchport mode trunk', + 'switchport trunk native vlan {segmentation_id}', + 'switchport trunk allowed vlan add {segmentation_id}' + ) + + ALLOW_NETWORK_ON_TRUNK = ( + 'interface {port}', + 'switchport trunk allowed vlan add {segmentation_id}' + ) diff --git a/networking_generic_switch/devices/netmiko_devices/cisco.py b/networking_generic_switch/devices/netmiko_devices/cisco.py index 4b919763..1587c6cd 100644 --- a/networking_generic_switch/devices/netmiko_devices/cisco.py +++ b/networking_generic_switch/devices/netmiko_devices/cisco.py @@ -39,6 +39,18 @@ class CiscoIos(netmiko_devices.NetmikoSwitch): 'switchport trunk allowed vlan none' ) + SET_NATIVE_VLAN = ( + 'interface {port}', + 'switchport mode trunk', + 'switchport trunk native vlan {segmentation_id}', + 'switchport trunk allowed vlan add {segmentation_id}' + ) + + ALLOW_NETWORK_ON_TRUNK = ( + 'interface {port}', + 'switchport trunk allowed vlan add {segmentation_id}' + ) + class CiscoNxOS(netmiko_devices.NetmikoSwitch): """Netmiko device driver for Cisco Nexus switches running NX-OS.""" diff --git a/networking_generic_switch/devices/netmiko_devices/dell.py b/networking_generic_switch/devices/netmiko_devices/dell.py index 6f197918..2ce89323 100644 --- a/networking_generic_switch/devices/netmiko_devices/dell.py +++ b/networking_generic_switch/devices/netmiko_devices/dell.py @@ -70,6 +70,19 @@ class DellOS10(netmiko_devices.NetmikoSwitch): "exit", ) + SET_NATIVE_VLAN = ( + 'interface {port}', + # Clean all the old trunked vlans by switching to access mode first + 'switchport mode access', + 'switchport mode trunk', + 'switchport access vlan {segmentation_id}', + ) + + ALLOW_NETWORK_ON_TRUNK = ( + 'interface {port}', + 'switchport trunk allowed vlan {segmentation_id}' + ) + ERROR_MSG_PATTERNS = () """Sequence of error message patterns. diff --git a/networking_generic_switch/exceptions.py b/networking_generic_switch/exceptions.py index 96b57e44..12d8c5f2 100644 --- a/networking_generic_switch/exceptions.py +++ b/networking_generic_switch/exceptions.py @@ -53,3 +53,8 @@ class GenericSwitchNetmikoConfigError(GenericSwitchException): class GenericSwitchBatchError(GenericSwitchException): message = _("Batching error: %(device)s, error: %(error)s") + + +class GenericSwitchNotSupported(GenericSwitchException): + message = _("Requested feature is not supported by " + "networking-generic-switch. %(error)s") diff --git a/networking_generic_switch/generic_switch_mech.py b/networking_generic_switch/generic_switch_mech.py index 237ac99c..46945f9a 100644 --- a/networking_generic_switch/generic_switch_mech.py +++ b/networking_generic_switch/generic_switch_mech.py @@ -23,6 +23,7 @@ from networking_generic_switch import config as gsw_conf from networking_generic_switch import devices from networking_generic_switch.devices import utils as device_utils +from networking_generic_switch import exceptions as ngs_exc LOG = logging.getLogger(__name__) @@ -393,6 +394,14 @@ def delete_port_postcommit(self, context): if self._is_port_bound(port): self._unplug_port_from_network(port, context.network.current) + def _is_vlan_translation_required(self, trunk_details): + """Check if vlan translation is required to configure specific trunk. + + :returns: True if vlan translation is required, False otherwise. + """ + # FIXME: removed for simplicity + return False + def bind_port(self, context): """Attempt to bind a port. @@ -445,7 +454,6 @@ def bind_port(self, context): # of the links should be processed. if not self._is_link_valid(port, network): return - is_802_3ad = self._is_802_3ad(port) for link in local_link_information: port_id = link.get('port_id') @@ -458,15 +466,39 @@ def bind_port(self, context): segments = context.segments_to_bind # If segmentation ID is None, set vlan 1 segmentation_id = segments[0].get('segmentation_id') or 1 + trunk_details = port.get('trunk_details', {}) 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 - if is_802_3ad and hasattr(switch, 'plug_bond_to_network'): - switch.plug_bond_to_network(port_id, segmentation_id) - else: - switch.plug_port_to_network(port_id, segmentation_id) + # START + try: + if trunk_details: + vtr = self._is_vlan_translation_required(trunk_details) + switch.plug_port_to_network_trunk( + port_id, segmentation_id, trunk_details, vtr) + elif (is_802_3ad + and hasattr(switch, 'plug_bond_to_network')): + switch.plug_bond_to_network(port_id, segmentation_id) + else: + switch.plug_port_to_network( + port_id, segmentation_id) + except ngs_exc.GenericSwitchNotSupported as e: + LOG.warning("Operation is not supported by " + "networking-generic-switch. %(err)s)", + {'err': e}) + raise e + except Exception as e: + LOG.error("Failed to bind port %(port_id)s in " + "segment %(segment_id)s on device " + "%(device)s due to error %(err)s", + {'port_id': port['id'], + 'device': switch_info, + 'segment_id': segmentation_id, + 'err': e}) + raise e + # END LOG.info("Successfully bound port %(port_id)s in segment " "%(segment_id)s on device %(device)s", {'port_id': port['id'], 'device': switch_info, diff --git a/networking_generic_switch/tests/unit/netmiko/test_arista_eos.py b/networking_generic_switch/tests/unit/netmiko/test_arista_eos.py index 69984376..5e17a360 100644 --- a/networking_generic_switch/tests/unit/netmiko/test_arista_eos.py +++ b/networking_generic_switch/tests/unit/netmiko/test_arista_eos.py @@ -14,6 +14,8 @@ from unittest import mock +from neutron.plugins.ml2 import driver_context + from networking_generic_switch.devices.netmiko_devices import arista from networking_generic_switch.tests.unit.netmiko import test_netmiko_base @@ -57,6 +59,43 @@ def test_delete_port(self, mock_exec): 'no switchport mode trunk', 'switchport trunk allowed vlan none']) + def test_get_trunk_port_cmds_no_vlan_translation(self): + mock_context = mock.create_autospec(driver_context.PortContext) + self.switch.ngs_config['vlan_translation_supported'] = False + trunk_details = {'trunk_id': 'aaa-bbb-ccc-ddd', + 'sub_ports': [{'segmentation_id': 130, + 'port_id': 'aaa-bbb-ccc-ddd', + 'segmentation_type': 'vlan', + 'mac_address': u'fa:16:3e:1c:c2:7e'}]} + mock_context.current = {'binding:profile': + {'local_link_information': + [ + { + 'switch_info': 'foo', + 'port_id': '2222' + } + ] + }, + 'binding:vnic_type': 'baremetal', + 'id': 'aaaa-bbbb-cccc', + 'trunk_details': trunk_details} + mock_context.network = mock.Mock() + mock_context.network.current = {'provider:segmentation_id': 123} + mock_context.segments_to_bind = [ + { + 'segmentation_id': 777, + 'id': 123 + } + ] + res = self.switch.get_trunk_port_cmds_no_vlan_translation( + '2222', 777, trunk_details) + self.assertEqual(['interface 2222', 'switchport mode trunk', + 'switchport trunk native vlan 777', + 'switchport trunk allowed vlan add 777', + 'interface 2222', + 'switchport trunk allowed vlan add 130'], + res) + def test__format_commands(self): cmd_set = self.switch._format_commands( arista.AristaEos.ADD_NETWORK, diff --git a/networking_generic_switch/tests/unit/netmiko/test_cisco_ios.py b/networking_generic_switch/tests/unit/netmiko/test_cisco_ios.py index 22705b66..0ffdb42f 100644 --- a/networking_generic_switch/tests/unit/netmiko/test_cisco_ios.py +++ b/networking_generic_switch/tests/unit/netmiko/test_cisco_ios.py @@ -14,6 +14,8 @@ from unittest import mock +from neutron.plugins.ml2 import driver_context + from networking_generic_switch.devices.netmiko_devices import cisco from networking_generic_switch.tests.unit.netmiko import test_netmiko_base @@ -56,6 +58,43 @@ def test_delete_port(self, mock_exec): ['interface 3333', 'no switchport access vlan 33', 'no switchport mode trunk', 'switchport trunk allowed vlan none']) + def test_get_trunk_port_cmds_no_vlan_translation(self): + mock_context = mock.create_autospec(driver_context.PortContext) + self.switch.ngs_config['vlan_translation_supported'] = True + trunk_details = {'trunk_id': 'aaa-bbb-ccc-ddd', + 'sub_ports': [{'segmentation_id': 130, + 'port_id': 'aaa-bbb-ccc-ddd', + 'segmentation_type': 'vlan', + 'mac_address': u'fa:16:3e:1c:c2:7e'}]} + mock_context.current = {'binding:profile': + {'local_link_information': + [ + { + 'switch_info': 'foo', + 'port_id': '2222' + } + ] + }, + 'binding:vnic_type': 'baremetal', + 'id': 'aaaa-bbbb-cccc', + 'trunk_details': trunk_details} + mock_context.network = mock.Mock() + mock_context.network.current = {'provider:segmentation_id': 123} + mock_context.segments_to_bind = [ + { + 'segmentation_id': 777, + 'id': 123 + } + ] + res = self.switch.get_trunk_port_cmds_no_vlan_translation( + '2222', 777, trunk_details) + self.assertEqual(['interface 2222', 'switchport mode trunk', + 'switchport trunk native vlan 777', + 'switchport trunk allowed vlan add 777', + 'interface 2222', + 'switchport trunk allowed vlan add 130'], + res) + def test__format_commands(self): cmd_set = self.switch._format_commands( cisco.CiscoIos.ADD_NETWORK, diff --git a/networking_generic_switch/tests/unit/netmiko/test_dell.py b/networking_generic_switch/tests/unit/netmiko/test_dell.py index 243cdfd9..efb5b6b5 100644 --- a/networking_generic_switch/tests/unit/netmiko/test_dell.py +++ b/networking_generic_switch/tests/unit/netmiko/test_dell.py @@ -14,6 +14,8 @@ from unittest import mock +from neutron.plugins.ml2 import driver_context + from networking_generic_switch.devices.netmiko_devices import dell from networking_generic_switch import exceptions as exc from networking_generic_switch.tests.unit.netmiko import test_netmiko_base @@ -144,7 +146,7 @@ def test_add_network(self, m_exec): @mock.patch('networking_generic_switch.devices.netmiko_devices.' 'NetmikoSwitch.send_commands_to_device') def test_add_network_with_trunk_ports(self, mock_exec): - switch = self._make_switch_device({'ngs_trunk_ports': 'port1, port2'}) + switch = self._make_switch_device({'ngs_trunk_ports': 'port1,port2'}) switch.add_network(33, '0ae071f5-5be9-43e4-80ea-e41fefe85b21') mock_exec.assert_called_with( ['interface vlan 33', @@ -165,7 +167,7 @@ def test_del_network(self, mock_exec): @mock.patch('networking_generic_switch.devices.netmiko_devices.' 'NetmikoSwitch.send_commands_to_device') def test_del_network_with_trunk_ports(self, mock_exec): - switch = self._make_switch_device({'ngs_trunk_ports': 'port1, port2'}) + switch = self._make_switch_device({'ngs_trunk_ports': 'port1,port2'}) switch.del_network(33, '0ae071f55be943e480eae41fefe85b21') mock_exec.assert_called_with( ['interface port1', 'no switchport trunk allowed vlan 33', 'exit', @@ -238,6 +240,43 @@ def test__format_commands(self): 'no switchport trunk allowed vlan 33', 'exit']) + def test_get_trunk_port_cmds_no_vlan_translation(self): + mock_context = mock.create_autospec(driver_context.PortContext) + self.switch.ngs_config['vlan_translation_supported'] = True + trunk_details = {'trunk_id': 'aaa-bbb-ccc-ddd', + 'sub_ports': [{'segmentation_id': 130, + 'port_id': 'aaa-bbb-ccc-ddd', + 'segmentation_type': 'vlan', + 'mac_address': u'fa:16:3e:1c:c2:7e'}]} + mock_context.current = {'binding:profile': + {'local_link_information': + [ + { + 'switch_info': 'foo', + 'port_id': '2222' + } + ] + }, + 'binding:vnic_type': 'baremetal', + 'id': 'aaaa-bbbb-cccc', + 'trunk_details': trunk_details} + mock_context.network = mock.Mock() + mock_context.network.current = {'provider:segmentation_id': 123} + mock_context.segments_to_bind = [ + { + 'segmentation_id': 777, + 'id': 123 + } + ] + res = self.switch.get_trunk_port_cmds_no_vlan_translation( + '2222', 777, trunk_details) + self.assertEqual(['interface 2222', 'switchport mode access', + 'switchport mode trunk', + 'switchport access vlan 777', + 'interface 2222', + 'switchport trunk allowed vlan 130'], + res) + class TestNetmikoDellPowerConnect(test_netmiko_base.NetmikoSwitchTestBase): 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 4d01b9ed..3f11b2d4 100644 --- a/networking_generic_switch/tests/unit/netmiko/test_netmiko_base.py +++ b/networking_generic_switch/tests/unit/netmiko/test_netmiko_base.py @@ -18,6 +18,7 @@ import fixtures import netmiko import netmiko.base_connection +from neutron.plugins.ml2 import driver_context from oslo_config import fixture as config_fixture import paramiko import tenacity @@ -411,3 +412,88 @@ def test_check_output_error(self): "fake op. Output: %s" % output) self.assertRaisesRegex(exc.GenericSwitchNetmikoConfigError, msg, self.switch.check_output, output, 'fake op') + + @mock.patch.object(netmiko_devices.netmiko, 'ConnectHandler') + @mock.patch.object(netmiko_devices.NetmikoSwitch, + 'send_commands_to_device') + @mock.patch.object(netmiko_devices.NetmikoSwitch, + 'get_trunk_port_cmds_no_vlan_translation') + @mock.patch.object(netmiko_devices.NetmikoSwitch, + 'get_trunk_port_cmds_vlan_translation') + def test_bind_port_trunk_no_vts(self, t_mock, nt_mock, sctd_mock, + nm_mock): + mock_context = mock.create_autospec(driver_context.PortContext) + connect_mock = mock.Mock() + nm_mock.return_value = connect_mock + self.switch.ngs_config['vlan_translation_supported'] = False + mock_context.current = {'binding:profile': + {'local_link_information': + [ + { + 'switch_info': 'foo', + 'port_id': '2222' + } + ] + }, + 'binding:vnic_type': 'baremetal', + 'id': 'aaaa-bbbb-cccc', + 'trunk_details': {'sub_ports': + [{'segmentation_id': 123}]}} + trunk_details = {'sub_ports': [{'segmentation_id': 123}]} + self.switch.plug_port_to_network_trunk('2222', None, trunk_details, + vtr=False) + nt_mock.assert_called_once_with( + '2222', None, {'sub_ports': [{'segmentation_id': 123}]}) + self.assertFalse(t_mock.called) + + @mock.patch.object(netmiko_devices.netmiko, 'ConnectHandler') + @mock.patch.object(netmiko_devices.NetmikoSwitch, + 'send_commands_to_device') + @mock.patch.object(netmiko_devices.NetmikoSwitch, + 'get_trunk_port_cmds_no_vlan_translation') + @mock.patch.object(netmiko_devices.NetmikoSwitch, + 'get_trunk_port_cmds_vlan_translation') + def test_bind_port_trunk_vts(self, t_mock, nt_mock, sctd_mock, + nm_mock): + mock_context = mock.create_autospec(driver_context.PortContext) + connect_mock = mock.Mock() + nm_mock.return_value = connect_mock + self.switch.ngs_config['vlan_translation_supported'] = True + mock_context.current = {'binding:profile': + {'local_link_information': + [ + { + 'switch_info': 'foo', + 'port_id': '2222' + } + ] + }, + 'binding:vnic_type': 'baremetal', + 'id': 'aaaa-bbbb-cccc', + 'trunk_details': {'sub_ports': + [{'segmentation_id': 123}]}} + trunk_details = {'sub_ports': [{'segmentation_id': 123}]} + self.switch.plug_port_to_network_trunk('2222', None, + trunk_details, vtr=False) + t_mock.assert_called_once_with( + '2222', None, {'sub_ports': [{'segmentation_id': 123}]}) + self.assertFalse(nt_mock.called) + + @mock.patch.object(netmiko_devices.netmiko, 'ConnectHandler') + @mock.patch.object(netmiko_devices.NetmikoSwitch, + 'send_commands_to_device') + @mock.patch.object(netmiko_devices.NetmikoSwitch, + 'get_trunk_port_cmds_no_vlan_translation') + @mock.patch.object(netmiko_devices.NetmikoSwitch, + 'get_trunk_port_cmds_vlan_translation') + def test_bind_port_trunk_no_vts_raise(self, t_mock, nt_mock, sctd_mock, + nm_mock): + connect_mock = mock.Mock() + nm_mock.return_value = connect_mock + self.switch.ngs_config['vlan_translation_supported'] = False + trunk_details = {'sub_ports': [{'segmentation_id': 123}]} + self.assertRaises(exc.GenericSwitchNotSupported, + self.switch.plug_port_to_network_trunk, '2222', None, + trunk_details, vtr=True) + self.assertFalse(t_mock.called) + self.assertFalse(nt_mock.called) diff --git a/networking_generic_switch/tests/unit/test_devices.py b/networking_generic_switch/tests/unit/test_devices.py index 7587837e..482dbd1f 100644 --- a/networking_generic_switch/tests/unit/test_devices.py +++ b/networking_generic_switch/tests/unit/test_devices.py @@ -218,3 +218,11 @@ def test__get_ssh_disabled_algorithms(self): "ciphers": ["blowfish-cbc", "3des-cbc"], } self.assertEqual(expected, algos) + + def test_driver_load_config_override(self): + device_cfg = {"device_type": 'netmiko_ovs_linux', + "vlan_translation_supported": True} + device = devices.device_manager(device_cfg) + self.assertIsInstance(device, devices.GenericSwitchDevice) + self.assertNotIn('vlan_translation_support', device.config) + self.assertTrue(device.ngs_config['vlan_translation_supported']) 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 6c3356c9..ebbdebd9 100644 --- a/networking_generic_switch/tests/unit/test_generic_switch_mech.py +++ b/networking_generic_switch/tests/unit/test_generic_switch_mech.py @@ -512,10 +512,13 @@ def test_update_port_postcommit_complete_provisioning(self, m_pc, m_list): resources.PORT, 'GENERICSWITCH') + @mock.patch.object(gsm.GenericSwitchDriver, + '_is_vlan_translation_required', return_value=False) @mock.patch.object(provisioning_blocks, 'provisioning_complete') def test_update_portgroup_postcommit_complete_provisioning(self, m_pc, - m_list): + m_list, + m_ivtr): driver = gsm.GenericSwitchDriver() driver.initialize() mock_context = mock.create_autospec(driver_context.PortContext) @@ -783,6 +786,55 @@ def test_bind_port(self, m_apc, m_list): resources.PORT, 'GENERICSWITCH') + @mock.patch.object(gsm.GenericSwitchDriver, + '_is_vlan_translation_required', return_value=False) + @mock.patch.object(provisioning_blocks, 'add_provisioning_component') + def test_bind_port_trunk(self, m_apc, m_list, m_vlan): + driver = gsm.GenericSwitchDriver() + driver.initialize() + mock_context = mock.create_autospec(driver_context.PortContext) + mock_context._plugin_context = mock.MagicMock() + trunk_details = {'trunk_id': 'aaa-bbb-ccc-ddd', + 'sub_ports': [{'segmentation_id': 130, + 'port_id': 'aaa-bbb-ccc-ddd', + 'segmentation_type': 'vlan', + 'mac_address': u'fa:16:3e:1c:c2:7e'}]} + mock_context.network.current = { + 'provider:physical_network': 'physnet1' + } + mock_context.current = {'binding:profile': + {'local_link_information': + [ + { + 'switch_info': 'foo', + 'port_id': '2222' + } + ] + }, + 'binding:vnic_type': 'baremetal', + 'id': 'aaaa-bbbb-cccc', + 'trunk_details': trunk_details} + mock_context.network = mock.Mock() + mock_context.network.current = { + 'provider:segmentation_id': 123, + 'provider:physical_network': 'physnet1' + } + mock_context.segments_to_bind = [ + { + 'segmentation_id': 777, + 'id': 123 + } + ] + + driver.bind_port(mock_context) + self.switch_mock.plug_port_to_network_trunk.assert_called_once_with( + '2222', 777, trunk_details, False) + mock_context.set_binding.assert_called_with(123, 'other', {}) + m_apc.assert_called_once_with(mock_context._plugin_context, + mock_context.current['id'], + resources.PORT, + 'GENERICSWITCH') + @mock.patch.object(provisioning_blocks, 'add_provisioning_component') def test_bind_portgroup(self, m_apc, m_list): driver = gsm.GenericSwitchDriver()