Remove support for NetApp E-Series systems

The deprecation notice [1] for this driver in Cinder was issued in
Rocky, so now it is time to remove them from the tree.

[1] http://lists.openstack.org/pipermail/openstack-operators/2018-July/015521.html

Change-Id: I82cfbacdf572d68dc4e6c21a3d3005bc92344a38
This commit is contained in:
tpsilva 2018-12-12 10:53:58 -02:00
parent 38e91b1030
commit 5bc5af7a94
28 changed files with 19 additions and 10587 deletions

View File

@ -318,7 +318,6 @@ def list_opts():
cinder_volume_drivers_netapp_options.netapp_cluster_opts,
cinder_volume_drivers_netapp_options.netapp_provisioning_opts,
cinder_volume_drivers_netapp_options.netapp_img_cache_opts,
cinder_volume_drivers_netapp_options.netapp_eseries_opts,
cinder_volume_drivers_netapp_options.netapp_nfs_extra_opts,
cinder_volume_drivers_netapp_options.netapp_san_opts,
cinder_volume_drivers_netapp_options.netapp_replication_opts,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,543 +0,0 @@
# Copyright (c) 2015 Alex Meade. All rights reserved.
# Copyright (c) 2015 Michael Price. All rights reserved.
# 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 abc
import copy
import ddt
import mock
import socket
from cinder import exception
from cinder import utils as cinder_utils
from cinder.volume import configuration as conf
from cinder.tests.unit.volume.drivers.netapp.eseries import fakes as \
fakes
from cinder.volume.drivers.netapp import common
from cinder.volume.drivers.netapp.eseries import client
from cinder.volume.drivers.netapp.eseries import library
from cinder.volume.drivers.netapp.eseries import utils
from cinder.volume.drivers.netapp import options
import cinder.volume.drivers.netapp.utils as na_utils
@ddt.ddt
class NetAppESeriesDriverTestCase(object):
"""Test case for NetApp e-series iscsi driver."""
volume = {'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}
snapshot = {'id': '17928122-553b-4da9-9737-e5c3dcd97f75',
'volume_id': '114774fb-e15a-4fae-8ee2-c9723e3645ef',
'size': 2, 'volume_name': 'lun1',
'volume_size': 2, 'project_id': 'project',
'display_name': None, 'display_description': 'lun1',
'volume_type_id': None}
volume_sec = {'id': 'b6c01641-8955-4917-a5e3-077147478575',
'size': 2, 'volume_name': 'lun1',
'os_type': 'linux', 'provider_location': 'lun1',
'name_id': 'b6c01641-8955-4917-a5e3-077147478575',
'provider_auth': None, 'project_id': 'project',
'display_name': None, 'display_description': 'lun1',
'volume_type_id': None}
volume_clone = {'id': 'b4b24b27-c716-4647-b66d-8b93ead770a5', 'size': 3,
'volume_name': 'lun1',
'os_type': 'linux', 'provider_location': 'cl_sm',
'name_id': 'b4b24b27-c716-4647-b66d-8b93ead770a5',
'provider_auth': None,
'project_id': 'project', 'display_name': None,
'display_description': 'lun1',
'volume_type_id': None}
volume_clone_large = {'id': 'f6ef5bf5-e24f-4cbb-b4c4-11d631d6e553',
'size': 6, 'volume_name': 'lun1',
'os_type': 'linux', 'provider_location': 'cl_lg',
'name_id': 'f6ef5bf5-e24f-4cbb-b4c4-11d631d6e553',
'provider_auth': None,
'project_id': 'project', 'display_name': None,
'display_description': 'lun1',
'volume_type_id': None}
fake_eseries_volume_label = utils.convert_uuid_to_es_fmt(volume['id'])
fake_size_gb = volume['size']
fake_eseries_pool_label = 'DDP'
fake_ref = {'source-name': 'CFDGJSLS'}
fake_ret_vol = {'id': 'vol_id', 'label': 'label',
'worldWideName': 'wwn', 'capacity': '2147583648'}
PROTOCOL = 'iscsi'
def setUp(self):
super(NetAppESeriesDriverTestCase, self).setUp()
self._custom_setup()
def _custom_setup(self):
self.mock_object(na_utils, 'OpenStackInfo')
configuration = self._set_config(self.create_configuration())
self.driver = common.NetAppDriver(configuration=configuration)
self.library = self.driver.library
self.mock_object(self.library,
'_check_mode_get_or_register_storage_system')
self.mock_object(self.library, '_version_check')
self.mock_object(self.driver.library, '_check_storage_system')
self.driver.do_setup(context='context')
self.driver.library._client._endpoint = fakes.FAKE_ENDPOINT_HTTP
self.driver.library._client.features = mock.Mock()
self.driver.library._client.features.REST_1_4_RELEASE = True
def _set_config(self, configuration):
configuration.netapp_storage_family = 'eseries'
configuration.netapp_storage_protocol = self.PROTOCOL
configuration.netapp_transport_type = 'http'
configuration.netapp_server_hostname = '127.0.0.1'
configuration.netapp_server_port = None
configuration.netapp_webservice_path = '/devmgr/vn'
configuration.netapp_controller_ips = '127.0.0.2,127.0.0.3'
configuration.netapp_sa_password = 'pass1234'
configuration.netapp_login = 'rw'
configuration.netapp_password = 'rw'
configuration.netapp_storage_pools = 'DDP'
configuration.netapp_enable_multiattach = False
return configuration
@staticmethod
def create_configuration():
configuration = conf.Configuration(None)
configuration.append_config_values(options.netapp_basicauth_opts)
configuration.append_config_values(options.netapp_eseries_opts)
configuration.append_config_values(options.netapp_san_opts)
return configuration
@abc.abstractmethod
@mock.patch.object(na_utils, 'validate_instantiation')
def test_instantiation(self, mock_validate_instantiation):
pass
def test_embedded_mode(self):
self.mock_object(client.RestClient, '_init_features')
configuration = self._set_config(self.create_configuration())
configuration.netapp_controller_ips = '127.0.0.1,127.0.0.3'
driver = common.NetAppDriver(configuration=configuration)
self.mock_object(driver.library, '_version_check')
self.mock_object(client.RestClient, 'list_storage_systems',
return_value=[fakes.STORAGE_SYSTEM])
driver.do_setup(context='context')
self.assertEqual('1fa6efb5-f07b-4de4-9f0e-52e5f7ff5d1b',
driver.library._client.get_system_id())
def test_check_system_pwd_not_sync(self):
def list_system():
if getattr(self, 'test_count', None):
self.test_count = 1
return {'status': 'passwordoutofsync'}
return {'status': 'needsAttention'}
self.library._client.list_storage_system = mock.Mock(wraps=list_system)
result = self.library._check_storage_system()
self.assertTrue(bool(result))
def test_create_destroy(self):
self.mock_object(client.RestClient, 'delete_volume',
return_value='None')
self.mock_object(self.driver.library, 'create_volume',
return_value=self.volume)
self.mock_object(self.library._client, 'list_volume',
return_value=fakes.VOLUME)
self.driver.create_volume(self.volume)
self.driver.delete_volume(self.volume)
def test_vol_stats(self):
self.driver.get_volume_stats(refresh=False)
def test_get_pool(self):
self.mock_object(self.library, '_get_volume',
return_value={'volumeGroupRef': 'fake_ref'})
self.mock_object(self.library._client, "get_storage_pool",
return_value={'volumeGroupRef': 'fake_ref',
'label': 'ddp1'})
pool = self.driver.get_pool({'name_id': 'fake-uuid'})
self.assertEqual('ddp1', pool)
def test_get_pool_no_pools(self):
self.mock_object(self.library, '_get_volume',
return_value={'volumeGroupRef': 'fake_ref'})
self.mock_object(self.library._client, "get_storage_pool",
return_value=None)
pool = self.driver.get_pool({'name_id': 'fake-uuid'})
self.assertIsNone(pool)
@mock.patch.object(library.NetAppESeriesLibrary, '_create_volume',
mock.Mock())
def test_create_volume(self):
self.driver.create_volume(self.volume)
self.library._create_volume.assert_called_with(
'DDP', self.fake_eseries_volume_label, self.volume['size'], {})
def test_create_volume_no_pool_provided_by_scheduler(self):
volume = copy.deepcopy(self.volume)
volume['host'] = "host@backend" # missing pool
self.assertRaises(exception.InvalidHost, self.driver.create_volume,
volume)
@mock.patch.object(client.RestClient, 'list_storage_pools')
def test_helper_create_volume_fail(self, fake_list_pools):
fake_pool = {}
fake_pool['label'] = self.fake_eseries_pool_label
fake_pool['volumeGroupRef'] = 'foo'
fake_pool['raidLevel'] = 'raidDiskPool'
fake_pools = [fake_pool]
fake_list_pools.return_value = fake_pools
wrong_eseries_pool_label = 'hostname@backend'
self.assertRaises(exception.NetAppDriverException,
self.library._create_volume,
wrong_eseries_pool_label,
self.fake_eseries_volume_label,
self.fake_size_gb)
@mock.patch.object(library.LOG, 'info')
@mock.patch.object(client.RestClient, 'list_storage_pools')
@mock.patch.object(client.RestClient, 'create_volume',
mock.MagicMock(return_value='CorrectVolume'))
def test_helper_create_volume(self, storage_pools, log_info):
fake_pool = {}
fake_pool['label'] = self.fake_eseries_pool_label
fake_pool['volumeGroupRef'] = 'foo'
fake_pool['raidLevel'] = 'raidDiskPool'
fake_pools = [fake_pool]
storage_pools.return_value = fake_pools
storage_vol = self.library._create_volume(
self.fake_eseries_pool_label,
self.fake_eseries_volume_label,
self.fake_size_gb)
log_info.assert_called_once_with("Created volume with label %s.",
self.fake_eseries_volume_label)
self.assertEqual('CorrectVolume', storage_vol)
@mock.patch.object(client.RestClient, 'list_storage_pools')
@mock.patch.object(client.RestClient, 'create_volume',
mock.MagicMock(
side_effect=exception.NetAppDriverException))
@mock.patch.object(library.LOG, 'info', mock.Mock())
def test_create_volume_check_exception(self, fake_list_pools):
fake_pool = {}
fake_pool['label'] = self.fake_eseries_pool_label
fake_pool['volumeGroupRef'] = 'foo'
fake_pool['raidLevel'] = 'raidDiskPool'
fake_pools = [fake_pool]
fake_list_pools.return_value = fake_pools
self.assertRaises(exception.NetAppDriverException,
self.library._create_volume,
self.fake_eseries_pool_label,
self.fake_eseries_volume_label, self.fake_size_gb)
def test_portal_for_vol_controller(self):
volume = {'id': 'vol_id', 'currentManager': 'ctrl1'}
vol_nomatch = {'id': 'vol_id', 'currentManager': 'ctrl3'}
portals = [{'controller': 'ctrl2', 'iqn': 'iqn2'},
{'controller': 'ctrl1', 'iqn': 'iqn1'}]
portal = self.library._get_iscsi_portal_for_vol(volume, portals)
self.assertEqual({'controller': 'ctrl1', 'iqn': 'iqn1'}, portal)
portal = self.library._get_iscsi_portal_for_vol(vol_nomatch, portals)
self.assertEqual({'controller': 'ctrl2', 'iqn': 'iqn2'}, portal)
def test_portal_for_vol_any_false(self):
vol_nomatch = {'id': 'vol_id', 'currentManager': 'ctrl3'}
portals = [{'controller': 'ctrl2', 'iqn': 'iqn2'},
{'controller': 'ctrl1', 'iqn': 'iqn1'}]
self.assertRaises(exception.NetAppDriverException,
self.library._get_iscsi_portal_for_vol,
vol_nomatch, portals, False)
def test_do_setup_all_default(self):
configuration = self._set_config(self.create_configuration())
driver = common.NetAppDriver(configuration=configuration)
driver.library._check_mode_get_or_register_storage_system = mock.Mock()
mock_invoke = self.mock_object(client, 'RestClient')
driver.do_setup(context='context')
mock_invoke.assert_called_with(**fakes.FAKE_CLIENT_PARAMS)
def test_do_setup_http_default_port(self):
configuration = self._set_config(self.create_configuration())
configuration.netapp_transport_type = 'http'
driver = common.NetAppDriver(configuration=configuration)
driver.library._check_mode_get_or_register_storage_system = mock.Mock()
mock_invoke = self.mock_object(client, 'RestClient')
driver.do_setup(context='context')
mock_invoke.assert_called_with(**fakes.FAKE_CLIENT_PARAMS)
def test_do_setup_https_default_port(self):
configuration = self._set_config(self.create_configuration())
configuration.netapp_transport_type = 'https'
driver = common.NetAppDriver(configuration=configuration)
driver.library._check_mode_get_or_register_storage_system = mock.Mock()
mock_invoke = self.mock_object(client, 'RestClient')
driver.do_setup(context='context')
FAKE_EXPECTED_PARAMS = dict(fakes.FAKE_CLIENT_PARAMS, port=8443,
scheme='https')
mock_invoke.assert_called_with(**FAKE_EXPECTED_PARAMS)
def test_do_setup_http_non_default_port(self):
configuration = self._set_config(self.create_configuration())
configuration.netapp_server_port = 81
driver = common.NetAppDriver(configuration=configuration)
driver.library._check_mode_get_or_register_storage_system = mock.Mock()
mock_invoke = self.mock_object(client, 'RestClient')
driver.do_setup(context='context')
FAKE_EXPECTED_PARAMS = dict(fakes.FAKE_CLIENT_PARAMS, port=81)
mock_invoke.assert_called_with(**FAKE_EXPECTED_PARAMS)
def test_do_setup_https_non_default_port(self):
configuration = self._set_config(self.create_configuration())
configuration.netapp_transport_type = 'https'
configuration.netapp_server_port = 446
driver = common.NetAppDriver(configuration=configuration)
driver.library._check_mode_get_or_register_storage_system = mock.Mock()
mock_invoke = self.mock_object(client, 'RestClient')
driver.do_setup(context='context')
FAKE_EXPECTED_PARAMS = dict(fakes.FAKE_CLIENT_PARAMS, port=446,
scheme='https')
mock_invoke.assert_called_with(**FAKE_EXPECTED_PARAMS)
def test_setup_good_controller_ip(self):
configuration = self._set_config(self.create_configuration())
configuration.netapp_controller_ips = '127.0.0.1'
driver = common.NetAppDriver(configuration=configuration)
driver.library._check_mode_get_or_register_storage_system
def test_setup_good_controller_ips(self):
configuration = self._set_config(self.create_configuration())
configuration.netapp_controller_ips = '127.0.0.2,127.0.0.1'
driver = common.NetAppDriver(configuration=configuration)
driver.library._check_mode_get_or_register_storage_system
def test_setup_missing_controller_ip(self):
configuration = self._set_config(self.create_configuration())
configuration.netapp_controller_ips = None
driver = common.NetAppDriver(configuration=configuration)
self.assertRaises(exception.InvalidInput,
driver.do_setup, context='context')
def test_setup_error_invalid_controller_ip(self):
configuration = self._set_config(self.create_configuration())
configuration.netapp_controller_ips = '987.65.43.21'
driver = common.NetAppDriver(configuration=configuration)
self.mock_object(cinder_utils, 'resolve_hostname',
side_effect=socket.gaierror)
self.assertRaises(
exception.NoValidBackend,
driver.library._check_mode_get_or_register_storage_system)
def test_setup_error_invalid_first_controller_ip(self):
configuration = self._set_config(self.create_configuration())
configuration.netapp_controller_ips = '987.65.43.21,127.0.0.1'
driver = common.NetAppDriver(configuration=configuration)
self.mock_object(cinder_utils, 'resolve_hostname',
side_effect=socket.gaierror)
self.assertRaises(
exception.NoValidBackend,
driver.library._check_mode_get_or_register_storage_system)
def test_setup_error_invalid_second_controller_ip(self):
configuration = self._set_config(self.create_configuration())
configuration.netapp_controller_ips = '127.0.0.1,987.65.43.21'
driver = common.NetAppDriver(configuration=configuration)
self.mock_object(cinder_utils, 'resolve_hostname',
side_effect=socket.gaierror)
self.assertRaises(
exception.NoValidBackend,
driver.library._check_mode_get_or_register_storage_system)
def test_setup_error_invalid_both_controller_ips(self):
configuration = self._set_config(self.create_configuration())
configuration.netapp_controller_ips = '564.124.1231.1,987.65.43.21'
driver = common.NetAppDriver(configuration=configuration)
self.mock_object(cinder_utils, 'resolve_hostname',
side_effect=socket.gaierror)
self.assertRaises(
exception.NoValidBackend,
driver.library._check_mode_get_or_register_storage_system)
def test_manage_existing_get_size(self):
self.library._get_existing_vol_with_manage_ref = mock.Mock(
return_value=self.fake_ret_vol)
size = self.driver.manage_existing_get_size(self.volume, self.fake_ref)
self.assertEqual(3, size)
self.library._get_existing_vol_with_manage_ref.assert_called_once_with(
self.fake_ref)
def test_get_exist_vol_source_name_missing(self):
self.library._client.list_volume = mock.Mock(
side_effect=exception.InvalidInput)
self.assertRaises(exception.ManageExistingInvalidReference,
self.library._get_existing_vol_with_manage_ref,
{'id': '1234'})
@ddt.data('source-id', 'source-name')
def test_get_exist_vol_source_not_found(self, attr_name):
def _get_volume(v_id):
d = {'id': '1', 'name': 'volume1', 'worldWideName': '0'}
if v_id in d:
return d[v_id]
else:
raise exception.VolumeNotFound(message=v_id)
self.library._client.list_volume = mock.Mock(wraps=_get_volume)
self.assertRaises(exception.ManageExistingInvalidReference,
self.library._get_existing_vol_with_manage_ref,
{attr_name: 'name2'})
self.library._client.list_volume.assert_called_once_with(
'name2')
def test_get_exist_vol_with_manage_ref(self):
fake_ret_vol = {'id': 'right'}
self.library._client.list_volume = mock.Mock(return_value=fake_ret_vol)
actual_vol = self.library._get_existing_vol_with_manage_ref(
{'source-name': 'name2'})
self.library._client.list_volume.assert_called_once_with('name2')
self.assertEqual(fake_ret_vol, actual_vol)
@mock.patch.object(utils, 'convert_uuid_to_es_fmt')
def test_manage_existing_same_label(self, mock_convert_es_fmt):
self.library._get_existing_vol_with_manage_ref = mock.Mock(
return_value=self.fake_ret_vol)
mock_convert_es_fmt.return_value = 'label'
self.driver.manage_existing(self.volume, self.fake_ref)
self.library._get_existing_vol_with_manage_ref.assert_called_once_with(
self.fake_ref)
mock_convert_es_fmt.assert_called_once_with(
'114774fb-e15a-4fae-8ee2-c9723e3645ef')
@mock.patch.object(utils, 'convert_uuid_to_es_fmt')
def test_manage_existing_new(self, mock_convert_es_fmt):
self.library._get_existing_vol_with_manage_ref = mock.Mock(
return_value=self.fake_ret_vol)
mock_convert_es_fmt.return_value = 'vol_label'
self.library._client.update_volume = mock.Mock(
return_value={'id': 'update', 'worldWideName': 'wwn'})
self.driver.manage_existing(self.volume, self.fake_ref)
self.library._get_existing_vol_with_manage_ref.assert_called_once_with(
self.fake_ref)
mock_convert_es_fmt.assert_called_once_with(
'114774fb-e15a-4fae-8ee2-c9723e3645ef')
self.library._client.update_volume.assert_called_once_with(
'vol_id', 'vol_label')
@mock.patch.object(library.LOG, 'info')
def test_unmanage(self, log_info):
self.library._get_volume = mock.Mock(return_value=self.fake_ret_vol)
self.driver.unmanage(self.volume)
self.library._get_volume.assert_called_once_with(
'114774fb-e15a-4fae-8ee2-c9723e3645ef')
self.assertEqual(1, log_info.call_count)
@mock.patch.object(library.NetAppESeriesLibrary, 'ensure_export',
mock.Mock())
def test_ensure_export(self):
self.driver.ensure_export('context', self.fake_ret_vol)
self.assertTrue(self.library.ensure_export.called)
@mock.patch.object(library.NetAppESeriesLibrary, 'extend_volume',
mock.Mock())
def test_extend_volume(self):
capacity = 10
self.driver.extend_volume(self.fake_ret_vol, capacity)
self.library.extend_volume.assert_called_with(self.fake_ret_vol,
capacity)
@mock.patch.object(library.NetAppESeriesLibrary,
'create_cgsnapshot', mock.Mock())
def test_create_cgsnapshot(self):
cgsnapshot = copy.deepcopy(fakes.FAKE_CINDER_CG_SNAPSHOT)
snapshots = copy.deepcopy([fakes.SNAPSHOT_IMAGE])
self.driver.create_cgsnapshot('ctx', cgsnapshot, snapshots)
self.library.create_cgsnapshot.assert_called_with(cgsnapshot,
snapshots)
@mock.patch.object(library.NetAppESeriesLibrary,
'delete_cgsnapshot', mock.Mock())
def test_delete_cgsnapshot(self):
cgsnapshot = copy.deepcopy(fakes.FAKE_CINDER_CG_SNAPSHOT)
snapshots = copy.deepcopy([fakes.SNAPSHOT_IMAGE])
self.driver.delete_cgsnapshot('ctx', cgsnapshot, snapshots)
self.library.delete_cgsnapshot.assert_called_with(cgsnapshot,
snapshots)
@mock.patch.object(library.NetAppESeriesLibrary,
'create_consistencygroup', mock.Mock())
def test_create_consistencygroup(self):
cg = copy.deepcopy(fakes.FAKE_CINDER_CG)
self.driver.create_consistencygroup('ctx', cg)
self.library.create_consistencygroup.assert_called_with(cg)
@mock.patch.object(library.NetAppESeriesLibrary,
'delete_consistencygroup', mock.Mock())
def test_delete_consistencygroup(self):
cg = copy.deepcopy(fakes.FAKE_CINDER_CG)
volumes = copy.deepcopy([fakes.VOLUME])
self.driver.delete_consistencygroup('ctx', cg, volumes)
self.library.delete_consistencygroup.assert_called_with(cg, volumes)
@mock.patch.object(library.NetAppESeriesLibrary,
'update_consistencygroup', mock.Mock())
def test_update_consistencygroup(self):
group = copy.deepcopy(fakes.FAKE_CINDER_CG)
self.driver.update_consistencygroup('ctx', group, {}, {})
self.library.update_consistencygroup.assert_called_with(group, {}, {})
@mock.patch.object(library.NetAppESeriesLibrary,
'create_consistencygroup_from_src', mock.Mock())
def test_create_consistencygroup_from_src(self):
cg = copy.deepcopy(fakes.FAKE_CINDER_CG)
volumes = copy.deepcopy([fakes.VOLUME])
source_vols = copy.deepcopy([fakes.VOLUME])
cgsnapshot = copy.deepcopy(fakes.FAKE_CINDER_CG_SNAPSHOT)
source_cg = copy.deepcopy(fakes.FAKE_CINDER_CG_SNAPSHOT)
snapshots = copy.deepcopy([fakes.SNAPSHOT_IMAGE])
self.driver.create_consistencygroup_from_src(
'ctx', cg, volumes, cgsnapshot, snapshots, source_cg,
source_vols)
self.library.create_consistencygroup_from_src.assert_called_with(
cg, volumes, cgsnapshot, snapshots, source_cg, source_vols)

View File

@ -1,35 +0,0 @@
# 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
from cinder.tests.unit.volume.drivers.netapp.eseries import test_driver
import cinder.volume.drivers.netapp.eseries.fc_driver as fc
from cinder.volume.drivers.netapp import utils as na_utils
class NetAppESeriesFibreChannelDriverTestCase(test_driver
.NetAppESeriesDriverTestCase,
test.TestCase):
PROTOCOL = 'fc'
@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)

