From 0b0000f8fcc5dca4b2f9153b8af66da2538368fb Mon Sep 17 00:00:00 2001 From: Avishay Traeger Date: Mon, 18 Apr 2016 18:19:40 +0300 Subject: [PATCH] Map volume/snapshot manage extensions to v3 Create routes for the resources in the volume_manage and snapshot_manage extensions in v3, and bump the microversion. APIImpact DocImpact Implements: blueprint list-manage-existing Change-Id: Ia0df241a43f41cac4248efffc0b9828032daf63a --- cinder/api/openstack/api_version_request.py | 3 +- .../openstack/rest_api_version_history.rst | 6 + cinder/api/v3/router.py | 13 ++ cinder/api/v3/snapshot_manage.py | 45 ++++++ cinder/api/v3/volume_manage.py | 45 ++++++ .../tests/unit/api/v3/test_snapshot_manage.py | 134 +++++++++++++++++ .../tests/unit/api/v3/test_volume_manage.py | 136 ++++++++++++++++++ .../manage-resources-v3-c06096f75927fd3b.yaml | 4 + 8 files changed, 385 insertions(+), 1 deletion(-) create mode 100644 cinder/api/v3/snapshot_manage.py create mode 100644 cinder/api/v3/volume_manage.py create mode 100644 cinder/tests/unit/api/v3/test_snapshot_manage.py create mode 100644 cinder/tests/unit/api/v3/test_volume_manage.py create mode 100644 releasenotes/notes/manage-resources-v3-c06096f75927fd3b.yaml diff --git a/cinder/api/openstack/api_version_request.py b/cinder/api/openstack/api_version_request.py index 43166e651d8..64e767cb0f7 100644 --- a/cinder/api/openstack/api_version_request.py +++ b/cinder/api/openstack/api_version_request.py @@ -55,6 +55,7 @@ REST_API_VERSION_HISTORY = """ * 3.6 - Allows to set empty description and empty name for consistency group in consisgroup-update operation. * 3.7 - Add cluster API and cluster_name field to service list API + * 3.8 - Adds resources from volume_manage and snapshot_manage extensions. """ @@ -63,7 +64,7 @@ REST_API_VERSION_HISTORY = """ # minimum version of the API supported. # Explicitly using /v1 or /v2 enpoints will still work _MIN_API_VERSION = "3.0" -_MAX_API_VERSION = "3.7" +_MAX_API_VERSION = "3.8" _LEGACY_API_VERSION1 = "1.0" _LEGACY_API_VERSION2 = "2.0" diff --git a/cinder/api/openstack/rest_api_version_history.rst b/cinder/api/openstack/rest_api_version_history.rst index d50cf36dadc..42930c6cd6f 100644 --- a/cinder/api/openstack/rest_api_version_history.rst +++ b/cinder/api/openstack/rest_api_version_history.rst @@ -146,3 +146,9 @@ user documentation. ... } ] + +3.8 +--- + Adds the following resources that were previously in extensions: + - os-volume-manage => /v3//manageable_volumes + - os-snapshot-manage => /v3//manageable_snapshots diff --git a/cinder/api/v3/router.py b/cinder/api/v3/router.py index 52b466e7c5a..71706203f76 100644 --- a/cinder/api/v3/router.py +++ b/cinder/api/v3/router.py @@ -29,6 +29,8 @@ from cinder.api.v2 import volume_metadata from cinder.api.v3 import clusters from cinder.api.v3 import consistencygroups from cinder.api.v3 import messages +from cinder.api.v3 import snapshot_manage +from cinder.api.v3 import volume_manage from cinder.api.v3 import volumes from cinder.api import versions @@ -112,3 +114,14 @@ class APIRouter(cinder.api.openstack.APIRouter): controller=self.resources['consistencygroups'], collection={'detail': 'GET'}, member={'action': 'POST'}) + + self.resources['manageable_volumes'] = volume_manage.create_resource() + mapper.resource("manageable_volume", "manageable_volumes", + controller=self.resources['manageable_volumes'], + collection={'detail': 'GET'}) + + self.resources['manageable_snapshots'] = \ + snapshot_manage.create_resource() + mapper.resource("manageable_snapshot", "manageable_snapshots", + controller=self.resources['manageable_snapshots'], + collection={'detail': 'GET'}) diff --git a/cinder/api/v3/snapshot_manage.py b/cinder/api/v3/snapshot_manage.py new file mode 100644 index 00000000000..4cd566712bf --- /dev/null +++ b/cinder/api/v3/snapshot_manage.py @@ -0,0 +1,45 @@ +# Copyright (c) 2016 Stratoscale, Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from cinder.api.contrib import snapshot_manage as snapshot_manage_v2 +from cinder.api.openstack import wsgi +from cinder import exception + + +class SnapshotManageController(snapshot_manage_v2.SnapshotManageController): + def _ensure_min_version(self, req, allowed_version): + version = req.api_version_request + if not version.matches(allowed_version, None): + raise exception.VersionNotFoundForAPIMethod(version=version) + + @wsgi.response(202) + def create(self, req, body): + self._ensure_min_version(req, "3.8") + return super(SnapshotManageController, self).create(req, body) + + @wsgi.extends + def index(self, req): + """Returns a summary list of snapshots available to manage.""" + self._ensure_min_version(req, "3.8") + return super(SnapshotManageController, self).index(req) + + @wsgi.extends + def detail(self, req): + """Returns a detailed list of snapshots available to manage.""" + self._ensure_min_version(req, "3.8") + return super(SnapshotManageController, self).detail(req) + + +def create_resource(): + return wsgi.Resource(SnapshotManageController()) diff --git a/cinder/api/v3/volume_manage.py b/cinder/api/v3/volume_manage.py new file mode 100644 index 00000000000..9f0133e3537 --- /dev/null +++ b/cinder/api/v3/volume_manage.py @@ -0,0 +1,45 @@ +# Copyright (c) 2016 Stratoscale, Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from cinder.api.contrib import volume_manage as volume_manage_v2 +from cinder.api.openstack import wsgi +from cinder import exception + + +class VolumeManageController(volume_manage_v2.VolumeManageController): + def _ensure_min_version(self, req, allowed_version): + version = req.api_version_request + if not version.matches(allowed_version, None): + raise exception.VersionNotFoundForAPIMethod(version=version) + + @wsgi.response(202) + def create(self, req, body): + self._ensure_min_version(req, "3.8") + return super(VolumeManageController, self).create(req, body) + + @wsgi.extends + def index(self, req): + """Returns a summary list of volumes available to manage.""" + self._ensure_min_version(req, "3.8") + return super(VolumeManageController, self).index(req) + + @wsgi.extends + def detail(self, req): + """Returns a detailed list of volumes available to manage.""" + self._ensure_min_version(req, "3.8") + return super(VolumeManageController, self).detail(req) + + +def create_resource(): + return wsgi.Resource(VolumeManageController()) diff --git a/cinder/tests/unit/api/v3/test_snapshot_manage.py b/cinder/tests/unit/api/v3/test_snapshot_manage.py new file mode 100644 index 00000000000..58d2ee0af91 --- /dev/null +++ b/cinder/tests/unit/api/v3/test_snapshot_manage.py @@ -0,0 +1,134 @@ +# Copyright (c) 2016 Stratoscale, Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +from oslo_serialization import jsonutils +try: + from urllib import urlencode +except ImportError: + from urllib.parse import urlencode +import webob + +from cinder.api.v3 import router as router_v3 +from cinder import context +from cinder import test +from cinder.tests.unit.api.contrib import test_snapshot_manage as test_contrib +from cinder.tests.unit.api import fakes +from cinder.tests.unit import fake_constants as fake +from cinder.tests.unit import fake_service + + +def app(): + # no auth, just let environ['cinder.context'] pass through + api = router_v3.APIRouter() + mapper = fakes.urlmap.URLMap() + mapper['/v3'] = api + return mapper + + +@mock.patch('cinder.volume.api.API.get', test_contrib.volume_get) +class SnapshotManageTest(test.TestCase): + """Test cases for cinder/api/v3/snapshot_manage.py""" + def setUp(self): + super(SnapshotManageTest, self).setUp() + self._admin_ctxt = context.RequestContext(fake.USER_ID, + fake.PROJECT_ID, + True) + + def _get_resp_post(self, body, version="3.8"): + """Helper to execute a POST manageable_snapshots API call.""" + req = webob.Request.blank('/v3/%s/manageable_snapshots' % + fake.PROJECT_ID) + req.method = 'POST' + req.headers['Content-Type'] = 'application/json' + req.headers['OpenStack-API-Version'] = 'volume ' + version + req.environ['cinder.context'] = self._admin_ctxt + req.body = jsonutils.dump_as_bytes(body) + res = req.get_response(app()) + return res + + @mock.patch('cinder.volume.rpcapi.VolumeAPI.manage_existing_snapshot') + @mock.patch('cinder.volume.api.API.create_snapshot_in_db') + @mock.patch('cinder.objects.service.Service.get_by_args') + def test_manage_snapshot_route(self, mock_service_get, + mock_create_snapshot, mock_rpcapi): + """Test call to manage snapshot. + + There is currently no change between the API in contrib and the API in + v3, so here we simply check that the call is routed properly, rather + than copying all the tests. + """ + mock_service_get.return_value = fake_service.fake_service_obj( + self._admin_ctxt, + binary='cinder-volume') + + body = {'snapshot': {'volume_id': fake.VOLUME_ID, 'ref': 'fake_ref'}} + res = self._get_resp_post(body) + self.assertEqual(202, res.status_int, res) + + def test_manage_snapshot_previous_version(self): + body = {'snapshot': {'volume_id': fake.VOLUME_ID, 'ref': 'fake_ref'}} + res = self._get_resp_post(body, version="3.7") + self.assertEqual(404, res.status_int, res) + + def _get_resp_get(self, host, detailed, paging, version="3.8"): + """Helper to execute a GET os-snapshot-manage API call.""" + params = {'host': host} + if paging: + params.update({'marker': '1234', 'limit': 10, + 'offset': 4, 'sort': 'reference:asc'}) + query_string = "?%s" % urlencode(params) + detail = "" + if detailed: + detail = "/detail" + req = webob.Request.blank('/v3/%s/manageable_snapshots%s%s' % + (fake.PROJECT_ID, detail, query_string)) + req.method = 'GET' + req.headers['Content-Type'] = 'application/json' + req.headers['OpenStack-API-Version'] = 'volume ' + version + req.environ['cinder.context'] = self._admin_ctxt + res = req.get_response(app()) + return res + + @mock.patch('cinder.volume.api.API.get_manageable_snapshots', + wraps=test_contrib.api_get_manageable_snapshots) + def test_get_manageable_snapshots_route(self, mock_api_manageable): + """Test call to get manageable volumes. + + There is currently no change between the API in contrib and the API in + v3, so here we simply check that the call is routed properly, rather + than copying all the tests. + """ + res = self._get_resp_get('fakehost', False, False) + self.assertEqual(200, res.status_int) + + def test_get_manageable_snapshots_previous_version(self): + res = self._get_resp_get('fakehost', False, False, version="3.7") + self.assertEqual(404, res.status_int) + + @mock.patch('cinder.volume.api.API.get_manageable_snapshots', + wraps=test_contrib.api_get_manageable_snapshots) + def test_get_manageable_snapshots_detail_route(self, mock_api_manageable): + """Test call to get manageable volumes (detailed). + + There is currently no change between the API in contrib and the API in + v3, so here we simply check that the call is routed properly, rather + than copying all the tests. + """ + res = self._get_resp_get('fakehost', True, True) + self.assertEqual(200, res.status_int) + + def test_get_manageable_snapshots_detail_previous_version(self): + res = self._get_resp_get('fakehost', True, True, version="3.7") + self.assertEqual(404, res.status_int) diff --git a/cinder/tests/unit/api/v3/test_volume_manage.py b/cinder/tests/unit/api/v3/test_volume_manage.py new file mode 100644 index 00000000000..68a12fa5100 --- /dev/null +++ b/cinder/tests/unit/api/v3/test_volume_manage.py @@ -0,0 +1,136 @@ +# Copyright (c) 2016 Stratoscale, Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +from oslo_serialization import jsonutils +try: + from urllib import urlencode +except ImportError: + from urllib.parse import urlencode +import webob + +from cinder.api.v3 import router as router_v3 +from cinder import context +from cinder import test +from cinder.tests.unit.api.contrib import test_volume_manage as test_contrib +from cinder.tests.unit.api import fakes +from cinder.tests.unit import fake_constants as fake + + +def app(): + # no auth, just let environ['cinder.context'] pass through + api = router_v3.APIRouter() + mapper = fakes.urlmap.URLMap() + mapper['/v3'] = api + return mapper + + +@mock.patch('cinder.objects.service.Service.get_by_host_and_topic', + test_contrib.service_get_by_host_and_topic) +@mock.patch('cinder.volume.volume_types.get_volume_type_by_name', + test_contrib.vt_get_volume_type_by_name) +@mock.patch('cinder.volume.volume_types.get_volume_type', + test_contrib.vt_get_volume_type) +class VolumeManageTest(test.TestCase): + """Test cases for cinder/api/v3/volume_manage.py""" + + def setUp(self): + super(VolumeManageTest, self).setUp() + self._admin_ctxt = context.RequestContext(fake.USER_ID, + fake.PROJECT_ID, + True) + + def _get_resp_post(self, body, version="3.8"): + """Helper to execute a POST manageable_volumes API call.""" + req = webob.Request.blank('/v3/%s/manageable_volumes' % + fake.PROJECT_ID) + req.method = 'POST' + req.headers['Content-Type'] = 'application/json' + req.headers['OpenStack-API-Version'] = 'volume ' + version + req.environ['cinder.context'] = self._admin_ctxt + req.body = jsonutils.dump_as_bytes(body) + res = req.get_response(app()) + return res + + @mock.patch('cinder.volume.api.API.manage_existing', + wraps=test_contrib.api_manage) + @mock.patch( + 'cinder.api.openstack.wsgi.Controller.validate_name_and_description') + def test_manage_volume_route(self, mock_validate, mock_api_manage): + """Test call to manage volume. + + There is currently no change between the API in contrib and the API in + v3, so here we simply check that the call is routed properly, rather + than copying all the tests. + """ + body = {'volume': {'host': 'host_ok', 'ref': 'fake_ref'}} + res = self._get_resp_post(body) + self.assertEqual(202, res.status_int, res) + + def test_manage_volume_previous_version(self): + body = {'volume': {'host': 'host_ok', 'ref': 'fake_ref'}} + res = self._get_resp_post(body) + self.assertEqual(404, res.status_int, res) + + def _get_resp_get(self, host, detailed, paging, version="3.8"): + """Helper to execute a GET os-volume-manage API call.""" + params = {'host': host} + if paging: + params.update({'marker': '1234', 'limit': 10, + 'offset': 4, 'sort': 'reference:asc'}) + query_string = "?%s" % urlencode(params) + detail = "" + if detailed: + detail = "/detail" + + req = webob.Request.blank('/v3/%s/manageable_volumes%s%s' % + (fake.PROJECT_ID, detail, query_string)) + req.method = 'GET' + req.headers['Content-Type'] = 'application/json' + req.headers['OpenStack-API-Version'] = 'volume ' + version + req.environ['cinder.context'] = self._admin_ctxt + res = req.get_response(app()) + return res + + @mock.patch('cinder.volume.api.API.get_manageable_volumes', + wraps=test_contrib.api_get_manageable_volumes) + def test_get_manageable_volumes_route(self, mock_api_manageable): + """Test call to get manageable volumes. + + There is currently no change between the API in contrib and the API in + v3, so here we simply check that the call is routed properly, rather + than copying all the tests. + """ + res = self._get_resp_get('fakehost', False, True) + self.assertEqual(200, res.status_int) + + def test_get_manageable_volumes_previous_version(self): + res = self._get_resp_get('fakehost', False, True, version="3.7") + self.assertEqual(404, res.status_int) + + @mock.patch('cinder.volume.api.API.get_manageable_volumes', + wraps=test_contrib.api_get_manageable_volumes) + def test_get_manageable_volumes_detail_route(self, mock_api_manageable): + """Test call to get manageable volumes (detailed). + + There is currently no change between the API in contrib and the API in + v3, so here we simply check that the call is routed properly, rather + than copying all the tests. + """ + res = self._get_resp_get('fakehost', True, False) + self.assertEqual(200, res.status_int) + + def test_get_manageable_volumes_detail_previous_version(self): + res = self._get_resp_get('fakehost', True, False, version="3.7") + self.assertEqual(404, res.status_int) diff --git a/releasenotes/notes/manage-resources-v3-c06096f75927fd3b.yaml b/releasenotes/notes/manage-resources-v3-c06096f75927fd3b.yaml new file mode 100644 index 00000000000..94ae8b3be2f --- /dev/null +++ b/releasenotes/notes/manage-resources-v3-c06096f75927fd3b.yaml @@ -0,0 +1,4 @@ +--- +features: + - The v2 API extensions os-volume-manage and os-snapshot-manage have been + mapped to the v3 resources manageable_volumes and manageable_snapshots