]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
EMC VMAX - Extend Volume for VMAX3
authorHelen Walsh <helen.walsh@emc.com>
Mon, 23 Nov 2015 23:32:35 +0000 (23:32 +0000)
committerHelen Walsh <helen.walsh@emc.com>
Sun, 20 Dec 2015 22:03:30 +0000 (22:03 +0000)
The Extend Volume functionality for the VMAX3 is supported in EMC
Solutions Enabler 8.1.0.3 onward.

Change-Id: Ic83f491f47be016e8fbe15216f0410881f9a3b3e
Blueprint: vmax3-extend-volume

cinder/tests/unit/test_emc_vmax.py
cinder/volume/drivers/emc/emc_vmax_common.py
cinder/volume/drivers/emc/emc_vmax_fc.py
cinder/volume/drivers/emc/emc_vmax_iscsi.py
cinder/volume/drivers/emc/emc_vmax_provision_v3.py

index d42e9eff47584031e54b05acc46227970f6dd026..48470fcd795d7ca1d217a98ac5dec074deb6b945 100644 (file)
@@ -723,17 +723,40 @@ class FakeEcomConnection(object):
     def AssociatorNames(self, objectpath,
                         ResultClass='default', AssocClass='default'):
         result = None
+        if objectpath == 'point_to_storage_instance_names':
+            result = ['FirstStorageTierInstanceNames']
 
+        if ResultClass != 'default':
+            result = self.ResultClassHelper(ResultClass, objectpath)
+
+        if result is None and AssocClass != 'default':
+            result = self.AssocClassHelper(AssocClass, objectpath)
+        if result is None:
+            result = self._default_assocnames(objectpath)
+        return result
+
+    def AssocClassHelper(self, AssocClass, objectpath):
+        if AssocClass == 'CIM_HostedService':
+            result = self._assocnames_hostedservice()
+        elif AssocClass == 'CIM_AssociatedTierPolicy':
+            result = self._assocnames_assoctierpolicy()
+        elif AssocClass == 'CIM_OrderedMemberOfCollection':
+            result = self._enum_storagevolumes()
+        elif AssocClass == 'CIM_BindsTo':
+            result = self._assocnames_bindsto()
+        elif AssocClass == 'CIM_MemberOfCollection':
+            result = self._assocnames_memberofcollection()
+        else:
+            result = None
+        return result
+
+    def ResultClassHelper(self, ResultClass, objectpath):
         if ResultClass == 'EMC_LunMaskingSCSIProtocolController':
             result = self._assocnames_lunmaskctrl()
-        elif AssocClass == 'CIM_HostedService':
-            result = self._assocnames_hostedservice()
         elif ResultClass == 'CIM_TierPolicyServiceCapabilities':
             result = self._assocnames_policyCapabilities()
         elif ResultClass == 'Symm_TierPolicyRule':
             result = self._assocnames_policyrule()
-        elif AssocClass == 'CIM_AssociatedTierPolicy':
-            result = self._assocnames_assoctierpolicy()
         elif ResultClass == 'CIM_StoragePool':
             result = self._assocnames_storagepool()
         elif ResultClass == 'EMC_VirtualProvisioningPool':
@@ -756,8 +779,6 @@ class FakeEcomConnection(object):
             result = self._enum_repservcpbls()
         elif ResultClass == 'CIM_ReplicationGroup':
             result = self._enum_repgroups()
-        elif AssocClass == 'CIM_OrderedMemberOfCollection':
-            result = self._enum_storagevolumes()
         elif ResultClass == 'Symm_FCSCSIProtocolEndpoint':
             result = self._enum_fcscsiendpoint()
         elif ResultClass == 'Symm_SRPStoragePool':
@@ -774,12 +795,12 @@ class FakeEcomConnection(object):
             result = self._enum_maskingView()
         elif ResultClass == 'EMC_Meta':
             result = self._enum_metavolume()