View File

@ -1,662 +0,0 @@
# Copyright (c) 2015 Alex Meade. All rights reserved.
# 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.
"""Mock unit tests for the NetApp E-series iscsi driver."""
import copy
import mock
import six
from cinder import exception
from cinder.objects import fields
from cinder import test
from cinder.tests.unit.volume.drivers.netapp.eseries \
import fakes as eseries_fakes
from cinder.volume.drivers.netapp.eseries import host_mapper
from cinder.volume.drivers.netapp.eseries import 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':
fields.VolumeAttachStatus.DETACHED, "status": "available"
}
FAKE_MAPPINGS = [{u'lun': 1}]
FAKE_USED_UP_MAPPINGS = [{u'lun': n} for n in range(256)]
FAKE_USED_UP_LUN_ID_DICT = {n: 1 for n in range(256)}
FAKE_UNUSED_LUN_ID = set([])
FAKE_USED_LUN_ID_DICT = ({0: 1, 1: 1})
FAKE_USED_LUN_IDS = [1, 2]
FAKE_SINGLE_USED_LUN_ID = 1
FAKE_USED_UP_LUN_IDS = range(256)
class NetAppEseriesHostMapperTestCase(test.TestCase):
def setUp(self):
super(NetAppEseriesHostMapperTestCase, self).setUp()
self.client = eseries_fakes.FakeEseriesClient()
def test_unmap_volume_from_host_volume_mapped_to_host(self):
fake_eseries_volume = copy.deepcopy(eseries_fakes.VOLUME)
fake_eseries_volume['listOfMappings'] = [
eseries_fakes.VOLUME_MAPPING
]
self.mock_object(self.client, 'list_volumes',
return_value=[fake_eseries_volume])
self.mock_object(self.client, 'delete_volume_mapping')
host_mapper.unmap_volume_from_host(self.client, get_fake_volume(),
eseries_fakes.HOST,
eseries_fakes.VOLUME_MAPPING)
self.assertTrue(self.client.delete_volume_mapping.called)
def test_unmap_volume_from_host_volume_mapped_to_different_host(self):
fake_eseries_volume = copy.deepcopy(eseries_fakes.VOLUME)
# Mapped to host 1
fake_eseries_volume['listOfMappings'] = [
eseries_fakes.VOLUME_MAPPING
]
self.mock_object(self.client, 'list_volumes',
return_value=[fake_eseries_volume])
self.mock_object(self.client, 'delete_volume_mapping')
self.mock_object(self.client, 'get_host_group',
side_effect=exception.NotFound)
err = self.assertRaises(exception.NetAppDriverException,
host_mapper.unmap_volume_from_host,
self.client, get_fake_volume(),
eseries_fakes.HOST_2,
eseries_fakes.VOLUME_MAPPING)
self.assertIn("not currently mapped to host", six.text_type(err))
def test_unmap_volume_from_host_volume_mapped_to_host_group_but_not_host(
self):
"""Test volume mapped to host not in specified host group.
Ensure an error is raised if the specified host is not in the
host group the volume is mapped to.
"""
fake_eseries_volume = copy.deepcopy(eseries_fakes.VOLUME)
fake_volume_mapping = copy.deepcopy(eseries_fakes.VOLUME_MAPPING)
fake_volume_mapping['mapRef'] = eseries_fakes.MULTIATTACH_HOST_GROUP[
'clusterRef']
fake_eseries_volume['listOfMappings'] = [fake_volume_mapping]
self.mock_object(self.client, 'list_volumes',
return_value=[fake_eseries_volume])
fake_host = copy.deepcopy(eseries_fakes.HOST)
fake_host['clusterRef'] = utils.NULL_REF
self.mock_object(self.client, 'list_hosts',
return_value=[fake_host])
err = self.assertRaises(exception.NetAppDriverException,
host_mapper.unmap_volume_from_host,
self.client, get_fake_volume(),
fake_host,
fake_volume_mapping)
self.assertIn("not currently mapped to host", six.text_type(err))
def test_unmap_volume_from_host_volume_mapped_to_multiattach_host_group(
self):
fake_eseries_volume = copy.deepcopy(eseries_fakes.VOLUME)
fake_volume_mapping = copy.deepcopy(eseries_fakes.VOLUME_MAPPING)
fake_volume_mapping['mapRef'] = eseries_fakes.MULTIATTACH_HOST_GROUP[
'clusterRef']
fake_eseries_volume['listOfMappings'] = [fake_volume_mapping]
self.mock_object(self.client, 'delete_volume_mapping')
self.mock_object(self.client, 'list_volumes',
return_value=[fake_eseries_volume])
fake_volume = get_fake_volume()
fake_volume['status'] = 'detaching'
host_mapper.unmap_volume_from_host(self.client, fake_volume,
eseries_fakes.HOST,
fake_volume_mapping)
self.assertTrue(self.client.delete_volume_mapping.called)
def test_unmap_volume_from_host_volume_mapped_to_multiattach_host_group_and_migrating( # noqa
self):
fake_eseries_volume = copy.deepcopy(eseries_fakes.VOLUME)
fake_volume_mapping = copy.deepcopy(eseries_fakes.VOLUME_MAPPING)
fake_volume_mapping['mapRef'] = eseries_fakes.MULTIATTACH_HOST_GROUP[
'clusterRef']
fake_eseries_volume['listOfMappings'] = [fake_volume_mapping]
self.mock_object(self.client, 'delete_volume_mapping')
self.mock_object(self.client, 'list_volumes',
return_value=[fake_eseries_volume])
fake_volume = get_fake_volume()
fake_volume['status'] = 'in-use'
host_mapper.unmap_volume_from_host(self.client, fake_volume,
eseries_fakes.HOST,
fake_volume_mapping)
self.assertFalse(self.client.delete_volume_mapping.called)
def test_unmap_volume_from_host_volume_mapped_to_outside_host_group(self):
"""Test volume mapped to host group without host.
Ensure we raise error when we find a volume is mapped to an unknown
host group that does not have the host.
"""
fake_eseries_volume = copy.deepcopy(eseries_fakes.VOLUME)
fake_volume_mapping = copy.deepcopy(eseries_fakes.VOLUME_MAPPING)
fake_ref = "8500000060080E500023C7340036035F515B78FD"
fake_volume_mapping['mapRef'] = fake_ref
fake_eseries_volume['listOfMappings'] = [fake_volume_mapping]
self.mock_object(self.client, 'list_volumes',
return_value=[fake_eseries_volume])
fake_host = copy.deepcopy(eseries_fakes.HOST)
fake_host['clusterRef'] = utils.NULL_REF
self.mock_object(self.client, 'list_hosts',
return_value=[fake_host])
self.mock_object(self.client, 'get_host_group',
return_value=eseries_fakes.FOREIGN_HOST_GROUP)
err = self.assertRaises(exception.NetAppDriverException,
host_mapper.unmap_volume_from_host,
self.client, get_fake_volume(),
eseries_fakes.HOST,
fake_volume_mapping)
self.assertIn("unsupported host group", six.text_type(err))
def test_unmap_volume_from_host_volume_mapped_to_outside_host_group_w_host(
self):
"""Test volume mapped to host in unknown host group.
Ensure we raise error when we find a volume is mapped to an unknown
host group that has the host.
"""
fake_eseries_volume = copy.deepcopy(eseries_fakes.VOLUME)
fake_volume_mapping = copy.deepcopy(eseries_fakes.VOLUME_MAPPING)
fake_ref = "8500000060080E500023C7340036035F515B78FD"
fake_volume_mapping['mapRef'] = fake_ref
fake_eseries_volume['clusterRef'] = fake_ref
fake_eseries_volume['listOfMappings'] = [fake_volume_mapping]
self.mock_object(self.client, 'list_volumes',
return_value=[fake_eseries_volume])
fake_host = copy.deepcopy(eseries_fakes.HOST)
fake_host['clusterRef'] = utils.NULL_REF
self.mock_object(self.client, 'list_hosts', return_value=[fake_host])
self.mock_object(self.client, 'get_host_group',
return_value=eseries_fakes.FOREIGN_HOST_GROUP)
err = self.assertRaises(exception.NetAppDriverException,
host_mapper.unmap_volume_from_host,
self.client, get_fake_volume(),
eseries_fakes.HOST,
fake_volume_mapping)
self.assertIn("unsupported host group", six.text_type(err))
def test_map_volume_to_single_host_volume_not_mapped(self):
self.mock_object(self.client, 'create_volume_mapping',
return_value=eseries_fakes.VOLUME_MAPPING)
host_mapper.map_volume_to_single_host(self.client, get_fake_volume(),
eseries_fakes.VOLUME,
eseries_fakes.HOST,
None,
False)
self.assertTrue(self.client.create_volume_mapping.called)
def test_map_volume_to_single_host_volume_already_mapped_to_target_host(
self):
"""Should be a no-op"""
self.mock_object(self.client, 'create_volume_mapping')
host_mapper.map_volume_to_single_host(self.client,
get_fake_volume(),
eseries_fakes.VOLUME,
eseries_fakes.HOST,
eseries_fakes.VOLUME_MAPPING,
False)
self.assertFalse(self.client.create_volume_mapping.called)
def test_map_volume_to_single_host_volume_mapped_to_multiattach_host_group(
self):
"""Test map volume to a single host.
Should move mapping to target host if volume is not migrating or
attached(in-use). If volume is not in use then it should not require a
mapping making it ok to sever the mapping to the host group.
"""
fake_mapping_to_other_host = copy.deepcopy(
eseries_fakes.VOLUME_MAPPING)
fake_mapping_to_other_host['mapRef'] = \
eseries_fakes.MULTIATTACH_HOST_GROUP['clusterRef']
self.mock_object(self.client, 'move_volume_mapping_via_symbol',
return_value={'lun': 5})
host_mapper.map_volume_to_single_host(self.client,
get_fake_volume(),
eseries_fakes.VOLUME,
eseries_fakes.HOST,
fake_mapping_to_other_host,
False)
self.assertTrue(self.client.move_volume_mapping_via_symbol.called)
def test_map_volume_to_single_host_volume_mapped_to_multiattach_host_group_and_migrating( # noqa
self):
"""Should raise error saying multiattach not enabled"""
fake_mapping_to_other_host = copy.deepcopy(
eseries_fakes.VOLUME_MAPPING)
fake_mapping_to_other_host['mapRef'] = \
eseries_fakes.MULTIATTACH_HOST_GROUP['clusterRef']
fake_volume = get_fake_volume()
fake_volume['attach_status'] = fields.VolumeAttachStatus.ATTACHED
err = self.assertRaises(exception.NetAppDriverException,
host_mapper.map_volume_to_single_host,
self.client, fake_volume,
eseries_fakes.VOLUME,
eseries_fakes.HOST,
fake_mapping_to_other_host,
False)
self.assertIn('multiattach is disabled', six.text_type(err))
def test_map_volume_to_single_host_volume_mapped_to_multiattach_host_group_and_attached( # noqa
self):
"""Should raise error saying multiattach not enabled"""
fake_mapping_to_other_host = copy.deepcopy(
eseries_fakes.VOLUME_MAPPING)
fake_mapping_to_other_host['mapRef'] = \
eseries_fakes.MULTIATTACH_HOST_GROUP['clusterRef']
fake_volume = get_fake_volume()
fake_volume['attach_status'] = fields.VolumeAttachStatus.ATTACHED
err = self.assertRaises(exception.NetAppDriverException,
host_mapper.map_volume_to_single_host,
self.client, fake_volume,
eseries_fakes.VOLUME,
eseries_fakes.HOST,
fake_mapping_to_other_host,
False)
self.assertIn('multiattach is disabled', six.text_type(err))
def test_map_volume_to_single_host_volume_mapped_to_another_host(self):
"""Should raise error saying multiattach not enabled"""
fake_mapping_to_other_host = copy.deepcopy(
eseries_fakes.VOLUME_MAPPING)
fake_mapping_to_other_host['mapRef'] = eseries_fakes.HOST_2[
'hostRef']
err = self.assertRaises(exception.NetAppDriverException,
host_mapper.map_volume_to_single_host,
self.client, get_fake_volume(),
eseries_fakes.VOLUME,
eseries_fakes.HOST,
fake_mapping_to_other_host,
False)
self.assertIn('multiattach is disabled', six.text_type(err))
def test_map_volume_to_multiple_hosts_volume_already_mapped_to_target_host(
self):
"""Should be a no-op."""
self.mock_object(self.client, 'create_volume_mapping')
host_mapper.map_volume_to_multiple_hosts(self.client,
get_fake_volume(),
eseries_fakes.VOLUME,
eseries_fakes.HOST,
eseries_fakes.VOLUME_MAPPING)
self.assertFalse(self.client.create_volume_mapping.called)
def test_map_volume_to_multiple_hosts_volume_mapped_to_multiattach_host_group( # noqa
self):
"""Should ensure target host is in the multiattach host group."""
fake_host = copy.deepcopy(eseries_fakes.HOST_2)
fake_host['clusterRef'] = utils.NULL_REF
fake_mapping_to_host_group = copy.deepcopy(
eseries_fakes.VOLUME_MAPPING)
fake_mapping_to_host_group['mapRef'] = \
eseries_fakes.MULTIATTACH_HOST_GROUP['clusterRef']
self.mock_object(self.client, 'set_host_group_for_host')
self.mock_object(self.client, 'get_host_group',
return_value=eseries_fakes.MULTIATTACH_HOST_GROUP)
host_mapper.map_volume_to_multiple_hosts(self.client,
get_fake_volume(),
eseries_fakes.VOLUME,
fake_host,
fake_mapping_to_host_group)
self.assertEqual(
1, self.client.set_host_group_for_host.call_count)
def test_map_volume_to_multiple_hosts_volume_mapped_to_multiattach_host_group_with_lun_collision( # noqa
self):
"""Should ensure target host is in the multiattach host group."""
fake_host = copy.deepcopy(eseries_fakes.HOST_2)
fake_host['clusterRef'] = utils.NULL_REF
fake_mapping_to_host_group = copy.deepcopy(
eseries_fakes.VOLUME_MAPPING)
fake_mapping_to_host_group['mapRef'] = \
eseries_fakes.MULTIATTACH_HOST_GROUP['clusterRef']
self.mock_object(self.client, 'set_host_group_for_host',
side_effect=exception.NetAppDriverException)
self.assertRaises(exception.NetAppDriverException,
host_mapper.map_volume_to_multiple_hosts,
self.client,
get_fake_volume(),
eseries_fakes.VOLUME,
fake_host,
fake_mapping_to_host_group)
def test_map_volume_to_multiple_hosts_volume_mapped_to_another_host(self):
"""Test that mapping moves to another host group.
Should ensure both existing host and destination host are in
multiattach host group and move the mapping to the host group.
"""
existing_host = copy.deepcopy(eseries_fakes.HOST)
existing_host['clusterRef'] = utils.NULL_REF
target_host = copy.deepcopy(eseries_fakes.HOST_2)
target_host['clusterRef'] = utils.NULL_REF
self.mock_object(self.client, 'get_host', return_value=existing_host)
self.mock_object(self.client, 'set_host_group_for_host')
self.mock_object(self.client, 'get_host_group',
side_effect=exception.NotFound)
mock_move_mapping = mock.Mock(
return_value=eseries_fakes.VOLUME_MAPPING_TO_MULTIATTACH_GROUP)
self.mock_object(self.client,
'move_volume_mapping_via_symbol',
mock_move_mapping)
host_mapper.map_volume_to_multiple_hosts(self.client,
get_fake_volume(),
eseries_fakes.VOLUME,
target_host,
eseries_fakes.VOLUME_MAPPING)
self.assertEqual(
2, self.client.set_host_group_for_host.call_count)
self.assertTrue(self.client.move_volume_mapping_via_symbol
.called)
def test_map_volume_to_multiple_hosts_volume_mapped_to_another_host_with_lun_collision_with_source_host( # noqa
self):
"""Test moving source host to multiattach host group.
Should fail attempting to move source host to multiattach host
group and raise an error.
"""
existing_host = copy.deepcopy(eseries_fakes.HOST)
existing_host['clusterRef'] = utils.NULL_REF
target_host = copy.deepcopy(eseries_fakes.HOST_2)
target_host['clusterRef'] = utils.NULL_REF
self.mock_object(self.client, 'get_host', return_value=existing_host)
self.mock_object(self.client, 'set_host_group_for_host',
side_effect=[None, exception.NetAppDriverException])
self.mock_object(self.client, 'get_host_group',
side_effect=exception.NotFound)
mock_move_mapping = mock.Mock(
return_value=eseries_fakes.VOLUME_MAPPING_TO_MULTIATTACH_GROUP)
self.mock_object(self.client,
'move_volume_mapping_via_symbol',
mock_move_mapping)
self.assertRaises(exception.NetAppDriverException,
host_mapper.map_volume_to_multiple_hosts,
self.client,
get_fake_volume(),
eseries_fakes.VOLUME,
target_host,
eseries_fakes.VOLUME_MAPPING)
def test_map_volume_to_multiple_hosts_volume_mapped_to_another_host_with_lun_collision_with_dest_host( # noqa
self):
"""Test moving destination host to multiattach host group.
Should fail attempting to move destination host to multiattach host
group and raise an error.
"""
existing_host = copy.deepcopy(eseries_fakes.HOST)
existing_host['clusterRef'] = utils.NULL_REF
target_host = copy.deepcopy(eseries_fakes.HOST_2)
target_host['clusterRef'] = utils.NULL_REF
self.mock_object(self.client, 'get_host', return_value=existing_host)
self.mock_object(self.client, 'set_host_group_for_host',
side_effect=[exception.NetAppDriverException, None])
self.mock_object(self.client, 'get_host_group',
side_effect=exception.NotFound)
mock_move_mapping = mock.Mock(
return_value=eseries_fakes.VOLUME_MAPPING_TO_MULTIATTACH_GROUP)
self.mock_object(self.client,
'move_volume_mapping_via_symbol',
mock_move_mapping)
self.assertRaises(exception.NetAppDriverException,
host_mapper.map_volume_to_multiple_hosts,
self.client,
get_fake_volume(),
eseries_fakes.VOLUME,
target_host,
eseries_fakes.VOLUME_MAPPING)
def test_map_volume_to_multiple_hosts_volume_mapped_to_foreign_host_group(
self):
"""Test a target when the host is in a foreign host group.
Should raise an error stating the volume is mapped to an
unsupported host group.
"""
fake_eseries_volume = copy.deepcopy(eseries_fakes.VOLUME)
fake_volume_mapping = copy.deepcopy(eseries_fakes.VOLUME_MAPPING)
fake_ref = "8500000060080E500023C7340036035F515B78FD"
fake_volume_mapping['mapRef'] = fake_ref
self.mock_object(self.client, 'list_volumes',
return_value=[fake_eseries_volume])
fake_host = copy.deepcopy(eseries_fakes.HOST)
fake_host['clusterRef'] = utils.NULL_REF
self.mock_object(self.client, 'get_host_group',
return_value=eseries_fakes.FOREIGN_HOST_GROUP)
err = self.assertRaises(exception.NetAppDriverException,
host_mapper.map_volume_to_multiple_hosts,
self.client,
get_fake_volume(),
eseries_fakes.VOLUME,
fake_host,
fake_volume_mapping)
self.assertIn("unsupported host group", six.text_type(err))
def test_map_volume_to_multiple_hosts_volume_mapped_to_host_in_foreign_host_group( # noqa
self):
"""Test a target when the host is in a foreign host group.
Should raise an error stating the volume is mapped to a
host that is in an unsupported host group.
"""
fake_eseries_volume = copy.deepcopy(eseries_fakes.VOLUME)
fake_volume_mapping = copy.deepcopy(eseries_fakes.VOLUME_MAPPING)
fake_host = copy.deepcopy(eseries_fakes.HOST_2)
fake_host['clusterRef'] = eseries_fakes.FOREIGN_HOST_GROUP[
'clusterRef']
fake_volume_mapping['mapRef'] = fake_host['hostRef']
fake_eseries_volume['listOfMappings'] = [fake_volume_mapping]
self.mock_object(self.client, 'list_volumes',
return_value=[fake_eseries_volume])
self.mock_object(self.client, 'get_host', return_value=fake_host)
self.mock_object(self.client, 'get_host_group',
side_effect=[eseries_fakes.FOREIGN_HOST_GROUP])
err = self.assertRaises(exception.NetAppDriverException,
host_mapper.map_volume_to_multiple_hosts,
self.client,
get_fake_volume(),
eseries_fakes.VOLUME,
eseries_fakes.HOST,
fake_volume_mapping)
self.assertIn("unsupported host group", six.text_type(err))
def test_map_volume_to_multiple_hosts_volume_target_host_in_foreign_host_group( # noqa
self):
"""Test a target when the host is in a foreign host group.
Should raise an error stating the target host is in an
unsupported host group.
"""
fake_eseries_volume = copy.deepcopy(eseries_fakes.VOLUME)
fake_volume_mapping = copy.deepcopy(eseries_fakes.VOLUME_MAPPING)
fake_host = copy.deepcopy(eseries_fakes.HOST_2)
fake_host['clusterRef'] = eseries_fakes.FOREIGN_HOST_GROUP[
'clusterRef']
self.mock_object(self.client, 'list_volumes',
return_value=[fake_eseries_volume])
self.mock_object(self.client, 'get_host',
return_value=eseries_fakes.HOST)
self.mock_object(self.client, 'get_host_group',
side_effect=[eseries_fakes.FOREIGN_HOST_GROUP])
err = self.assertRaises(exception.NetAppDriverException,
host_mapper.map_volume_to_multiple_hosts,
self.client,
get_fake_volume(),
eseries_fakes.VOLUME,
fake_host,
fake_volume_mapping)
self.assertIn("unsupported host group", six.text_type(err))
def test_get_unused_lun_ids(self):
unused_lun_ids = host_mapper._get_unused_lun_ids(FAKE_MAPPINGS)
self.assertEqual(set(range(2, 256)), unused_lun_ids)
def test_get_unused_lun_id_counter(self):
used_lun_id_count = host_mapper._get_used_lun_id_counter(
FAKE_MAPPINGS)
self.assertEqual(FAKE_USED_LUN_ID_DICT, used_lun_id_count)
def test_get_unused_lun_ids_used_up_luns(self):
unused_lun_ids = host_mapper._get_unused_lun_ids(
FAKE_USED_UP_MAPPINGS)
self.assertEqual(FAKE_UNUSED_LUN_ID, unused_lun_ids)
def test_get_lun_id_counter_used_up_luns(self):
used_lun_ids = host_mapper._get_used_lun_id_counter(
FAKE_USED_UP_MAPPINGS)
self.assertEqual(FAKE_USED_UP_LUN_ID_DICT, used_lun_ids)
def test_host_not_full(self):
fake_host = copy.deepcopy(eseries_fakes.HOST)
self.assertFalse(host_mapper._is_host_full(self.client, fake_host))
def test_host_full(self):
fake_host = copy.deepcopy(eseries_fakes.HOST)
self.mock_object(self.client, 'get_volume_mappings_for_host',
return_value=FAKE_USED_UP_MAPPINGS)
self.assertTrue(host_mapper._is_host_full(self.client, fake_host))
def test_get_free_lun(self):
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,
[])
self.assertEqual(3, lun)
def test_get_free_lun_host_full(self):
fake_host = copy.deepcopy(eseries_fakes.HOST)
self.mock_object(host_mapper, '_is_host_full', return_value=True)
self.assertRaises(
exception.NetAppDriverException,
host_mapper._get_free_lun,
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)
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(host_mapper, '_is_host_full', return_value=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_for_host',
return_value=FAKE_USED_UP_MAPPINGS)
self.assertRaises(exception.NetAppDriverException,
host_mapper._get_free_lun,
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.mock_object(self.client, 'get_volume_mappings',
return_value=FAKE_USED_UP_MAPPINGS)
self.assertRaises(exception.NetAppDriverException,
host_mapper._get_free_lun,
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)
self.assertEqual(set([0, 1]), used_luns)
def test_get_lun_by_mapping_no_mapping(self):
used_luns = host_mapper._get_used_lun_ids_for_mappings([])
self.assertEqual(set([0]), used_luns)
def test_lun_id_available_on_host(self):
fake_host = copy.deepcopy(eseries_fakes.HOST)
self.assertTrue(host_mapper._is_lun_id_available_on_host(
self.client, fake_host, FAKE_UNUSED_LUN_ID))
def test_no_lun_id_available_on_host(self):
fake_host = copy.deepcopy(eseries_fakes.HOST_3)
self.mock_object(self.client, 'get_volume_mappings_for_host',
return_value=FAKE_USED_UP_MAPPINGS)
self.assertFalse(host_mapper._is_lun_id_available_on_host(
self.client, fake_host, FAKE_SINGLE_USED_LUN_ID))

View File

@ -1,33 +0,0 @@
# Copyright (c) 2015 Alex Meade. All rights reserved.
# Copyright (c) 2015 Michael Price. All rights reserved.
# 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 ddt
import mock
from cinder import test
from cinder.tests.unit.volume.drivers.netapp.eseries import test_driver
from cinder.volume.drivers.netapp.eseries import iscsi_driver as iscsi
import cinder.volume.drivers.netapp.utils as na_utils
@ddt.ddt
class NetAppESeriesIscsiDriverTestCase(test_driver.NetAppESeriesDriverTestCase,
test.TestCase):
@mock.patch.object(na_utils, 'validate_instantiation')
def test_instantiation(self, mock_validate_instantiation):
iscsi.NetAppEseriesISCSIDriver(configuration=mock.Mock())

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +0,0 @@
# Copyright (c) 2014 Clinton Knight. All rights reserved.
# 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.
"""
Mock unit tests for the NetApp E-series driver utility module
"""
import six
from cinder import test
from cinder.volume.drivers.netapp.eseries import utils
class NetAppEseriesDriverUtilsTestCase(test.TestCase):
def test_convert_uuid_to_es_fmt(self):
value = 'e67e931a-b2ed-4890-938b-3acc6a517fac'
result = utils.convert_uuid_to_es_fmt(value)
self.assertEqual('4Z7JGGVS5VEJBE4LHLGGUUL7VQ', result)
def test_convert_es_fmt_to_uuid(self):
value = '4Z7JGGVS5VEJBE4LHLGGUUL7VQ'
result = six.text_type(utils.convert_es_fmt_to_uuid(value))
self.assertEqual('e67e931a-b2ed-4890-938b-3acc6a517fac', result)

View File

@ -32,7 +32,6 @@ from cinder.volume.drivers.netapp import utils as na_utils
LOG = logging.getLogger(__name__)
DATAONTAP_PATH = 'cinder.volume.drivers.netapp.dataontap'
ESERIES_PATH = 'cinder.volume.drivers.netapp.eseries'
# Add new drivers here, no other code changes required.
NETAPP_UNIFIED_DRIVER_REGISTRY = {
@ -41,11 +40,6 @@ NETAPP_UNIFIED_DRIVER_REGISTRY = {
'iscsi': DATAONTAP_PATH + '.iscsi_cmode.NetAppCmodeISCSIDriver',
'nfs': DATAONTAP_PATH + '.nfs_cmode.NetAppCmodeNfsDriver',
'fc': DATAONTAP_PATH + '.fc_cmode.NetAppCmodeFibreChannelDriver'
},
'eseries':
{
'iscsi': ESERIES_PATH + '.iscsi_driver.NetAppEseriesISCSIDriver',
'fc': ESERIES_PATH + '.fc_driver.NetAppEseriesFibreChannelDriver'
}}

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +0,0 @@
# Copyright (c) 2015 Alex Meade. All Rights Reserved.
# 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.
from cinder import exception
from cinder.i18n import _
class VolumeNotMapped(exception.NetAppDriverException):
message = _("Volume %(volume_id)s is not currently mapped to host "
"%(host)s")
class UnsupportedHostGroup(exception.NetAppDriverException):
message = _("Volume %(volume_id)s is currently mapped to unsupported "
"host group %(group)s")
class WebServiceException(exception.NetAppDriverException):
def __init__(self, message=None, status_code=None):
self.status_code = status_code
super(WebServiceException, self).__init__(message=message)

View File

@ -1,132 +0,0 @@
# 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 cinder import interface
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
@interface.volumedriver
class NetAppEseriesFibreChannelDriver(driver.BaseVD,
driver.ManageableVD):
"""NetApp E-Series FibreChannel volume driver."""
DRIVER_NAME = 'NetApp_FibreChannel_ESeries'
# ThirdPartySystems wiki page
CI_WIKI_NAME = "NetApp_Eseries_CI"
VERSION = library.NetAppESeriesLibrary.VERSION
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):
return 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, connector):
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)
def initialize_connection(self, volume, connector, **kwargs):
conn_info = self.library.initialize_connection_fc(volume, connector)
fczm_utils.add_fc_zone(conn_info)
return conn_info
def terminate_connection(self, volume, connector, **kwargs):
conn_info = self.library.terminate_connection_fc(volume, connector,
**kwargs)
fczm_utils.remove_fc_zone(conn_info)
return conn_info
def get_pool(self, volume):
return self.library.get_pool(volume)
def create_cgsnapshot(self, context, cgsnapshot, snapshots):
return self.library.create_cgsnapshot(cgsnapshot, snapshots)
def delete_cgsnapshot(self, context, cgsnapshot, snapshots):
return self.library.delete_cgsnapshot(cgsnapshot, snapshots)
def create_consistencygroup(self, context, group):
return self.library.create_consistencygroup(group)
def delete_consistencygroup(self, context, group, volumes):
return self.library.delete_consistencygroup(group, volumes)
def update_consistencygroup(self, context, group,
add_volumes=None, remove_volumes=None):
return self.library.update_consistencygroup(
group, add_volumes, remove_volumes)
def create_consistencygroup_from_src(self, context, group, volumes,
cgsnapshot=None, snapshots=None,
source_cg=None, source_vols=None):
return self.library.create_consistencygroup_from_src(
group, volumes, cgsnapshot, snapshots, source_cg, source_vols)
def create_group(self, context, group):
return self.library.create_consistencygroup(group)
def delete_group(self, context, group, volumes):
return self.library.delete_consistencygroup(group, volumes)

