diff --git a/cinder/tests/unit/volume/drivers/nec/test_volume.py b/cinder/tests/unit/volume/drivers/nec/test_volume.py index 73405165d8e..90d41c3b037 100644 --- a/cinder/tests/unit/volume/drivers/nec/test_volume.py +++ b/cinder/tests/unit/volume/drivers/nec/test_volume.py @@ -187,8 +187,8 @@ xml_out = ''' 4T7JpyqI3UuPlKeT9D3VQF 6442450944 0001 - RPL - IV + (invalid attribute) + SV @@ -303,6 +303,14 @@ xml_out = '''
1000-0090-FAA0-786A
+
+ 0000 + 0005 +
+
+ 0001 + 0006 +
@@ -1163,3 +1171,182 @@ class Migrate_test(volume_helper.MStorageDSVDriver, test.TestCase): self.newvol, 'available') self.assertIsNone(update_data['_name_id']) self.assertIsNone(update_data['provider_location']) + + +class ManageUnmanage_test(volume_helper.MStorageDSVDriver, test.TestCase): + + @mock.patch('cinder.volume.drivers.nec.volume_common.MStorageVolumeCommon.' + '_create_ismview_dir', new=mock.Mock()) + def setUp(self): + super(ManageUnmanage_test, self).setUp() + self._set_config(conf.Configuration(None), 'dummy', 'dummy') + self.do_setup(None) + self._properties['pool_pools'] = {0} + self._properties['pool_backup_pools'] = {1} + + def test_is_manageable_volume(self): + ld_ok_iv = {'pool_num': 0, 'RPL Attribute': 'IV', 'Purpose': '---'} + ld_ok_bv = {'pool_num': 0, 'RPL Attribute': 'BV', 'Purpose': 'INV'} + ld_ng_pool = {'pool_num': 1, 'RPL Attribute': 'IV', 'Purpose': '---'} + ld_ng_rpl1 = {'pool_num': 0, 'RPL Attribute': 'MV', 'Purpose': 'INV'} + ld_ng_rpl2 = {'pool_num': 0, 'RPL Attribute': 'RV', 'Purpose': 'INV'} + ld_ng_rpl3 = {'pool_num': 0, 'RPL Attribute': 'SV', 'Purpose': 'INV'} + ld_ng_purp = {'pool_num': 0, 'RPL Attribute': 'IV', 'Purpose': 'INV'} + self.assertTrue(self._is_manageable_volume(ld_ok_iv)) + self.assertTrue(self._is_manageable_volume(ld_ok_bv)) + self.assertFalse(self._is_manageable_volume(ld_ng_pool)) + self.assertFalse(self._is_manageable_volume(ld_ng_rpl1)) + self.assertFalse(self._is_manageable_volume(ld_ng_rpl2)) + self.assertFalse(self._is_manageable_volume(ld_ng_rpl3)) + self.assertFalse(self._is_manageable_volume(ld_ng_purp)) + + @mock.patch('cinder.volume.drivers.nec.cli.MStorageISMCLI.' + 'view_all', patch_view_all) + def test_get_manageable_volumes(self): + current_volumes = [] + volumes = self.get_manageable_volumes(current_volumes, None, + 100, 0, ['reference'], ['dec']) + self.assertEqual('LX:287RbQoP7VdwR1WsPC2fZT', + volumes[2]['reference']['source-name']) + current_volumes = [] + volumes = self.get_manageable_volumes(current_volumes, None, + 100, 0, ['reference'], ['asc']) + self.assertEqual(' :2000000991020012000A', + volumes[0]['reference']['source-name']) + self.assertEqual(10, len(volumes)) + + volume = {'id': '46045673-41e7-44a7-9333-02f07feab04b'} + current_volumes = [] + current_volumes.append(volume) + volumes = self.get_manageable_volumes(current_volumes, None, + 100, 0, ['reference'], ['dec']) + self.assertFalse(volumes[2]['safe_to_manage']) + self.assertFalse(volumes[3]['safe_to_manage']) + self.assertTrue(volumes[4]['safe_to_manage']) + + @mock.patch('cinder.volume.drivers.nec.cli.MStorageISMCLI.' + 'view_all', patch_view_all) + def test_manage_existing(self): + mock_rename = mock.Mock() + self._cli.changeldname = mock_rename + self.newvol = DummyVolume() + self.newvol.id = "46045673-41e7-44a7-9333-02f07feab04b" + + current_volumes = [] + volumes = self.get_manageable_volumes(current_volumes, None, + 100, 0, ['reference'], ['dec']) + self.manage_existing(self.newvol, volumes[4]['reference']) + self._cli.changeldname.assert_called_once_with( + None, + 'LX:287RbQoP7VdwR1WsPC2fZT', + ' :20000009910200140009') + with self.assertRaisesRegex(exception.ManageExistingInvalidReference, + 'Specified resource is already in-use.'): + self.manage_existing(self.newvol, volumes[3]['reference']) + volume = {'source-name': 'LX:yEUHrXa5AHMjOZZLb93eP'} + with self.assertRaisesRegex(exception.ManageExistingVolumeTypeMismatch, + 'Volume type is unmatched.'): + self.manage_existing(self.newvol, volume) + + @mock.patch('cinder.volume.drivers.nec.cli.MStorageISMCLI.' + 'view_all', patch_view_all) + def test_manage_existing_get_size(self): + self.newvol = DummyVolume() + self.newvol.id = "46045673-41e7-44a7-9333-02f07feab04b" + + current_volumes = [] + volumes = self.get_manageable_volumes(current_volumes, None, + 100, 0, ['reference'], ['dec']) + size_in_gb = self.manage_existing_get_size(self.newvol, + volumes[3]['reference']) + self.assertEqual(10, size_in_gb) + + +class ManageUnmanage_Snap_test(volume_helper.MStorageDSVDriver, test.TestCase): + + @mock.patch('cinder.volume.drivers.nec.volume_common.MStorageVolumeCommon.' + '_create_ismview_dir', new=mock.Mock()) + def setUp(self): + super(ManageUnmanage_Snap_test, self).setUp() + self._set_config(conf.Configuration(None), 'dummy', 'dummy') + self.do_setup(None) + self._properties['pool_pools'] = {0} + self._properties['pool_backup_pools'] = {1} + + def test_is_manageable_snapshot(self): + ld_ok_sv1 = {'pool_num': 1, 'RPL Attribute': 'SV', 'Purpose': 'INV'} + ld_ok_sv2 = {'pool_num': 1, 'RPL Attribute': 'SV', 'Purpose': '---'} + ld_ng_pool = {'pool_num': 0, 'RPL Attribute': 'SV', 'Purpose': 'INV'} + ld_ng_rpl1 = {'pool_num': 1, 'RPL Attribute': 'MV', 'Purpose': 'INV'} + ld_ng_rpl2 = {'pool_num': 1, 'RPL Attribute': 'RV', 'Purpose': 'INV'} + ld_ng_rpl3 = {'pool_num': 1, 'RPL Attribute': 'IV', 'Purpose': '---'} + ld_ng_rpl4 = {'pool_num': 1, 'RPL Attribute': 'BV', 'Purpose': 'INV'} + self.assertTrue(self._is_manageable_snapshot(ld_ok_sv1)) + self.assertTrue(self._is_manageable_snapshot(ld_ok_sv2)) + self.assertFalse(self._is_manageable_snapshot(ld_ng_pool)) + self.assertFalse(self._is_manageable_snapshot(ld_ng_rpl1)) + self.assertFalse(self._is_manageable_snapshot(ld_ng_rpl2)) + self.assertFalse(self._is_manageable_snapshot(ld_ng_rpl3)) + self.assertFalse(self._is_manageable_snapshot(ld_ng_rpl4)) + + @mock.patch('cinder.volume.drivers.nec.cli.MStorageISMCLI.' + 'view_all', patch_view_all) + def test_get_manageable_snapshots(self): + mock_getbvname = mock.Mock() + self._cli.get_bvname = mock_getbvname + self._cli.get_bvname.return_value = "yEUHrXa5AHMjOZZLb93eP" + current_snapshots = [] + volumes = self.get_manageable_snapshots(current_snapshots, None, + 100, 0, ['reference'], ['asc']) + self.assertEqual('LX:4T7JpyqI3UuPlKeT9D3VQF', + volumes[0]['reference']['source-name']) + + @mock.patch('cinder.volume.drivers.nec.cli.MStorageISMCLI.' + 'view_all', patch_view_all) + def test_manage_existing_snapshot(self): + mock_rename = mock.Mock() + self._cli.changeldname = mock_rename + self.newsnap = DummyVolume() + self.newsnap.id = "46045673-41e7-44a7-9333-02f07feab04b" + self.newsnap.volume_id = "1febb976-86d0-42ed-9bc0-4aa3e158f27d" + mock_getbvname = mock.Mock() + self._cli.get_bvname = mock_getbvname + + self._cli.get_bvname.return_value = "yEUHrXa5AHMjOZZLb93eP" + current_snapshots = [] + snaps = self.get_manageable_snapshots(current_snapshots, None, + 100, 0, ['reference'], ['asc']) + self.manage_existing_snapshot(self.newsnap, snaps[0]['reference']) + self._cli.changeldname.assert_called_once_with( + None, + 'LX:287RbQoP7VdwR1WsPC2fZT', + 'LX:4T7JpyqI3UuPlKeT9D3VQF') + + self.newsnap.volume_id = "AAAAAAAA" + with self.assertRaisesRegex(exception.ManageExistingInvalidReference, + 'Snapshot source is unmatch.'): + self.manage_existing_snapshot(self.newsnap, snaps[0]['reference']) + + self._cli.get_bvname.return_value = "2000000991020012000C" + self.newsnap.volume_id = "00046058-d38e-7f60-67b7-59ed6422520c" + snap = {'source-name': ' :2000000991020012000B'} + with self.assertRaisesRegex(exception.ManageExistingVolumeTypeMismatch, + 'Volume type is unmatched.'): + self.manage_existing_snapshot(self.newsnap, snap) + + @mock.patch('cinder.volume.drivers.nec.cli.MStorageISMCLI.' + 'view_all', patch_view_all) + def test_manage_existing_snapshot_get_size(self): + self.newsnap = DummyVolume() + self.newsnap.id = "46045673-41e7-44a7-9333-02f07feab04b" + mock_getbvname = mock.Mock() + self._cli.get_bvname = mock_getbvname + self._cli.get_bvname.return_value = "yEUHrXa5AHMjOZZLb93eP" + + current_snapshots = [] + snaps = self.get_manageable_snapshots(current_snapshots, None, + 100, 0, ['reference'], ['asc']) + size_in_gb = self.manage_existing_snapshot_get_size( + self.newsnap, + snaps[0]['reference']) + self.assertEqual(6, size_in_gb) diff --git a/cinder/volume/drivers/nec/cli.py b/cinder/volume/drivers/nec/cli.py index 00810401644..e7516e1ea42 100644 --- a/cinder/volume/drivers/nec/cli.py +++ b/cinder/volume/drivers/nec/cli.py @@ -554,6 +554,17 @@ class MStorageISMCLI(object): LOG.debug('snap/state:%s.', query_status) return query_status + def get_bvname(self, svname): + cmd = ('iSMsc_query -sv %s -svflg ld -summary | ' + 'while builtin read line;do ' + 'if [[ "$line" =~ "LD Name" ]]; ' + 'then builtin echo "$line";fi;done' + % svname[3:]) + out, err, status = self._execute(cmd) + + query_status = out[15:39].strip() + return query_status + def set_io_limit(self, ldname, specs, force_delete=True): if specs['upperlimit'] is not None: upper = int(specs['upperlimit'], 10) diff --git a/cinder/volume/drivers/nec/volume_common.py b/cinder/volume/drivers/nec/volume_common.py index 0723ae9e77b..3a8dd0089ba 100644 --- a/cinder/volume/drivers/nec/volume_common.py +++ b/cinder/volume/drivers/nec/volume_common.py @@ -876,6 +876,15 @@ class MStorageVolumeCommon(object): else: specs['upperreport'] = None + def check_accesscontrol(self, ldsets, ld): + """Check Logical disk is in-use or not.""" + set_accesscontrol = False + for ldset in ldsets.values(): + if ld['ldn'] in ldset['lds']: + set_accesscontrol = True + break + return set_accesscontrol + def validates_number(self, value): return re.match(r'^(?![-+]0+$)[-+]?([1-9][0-9]*)?[0-9](\.[0-9]+)?$', '%s' % value) and True or False diff --git a/cinder/volume/drivers/nec/volume_helper.py b/cinder/volume/drivers/nec/volume_helper.py index ac352b9a761..a38f7d6a3a4 100644 --- a/cinder/volume/drivers/nec/volume_helper.py +++ b/cinder/volume/drivers/nec/volume_helper.py @@ -15,6 +15,7 @@ # under the License. import random +import re import traceback from oslo_log import log as logging @@ -26,6 +27,7 @@ from cinder import exception from cinder.i18n import _ from cinder.volume.drivers.nec import cli from cinder.volume.drivers.nec import volume_common +from cinder.volume import utils as volutils LOG = logging.getLogger(__name__) @@ -1450,6 +1452,206 @@ class MStorageDriver(volume_common.MStorageVolumeCommon): self._cli.unbind(ldname) LOG.debug('LD unbound. Name=%s.', ldname) + def _is_manageable_volume(self, ld): + if ld['RPL Attribute'] == '---': + return False + if ld['Purpose'] != '---' and 'BV' not in ld['RPL Attribute']: + return False + if ld['pool_num'] not in self._properties['pool_pools']: + return False + return True + + def _is_manageable_snapshot(self, ld): + if ld['RPL Attribute'] == '---': + return False + if 'SV' not in ld['RPL Attribute']: + return False + if ld['pool_num'] not in self._properties['pool_backup_pools']: + return False + return True + + def _reference_to_ldname(self, resource_type, volume, existing_ref): + if resource_type == 'volume': + ldname_format = self._properties['ld_name_format'] + else: + ldname_format = self._properties['ld_backupname_format'] + + id_name = self.get_ldname(volume.id, ldname_format) + ref_name = existing_ref['source-name'] + volid = re.search( + r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}', + ref_name) + if volid: + ref_name = self.get_ldname(volid.group(0), ldname_format) + + return id_name, ref_name + + def _get_manageable_resources(self, resource_type, cinder_volumes, marker, + limit, offset, sort_keys, sort_dirs): + entries = [] + xml = self._cli.view_all(self._properties['ismview_path']) + pools, lds, ldsets, used_ldns, hostports, max_ld_count = ( + self.configs(xml)) + cinder_ids = [resource['id'] for resource in cinder_volumes] + + for ld in lds.values(): + if ((resource_type == 'volume' and + not self._is_manageable_volume(ld)) or + (resource_type == 'snapshot' and + not self._is_manageable_snapshot(ld))): + continue + + ld_info = {'reference': {'source-name': ld['ldname']}, + 'size': ld['ld_capacity'], + 'cinder_id': None, + 'extra_info': None} + + potential_id = volume_common.convert_to_id(ld['ldname'][3:]) + if potential_id in cinder_ids: + ld_info['safe_to_manage'] = False + ld_info['reason_not_safe'] = 'already managed' + ld_info['cinder_id'] = potential_id + elif self.check_accesscontrol(ldsets, ld): + ld_info['safe_to_manage'] = False + ld_info['reason_not_safe'] = '%s in use' % resource_type + else: + ld_info['safe_to_manage'] = True + ld_info['reason_not_safe'] = None + + if resource_type == 'snapshot': + bvname = self._cli.get_bvname(ld['ldname']) + bv_id = volume_common.convert_to_id(bvname) + ld_info['source_reference'] = {'source-name': bv_id} + + entries.append(ld_info) + + return volutils.paginate_entries_list(entries, marker, limit, offset, + sort_keys, sort_dirs) + + def _manage_existing_get_size(self, resource_type, volume, existing_ref): + if 'source-name' not in existing_ref: + reason = _('Reference must contain source-name element.') + raise exception.ManageExistingInvalidReference( + existing_ref=existing_ref, reason=reason) + xml = self._cli.view_all(self._properties['ismview_path']) + pools, lds, ldsets, used_ldns, hostports, max_ld_count = ( + self.configs(xml)) + + id_name, ref_name = self._reference_to_ldname(resource_type, + volume, + existing_ref) + if ref_name not in lds: + reason = _('Specified resource does not exist.') + raise exception.ManageExistingInvalidReference( + existing_ref=existing_ref, reason=reason) + ld = lds[ref_name] + return ld['ld_capacity'] + + def get_manageable_volumes(self, cinder_volumes, marker, limit, offset, + sort_keys, sort_dirs): + """List volumes on the backend available for management by Cinder.""" + LOG.debug('get_manageable_volumes Start.') + return self._get_manageable_resources('volume', + cinder_volumes, marker, limit, + offset, sort_keys, sort_dirs) + + def manage_existing(self, volume, existing_ref): + """Brings an existing backend storage object under Cinder management. + + Rename the backend storage object so that it matches the, + volume['name'] which is how drivers traditionally map between a + cinder volume and the associated backend storage object. + """ + LOG.debug('manage_existing Start.') + + xml = self._cli.view_all(self._properties['ismview_path']) + pools, lds, ldsets, used_ldns, hostports, max_ld_count = ( + self.configs(xml)) + + newname, oldname = self._reference_to_ldname('volume', + volume, + existing_ref) + if self.check_accesscontrol(ldsets, lds[oldname]): + reason = _('Specified resource is already in-use.') + raise exception.ManageExistingInvalidReference( + existing_ref=existing_ref, reason=reason) + + if lds[oldname]['pool_num'] not in self._properties['pool_pools']: + reason = _('Volume type is unmatched.') + raise exception.ManageExistingVolumeTypeMismatch( + existing_ref=existing_ref, reason=reason) + + try: + self._cli.changeldname(None, newname, oldname) + except exception.CinderException as e: + LOG.warning('Unable to manage existing volume ' + '(reference = %(ref)s), (%(exception)s)', + {'ref': existing_ref['source-name'], 'exception': e}) + return + + def manage_existing_get_size(self, volume, existing_ref): + """Return size of volume to be managed by manage_existing.""" + LOG.debug('manage_existing_get_size Start.') + return self._manage_existing_get_size('volume', volume, existing_ref) + + def unmanage(self, volume): + """Removes the specified volume from Cinder management.""" + pass + + def get_manageable_snapshots(self, cinder_snapshots, marker, limit, offset, + sort_keys, sort_dirs): + """List snapshots on the backend available for management by Cinder.""" + LOG.debug('get_manageable_snapshots Start.') + return self._get_manageable_resources('snapshot', + cinder_snapshots, marker, limit, + offset, sort_keys, sort_dirs) + + def manage_existing_snapshot(self, snapshot, existing_ref): + """Brings an existing backend storage object under Cinder management. + + Rename the backend storage object so that it matches the + snapshot['name'] which is how drivers traditionally map between a + cinder snapshot and the associated backend storage object. + """ + LOG.debug('manage_existing_snapshots Start.') + + xml = self._cli.view_all(self._properties['ismview_path']) + pools, lds, ldsets, used_ldns, hostports, max_ld_count = ( + self.configs(xml)) + + newname, oldname = self._reference_to_ldname('snapshot', + snapshot, + existing_ref) + param_source = self.get_ldname(snapshot.volume_id, + self._properties['ld_name_format']) + ref_source = self._cli.get_bvname(oldname) + if param_source[3:] != ref_source: + reason = _('Snapshot source is unmatched.') + raise exception.ManageExistingInvalidReference( + existing_ref=existing_ref, reason=reason) + if (lds[oldname]['pool_num'] + not in self._properties['pool_backup_pools']): + reason = _('Volume type is unmatched.') + raise exception.ManageExistingVolumeTypeMismatch( + existing_ref=existing_ref, reason=reason) + + try: + self._cli.changeldname(None, newname, oldname) + except exception.CinderException as e: + LOG.warning('Unable to manage existing snapshot ' + '(reference = %(ref)s), (%(exception)s)', + {'ref': existing_ref['source-name'], 'exception': e}) + + def manage_existing_snapshot_get_size(self, snapshot, existing_ref): + """Return size of snapshot to be managed by manage_existing.""" + LOG.debug('manage_existing_snapshot_get_size Start.') + return self._manage_existing_get_size('snapshot', + snapshot, existing_ref) + + def unmanage_snapshot(self, snapshot): + """Removes the specified snapshot from Cinder management.""" + pass + class MStorageDSVDriver(MStorageDriver): """M-Series Storage Snapshot helper class.""" @@ -1522,7 +1724,7 @@ class MStorageDSVDriver(MStorageDriver): ldname = self.get_ldname(snapshot.volume_id, self._properties['ld_name_format']) if ldname not in lds: - LOG.debug('LD(MV) `%s` already unbound?', ldname) + LOG.debug('LD(BV) `%s` already unbound?', ldname) return # get SV name. diff --git a/releasenotes/notes/nec-manage-unmanage-06f9beb3004fc227.yaml b/releasenotes/notes/nec-manage-unmanage-06f9beb3004fc227.yaml new file mode 100644 index 00000000000..c568d531627 --- /dev/null +++ b/releasenotes/notes/nec-manage-unmanage-06f9beb3004fc227.yaml @@ -0,0 +1,4 @@ +--- +features: + - Support manage/unmanage volume and manage/unmanage snapshot + functions for the NEC volume driver.