-        elif AssocClass == 'CIM_BindsTo':
-            result = self._assocnames_bindsto()
-        elif AssocClass == 'CIM_MemberOfCollection':
-            result = self._assocnames_memberofcollection()
+        elif ResultClass == 'EMC_FrontEndSCSIProtocolController':
+            result = self._enum_maskingView()
+        elif ResultClass == 'CIM_TierPolicyRule':
+            result = self._assocnames_tierpolicy(objectpath)
         else:
-            result = self._default_assocnames(objectpath)
+            result = None
         return result
 
     def ReferenceNames(self, objectpath,
@@ -6092,6 +6113,68 @@ class EMCV3DriverTestCase(test.TestCase):
             self.data.remainingSLOCapacity)
         self.assertEqual(remainingSLOCapacityGb, remainingCapacityGb)
 
+    @mock.patch.object(
+        emc_vmax_utils.EMCVMAXUtils,
+        'get_volume_size',
+        return_value='2147483648')
+    def test_extend_volume(self, mock_volume_size):
+        newSize = '2'
+        self.driver.common._initial_setup = mock.Mock(
+            return_value=self.default_extraspec())
+        self.driver.extend_volume(self.data.test_volume_v3, newSize)
+
+    def test_extend_volume_smaller_size_exception(self):
+        test_local_volume = {'name': 'vol1',
+                             'size': 4,
+                             'volume_name': 'vol1',
+                             'id': 'vol1',
+                             'device_id': '1',
+                             'provider_auth': None,
+                             'project_id': 'project',
+                             'display_name': 'vol1',
+                             'display_description': 'test volume',
+                             'volume_type_id': 'abc',
+                             'provider_location': six.text_type(
+                                 self.data.provider_location),
+                             'status': 'available',
+                             'host': self.data.fake_host_v3,
+                             'NumberOfBlocks': 100,
+                             'BlockSize': self.data.block_size
+                             }
+        newSize = '2'
+        self.driver.common._initial_setup = mock.Mock(
+            return_value=self.default_extraspec())
+        self.assertRaises(
+            exception.VolumeBackendAPIException,
+            self.driver.extend_volume,
+            test_local_volume, newSize)
+
+    def test_extend_volume_exception(self):
+        common = self.driver.common
+        newsize = '2'
+        common._initial_setup = mock.Mock(return_value=None)
+        common._find_lun = mock.Mock(return_value=None)
+        self.assertRaises(
+            exception.VolumeBackendAPIException,
+            common.extend_volume,
+            self.data.test_volume, newsize)
+
+    def test_extend_volume_size_tally_exception(self):
+        common = self.driver.common
+        newsize = '2'
+        self.driver.common._initial_setup = mock.Mock(
+            return_value=self.data.extra_specs)
+        vol = {'SystemName': self.data.storage_system}
+        common._find_lun = mock.Mock(return_value=vol)
+        common._extend_v3_volume = mock.Mock(return_value=(0, vol))
+        common.utils.find_volume_instance = mock.Mock(
+            return_value='2147483648')
+        common.utils.get_volume_size = mock.Mock(return_value='2147483646')
+        self.assertRaises(
+            exception.VolumeBackendAPIException,
+            common.extend_volume,
+            self.data.test_volume, newsize)
+
     def _cleanup(self):
         bExists = os.path.exists(self.config_file_path)
         if bExists:
@@ -7029,3 +7112,63 @@ class EMCVMAXProvisionV3Test(test.TestCase):
         self.assertRaises(exception.VolumeBackendAPIException,
                           provisionv3.get_storage_pool_setting,
                           conn, storagePoolCapability, slo, workload)
