-# Copyright (c) - 2015, Alex Meade. All Rights Reserved.
+# Copyright (c) - 2015, Alex Meade
+# Copyright (c) - 2015, Yogesh Kshirsagar
+# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
INITIATOR_NAME = 'iqn.1998-01.com.vmware:localhost-28a58148'
INITIATOR_NAME_2 = 'iqn.1998-01.com.vmware:localhost-28a58149'
INITIATOR_NAME_3 = 'iqn.1998-01.com.vmware:localhost-28a58150'
+WWPN = '20130080E5322230'
+WWPN_2 = '20230080E5322230'
+
+FC_TARGET_WWPNS = [
+ '500a098280feeba5',
+ '500a098290feeba5',
+ '500a098190feeba5',
+ '500a098180feeba5'
+]
+
+FC_I_T_MAP = {
+ '20230080E5322230': [
+ '500a098280feeba5',
+ '500a098290feeba5'
+ ],
+ '20130080E5322230': [
+ '500a098190feeba5',
+ '500a098180feeba5'
+ ]
+}
+
+FC_FABRIC_MAP = {
+ 'fabricB': {
+ 'target_port_wwn_list': [
+ '500a098190feeba5',
+ '500a098180feeba5'
+ ],
+ 'initiator_port_wwn_list': [
+ '20130080E5322230'
+ ]
+ },
+ 'fabricA': {
+ 'target_port_wwn_list': [
+ '500a098290feeba5',
+ '500a098280feeba5'
+ ],
+ 'initiator_port_wwn_list': [
+ '20230080E5322230'
+ ]
+ }
+}
HOST = {
'isSAControlled': False,
'interfaceRef': '2202040000000000000000000000000000000000',
'iqn': 'iqn.1992-01.com.lsi:2365.60080e500023c73400000000515af323'
}
+ ],
+ 'fibrePorts': [
+ {
+ "channel": 1,
+ "loopID": 126,
+ "speed": 800,
+ "hardAddress": 6,
+ "nodeName": "20020080E5322230",
+ "portName": "20130080E5322230",
+ "portId": "011700",
+ "topology": "fabric",
+ "part": "PM8032 ",
+ "revision": 8,
+ "chanMiswire": False,
+ "esmMiswire": False,
+ "linkStatus": "up",
+ "isDegraded": False,
+ "speedControl": "auto",
+ "maxSpeed": 800,
+ "speedNegError": False,
+ "reserved1": "000000000000000000000000",
+ "reserved2": "",
+ "ddsChannelState": 0,
+ "ddsStateReason": 0,
+ "ddsStateWho": 0,
+ "isLocal": True,
+ "channelPorts": [],
+ "currentInterfaceSpeed": "speed8gig",
+ "maximumInterfaceSpeed": "speed8gig",
+ "interfaceRef": "2202020000000000000000000000000000000000",
+ "physicalLocation": {
+ "trayRef": "0000000000000000000000000000000000000000",
+ "slot": 0,
+ "locationParent": {
+ "refType": "generic",
+ "controllerRef": None,
+ "symbolRef": "0000000000000000000000000000000000000000",
+ "typedReference": None
+ },
+ "locationPosition": 0
+ },
+ "isTrunkCapable": False,
+ "trunkMiswire": False,
+ "protectionInformationCapable": True,
+ "controllerId": "070000000000000000000002",
+ "interfaceId": "2202020000000000000000000000000000000000",
+ "addressId": "20130080E5322230",
+ "niceAddressId": "20:13:00:80:E5:32:22:30"
+ },
+ {
+ "channel": 2,
+ "loopID": 126,
+ "speed": 800,
+ "hardAddress": 7,
+ "nodeName": "20020080E5322230",
+ "portName": "20230080E5322230",
+ "portId": "011700",
+ "topology": "fabric",
+ "part": "PM8032 ",
+ "revision": 8,
+ "chanMiswire": False,
+ "esmMiswire": False,
+ "linkStatus": "up",
+ "isDegraded": False,
+ "speedControl": "auto",
+ "maxSpeed": 800,
+ "speedNegError": False,
+ "reserved1": "000000000000000000000000",
+ "reserved2": "",
+ "ddsChannelState": 0,
+ "ddsStateReason": 0,
+ "ddsStateWho": 0,
+ "isLocal": True,
+ "channelPorts": [],
+ "currentInterfaceSpeed": "speed8gig",
+ "maximumInterfaceSpeed": "speed8gig",
+ "interfaceRef": "2202030000000000000000000000000000000000",
+ "physicalLocation": {
+ "trayRef": "0000000000000000000000000000000000000000",
+ "slot": 0,
+ "locationParent": {
+ "refType": "generic",
+ "controllerRef": None,
+ "symbolRef": "0000000000000000000000000000000000000000",
+ "typedReference": None
+ },
+ "locationPosition": 0
+ },
+ "isTrunkCapable": False,
+ "trunkMiswire": False,
+ "protectionInformationCapable": True,
+ "controllerId": "070000000000000000000002",
+ "interfaceId": "2202030000000000000000000000000000000000",
+ "addressId": "20230080E5322230",
+ "niceAddressId": "20:23:00:80:E5:32:22:30"
+ },
]
}
def set_host_group_for_host(self, *args, **kwargs):
pass
- def create_host_with_port(self, *args, **kwargs):
+ def create_host_with_ports(self, *args, **kwargs):
return HOST
def list_hosts(self):
def get_volume_mappings(self):
return [VOLUME_MAPPING]
+ def get_volume_mappings_for_volume(self, volume):
+ return [VOLUME_MAPPING]
+
+ def get_volume_mappings_for_host(self, host_ref):
+ return [VOLUME_MAPPING]
+
+ def get_volume_mappings_for_host_group(self, hg_ref):
+ return [VOLUME_MAPPING]
+
def delete_volume_mapping(self):
return
def delete_snapshot_volume(self, *args, **kwargs):
pass
+
+ def list_target_wwpns(self, *args, **kwargs):
+ return [WWPN_2]
# Copyright (c) 2014 Alex Meade
+# Copyright (c) 2015 Yogesh Kshirsagar
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# License for the specific language governing permissions and limitations
# under the License.
+import copy
+
import mock
from cinder import test
+from cinder.tests.unit.volume.drivers.netapp.eseries import fakes as \
+ eseries_fake
from cinder.volume.drivers.netapp.eseries import client
for call in self.mock_log.debug.mock_calls:
__, args, __ = call
self.assertNotIn(self.fake_password, args[0])
+
+ def test_list_target_wwpns(self):
+ fake_hardware_inventory = copy.deepcopy(
+ eseries_fake.HARDWARE_INVENTORY)
+
+ mock_hardware_inventory = mock.Mock(
+ return_value=fake_hardware_inventory)
+ self.mock_object(self.my_client, 'list_hardware_inventory',
+ mock_hardware_inventory)
+ expected_wwpns = [eseries_fake.WWPN, eseries_fake.WWPN_2]
+
+ actual_wwpns = self.my_client.list_target_wwpns()
+
+ self.assertEqual(expected_wwpns, actual_wwpns)
+
+ def test_list_target_wwpns_single_wwpn(self):
+ fake_hardware_inventory = copy.deepcopy(
+ eseries_fake.HARDWARE_INVENTORY)
+
+ fake_hardware_inventory['fibrePorts'] = [
+ fake_hardware_inventory['fibrePorts'][0]
+ ]
+ mock_hardware_inventory = mock.Mock(
+ return_value=fake_hardware_inventory)
+ self.mock_object(self.my_client, 'list_hardware_inventory',
+ mock_hardware_inventory)
+ expected_wwpns = [eseries_fake.WWPN]
+
+ actual_wwpns = self.my_client.list_target_wwpns()
+
+ self.assertEqual(expected_wwpns, actual_wwpns)
+
+ def test_list_target_wwpns_no_wwpn(self):
+ fake_hardware_inventory = copy.deepcopy(
+ eseries_fake.HARDWARE_INVENTORY)
+
+ fake_hardware_inventory['fibrePorts'] = []
+ mock_hardware_inventory = mock.Mock(
+ return_value=fake_hardware_inventory)
+ self.mock_object(self.my_client, 'list_hardware_inventory',
+ mock_hardware_inventory)
+ expected_wwpns = []
+
+ actual_wwpns = self.my_client.list_target_wwpns()
+
+ self.assertEqual(expected_wwpns, actual_wwpns)
+
+ def test_create_host_from_ports_fc(self):
+ label = 'fake_host'
+ host_type = 'linux'
+ port_type = 'fc'
+ port_ids = [eseries_fake.WWPN, eseries_fake.WWPN_2]
+ expected_ports = [
+ {'type': port_type, 'port': eseries_fake.WWPN, 'label': mock.ANY},
+ {'type': port_type, 'port': eseries_fake.WWPN_2,
+ 'label': mock.ANY}]
+ mock_create_host = self.mock_object(self.my_client, 'create_host')
+
+ self.my_client.create_host_with_ports(label, host_type, port_ids,
+ port_type)
+
+ mock_create_host.assert_called_once_with(label, host_type,
+ expected_ports, None)
+
+ def test_host_from_ports_with_no_ports_provided_fc(self):
+ label = 'fake_host'
+ host_type = 'linux'
+ port_type = 'fc'
+ port_ids = []
+ expected_ports = []
+ mock_create_host = self.mock_object(self.my_client, 'create_host')
+
+ self.my_client.create_host_with_ports(label, host_type, port_ids,
+ port_type)
+
+ mock_create_host.assert_called_once_with(label, host_type,
+ expected_ports, None)
+
+ def test_create_host_from_ports_iscsi(self):
+ label = 'fake_host'
+ host_type = 'linux'
+ port_type = 'iscsi'
+ port_ids = [eseries_fake.INITIATOR_NAME,
+ eseries_fake.INITIATOR_NAME_2]
+ expected_ports = [
+ {'type': port_type, 'port': eseries_fake.INITIATOR_NAME,
+ 'label': mock.ANY},
+ {'type': port_type, 'port': eseries_fake.INITIATOR_NAME_2,
+ 'label': mock.ANY}]
+ mock_create_host = self.mock_object(self.my_client, 'create_host')
+
+ self.my_client.create_host_with_ports(label, host_type, port_ids,
+ port_type)
+
+ mock_create_host.assert_called_once_with(label, host_type,
+ expected_ports, None)
+
+ def test_get_volume_mappings_for_volume(self):
+ volume_mapping_1 = copy.deepcopy(eseries_fake.VOLUME_MAPPING)
+ volume_mapping_2 = copy.deepcopy(eseries_fake.VOLUME_MAPPING)
+ volume_mapping_2['volumeRef'] = '2'
+ self.mock_object(self.my_client, 'get_volume_mappings',
+ mock.Mock(return_value=[volume_mapping_1,
+ volume_mapping_2]))
+
+ mappings = self.my_client.get_volume_mappings_for_volume(
+ eseries_fake.VOLUME)
+
+ self.assertEqual([volume_mapping_1], mappings)
+
+ def test_get_volume_mappings_for_host(self):
+ volume_mapping_1 = copy.deepcopy(
+ eseries_fake.VOLUME_MAPPING)
+ volume_mapping_2 = copy.deepcopy(eseries_fake.VOLUME_MAPPING)
+ volume_mapping_2['volumeRef'] = '2'
+ volume_mapping_2['mapRef'] = 'hostRef'
+ self.mock_object(self.my_client, 'get_volume_mappings',
+ mock.Mock(return_value=[volume_mapping_1,
+ volume_mapping_2]))
+
+ mappings = self.my_client.get_volume_mappings_for_host(
+ 'hostRef')
+
+ self.assertEqual([volume_mapping_2], mappings)
+
+ def test_get_volume_mappings_for_hostgroup(self):
+ volume_mapping_1 = copy.deepcopy(
+ eseries_fake.VOLUME_MAPPING)
+ volume_mapping_2 = copy.deepcopy(eseries_fake.VOLUME_MAPPING)
+ volume_mapping_2['volumeRef'] = '2'
+ volume_mapping_2['mapRef'] = 'hostGroupRef'
+ self.mock_object(self.my_client, 'get_volume_mappings',
+ mock.Mock(return_value=[volume_mapping_1,
+ volume_mapping_2]))
+
+ mappings = self.my_client.get_volume_mappings_for_host_group(
+ 'hostGroupRef')
+
+ self.assertEqual([volume_mapping_2], mappings)
--- /dev/null
+# Copyright (c) 2015 Alex Meade
+# Copyright (c) 2015 Yogesh Kshirsagar
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import mock
+
+from cinder import test
+import cinder.volume.drivers.netapp.eseries.fc_driver as fc
+from cinder.volume.drivers.netapp import utils as na_utils
+
+
+class NetAppESeriesFibreChannelDriverTestCase(test.TestCase):
+
+ @mock.patch.object(na_utils, 'validate_instantiation')
+ def test_instantiation(self, mock_validate_instantiation):
+ fc.NetAppEseriesFibreChannelDriver(configuration=mock.Mock())
+
+ self.assertTrue(mock_validate_instantiation.called)
self.client = eseries_fakes.FakeEseriesClient()
- def test_get_host_mapping_for_vol_frm_array(self):
- volume_mapping_1 = copy.deepcopy(eseries_fakes.VOLUME_MAPPING)
- volume_mapping_2 = copy.deepcopy(eseries_fakes.VOLUME_MAPPING)
- volume_mapping_2['volumeRef'] = '2'
- self.mock_object(self.client, 'get_volume_mappings',
- mock.Mock(return_value=[volume_mapping_1,
- volume_mapping_2]))
- mappings = host_mapper.get_host_mapping_for_vol_frm_array(
- self.client, eseries_fakes.VOLUME)
-
- self.assertEqual([volume_mapping_1], mappings)
-
def test_unmap_volume_from_host_volume_mapped_to_host(self):
fake_eseries_volume = copy.deepcopy(eseries_fakes.VOLUME)
fake_eseries_volume['listOfMappings'] = [
def test_host_full(self):
fake_host = copy.deepcopy(eseries_fakes.HOST)
- self.mock_object(host_mapper, '_get_vol_mapping_for_host_frm_array',
+ self.mock_object(self.client, 'get_volume_mappings_for_host',
mock.Mock(return_value=FAKE_USED_UP_MAPPINGS))
self.assertTrue(host_mapper._is_host_full(self.client, fake_host))
fake_host = copy.deepcopy(eseries_fakes.HOST)
with mock.patch('random.sample') as mock_random:
mock_random.return_value = [3]
- lun = host_mapper._get_free_lun(self.client, fake_host, False)
+ lun = host_mapper._get_free_lun(self.client, fake_host, False,
+ [])
self.assertEqual(3, lun)
def test_get_free_lun_host_full(self):
self.assertRaises(
exception.NetAppDriverException,
host_mapper._get_free_lun,
- self.client, fake_host, False)
+ self.client, fake_host, False, FAKE_USED_UP_MAPPINGS)
def test_get_free_lun_no_unused_luns(self):
fake_host = copy.deepcopy(eseries_fakes.HOST)
- self.mock_object(self.client, 'get_volume_mappings',
- mock.Mock(return_value=FAKE_USED_UP_MAPPINGS))
- lun = host_mapper._get_free_lun(self.client, fake_host, False)
+ lun = host_mapper._get_free_lun(self.client, fake_host, False,
+ FAKE_USED_UP_MAPPINGS)
self.assertEqual(255, lun)
def test_get_free_lun_no_unused_luns_host_not_full(self):
fake_host = copy.deepcopy(eseries_fakes.HOST)
- self.mock_object(self.client, 'get_volume_mappings',
- mock.Mock(return_value=FAKE_USED_UP_MAPPINGS))
self.mock_object(host_mapper, '_is_host_full',
mock.Mock(return_value=False))
- lun = host_mapper._get_free_lun(self.client, fake_host, False)
+ lun = host_mapper._get_free_lun(self.client, fake_host, False,
+ FAKE_USED_UP_MAPPINGS)
self.assertEqual(255, lun)
def test_get_free_lun_no_lun_available(self):
fake_host = copy.deepcopy(eseries_fakes.HOST_3)
- self.mock_object(self.client, 'get_volume_mappings',
- mock.Mock(return_value=FAKE_USED_UP_MAPPINGS))
- self.mock_object(host_mapper, '_get_vol_mapping_for_host_frm_array',
+ self.mock_object(self.client, 'get_volume_mappings_for_host',
mock.Mock(return_value=FAKE_USED_UP_MAPPINGS))
self.assertRaises(exception.NetAppDriverException,
host_mapper._get_free_lun,
- self.client, fake_host, False)
+ self.client, fake_host, False,
+ FAKE_USED_UP_MAPPINGS)
def test_get_free_lun_multiattach_enabled_no_unused_ids(self):
fake_host = copy.deepcopy(eseries_fakes.HOST_3)
self.assertRaises(exception.NetAppDriverException,
host_mapper._get_free_lun,
- self.client, fake_host, True)
+ self.client, fake_host, True,
+ FAKE_USED_UP_MAPPINGS)
def test_get_lun_by_mapping(self):
used_luns = host_mapper._get_used_lun_ids_for_mappings(FAKE_MAPPINGS)
def test_no_lun_id_available_on_host(self):
fake_host = copy.deepcopy(eseries_fakes.HOST_3)
- self.mock_object(host_mapper, '_get_vol_mapping_for_host_frm_array',
+ self.mock_object(self.client, 'get_volume_mappings_for_host',
mock.Mock(return_value=FAKE_USED_UP_MAPPINGS))
self.assertFalse(host_mapper._is_lun_id_available_on_host(
-# Copyright (c) 2014 Andrew Kerr. All rights reserved.
-# Copyright (c) 2015 Alex Meade. All rights reserved.
-# Copyright (c) 2015 Rushil Chugh. All rights reserved.
+# Copyright (c) 2014 Andrew Kerr
+# Copyright (c) 2015 Alex Meade
+# Copyright (c) 2015 Rushil Chugh
+# Copyright (c) 2015 Yogesh Kshirsagar
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# under the License.
import copy
+import ddt
import mock
-import six
from cinder import exception
from cinder import test
from cinder.tests.unit import fake_snapshot
from cinder.tests.unit.volume.drivers.netapp.eseries import fakes as \
- eseries_fakes
+ eseries_fake
from cinder.volume.drivers.netapp.eseries import client as es_client
+from cinder.volume.drivers.netapp.eseries import exception as eseries_exc
from cinder.volume.drivers.netapp.eseries import host_mapper
from cinder.volume.drivers.netapp.eseries import library
from cinder.volume.drivers.netapp.eseries import utils
from cinder.volume.drivers.netapp import utils as na_utils
+from cinder.zonemanager import utils as fczm_utils
def get_fake_volume():
}
+@ddt.ddt
class NetAppEseriesLibraryTestCase(test.TestCase):
def setUp(self):
super(NetAppEseriesLibraryTestCase, self).setUp()
kwargs = {'configuration':
- eseries_fakes.create_configuration_eseries()}
+ eseries_fake.create_configuration_eseries()}
self.library = library.NetAppESeriesLibrary('FAKE', **kwargs)
- self.library._client = eseries_fakes.FakeEseriesClient()
+ self.library._client = eseries_fake.FakeEseriesClient()
self.library.check_for_setup_error()
def test_do_setup(self):
self.mock_object(self.library,
'_check_mode_get_or_register_storage_system')
self.mock_object(es_client, 'RestClient',
- eseries_fakes.FakeEseriesClient)
+ eseries_fake.FakeEseriesClient)
mock_check_flags = self.mock_object(na_utils, 'check_flags')
self.library.do_setup(mock.Mock())
ssc_stats)
def test_terminate_connection_iscsi_no_hosts(self):
- connector = {'initiator': eseries_fakes.INITIATOR_NAME}
+ connector = {'initiator': eseries_fake.INITIATOR_NAME}
self.mock_object(self.library._client, 'list_hosts',
mock.Mock(return_value=[]))
connector)
def test_terminate_connection_iscsi_volume_not_mapped(self):
- connector = {'initiator': eseries_fakes.INITIATOR_NAME}
- err = self.assertRaises(exception.NetAppDriverException,
- self.library.terminate_connection_iscsi,
- get_fake_volume(),
- connector)
- self.assertIn("not currently mapped to host", six.text_type(err))
+ connector = {'initiator': eseries_fake.INITIATOR_NAME}
+ self.assertRaises(eseries_exc.VolumeNotMapped,
+ self.library.terminate_connection_iscsi,
+ get_fake_volume(),
+ connector)
def test_terminate_connection_iscsi_volume_mapped(self):
- connector = {'initiator': eseries_fakes.INITIATOR_NAME}
- fake_eseries_volume = copy.deepcopy(eseries_fakes.VOLUME)
+ connector = {'initiator': eseries_fake.INITIATOR_NAME}
+ fake_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
fake_eseries_volume['listOfMappings'] = [
- eseries_fakes.VOLUME_MAPPING
+ eseries_fake.VOLUME_MAPPING
]
self.mock_object(self.library._client, 'list_volumes',
mock.Mock(return_value=[fake_eseries_volume]))
def test_terminate_connection_iscsi_not_mapped_initiator_does_not_exist(
self):
- connector = {'initiator': eseries_fakes.INITIATOR_NAME}
+ connector = {'initiator': eseries_fake.INITIATOR_NAME}
self.mock_object(self.library._client, 'list_hosts',
- mock.Mock(return_value=[eseries_fakes.HOST_2]))
+ mock.Mock(return_value=[eseries_fake.HOST_2]))
self.assertRaises(exception.NotFound,
self.library.terminate_connection_iscsi,
get_fake_volume(),
connector)
def test_initialize_connection_iscsi_volume_not_mapped(self):
- connector = {'initiator': eseries_fakes.INITIATOR_NAME}
- self.mock_object(self.library._client, 'get_volume_mappings',
+ connector = {'initiator': eseries_fake.INITIATOR_NAME}
+ self.mock_object(self.library._client,
+ 'get_volume_mappings_for_volume',
mock.Mock(return_value=[]))
self.mock_object(host_mapper, 'map_volume_to_single_host',
mock.Mock(
- return_value=eseries_fakes.VOLUME_MAPPING))
+ return_value=eseries_fake.VOLUME_MAPPING))
self.library.initialize_connection_iscsi(get_fake_volume(), connector)
- self.assertTrue(self.library._client.get_volume_mappings.called)
+ self.assertTrue(
+ self.library._client.get_volume_mappings_for_volume.called)
self.assertTrue(host_mapper.map_volume_to_single_host.called)
def test_initialize_connection_iscsi_volume_not_mapped_host_does_not_exist(
self):
- connector = {'initiator': eseries_fakes.INITIATOR_NAME}
- self.mock_object(self.library._client, 'get_volume_mappings',
+ connector = {'initiator': eseries_fake.INITIATOR_NAME}
+ self.mock_object(self.library._client,
+ 'get_volume_mappings_for_volume',
mock.Mock(return_value=[]))
self.mock_object(self.library._client, 'list_hosts',
mock.Mock(return_value=[]))
- self.mock_object(self.library._client, 'create_host_with_port',
- mock.Mock(return_value=eseries_fakes.HOST))
+ self.mock_object(self.library._client, 'create_host_with_ports',
+ mock.Mock(return_value=eseries_fake.HOST))
self.mock_object(host_mapper, 'map_volume_to_single_host',
mock.Mock(
- return_value=eseries_fakes.VOLUME_MAPPING))
+ return_value=eseries_fake.VOLUME_MAPPING))
self.library.initialize_connection_iscsi(get_fake_volume(), connector)
- self.assertTrue(self.library._client.get_volume_mappings.called)
+ self.assertTrue(
+ self.library._client.get_volume_mappings_for_volume.called)
self.assertTrue(self.library._client.list_hosts.called)
- self.assertTrue(self.library._client.create_host_with_port.called)
+ self.assertTrue(self.library._client.create_host_with_ports.called)
self.assertTrue(host_mapper.map_volume_to_single_host.called)
def test_initialize_connection_iscsi_volume_already_mapped_to_target_host(
self):
"""Should be a no-op"""
- connector = {'initiator': eseries_fakes.INITIATOR_NAME}
+ connector = {'initiator': eseries_fake.INITIATOR_NAME}
self.mock_object(host_mapper, 'map_volume_to_single_host',
mock.Mock(
- return_value=eseries_fakes.VOLUME_MAPPING))
+ return_value=eseries_fake.VOLUME_MAPPING))
self.library.initialize_connection_iscsi(get_fake_volume(), connector)
def test_initialize_connection_iscsi_volume_mapped_to_another_host(self):
"""Should raise error saying multiattach not enabled"""
- connector = {'initiator': eseries_fakes.INITIATOR_NAME}
+ connector = {'initiator': eseries_fake.INITIATOR_NAME}
fake_mapping_to_other_host = copy.deepcopy(
- eseries_fakes.VOLUME_MAPPING)
- fake_mapping_to_other_host['mapRef'] = eseries_fakes.HOST_2[
+ eseries_fake.VOLUME_MAPPING)
+ fake_mapping_to_other_host['mapRef'] = eseries_fake.HOST_2[
'hostRef']
self.mock_object(host_mapper, 'map_volume_to_single_host',
mock.Mock(
self.assertTrue(host_mapper.map_volume_to_single_host.called)
+ @ddt.data(eseries_fake.WWPN,
+ fczm_utils.get_formatted_wwn(eseries_fake.WWPN))
+ def test_get_host_with_matching_port_wwpn(self, port_id):
+ port_ids = [port_id]
+ host = copy.deepcopy(eseries_fake.HOST)
+ host.update(
+ {
+ 'hostSidePorts': [{'label': 'NewStore', 'type': 'fc',
+ 'address': eseries_fake.WWPN}]
+ }
+ )
+ host_2 = copy.deepcopy(eseries_fake.HOST_2)
+ host_2.update(
+ {
+ 'hostSidePorts': [{'label': 'NewStore', 'type': 'fc',
+ 'address': eseries_fake.WWPN_2}]
+ }
+ )
+ host_list = [host, host_2]
+ self.mock_object(self.library._client,
+ 'list_hosts',
+ mock.Mock(return_value=host_list))
+
+ actual_host = self.library._get_host_with_matching_port(
+ port_ids)
+
+ self.assertEqual(host, actual_host)
+
+ def test_get_host_with_matching_port_iqn(self):
+ port_ids = [eseries_fake.INITIATOR_NAME]
+ host = copy.deepcopy(eseries_fake.HOST)
+ host.update(
+ {
+ 'hostSidePorts': [{'label': 'NewStore', 'type': 'iscsi',
+ 'address': eseries_fake.INITIATOR_NAME}]
+ }
+ )
+ host_2 = copy.deepcopy(eseries_fake.HOST_2)
+ host_2.update(
+ {
+ 'hostSidePorts': [{'label': 'NewStore', 'type': 'iscsi',
+ 'address': eseries_fake.INITIATOR_NAME_2}]
+ }
+ )
+ host_list = [host, host_2]
+ self.mock_object(self.library._client,
+ 'list_hosts',
+ mock.Mock(return_value=host_list))
+
+ actual_host = self.library._get_host_with_matching_port(
+ port_ids)
+
+ self.assertEqual(host, actual_host)
+
+ def test_terminate_connection_fc_no_hosts(self):
+ connector = {'wwpns': [eseries_fake.WWPN]}
+
+ self.mock_object(self.library._client, 'list_hosts',
+ mock.Mock(return_value=[]))
+
+ self.assertRaises(exception.NotFound,
+ self.library.terminate_connection_fc,
+ get_fake_volume(),
+ connector)
+
+ def test_terminate_connection_fc_volume_not_mapped(self):
+ connector = {'wwpns': [eseries_fake.WWPN]}
+ fake_host = copy.deepcopy(eseries_fake.HOST)
+ fake_host['hostSidePorts'] = [{
+ 'label': 'NewStore',
+ 'type': 'fc',
+ 'address': eseries_fake.WWPN
+ }]
+
+ self.mock_object(self.library._client, 'list_hosts',
+ mock.Mock(return_value=[fake_host]))
+
+ self.assertRaises(eseries_exc.VolumeNotMapped,
+ self.library.terminate_connection_fc,
+ get_fake_volume(),
+ connector)
+
+ def test_terminate_connection_fc_volume_mapped(self):
+ connector = {'wwpns': [eseries_fake.WWPN]}
+ fake_host = copy.deepcopy(eseries_fake.HOST)
+ fake_host['hostSidePorts'] = [{
+ 'label': 'NewStore',
+ 'type': 'fc',
+ 'address': eseries_fake.WWPN
+ }]
+ fake_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
+ fake_eseries_volume['listOfMappings'] = [
+ copy.deepcopy(eseries_fake.VOLUME_MAPPING)
+ ]
+ self.mock_object(self.library._client, 'list_hosts',
+ mock.Mock(return_value=[fake_host]))
+ self.mock_object(self.library._client, 'list_volumes',
+ mock.Mock(return_value=[fake_eseries_volume]))
+ self.mock_object(host_mapper, 'unmap_volume_from_host')
+
+ self.library.terminate_connection_fc(get_fake_volume(), connector)
+
+ self.assertTrue(host_mapper.unmap_volume_from_host.called)
+
+ def test_terminate_connection_fc_volume_mapped_no_cleanup_zone(self):
+ connector = {'wwpns': [eseries_fake.WWPN]}
+ fake_host = copy.deepcopy(eseries_fake.HOST)
+ fake_host['hostSidePorts'] = [{
+ 'label': 'NewStore',
+ 'type': 'fc',
+ 'address': eseries_fake.WWPN
+ }]
+ expected_target_info = {
+ 'driver_volume_type': 'fibre_channel',
+ 'data': {},
+ }
+ fake_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
+ fake_eseries_volume['listOfMappings'] = [
+ copy.deepcopy(eseries_fake.VOLUME_MAPPING)
+ ]
+ self.mock_object(self.library._client, 'list_hosts',
+ mock.Mock(return_value=[fake_host]))
+ self.mock_object(self.library._client, 'list_volumes',
+ mock.Mock(return_value=[fake_eseries_volume]))
+ self.mock_object(host_mapper, 'unmap_volume_from_host')
+ self.mock_object(self.library._client, 'get_volume_mappings_for_host',
+ mock.Mock(return_value=[copy.deepcopy
+ (eseries_fake.
+ VOLUME_MAPPING)]))
+
+ target_info = self.library.terminate_connection_fc(get_fake_volume(),
+ connector)
+ self.assertDictEqual(expected_target_info, target_info)
+
+ self.assertTrue(host_mapper.unmap_volume_from_host.called)
+
+ def test_terminate_connection_fc_volume_mapped_cleanup_zone(self):
+ connector = {'wwpns': [eseries_fake.WWPN]}
+ fake_host = copy.deepcopy(eseries_fake.HOST)
+ fake_host['hostSidePorts'] = [{
+ 'label': 'NewStore',
+ 'type': 'fc',
+ 'address': eseries_fake.WWPN
+ }]
+ expected_target_info = {
+ 'driver_volume_type': 'fibre_channel',
+ 'data': {
+ 'target_wwn': [eseries_fake.WWPN_2],
+ 'initiator_target_map': {
+ eseries_fake.WWPN: [eseries_fake.WWPN_2]
+ },
+ },
+ }
+ fake_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
+ fake_eseries_volume['listOfMappings'] = [
+ copy.deepcopy(eseries_fake.VOLUME_MAPPING)
+ ]
+ self.mock_object(self.library._client, 'list_hosts',
+ mock.Mock(return_value=[fake_host]))
+ self.mock_object(self.library._client, 'list_volumes',
+ mock.Mock(return_value=[fake_eseries_volume]))
+ self.mock_object(host_mapper, 'unmap_volume_from_host')
+ self.mock_object(self.library._client, 'get_volume_mappings_for_host',
+ mock.Mock(return_value=[]))
+
+ target_info = self.library.terminate_connection_fc(get_fake_volume(),
+ connector)
+ self.assertDictEqual(expected_target_info, target_info)
+
+ self.assertTrue(host_mapper.unmap_volume_from_host.called)
+
+ def test_terminate_connection_fc_not_mapped_host_with_wwpn_does_not_exist(
+ self):
+ connector = {'wwpns': [eseries_fake.WWPN]}
+ self.mock_object(self.library._client, 'list_hosts',
+ mock.Mock(return_value=[eseries_fake.HOST_2]))
+ self.assertRaises(exception.NotFound,
+ self.library.terminate_connection_fc,
+ get_fake_volume(),
+ connector)
+
+ def test_initialize_connection_fc_volume_not_mapped(self):
+ connector = {'wwpns': [eseries_fake.WWPN]}
+ self.mock_object(self.library._client,
+ 'get_volume_mappings_for_volume',
+ mock.Mock(return_value=[]))
+ self.mock_object(host_mapper, 'map_volume_to_single_host',
+ mock.Mock(
+ return_value=eseries_fake.VOLUME_MAPPING))
+ expected_target_info = {
+ 'driver_volume_type': 'fibre_channel',
+ 'data': {
+ 'target_discovered': True,
+ 'target_lun': 0,
+ 'target_wwn': [eseries_fake.WWPN_2],
+ 'access_mode': 'rw',
+ 'initiator_target_map': {
+ eseries_fake.WWPN: [eseries_fake.WWPN_2]
+ },
+ },
+ }
+
+ target_info = self.library.initialize_connection_fc(get_fake_volume(),
+ connector)
+
+ self.assertTrue(
+ self.library._client.get_volume_mappings_for_volume.called)
+ self.assertTrue(host_mapper.map_volume_to_single_host.called)
+ self.assertDictEqual(expected_target_info, target_info)
+
+ def test_initialize_connection_fc_volume_not_mapped_host_does_not_exist(
+ self):
+ connector = {'wwpns': [eseries_fake.WWPN]}
+ self.library.driver_protocol = 'FC'
+ self.mock_object(self.library._client,
+ 'get_volume_mappings_for_volume',
+ mock.Mock(return_value=[]))
+ self.mock_object(self.library._client, 'list_hosts',
+ mock.Mock(return_value=[]))
+ self.mock_object(self.library._client, 'create_host_with_ports',
+ mock.Mock(return_value=eseries_fake.HOST))
+ self.mock_object(host_mapper, 'map_volume_to_single_host',
+ mock.Mock(
+ return_value=eseries_fake.VOLUME_MAPPING))
+
+ self.library.initialize_connection_fc(get_fake_volume(), connector)
+
+ self.library._client.create_host_with_ports.assert_called_once_with(
+ mock.ANY, mock.ANY,
+ [fczm_utils.get_formatted_wwn(eseries_fake.WWPN)],
+ port_type='fc', group_id=None
+ )
+
+ def test_initialize_connection_fc_volume_already_mapped_to_target_host(
+ self):
+ """Should be a no-op"""
+ connector = {'wwpns': [eseries_fake.WWPN]}
+ self.mock_object(host_mapper, 'map_volume_to_single_host',
+ mock.Mock(
+ return_value=eseries_fake.VOLUME_MAPPING))
+
+ self.library.initialize_connection_fc(get_fake_volume(), connector)
+
+ self.assertTrue(host_mapper.map_volume_to_single_host.called)
+
+ def test_initialize_connection_fc_volume_mapped_to_another_host(self):
+ """Should raise error saying multiattach not enabled"""
+ connector = {'wwpns': [eseries_fake.WWPN]}
+ fake_mapping_to_other_host = copy.deepcopy(
+ eseries_fake.VOLUME_MAPPING)
+ fake_mapping_to_other_host['mapRef'] = eseries_fake.HOST_2[
+ 'hostRef']
+ self.mock_object(host_mapper, 'map_volume_to_single_host',
+ mock.Mock(
+ side_effect=exception.NetAppDriverException))
+
+ self.assertRaises(exception.NetAppDriverException,
+ self.library.initialize_connection_fc,
+ get_fake_volume(), connector)
+
+ self.assertTrue(host_mapper.map_volume_to_single_host.called)
+
+ def test_initialize_connection_fc_no_target_wwpns(self):
+ """Should be a no-op"""
+ connector = {'wwpns': [eseries_fake.WWPN]}
+ self.mock_object(host_mapper, 'map_volume_to_single_host',
+ mock.Mock(
+ return_value=eseries_fake.VOLUME_MAPPING))
+ self.mock_object(self.library._client, 'list_target_wwpns',
+ mock.Mock(return_value=[]))
+
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.library.initialize_connection_fc,
+ get_fake_volume(), connector)
+ self.assertTrue(host_mapper.map_volume_to_single_host.called)
+
+ def test_build_initiator_target_map_fc_with_lookup_service(
+ self):
+ connector = {'wwpns': [eseries_fake.WWPN, eseries_fake.WWPN_2]}
+ self.library.lookup_service = mock.Mock()
+ self.library.lookup_service.get_device_mapping_from_network = (
+ mock.Mock(return_value=eseries_fake.FC_FABRIC_MAP))
+
+ (target_wwpns, initiator_target_map, num_paths) = (
+ self.library._build_initiator_target_map_fc(connector))
+
+ self.assertSetEqual(set(eseries_fake.FC_TARGET_WWPNS),
+ set(target_wwpns))
+ self.assertDictEqual(eseries_fake.FC_I_T_MAP, initiator_target_map)
+ self.assertEqual(4, num_paths)
+
class NetAppEseriesLibraryMultiAttachTestCase(test.TestCase):
"""Test driver behavior when the netapp_enable_multiattach
def setUp(self):
super(NetAppEseriesLibraryMultiAttachTestCase, self).setUp()
- config = eseries_fakes.create_configuration_eseries()
+ config = eseries_fake.create_configuration_eseries()
config.netapp_enable_multiattach = True
kwargs = {'configuration': config}
self.library = library.NetAppESeriesLibrary("FAKE", **kwargs)
- self.library._client = eseries_fakes.FakeEseriesClient()
+ self.library._client = eseries_fake.FakeEseriesClient()
self.library.check_for_setup_error()
def test_do_setup_host_group_already_exists(self):
mock_check_flags = self.mock_object(na_utils, 'check_flags')
self.mock_object(self.library,
'_check_mode_get_or_register_storage_system')
- fake_rest_client = eseries_fakes.FakeEseriesClient()
+ fake_rest_client = eseries_fake.FakeEseriesClient()
self.mock_object(self.library, '_create_rest_client',
mock.Mock(return_value=fake_rest_client))
mock_create = self.mock_object(fake_rest_client, 'create_host_group')
def test_do_setup_host_group_does_not_exist(self):
mock_check_flags = self.mock_object(na_utils, 'check_flags')
- fake_rest_client = eseries_fakes.FakeEseriesClient()
+ fake_rest_client = eseries_fake.FakeEseriesClient()
self.mock_object(self.library, '_create_rest_client',
mock.Mock(return_value=fake_rest_client))
mock_get_host_group = self.mock_object(
def test_create_volume(self):
self.library._client.create_volume = mock.Mock(
- return_value=eseries_fakes.VOLUME)
+ return_value=eseries_fake.VOLUME)
self.library.create_volume(get_fake_volume())
self.assertTrue(self.library._client.create_volume.call_count)
def test_create_volume_too_many_volumes(self):
self.library._client.list_volumes = mock.Mock(
- return_value=[eseries_fakes.VOLUME for __ in
+ return_value=[eseries_fake.VOLUME for __ in
range(utils.MAX_LUNS_PER_HOST_GROUP + 1)])
self.library._client.create_volume = mock.Mock(
- return_value=eseries_fakes.VOLUME)
+ return_value=eseries_fake.VOLUME)
self.assertRaises(exception.NetAppDriverException,
self.library.create_volume,
self.assertFalse(self.library._client.create_volume.call_count)
def test_create_volume_from_snapshot(self):
- fake_eseries_volume = copy.deepcopy(eseries_fakes.VOLUME)
+ fake_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
self.mock_object(self.library, "_schedule_and_create_volume",
mock.Mock(return_value=fake_eseries_volume))
self.mock_object(self.library, "_create_snapshot_volume",
1, self.library._client.delete_snapshot_volume.call_count)
def test_create_volume_from_snapshot_create_fails(self):
- fake_dest_eseries_volume = copy.deepcopy(eseries_fakes.VOLUME)
+ fake_dest_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
self.mock_object(self.library, "_schedule_and_create_volume",
mock.Mock(return_value=fake_dest_eseries_volume))
self.mock_object(self.library, "_create_snapshot_volume",
fake_dest_eseries_volume['volumeRef'])
def test_create_volume_from_snapshot_copy_job_fails(self):
- fake_dest_eseries_volume = copy.deepcopy(eseries_fakes.VOLUME)
+ fake_dest_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
self.mock_object(self.library, "_schedule_and_create_volume",
mock.Mock(return_value=fake_dest_eseries_volume))
self.mock_object(self.library, "_create_snapshot_volume",
self.mock_object(self.library._client, "delete_volume")
fake_failed_volume_copy_job = copy.deepcopy(
- eseries_fakes.VOLUME_COPY_JOB)
+ eseries_fake.VOLUME_COPY_JOB)
fake_failed_volume_copy_job['status'] = 'failed'
self.mock_object(self.library._client,
"create_volume_copy_job",
fake_dest_eseries_volume['volumeRef'])
def test_create_volume_from_snapshot_fail_to_delete_snapshot_volume(self):
- fake_dest_eseries_volume = copy.deepcopy(eseries_fakes.VOLUME)
+ fake_dest_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
fake_dest_eseries_volume['volumeRef'] = 'fake_volume_ref'
self.mock_object(self.library, "_schedule_and_create_volume",
mock.Mock(return_value=fake_dest_eseries_volume))
self.mock_object(self.library, "_create_snapshot_volume",
mock.Mock(return_value=copy.deepcopy(
- eseries_fakes.VOLUME)))
+ eseries_fake.VOLUME)))
self.mock_object(self.library._client, "delete_snapshot_volume",
mock.Mock(side_effect=exception.NetAppDriverException)
)
def test_map_volume_to_host_volume_not_mapped(self):
"""Map the volume directly to destination host."""
- self.mock_object(self.library._client, 'get_volume_mappings',
+ self.mock_object(self.library._client,
+ 'get_volume_mappings_for_volume',
mock.Mock(return_value=[]))
self.mock_object(host_mapper, 'map_volume_to_single_host',
mock.Mock(
- return_value=eseries_fakes.VOLUME_MAPPING))
+ return_value=eseries_fake.VOLUME_MAPPING))
self.library.map_volume_to_host(get_fake_volume(),
- eseries_fakes.VOLUME,
- eseries_fakes.INITIATOR_NAME_2)
+ eseries_fake.VOLUME,
+ eseries_fake.INITIATOR_NAME_2)
- self.assertTrue(self.library._client.get_volume_mappings.called)
+ self.assertTrue(
+ self.library._client.get_volume_mappings_for_volume.called)
self.assertTrue(host_mapper.map_volume_to_single_host.called)
def test_map_volume_to_host_volume_not_mapped_host_does_not_exist(self):
"""Should create the host map directly to the host."""
self.mock_object(self.library._client, 'list_hosts',
mock.Mock(return_value=[]))
- self.mock_object(self.library._client, 'create_host_with_port',
+ self.mock_object(self.library._client, 'create_host_with_ports',
mock.Mock(
- return_value=eseries_fakes.HOST_2))
- self.mock_object(self.library._client, 'get_volume_mappings',
+ return_value=eseries_fake.HOST_2))
+ self.mock_object(self.library._client,
+ 'get_volume_mappings_for_volume',
mock.Mock(return_value=[]))
self.mock_object(host_mapper, 'map_volume_to_single_host',
mock.Mock(
- return_value=eseries_fakes.VOLUME_MAPPING))
+ return_value=eseries_fake.VOLUME_MAPPING))
self.library.map_volume_to_host(get_fake_volume(),
- eseries_fakes.VOLUME,
- eseries_fakes.INITIATOR_NAME_2)
+ eseries_fake.VOLUME,
+ eseries_fake.INITIATOR_NAME_2)
- self.assertTrue(self.library._client.create_host_with_port.called)
- self.assertTrue(self.library._client.get_volume_mappings.called)
+ self.assertTrue(self.library._client.create_host_with_ports.called)
+ self.assertTrue(
+ self.library._client.get_volume_mappings_for_volume.called)
self.assertTrue(host_mapper.map_volume_to_single_host.called)
def test_map_volume_to_host_volume_already_mapped(self):
"""Should be a no-op."""
self.mock_object(host_mapper, 'map_volume_to_multiple_hosts',
mock.Mock(
- return_value=eseries_fakes.VOLUME_MAPPING))
+ return_value=eseries_fake.VOLUME_MAPPING))
self.library.map_volume_to_host(get_fake_volume(),
- eseries_fakes.VOLUME,
- eseries_fakes.INITIATOR_NAME)
+ eseries_fake.VOLUME,
+ eseries_fake.INITIATOR_NAME)
self.assertTrue(host_mapper.map_volume_to_multiple_hosts.called)
# Copyright (c) 2014 Navneet Singh. All rights reserved.
# Copyright (c) 2014 Clinton Knight. All rights reserved.
+# Copyright (c) 2015 Alex Meade. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
},
'eseries':
{
- 'iscsi': ESERIES_PATH + '.iscsi_driver.NetAppEseriesISCSIDriver'
+ 'iscsi': ESERIES_PATH + '.iscsi_driver.NetAppEseriesISCSIDriver',
+ 'fc': ESERIES_PATH + '.fc_driver.NetAppEseriesFibreChannelDriver'
}}
-# Copyright (c) 2014 NetApp, Inc. All rights reserved.
-# Copyright (c) 2014 Navneet Singh. All rights reserved.
-# Copyright (c) 2015 Alex Meade. All Rights Reserved.
-# Copyright (c) 2015 Rushil Chugh. All Rights Reserved.
+# Copyright (c) 2014 NetApp, Inc
+# Copyright (c) 2014 Navneet Singh
+# Copyright (c) 2015 Alex Meade
+# Copyright (c) 2015 Rushil Chugh
+# Copyright (c) 2015 Yogesh Kshirsagar
+# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
import copy
import json
+import uuid
from oslo_log import log as logging
import requests
+import six
from six.moves import urllib
from cinder import exception
path = "/storage-systems/{system-id}/volume-mappings"
return self._invoke('GET', path)
+ def get_volume_mappings_for_volume(self, volume):
+ """Gets all host mappings for given volume from array."""
+ mappings = self.get_volume_mappings() or []
+ host_maps = filter(lambda x: x.get('volumeRef') == volume['volumeRef'],
+ mappings)
+ return host_maps
+
+ def get_volume_mappings_for_host(self, host_ref):
+ """Gets all volume mappings for given host from array."""
+ mappings = self.get_volume_mappings() or []
+ host_maps = filter(lambda x: x.get('mapRef') == host_ref, mappings)
+ return host_maps
+
+ def get_volume_mappings_for_host_group(self, hg_ref):
+ """Gets all volume mappings for given host group from array."""
+ mappings = self.get_volume_mappings() or []
+ hg_maps = filter(lambda x: x.get('mapRef') == hg_ref, mappings)
+ return hg_maps
+
def create_volume_mapping(self, object_id, target_id, lun):
"""Creates volume mapping on array."""
path = "/storage-systems/{system-id}/volume-mappings"
path = "/storage-systems/{system-id}/hardware-inventory"
return self._invoke('GET', path)
+ def list_target_wwpns(self):
+ """Lists the world-wide port names of the target."""
+ inventory = self.list_hardware_inventory()
+ fc_ports = inventory.get("fibrePorts", [])
+ wwpns = [port['portName'] for port in fc_ports]
+ return wwpns
+
def create_host_group(self, label):
"""Creates a host group on the array."""
path = "/storage-systems/{system-id}/host-groups"
data.setdefault('ports', ports if ports else None)
return self._invoke('POST', path, data)
- def create_host_with_port(self, label, host_type, port_id,
- port_label, port_type='iscsi', group_id=None):
+ def create_host_with_ports(self, label, host_type, port_ids,
+ port_type='iscsi', group_id=None):
"""Creates host on array with given port information."""
- port = {'type': port_type, 'port': port_id, 'label': port_label}
- return self.create_host(label, host_type, [port], group_id)
+ if port_type == 'fc':
+ port_ids = [six.text_type(wwpn).replace(':', '')
+ for wwpn in port_ids]
+ ports = []
+ for port_id in port_ids:
+ port_label = utils.convert_uuid_to_es_fmt(uuid.uuid4())
+ port = {'type': port_type, 'port': port_id, 'label': port_label}
+ ports.append(port)
+ return self.create_host(label, host_type, ports, group_id)
def update_host(self, host_ref, data):
"""Updates host type for a given host."""
--- /dev/null
+# Copyright (c) - 2014, Alex Meade. All rights reserved.
+# Copyright (c) - 2015, Yogesh Kshirsagar. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""
+Volume driver for NetApp E-Series FibreChannel storage systems.
+"""
+
+from oslo_log import log as logging
+
+from cinder.volume import driver
+from cinder.volume.drivers.netapp.eseries import library
+from cinder.volume.drivers.netapp import utils as na_utils
+from cinder.zonemanager import utils as fczm_utils
+
+LOG = logging.getLogger(__name__)
+
+
+class NetAppEseriesFibreChannelDriver(driver.BaseVD,
+ driver.ManageableVD,
+ driver.ExtendVD,
+ driver.CloneableVD,
+ driver.TransferVD,
+ driver.SnapshotVD):
+ """NetApp E-Series FibreChannel volume driver."""
+
+ DRIVER_NAME = 'NetApp_FibreChannel_ESeries'
+
+ def __init__(self, *args, **kwargs):
+ super(NetAppEseriesFibreChannelDriver, self).__init__(*args, **kwargs)
+ na_utils.validate_instantiation(**kwargs)
+ self.library = library.NetAppESeriesLibrary(self.DRIVER_NAME,
+ 'FC', **kwargs)
+
+ def do_setup(self, context):
+ self.library.do_setup(context)
+
+ def check_for_setup_error(self):
+ self.library.check_for_setup_error()
+
+ def create_volume(self, volume):
+ self.library.create_volume(volume)
+
+ def create_volume_from_snapshot(self, volume, snapshot):
+ self.library.create_volume_from_snapshot(volume, snapshot)
+
+ def create_cloned_volume(self, volume, src_vref):
+ self.library.create_cloned_volume(volume, src_vref)
+
+ def delete_volume(self, volume):
+ self.library.delete_volume(volume)
+
+ def create_snapshot(self, snapshot):
+ self.library.create_snapshot(snapshot)
+
+ def delete_snapshot(self, snapshot):
+ self.library.delete_snapshot(snapshot)
+
+ def get_volume_stats(self, refresh=False):
+ return self.library.get_volume_stats(refresh)
+
+ def extend_volume(self, volume, new_size):
+ self.library.extend_volume(volume, new_size)
+
+ def ensure_export(self, context, volume):
+ return self.library.ensure_export(context, volume)
+
+ def create_export(self, context, volume):
+ return self.library.create_export(context, volume)
+
+ def remove_export(self, context, volume):
+ self.library.remove_export(context, volume)
+
+ def manage_existing(self, volume, existing_ref):
+ return self.library.manage_existing(volume, existing_ref)
+
+ def manage_existing_get_size(self, volume, existing_ref):
+ return self.library.manage_existing_get_size(volume, existing_ref)
+
+ def unmanage(self, volume):
+ return self.library.unmanage(volume)
+
+ @fczm_utils.AddFCZone
+ def initialize_connection(self, volume, connector, **kwargs):
+ return self.library.initialize_connection_fc(volume, connector)
+
+ @fczm_utils.RemoveFCZone
+ def terminate_connection(self, volume, connector, **kwargs):
+ return self.library.terminate_connection_fc(volume, connector,
+ **kwargs)
+
+ def get_pool(self, volume):
+ return self.library.get_pool(volume)
# Copyright (c) 2015 Alex Meade. All Rights Reserved.
+# Copyright (c) 2015 Yogesh Kshirsagar. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# If volume is not mapped on the backend, map directly to host
if not vol_map:
- lun = _get_free_lun(client, host, multiattach_enabled)
+ mappings = client.get_volume_mappings_for_host(host['hostRef'])
+ lun = _get_free_lun(client, host, multiattach_enabled, mappings)
return client.create_volume_mapping(eseries_vol['volumeRef'],
host['hostRef'], lun)
LOG.debug("Volume %(vol)s is not currently attached, moving "
"existing mapping to host %(host)s.",
{'vol': volume['id'], 'host': host['label']})
- lun = _get_free_lun(client, host, multiattach_enabled)
+ mappings = client.get_volume_mappings_for_host(
+ host['hostRef'])
+ lun = _get_free_lun(client, host, multiattach_enabled, mappings)
return client.move_volume_mapping_via_symbol(
vol_map.get('mapRef'), host['hostRef'], lun
)
return mapping
-def _get_free_lun(client, host, multiattach_enabled):
+def _get_free_lun(client, host, multiattach_enabled, mappings):
"""Returns least used LUN ID available on the given host."""
- mappings = client.get_volume_mappings()
if not _is_host_full(client, host):
unused_luns = _get_unused_lun_ids(mappings)
if unused_luns:
def _is_host_full(client, host):
"""Checks whether maximum volumes attached to a host have been reached."""
- luns = _get_vol_mapping_for_host_frm_array(client, host['hostRef'])
+ luns = client.get_volume_mappings_for_host(host['hostRef'])
return len(luns) >= utils.MAX_LUNS_PER_HOST
def _is_lun_id_available_on_host(client, host, lun_id):
"""Returns a boolean value depending on whether a LUN ID is available."""
- mapping = _get_vol_mapping_for_host_frm_array(client, host['hostRef'])
+ mapping = client.get_volume_mappings_for_host(host['hostRef'])
used_lun_ids = _get_used_lun_ids_for_mappings(mapping)
return lun_id not in used_lun_ids
return used_luns
-def _get_vol_mapping_for_host_frm_array(client, host_ref):
- """Gets all volume mappings for given host from array."""
- mappings = client.get_volume_mappings() or []
- host_maps = filter(lambda x: x.get('mapRef') == host_ref, mappings)
- return host_maps
-
-
-def _get_vol_mapping_for_host_group_frm_array(client, hg_ref):
- """Gets all volume mappings for given host from array."""
- mappings = client.get_volume_mappings() or []
- hg_maps = filter(lambda x: x.get('mapRef') == hg_ref, mappings)
- return hg_maps
-
-
def unmap_volume_from_host(client, volume, host, mapping):
# Volume is mapped directly to host, so delete the mapping
if mapping.get('mapRef') == host['hostRef']:
LOG.debug("Volume %s is mapped directly to multiattach host group but "
"is not currently attached; removing mapping.", volume['id'])
client.delete_volume_mapping(mapping['lunMappingRef'])
-
-
-def get_host_mapping_for_vol_frm_array(client, volume):
- """Gets all host mappings for given volume from array."""
- mappings = client.get_volume_mappings() or []
- host_maps = filter(lambda x: x.get('volumeRef') == volume['volumeRef'],
- mappings)
- return host_maps
-# Copyright (c) 2014 NetApp, Inc. All Rights Reserved.
-# Copyright (c) 2015 Alex Meade. All Rights Reserved.
-# Copyright (c) 2015 Rushil Chugh. All Rights Reserved.
-# Copyright (c) 2015 Navneet Singh. All Rights Reserved.
+# Copyright (c) 2015 Alex Meade
+# Copyright (c) 2015 Rushil Chugh
+# Copyright (c) 2015 Navneet Singh
+# Copyright (c) 2015 Yogesh Kshirsagar
+# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
from cinder.volume.drivers.netapp import options as na_opts
from cinder.volume.drivers.netapp import utils as na_utils
from cinder.volume import utils as volume_utils
+from cinder.zonemanager import utils as fczm_utils
LOG = logging.getLogger(__name__)
self.configuration.append_config_values(na_opts.netapp_transport_opts)
self.configuration.append_config_values(na_opts.netapp_eseries_opts)
self.configuration.append_config_values(na_opts.netapp_san_opts)
+ self.lookup_service = fczm_utils.create_lookup_service()
self._backend_name = self.configuration.safe_get(
"volume_backend_name") or "NetApp_ESeries"
self.driver_name = driver_name
{'host': host, 'e': e})
raise exception.NoValidHost(
_("Controller IP '%(host)s' could not be resolved: %(e)s.")
- % {'host': host, 'e': six.text_type(e)})
+ % {'host': host, 'e': e})
ips = self.configuration.netapp_controller_ips
ips = [i.strip() for i in ips.split(",")]
"""Removes an export for a volume."""
pass
- def map_volume_to_host(self, volume, eseries_volume, initiator_name):
+ def map_volume_to_host(self, volume, eseries_volume, initiators):
"""Ensures the specified initiator has access to the volume."""
- existing_maps = host_mapper.get_host_mapping_for_vol_frm_array(
- self._client, eseries_volume)
- host = self._get_or_create_host(initiator_name, self.host_type)
+ existing_maps = self._client.get_volume_mappings_for_volume(
+ eseries_volume)
+ host = self._get_or_create_host(initiators, self.host_type)
# There can only be one or zero mappings on a volume in E-Series
current_map = existing_maps[0] if existing_maps else None
self.configuration.netapp_enable_multiattach)
return mapping
+ def initialize_connection_fc(self, volume, connector):
+ """Initializes the connection and returns connection info.
+
+ Assigns the specified volume to a compute node/host so that it can be
+ used from that host.
+
+ The driver returns a driver_volume_type of 'fibre_channel'.
+ The target_wwn can be a single entry or a list of wwns that
+ correspond to the list of remote wwn(s) that will export the volume.
+ Example return values:
+ {
+ 'driver_volume_type': 'fibre_channel'
+ 'data': {
+ 'target_discovered': True,
+ 'target_lun': 1,
+ 'target_wwn': '500a098280feeba5',
+ 'access_mode': 'rw',
+ 'initiator_target_map': {
+ '21000024ff406cc3': ['500a098280feeba5'],
+ '21000024ff406cc2': ['500a098280feeba5']
+ }
+ }
+ }
+
+ or
+
+ {
+ 'driver_volume_type': 'fibre_channel'
+ 'data': {
+ 'target_discovered': True,
+ 'target_lun': 1,
+ 'target_wwn': ['500a098280feeba5', '500a098290feeba5',
+ '500a098190feeba5', '500a098180feeba5'],
+ 'access_mode': 'rw',
+ 'initiator_target_map': {
+ '21000024ff406cc3': ['500a098280feeba5',
+ '500a098290feeba5'],
+ '21000024ff406cc2': ['500a098190feeba5',
+ '500a098180feeba5']
+ }
+ }
+ }
+ """
+
+ initiators = [fczm_utils.get_formatted_wwn(wwpn)
+ for wwpn in connector['wwpns']]
+
+ eseries_vol = self._get_volume(volume['name_id'])
+ mapping = self.map_volume_to_host(volume, eseries_vol,
+ initiators)
+ lun_id = mapping['lun']
+
+ initiator_info = self._build_initiator_target_map_fc(connector)
+ target_wwpns, initiator_target_map, num_paths = initiator_info
+
+ if target_wwpns:
+ msg = ("Successfully fetched target details for LUN %(id)s "
+ "and initiator(s) %(initiators)s.")
+ msg_fmt = {'id': volume['id'], 'initiators': initiators}
+ LOG.debug(msg, msg_fmt)
+ else:
+ msg = _('Failed to get LUN target details for the LUN %s.')
+ raise exception.VolumeBackendAPIException(data=msg % volume['id'])
+
+ target_info = {'driver_volume_type': 'fibre_channel',
+ 'data': {'target_discovered': True,
+ 'target_lun': int(lun_id),
+ 'target_wwn': target_wwpns,
+ 'access_mode': 'rw',
+ 'initiator_target_map': initiator_target_map}}
+
+ return target_info
+
+ def terminate_connection_fc(self, volume, connector, **kwargs):
+ """Disallow connection from connector.
+
+ Return empty data if other volumes are in the same zone.
+ The FibreChannel ZoneManager doesn't remove zones
+ if there isn't an initiator_target_map in the
+ return of terminate_connection.
+
+ :returns: data - the target_wwns and initiator_target_map if the
+ zone is to be removed, otherwise the same map with
+ an empty dict for the 'data' key
+ """
+
+ eseries_vol = self._get_volume(volume['name_id'])
+ initiators = [fczm_utils.get_formatted_wwn(wwpn)
+ for wwpn in connector['wwpns']]
+ host = self._get_host_with_matching_port(initiators)
+ mappings = eseries_vol.get('listOfMappings', [])
+
+ # There can only be one or zero mappings on a volume in E-Series
+ mapping = mappings[0] if mappings else None
+
+ if not mapping:
+ raise eseries_exc.VolumeNotMapped(volume_id=volume['id'],
+ host=host['label'])
+ host_mapper.unmap_volume_from_host(self._client, volume, host, mapping)
+
+ info = {'driver_volume_type': 'fibre_channel',
+ 'data': {}}
+
+ if len(self._client.get_volume_mappings_for_host(
+ host['hostRef'])) == 0:
+ # No more exports for this host, so tear down zone.
+ LOG.info(_LI("Need to remove FC Zone, building initiator "
+ "target map."))
+
+ initiator_info = self._build_initiator_target_map_fc(connector)
+ target_wwpns, initiator_target_map, num_paths = initiator_info
+
+ info['data'] = {'target_wwn': target_wwpns,
+ 'initiator_target_map': initiator_target_map}
+
+ return info
+
+ def _build_initiator_target_map_fc(self, connector):
+ """Build the target_wwns and the initiator target map."""
+
+ # get WWPNs from controller and strip colons
+ all_target_wwpns = self._client.list_target_wwpns()
+ all_target_wwpns = [six.text_type(wwpn).replace(':', '')
+ for wwpn in all_target_wwpns]
+
+ target_wwpns = []
+ init_targ_map = {}
+ num_paths = 0
+
+ if self.lookup_service:
+ # Use FC SAN lookup to determine which ports are visible.
+ dev_map = self.lookup_service.get_device_mapping_from_network(
+ connector['wwpns'],
+ all_target_wwpns)
+
+ for fabric_name in dev_map:
+ fabric = dev_map[fabric_name]
+ target_wwpns += fabric['target_port_wwn_list']
+ for initiator in fabric['initiator_port_wwn_list']:
+ if initiator not in init_targ_map:
+ init_targ_map[initiator] = []
+ init_targ_map[initiator] += fabric['target_port_wwn_list']
+ init_targ_map[initiator] = list(set(
+ init_targ_map[initiator]))
+ for target in init_targ_map[initiator]:
+ num_paths += 1
+ target_wwpns = list(set(target_wwpns))
+ else:
+ initiator_wwns = connector['wwpns']
+ target_wwpns = all_target_wwpns
+
+ for initiator in initiator_wwns:
+ init_targ_map[initiator] = target_wwpns
+
+ return target_wwpns, init_targ_map, num_paths
+
def initialize_connection_iscsi(self, volume, connector):
"""Allow connection to connector and return connection info."""
initiator_name = connector['initiator']
eseries_vol = self._get_volume(volume['name_id'])
- mapping = self.map_volume_to_host(volume, eseries_vol, initiator_name)
+ mapping = self.map_volume_to_host(volume, eseries_vol,
+ [initiator_name])
lun_id = mapping['lun']
msg_fmt = {'id': volume['id'], 'initiator_name': initiator_name}
raise exception.NetAppDriverException(
msg % self._client.get_system_id())
- def _get_or_create_host(self, port_id, host_type):
+ def _get_or_create_host(self, port_ids, host_type):
"""Fetch or create a host by given port."""
try:
- host = self._get_host_with_port(port_id)
+ host = self._get_host_with_matching_port(port_ids)
ht_def = self._get_host_type_definition(host_type)
if host.get('hostTypeIndex') != ht_def.get('index'):
try:
return host
except exception.NotFound as e:
LOG.warning(_LW("Message - %s."), e.msg)
- return self._create_host(port_id, host_type)
+ return self._create_host(port_ids, host_type)
- def _get_host_with_port(self, port_id):
+ def _get_host_with_matching_port(self, port_ids):
"""Gets or creates a host with given port id."""
+ # Remove any extra colons
+ port_ids = [six.text_type(wwpn).replace(':', '')
+ for wwpn in port_ids]
hosts = self._client.list_hosts()
- for host in hosts:
- if host.get('hostSidePorts'):
- ports = host.get('hostSidePorts')
- for port in ports:
- if (port.get('type') == 'iscsi'
- and port.get('address') == port_id):
- return host
- msg = _("Host with port %(port)s not found.")
- raise exception.NotFound(msg % {'port': port_id})
-
- def _create_host(self, port_id, host_type, host_group=None):
+ for port_id in port_ids:
+ for host in hosts:
+ if host.get('hostSidePorts'):
+ ports = host.get('hostSidePorts')
+ for port in ports:
+ address = port.get('address').upper().replace(':', '')
+ if address == port_id.upper():
+ return host
+ msg = _("Host with ports %(ports)s not found.")
+ raise exception.NotFound(msg % {'ports': port_ids})
+
+ def _create_host(self, port_ids, host_type, host_group=None):
"""Creates host on system with given initiator as port_id."""
- LOG.info(_LI("Creating host with port %s."), port_id)
- label = utils.convert_uuid_to_es_fmt(uuid.uuid4())
- port_label = utils.convert_uuid_to_es_fmt(uuid.uuid4())
+ LOG.info(_LI("Creating host with ports %s."), port_ids)
+ host_label = utils.convert_uuid_to_es_fmt(uuid.uuid4())
host_type = self._get_host_type_definition(host_type)
- return self._client.create_host_with_port(label, host_type,
- port_id, port_label,
- group_id=host_group)
+ port_type = self.driver_protocol.lower()
+ return self._client.create_host_with_ports(host_label,
+ host_type,
+ port_ids,
+ group_id=host_group,
+ port_type=port_type)
def _get_host_type_definition(self, host_type):
"""Gets supported host type if available on storage system."""
"""Disallow connection from connector."""
eseries_vol = self._get_volume(volume['name_id'])
initiator = connector['initiator']
- host = self._get_host_with_port(initiator)
+ host = self._get_host_with_matching_port([initiator])
mappings = eseries_vol.get('listOfMappings', [])
# There can only be one or zero mappings on a volume in E-Series
hacking<0.11,>=0.10.0
coverage>=3.6
+ddt>=0.7.0
discover
fixtures>=0.3.14
mock>=1.0