View File

@ -1,250 +0,0 @@
# 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
# 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.
""" This module handles mapping E-Series volumes to E-Series Hosts and Host
Groups.
"""
import collections
import random
from oslo_log import log as logging
from six.moves import range
from cinder import exception
from cinder.i18n import _
from cinder.objects import fields
from cinder import utils as cinder_utils
from cinder.volume.drivers.netapp.eseries import exception as eseries_exc
from cinder.volume.drivers.netapp.eseries import utils
LOG = logging.getLogger(__name__)
@cinder_utils.trace_method
@cinder_utils.synchronized('map_es_volume')
def map_volume_to_single_host(client, volume, eseries_vol, host,
vol_map, multiattach_enabled):
"""Maps the e-series volume to host with initiator."""
LOG.debug("Attempting to map volume %s to single host.", volume['id'])
# If volume is not mapped on the backend, map directly to host
if not vol_map:
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)
# If volume is already mapped to desired host
if vol_map.get('mapRef') == host['hostRef']:
return vol_map
multiattach_cluster_ref = None
try:
host_group = client.get_host_group_by_name(
utils.MULTI_ATTACH_HOST_GROUP_NAME)
multiattach_cluster_ref = host_group['clusterRef']
except exception.NotFound:
pass
# Volume is mapped to the multiattach host group
if vol_map.get('mapRef') == multiattach_cluster_ref:
LOG.debug("Volume %s is mapped to multiattach host group.",
volume['id'])
# If volume is not currently attached according to Cinder, it is
# safe to delete the mapping
if not (volume['attach_status'] == fields.VolumeAttachStatus.ATTACHED):
LOG.debug("Volume %(vol)s is not currently attached, moving "
"existing mapping to host %(host)s.",
{'vol': volume['id'], 'host': host['label']})
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
)
# If we got this far, volume must be mapped to something else
msg = _("Cannot attach already attached volume %s; "
"multiattach is disabled via the "
"'netapp_enable_multiattach' configuration option.")
raise exception.NetAppDriverException(msg % volume['id'])
@cinder_utils.trace_method
@cinder_utils.synchronized('map_es_volume')
def map_volume_to_multiple_hosts(client, volume, eseries_vol, target_host,
mapping):
"""Maps the e-series volume to multiattach host group."""
LOG.debug("Attempting to map volume %s to multiple hosts.", volume['id'])
# If volume is already mapped to desired host, return the mapping
if mapping['mapRef'] == target_host['hostRef']:
LOG.debug("Volume %(vol)s already mapped to host %(host)s",
{'vol': volume['id'], 'host': target_host['label']})
return mapping
# If target host in a host group, ensure it is the multiattach host group
if target_host['clusterRef'] != utils.NULL_REF:
host_group = client.get_host_group(target_host[
'clusterRef'])
if host_group['label'] != utils.MULTI_ATTACH_HOST_GROUP_NAME:
msg = _("Specified host to map to volume %(vol)s is in "
"unsupported host group with %(group)s.")
params = {'vol': volume['id'], 'group': host_group['label']}
raise eseries_exc.UnsupportedHostGroup(msg % params)
mapped_host_group = None
multiattach_host_group = None
try:
mapped_host_group = client.get_host_group(mapping['mapRef'])
# If volume is mapped to a foreign host group raise an error
if mapped_host_group['label'] != utils.MULTI_ATTACH_HOST_GROUP_NAME:
raise eseries_exc.UnsupportedHostGroup(
volume_id=volume['id'], group=mapped_host_group['label'])
multiattach_host_group = mapped_host_group
except exception.NotFound:
pass
if not multiattach_host_group:
multiattach_host_group = client.get_host_group_by_name(
utils.MULTI_ATTACH_HOST_GROUP_NAME)
# If volume is mapped directly to a host, move the host into the
# multiattach host group. Error if the host is in a foreign host group
if not mapped_host_group:
current_host = client.get_host(mapping['mapRef'])
if current_host['clusterRef'] != utils.NULL_REF:
host_group = client.get_host_group(current_host[
'clusterRef'])
if host_group['label'] != utils.MULTI_ATTACH_HOST_GROUP_NAME:
msg = _("Currently mapped host for volume %(vol)s is in "
"unsupported host group with %(group)s.")
params = {'vol': volume['id'], 'group': host_group['label']}
raise eseries_exc.UnsupportedHostGroup(msg % params)
client.set_host_group_for_host(current_host['hostRef'],
multiattach_host_group['clusterRef'])
# Move destination host into multiattach host group
client.set_host_group_for_host(target_host[
'hostRef'], multiattach_host_group['clusterRef'])
# Once both existing and target hosts are in the multiattach host group,
# move the volume mapping to said group.
if not mapped_host_group:
LOG.debug("Moving mapping for volume %s to multiattach host group.",
volume['id'])
return client.move_volume_mapping_via_symbol(
mapping.get('lunMappingRef'),
multiattach_host_group['clusterRef'],
mapping['lun']
)
return mapping
def _get_free_lun(client, host, multiattach_enabled, mappings):
"""Returns least used LUN ID available on the given host."""
if not _is_host_full(client, host):
unused_luns = _get_unused_lun_ids(mappings)
if unused_luns:
chosen_lun = random.sample(unused_luns, 1)
return chosen_lun[0]
elif multiattach_enabled:
msg = _("No unused LUN IDs are available on the host; "
"multiattach is enabled which requires that all LUN IDs "
"to be unique across the entire host group.")
raise exception.NetAppDriverException(msg)
used_lun_counts = _get_used_lun_id_counter(mappings)
# most_common returns an arbitrary tuple of members with same frequency
for lun_id, __ in reversed(used_lun_counts.most_common()):
if _is_lun_id_available_on_host(client, host, lun_id):
return lun_id
msg = _("No free LUN IDs left. Maximum number of volumes that can be "
"attached to host (%s) has been exceeded.")
raise exception.NetAppDriverException(msg % utils.MAX_LUNS_PER_HOST)
def _get_unused_lun_ids(mappings):
"""Returns unused LUN IDs given mappings."""
used_luns = _get_used_lun_ids_for_mappings(mappings)
unused_luns = (set(range(utils.MAX_LUNS_PER_HOST)) - set(used_luns))
return unused_luns
def _get_used_lun_id_counter(mapping):
"""Returns used LUN IDs with count as a dictionary."""
used_luns = _get_used_lun_ids_for_mappings(mapping)
used_lun_id_counter = collections.Counter(used_luns)
return used_lun_id_counter
def _is_host_full(client, host):
"""Checks whether maximum volumes attached to a host have been reached."""
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 = 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
def _get_used_lun_ids_for_mappings(mappings):
"""Returns used LUNs when provided with mappings."""
used_luns = set(map(lambda lun: int(lun['lun']), mappings))
# E-Series uses LUN ID 0 for special purposes and should not be
# assigned for general use
used_luns.add(0)
return used_luns
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 %(vol)s is mapped directly to host %(host)s; "
"removing mapping.", {'vol': volume['id'],
'host': host['label']})
client.delete_volume_mapping(mapping['lunMappingRef'])
return
try:
host_group = client.get_host_group(mapping['mapRef'])
except exception.NotFound:
# Volumes is mapped but to a different initiator
raise eseries_exc.VolumeNotMapped(volume_id=volume['id'],
host=host['label'])
# If volume is mapped to a foreign host group raise error
if host_group['label'] != utils.MULTI_ATTACH_HOST_GROUP_NAME:
raise eseries_exc.UnsupportedHostGroup(volume_id=volume['id'],
group=host_group['label'])
# If target host is not in the multiattach host group
if host['clusterRef'] != host_group['clusterRef']:
raise eseries_exc.VolumeNotMapped(volume_id=volume['id'],
host=host['label'])
# Volume is mapped to multiattach host group
# Remove mapping if volume should no longer be attached after this
# operation.
if volume['status'] == 'detaching':
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'])

