From d469062417bd953207e74b6131e4e80b672fff10 Mon Sep 17 00:00:00 2001 From: Daniel Wilson Date: Thu, 19 Feb 2015 10:35:05 -0800 Subject: [PATCH] Over subscription for Pure Storage iSCSI driver. Add support for over subscription in Pure Storage iSCSI driver. Implements: blueprint pure-iscsi-over-subscription-thin-provisioning Change-Id: Ia9de828ad718c0790f4f04ee2a2f1eb4c3fe2788 --- cinder/tests/test_pure.py | 69 +++++++++++++++++++++++++++++++---- cinder/volume/drivers/pure.py | 31 +++++++++++++--- 2 files changed, 86 insertions(+), 14 deletions(-) diff --git a/cinder/tests/test_pure.py b/cinder/tests/test_pure.py index fb6ba618e8d..52c4732eb6e 100644 --- a/cinder/tests/test_pure.py +++ b/cinder/tests/test_pure.py @@ -103,11 +103,16 @@ PORTS_WITHOUT = [NON_ISCSI_PORT] VOLUME_CONNECTIONS = [{"host": "h1", "name": VOLUME["name"] + "-cinder"}, {"host": "h2", "name": VOLUME["name"] + "-cinder"}, ] -TOTAL_SPACE = 50.0 -FREE_SPACE = 32.1 -SPACE_INFO = {"capacity": TOTAL_SPACE * units.Gi, - "total": (TOTAL_SPACE - FREE_SPACE) * units.Gi, +TOTAL_CAPACITY = 50.0 +USED_SPACE = 32.1 +PROVISIONED_CAPACITY = 70.0 +DEFAULT_OVER_SUBSCRIPTION = 20 +SPACE_INFO = {"capacity": TOTAL_CAPACITY * units.Gi, + "total": USED_SPACE * units.Gi } +SPACE_INFO_EMPTY = {"capacity": TOTAL_CAPACITY * units.Gi, + "total": 0 + } class FakePureStorageHTTPError(Exception): @@ -570,17 +575,65 @@ class PureISCSIDriverTestCase(test.TestCase): self.assertFalse(self.array.list_host_connections.called) self.assertFalse(self.array.delete_host.called) - def test_get_volume_stats(self): + @mock.patch(DRIVER_OBJ + "._get_provisioned_space", autospec=True) + def test_get_volume_stats(self, mock_space): + mock_space.return_value = PROVISIONED_CAPACITY * units.Gi self.assertEqual(self.driver.get_volume_stats(), {}) self.array.get.return_value = SPACE_INFO result = {"volume_backend_name": VOLUME_BACKEND_NAME, "vendor_name": "Pure Storage", "driver_version": self.driver.VERSION, "storage_protocol": "iSCSI", - "total_capacity_gb": TOTAL_SPACE, - "free_capacity_gb": FREE_SPACE, + "total_capacity_gb": TOTAL_CAPACITY, + "free_capacity_gb": TOTAL_CAPACITY - USED_SPACE, "reserved_percentage": 0, - "consistencygroup_support": True + "consistencygroup_support": True, + "thin_provisioning_support": True, + "provisioned_capacity": PROVISIONED_CAPACITY, + "max_over_subscription_ratio": (PROVISIONED_CAPACITY / + USED_SPACE) + } + real_result = self.driver.get_volume_stats(refresh=True) + self.assertDictMatch(result, real_result) + self.assertDictMatch(result, self.driver._stats) + + @mock.patch(DRIVER_OBJ + "._get_provisioned_space", autospec=True) + def test_get_volume_stats_empty_array(self, mock_space): + mock_space.return_value = PROVISIONED_CAPACITY * units.Gi + self.assertEqual(self.driver.get_volume_stats(), {}) + self.array.get.return_value = SPACE_INFO_EMPTY + result = {"volume_backend_name": VOLUME_BACKEND_NAME, + "vendor_name": "Pure Storage", + "driver_version": self.driver.VERSION, + "storage_protocol": "iSCSI", + "total_capacity_gb": TOTAL_CAPACITY, + "free_capacity_gb": TOTAL_CAPACITY, + "reserved_percentage": 0, + "consistencygroup_support": True, + "thin_provisioning_support": True, + "provisioned_capacity": PROVISIONED_CAPACITY, + "max_over_subscription_ratio": DEFAULT_OVER_SUBSCRIPTION + } + real_result = self.driver.get_volume_stats(refresh=True) + self.assertDictMatch(result, real_result) + self.assertDictMatch(result, self.driver._stats) + + @mock.patch(DRIVER_OBJ + "._get_provisioned_space", autospec=True) + def test_get_volume_stats_nothing_provisioned(self, mock_space): + mock_space.return_value = 0 + self.assertEqual(self.driver.get_volume_stats(), {}) + self.array.get.return_value = SPACE_INFO + result = {"volume_backend_name": VOLUME_BACKEND_NAME, + "vendor_name": "Pure Storage", + "driver_version": self.driver.VERSION, + "storage_protocol": "iSCSI", + "total_capacity_gb": TOTAL_CAPACITY, + "free_capacity_gb": TOTAL_CAPACITY - USED_SPACE, + "reserved_percentage": 0, + "consistencygroup_support": True, + "thin_provisioning_support": True, + "provisioned_capacity": 0, + "max_over_subscription_ratio": DEFAULT_OVER_SUBSCRIPTION } real_result = self.driver.get_volume_stats(refresh=True) self.assertDictMatch(result, real_result) diff --git a/cinder/volume/drivers/pure.py b/cinder/volume/drivers/pure.py index 190a514f731..af87a249293 100644 --- a/cinder/volume/drivers/pure.py +++ b/cinder/volume/drivers/pure.py @@ -99,7 +99,7 @@ def _generate_purity_host_name(name): class PureISCSIDriver(san.SanISCSIDriver): """Performs volume management on Pure Storage FlashArray.""" - VERSION = "2.0.3" + VERSION = "2.0.4" SUPPORTED_REST_API_VERSIONS = ['1.2', '1.3', '1.4'] @@ -382,19 +382,38 @@ class PureISCSIDriver(san.SanISCSIDriver): def _update_stats(self): """Set self._stats with relevant information.""" info = self._array.get(space=True) - total = float(info["capacity"]) / units.Gi - free = float(info["capacity"] - info["total"]) / units.Gi + total_capacity = float(info["capacity"]) / units.Gi + used_space = float(info["total"]) / units.Gi + free_space = float(total_capacity - used_space) + provisioned_space = float(self._get_provisioned_space()) / units.Gi + # If array is empty we can not calculate a max oversubscription ratio. + # In this case we choose 20 as a default value for the ratio. Once + # some volumes are actually created and some data is stored on the + # array a much more accurate number will be presented based on current + # usage. + if used_space == 0 or provisioned_space == 0: + thin_provisioning = 20 + else: + thin_provisioning = provisioned_space / used_space data = {"volume_backend_name": self._backend_name, "vendor_name": "Pure Storage", "driver_version": self.VERSION, "storage_protocol": "iSCSI", - "total_capacity_gb": total, - "free_capacity_gb": free, + "total_capacity_gb": total_capacity, + "free_capacity_gb": free_space, "reserved_percentage": 0, - "consistencygroup_support": True + "consistencygroup_support": True, + "thin_provisioning_support": True, + "provisioned_capacity": provisioned_space, + "max_over_subscription_ratio": thin_provisioning } self._stats = data + def _get_provisioned_space(self): + """Sum up provisioned size of all volumes on array""" + volumes = self._array.list_volumes(pending=True) + return sum(item["size"] for item in volumes) + def extend_volume(self, volume, new_size): """Extend volume to new_size.""" LOG.debug("Enter PureISCSIDriver.extend_volume.")