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:
parent
38e91b1030
commit
5bc5af7a94
@ -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
@ -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)
|
@ -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)
|
@ -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))
|
@ -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
@ -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)
|
@ -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
@ -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)
|
@ -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)
|
@ -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'])
|
@ -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
@ -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)
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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``
|
||||
|
@ -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``
|
||||
|
@ -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.
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
5
releasenotes/notes/remove_eseries-bb1bc134645aee50.yaml
Normal file
5
releasenotes/notes/remove_eseries-bb1bc134645aee50.yaml
Normal 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.
|
Loading…
Reference in New Issue
Block a user