diff --git a/manila/exception.py b/manila/exception.py index 682cf86257..c97d274c5d 100644 --- a/manila/exception.py +++ b/manila/exception.py @@ -763,6 +763,10 @@ class IPAddressInUse(InUse): message = _("IP address %(ip)s is already used.") +class MacAddressInUse(InUse): + message = _("MAC address %(mac)s is already in use.") + + class ShareGroupTypeInUse(ManilaException): message = _("Share group Type %(type_id)s deletion is not allowed " "with groups present with the type.") diff --git a/manila/network/neutron/api.py b/manila/network/neutron/api.py index 900c8368a7..c027bc325f 100644 --- a/manila/network/neutron/api.py +++ b/manila/network/neutron/api.py @@ -72,6 +72,10 @@ class PortBindingAlreadyExistsClient(neutron_client_exc.Conflict): pass +class MacAddressInUseClient(neutron_client_exc.Conflict): + pass + + # We need to monkey-patch neutronclient.common.exceptions module, to make # neutron client to raise error specific exceptions. E.g. exception # PortBindingAlreadyExistsClient is raised for Neutron API error @@ -79,6 +83,7 @@ class PortBindingAlreadyExistsClient(neutron_client_exc.Conflict): # Conflict will be raised. neutron_client_exc.PortBindingAlreadyExistsClient = \ PortBindingAlreadyExistsClient +neutron_client_exc.MacAddressInUseClient = MacAddressInUseClient def list_opts(): @@ -197,6 +202,8 @@ def _create_port(self, tenant_id, network_id, host_id=None, subnet_id=None, except neutron_client_exc.NeutronClientException as e: LOG.warning('Neutron error creating port on network %s', network_id) + if e.status_code == 409 and 'mac' in str(e.message).lower(): + raise exception.MacAddressInUse(mac=e.message) if e.status_code == 409: raise exception.PortLimitExceeded() raise exception.NetworkException(code=e.status_code, diff --git a/manila/network/neutron/neutron_network_plugin.py b/manila/network/neutron/neutron_network_plugin.py index e237298286..8a247a3c6b 100644 --- a/manila/network/neutron/neutron_network_plugin.py +++ b/manila/network/neutron/neutron_network_plugin.py @@ -165,6 +165,7 @@ def allocate_network(self, context, share_server, share_network=None, allocation_count = kwargs.get('count', 1) device_owner = kwargs.get('device_owner', 'share') + mac_address = kwargs.get('mac_address') ports = [] for current_count in range(0, allocation_count): @@ -175,7 +176,8 @@ def allocate_network(self, context, share_server, share_network=None, share_network_subnet, device_owner, current_count, - is_external_network=is_external_network), + is_external_network=is_external_network, + mac_address=mac_address), ) return ports @@ -348,8 +350,8 @@ def deallocate_network(self, context, share_server_id, def _get_port_create_args(self, share_server, share_network_subnet, device_owner, count=0, - is_external_network=False): - return { + is_external_network=False, mac_address=None): + args = { "network_id": share_network_subnet['neutron_net_id'], "subnet_id": share_network_subnet['neutron_subnet_id'], "device_owner": 'manila:' + device_owner, @@ -360,16 +362,32 @@ def _get_port_create_args(self, share_server, share_network_subnet, # neutron creates merely assist in IPAM. "admin_state_up": not is_external_network, } + if mac_address: + args["mac_address"] = mac_address + return args def _create_port(self, context, share_server, share_network, share_network_subnet, device_owner, count=0, - is_external_network=False): + is_external_network=False, mac_address=None): create_args = self._get_port_create_args( share_server, share_network_subnet, device_owner, count, - is_external_network=is_external_network) + is_external_network=is_external_network, + mac_address=mac_address) - port = self.neutron_api.create_port( - share_network['project_id'], **create_args) + try: + port = self.neutron_api.create_port( + share_network['project_id'], **create_args) + except exception.MacAddressInUse: + LOG.warning('MAC %s already in use on network %s; retrying ' + 'without MAC (overflow port — real wire MAC recorded ' + 'in port description).', + mac_address, + share_network_subnet['neutron_net_id']) + create_args.pop('mac_address', None) + create_args['description'] = ( + 'NetApp LIF; effective wire MAC=%s' % mac_address) + port = self.neutron_api.create_port( + share_network['project_id'], **create_args) if is_external_network: msg = ( @@ -639,11 +657,12 @@ def _wait_for_ports_bind(self, ports, share_server): def _get_port_create_args(self, share_server, share_network_subnet, device_owner, count=0, - is_external_network=False): + is_external_network=False, mac_address=None): arguments = super( NeutronBindNetworkPlugin, self)._get_port_create_args( share_server, share_network_subnet, device_owner, count, - is_external_network=is_external_network + is_external_network=is_external_network, + mac_address=mac_address ) arguments['host_id'] = self.config.neutron_host_id arguments['binding:vnic_type'] = self.config.neutron_vnic_type diff --git a/manila/share/drivers/netapp/dataontap/client/client_cmode.py b/manila/share/drivers/netapp/dataontap/client/client_cmode.py index 7cccb77e85..0c81aee513 100644 --- a/manila/share/drivers/netapp/dataontap/client/client_cmode.py +++ b/manila/share/drivers/netapp/dataontap/client/client_cmode.py @@ -757,6 +757,7 @@ def get_node_data_ports(self, node): 'node': None, 'operational-speed': None, 'ifgrp-port': None, + 'mac-address': None, }, }, } @@ -775,6 +776,7 @@ def get_node_data_ports(self, node): 'node': port_info.get_child_content('node'), 'port': port_info.get_child_content('port'), 'speed': port_info.get_child_content('operational-speed'), + 'mac-address': port_info.get_child_content('mac-address'), } ports.append(port) diff --git a/manila/share/drivers/netapp/dataontap/client/client_cmode_rest.py b/manila/share/drivers/netapp/dataontap/client/client_cmode_rest.py index 95e20ada20..dbbb3d4415 100644 --- a/manila/share/drivers/netapp/dataontap/client/client_cmode_rest.py +++ b/manila/share/drivers/netapp/dataontap/client/client_cmode_rest.py @@ -5141,7 +5141,7 @@ def get_node_data_ports(self, node): 'state': 'up', 'type': 'physical', 'broadcast_domain.name': 'Default', - 'fields': 'node.name,speed,name' + 'fields': 'node.name,speed,name,mac_address' } result = self.send_request('/network/ethernet/ports', 'get', @@ -5174,6 +5174,7 @@ def get_node_data_ports(self, node): 'node': port_info['node']['name'], 'port': port_info['name'], 'speed': port_info['speed'], + 'mac-address': port_info.get('mac_address'), } ports.append(port) diff --git a/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py b/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py index a775ed4c12..dac39baff6 100644 --- a/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py +++ b/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py @@ -139,6 +139,17 @@ def get_share_server_pools(self, share_server): def get_network_allocations_number(self): return self.library.get_network_allocations_number() + def allocate_network(self, context, share_server, share_network, + share_network_subnet, **kwargs): + """Allocate network, injecting the ifgroup MAC for the first port.""" + + mac = self.library.get_node_mac_for_network_allocation() + if mac: + kwargs.setdefault('mac_address', mac) + return super().allocate_network( + context, share_server, share_network, share_network_subnet, + **kwargs) + def get_admin_network_allocations_number(self): return self.library.get_admin_network_allocations_number( self.admin_network_api) diff --git a/manila/share/drivers/netapp/dataontap/cluster_mode/drv_single_svm.py b/manila/share/drivers/netapp/dataontap/cluster_mode/drv_single_svm.py index 08e8b597bd..0350a3e1a8 100644 --- a/manila/share/drivers/netapp/dataontap/cluster_mode/drv_single_svm.py +++ b/manila/share/drivers/netapp/dataontap/cluster_mode/drv_single_svm.py @@ -123,6 +123,12 @@ def get_share_server_pools(self, share_server): def get_network_allocations_number(self): return self.library.get_network_allocations_number() + def allocate_network(self, context, share_server, share_network, + share_network_subnet, **kwargs): + return super().allocate_network( + context, share_server, share_network, share_network_subnet, + **kwargs) + def get_admin_network_allocations_number(self): return self.library.get_admin_network_allocations_number() diff --git a/manila/share/drivers/netapp/dataontap/cluster_mode/lib_multi_svm.py b/manila/share/drivers/netapp/dataontap/cluster_mode/lib_multi_svm.py index dcc564ad58..520caf2081 100644 --- a/manila/share/drivers/netapp/dataontap/cluster_mode/lib_multi_svm.py +++ b/manila/share/drivers/netapp/dataontap/cluster_mode/lib_multi_svm.py @@ -29,6 +29,8 @@ from oslo_utils import uuidutils from manila.common import constants +from manila import context as manila_context +from manila import db from manila import exception from manila.i18n import _ from manila.message import message_field @@ -534,6 +536,25 @@ def _create_vserver_lifs(self, vserver_name, vserver_client, network_info, node_name, lif_name, network_allocation, lif_home_port=lif_home_port) + # Sync the real LIF MAC (inherited from the node's physical port) + self._sync_lif_mac_to_db(node_name, network_allocation) + + @na_utils.trace + def _sync_lif_mac_to_db(self, node_name, network_allocation): + """Update network_allocations.mac_address with the LIF's real MAC.""" + + node_mac = self._get_node_data_port_mac(node_name) + if not node_mac: + LOG.warning('Could not retrieve MAC for node %s; leaving ' + 'network_allocation %s untouched.', + node_name, network_allocation['id']) + return + admin_ctx = manila_context.get_admin_context() + db.network_allocation_update( + admin_ctx, network_allocation['id'], {'mac_address': node_mac}) + LOG.info('Synced LIF MAC for node %s: allocation %s updated to %s.', + node_name, network_allocation['id'], node_mac) + @na_utils.trace def _create_vserver_admin_lif(self, vserver_name, vserver_client, network_info, ipspace_name, @@ -558,6 +579,8 @@ def _create_vserver_admin_lif(self, vserver_name, vserver_client, node_name, lif_name, network_allocation, lif_home_port=home_port) + self._sync_lif_mac_to_db(node_name, network_allocation) + @na_utils.trace def _create_vserver_routes(self, vserver_client, network_info): """Create Vserver route and set gateways.""" @@ -584,6 +607,17 @@ def _get_node_data_port(self, node): 'to create Vserver LIFs.') % node) return matched_port_names[0] + @na_utils.trace + def _get_node_data_port_mac(self, node): + """Return MAC address of the data port selected for LIF creation.""" + + pattern = self.configuration.netapp_port_name_search_pattern + ports = self._client.get_node_data_ports(node) + matched = [p for p in ports if re.match(pattern, p.get('port', ''))] + if matched: + return matched[0].get('mac-address') + return None + def _get_lif_name(self, node_name, network_allocation): """Get LIF name based on template from manila.conf file.""" lif_name_args = { @@ -647,6 +681,15 @@ def get_network_allocations_number(self): """Get number of network interfaces to be created.""" return len(self._client.list_cluster_nodes()) + @na_utils.trace + def get_node_mac_for_network_allocation(self): + """Return the ifgroup MAC of the first cluster node.""" + + nodes = self._client.list_cluster_nodes() + if nodes: + return self._get_node_data_port_mac(nodes[0]) + return None + @na_utils.trace def get_admin_network_allocations_number(self, admin_network_api): """Get number of network allocations for creating admin LIFs.""" diff --git a/manila/tests/network/neutron/test_neutron_api.py b/manila/tests/network/neutron/test_neutron_api.py index f4e2005507..2ce6fb4a86 100644 --- a/manila/tests/network/neutron/test_neutron_api.py +++ b/manila/tests/network/neutron/test_neutron_api.py @@ -276,6 +276,36 @@ def test_create_port_exception_status_409(self): self.assertTrue(clientv20.Client.called) self.assertTrue(self.neutron_api.client.create_port.called) + @mock.patch.object(neutron_api.LOG, 'warning', mock.Mock()) + def test_create_port_exception_status_409_mac_in_use(self): + self.mock_object( + self.neutron_api.client, 'create_port', + mock.Mock(side_effect=neutron_client_exc.NeutronClientException( + status_code=409, + message='The mac address fa:16:3e:xx is in use.'))) + port_args = {'tenant_id': 'test tenant', 'network_id': 'test net'} + + self.assertRaises(exception.MacAddressInUse, + self.neutron_api.create_port, + **port_args) + + self.assertTrue(neutron_api.LOG.warning.called) + + @mock.patch.object(neutron_api.LOG, 'warning', mock.Mock()) + def test_create_port_exception_status_409_quota_not_mac(self): + self.mock_object( + self.neutron_api.client, 'create_port', + mock.Mock(side_effect=neutron_client_exc.NeutronClientException( + status_code=409, + message='Quota exceeded for resources: [\'port\']'))) + port_args = {'tenant_id': 'test tenant', 'network_id': 'test net'} + + self.assertRaises(exception.PortLimitExceeded, + self.neutron_api.create_port, + **port_args) + + self.assertTrue(neutron_api.LOG.warning.called) + def test_delete_port(self): # Set up test data self.mock_object(self.neutron_api.client, 'delete_port') diff --git a/manila/tests/network/neutron/test_neutron_plugin.py b/manila/tests/network/neutron/test_neutron_plugin.py index 8c678c3400..a4c0ab48c8 100644 --- a/manila/tests/network/neutron/test_neutron_plugin.py +++ b/manila/tests/network/neutron/test_neutron_plugin.py @@ -416,6 +416,77 @@ def test_allocate_network_create_port_exception(self): save_subnet_data.stop() create_port.stop() + @mock.patch.object(db_api, 'network_allocation_create', + mock.Mock(return_values=fake_network_allocation)) + @mock.patch.object(db_api, 'share_network_get', + mock.Mock(return_value=fake_share_network)) + @mock.patch.object(db_api, 'share_server_get', + mock.Mock(return_value=fake_share_server)) + def test_allocate_network_with_mac_address(self): + fake_mac = 'd2:39:ea:ac:06:9c' + has_provider_nw_ext = mock.patch.object( + self.plugin, '_has_provider_network_extension').start() + has_provider_nw_ext.return_value = True + mock.patch.object(self.plugin, '_save_neutron_network_data').start() + mock.patch.object(self.plugin, '_save_neutron_subnet_data').start() + + with mock.patch.object(self.plugin.neutron_api, 'create_port', + mock.Mock(return_value=fake_neutron_port)): + self.plugin.allocate_network( + self.fake_context, + fake_share_server, + fake_share_network, + fake_share_network_subnet, + mac_address=fake_mac) + + self.plugin.neutron_api.create_port.assert_called_once_with( + fake_share_network['project_id'], + network_id=fake_share_network_subnet['neutron_net_id'], + subnet_id=fake_share_network_subnet['neutron_subnet_id'], + device_owner='manila:share', + device_id=fake_share_network['id'], + name=fake_share_network['id'] + '_0', + admin_state_up=False, + mac_address=fake_mac, + ) + + @mock.patch.object(db_api, 'network_allocation_create', + mock.Mock(return_values=fake_network_allocation)) + @mock.patch.object(db_api, 'share_network_get', + mock.Mock(return_value=fake_share_network)) + @mock.patch.object(db_api, 'share_server_get', + mock.Mock(return_value=fake_share_server)) + @mock.patch.object(plugin.LOG, 'warning', mock.Mock()) + def test_allocate_network_mac_in_use_retries_without_mac(self): + fake_mac = 'd2:39:ea:ac:06:9c' + has_provider_nw_ext = mock.patch.object( + self.plugin, '_has_provider_network_extension').start() + has_provider_nw_ext.return_value = True + mock.patch.object(self.plugin, '_save_neutron_network_data').start() + mock.patch.object(self.plugin, '_save_neutron_subnet_data').start() + + create_port = mock.patch.object( + self.plugin.neutron_api, 'create_port', + mock.Mock(side_effect=[ + exception.MacAddressInUse(mac=fake_mac), + fake_neutron_port, + ])).start() + + self.plugin.allocate_network( + self.fake_context, + fake_share_server, + fake_share_network, + fake_share_network_subnet, + mac_address=fake_mac) + + self.assertEqual(2, create_port.call_count) + _, second_kwargs = create_port.call_args + + self.assertNotIn('mac_address', second_kwargs) + self.assertIn('description', second_kwargs) + self.assertIn(fake_mac, second_kwargs['description']) + plugin.LOG.warning.assert_called() + def _setup_manage_network_allocations(self): allocations = ['192.168.0.11', '192.168.0.12', 'fd12::2000'] diff --git a/manila/tests/share/drivers/netapp/dataontap/client/fakes.py b/manila/tests/share/drivers/netapp/dataontap/client/fakes.py index b6a5391295..8e51ca50c2 100644 --- a/manila/tests/share/drivers/netapp/dataontap/client/fakes.py +++ b/manila/tests/share/drivers/netapp/dataontap/client/fakes.py @@ -833,10 +833,14 @@ """ % {'node_name': NODE_NAME}) SPEED_SORTED_PORTS = ( - {'node': NODE_NAME, 'port': 'e0d', 'speed': '10000'}, - {'node': NODE_NAME, 'port': 'e0c', 'speed': '1000'}, - {'node': NODE_NAME, 'port': 'e0b', 'speed': '100'}, - {'node': NODE_NAME, 'port': 'e0a', 'speed': '10'}, + {'node': NODE_NAME, 'port': 'e0d', 'speed': '10000', + 'mac-address': '00:0c:29:fc:04:f7'}, + {'node': NODE_NAME, 'port': 'e0c', 'speed': '1000', + 'mac-address': '00:0c:29:fc:04:ed'}, + {'node': NODE_NAME, 'port': 'e0b', 'speed': '100', + 'mac-address': '00:0c:29:fc:04:e3'}, + {'node': NODE_NAME, 'port': 'e0a', 'speed': '10', + 'mac-address': '00:0c:29:fc:04:d9'}, ) PORT_NAMES = ('e0a', 'e0b', 'e0c', 'e0d') SPEED_SORTED_PORT_NAMES = ('e0d', 'e0c', 'e0b', 'e0a') @@ -4613,15 +4617,21 @@ } REST_SPEED_SORTED_PORTS = [ - {'node': NODE_NAME, 'port': 'e0d', 'speed': 10000}, - {'node': NODE_NAME, 'port': 'e0c', 'speed': 1000}, - {'node': NODE_NAME, 'port': 'e0b', 'speed': 100}, + {'node': NODE_NAME, 'port': 'e0d', 'speed': 10000, + 'mac-address': 'aa:bb:cc:dd:ee:04'}, + {'node': NODE_NAME, 'port': 'e0c', 'speed': 1000, + 'mac-address': 'aa:bb:cc:dd:ee:03'}, + {'node': NODE_NAME, 'port': 'e0b', 'speed': 100, + 'mac-address': 'aa:bb:cc:dd:ee:02'}, ] REST_SPEED_NOT_SORTED_PORTS = [ - {'node': NODE_NAME, 'port': 'e0b', 'speed': 100}, - {'node': NODE_NAME, 'port': 'e0c', 'speed': 1000}, - {'node': NODE_NAME, 'port': 'e0d', 'speed': 10000}, + {'node': NODE_NAME, 'port': 'e0b', 'speed': 100, + 'mac-address': 'aa:bb:cc:dd:ee:02'}, + {'node': NODE_NAME, 'port': 'e0c', 'speed': 1000, + 'mac-address': 'aa:bb:cc:dd:ee:03'}, + {'node': NODE_NAME, 'port': 'e0d', 'speed': 10000, + 'mac-address': 'aa:bb:cc:dd:ee:04'}, ] REST_ETHERNET_PORTS = { @@ -4641,6 +4651,7 @@ }, "state": "up", "speed": 10, + "mac_address": "aa:bb:cc:dd:ee:01", }, { "uuid": "fake_uuid2", @@ -4657,6 +4668,7 @@ }, "state": "up", "speed": 100, + "mac_address": "aa:bb:cc:dd:ee:02", }, { "uuid": "fake_uuid3", @@ -4673,6 +4685,7 @@ }, "state": "up", "speed": 1000, + "mac_address": "aa:bb:cc:dd:ee:03", }, { "uuid": "fake_uuid4", @@ -4689,15 +4702,11 @@ }, "state": "up", "speed": 10000, + "mac_address": "aa:bb:cc:dd:ee:04", } ], } -SVM_ITEM_SIMPLE_RESPONSE_REST = { - "uuid": "fake_uuid", - "name": VSERVER_NAME, -} - FAKE_GET_BROADCAST_DOMAIN = { 'records': [ { diff --git a/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py b/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py index fe845b6f43..ed49218cf9 100644 --- a/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py +++ b/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py @@ -1077,6 +1077,7 @@ def test_get_node_data_ports(self): 'node': None, 'operational-speed': None, 'ifgrp-port': None, + 'mac-address': None, }, }, } diff --git a/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode_rest.py b/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode_rest.py index ef99fc7c1f..c5a568ccdc 100644 --- a/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode_rest.py +++ b/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode_rest.py @@ -5178,7 +5178,7 @@ def test_get_node_data_ports(self): 'state': 'up', 'type': 'physical', 'broadcast_domain.name': 'Default', - 'fields': 'node.name,speed,name' + 'fields': 'node.name,speed,name,mac_address' } query_interfaces = { diff --git a/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_multi_svm.py b/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_multi_svm.py index 9b05dae9e5..fb3dab73e0 100644 --- a/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_multi_svm.py +++ b/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_multi_svm.py @@ -28,6 +28,7 @@ from manila import exception from manila.share.drivers.netapp.dataontap.client import api as netapp_api from manila.share.drivers.netapp.dataontap.cluster_mode import data_motion +from manila.share.drivers.netapp.dataontap.cluster_mode import drv_multi_svm from manila.share.drivers.netapp.dataontap.cluster_mode import lib_base from manila.share.drivers.netapp.dataontap.cluster_mode import lib_multi_svm from manila.share.drivers.netapp import utils as na_utils @@ -1037,6 +1038,7 @@ def test_create_vserver_lifs(self): '_get_lif_name', mock.Mock(side_effect=['fake_lif1', 'fake_lif2'])) self.mock_object(self.library, '_create_lif') + self.mock_object(self.library, '_sync_lif_mac_to_db') self.library._create_vserver_lifs(fake.VSERVER1, 'fake_vserver_client', @@ -1062,6 +1064,7 @@ def test_create_vserver_lifs_pre_configured_home_ports(self): '_get_lif_name', mock.Mock(side_effect=['fake_lif1', 'fake_lif2'])) self.mock_object(self.library, '_create_lif') + self.mock_object(self.library, '_sync_lif_mac_to_db') lif_home_ports = { fake.CLUSTER_NODES[0]: 'fake_port1', @@ -1093,6 +1096,7 @@ def test_create_vserver_admin_lif(self): '_get_lif_name', mock.Mock(return_value='fake_admin_lif')) self.mock_object(self.library, '_create_lif') + self.mock_object(self.library, '_sync_lif_mac_to_db') self.library._create_vserver_admin_lif(fake.VSERVER1, 'fake_vserver_client', @@ -1125,6 +1129,116 @@ def test_create_vserver_admin_lif_no_admin_network(self): self.assertFalse(self.library._create_lif.called) + def test_create_vserver_lifs_syncs_mac(self): + self.mock_object(self.library._client, + 'list_cluster_nodes', + mock.Mock(return_value=fake.CLUSTER_NODES)) + self.mock_object(self.library, + '_get_lif_name', + mock.Mock(side_effect=['fake_lif1', 'fake_lif2'])) + self.mock_object(self.library, '_create_lif') + self.mock_object(self.library, '_sync_lif_mac_to_db') + + self.library._create_vserver_lifs(fake.VSERVER1, + 'fake_vserver_client', + fake.NETWORK_INFO, + fake.IPSPACE) + + self.library._sync_lif_mac_to_db.assert_has_calls([ + mock.call(fake.CLUSTER_NODES[0], + fake.NETWORK_INFO['network_allocations'][0]), + mock.call(fake.CLUSTER_NODES[1], + fake.NETWORK_INFO['network_allocations'][1])]) + + def test_create_vserver_admin_lif_syncs_mac(self): + self.mock_object(self.library._client, + 'list_cluster_nodes', + mock.Mock(return_value=fake.CLUSTER_NODES)) + self.mock_object(self.library, + '_get_lif_name', + mock.Mock(return_value='fake_admin_lif')) + self.mock_object(self.library, '_create_lif') + self.mock_object(self.library, '_sync_lif_mac_to_db') + + self.library._create_vserver_admin_lif(fake.VSERVER1, + 'fake_vserver_client', + fake.NETWORK_INFO, + fake.IPSPACE) + + self.library._sync_lif_mac_to_db.assert_called_once_with( + fake.CLUSTER_NODES[0], + fake.NETWORK_INFO['admin_network_allocations'][0]) + + def test_sync_lif_mac_to_db(self): + fake_mac = 'd2:39:ea:ac:06:9c' + fake_allocation = fake.NETWORK_INFO['network_allocations'][0] + self.mock_object(self.library, + '_get_node_data_port_mac', + mock.Mock(return_value=fake_mac)) + self.mock_object(context, + 'get_admin_context', + mock.Mock(return_value='fake_admin_context')) + mock_db_update = self.mock_object(lib_multi_svm.db, + 'network_allocation_update') + + self.library._sync_lif_mac_to_db( + fake.CLUSTER_NODES[0], fake_allocation) + + self.library._get_node_data_port_mac.assert_called_once_with( + fake.CLUSTER_NODES[0]) + mock_db_update.assert_called_once_with( + 'fake_admin_context', fake_allocation['id'], + {'mac_address': fake_mac}) + + def test_sync_lif_mac_to_db_no_mac(self): + fake_allocation = fake.NETWORK_INFO['network_allocations'][0] + self.mock_object(self.library, + '_get_node_data_port_mac', + mock.Mock(return_value=None)) + mock_db_update = self.mock_object(lib_multi_svm.db, + 'network_allocation_update') + + self.library._sync_lif_mac_to_db( + fake.CLUSTER_NODES[0], fake_allocation) + + mock_db_update.assert_not_called() + + def test_get_node_mac_for_network_allocation(self): + fake_mac = 'd2:39:ea:ac:06:9c' + self.mock_object(self.library._client, + 'list_cluster_nodes', + mock.Mock(return_value=fake.CLUSTER_NODES)) + self.mock_object(self.library, + '_get_node_data_port_mac', + mock.Mock(return_value=fake_mac)) + + result = self.library.get_node_mac_for_network_allocation() + + self.assertEqual(fake_mac, result) + self.library._get_node_data_port_mac.assert_called_once_with( + fake.CLUSTER_NODES[0]) + + def test_get_node_mac_for_network_allocation_no_nodes(self): + self.mock_object(self.library._client, + 'list_cluster_nodes', + mock.Mock(return_value=[])) + + result = self.library.get_node_mac_for_network_allocation() + + self.assertIsNone(result) + + def test_get_node_mac_for_network_allocation_no_mac(self): + self.mock_object(self.library._client, + 'list_cluster_nodes', + mock.Mock(return_value=fake.CLUSTER_NODES)) + self.mock_object(self.library, + '_get_node_data_port_mac', + mock.Mock(return_value=None)) + + result = self.library.get_node_mac_for_network_allocation() + + self.assertIsNone(result) + @ddt.data( fake.get_network_info(fake.USER_NETWORK_ALLOCATIONS, fake.ADMIN_NETWORK_ALLOCATIONS), @@ -4370,3 +4484,75 @@ def test__check_data_lif_count_limit_reached_for_ha_pair_true(self): self.library._check_data_lif_count_limit_reached_for_ha_pair, self.client, ) + + +class NetAppMultiSvmDriverAllocateNetworkTestCase(test.TestCase): + + def setUp(self): + super(NetAppMultiSvmDriverAllocateNetworkTestCase, self).setUp() + self.mock_object(drv_multi_svm.NetAppCmodeMultiSvmShareDriver, + '__init__', + mock.Mock(return_value=None)) + self.driver = drv_multi_svm.NetAppCmodeMultiSvmShareDriver() + self.driver.library = mock.Mock() + self.driver.network_api = mock.Mock() + self.fake_context = 'fake_context' + self.fake_share_server = {'id': 'fake-ss-id'} + self.fake_share_network = {'project_id': 'fake-project'} + self.fake_share_network_subnet = {'neutron_net_id': 'fake-net'} + + def test_allocate_network_injects_mac_from_library(self): + fake_mac = 'd2:39:ea:ac:06:9c' + mock_get_mac = ( + self.driver.library.get_node_mac_for_network_allocation) + mock_get_mac.return_value = fake_mac + super_mock = self.mock_object( + drv_multi_svm.driver.ShareDriver, 'allocate_network') + + self.driver.allocate_network( + self.fake_context, + self.fake_share_server, + self.fake_share_network, + self.fake_share_network_subnet) + + super_mock.assert_called_once_with( + self.fake_context, + self.fake_share_server, + self.fake_share_network, + self.fake_share_network_subnet, + mac_address=fake_mac) + + def test_allocate_network_no_mac_does_not_inject(self): + mock_get_mac = ( + self.driver.library.get_node_mac_for_network_allocation) + mock_get_mac.return_value = None + super_mock = self.mock_object( + drv_multi_svm.driver.ShareDriver, 'allocate_network') + + self.driver.allocate_network( + self.fake_context, + self.fake_share_server, + self.fake_share_network, + self.fake_share_network_subnet) + + _, call_kwargs = super_mock.call_args + self.assertNotIn('mac_address', call_kwargs) + + def test_allocate_network_does_not_overwrite_caller_mac(self): + caller_mac = 'aa:bb:cc:dd:ee:ff' + lib_mac = 'd2:39:ea:ac:06:9c' + mock_get_mac = ( + self.driver.library.get_node_mac_for_network_allocation) + mock_get_mac.return_value = lib_mac + super_mock = self.mock_object( + drv_multi_svm.driver.ShareDriver, 'allocate_network') + + self.driver.allocate_network( + self.fake_context, + self.fake_share_server, + self.fake_share_network, + self.fake_share_network_subnet, + mac_address=caller_mac) + + _, call_kwargs = super_mock.call_args + self.assertEqual(caller_mac, call_kwargs['mac_address'])