From 55857055fed82d9b599d0485cd980ee312d00269 Mon Sep 17 00:00:00 2001 From: Patrick East Date: Fri, 5 Dec 2014 10:18:46 -0800 Subject: [PATCH] Add support for Purity Protection Groups to PureISCSIDriver We will be needing access to the Protection Group features of Purity to enable features like Replication and Consistency Groups. To do this we need to add new helpers for the FlashArray object for the required REST API methods. DocImpact: To bring in pgroup support we will need to bump up the required REST API version to 1.2+ and remove support for 1.0 and 1.1 Implements: blueprint pure-iscsi-add-pgroup-support Change-Id: Ia446615e0c03b91fc1da75489855e77c29639efa --- cinder/tests/test_pure.py | 68 +++++++++++++++++++++++++++++++++-- cinder/volume/drivers/pure.py | 25 +++++++++++-- 2 files changed, 88 insertions(+), 5 deletions(-) diff --git a/cinder/tests/test_pure.py b/cinder/tests/test_pure.py index b0bae9a59..fdc03afac 100644 --- a/cinder/tests/test_pure.py +++ b/cinder/tests/test_pure.py @@ -614,16 +614,16 @@ class FlashArrayHttpRequestTestCase(FlashArrayBaseTestCase): # Test with _http_requests rather than rest calls to ensure # root_url change happens properly def test_choose_rest_version(self): - response_string = '{"version": ["0.1", "1.3", "1.1", "1.0"]}' + response_string = '{"version": ["0.1", "1.4", "1.3", "1.0"]}' self.response.read.return_value = response_string self.array._opener.open.return_value = self.response result = self.array._choose_rest_version() - self.assertEqual(result, "1.1") + self.assertEqual(result, "1.3") self.array._opener.open.assert_called_with(FakeRequest( "GET", "https://%s/api/api_version" % TARGET, headers=self.headers), "null") self.array._opener.open.reset_mock() - self.response.read.return_value = '{"version": ["0.1", "1.3"]}' + self.response.read.return_value = '{"version": ["0.1", "1.4"]}' self.assertRaises(exception.PureDriverException, self.array._choose_rest_version) @@ -765,6 +765,68 @@ class FlashArrayRESTTestCase(FlashArrayBaseTestCase): self.assert_error_propagates([mock_req], self.array.list_volume_hosts, "vol-name") + def test_create_pgroup(self, mock_req): + mock_req.return_value = self.result + pgroup_name = "cgroup_id" + result = self.array.create_pgroup(pgroup_name) + self.assertEqual(self.result, result) + req_url = "pgroup/" + pgroup_name + mock_req.assert_called_with(self.array, "POST", req_url) + self.assert_error_propagates([mock_req], self.array.create_pgroup, + pgroup_name) + + def test_delete_pgroup(self, mock_req): + mock_req.return_value = self.result + pgroup_name = "cgroup_id" + result = self.array.delete_pgroup(pgroup_name) + self.assertEqual(self.result, result) + req_url = "pgroup/" + pgroup_name + mock_req.assert_called_with(self.array, "DELETE", req_url) + self.assert_error_propagates([mock_req], self.array.delete_pgroup, + pgroup_name) + + def test_create_pgroup_snapshot(self, mock_req): + mock_req.return_value = self.result + pgroup_name = "cgroup_id" + snap_suffix = "snap_suffix" + result = self.array.create_pgroup_snapshot(pgroup_name, snap_suffix) + self.assertEqual(self.result, result) + expected_params = { + "snap": True, + "suffix": snap_suffix, + "source": [pgroup_name] + } + mock_req.assert_called_with(self.array, "POST", "pgroup", + expected_params) + self.assert_error_propagates([mock_req], + self.array.create_pgroup_snapshot, + pgroup_name, snap_suffix) + + def test_delete_pgroup_snapshot(self, mock_req): + mock_req.return_value = self.result + snapshot_name = "snap1" + result = self.array.delete_pgroup_snapshot(snapshot_name) + self.assertEqual(self.result, result) + req_url = "pgroup/" + snapshot_name + mock_req.assert_called_with(self.array, "DELETE", req_url) + self.assert_error_propagates([mock_req], + self.array.delete_pgroup_snapshot, + snapshot_name) + + def test_add_volume_to_pgroup(self, mock_req): + mock_req.return_value = self.result + pgroup_name = "cgroup_id" + volume_name = "myvol-1" + expected_params = {"addvollist": [volume_name]} + result = self.array.add_volume_to_pgroup(pgroup_name, volume_name) + self.assertEqual(self.result, result) + req_url = "pgroup/" + pgroup_name + mock_req.assert_called_with(self.array, "PUT", req_url, + expected_params) + self.assert_error_propagates([mock_req], + self.array.add_volume_to_pgroup, + pgroup_name, volume_name) + class FakeFlashArray(pure.FlashArray): diff --git a/cinder/volume/drivers/pure.py b/cinder/volume/drivers/pure.py index 12c7362b2..e5335b592 100644 --- a/cinder/volume/drivers/pure.py +++ b/cinder/volume/drivers/pure.py @@ -71,7 +71,7 @@ def _generate_purity_host_name(name): class PureISCSIDriver(san.SanISCSIDriver): """Performs volume management on Pure Storage FlashArray.""" - VERSION = "1.0.0" + VERSION = "2.0.0" def __init__(self, *args, **kwargs): execute = kwargs.pop("execute", utils.execute) @@ -323,7 +323,7 @@ class PureISCSIDriver(san.SanISCSIDriver): class FlashArray(object): """Wrapper for Pure Storage REST API.""" - SUPPORTED_REST_API_VERSIONS = ["1.2", "1.1", "1.0"] + SUPPORTED_REST_API_VERSIONS = ["1.3", "1.2"] def __init__(self, target, api_token): cookie_handler = urllib2.HTTPCookieProcessor(cookielib.CookieJar()) @@ -463,3 +463,24 @@ class FlashArray(object): def list_volume_hosts(self, volume): """Return a list of dictionaries describing connected hosts.""" return self._http_request("GET", "volume/%s/host" % volume) + + def create_pgroup(self, name): + return self._http_request("POST", "pgroup/%s" % name) + + def delete_pgroup(self, name): + return self._http_request("DELETE", "pgroup/%s" % name) + + def create_pgroup_snapshot(self, pgroup_name, pgsnapshot_suffix): + params = { + "snap": True, + "suffix": pgsnapshot_suffix, + "source": [pgroup_name] + } + return self._http_request("POST", "pgroup", params) + + def delete_pgroup_snapshot(self, name): + return self._http_request("DELETE", "pgroup/%s" % name) + + def add_volume_to_pgroup(self, pgroup_name, volume_name): + return self._http_request("PUT", "pgroup/%s" % pgroup_name, + {"addvollist": [volume_name]}) -- 2.45.2