]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Storwize volume manage/unmanage support
authorAvishay Traeger <avishay@gmail.com>
Tue, 4 Mar 2014 12:40:01 +0000 (14:40 +0200)
committerAvishay Traeger <avishay@gmail.com>
Tue, 4 Mar 2014 17:56:53 +0000 (19:56 +0200)
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
cinder/volume/drivers/ibm/storwize_svc/__init__.py
cinder/volume/drivers/ibm/storwize_svc/helpers.py
cinder/volume/drivers/ibm/storwize_svc/ssh.py

index 04b3be474cd5718b298d800d7c8c56ccc844c07c..35a3f8f76407a5e1889a5d773d66e6a3a2a3841f 100644 (file)
@@ -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):
index e15476cf865d661c521b12ae447546f45c088d30..1308f1c4c09bc8323bb163c41d833c108311fef1 100644 (file)
@@ -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': <uid of disk>}
+
+        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.
 
index 27141d4f7a391cba315799a312cb6ad1b4fc0204..691249e6c768896be19f1738778d83c870ad6921 100644 (file)
@@ -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])
index e05cfaf975a5a63722fc1e02050f856730260974..d4a5e4a8d5b0d2723a21f3b194bbbe8dac755630 100644 (file)
@@ -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)