cinder/cinder/tests/unit/volume/drivers/netapp/eseries/test_library.py

1209 lines
53 KiB
Python

# Copyright (c) 2014 Andrew Kerr
# Copyright (c) 2015 Alex Meade
# Copyright (c) 2015 Rushil Chugh
# Copyright (c) 2015 Yogesh Kshirsagar
# Copyright (c) 2015 Michael Price
# 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 oslo_utils import units
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_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)
# Deprecated Option
self.library.configuration.netapp_storage_pools = None
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_get_storage_pools_empty_result(self):
"""Verify an exception is raised if no pools are returned."""
self.library.configuration.netapp_pool_name_search_pattern = '$'
self.assertRaises(exception.NetAppDriverException,
self.library.check_for_setup_error)
def test_get_storage_pools_invalid_conf(self):
"""Verify an exception is raised if the regex pattern is invalid."""
self.library.configuration.netapp_pool_name_search_pattern = '(.*'
self.assertRaises(exception.InvalidConfigurationValue,
self.library._get_storage_pools)
def test_get_storage_pools_default(self):
"""Verify that all pools are returned if the search option is empty."""
filtered_pools = self.library._get_storage_pools()
self.assertEqual(eseries_fake.STORAGE_POOLS, filtered_pools)
@ddt.data(('[\d]+,a', ['1', '2', 'a', 'b'], ['1', '2', 'a']),
('1 , 3', ['1', '2', '3'], ['1', '3']),
('$,3', ['1', '2', '3'], ['3']),
('[a-zA-Z]+', ['1', 'a', 'B'], ['a', 'B']),
('', ['1', '2'], ['1', '2'])
)
@ddt.unpack
def test_get_storage_pools(self, pool_filter, pool_labels,
expected_pool_labels):
"""Verify that pool filtering via the search_pattern works correctly
:param pool_filter: A regular expression to be used for filtering via
pool labels
:param pool_labels: A list of pool labels
:param expected_pool_labels: The labels from 'pool_labels' that
should be matched by 'pool_filter'
"""
self.library.configuration.netapp_pool_name_search_pattern = (
pool_filter)
pools = [{'label': label} for label in pool_labels]
self.library._client.list_storage_pools = mock.Mock(
return_value=pools)
filtered_pools = self.library._get_storage_pools()
filtered_pool_labels = [pool['label'] for pool in filtered_pools]
self.assertEqual(expected_pool_labels, filtered_pool_labels)
def test_get_volume(self):
fake_volume = copy.deepcopy(get_fake_volume())
volume = copy.deepcopy(eseries_fake.VOLUME)
self.library._client.list_volume = mock.Mock(return_value=volume)
result = self.library._get_volume(fake_volume['id'])
self.assertEqual(1, self.library._client.list_volume.call_count)
self.assertDictMatch(volume, result)
def test_get_volume_bad_input(self):
volume = copy.deepcopy(eseries_fake.VOLUME)
self.library._client.list_volume = mock.Mock(return_value=volume)
self.assertRaises(exception.InvalidInput, self.library._get_volume,
None)
def test_get_volume_bad_uuid(self):
volume = copy.deepcopy(eseries_fake.VOLUME)
self.library._client.list_volume = mock.Mock(return_value=volume)
self.assertRaises(ValueError, self.library._get_volume, '1')
def test_update_ssc_info_no_ssc(self):
drives = [{'currentVolumeGroupRef': 'test_vg1',
'driveMediaType': 'ssd'}]
pools = [{'volumeGroupRef': 'test_vg1', 'label': 'test_vg1',
'raidLevel': 'raid6', 'securityType': 'enabled'}]
self.library._client = mock.Mock()
self.library._client.features.SSC_API_V2 = na_utils.FeatureState(
False, minimum_version="1.53.9000.1")
self.library._client.SSC_VALID_VERSIONS = [(1, 53, 9000, 1),
(1, 53, 9010, 15)]
self.library.configuration.netapp_pool_name_search_pattern = "test_vg1"
self.library._client.list_storage_pools = mock.Mock(return_value=pools)
self.library._client.list_drives = mock.Mock(return_value=drives)
self.library._update_ssc_info()
self.assertEqual(
{'test_vg1': {'netapp_disk_encryption': 'true',
'netapp_disk_type': 'SSD',
'netapp_raid_type': 'raid6'}},
self.library._ssc_stats)
@ddt.data(True, False)
def test_update_ssc_info(self, data_assurance_supported):
self.library._client = mock.Mock()
self.library._client.features.SSC_API_V2 = na_utils.FeatureState(
True, minimum_version="1.53.9000.1")
self.library._client.list_ssc_storage_pools = mock.Mock(
return_value=eseries_fake.SSC_POOLS)
self.library._get_storage_pools = mock.Mock(
return_value=eseries_fake.STORAGE_POOLS)
# Data Assurance is not supported on some storage backends
self.library._is_data_assurance_supported = mock.Mock(
return_value=data_assurance_supported)
self.library._update_ssc_info()
for pool in eseries_fake.SSC_POOLS:
poolId = pool['poolId']
raid_lvl = self.library.SSC_RAID_TYPE_MAPPING.get(
pool['raidLevel'], 'unknown')
if pool['pool']["driveMediaType"] == 'ssd':
disk_type = 'SSD'
else:
disk_type = pool['pool']['drivePhysicalType']
disk_type = (
self.library.SSC_DISK_TYPE_MAPPING.get(
disk_type, 'unknown'))
da_enabled = pool['dataAssuranceCapable'] and (
data_assurance_supported)
thin_provisioned = pool['thinProvisioningCapable']
expected = {
'netapp_disk_encryption':
six.text_type(pool['encrypted']).lower(),
'netapp_eseries_flash_read_cache':
six.text_type(pool['flashCacheCapable']).lower(),
'netapp_thin_provisioned':
six.text_type(thin_provisioned).lower(),
'netapp_eseries_data_assurance':
six.text_type(da_enabled).lower(),
'netapp_eseries_disk_spindle_speed': pool['spindleSpeed'],
'netapp_raid_type': raid_lvl,
'netapp_disk_type': disk_type
}
actual = self.library._ssc_stats[poolId]
self.assertDictMatch(expected, actual)
@ddt.data(('FC', True), ('iSCSI', False))
@ddt.unpack
def test_is_data_assurance_supported(self, backend_storage_protocol,
enabled):
self.mock_object(self.library, 'driver_protocol',
backend_storage_protocol)
actual = self.library._is_data_assurance_supported()
self.assertEqual(enabled, actual)
@ddt.data('scsi', 'fibre', 'sas', 'sata', 'garbage')
def test_update_ssc_disk_types(self, disk_type):
drives = [{'currentVolumeGroupRef': 'test_vg1',
'interfaceType': {'driveType': disk_type}}]
pools = [{'volumeGroupRef': 'test_vg1'}]
self.library._client.list_drives = mock.Mock(return_value=drives)
self.library._client.get_storage_pool = mock.Mock(return_value=pools)
ssc_stats = self.library._update_ssc_disk_types(pools)
expected = self.library.SSC_DISK_TYPE_MAPPING.get(disk_type, 'unknown')
self.assertEqual({'test_vg1': {'netapp_disk_type': expected}},
ssc_stats)
@ddt.data('scsi', 'fibre', 'sas', 'sata', 'garbage')
def test_update_ssc_disk_types_ssd(self, disk_type):
drives = [{'currentVolumeGroupRef': 'test_vg1',
'driveMediaType': 'ssd', 'driveType': disk_type}]
pools = [{'volumeGroupRef': 'test_vg1'}]
self.library._client.list_drives = mock.Mock(return_value=drives)
self.library._client.get_storage_pool = mock.Mock(return_value=pools)
ssc_stats = self.library._update_ssc_disk_types(pools)
self.assertEqual({'test_vg1': {'netapp_disk_type': 'SSD'}},
ssc_stats)
@ddt.data('enabled', 'none', 'capable', 'unknown', '__UNDEFINED',
'garbage')
def test_update_ssc_disk_encryption(self, securityType):
pools = [{'volumeGroupRef': 'test_vg1', 'securityType': securityType}]
self.library._client.list_storage_pools = mock.Mock(return_value=pools)
ssc_stats = self.library._update_ssc_disk_encryption(pools)
# Convert the boolean value to a lower-case string value
expected = 'true' if securityType == "enabled" else 'false'
self.assertEqual({'test_vg1': {'netapp_disk_encryption': expected}},
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(pools)
self.assertEqual({'test_vg1': {'netapp_disk_encryption': 'false'},
'test_vg2': {'netapp_disk_encryption': 'true'}},
ssc_stats)
@ddt.data(True, False)
def test_get_volume_stats(self, refresh):
fake_stats = {'key': 'val'}
def populate_stats():
self.library._stats = fake_stats
self.library._update_volume_stats = mock.Mock(
side_effect=populate_stats)
self.library._update_ssc_info = mock.Mock()
self.library._ssc_stats = {self.library.THIN_UQ_SPEC: True}
actual = self.library.get_volume_stats(refresh = refresh)
if(refresh):
self.library._update_volume_stats.assert_called_once_with()
self.assertEqual(fake_stats, actual)
else:
self.assertEqual(0, self.library._update_volume_stats.call_count)
self.assertEqual(0, self.library._update_ssc_info.call_count)
def test_get_volume_stats_no_ssc(self):
"""Validate that SSC data is collected if not yet populated"""
fake_stats = {'key': 'val'}
def populate_stats():
self.library._stats = fake_stats
self.library._update_volume_stats = mock.Mock(
side_effect=populate_stats)
self.library._update_ssc_info = mock.Mock()
self.library._ssc_stats = None
actual = self.library.get_volume_stats(refresh = True)
self.library._update_volume_stats.assert_called_once_with()
self.library._update_ssc_info.assert_called_once_with()
self.assertEqual(fake_stats, actual)
def test_update_volume_stats_provisioning(self):
"""Validate pool capacity calculations"""
fake_pool = copy.deepcopy(eseries_fake.STORAGE_POOL)
self.library._get_storage_pools = mock.Mock(return_value=[fake_pool])
self.mock_object(self.library, '_ssc_stats', new_attr={fake_pool[
"volumeGroupRef"]: {self.library.THIN_UQ_SPEC: True}})
self.library.configuration = mock.Mock()
reserved_pct = 5
over_subscription_ratio = 1.0
self.library.configuration.max_over_subscription_ratio = (
over_subscription_ratio)
self.library.configuration.reserved_percentage = reserved_pct
total_gb = int(fake_pool['totalRaidedSpace']) / units.Gi
used_gb = int(fake_pool['usedSpace']) / units.Gi
free_gb = total_gb - used_gb
self.library._update_volume_stats()
self.assertEqual(1, len(self.library._stats['pools']))
pool_stats = self.library._stats['pools'][0]
self.assertEqual(fake_pool['label'], pool_stats.get('pool_name'))
self.assertEqual(reserved_pct, pool_stats['reserved_percentage'])
self.assertEqual(over_subscription_ratio,
pool_stats['max_oversubscription_ratio'])
self.assertEqual(total_gb, pool_stats.get('total_capacity_gb'))
self.assertEqual(used_gb, pool_stats.get('provisioned_capacity_gb'))
self.assertEqual(free_gb, pool_stats.get('free_capacity_gb'))
@ddt.data(False, True)
def test_update_volume_stats_thin_provisioning(self, thin_provisioning):
"""Validate that thin provisioning support is correctly reported"""
fake_pool = copy.deepcopy(eseries_fake.STORAGE_POOL)
self.library._get_storage_pools = mock.Mock(return_value=[fake_pool])
self.mock_object(self.library, '_ssc_stats', new_attr={fake_pool[
"volumeGroupRef"]: {self.library.THIN_UQ_SPEC: thin_provisioning}})
self.library._update_volume_stats()
self.assertEqual(1, len(self.library._stats['pools']))
pool_stats = self.library._stats['pools'][0]
self.assertEqual(thin_provisioning, pool_stats.get(
'thin_provisioning_support'))
# Should always be True
self.assertTrue(pool_stats.get('thick_provisioning_support'))
def test_update_volume_stats_ssc(self):
"""Ensure that the SSC data is correctly reported in the pool stats"""
ssc = {self.library.THIN_UQ_SPEC: True, 'key': 'val'}
fake_pool = copy.deepcopy(eseries_fake.STORAGE_POOL)
self.library._get_storage_pools = mock.Mock(return_value=[fake_pool])
self.mock_object(self.library, '_ssc_stats', new_attr={fake_pool[
"volumeGroupRef"]: ssc})
self.library._update_volume_stats()
self.assertEqual(1, len(self.library._stats['pools']))
pool_stats = self.library._stats['pools'][0]
for key in ssc:
self.assertIn(key, pool_stats)
self.assertEqual(ssc[key], pool_stats[key])
def test_update_volume_stats_no_ssc(self):
"""Ensure that that pool stats are correctly reported without SSC"""
fake_pool = copy.deepcopy(eseries_fake.STORAGE_POOL)
self.library._get_storage_pools = mock.Mock(return_value=[fake_pool])
self.library._update_volume_stats()
self.assertEqual(1, len(self.library._stats['pools']))
pool_stats = self.library._stats['pools'][0]
self.assertFalse(pool_stats.get('thin_provisioning_support'))
# Should always be True
self.assertTrue(pool_stats.get('thick_provisioning_support'))
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}
volume = copy.deepcopy(eseries_fake.VOLUME)
volume['listOfMappings'] = []
self.library._get_volume = mock.Mock(return_value=volume)
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_volume',
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))
fake_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
fake_eseries_volume['listOfMappings'] = [
eseries_fake.VOLUME_MAPPING
]
self.mock_object(self.library._client, 'list_volume',
mock.Mock(return_value=fake_eseries_volume))
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))
fake_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
fake_eseries_volume['listOfMappings'] = [
eseries_fake.VOLUME_MAPPING
]
self.mock_object(self.library._client, 'list_volume',
mock.Mock(return_value=fake_eseries_volume))
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))
fake_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
self.mock_object(self.library._client, 'list_volume',
mock.Mock(return_value=fake_eseries_volume))
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
}]
volume = copy.deepcopy(eseries_fake.VOLUME)
volume['listOfMappings'] = []
self.mock_object(self.library, '_get_volume',
mock.Mock(return_value=volume))
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_volume',
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_volume',
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_volume',
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)
@ddt.data(('raid0', 'raid0'), ('raid1', 'raid1'), ('raid3', 'raid5'),
('raid5', 'raid5'), ('raid6', 'raid6'), ('raidDiskPool', 'DDP'))
@ddt.unpack
def test_update_ssc_raid_type(self, raid_lvl, raid_lvl_mapping):
pools = [{'volumeGroupRef': 'test_vg1', 'raidLevel': raid_lvl}]
self.library._client.list_storage_pools = mock.Mock(return_value=pools)
ssc_stats = self.library._update_ssc_raid_type(pools)
self.assertEqual({'test_vg1': {'netapp_raid_type': raid_lvl_mapping}},
ssc_stats)
@ddt.data('raidAll', '__UNDEFINED', 'unknown',
'raidUnsupported', 'garbage')
def test_update_ssc_raid_type_invalid(self, raid_lvl):
pools = [{'volumeGroupRef': 'test_vg1', 'raidLevel': raid_lvl}]
self.library._client.list_storage_pools = mock.Mock(return_value=pools)
ssc_stats = self.library._update_ssc_raid_type(pools)
self.assertEqual({'test_vg1': {'netapp_raid_type': 'unknown'}},
ssc_stats)
def test_create_asup(self):
self.library._client = mock.Mock()
self.library._client.features.AUTOSUPPORT = na_utils.FeatureState()
self.library._client.api_operating_mode = (
eseries_fake.FAKE_ASUP_DATA['operating-mode'])
self.library._app_version = eseries_fake.FAKE_APP_VERSION
self.mock_object(
self.library._client, 'get_firmware_version',
mock.Mock(return_value=(
eseries_fake.FAKE_ASUP_DATA['system-version'])))
self.mock_object(
self.library._client, 'get_serial_numbers',
mock.Mock(return_value=eseries_fake.FAKE_SERIAL_NUMBERS))
self.mock_object(
self.library._client, 'get_model_name',
mock.Mock(
return_value=eseries_fake.FAKE_CONTROLLERS[0]['modelName']))
self.mock_object(
self.library._client, 'set_counter',
mock.Mock(return_value={'value': 1}))
mock_invoke = self.mock_object(
self.library._client, 'add_autosupport_data')
self.library._create_asup(eseries_fake.FAKE_CINDER_HOST)
mock_invoke.assert_called_with(eseries_fake.FAKE_KEY,
eseries_fake.FAKE_ASUP_DATA)
def test_create_asup_not_supported(self):
self.library._client = mock.Mock()
self.library._client.features.AUTOSUPPORT = na_utils.FeatureState(
supported=False)
mock_invoke = self.mock_object(
self.library._client, 'add_autosupport_data')
self.library._create_asup(eseries_fake.FAKE_CINDER_HOST)
mock_invoke.assert_not_called()
@ddt.ddt
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)
@ddt.data(('netapp_eseries_flash_read_cache', 'flash_cache', 'true'),
('netapp_eseries_flash_read_cache', 'flash_cache', 'false'),
('netapp_eseries_flash_read_cache', 'flash_cache', None),
('netapp_thin_provisioned', 'thin_provision', 'true'),
('netapp_thin_provisioned', 'thin_provision', 'false'),
('netapp_thin_provisioned', 'thin_provision', None),
('netapp_eseries_data_assurance', 'data_assurance', 'true'),
('netapp_eseries_data_assurance', 'data_assurance', 'false'),
('netapp_eseries_data_assurance', 'data_assurance', None),
('netapp:write_cache', 'write_cache', 'true'),
('netapp:write_cache', 'write_cache', 'false'),
('netapp:write_cache', 'write_cache', None),
('netapp:read_cache', 'read_cache', 'true'),
('netapp:read_cache', 'read_cache', 'false'),
('netapp:read_cache', 'read_cache', None),
('netapp_eseries_flash_read_cache', 'flash_cache', 'True'),
('netapp_eseries_flash_read_cache', 'flash_cache', '1'),
('netapp_eseries_data_assurance', 'data_assurance', ''))
@ddt.unpack
def test_create_volume_with_extra_spec(self, spec, key, value):
fake_volume = get_fake_volume()
extra_specs = {spec: value}
volume = copy.deepcopy(eseries_fake.VOLUME)
self.library._client.create_volume = mock.Mock(
return_value=volume)
# Make this utility method return our extra spec
mocked_spec_method = self.mock_object(na_utils,
'get_volume_extra_specs')
mocked_spec_method.return_value = extra_specs
self.library.create_volume(fake_volume)
self.assertEqual(1, self.library._client.create_volume.call_count)
# Ensure create_volume is called with the correct argument
args, kwargs = self.library._client.create_volume.call_args
self.assertIn(key, kwargs)
if(value is not None):
expected = na_utils.to_bool(value)
else:
expected = value
self.assertEqual(expected, kwargs[key])
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_extend_volume(self):
fake_volume = copy.deepcopy(get_fake_volume())
volume = copy.deepcopy(eseries_fake.VOLUME)
new_capacity = 10
volume['objectType'] = 'volume'
self.library.create_cloned_volume = mock.Mock()
self.library._get_volume = mock.Mock(return_value=volume)
self.library._client.update_volume = mock.Mock()
self.library.extend_volume(fake_volume, new_capacity)
self.library.create_cloned_volume.assert_called_with(mock.ANY,
fake_volume)
def test_extend_volume_thin(self):
fake_volume = copy.deepcopy(get_fake_volume())
volume = copy.deepcopy(eseries_fake.VOLUME)
new_capacity = 10
volume['objectType'] = 'thinVolume'
self.library._client.expand_volume = mock.Mock(return_value=volume)
self.library._get_volume = mock.Mock(return_value=volume)
self.library.extend_volume(fake_volume, new_capacity)
self.library._client.expand_volume.assert_called_with(volume['id'],
new_capacity)
def test_extend_volume_stage_2_failure(self):
fake_volume = copy.deepcopy(get_fake_volume())
volume = copy.deepcopy(eseries_fake.VOLUME)
new_capacity = 10
volume['objectType'] = 'volume'
self.library.create_cloned_volume = mock.Mock()
self.library._client.delete_volume = mock.Mock()
# Create results for multiple calls to _get_volume and _update_volume
get_volume_results = [volume, {'id': 'newId', 'label': 'newVolume'}]
self.library._get_volume = mock.Mock(side_effect=get_volume_results)
update_volume_results = [volume, exception.NetAppDriverException,
volume]
self.library._client.update_volume = mock.Mock(
side_effect=update_volume_results)
self.assertRaises(exception.NetAppDriverException,
self.library.extend_volume, fake_volume,
new_capacity)
self.assertTrue(self.library._client.delete_volume.called)
def test_extend_volume_stage_1_failure(self):
fake_volume = copy.deepcopy(get_fake_volume())
volume = copy.deepcopy(eseries_fake.VOLUME)
new_capacity = 10
volume['objectType'] = 'volume'
self.library.create_cloned_volume = mock.Mock()
self.library._get_volume = mock.Mock(return_value=volume)
self.library._client.update_volume = mock.Mock(
side_effect=exception.NetAppDriverException)
self.assertRaises(exception.NetAppDriverException,
self.library.extend_volume, fake_volume,
new_capacity)
def test_delete_non_existing_volume(self):
volume2 = get_fake_volume()
# Change to a nonexistent id.
volume2['name_id'] = '88888888-4444-4444-4444-cccccccccccc'
self.assertIsNone(self.library.delete_volume(volume2))
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)