+
+    def test_extend_volume_in_SG(self):
+        provisionv3 = self.driver.common.provisionv3
+        conn = FakeEcomConnection()
+        storageConfigService = {
+            'CreationClassName': 'Symm_ElementCompositionService',
+            'SystemName': 'SYMMETRIX+000195900551'}
+        theVolumeInstanceName = (
+            conn.EnumerateInstanceNames("EMC_StorageVolume")[0])
+        inVolumeInstanceName = (
+            conn.EnumerateInstanceNames("EMC_StorageVolume")[0])
+        volumeSize = 3
+
+        extraSpecs = {'volume_backend_name': 'GOLD_BE',
+                      'isV3': True}
+        job = {
+            'Job': {'InstanceID': '9999', 'status': 'success', 'type': None}}
+        conn.InvokeMethod = mock.Mock(return_value=(4096, job))
+        provisionv3.utils.wait_for_job_complete = mock.Mock(return_value=(
+            0, 'Success'))
+        volumeDict = {'classname': u'Symm_StorageVolume',
+                      'keybindings': EMCVMAXCommonData.keybindings}
+        provisionv3.get_volume_dict_from_job = (
+            mock.Mock(return_value=volumeDict))
+        result = provisionv3.extend_volume_in_SG(conn, storageConfigService,
+                                                 theVolumeInstanceName,
+                                                 inVolumeInstanceName,
+                                                 volumeSize, extraSpecs)
+        self.assertEqual(
+            ({'classname': u'Symm_StorageVolume',
+              'keybindings': {
+                  'CreationClassName': u'Symm_StorageVolume',
+                  'DeviceID': u'1',
+                  'SystemCreationClassName': u'Symm_StorageSystem',
+                  'SystemName': u'SYMMETRIX+000195900551'}}, 0), result)
+
+    def test_extend_volume_in_SG_with_Exception(self):
+        provisionv3 = self.driver.common.provisionv3
+        conn = FakeEcomConnection()
+        storageConfigService = {
+            'CreationClassName': 'Symm_ElementCompositionService',
+            'SystemName': 'SYMMETRIX+000195900551'}
+        theVolumeInstanceName = (
+            conn.EnumerateInstanceNames("EMC_StorageVolume")[0])
+        inVolumeInstanceName = (
+            conn.EnumerateInstanceNames("EMC_StorageVolume")[0])
+        volumeSize = 3
+
+        extraSpecs = {'volume_backend_name': 'GOLD_BE',
+                      'isV3': True}
+        job = {
+            'Job': {'InstanceID': '9999', 'status': 'success', 'type': None}}
+        conn.InvokeMethod = mock.Mock(return_value=(4096, job))
+        provisionv3.utils.wait_for_job_complete = mock.Mock(return_value=(
+            2, 'Failure'))
+        self.assertRaises(
+            exception.VolumeBackendAPIException,
+            provisionv3.extend_volume_in_SG, conn, storageConfigService,
+            theVolumeInstanceName, inVolumeInstanceName, volumeSize,
+            extraSpecs)
index 27b21c31095b23dd432624d57586298e1af9d116..d8c03151866996db73bf5871c7aec15d0f71b367 100644 (file)
@@ -519,10 +519,14 @@ class EMCVMAXCommon(object):
         additionalVolumeSize = self.utils.convert_gb_to_bits(
             additionalVolumeSize)
 
-        # This is V2
-        rc, modifiedVolumeDict = self._extend_composite_volume(
-            volumeInstance, volumeName, newSize, additionalVolumeSize,
-            extraSpecs)
+        if extraSpecs[ISV3]:
+            rc, modifiedVolumeDict = self._extend_v3_volume(
+                volumeInstance, volumeName, newSize, extraSpecs)
+        else:
+            # This is V2.
+            rc, modifiedVolumeDict = self._extend_composite_volume(
+                volumeInstance, volumeName, newSize, additionalVolumeSize,
+                extraSpecs)
 
         # Check the occupied space of the new extended volume.
         extendedVolumeInstance = self.utils.find_volume_instance(
@@ -4361,3 +4365,23 @@ class EMCVMAXCommon(object):
                             conn, ipendpointinstancename))
                     foundipaddresses.append(ipaddress)
         return foundipaddresses
