From 9c3e6aa9cd8702a16b7de69267bf994c7e421890 Mon Sep 17 00:00:00 2001 From: Sonia Ghanekar Date: Wed, 26 Aug 2015 10:08:53 -0700 Subject: [PATCH] Add manage/unmanage volume support for Nimble This patch adds support for volume manage/unmanage for a Nimble ISCSI driver. It uses an additional field 'agent-type' on the backend for distinguishing volumes managed by the Nimble driver. While managing an existing volume it sets the agent-type to Openstack and while unmanaging sets it back to None. Since this field was not used in the earlier versions, in order to be backward compatible, during the initialization of the driver, the 'agent-type' is updated to Openstack for all the existing Nimble volumes. Implements: blueprint nimble-add-volume-manage-unmanage-support Change-Id: Ia960f2a2911288409977324cfc287c216b60b589 --- cinder/tests/unit/test_nimble.py | 236 +++++++++++++++++++++++++++++-- cinder/volume/drivers/nimble.py | 140 ++++++++++++++++-- 2 files changed, 358 insertions(+), 18 deletions(-) diff --git a/cinder/tests/unit/test_nimble.py b/cinder/tests/unit/test_nimble.py index f32a73459..ceda31bba 100755 --- a/cinder/tests/unit/test_nimble.py +++ b/cinder/tests/unit/test_nimble.py @@ -19,6 +19,7 @@ import mock from oslo_config import cfg from cinder import exception +from cinder.objects import volume as obj_volume from cinder import test from cinder.volume.drivers import nimble from cinder.volume import volume_types @@ -109,9 +110,30 @@ FAKE_IGROUP_LIST_RESPONSE = { {'initiator-list': [{'name': 'test-initiator1'}], 'name': 'test-igrp2'}]} -FAKE_GET_VOL_INFO_RESPONSE = {'err-list': {'err-list': - [{'code': 0}]}, - 'vol': {'target-name': 'iqn.test'}} +FAKE_GET_VOL_INFO_RESPONSE = { + 'err-list': {'err-list': [{'code': 0}]}, + 'vol': {'target-name': 'iqn.test', + 'name': 'test_vol', + 'agent-type': 1, + 'online': False}} + +FAKE_GET_VOL_INFO_ONLINE = { + 'err-list': {'err-list': [{'code': 0}]}, + 'vol': {'target-name': 'iqn.test', + 'name': 'test_vol', + 'agent-type': 1, + 'online': True}} + +FAKE_GET_VOL_INFO_ERROR = { + 'err-list': {'err-list': [{'code': 2}]}, + 'vol': {'target-name': 'iqn.test'}} + +FAKE_GET_VOL_INFO_RESPONSE_WITH_SET_AGENT_TYPE = { + 'err-list': {'err-list': [{'code': 0}]}, + 'vol': {'target-name': 'iqn.test', + 'name': 'test_vol', + 'agent-type': 5}} + FAKE_TYPE_ID = 12345 @@ -171,6 +193,8 @@ class NimbleDriverLoginTestCase(NimbleDriverBaseTestCase): @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) def test_do_setup_positive(self): @@ -191,6 +215,8 @@ class NimbleDriverLoginTestCase(NimbleDriverBaseTestCase): @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) def test_expire_session_id(self): @@ -225,6 +251,12 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase): @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) + @mock.patch.object(volume_types, 'get_volume_type_extra_specs', + mock.Mock(type_id=FAKE_TYPE_ID, return_value={ + 'nimble:perfpol-name': 'default', + 'nimble:encryption': 'yes'})) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) def test_create_volume_positive(self): @@ -250,11 +282,13 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase): 'online': True, 'pool-name': 'default', 'size': 1073741824, 'quota': 1073741824, 'perfpol-name': 'default', 'description': '', - 'encryptionAttr': {'cipher': 3}}, + 'agent-type': 5, 'encryptionAttr': {'cipher': 3}}, 'sid': 'a9b9aba7'}) @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) @mock.patch.object(volume_types, 'get_volume_type_extra_specs', mock.Mock(type_id=FAKE_TYPE_ID, return_value={ 'nimble:perfpol-name': 'default', @@ -289,11 +323,13 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase): 'online': True, 'pool-name': 'default', 'size': 1073741824, 'quota': 1073741824, 'perfpol-name': 'default', 'description': '', - 'encryptionAttr': {'cipher': 2}}, + 'agent-type': 5, 'encryptionAttr': {'cipher': 2}}, 'sid': 'a9b9aba7'}) @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) @mock.patch.object(volume_types, 'get_volume_type_extra_specs', mock.Mock(type_id=FAKE_TYPE_ID, return_value={ 'nimble:perfpol-name': 'VMware ESX', @@ -328,11 +364,13 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase): 'online': True, 'pool-name': 'default', 'size': 1073741824, 'quota': 1073741824, 'perfpol-name': 'VMware ESX', 'description': '', - 'encryptionAttr': {'cipher': 3}}, + 'agent-type': 5, 'encryptionAttr': {'cipher': 3}}, 'sid': 'a9b9aba7'}) @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) def test_create_volume_negative(self): @@ -349,6 +387,8 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase): @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) def test_create_volume_encryption_negative(self): @@ -365,6 +405,8 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase): @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) def test_create_volume_perfpolicy_negative(self): @@ -381,6 +423,8 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase): @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) def test_delete_volume(self): @@ -402,6 +446,8 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase): @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) def test_extend_volume(self): @@ -420,6 +466,8 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase): @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*', False)) @mock.patch(NIMBLE_RANDOM) @@ -455,19 +503,174 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase): 'reserve': 5368709120, 'online': True, 'warn-level': 4294967296, - 'perfpol-name': 'default'}, + 'perfpol-name': 'default', + 'agent-type': 5}, 'name': 'testvolume', 'sid': 'a9b9aba7'})] self.mock_client_service.assert_has_calls(expected_calls) @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) + @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( + 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) + def test_manage_volume_positive(self): + self.mock_client_service.service.getNetConfig.return_value = ( + FAKE_POSITIVE_NETCONFIG_RESPONSE) + self.mock_client_service.service.getVolInfo.return_value = ( + FAKE_GET_VOL_INFO_RESPONSE) + self.mock_client_service.service.onlineVol.return_value = ( + FAKE_GENERIC_POSITIVE_RESPONSE) + self.mock_client_service.service.editVol.return_value = ( + FAKE_CREATE_VOLUME_POSITIVE_RESPONSE) + self.assertEqual({ + 'provider_location': '172.18.108.21:3260 iqn.test 0', + 'provider_auth': None}, + self.driver.manage_existing({'name': 'volume-abcdef'}, + {'source-name': 'test-vol'})) + expected_calls = [ + mock.call.service.editVol( + request={ + 'attr': { + 'name': 'volume-abcdef', 'agent-type': 5}, + 'mask': 262145, + 'name': 'test-vol', + 'sid': 'a9b9aba7'}), + mock.call.service.onlineVol( + request={'online': True, + 'name': 'volume-abcdef', + 'sid': 'a9b9aba7'} + ) + ] + self.mock_client_service.assert_has_calls(expected_calls) + + @mock.patch(NIMBLE_URLLIB2) + @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) + @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( + 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) + def test_manage_volume_which_is_online(self): + self.mock_client_service.service.getNetConfig.return_value = ( + FAKE_POSITIVE_NETCONFIG_RESPONSE) + self.mock_client_service.service.getVolInfo.return_value = ( + FAKE_GET_VOL_INFO_ONLINE) + self.assertRaises( + exception.InvalidVolume, + self.driver.manage_existing, + {'name': 'volume-abcdef'}, + {'source-name': 'test-vol'}) + + @mock.patch(NIMBLE_URLLIB2) + @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) + @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( + 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) + def test_manage_volume_with_improper_ref(self): + self.assertRaises( + exception.ManageExistingInvalidReference, + self.driver.manage_existing, + {'name': 'volume-abcdef'}, + {'source-id': 'test-vol'}) + + @mock.patch(NIMBLE_URLLIB2) + @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) + @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( + 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) + def test_manage_volume_with_nonexistant_volume(self): + self.mock_client_service.service.getVolInfo.return_value = ( + FAKE_GET_VOL_INFO_ERROR) + self.assertRaises( + exception.VolumeBackendAPIException, + self.driver.manage_existing, + {'name': 'volume-abcdef'}, + {'source-name': 'test-vol'}) + + @mock.patch(NIMBLE_URLLIB2) + @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) + @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( + 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) + def test_manage_volume_with_wrong_agent_type(self): + self.mock_client_service.service.getVolInfo.return_value = ( + FAKE_GET_VOL_INFO_RESPONSE_WITH_SET_AGENT_TYPE) + self.assertRaises( + exception.ManageExistingAlreadyManaged, + self.driver.manage_existing, + {'id': 'abcdef', 'name': 'volume-abcdef'}, + {'source-name': 'test-vol'}) + + @mock.patch(NIMBLE_URLLIB2) + @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) + @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( + 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) + def test_unmanage_volume_positive(self): + self.mock_client_service.service.getVolInfo.return_value = ( + FAKE_GET_VOL_INFO_RESPONSE_WITH_SET_AGENT_TYPE) + self.mock_client_service.service.editVol.return_value = ( + FAKE_CREATE_VOLUME_POSITIVE_RESPONSE) + self.driver.unmanage({'name': 'volume-abcdef'}) + expected_calls = [ + mock.call.service.editVol( + request={'attr': {'agent-type': 1}, + 'mask': 262144, + 'name': 'volume-abcdef', + 'sid': 'a9b9aba7'}), + mock.call.service.onlineVol( + request={'online': False, + 'name': 'volume-abcdef', + 'sid': 'a9b9aba7'} + ) + ] + self.mock_client_service.assert_has_calls(expected_calls) + + @mock.patch(NIMBLE_URLLIB2) + @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) + @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( + 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) + def test_unmanage_with_invalid_volume(self): + self.mock_client_service.service.getVolInfo.return_value = ( + FAKE_GET_VOL_INFO_ERROR) + self.assertRaises( + exception.VolumeBackendAPIException, + self.driver.unmanage, + {'name': 'volume-abcdef'} + ) + + @mock.patch(NIMBLE_URLLIB2) + @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) + @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( + 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) + def test_unmanage_with_invalid_agent_type(self): + self.mock_client_service.service.getVolInfo.return_value = ( + FAKE_GET_VOL_INFO_RESPONSE) + self.assertRaises( + exception.InvalidVolume, + self.driver.unmanage, + {'name': 'volume-abcdef'} + ) + + @mock.patch(NIMBLE_URLLIB2) + @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) def test_get_volume_stats(self): self.mock_client_service.service.getGroupConfig.return_value = \ FAKE_POSITIVE_GROUP_CONFIG_RESPONSE - expected_res = {'driver_version': '1.1.3', + expected_res = {'driver_version': '2.0.0', 'vendor_name': 'Nimble', 'volume_backend_name': 'NIMBLE', 'storage_protocol': 'iSCSI', @@ -487,6 +690,8 @@ class NimbleDriverSnapshotTestCase(NimbleDriverBaseTestCase): @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) def test_create_snapshot(self): @@ -507,6 +712,8 @@ class NimbleDriverSnapshotTestCase(NimbleDriverBaseTestCase): @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) def test_delete_snapshot(self): @@ -530,6 +737,8 @@ class NimbleDriverSnapshotTestCase(NimbleDriverBaseTestCase): @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) def test_create_volume_from_snapshot(self): @@ -557,7 +766,8 @@ class NimbleDriverSnapshotTestCase(NimbleDriverBaseTestCase): 'online': True, 'reserve': 0, 'warn-level': 858993459, - 'perfpol-name': 'default'}, + 'perfpol-name': 'default', + 'agent-type': 5}, 'name': 'testvolume', 'sid': 'a9b9aba7'}), mock.call.service.editVol( @@ -578,6 +788,8 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase): @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) def test_initialize_connection_igroup_exist(self): @@ -617,6 +829,8 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase): @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) @mock.patch(NIMBLE_RANDOM) @@ -659,6 +873,8 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase): @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) def test_terminate_connection_positive(self): @@ -684,6 +900,8 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase): @mock.patch(NIMBLE_URLLIB2) @mock.patch(NIMBLE_CLIENT) + @mock.patch.object(obj_volume.VolumeList, 'get_all', + mock.Mock(return_value=[])) @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration( 'nimble', 'nimble_pass', '10.18.108.55', 'default', '*')) def test_terminate_connection_negative(self): diff --git a/cinder/volume/drivers/nimble.py b/cinder/volume/drivers/nimble.py index 2c4eb4462..50f9e8f20 100755 --- a/cinder/volume/drivers/nimble.py +++ b/cinder/volume/drivers/nimble.py @@ -32,12 +32,13 @@ from six.moves import urllib from suds import client from cinder import exception -from cinder.i18n import _, _LE, _LI +from cinder.i18n import _, _LE, _LI, _LW +from cinder.objects import volume from cinder.volume.drivers.san import san from cinder.volume import volume_types -DRIVER_VERSION = '1.1.3' +DRIVER_VERSION = '2.0.0' AES_256_XTS_CIPHER = 2 DEFAULT_CIPHER = 3 EXTRA_SPEC_ENCRYPTION = 'nimble:encryption' @@ -46,6 +47,10 @@ DEFAULT_PERF_POLICY_SETTING = 'default' DEFAULT_ENCRYPTION_SETTING = 'no' DEFAULT_SNAP_QUOTA = sys.maxsize VOL_EDIT_MASK = 4 + 16 + 32 + 64 + 256 + 512 +MANAGE_EDIT_MASK = 1 + 262144 +UNMANAGE_EDIT_MASK = 262144 +AGENT_TYPE_OPENSTACK = 5 +AGENT_TYPE_NONE = 1 SOAP_PORT = 5391 SM_ACL_APPLY_TO_BOTH = 3 SM_ACL_CHAP_USER_ANY = '*' @@ -83,10 +88,11 @@ class NimbleISCSIDriver(san.SanISCSIDriver): Version history: 1.0 - Initial driver - 1.1.0 - Added Extra Spec Capability 1.1.1 - Updated VERSION to Nimble driver version 1.1.2 - Update snap-quota to unlimited - 1.1.3 - Correct capacity reporting + 2.0.0 - Added Extra Spec Capability + Correct capacity reporting + Added Manage/Unmanage volume support """ VERSION = DRIVER_VERSION @@ -142,6 +148,23 @@ class NimbleISCSIDriver(san.SanISCSIDriver): else: raise NimbleDriverException(_('No suitable discovery ip found')) + def _update_existing_vols_agent_type(self, context): + LOG.debug("Updating existing volumes to have " + "agent_type = 'OPENSTACK'") + backend_name = self.configuration.safe_get('volume_backend_name') + all_vols = volume.VolumeList.get_all( + context, None, None, None, None, {'status': 'available'}) + for vol in all_vols: + if backend_name in vol.host: + try: + self.APIExecutor.edit_vol( + vol.name, + UNMANAGE_EDIT_MASK, + {'agent-type': AGENT_TYPE_OPENSTACK}) + except NimbleAPIException: + LOG.warning(_LW('Error updating agent-type for ' + 'volume %s.'), vol.name) + def do_setup(self, context): """Setup the Nimble Cinder volume driver.""" self._check_config() @@ -156,6 +179,7 @@ class NimbleISCSIDriver(san.SanISCSIDriver): 'Check san_ip, username, password' ' and make sure the array version is compatible')) raise + self._update_existing_vols_agent_type(context) def _get_provider_location(self, volume_name): """Get volume iqn for initiator access.""" @@ -312,6 +336,99 @@ class NimbleISCSIDriver(san.SanISCSIDriver): 'quota': vol_size, 'snap-quota': DEFAULT_SNAP_QUOTA}) + def _get_existing_volume_ref_name(self, existing_ref): + """Returns the volume name of an existing ref""" + + vol_name = None + if 'source-name' in existing_ref: + vol_name = existing_ref['source-name'] + else: + reason = _("Reference must contain source-name.") + raise exception.ManageExistingInvalidReference( + existing_ref=existing_ref, + reason=reason) + + return vol_name + + def manage_existing(self, volume, external_ref): + """Manage an existing nimble volume (import to cinder)""" + + # Get the volume name from the external reference + target_vol_name = self._get_existing_volume_ref_name(external_ref) + LOG.debug('Entering manage_existing. ' + 'Target_volume_name = %s', target_vol_name) + + # Get vol info from the volume name obtained from the reference + vol_info = self.APIExecutor.get_vol_info(target_vol_name) + + # Check if volume is already managed by Openstack + if vol_info['agent-type'] == AGENT_TYPE_OPENSTACK: + msg = (_('Volume %s is already managed by Openstack.') + % target_vol_name) + raise exception.ManageExistingAlreadyManaged( + volume_ref=volume['id']) + + # If agent-type is not None then raise exception + if vol_info['agent-type'] != AGENT_TYPE_NONE: + msg = (_('Volume should have agent-type set as None.')) + raise exception.InvalidVolume(reason=msg) + + new_vol_name = volume['name'] + + if vol_info['online']: + msg = (_('Volume %s is online. Set volume to offline for ' + 'managing using Openstack.') % target_vol_name) + raise exception.InvalidVolume(reason=msg) + + # edit the volume + self.APIExecutor.edit_vol(target_vol_name, + MANAGE_EDIT_MASK, + {'name': new_vol_name, + 'agent-type': AGENT_TYPE_OPENSTACK}) + + # make the volume online after rename + self.APIExecutor.online_vol(new_vol_name, True, ignore_list=[ + 'SM-enoent']) + + return self._get_model_info(new_vol_name) + + def manage_existing_get_size(self, volume, external_ref): + """Return size of an existing volume""" + + LOG.debug('Volume name : %(name)s External ref : %(ref)s', + {'name': volume['name'], 'ref': external_ref}) + + target_vol_name = self._get_existing_volume_ref_name(external_ref) + + # get vol info + vol_info = self.APIExecutor.get_vol_info(target_vol_name) + + LOG.debug('Volume size : %(size)s Volume-name : %(name)s', + {'size': vol_info['size'], 'name': vol_info['name']}) + + return int(vol_info['size'] / units.Gi) + + def unmanage(self, volume): + """Removes the specified volume from Cinder management.""" + + vol_name = volume['name'] + LOG.info(_LI("Entering unmanage_volume volume = %s"), vol_name) + + # check agent type + vol_info = self.APIExecutor.get_vol_info(vol_name) + if vol_info['agent-type'] != AGENT_TYPE_OPENSTACK: + msg = (_('Only volumes managed by Openstack can be unmanaged.')) + raise exception.InvalidVolume(reason=msg) + + # update the agent-type to None + self.APIExecutor.edit_vol(vol_name, + UNMANAGE_EDIT_MASK, + {'agent-type': AGENT_TYPE_NONE}) + + # offline the volume + self.APIExecutor.online_vol(vol_name, False, ignore_list=[ + 'SM-enoent']) + def _create_igroup_for_initiator(self, initiator_name): """Creates igroup for an initiator and returns the igroup name.""" igrp_name = 'openstack-' + self._generate_random_string(12) @@ -527,7 +644,8 @@ class NimbleAPIExecutor(object): ' reserve=%(reserve)s in pool=%(pool)s' ' description=%(description)s with Extra Specs' ' perfpol-name=%(perfpol-name)s' - ' encryption=%(encryption)s cipher=%(cipher)s', + ' encryption=%(encryption)s cipher=%(cipher)s' + ' agent-type=%(agent-type)s', {'vol': volume['name'], 'size': volume_size, 'reserve': reserve, @@ -535,7 +653,8 @@ class NimbleAPIExecutor(object): 'description': description, 'perfpol-name': perf_policy_name, 'encryption': encrypt, - 'cipher': cipher}) + 'cipher': cipher, + 'agent-type': AGENT_TYPE_OPENSTACK}) return self.client.service.createVol( request={'sid': self.sid, @@ -548,6 +667,7 @@ class NimbleAPIExecutor(object): 'snap-quota': DEFAULT_SNAP_QUOTA, 'online': True, 'pool-name': pool_name, + 'agent-type': AGENT_TYPE_OPENSTACK, 'perfpol-name': perf_policy_name, 'encryptionAttr': {'cipher': cipher}}}) @@ -695,12 +815,13 @@ class NimbleAPIExecutor(object): reserve_size = snap_size * units.Gi if reserve else 0 LOG.info(_LI('Cloning volume from snapshot volume=%(vol)s ' 'snapshot=%(snap)s clone=%(clone)s snap_size=%(size)s' - 'reserve=%(reserve)s'), + 'reserve=%(reserve)s' 'agent-type=%(agent-type)s'), {'vol': volume_name, 'snap': snap_name, 'clone': clone_name, 'size': snap_size, - 'reserve': reserve}) + 'reserve': reserve, + 'agent-type': AGENT_TYPE_OPENSTACK}) clone_size = snap_size * units.Gi return self.client.service.cloneVol( request={'sid': self.sid, @@ -711,7 +832,8 @@ class NimbleAPIExecutor(object): 'warn-level': int(clone_size * WARN_LEVEL), 'quota': clone_size, 'snap-quota': DEFAULT_SNAP_QUOTA, - 'online': True}, + 'online': True, + 'agent-type': AGENT_TYPE_OPENSTACK}, 'snap-name': snap_name}) @_connection_checker -- 2.45.2