View File

@ -1,129 +0,0 @@
# 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.
#
# 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 iSCSI storage systems.
"""
from cinder import interface
from cinder.volume import driver
from cinder.volume.drivers.netapp.eseries import library
from cinder.volume.drivers.netapp import utils as na_utils
@interface.volumedriver
class NetAppEseriesISCSIDriver(driver.BaseVD,
driver.ManageableVD):
"""NetApp E-Series iSCSI volume driver."""
DRIVER_NAME = 'NetApp_iSCSI_ESeries'
# ThirdPartySystems wiki page
CI_WIKI_NAME = "NetApp_Eseries_CI"
VERSION = library.NetAppESeriesLibrary.VERSION
def __init__(self, *args, **kwargs):
super(NetAppEseriesISCSIDriver, self).__init__(*args, **kwargs)
na_utils.validate_instantiation(**kwargs)
self.library = library.NetAppESeriesLibrary(self.DRIVER_NAME,
'iSCSI', **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):
return 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, connector):
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)
def initialize_connection(self, volume, connector):
return self.library.initialize_connection_iscsi(volume, connector)
def terminate_connection(self, volume, connector, **kwargs):
return self.library.terminate_connection_iscsi(volume, connector,
**kwargs)
def get_pool(self, volume):
return self.library.get_pool(volume)
def create_cgsnapshot(self, context, cgsnapshot, snapshots):
return self.library.create_cgsnapshot(cgsnapshot, snapshots)
def delete_cgsnapshot(self, context, cgsnapshot, snapshots):
return self.library.delete_cgsnapshot(cgsnapshot, snapshots)
def create_consistencygroup(self, context, group):
return self.library.create_consistencygroup(group)
def delete_consistencygroup(self, context, group, volumes):
return self.library.delete_consistencygroup(group, volumes)
def update_consistencygroup(self, context, group,
add_volumes=None, remove_volumes=None):
return self.library.update_consistencygroup(
group, add_volumes, remove_volumes)
def create_consistencygroup_from_src(self, context, group, volumes,
cgsnapshot=None, snapshots=None,
source_cg=None, source_vols=None):
return self.library.create_consistencygroup_from_src(
group, volumes, cgsnapshot, snapshots, source_cg, source_vols)
def create_group(self, context, group):
return self.create_consistencygroup(context, group)
def delete_group(self, context, group, volumes):
return self.library.delete_consistencygroup(group, volumes)

File diff suppressed because it is too large Load Diff

View File

@ -1,63 +0,0 @@
# Copyright (c) 2014 Navneet Singh. All rights reserved.
# Copyright (c) 2014 Clinton Knight. 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.
"""
Utilities for NetApp E-series drivers.
"""
import base64
import binascii
import uuid
import six
MULTI_ATTACH_HOST_GROUP_NAME = 'cinder-multi-attach'
NULL_REF = '0000000000000000000000000000000000000000'
MAX_LUNS_PER_HOST = 256
MAX_LUNS_PER_HOST_GROUP = 256
def encode_hex_to_base32(hex_string):
"""Encodes hex to base32 bit as per RFC4648."""
bin_form = binascii.unhexlify(hex_string)
return base64.b32encode(bin_form)
def decode_base32_to_hex(base32_string):
"""Decodes base32 string to hex string."""
bin_form = base64.b32decode(base32_string)
return binascii.hexlify(bin_form)
def convert_uuid_to_es_fmt(uuid_str):
"""Converts uuid to e-series compatible name format."""
uuid_base32 = encode_hex_to_base32(uuid.UUID(six.text_type(uuid_str)).hex)
es_label = uuid_base32.strip(b'=')
if six.PY3:
es_label = es_label.decode('ascii')
return es_label
def convert_es_fmt_to_uuid(es_label):
"""Converts e-series name format to uuid."""
if isinstance(es_label, six.text_type):
es_label = es_label.encode('utf-8')
if es_label.startswith(b'tmp-'):
es_label = es_label[4:]
es_label = es_label.ljust(32, b'=')
es_label = binascii.hexlify(base64.b32decode(es_label))
if six.PY3:
es_label = es_label.decode('ascii')
return uuid.UUID(es_label)

View File

@ -34,10 +34,10 @@ NETAPP_SIZE_MULTIPLIER_DEFAULT = 1.2
netapp_proxy_opts = [
cfg.StrOpt('netapp_storage_family',
default='ontap_cluster',
choices=['ontap_cluster', 'eseries'],
choices=['ontap_cluster'],
help=('The storage family type used on the storage system; '
'valid values are ontap_cluster for using clustered '
'Data ONTAP, or eseries for using E-Series.')),
'the only valid value is ontap_cluster for using '
'clustered Data ONTAP.')),
cfg.StrOpt('netapp_storage_protocol',
choices=['iscsi', 'fc', 'nfs'],
help=('The storage protocol to be used on the data path with '
@ -50,8 +50,7 @@ netapp_connection_opts = [
cfg.IntOpt('netapp_server_port',
help=('The TCP port to use for communication with the storage '
'system or proxy server. If not specified, Data ONTAP '
'drivers will use 80 for HTTP and 443 for HTTPS; '
'E-Series will use 8080 for HTTP and 8443 for HTTPS.')), ]
'drivers will use 80 for HTTP and 443 for HTTPS.')), ]
netapp_transport_opts = [
cfg.StrOpt('netapp_transport_type',
@ -115,35 +114,6 @@ netapp_img_cache_opts = [
'the value of this parameter, will be deleted from the '
'cache to create free space on the NFS share.')), ]
netapp_eseries_opts = [
cfg.StrOpt('netapp_webservice_path',
default='/devmgr/v2',
help=('This option is used to specify the path to the E-Series '
'proxy application on a proxy server. The value is '
'combined with the value of the netapp_transport_type, '
'netapp_server_hostname, and netapp_server_port options '
'to create the URL used by the driver to connect to the '
'proxy application.')),
cfg.StrOpt('netapp_controller_ips',
help=('This option is only utilized when the storage family '
'is configured to eseries. This option is used to '
'restrict provisioning to the specified controllers. '
'Specify the value of this option to be a comma '
'separated list of controller hostnames or IP addresses '
'to be used for provisioning.')),
cfg.StrOpt('netapp_sa_password',
help=('Password for the NetApp E-Series storage array.'),
secret=True),
cfg.BoolOpt('netapp_enable_multiattach',
default=False,
help='This option specifies whether the driver should allow '
'operations that require multiple attachments to a '
'volume. An example would be live migration of servers '
'that have volumes attached. When enabled, this backend '
'is limited to 256 total volumes in order to '
'guarantee volumes can be accessed by more than one '
'host.'),
]
netapp_nfs_extra_opts = [
cfg.StrOpt('netapp_copyoffload_tool_path',
help=('This option specifies the path of the NetApp copy '
@ -214,7 +184,6 @@ CONF.register_opts(netapp_basicauth_opts, group=conf.SHARED_CONF_GROUP)
CONF.register_opts(netapp_cluster_opts, group=conf.SHARED_CONF_GROUP)
CONF.register_opts(netapp_provisioning_opts, group=conf.SHARED_CONF_GROUP)
CONF.register_opts(netapp_img_cache_opts, group=conf.SHARED_CONF_GROUP)
CONF.register_opts(netapp_eseries_opts, group=conf.SHARED_CONF_GROUP)
CONF.register_opts(netapp_nfs_extra_opts, group=conf.SHARED_CONF_GROUP)
CONF.register_opts(netapp_san_opts, group=conf.SHARED_CONF_GROUP)
CONF.register_opts(netapp_replication_opts, group=conf.SHARED_CONF_GROUP)

View File

@ -22,7 +22,7 @@ consistency groups and the release when the support was added:
- Liberty: Dell Storage Center, EMC XtremIO, HPE 3Par and LeftHand
- Mitaka: EMC ScaleIO, NetApp Data ONTAP and E-Series, SolidFire
- Mitaka: EMC ScaleIO, NetApp Data ONTAP, SolidFire
- Newton: CoprHD, FalconStor, Huawei

View File

@ -3,8 +3,8 @@ NetApp unified driver
=====================
The NetApp unified driver is a Block Storage driver that supports
multiple storage families and protocols. A storage family corresponds to
storage systems built on either clustered Data ONTAP or E-Series. The
multiple storage families and protocols. Currently, the only storage
family supported by this driver is the clustered Data ONTAP. The
storage protocol refers to the protocol used to initiate data storage and
access operations on those storage systems like iSCSI and NFS. The NetApp
unified driver can be configured to provision and manage OpenStack volumes
@ -27,8 +27,8 @@ that can support new storage families and protocols.
In releases prior to Juno, the NetApp unified driver contained some
scheduling logic that determined which NetApp storage container
(namely, a FlexVol volume for Data ONTAP, or a dynamic disk pool for
E-Series) that a new Block Storage volume would be placed into.
(namely, a FlexVol volume for Data ONTAP) that a new Block Storage
volume would be placed into.
With the introduction of pools, all scheduling logic is performed
completely within the Block Storage scheduler, as each
@ -248,106 +248,3 @@ Define Block Storage volume types by using the :command:`openstack volume
type set` command.
.. include:: ../../tables/manual/cinder-netapp_cdot_extraspecs.inc
NetApp E-Series storage family
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The NetApp E-Series storage family represents a configuration group which
provides OpenStack compute instances access to E-Series storage systems. At
present it can be configured in Block Storage to work with the iSCSI
storage protocol.
NetApp iSCSI configuration for E-Series
---------------------------------------
The NetApp iSCSI configuration for E-Series is an interface from OpenStack to
E-Series storage systems. It provisions and manages the SAN block storage
entity, which is a NetApp LUN which can be accessed using the iSCSI protocol.
The iSCSI configuration for E-Series is an interface from Block
Storage to the E-Series proxy instance and as such requires the deployment of
the proxy instance in order to achieve the desired functionality. The driver
uses REST APIs to interact with the E-Series proxy instance, which in turn
interacts directly with the E-Series controllers.
The use of multipath and DM-MP are required when using the Block
Storage driver for E-Series. In order for Block Storage and OpenStack
Compute to take advantage of multiple paths, the following configuration
options must be correctly configured:
- The ``use_multipath_for_image_xfer`` option should be set to ``True`` in the
``cinder.conf`` file within the driver-specific stanza (for example,
``[myDriver]``).
- The ``volume_use_multipath`` option should be set to ``True`` in the
``nova.conf`` file within the ``[libvirt]`` stanza.
In versions prior to Newton, the option was called ``iscsi_use_multipath``.
**Configuration options**
Configure the volume driver, storage family, and storage protocol to the
NetApp unified driver, E-Series, and iSCSI respectively by setting the
``volume_driver``, ``netapp_storage_family`` and
``netapp_storage_protocol`` options in the ``cinder.conf`` file as follows:
.. code-block:: ini
volume_driver = cinder.volume.drivers.netapp.common.NetAppDriver
netapp_storage_family = eseries
netapp_storage_protocol = iscsi
netapp_server_hostname = myhostname
netapp_server_port = 80
netapp_login = username
netapp_password = password
netapp_controller_ips = 1.2.3.4,5.6.7.8
netapp_sa_password = arrayPassword
netapp_storage_pools = pool1,pool2
use_multipath_for_image_xfer = True
.. note::
To use the E-Series driver, you must override the default value of
``netapp_storage_family`` with ``eseries``.
To use the iSCSI protocol, you must override the default value of
``netapp_storage_protocol`` with ``iscsi``.
.. include:: ../../tables/cinder-netapp_eseries_iscsi.inc
.. tip::
For more information on these options and other deployment and
operational scenarios, visit the `NetApp OpenStack website
<http://netapp.io/openstack/>`_.
NetApp-supported extra specs for E-Series
-----------------------------------------
Extra specs enable vendors to specify extra filter criteria.
The Block Storage scheduler uses the specs when the scheduler determines
which volume node should fulfill a volume provisioning request.
When you use the NetApp unified driver with an E-Series storage system,
you can leverage extra specs with Block Storage volume types to ensure
that Block Storage volumes are created on storage back ends that have
certain properties. An example of this is when you configure thin
provisioning for a storage back end.
Extra specs are associated with Block Storage volume types.
When users request volumes of a particular volume type, the volumes are
created on storage back ends that meet the list of requirements.
An example of this is the back ends that have the available space or
extra specs. Use the specs in the following table to configure volumes.
Define Block Storage volume types by using the :command:`openstack volume
type set` command.
.. list-table:: Description of extra specs options for NetApp Unified Driver with E-Series
:header-rows: 1
* - Extra spec
- Type
- Description
* - ``netapp_thin_provisioned``
- Boolean
- Limit the candidate volume list to only the ones that support thin
provisioning on the storage controller.

View File

@ -33,13 +33,13 @@
* - ``netapp_server_hostname`` = ``None``
- (String) The hostname (or IP address) for the storage system or proxy server.
* - ``netapp_server_port`` = ``None``
- (Integer) The TCP port to use for communication with the storage system or proxy server. If not specified, Data ONTAP drivers will use 80 for HTTP and 443 for HTTPS; E-Series will use 8080 for HTTP and 8443 for HTTPS.
- (Integer) The TCP port to use for communication with the storage system or proxy server. If not specified, Data ONTAP drivers will use 80 for HTTP and 443 for HTTPS.
* - ``netapp_size_multiplier`` = ``1.2``
- (Floating point) The quantity to be multiplied by the requested volume size to ensure enough space is available on the virtual storage server (Vserver) to fulfill the volume creation request. Note: this option is deprecated and will be removed in favor of "reserved_percentage" in the Mitaka release.
* - ``netapp_snapmirror_quiesce_timeout`` = ``3600``
- (Integer) The maximum time in seconds to wait for existing SnapMirror transfers to complete before aborting during a failover.
* - ``netapp_storage_family`` = ``ontap_cluster``
- (String) The storage family type used on the storage system; valid values are ontap_cluster for using clustered Data ONTAP, or eseries for using E-Series.
- (String) The storage family type used on the storage system; the only valid value is ontap_cluster for using clustered Data ONTAP.
* - ``netapp_storage_protocol`` = ``None``
- (String) The storage protocol to be used on the data path with the storage system.
* - ``netapp_transport_type`` = ``http``

View File

@ -39,11 +39,11 @@
* - ``netapp_server_hostname`` = ``None``
- (String) The hostname (or IP address) for the storage system or proxy server.
* - ``netapp_server_port`` = ``None``
- (Integer) The TCP port to use for communication with the storage system or proxy server. If not specified, Data ONTAP drivers will use 80 for HTTP and 443 for HTTPS; E-Series will use 8080 for HTTP and 8443 for HTTPS.
- (Integer) The TCP port to use for communication with the storage system or proxy server. If not specified, Data ONTAP drivers will use 80 for HTTP and 443 for HTTPS.
* - ``netapp_snapmirror_quiesce_timeout`` = ``3600``
- (Integer) The maximum time in seconds to wait for existing SnapMirror transfers to complete before aborting during a failover.
* - ``netapp_storage_family`` = ``ontap_cluster``
- (String) The storage family type used on the storage system; valid values are ontap_cluster for using clustered Data ONTAP, or eseries for using E-Series.
- (String) The storage family type used on the storage system; the only valid value is ontap_cluster for using clustered Data ONTAP.
* - ``netapp_storage_protocol`` = ``None``
- (String) The storage protocol to be used on the data path with the storage system.
* - ``netapp_transport_type`` = ``http``

View File

@ -1,48 +0,0 @@
..
Warning: Do not edit this file. It is automatically generated from the
software project's code and your changes will be overwritten.
The tool to generate this file lives in openstack-doc-tools repository.
Please make any changes needed in the code, then run the
autogenerate-config-doc tool from the openstack-doc-tools repository, or
ask for help on the documentation mailing list, IRC channel or meeting.
.. _cinder-netapp_eseries_iscsi:
.. list-table:: Description of NetApp E-Series driver configuration options
:header-rows: 1
:class: config-ref-table
* - Configuration option = Default value
- Description
* - **[DEFAULT]**
-
* - ``netapp_controller_ips`` = ``None``
- (String) This option is only utilized when the storage family is configured to eseries. This option is used to restrict provisioning to the specified controllers. Specify the value of this option to be a comma separated list of controller hostnames or IP addresses to be used for provisioning.
* - ``netapp_enable_multiattach`` = ``False``
- (Boolean) This option specifies whether the driver should allow operations that require multiple attachments to a volume. An example would be live migration of servers that have volumes attached. When enabled, this backend is limited to 256 total volumes in order to guarantee volumes can be accessed by more than one host.
* - ``netapp_host_type`` = ``None``
- (String) This option defines the type of operating system for all initiators that can access a LUN. This information is used when mapping LUNs to individual hosts or groups of hosts.
* - ``netapp_login`` = ``None``
- (String) Administrative user account name used to access the storage system or proxy server.
* - ``netapp_password`` = ``None``
- (String) Password for the administrative user account specified in the netapp_login option.
* - ``netapp_pool_name_search_pattern`` = ``(.+)``
- (String) This option is used to restrict provisioning to the specified pools. Specify the value of this option to be a regular expression which will be applied to the names of objects from the storage backend which represent pools in Cinder. This option is only utilized when the storage protocol is configured to use iSCSI or FC.
* - ``netapp_replication_aggregate_map`` = ``None``
- (Unknown) Multi opt of dictionaries to represent the aggregate mapping between source and destination back ends when using whole back end replication. For every source aggregate associated with a cinder pool (NetApp FlexVol), you would need to specify the destination aggregate on the replication target device. A replication target device is configured with the configuration option replication_device. Specify this option as many times as you have replication devices. Each entry takes the standard dict config form: netapp_replication_aggregate_map = backend_id:<name_of_replication_device_section>,src_aggr_name1:dest_aggr_name1,src_aggr_name2:dest_aggr_name2,...
* - ``netapp_sa_password`` = ``None``
- (String) Password for the NetApp E-Series storage array.
* - ``netapp_server_hostname`` = ``None``
- (String) The hostname (or IP address) for the storage system or proxy server.
* - ``netapp_server_port`` = ``None``
- (Integer) The TCP port to use for communication with the storage system or proxy server. If not specified, Data ONTAP drivers will use 80 for HTTP and 443 for HTTPS; E-Series will use 8080 for HTTP and 8443 for HTTPS.
* - ``netapp_snapmirror_quiesce_timeout`` = ``3600``
- (Integer) The maximum time in seconds to wait for existing SnapMirror transfers to complete before aborting during a failover.
* - ``netapp_storage_family`` = ``ontap_cluster``
- (String) The storage family type used on the storage system; valid values are ontap_cluster for using clustered Data ONTAP, or eseries for using E-Series.
* - ``netapp_transport_type`` = ``http``
- (String) The transport protocol used when communicating with the storage system or proxy server.
* - ``netapp_webservice_path`` = ``/devmgr/v2``
- (String) This option is used to specify the path to the E-Series proxy application on a proxy server. The value is combined with the value of the netapp_transport_type, netapp_server_hostname, and netapp_server_port options to create the URL used by the driver to connect to the proxy application.

View File

@ -33,7 +33,7 @@ Drivers currently supporting consistency groups are in the following:
- Liberty: Dell Storage Center, EMC XtremIO, HPE 3Par and LeftHand
- Mitaka: EMC ScaleIO, NetApp Data ONTAP and E-Series, SolidFire
- Mitaka: EMC ScaleIO, NetApp Data ONTAP, SolidFire
- Newton: CoprHD, FalconStor, Huawei

View File

@ -120,9 +120,6 @@ title=NEC Storage M Series Driver (iSCSI, FC)
[driver.netapp_ontap]
title=NetApp Data ONTAP Driver (iSCSI, NFS, FC)
[driver.netapp_e_ef]
title=NetApp E/EF Series Driver (iSCSI, FC)
[driver.netapp_solidfire]
title=NetApp Solidfire Driver (iSCSI)
@ -234,7 +231,6 @@ driver.linbit_drbd=complete
driver.lvm=complete
driver.nec=complete
driver.netapp_ontap=complete
driver.netapp_e_ef=complete
driver.netapp_solidfire=complete
driver.nexenta=complete
driver.nfs=complete
@ -298,7 +294,6 @@ driver.linbit_drbd=complete
driver.lvm=complete
driver.nec=complete
driver.netapp_ontap=missing
driver.netapp_e_ef=complete
driver.netapp_solidfire=complete
driver.nexenta=complete
driver.nfs=complete
@ -362,7 +357,6 @@ driver.linbit_drbd=missing
driver.lvm=missing
driver.nec=complete
driver.netapp_ontap=missing
driver.netapp_e_ef=missing
driver.netapp_solidfire=missing
driver.nexenta=missing
driver.nfs=missing
@ -427,7 +421,6 @@ driver.linbit_drbd=missing
driver.lvm=missing
driver.nec=complete
driver.netapp_ontap=complete
driver.netapp_e_ef=missing
driver.netapp_solidfire=complete
driver.nexenta=missing
driver.nfs=missing
@ -493,7 +486,6 @@ driver.linbit_drbd=missing
driver.lvm=missing
driver.nec=missing
driver.netapp_ontap=complete
driver.netapp_e_ef=missing
driver.netapp_solidfire=complete
driver.nexenta=missing
driver.nfs=missing
@ -560,7 +552,6 @@ driver.linbit_drbd=missing
driver.lvm=missing
driver.nec=missing
driver.netapp_ontap=complete
driver.netapp_e_ef=complete
driver.netapp_solidfire=complete
driver.nexenta=missing
driver.nfs=missing
@ -626,7 +617,6 @@ driver.linbit_drbd=missing
driver.lvm=complete
driver.nec=missing
driver.netapp_ontap=complete
driver.netapp_e_ef=complete
driver.netapp_solidfire=complete
driver.nexenta=missing
driver.nfs=complete
@ -693,7 +683,6 @@ driver.linbit_drbd=missing
driver.lvm=missing
driver.nec=missing
driver.netapp_ontap=missing
driver.netapp_e_ef=missing
driver.netapp_solidfire=missing
driver.nexenta=missing
driver.nfs=missing
@ -760,7 +749,6 @@ driver.linbit_drbd=missing
driver.lvm=complete
driver.nec=missing
driver.netapp_ontap=complete
driver.netapp_e_ef=missing
driver.netapp_solidfire=complete
driver.nexenta=missing
driver.nfs=missing

View File

@ -0,0 +1,5 @@
---
upgrade:
- Support for NetApp E-Series has been removed. The
NetApp Unified driver can now only be used with
NetApp Clustered Data ONTAP.