+
+    def _extend_v3_volume(self, volumeInstance, volumeName, newSize,
+                          extraSpecs):
+        """Extends a VMAX3 volume.
+
+        :param volumeInstance: volume instance
+        :param volumeName: volume name
+        :param newSize: new size the volume will be increased to
+        :param extraSpecs: extra specifications
+        :returns: int -- return code
+        :returns: volumeDict
+        """
+        new_size_in_bits = int(self.utils.convert_gb_to_bits(newSize))
+        storageConfigService = self.utils.find_storage_configuration_service(
+            self.conn, volumeInstance['SystemName'])
+        volumeDict, rc = self.provisionv3.extend_volume_in_SG(
+            self.conn, storageConfigService, volumeInstance.path,
+            volumeName, new_size_in_bits, extraSpecs)
+
+        return rc, volumeDict
index cf33cc6fbe630524ffa82edc596e9936749a0161..690faadd68c42c24cfcdd2cedb828b551191e8b3 100644 (file)
@@ -47,6 +47,8 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver):
               - get_short_host_name needs to be called in find_device_number
                 (bug #1520635)
               - Proper error handling for invalid SLOs (bug #1512795)
+              - Extend Volume for VMAX3, SE8.1.0.3
+              https://blueprints.launchpad.net/cinder/+spec/vmax3-extend-volume
     """
 
     VERSION = "2.3.0"
index 878a268499d6a975447e579b9733acf6816b04de..97525de782f0cee356d3af3f5b4d849946d686e0 100644 (file)
@@ -55,6 +55,8 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver):
               - get_short_host_name needs to be called in find_device_number
                 (bug #1520635)
               - Proper error handling for invalid SLOs (bug #1512795)
+              - Extend Volume for VMAX3, SE8.1.0.3
+              https://blueprints.launchpad.net/cinder/+spec/vmax3-extend-volume
     """
 
     VERSION = "2.3.0"
index dc8442e1c6e5536a216a3ebb8fe09e26600c97e1..3c301930a6e20a84271cf937b32534b0c7e88f41 100644 (file)
@@ -676,3 +676,52 @@ class EMCVMAXProvisionV3(object):
         except KeyError:
             pass
         return remainingCapacityGb
+
+    def extend_volume_in_SG(
+            self, conn, storageConfigService, volumeInstanceName,
+            volumeName, volumeSize, extraSpecs):
+        """Extend a volume instance.
+
+        :param conn: connection the the ecom server
+        :param storageConfigservice: the storage configuration service
+        :param volumeInstanceName: the volume instance name
+        :param volumeName: the volume name (String)
+        :param volumeSize: the volume size
+        :param extraSpecs: additional info
+        :returns: volumeDict
+        :returns: int -- return code
+        :raises: VolumeBackendAPIException
+        """
+        startTime = time.time()
+
+        rc, job = conn.InvokeMethod(
+            'CreateOrModifyElementFromStoragePool',
+            storageConfigService, TheElement=volumeInstanceName,
+            Size=self.utils.get_num(volumeSize, '64'))
+
+        LOG.debug("Extend Volume: %(volumename)s. Return code: %(rc)lu.",
+                  {'volumename': volumeName,
+                   'rc': rc})
+
+        if rc != 0:
+            rc, error_desc = self.utils.wait_for_job_complete(conn, job,
+                                                              extraSpecs)
+            if rc != 0:
+                exceptionMessage = (_(
+                    "Error Extend Volume: %(volumeName)s. "
+                    "Return code: %(rc)lu.  Error: %(error)s.")
+                    % {'volumeName': volumeName,
+                       'rc': rc,
+                       'error': error_desc})
+                LOG.error(exceptionMessage)
+                raise exception.VolumeBackendAPIException(
+                    data=exceptionMessage)
+
+        LOG.debug("InvokeMethod CreateOrModifyElementFromStoragePool "
+                  "took: %(delta)s H:MM:SS.",
+                  {'delta': self.utils.get_time_delta(startTime,
+                                                      time.time())})
+
+        # Find the newly created volume.
+        volumeDict = self.get_volume_dict_from_job(conn, job['Job'])
+        return volumeDict, rc