diff --git a/cinder/tests/unit/volume/drivers/dell_emc/scaleio/__init__.py b/cinder/tests/unit/volume/drivers/dell_emc/scaleio/__init__.py index c5c035fe17a..aab8161e0bb 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/scaleio/__init__.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/scaleio/__init__.py @@ -65,11 +65,13 @@ class TestScaleIODriver(test.TestCase): Valid='0', Invalid='1', BadStatus='2', + ValidVariant='3', )) __RESPONSE_MODE_NAMES = { '0': 'Valid', '1': 'Invalid', '2': 'BadStatus', + '3': 'ValidVariant', } BAD_STATUS_RESPONSE = mocks.MockHTTPSResponse( diff --git a/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_misc.py b/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_misc.py index f4f8fc37b5b..57ca5ae7877 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_misc.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_misc.py @@ -154,6 +154,55 @@ class TestMisc(scaleio.TestScaleIODriver): self.driver.storage_pools = self.STORAGE_POOLS self.driver.get_volume_stats(True) + def _setup_valid_variant_property(self, property): + """Setup valid response that returns a variety of property name + + """ + self.HTTPS_MOCK_RESPONSES = { + self.RESPONSE_MODE.ValidVariant: { + 'types/Domain/instances/getByName::' + + self.domain_name_enc: '"{}"'.format(self.DOMAIN_NAME).encode( + 'ascii', + 'ignore' + ), + 'types/Pool/instances/getByName::{},{}'.format( + self.DOMAIN_NAME, + self.POOL_NAME + ): '"{}"'.format(self.POOL_NAME).encode('ascii', 'ignore'), + 'types/StoragePool/instances/action/querySelectedStatistics': { + '"{}"'.format(self.POOL_NAME): { + 'capacityAvailableForVolumeAllocationInKb': 5000000, + 'capacityLimitInKb': 16000000, + 'spareCapacityInKb': 6000000, + 'thickCapacityInUseInKb': 266, + property: 0, + }, + }, + 'instances/Volume::{}/action/setVolumeName'.format( + self.volume['provider_id']): + self.new_volume['provider_id'], + 'instances/Volume::{}/action/setVolumeName'.format( + self.new_volume['provider_id']): + self.volume['provider_id'], + 'version': '"{}"'.format('2.0.1'), + } + } + + def test_get_volume_stats_with_varying_properties(self): + """Test getting volume stats with various property names + + In SIO 3.0, a property was renamed. + The change is backwards compatible for now but this tests + ensures that the driver is tolerant of that change + """ + self.driver.storage_pools = self.STORAGE_POOLS + self._setup_valid_variant_property("thinCapacityAllocatedInKb") + self.set_https_response_mode(self.RESPONSE_MODE.ValidVariant) + self.driver.get_volume_stats(True) + self._setup_valid_variant_property("nonexistentProperty") + self.set_https_response_mode(self.RESPONSE_MODE.ValidVariant) + self.driver.get_volume_stats(True) + @mock.patch( 'cinder.volume.drivers.dell_emc.scaleio.driver.ScaleIODriver.' '_rename_volume', diff --git a/cinder/volume/drivers/dell_emc/scaleio/driver.py b/cinder/volume/drivers/dell_emc/scaleio/driver.py index ffaabea3297..5d14cefcfd6 100644 --- a/cinder/volume/drivers/dell_emc/scaleio/driver.py +++ b/cinder/volume/drivers/dell_emc/scaleio/driver.py @@ -141,6 +141,8 @@ class ScaleIODriver(driver.VolumeDriver): self.server_password = self.configuration.san_password self.server_token = None self.server_api_version = self.configuration.sio_server_api_version + # list of statistics/properties to query from SIO + self.statisticProperties = None self.verify_server_certificate = ( self.configuration.sio_verify_server_certificate) self.server_certificate_path = None @@ -264,6 +266,39 @@ class ScaleIODriver(driver.VolumeDriver): "deprecated and will be removed in a future version")) versionutils.report_deprecated_feature(LOG, msg) + def _get_queryable_statistics(self, sio_type, sio_id): + if self.statisticProperties is None: + self.statisticProperties = [ + "capacityAvailableForVolumeAllocationInKb", + "capacityLimitInKb", "spareCapacityInKb", + "thickCapacityInUseInKb"] + # version 2.0 of SIO introduced thin volumes + if self._version_greater_than_or_equal( + self._get_server_api_version(), + "2.0.0"): + # check to see if thinCapacityAllocatedInKb is valid + # needed due to non-backwards compatible API + req_vars = {'server_ip': self.server_ip, + 'server_port': self.server_port, + 'sio_type': sio_type} + request = ("https://%(server_ip)s:%(server_port)s" + "/api/types/%(sio_type)s/instances/action/" + "querySelectedStatistics") % req_vars + params = {'ids': [sio_id], + 'properties': ["thinCapacityAllocatedInKb"]} + r, response = self._execute_scaleio_post_request(params, + request) + if r.status_code == http_client.OK: + # is it valid, use it + self.statisticProperties.append( + "thinCapacityAllocatedInKb") + else: + # it is not valid, assume use of thinCapacityAllocatedInKm + self.statisticProperties.append( + "thinCapacityAllocatedInKm") + + return self.statisticProperties + def _find_storage_pool_id_from_storage_type(self, storage_type): # Default to what was configured in configuration file if not defined. return storage_type.get(STORAGE_POOL_ID, @@ -922,20 +957,9 @@ class ScaleIODriver(driver.VolumeDriver): request = ("https://%(server_ip)s:%(server_port)s" "/api/types/StoragePool/instances/action/" "querySelectedStatistics") % req_vars - # SIO version 2+ added a property... - if self._version_greater_than_or_equal( - self._get_server_api_version(), - "2.0.0"): - # The 'Km' in thinCapacityAllocatedInKm is a bug in REST API - params = {'ids': [pool_id], 'properties': [ - "capacityAvailableForVolumeAllocationInKb", - "capacityLimitInKb", "spareCapacityInKb", - "thickCapacityInUseInKb", "thinCapacityAllocatedInKm"]} - else: - params = {'ids': [pool_id], 'properties': [ - "capacityAvailableForVolumeAllocationInKb", - "capacityLimitInKb", "spareCapacityInKb", - "thickCapacityInUseInKb"]} + + props = self._get_queryable_statistics("StoragePool", pool_id) + params = {'ids': [pool_id], 'properties': props} r, response = self._execute_scaleio_post_request(params, request) LOG.info("Query capacity stats response: %s.", response) @@ -949,16 +973,23 @@ class ScaleIODriver(driver.VolumeDriver): # to 8 GB granularity in backend free_capacity_gb = ( res['capacityAvailableForVolumeAllocationInKb'] / units.Mi) + thin_capacity_allocated = 0 + # some versions of the API had a typo in the response + try: + thin_capacity_allocated = res['thinCapacityAllocatedInKm'] + except (TypeError, KeyError): + pass + # some versions of the API respond without a typo + try: + thin_capacity_allocated = res['thinCapacityAllocatedInKb'] + except (TypeError, KeyError): + pass + # Divide by two because ScaleIO creates a copy for each volume - if self._version_greater_than_or_equal( - self._get_server_api_version(), - "2.0.0"): - provisioned_capacity = ( - ((res['thickCapacityInUseInKb'] + - res['thinCapacityAllocatedInKm']) / 2) / units.Mi) - else: - provisioned_capacity = ( - (res['thickCapacityInUseInKb'] / 2) / units.Mi) + provisioned_capacity = ( + ((res['thickCapacityInUseInKb'] + + thin_capacity_allocated) / 2) / units.Mi) + LOG.info("free capacity of pool %(pool)s is: %(free)s, " "total capacity: %(total)s, " "provisioned capacity: %(prov)s",