diff --git a/cinder/tests/unit/volume/drivers/test_nimble.py b/cinder/tests/unit/volume/drivers/test_nimble.py index 20076c5a90e..68a6b8cec37 100644 --- a/cinder/tests/unit/volume/drivers/test_nimble.py +++ b/cinder/tests/unit/volume/drivers/test_nimble.py @@ -17,12 +17,14 @@ import sys from unittest import mock +from oslo_utils import uuidutils from six.moves import http_client from cinder import context from cinder import exception from cinder.objects import volume as obj_volume from cinder.tests.unit import fake_constants as fake +from cinder.tests.unit import fake_volume from cinder.tests.unit import test from cinder.volume.drivers import nimble from cinder.volume import volume_types @@ -83,6 +85,9 @@ FAKE_CREATE_VOLUME_POSITIVE_RESPONSE_QOS = { 'clone': False, 'name': "testvolume-qos"} +FAKE_EXTRA_SPECS = {'multiattach': ' True', + 'nimble:iops-limit': '1024'} + FAKE_GET_VOL_INFO_RESPONSE = {'name': 'testvolume', 'clone': False, 'target_name': 'iqn.test', @@ -99,6 +104,13 @@ FAKE_GET_VOL_INFO_ONLINE = {'name': 'testvolume', 'online': True, 'agent_type': 'none'} +FAKE_GET_VOL_INFO_RETYPE = {'name': 'testvolume', + 'size': 2048, + 'online': True, + 'agent_type': 'none', + 'pool_id': 'none', + 'pool_name': 'none'} + FAKE_GET_VOL_INFO_BACKUP_RESPONSE = {'name': 'testvolume', 'clone': True, 'target_name': 'iqn.test', @@ -250,6 +262,26 @@ class NimbleDriverBaseTestCase(test.TestCase): return inner_client_mock return client_mock_wrapper + @staticmethod + def client_mock_decorator_nimble_api(username, password, ip, verify): + def client_mock_wrapper(func): + def inner_client_mock( + self, mock_client_class, mock_urllib2, *args, **kwargs): + self.mock_client_class = mock_client_class + self.mock_client_service = mock.MagicMock(name='Client') + self.mock_client_class.return_value = ( + self.mock_client_service) + self.driver = nimble.NimbleRestAPIExecutor( + username=username, password=password, ip=ip, verify=verify) + mock_login_response = mock_urllib2.post.return_value + mock_login_response = mock.MagicMock() + mock_login_response.status_code.return_value = http_client.OK + mock_login_response.json.return_value = ( + FAKE_LOGIN_POST_RESPONSE) + func(self, *args, **kwargs) + return inner_client_mock + return client_mock_wrapper + class NimbleDriverLoginTestCase(NimbleDriverBaseTestCase): @@ -371,7 +403,7 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase): mock.Mock(type_id=FAKE_TYPE_ID, return_value={ 'nimble:perfpol-name': 'default', 'nimble:encryption': 'yes', - 'nimble:multi-initiator': 'false'})) + 'multiattach': 'false'})) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) def test_create_volume_encryption_positive(self): @@ -412,7 +444,7 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase): mock.Mock(type_id=FAKE_TYPE_ID, return_value={ 'nimble:perfpol-name': 'VMware ESX', 'nimble:encryption': 'no', - 'nimble:multi-initiator': 'false'})) + 'multiattach': 'false'})) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) def test_create_volume_perfpolicy_positive(self): @@ -452,7 +484,7 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase): mock.Mock(type_id=FAKE_TYPE_ID, return_value={ 'nimble:perfpol-name': 'default', 'nimble:encryption': 'no', - 'nimble:multi-initiator': 'true'})) + 'multiattach': 'true'})) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) def test_create_volume_multi_initiator_positive(self): @@ -573,7 +605,7 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase): mock.Mock(type_id=FAKE_TYPE_ID, return_value={ 'nimble:perfpol-name': 'default', 'nimble:encryption': 'no', - 'nimble:multi-initiator': 'true'})) + 'multiattach': 'false'})) def test_create_volume_negative(self): self.mock_client_service.get_vol_info.side_effect = ( FAKE_CREATE_VOLUME_NEGATIVE_RESPONSE) @@ -761,7 +793,7 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase): return_value={ 'nimble:perfpol-name': 'default', 'nimble:encryption': 'yes', - 'nimble:multi-initiator': 'false', + 'multiattach': False, 'nimble:iops-limit': '1024'})) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*', False)) @@ -979,7 +1011,7 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase): 'extra_specs': {'nimble:perfpol-name': 'default', 'nimble:encryption': 'yes', - 'nimble:multi-initiator': 'false', + 'multiattach': False, 'nimble:iops-limit': '1024'}})) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) @@ -994,6 +1026,27 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase): self.assertTrue(retype) self.assertIsNone(update) + @mock.patch(NIMBLE_URLLIB2) + @mock.patch(NIMBLE_ISCSI_DRIVER) + @mock.patch.object(nimble.NimbleRestAPIExecutor, 'login') + @mock.patch.object(nimble.NimbleRestAPIExecutor, + 'get_performance_policy_id') + @mock.patch.object(nimble.NimbleRestAPIExecutor, 'get_pool_info') + @mock.patch.object(nimble.NimbleRestAPIExecutor, 'get_folder_id') + @NimbleDriverBaseTestCase.client_mock_decorator_nimble_api( + 'nimble', 'nimble_pass', '10.18.108.55', 'False') + def test_nimble_extraspecs_retype(self, mock_folder, + mock_pool, mock_perf_id, + mock_login): + mock_folder.return_value = None + mock_pool.return_value = None + mock_perf_id.return_value = None + mock_login.return_value = None + data = self.driver.get_valid_nimble_extraspecs( + FAKE_EXTRA_SPECS, + FAKE_GET_VOL_INFO_RETYPE) + self.assertTrue(data['multi_initiator']) + @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) @mock.patch.object(obj_volume.VolumeList, 'get_all_by_host', @@ -1011,7 +1064,8 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase): 'total_capacity_gb': 7466.30419921875, 'free_capacity_gb': 7463.567649364471, 'reserved_percentage': 0, - 'QoS_support': False}]} + 'QoS_support': False, + 'multiattach': True}]} self.assertEqual( expected_res, self.driver.get_volume_stats(refresh=True)) @@ -1095,7 +1149,7 @@ class NimbleDriverSnapshotTestCase(NimbleDriverBaseTestCase): mock.Mock(type_id=FAKE_TYPE_ID, return_value={ 'nimble:perfpol-name': 'default', 'nimble:encryption': 'yes', - 'nimble:multi-initiator': 'false'})) + 'multiattach': False})) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) def test_create_volume_from_snapshot(self): @@ -1361,10 +1415,15 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase): def test_terminate_connection_positive(self): self.mock_client_service.get_initiator_grp_list.return_value = ( FAKE_IGROUP_LIST_RESPONSE) + ctx = context.get_admin_context() + volume = fake_volume.fake_volume_obj( + ctx, name='test-volume', + host='fakehost@nimble#Openstack', + provider_location='12 13', + id=12, multiattach=False) + self.driver.terminate_connection( - {'name': 'test-volume', - 'provider_location': '12 13', - 'id': 12}, + volume, {'initiator': 'test-initiator1'}) expected_calls = [mock.call._get_igroupname_for_initiator( 'test-initiator1'), @@ -1406,10 +1465,15 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase): mock_wwpns.return_value = ["1111111111111101"] self.mock_client_service.get_initiator_grp_list.return_value = ( FAKE_IGROUP_LIST_RESPONSE_FC) + ctx = context.get_admin_context() + volume = fake_volume.fake_volume_obj( + ctx, name='test-volume', + host='fakehost@nimble#Openstack', + provider_location='12 13', + id=14, multiattach=False) + self.driver.terminate_connection( - {'name': 'test-volume', - 'provider_location': 'array1', - 'id': 12}, + volume, {'initiator': 'test-initiator1', 'wwpns': ['1000000000000000']}) expected_calls = [ @@ -1430,9 +1494,159 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase): def test_terminate_connection_negative(self): self.mock_client_service.get_initiator_grp_list.return_value = ( FAKE_IGROUP_LIST_RESPONSE) + ctx = context.get_admin_context() + + volume = fake_volume.fake_volume_obj( + ctx, name='test-volume', + host='fakehost@nimble#Openstack', + provider_location='12 13', + id=12, multiattach=False) + self.assertRaises( exception.VolumeDriverException, self.driver.terminate_connection, - {'name': 'test-volume', - 'provider_location': '12 13', 'id': 12}, + volume, {'initiator': 'test-initiator3'}) + + @mock.patch(NIMBLE_URLLIB2) + @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all_by_host', + mock.Mock(return_value=[])) + @NimbleDriverBaseTestCase.client_mock_decorator_fc(create_configuration( + 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) + @mock.patch(NIMBLE_FC_DRIVER + ".get_wwpns_from_array") + def test_terminate_connection_negative_fc(self, mock_wwpns): + mock_wwpns.return_value = ["1111111111111101"] + self.mock_client_service.get_initiator_grp_list.return_value = ( + FAKE_IGROUP_LIST_RESPONSE_FC) + ctx = context.get_admin_context() + volume = fake_volume.fake_volume_obj( + ctx, name='test-volume', + host='fakehost@nimble#Openstack', + provider_location='12 13', + id=12, multiattach=False) + self.assertRaises( + exception.VolumeDriverException, + self.driver.terminate_connection, + volume, + {'initiator': 'test-initiator3', + 'wwpns': ['1000000000000010']}) + + @mock.patch(NIMBLE_URLLIB2) + @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all_by_host', + mock.Mock(return_value=[])) + @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( + 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) + def test_terminate_connection_multiattach(self): + self.mock_client_service.get_initiator_grp_list.return_value = ( + FAKE_IGROUP_LIST_RESPONSE) + ctx = context.get_admin_context() + + att_1 = fake_volume.volume_attachment_ovo( + ctx, id=uuidutils.generate_uuid()) + att_2 = fake_volume.volume_attachment_ovo( + ctx, id=uuidutils.generate_uuid()) + volume = fake_volume.fake_volume_obj( + ctx, name='test-volume', + host='fakehost@nimble#Openstack', + provider_location='12 13', + id=12, multiattach=True) + volume.volume_attachment.objects = [att_1, att_2] + self.driver.terminate_connection( + volume, + {'initiator': 'test-initiator1'}) + self.mock_client_service.remove_acl.assert_not_called() + + @mock.patch(NIMBLE_URLLIB2) + @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all_by_host', + mock.Mock(return_value=[])) + @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( + 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) + def test_terminate_connection_multiattach_complete(self): + self.mock_client_service.get_initiator_grp_list.return_value = ( + FAKE_IGROUP_LIST_RESPONSE) + ctx = context.get_admin_context() + + att_1 = fake_volume.volume_attachment_ovo( + ctx, id=uuidutils.generate_uuid()) + volume = fake_volume.fake_volume_obj( + ctx, name='test-volume', + host='fakehost@nimble#Openstack', + provider_location='12 13', + id=12, multiattach=True) + volume.volume_attachment.objects = [att_1] + self.driver.terminate_connection( + volume, + {'initiator': 'test-initiator1'}) + expected_calls = [mock.call._get_igroupname_for_initiator( + 'test-initiator1'), + mock.call.remove_acl({'name': 'test-volume'}, + 'test-igrp1')] + self.mock_client_service.assert_has_calls( + self.mock_client_service.method_calls, + expected_calls) + + @mock.patch(NIMBLE_URLLIB2) + @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all_by_host', + mock.Mock(return_value=[])) + @NimbleDriverBaseTestCase.client_mock_decorator_fc(create_configuration( + 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) + @mock.patch(NIMBLE_FC_DRIVER + ".get_wwpns_from_array") + def test_terminate_connection_multiattach_fc(self, mock_wwpns): + mock_wwpns.return_value = ["1111111111111101"] + self.mock_client_service.get_initiator_grp_list.return_value = ( + FAKE_IGROUP_LIST_RESPONSE_FC) + ctx = context.get_admin_context() + + att_1 = fake_volume.volume_attachment_ovo( + ctx, id=uuidutils.generate_uuid()) + att_2 = fake_volume.volume_attachment_ovo( + ctx, id=uuidutils.generate_uuid()) + volume = fake_volume.fake_volume_obj( + ctx, name='test-volume', + host='fakehost@nimble#Openstack', + provider_location='12 13', + id=12, multiattach=True) + volume.volume_attachment.objects = [att_1, att_2] + self.driver.terminate_connection( + volume, + {'initiator': 'test-initiator1', + 'wwpns': ['1000000000000000']}) + self.mock_client_service.remove_acl.assert_not_called() + + @mock.patch(NIMBLE_URLLIB2) + @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all_by_host', + mock.Mock(return_value=[])) + @NimbleDriverBaseTestCase.client_mock_decorator_fc(create_configuration( + 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) + @mock.patch(NIMBLE_FC_DRIVER + ".get_wwpns_from_array") + def test_terminate_connection_multiattach_complete_fc(self, mock_wwpns): + mock_wwpns.return_value = ["1111111111111101"] + self.mock_client_service.get_initiator_grp_list.return_value = ( + FAKE_IGROUP_LIST_RESPONSE_FC) + ctx = context.get_admin_context() + + att_1 = fake_volume.volume_attachment_ovo( + ctx, id=uuidutils.generate_uuid()) + volume = fake_volume.fake_volume_obj( + ctx, name='test-volume', + host='fakehost@nimble#Openstack', + provider_location='12 13', + id=12, multiattach=True) + volume.volume_attachment.objects = [att_1] + self.driver.terminate_connection( + volume, + {'initiator': 'test-initiator1', + 'wwpns': ['1000000000000000']}) + expected_calls = [ + mock.call.get_igroupname_for_initiator_fc( + "10:00:00:00:00:00:00:00"), + mock.call.remove_acl({'name': 'test-volume'}, + 'test-igrp1')] + self.mock_client_service.assert_has_calls( + self.mock_client_service.method_calls, + expected_calls) diff --git a/cinder/volume/drivers/nimble.py b/cinder/volume/drivers/nimble.py index 9d7b09ec9ef..018cce30360 100644 --- a/cinder/volume/drivers/nimble.py +++ b/cinder/volume/drivers/nimble.py @@ -50,7 +50,6 @@ AES_256_XTS_CIPHER = 'aes_256_xts' DEFAULT_CIPHER = 'none' EXTRA_SPEC_ENCRYPTION = 'nimble:encryption' EXTRA_SPEC_PERF_POLICY = 'nimble:perfpol-name' -EXTRA_SPEC_MULTI_INITIATOR = 'nimble:multi-initiator' EXTRA_SPEC_DEDUPE = 'nimble:dedupe' EXTRA_SPEC_IOPS_LIMIT = 'nimble:iops-limit' EXTRA_SPEC_FOLDER = 'nimble:folder' @@ -58,7 +57,6 @@ DEFAULT_PERF_POLICY_SETTING = 'default' DEFAULT_ENCRYPTION_SETTING = 'no' DEFAULT_DEDUPE_SETTING = 'false' DEFAULT_IOPS_LIMIT_SETTING = None -DEFAULT_MULTI_INITIATOR_SETTING = 'false' DEFAULT_FOLDER_SETTING = None DEFAULT_SNAP_QUOTA = sys.maxsize BACKUP_VOL_PREFIX = 'backup-vol-' @@ -393,7 +391,8 @@ class NimbleBaseVolumeDriver(san.SanDriver): total_capacity_gb=total_capacity, free_capacity_gb=free_space, reserved_percentage=0, - QoS_support=False) + QoS_support=False, + multiattach=True) self.group_stats['pools'] = [single_pool] return self.group_stats @@ -721,6 +720,24 @@ class NimbleBaseVolumeDriver(san.SanDriver): {'vol': volume['name'], 'igroup': initiator_group_name}) + def _is_multiattach(self, volume): + if volume.multiattach: + attachment_list = volume.volume_attachment + try: + attachment_list = attachment_list.objects + except AttributeError: + pass + + if attachment_list is not None and len(attachment_list) > 1: + LOG.info("Volume %(volume)s is attached to multiple " + "instances on host %(host_name)s, " + "skip terminate volume connection", + {'volume': volume.name, + 'host_name': volume.host.split('@')[0]}) + return True + else: + return False + @interface.volumedriver class NimbleISCSIDriver(NimbleBaseVolumeDriver, san.SanISCSIDriver): @@ -799,6 +816,8 @@ class NimbleISCSIDriver(NimbleBaseVolumeDriver, san.SanISCSIDriver): volume) self.APIExecutor.remove_all_acls(volume) return + if self._is_multiattach(volume): + return initiator_name = connector['initiator'] initiator_group_name = self._get_igroupname_for_initiator( @@ -996,6 +1015,8 @@ class NimbleFCDriver(NimbleBaseVolumeDriver, driver.FibreChannelDriver): volume) self.APIExecutor.remove_all_acls(volume) return + if self._is_multiattach(volume): + return initiator_name = connector['initiator'] for wwpn in connector['wwpns']: @@ -1161,8 +1182,6 @@ class NimbleRestAPIExecutor(object): DEFAULT_PERF_POLICY_SETTING) encryption = extra_specs.get(EXTRA_SPEC_ENCRYPTION, DEFAULT_ENCRYPTION_SETTING) - multi_initiator = extra_specs.get(EXTRA_SPEC_MULTI_INITIATOR, - DEFAULT_MULTI_INITIATOR_SETTING) iops_limit = extra_specs.get(EXTRA_SPEC_IOPS_LIMIT, DEFAULT_IOPS_LIMIT_SETTING) folder_name = extra_specs.get(EXTRA_SPEC_FOLDER, @@ -1172,11 +1191,9 @@ class NimbleRestAPIExecutor(object): extra_specs_map = {} extra_specs_map[EXTRA_SPEC_PERF_POLICY] = perf_policy_name extra_specs_map[EXTRA_SPEC_ENCRYPTION] = encryption - extra_specs_map[EXTRA_SPEC_MULTI_INITIATOR] = multi_initiator extra_specs_map[EXTRA_SPEC_IOPS_LIMIT] = iops_limit extra_specs_map[EXTRA_SPEC_DEDUPE] = dedupe extra_specs_map[EXTRA_SPEC_FOLDER] = folder_name - return extra_specs_map def get_valid_nimble_extraspecs(self, extra_specs_map, vol_info): @@ -1192,10 +1209,10 @@ class NimbleRestAPIExecutor(object): if encrypt.lower() == 'yes': cipher = AES_256_XTS_CIPHER data['cipher'] = cipher - - multi_initiator = extra_specs_map_updated[EXTRA_SPEC_MULTI_INITIATOR] - data['multi_initiator'] = multi_initiator - + if extra_specs_map.get('multiattach') == " True": + data['multi_initiator'] = True + else: + data['multi_initiator'] = False folder_name = extra_specs_map_updated[EXTRA_SPEC_FOLDER] folder_id = None pool_id = vol_info['pool_id'] @@ -1290,7 +1307,7 @@ class NimbleRestAPIExecutor(object): perf_policy_name = extra_specs_map[EXTRA_SPEC_PERF_POLICY] perf_policy_id = self.get_performance_policy_id(perf_policy_name) encrypt = extra_specs_map[EXTRA_SPEC_ENCRYPTION] - multi_initiator = extra_specs_map[EXTRA_SPEC_MULTI_INITIATOR] + multi_initiator = volume.get('multiattach', False) folder_name = extra_specs_map[EXTRA_SPEC_FOLDER] iops_limit = extra_specs_map[EXTRA_SPEC_IOPS_LIMIT] dedupe = extra_specs_map[EXTRA_SPEC_DEDUPE] @@ -1521,11 +1538,11 @@ class NimbleRestAPIExecutor(object): "initiator_group_id": initiator_group_id} api = "access_control_records" r = self.get_query(api, filter) + LOG.info("ACL record is %result", {'result': r.json()}) if not r.json()['data']: - raise NimbleAPIException(_("Unable to retrieve ACL for volume: " - "%(vol)s %(igroup)s ") % - {'vol': volume_id, - 'igroup': initiator_group_id}) + LOG.warning('ACL is not available for this volume %vol_id', { + 'vol_id': volume_id}) + return return r.json()['data'][0] def get_volume_acl_records(self, volume_id): @@ -1560,9 +1577,10 @@ class NimbleRestAPIExecutor(object): try: acl_record = self.get_acl_record(volume_id, initiator_group_id) LOG.debug("ACL Record %(acl)s", {"acl": acl_record}) - acl_id = acl_record['id'] - api = 'access_control_records/' + six.text_type(acl_id) - self.delete(api) + if acl_record is not None: + acl_id = acl_record['id'] + api = 'access_control_records/' + six.text_type(acl_id) + self.delete(api) except NimbleAPIException as ex: LOG.debug("remove_acl_exception: %s", ex) if SM_OBJ_ENOENT_MSG in six.text_type(ex): @@ -1700,7 +1718,7 @@ class NimbleRestAPIExecutor(object): perf_policy_name = extra_specs_map.get(EXTRA_SPEC_PERF_POLICY) perf_policy_id = self.get_performance_policy_id(perf_policy_name) encrypt = extra_specs_map.get(EXTRA_SPEC_ENCRYPTION) - multi_initiator = extra_specs_map.get(EXTRA_SPEC_MULTI_INITIATOR) + multi_initiator = volume.get('multiattach', False) iops_limit = extra_specs_map[EXTRA_SPEC_IOPS_LIMIT] folder_name = extra_specs_map[EXTRA_SPEC_FOLDER] pool_id = self.get_pool_id(pool_name) diff --git a/doc/source/configuration/block-storage/drivers/nimble-volume-driver.rst b/doc/source/configuration/block-storage/drivers/nimble-volume-driver.rst index 77a115abbd2..4d1e18c908e 100644 --- a/doc/source/configuration/block-storage/drivers/nimble-volume-driver.rst +++ b/doc/source/configuration/block-storage/drivers/nimble-volume-driver.rst @@ -37,6 +37,8 @@ Supported operations * Force backup of an in-use volume * Retype a volume * Create a Thinly Provisioned Volume +* Attach a volume to multiple servers simultaneously (multiattach) + Nimble Storage driver configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -126,9 +128,6 @@ The Nimble volume driver also supports the following extra spec options: PERF_POL_NAME is the name of a performance policy which exists on the Nimble array and should be enabled for every volume in a volume type. -'nimble:multi-initiator'='true' - Used to enable multi-initiator access for a volume-type. - nimble:dedupe'='true' Used to enable dedupe support for a volume-type. diff --git a/doc/source/reference/support-matrix.ini b/doc/source/reference/support-matrix.ini index 8aa717a9aed..0ed9fe5dbc9 100644 --- a/doc/source/reference/support-matrix.ini +++ b/doc/source/reference/support-matrix.ini @@ -768,7 +768,7 @@ driver.netapp_ontap=complete driver.netapp_solidfire=complete driver.nexenta=missing driver.nfs=missing -driver.nimble=missing +driver.nimble=complete driver.prophetstor=missing driver.pure=complete driver.qnap=missing diff --git a/releasenotes/notes/1885946-17bc5c3dc0535044.yaml b/releasenotes/notes/1885946-17bc5c3dc0535044.yaml new file mode 100644 index 00000000000..994988032d5 --- /dev/null +++ b/releasenotes/notes/1885946-17bc5c3dc0535044.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add Multi-attach feature in Nimble driver. +upgrade: + - | + Nimble specific extra-spec nimble:multi-initiator is removed. + Common extra-spec multiattach is added.