From 27a1f66773755456a1bb135a940ba2a835c121a3 Mon Sep 17 00:00:00 2001 From: Daniel Wilson Date: Wed, 22 Apr 2015 14:07:02 -0700 Subject: [PATCH] Enable use of filter_function in PureISCIDriver MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Adding filter_function to capabilities of PureISCIDriver. Return total_volumes as well for use with filter_function. This will open a way for admins to prevent Cinder from scheduling volumes on a Pure backend once it has reached the array’s limits. The primary use case for this is volume limits. An example of a filter_function that will prevent this issue would be: filter_function="capabilities.total_volumes < 500” This will prevent any issues with Purity versions that are limited to 500 volumes. Without that filter_function the scheduler will continue to try and schedule volume creation if space is available instead of selecting another backend. The end result would be volume creation fails and the volume is in an error state even though there are backends available that could have handled it. DocImpact: This means the PureISCSIDriver will now respect the filter_function config option. Special Pure specific fields to filter off of need to be added to the driver config manual. Change-Id: I1368b74f79eb8c8560cf86df27f9af9541963a87 Closes-Bug: 1437082 --- cinder/tests/unit/test_pure.py | 34 +++++++++++++++++++++++++--------- cinder/volume/drivers/pure.py | 9 ++++++--- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/cinder/tests/unit/test_pure.py b/cinder/tests/unit/test_pure.py index 7b3e22e43..1208e8870 100644 --- a/cinder/tests/unit/test_pure.py +++ b/cinder/tests/unit/test_pure.py @@ -656,9 +656,12 @@ class PureISCSIDriverTestCase(test.TestCase): self.assertFalse(self.array.list_host_connections.called) self.assertFalse(self.array.delete_host.called) + @mock.patch(DRIVER_OBJ + ".get_filter_function", autospec=True) @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 + def test_get_volume_stats(self, mock_space, mock_filter): + filter_function = "capabilities.total_volumes < 10" + mock_space.return_value = (PROVISIONED_CAPACITY * units.Gi, 100) + mock_filter.return_value = filter_function self.assertEqual(self.driver.get_volume_stats(), {}) self.array.get.return_value = SPACE_INFO result = {"volume_backend_name": VOLUME_BACKEND_NAME, @@ -672,15 +675,20 @@ class PureISCSIDriverTestCase(test.TestCase): "thin_provisioning_support": True, "provisioned_capacity": PROVISIONED_CAPACITY, "max_over_subscription_ratio": (PROVISIONED_CAPACITY / - USED_SPACE) + USED_SPACE), + "total_volumes": 100, + "filter_function": filter_function } 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_filter_function", autospec=True) @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 + def test_get_volume_stats_empty_array(self, mock_space, mock_filter): + filter_function = "capabilities.total_volumes < 10" + mock_space.return_value = (PROVISIONED_CAPACITY * units.Gi, 100) + mock_filter.return_value = filter_function self.assertEqual(self.driver.get_volume_stats(), {}) self.array.get.return_value = SPACE_INFO_EMPTY result = {"volume_backend_name": VOLUME_BACKEND_NAME, @@ -693,15 +701,21 @@ class PureISCSIDriverTestCase(test.TestCase): "consistencygroup_support": True, "thin_provisioning_support": True, "provisioned_capacity": PROVISIONED_CAPACITY, - "max_over_subscription_ratio": DEFAULT_OVER_SUBSCRIPTION + "max_over_subscription_ratio": DEFAULT_OVER_SUBSCRIPTION, + "total_volumes": 100, + "filter_function": filter_function } 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_filter_function", autospec=True) @mock.patch(DRIVER_OBJ + "._get_provisioned_space", autospec=True) - def test_get_volume_stats_nothing_provisioned(self, mock_space): - mock_space.return_value = 0 + def test_get_volume_stats_nothing_provisioned(self, mock_space, + mock_filter): + filter_function = "capabilities.total_volumes < 10" + mock_space.return_value = (0, 0) + mock_filter.return_value = filter_function self.assertEqual(self.driver.get_volume_stats(), {}) self.array.get.return_value = SPACE_INFO result = {"volume_backend_name": VOLUME_BACKEND_NAME, @@ -714,7 +728,9 @@ class PureISCSIDriverTestCase(test.TestCase): "consistencygroup_support": True, "thin_provisioning_support": True, "provisioned_capacity": 0, - "max_over_subscription_ratio": DEFAULT_OVER_SUBSCRIPTION + "max_over_subscription_ratio": DEFAULT_OVER_SUBSCRIPTION, + "total_volumes": 0, + "filter_function": filter_function } 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 c35b7df1e..5cc6c453a 100644 --- a/cinder/volume/drivers/pure.py +++ b/cinder/volume/drivers/pure.py @@ -465,7 +465,8 @@ class PureISCSIDriver(san.SanISCSIDriver): 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 + prov_space, total_vols = self._get_provisioned_space() + provisioned_space = float(prov_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 @@ -485,14 +486,16 @@ class PureISCSIDriver(san.SanISCSIDriver): "consistencygroup_support": True, "thin_provisioning_support": True, "provisioned_capacity": provisioned_space, - "max_over_subscription_ratio": thin_provisioning + "max_over_subscription_ratio": thin_provisioning, + "total_volumes": total_vols, + "filter_function": self.get_filter_function() } 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) + return sum(item["size"] for item in volumes), len(volumes) def extend_volume(self, volume, new_size): """Extend volume to new_size.""" -- 2.45.2