829 lines
36 KiB
Python
829 lines
36 KiB
Python
# 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
|
|
# 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 copy
|
|
import ddt
|
|
|
|
import mock
|
|
|
|
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_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():
|
|
return {
|
|
'id': '114774fb-e15a-4fae-8ee2-c9723e3645ef', 'size': 1,
|
|
'volume_name': 'lun1', 'host': 'hostname@backend#DDP',
|
|
'os_type': 'linux', 'provider_location': 'lun1',
|
|
'name_id': '114774fb-e15a-4fae-8ee2-c9723e3645ef',
|
|
'provider_auth': 'provider a b', 'project_id': 'project',
|
|
'display_name': None, 'display_description': 'lun1',
|
|
'volume_type_id': None, 'migration_status': None, 'attach_status':
|
|
"detached"
|
|
}
|
|
|
|
|
|
@ddt.ddt
|
|
class NetAppEseriesLibraryTestCase(test.TestCase):
|
|
def setUp(self):
|
|
super(NetAppEseriesLibraryTestCase, self).setUp()
|
|
|
|
kwargs = {'configuration':
|
|
eseries_fake.create_configuration_eseries()}
|
|
|
|
self.library = library.NetAppESeriesLibrary('FAKE', **kwargs)
|
|
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_fake.FakeEseriesClient)
|
|
mock_check_flags = self.mock_object(na_utils, 'check_flags')
|
|
self.library.do_setup(mock.Mock())
|
|
|
|
self.assertTrue(mock_check_flags.called)
|
|
|
|
def test_update_ssc_info(self):
|
|
drives = [{'currentVolumeGroupRef': 'test_vg1',
|
|
'driveMediaType': 'ssd'}]
|
|
|
|
self.library._get_storage_pools = mock.Mock(return_value=['test_vg1'])
|
|
self.library._client.list_storage_pools = mock.Mock(return_value=[])
|
|
self.library._client.list_drives = mock.Mock(return_value=drives)
|
|
|
|
self.library._update_ssc_info()
|
|
|
|
self.assertEqual({'test_vg1': {'netapp_disk_type': 'SSD'}},
|
|
self.library._ssc_stats)
|
|
|
|
def test_update_ssc_disk_types_ssd(self):
|
|
drives = [{'currentVolumeGroupRef': 'test_vg1',
|
|
'driveMediaType': 'ssd'}]
|
|
self.library._client.list_drives = mock.Mock(return_value=drives)
|
|
|
|
ssc_stats = self.library._update_ssc_disk_types(['test_vg1'])
|
|
|
|
self.assertEqual({'test_vg1': {'netapp_disk_type': 'SSD'}},
|
|
ssc_stats)
|
|
|
|
def test_update_ssc_disk_types_scsi(self):
|
|
drives = [{'currentVolumeGroupRef': 'test_vg1',
|
|
'interfaceType': {'driveType': 'scsi'}}]
|
|
self.library._client.list_drives = mock.Mock(return_value=drives)
|
|
|
|
ssc_stats = self.library._update_ssc_disk_types(['test_vg1'])
|
|
|
|
self.assertEqual({'test_vg1': {'netapp_disk_type': 'SCSI'}},
|
|
ssc_stats)
|
|
|
|
def test_update_ssc_disk_types_fcal(self):
|
|
drives = [{'currentVolumeGroupRef': 'test_vg1',
|
|
'interfaceType': {'driveType': 'fibre'}}]
|
|
self.library._client.list_drives = mock.Mock(return_value=drives)
|
|
|
|
ssc_stats = self.library._update_ssc_disk_types(['test_vg1'])
|
|
|
|
self.assertEqual({'test_vg1': {'netapp_disk_type': 'FCAL'}},
|
|
ssc_stats)
|
|
|
|
def test_update_ssc_disk_types_sata(self):
|
|
drives = [{'currentVolumeGroupRef': 'test_vg1',
|
|
'interfaceType': {'driveType': 'sata'}}]
|
|
self.library._client.list_drives = mock.Mock(return_value=drives)
|
|
|
|
ssc_stats = self.library._update_ssc_disk_types(['test_vg1'])
|
|
|
|
self.assertEqual({'test_vg1': {'netapp_disk_type': 'SATA'}},
|
|
ssc_stats)
|
|
|
|
def test_update_ssc_disk_types_sas(self):
|
|
drives = [{'currentVolumeGroupRef': 'test_vg1',
|
|
'interfaceType': {'driveType': 'sas'}}]
|
|
self.library._client.list_drives = mock.Mock(return_value=drives)
|
|
|
|
ssc_stats = self.library._update_ssc_disk_types(['test_vg1'])
|
|
|
|
self.assertEqual({'test_vg1': {'netapp_disk_type': 'SAS'}},
|
|
ssc_stats)
|
|
|
|
def test_update_ssc_disk_types_unknown(self):
|
|
drives = [{'currentVolumeGroupRef': 'test_vg1',
|
|
'interfaceType': {'driveType': 'unknown'}}]
|
|
self.library._client.list_drives = mock.Mock(return_value=drives)
|
|
|
|
ssc_stats = self.library._update_ssc_disk_types(['test_vg1'])
|
|
|
|
self.assertEqual({'test_vg1': {'netapp_disk_type': 'unknown'}},
|
|
ssc_stats)
|
|
|
|
def test_update_ssc_disk_types_undefined(self):
|
|
drives = [{'currentVolumeGroupRef': 'test_vg1',
|
|
'interfaceType': {'driveType': '__UNDEFINED'}}]
|
|
self.library._client.list_drives = mock.Mock(return_value=drives)
|
|
|
|
ssc_stats = self.library._update_ssc_disk_types(['test_vg1'])
|
|
|
|
self.assertEqual({'test_vg1': {'netapp_disk_type': 'unknown'}},
|
|
ssc_stats)
|
|
|
|
def test_update_ssc_disk_encryption_SecType_enabled(self):
|
|
pools = [{'volumeGroupRef': 'test_vg1', 'securityType': 'enabled'}]
|
|
self.library._client.list_storage_pools = mock.Mock(return_value=pools)
|
|
|
|
ssc_stats = self.library._update_ssc_disk_encryption(['test_vg1'])
|
|
|
|
self.assertEqual({'test_vg1': {'netapp_disk_encryption': 'true'}},
|
|
ssc_stats)
|
|
|
|
def test_update_ssc_disk_encryption_SecType_unknown(self):
|
|
pools = [{'volumeGroupRef': 'test_vg1', 'securityType': 'unknown'}]
|
|
self.library._client.list_storage_pools = mock.Mock(return_value=pools)
|
|
|
|
ssc_stats = self.library._update_ssc_disk_encryption(['test_vg1'])
|
|
|
|
self.assertEqual({'test_vg1': {'netapp_disk_encryption': 'false'}},
|
|
ssc_stats)
|
|
|
|
def test_update_ssc_disk_encryption_SecType_none(self):
|
|
pools = [{'volumeGroupRef': 'test_vg1', 'securityType': 'none'}]
|
|
self.library._client.list_storage_pools = mock.Mock(return_value=pools)
|
|
|
|
ssc_stats = self.library._update_ssc_disk_encryption(['test_vg1'])
|
|
|
|
self.assertEqual({'test_vg1': {'netapp_disk_encryption': 'false'}},
|
|
ssc_stats)
|
|
|
|
def test_update_ssc_disk_encryption_SecType_capable(self):
|
|
pools = [{'volumeGroupRef': 'test_vg1', 'securityType': 'capable'}]
|
|
self.library._client.list_storage_pools = mock.Mock(return_value=pools)
|
|
|
|
ssc_stats = self.library._update_ssc_disk_encryption(['test_vg1'])
|
|
|
|
self.assertEqual({'test_vg1': {'netapp_disk_encryption': 'false'}},
|
|
ssc_stats)
|
|
|
|
def test_update_ssc_disk_encryption_SecType_garbage(self):
|
|
pools = [{'volumeGroupRef': 'test_vg1', 'securityType': 'garbage'}]
|
|
self.library._client.list_storage_pools = mock.Mock(return_value=pools)
|
|
|
|
ssc_stats = self.library._update_ssc_disk_encryption(['test_vg1'])
|
|
|
|
self.assertRaises(TypeError, 'test_vg1',
|
|
{'netapp_disk_encryption': 'false'}, ssc_stats)
|
|
|
|
def test_update_ssc_disk_encryption_multiple(self):
|
|
pools = [{'volumeGroupRef': 'test_vg1', 'securityType': 'none'},
|
|
{'volumeGroupRef': 'test_vg2', 'securityType': 'enabled'}]
|
|
self.library._client.list_storage_pools = mock.Mock(return_value=pools)
|
|
|
|
ssc_stats = self.library._update_ssc_disk_encryption(['test_vg1',
|
|
'test_vg2'])
|
|
|
|
self.assertEqual({'test_vg1': {'netapp_disk_encryption': 'false'},
|
|
'test_vg2': {'netapp_disk_encryption': 'true'}},
|
|
ssc_stats)
|
|
|
|
def test_terminate_connection_iscsi_no_hosts(self):
|
|
connector = {'initiator': eseries_fake.INITIATOR_NAME}
|
|
|
|
self.mock_object(self.library._client, 'list_hosts',
|
|
mock.Mock(return_value=[]))
|
|
|
|
self.assertRaises(exception.NotFound,
|
|
self.library.terminate_connection_iscsi,
|
|
get_fake_volume(),
|
|
connector)
|
|
|
|
def test_terminate_connection_iscsi_volume_not_mapped(self):
|
|
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_fake.INITIATOR_NAME}
|
|
fake_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
|
|
fake_eseries_volume['listOfMappings'] = [
|
|
eseries_fake.VOLUME_MAPPING
|
|
]
|
|
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_iscsi(get_fake_volume(), connector)
|
|
|
|
self.assertTrue(host_mapper.unmap_volume_from_host.called)
|
|
|
|
def test_terminate_connection_iscsi_not_mapped_initiator_does_not_exist(
|
|
self):
|
|
connector = {'initiator': eseries_fake.INITIATOR_NAME}
|
|
self.mock_object(self.library._client, 'list_hosts',
|
|
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_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_fake.VOLUME_MAPPING))
|
|
|
|
self.library.initialize_connection_iscsi(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)
|
|
|
|
def test_initialize_connection_iscsi_volume_not_mapped_host_does_not_exist(
|
|
self):
|
|
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_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_iscsi(get_fake_volume(), connector)
|
|
|
|
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_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_fake.INITIATOR_NAME}
|
|
self.mock_object(host_mapper, 'map_volume_to_single_host',
|
|
mock.Mock(
|
|
return_value=eseries_fake.VOLUME_MAPPING))
|
|
|
|
self.library.initialize_connection_iscsi(get_fake_volume(), connector)
|
|
|
|
self.assertTrue(host_mapper.map_volume_to_single_host.called)
|
|
|
|
def test_initialize_connection_iscsi_volume_mapped_to_another_host(self):
|
|
"""Should raise error saying multiattach not enabled"""
|
|
connector = {'initiator': eseries_fake.INITIATOR_NAME}
|
|
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_iscsi,
|
|
get_fake_volume(), connector)
|
|
|
|
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 when netapp_enable_multiattach is enabled.
|
|
|
|
Test driver behavior when the netapp_enable_multiattach configuration
|
|
option is True.
|
|
"""
|
|
|
|
def setUp(self):
|
|
super(NetAppEseriesLibraryMultiAttachTestCase, self).setUp()
|
|
config = eseries_fake.create_configuration_eseries()
|
|
config.netapp_enable_multiattach = True
|
|
|
|
kwargs = {'configuration': config}
|
|
|
|
self.library = library.NetAppESeriesLibrary("FAKE", **kwargs)
|
|
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_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')
|
|
|
|
self.library.do_setup(mock.Mock())
|
|
|
|
self.assertTrue(mock_check_flags.called)
|
|
self.assertFalse(mock_create.call_count)
|
|
|
|
def test_do_setup_host_group_does_not_exist(self):
|
|
mock_check_flags = self.mock_object(na_utils, 'check_flags')
|
|
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(
|
|
fake_rest_client, "get_host_group_by_name",
|
|
mock.Mock(side_effect=exception.NotFound))
|
|
self.mock_object(self.library,
|
|
'_check_mode_get_or_register_storage_system')
|
|
|
|
self.library.do_setup(mock.Mock())
|
|
|
|
self.assertTrue(mock_check_flags.called)
|
|
self.assertTrue(mock_get_host_group.call_count)
|
|
|
|
def test_create_volume(self):
|
|
self.library._client.create_volume = mock.Mock(
|
|
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_fake.VOLUME for __ in
|
|
range(utils.MAX_LUNS_PER_HOST_GROUP + 1)])
|
|
self.library._client.create_volume = mock.Mock(
|
|
return_value=eseries_fake.VOLUME)
|
|
|
|
self.assertRaises(exception.NetAppDriverException,
|
|
self.library.create_volume,
|
|
get_fake_volume())
|
|
self.assertFalse(self.library._client.create_volume.call_count)
|
|
|
|
def test_create_volume_from_snapshot(self):
|
|
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",
|
|
mock.Mock(return_value=fake_eseries_volume))
|
|
self.mock_object(self.library._client, "delete_snapshot_volume")
|
|
|
|
self.library.create_volume_from_snapshot(
|
|
get_fake_volume(), fake_snapshot.fake_snapshot_obj(None))
|
|
|
|
self.assertEqual(
|
|
1, self.library._schedule_and_create_volume.call_count)
|
|
self.assertEqual(1, self.library._create_snapshot_volume.call_count)
|
|
self.assertEqual(
|
|
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_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",
|
|
mock.Mock(side_effect=exception.NetAppDriverException)
|
|
)
|
|
self.mock_object(self.library._client, "delete_snapshot_volume")
|
|
self.mock_object(self.library._client, "delete_volume")
|
|
|
|
self.assertRaises(exception.NetAppDriverException,
|
|
self.library.create_volume_from_snapshot,
|
|
get_fake_volume(),
|
|
fake_snapshot.fake_snapshot_obj(None))
|
|
|
|
self.assertEqual(
|
|
1, self.library._schedule_and_create_volume.call_count)
|
|
self.assertEqual(1, self.library._create_snapshot_volume.call_count)
|
|
self.assertEqual(
|
|
0, self.library._client.delete_snapshot_volume.call_count)
|
|
# Ensure the volume we were going to copy to is cleaned up
|
|
self.library._client.delete_volume.assert_called_once_with(
|
|
fake_dest_eseries_volume['volumeRef'])
|
|
|
|
def test_create_volume_from_snapshot_copy_job_fails(self):
|
|
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",
|
|
mock.Mock(return_value=fake_dest_eseries_volume))
|
|
self.mock_object(self.library._client, "delete_snapshot_volume")
|
|
self.mock_object(self.library._client, "delete_volume")
|
|
|
|
fake_failed_volume_copy_job = copy.deepcopy(
|
|
eseries_fake.VOLUME_COPY_JOB)
|
|
fake_failed_volume_copy_job['status'] = 'failed'
|
|
self.mock_object(self.library._client,
|
|
"create_volume_copy_job",
|
|
mock.Mock(return_value=fake_failed_volume_copy_job))
|
|
self.mock_object(self.library._client,
|
|
"list_vol_copy_job",
|
|
mock.Mock(return_value=fake_failed_volume_copy_job))
|
|
|
|
self.assertRaises(exception.NetAppDriverException,
|
|
self.library.create_volume_from_snapshot,
|
|
get_fake_volume(),
|
|
fake_snapshot.fake_snapshot_obj(None))
|
|
|
|
self.assertEqual(
|
|
1, self.library._schedule_and_create_volume.call_count)
|
|
self.assertEqual(1, self.library._create_snapshot_volume.call_count)
|
|
self.assertEqual(
|
|
1, self.library._client.delete_snapshot_volume.call_count)
|
|
# Ensure the volume we were going to copy to is cleaned up
|
|
self.library._client.delete_volume.assert_called_once_with(
|
|
fake_dest_eseries_volume['volumeRef'])
|
|
|
|
def test_create_volume_from_snapshot_fail_to_delete_snapshot_volume(self):
|
|
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_fake.VOLUME)))
|
|
self.mock_object(self.library._client, "delete_snapshot_volume",
|
|
mock.Mock(side_effect=exception.NetAppDriverException)
|
|
)
|
|
self.mock_object(self.library._client, "delete_volume")
|
|
|
|
self.library.create_volume_from_snapshot(
|
|
get_fake_volume(), fake_snapshot.fake_snapshot_obj(None))
|
|
|
|
self.assertEqual(
|
|
1, self.library._schedule_and_create_volume.call_count)
|
|
self.assertEqual(1, self.library._create_snapshot_volume.call_count)
|
|
self.assertEqual(
|
|
1, self.library._client.delete_snapshot_volume.call_count)
|
|
# Ensure the volume we created is not cleaned up
|
|
self.assertEqual(0, self.library._client.delete_volume.call_count)
|
|
|
|
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_for_volume',
|
|
mock.Mock(return_value=[]))
|
|
self.mock_object(host_mapper, 'map_volume_to_single_host',
|
|
mock.Mock(
|
|
return_value=eseries_fake.VOLUME_MAPPING))
|
|
|
|
self.library.map_volume_to_host(get_fake_volume(),
|
|
eseries_fake.VOLUME,
|
|
eseries_fake.INITIATOR_NAME_2)
|
|
|
|
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_ports',
|
|
mock.Mock(
|
|
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_fake.VOLUME_MAPPING))
|
|
|
|
self.library.map_volume_to_host(get_fake_volume(),
|
|
eseries_fake.VOLUME,
|
|
eseries_fake.INITIATOR_NAME_2)
|
|
|
|
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_fake.VOLUME_MAPPING))
|
|
|
|
self.library.map_volume_to_host(get_fake_volume(),
|
|
eseries_fake.VOLUME,
|
|
eseries_fake.INITIATOR_NAME)
|
|
|
|
self.assertTrue(host_mapper.map_volume_to_multiple_hosts.called)
|