From 8bdbb24cc77ef77855b3db5f94e21cb45a3051b9 Mon Sep 17 00:00:00 2001 From: Avishay Traeger Date: Tue, 4 Mar 2014 14:40:01 +0200 Subject: [PATCH] Storwize volume manage/unmanage support IBM Storwize/SVC support for managing and unmanaging volumes. Partially Implements: blueprint add-export-import-volumes Change-Id: Ie90d5c0a18fcfcf7208990abf3476f432ea863c3 --- cinder/tests/test_storwize_svc.py | 147 +++++++++++++++++- .../drivers/ibm/storwize_svc/__init__.py | 54 ++++++- .../drivers/ibm/storwize_svc/helpers.py | 30 ++++ cinder/volume/drivers/ibm/storwize_svc/ssh.py | 9 ++ 4 files changed, 237 insertions(+), 3 deletions(-) diff --git a/cinder/tests/test_storwize_svc.py b/cinder/tests/test_storwize_svc.py index 04b3be474..35a3f8f76 100644 --- a/cinder/tests/test_storwize_svc.py +++ b/cinder/tests/test_storwize_svc.py @@ -654,7 +654,8 @@ port_speed!N/A for vol in self._volumes_list.itervalues(): if (('filtervalue' not in kwargs) or - (kwargs['filtervalue'] == 'name=' + vol['name'])): + (kwargs['filtervalue'] == 'name=' + vol['name']) or + (kwargs['filtervalue'] == 'vdisk_UID=' + vol['uid'])): fcmap_info = self._get_fcmap_info(vol['name']) if 'bytes' in kwargs: @@ -742,7 +743,6 @@ port_speed!N/A if 'delim' in kwargs: for index in range(len(rows)): rows[index] = kwargs['delim'].join(rows[index]) - return ('%s' % '\n'.join(rows), '') def _cmd_lsiogrp(self, **kwargs): @@ -1345,6 +1345,10 @@ port_speed!N/A if key == 'warning': vol['warning'] = value.rstrip('%') continue + if key == 'name': + vol['name'] = value + del self._volumes_list[vol_name] + self._volumes_list[value] = vol if key in params: vol[key] = value else: @@ -2493,6 +2497,145 @@ class StorwizeSVCDriverTestCase(test.TestCase): self.assertEqual(term_data, term_ret) + def _get_vdisk_uid(self, vdisk_name): + """Return vdisk_UID for given vdisk. + + Given a vdisk by name, performs an lvdisk command that extracts + the vdisk_UID parameter and returns it. + Returns None if the specified vdisk does not exist. + """ + vdisk_properties, err = self.sim._cmd_lsvdisk(obj=vdisk_name, + delim='!') + + # Iterate through each row until we find the vdisk_UID entry + for row in vdisk_properties.split('\n'): + words = row.split('!') + if words[0] == 'vdisk_UID': + return words[1] + return None + + def _create_volume_and_return_uid(self, volume_name): + """Creates a volume and returns its UID. + + Creates a volume with the specified name, and returns the UID that + the Storwize controller allocated for it. We do this by executing a + create_volume and then calling into the simulator to perform an + lsvdisk directly. + """ + volume = self._generate_vol_info(None, None) + self.driver.create_volume(volume) + + return (volume, self._get_vdisk_uid(volume['name'])) + + def test_manage_existing_bad_ref(self): + """Error on manage with bad reference. + + This test case attempts to manage an existing volume but passes in + a bad reference that the Storwize driver doesn't understand. We + expect an exception to be raised. + """ + volume = self._generate_vol_info(None, None) + ref = {} + self.assertRaises(exception.ManageExistingInvalidReference, + self.driver.manage_existing_get_size, volume, ref) + + def test_manage_existing_bad_uid(self): + """Error when the specified UUID does not exist.""" + volume = self._generate_vol_info(None, None) + ref = {'vdisk_UID': 'bad_uid'} + self.assertRaises(exception.ManageExistingInvalidReference, + self.driver.manage_existing_get_size, volume, ref) + pass + + def test_manage_existing_good_uid_not_mapped(self): + """Tests managing a volume with no mappings. + + This test case attempts to manage an existing volume by UID, and + we expect it to succeed. We verify that the backend volume was + renamed to have the name of the Cinder volume that we asked for it to + be associated with. + """ + + # Create a volume as a way of getting a vdisk created, and find out the + # UID of that vdisk. + volume, uid = self._create_volume_and_return_uid('manage_test') + + # Descriptor of the Cinder volume that we want to own the vdisk + # refrerenced by uid. + new_volume = self._generate_vol_info(None, None) + + # Submit the request to manage it. + ref = {'vdisk_UID': uid} + size = self.driver.manage_existing_get_size(new_volume, ref) + self.assertEqual(size, 10) + self.driver.manage_existing(new_volume, ref) + + # Assert that there is a disk named after the new volume that has the + # ID that we passed in, indicating that the disk has been renamed. + uid_of_new_volume = self._get_vdisk_uid(new_volume['name']) + self.assertEqual(uid, uid_of_new_volume) + + def test_manage_existing_good_uid_mapped(self): + """Tests managing a mapped volume with no override. + + This test case attempts to manage an existing volume by UID, but + the volume is mapped to a host, so we expect to see an exception + raised. + """ + # Create a volume as a way of getting a vdisk created, and find out the + # UUID of that vdisk. + volume, uid = self._create_volume_and_return_uid('manage_test') + + # Map a host to the disk + conn = {'initiator': u'unicode:initiator3', + 'ip': '10.10.10.12', + 'host': u'unicode.foo}.bar}.baz'} + self.driver.initialize_connection(volume, conn) + + # Descriptor of the Cinder volume that we want to own the vdisk + # refrerenced by uid. + volume = self._generate_vol_info(None, None) + ref = {'vdisk_UID': uid} + + # Attempt to manage this disk, and except an exception beause the + # volume is already mapped. + self.assertRaises(exception.ManageExistingInvalidReference, + self.driver.manage_existing_get_size, volume, ref) + + def test_manage_existing_good_uid_mapped_with_override(self): + """Tests managing a mapped volume with override. + + This test case attempts to manage an existing volume by UID, when it + it already mapped to a host, but the ref specifies that this is OK. + We verify that the backend volume was renamed to have the name of the + Cinder volume that we asked for it to be associated with. + """ + # Create a volume as a way of getting a vdisk created, and find out the + # UUID of that vdisk. + volume, uid = self._create_volume_and_return_uid('manage_test') + + # Map a host to the disk + conn = {'initiator': u'unicode:initiator3', + 'ip': '10.10.10.12', + 'host': u'unicode.foo}.bar}.baz'} + self.driver.initialize_connection(volume, conn) + + # Descriptor of the Cinder volume that we want to own the vdisk + # refrerenced by uid. + new_volume = self._generate_vol_info(None, None) + + # Submit the request to manage it, specifying that it is OK to + # manage a volume that is already attached. + ref = {'vdisk_UID': uid, 'manage_if_in_use': True} + size = self.driver.manage_existing_get_size(new_volume, ref) + self.assertEqual(size, 10) + self.driver.manage_existing(new_volume, ref) + + # Assert that there is a disk named after the new volume that has the + # ID that we passed in, indicating that the disk has been renamed. + uid_of_new_volume = self._get_vdisk_uid(new_volume['name']) + self.assertEqual(uid, uid_of_new_volume) + class CLIResponseTestCase(test.TestCase): def test_empty(self): diff --git a/cinder/volume/drivers/ibm/storwize_svc/__init__.py b/cinder/volume/drivers/ibm/storwize_svc/__init__.py index e15476cf8..1308f1c4c 100644 --- a/cinder/volume/drivers/ibm/storwize_svc/__init__.py +++ b/cinder/volume/drivers/ibm/storwize_svc/__init__.py @@ -34,6 +34,7 @@ Limitations: """ +import math from oslo.config import cfg from cinder import context @@ -116,9 +117,10 @@ class StorwizeSVCDriver(san.SanDriver): lsfabric, clear unused data from connections, ensure matching WWPNs by comparing lower case 1.2.4 - Fix bug #1278035 (async migration/retype) + 1.2.5 - Added support for manage_existing (unmanage is inherited) """ - VERSION = "1.2.4" + VERSION = "1.2.5" VDISKCOPYOPS_INTERVAL = 600 def __init__(self, *args, **kwargs): @@ -747,6 +749,56 @@ class StorwizeSVCDriver(san.SanDriver): 'host': host['host']}) return True + def manage_existing(self, volume, ref): + """Manages an existing vdisk. + + Renames the vdisk to match the expected name for the volume. + Error checking done by manage_existing_get_size is not repeated - + if we got here then we have a vdisk that isn't in use (or we don't + care if it is in use. + """ + vdisk = self._helpers.vdisk_by_uid(ref['vdisk_UID']) + if vdisk is None: + reason = _('No vdisk with the specified vdisk_UID.') + raise exception.ManageExistingInvalidReference(existing_ref=ref, + reason=reason) + self._helpers.rename_vdisk(vdisk['name'], volume['name']) + + def manage_existing_get_size(self, volume, ref): + """Return size of an existing LV for manage_existing. + + existing_ref is a dictionary of the form: + {'vdisk_UID': } + + Optional elements are: + 'manage_if_in_use': True/False (default is False) + If set to True, a volume will be managed even if it is currently + attached to a host system. + """ + + # Check that the reference is valid + if 'vdisk_UID' not in ref: + reason = _('Reference must contain vdisk_UID element.') + raise exception.ManageExistingInvalidReference(existing_ref=ref, + reason=reason) + + # Check for existence of the vdisk + vdisk = self._helpers.vdisk_by_uid(ref['vdisk_UID']) + if vdisk is None: + reason = _('No vdisk with the specified vdisk_UID.') + raise exception.ManageExistingInvalidReference(existing_ref=ref, + reason=reason) + + # Check if the disk is in use, if we need to. + manage_if_in_use = ref.get('manage_if_in_use', False) + if (not manage_if_in_use and + self._helpers.is_vdisk_in_use(vdisk['name'])): + reason = _('The specified vdisk is mapped to a host.') + raise exception.ManageExistingInvalidReference(existing_ref=ref, + reason=reason) + + return int(math.ceil(float(vdisk['capacity']) / units.GiB)) + def get_volume_stats(self, refresh=False): """Get volume stats. diff --git a/cinder/volume/drivers/ibm/storwize_svc/helpers.py b/cinder/volume/drivers/ibm/storwize_svc/helpers.py index 27141d4f7..691249e6c 100644 --- a/cinder/volume/drivers/ibm/storwize_svc/helpers.py +++ b/cinder/volume/drivers/ibm/storwize_svc/helpers.py @@ -746,3 +746,33 @@ class StorwizeHelpers(object): for key in changes: self.ssh.chvdisk(vdisk, ['-' + key, opts[key]]) + + def vdisk_by_uid(self, vdisk_uid): + """Returns the properties of the vdisk with the specified UID. + + Returns None if no such disk exists. + """ + + vdisks = self.ssh.lsvdisks_from_filter('vdisk_UID', vdisk_uid) + + if len(vdisks) == 0: + return None + + if len(vdisks) != 1: + msg = (_('Expected single vdisk returned from lsvdisk when ' + 'filtering on vdisk_UID. %{count}s were returned.') % + {'count': len(vdisks)}) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + vdisk = vdisks.result[0] + + return self.ssh.lsvdisk(vdisk['name']) + + def is_vdisk_in_use(self, vdisk): + """Returns True if the specified vdisk is mapped to at least 1 host.""" + resp = self.ssh.lsvdiskhostmap(vdisk) + return len(resp) != 0 + + def rename_vdisk(self, vdisk, new_name): + self.ssh.chvdisk(vdisk, ['-name', new_name]) diff --git a/cinder/volume/drivers/ibm/storwize_svc/ssh.py b/cinder/volume/drivers/ibm/storwize_svc/ssh.py index e05cfaf97..d4a5e4a8d 100644 --- a/cinder/volume/drivers/ibm/storwize_svc/ssh.py +++ b/cinder/volume/drivers/ibm/storwize_svc/ssh.py @@ -218,6 +218,15 @@ class StorwizeSSH(object): LOG.error(msg) raise exception.VolumeBackendAPIException(data=msg) + def lsvdisks_from_filter(self, filter_name, value): + """Performs an lsvdisk command, filtering the results as specified. + + Returns an iterable for all matching vdisks. + """ + ssh_cmd = ['svcinfo', 'lsvdisk', '-bytes', '-delim', '!', + '-filtervalue', '%s=%s' % (filter_name, value)] + return self.run_ssh_info(ssh_cmd, with_header=True) + def chvdisk(self, vdisk, params): ssh_cmd = ['svctask', 'chvdisk'] + params + [vdisk] self.run_ssh_assert_no_output(ssh_cmd) -- 2.45.2