From 0e741d0ba06e9c40647484e815461423657d3ae9 Mon Sep 17 00:00:00 2001 From: Kevin Zhao Date: Tue, 13 Feb 2018 17:30:54 +0800 Subject: [PATCH] Move the Capsule API from Experimental to V1 Part of blueprint golang-client Change-Id: Id47c95ca60a47ce43f4a89c29cb5d19b09427b7b Signed-off-by: Kevin Zhao --- devstack/lib/zun | 8 - doc/source/contributor/capsule.rst | 39 +---- zun/api/controllers/experimental/__init__.py | 153 ------------------ .../controllers/experimental/collection.py | 43 ----- .../experimental/schemas/__init__.py | 0 .../experimental/views/__init__.py | 0 zun/api/controllers/root.py | 4 +- zun/api/controllers/v1/__init__.py | 11 +- .../{experimental => v1}/capsules.py | 7 +- .../{experimental => v1}/schemas/capsules.py | 0 .../views/capsules_view.py | 0 .../controllers/auth-experimental-access.ini | 19 --- .../api/controllers/experimental/__init__.py | 0 zun/tests/unit/api/controllers/test_root.py | 31 +--- .../{experimental => v1}/test_capsules.py | 28 ++-- 15 files changed, 37 insertions(+), 306 deletions(-) delete mode 100644 zun/api/controllers/experimental/__init__.py delete mode 100644 zun/api/controllers/experimental/collection.py delete mode 100644 zun/api/controllers/experimental/schemas/__init__.py delete mode 100644 zun/api/controllers/experimental/views/__init__.py rename zun/api/controllers/{experimental => v1}/capsules.py (98%) rename zun/api/controllers/{experimental => v1}/schemas/capsules.py (100%) rename zun/api/controllers/{experimental => v1}/views/capsules_view.py (100%) delete mode 100644 zun/tests/unit/api/controllers/auth-experimental-access.ini delete mode 100644 zun/tests/unit/api/controllers/experimental/__init__.py rename zun/tests/unit/api/controllers/{experimental => v1}/test_capsules.py (97%) diff --git a/devstack/lib/zun b/devstack/lib/zun index 0caefd4f3..dcb42d190 100644 --- a/devstack/lib/zun +++ b/devstack/lib/zun @@ -141,7 +141,6 @@ function upload_sandbox_image { function create_zun_accounts { create_service_user "zun" "admin" - create_service_user "zun-experimental" "admin" if is_service_enabled zun-api; then @@ -154,18 +153,11 @@ function create_zun_accounts { local zun_service=$(get_or_create_service "zun" \ "container" "Container As Service") - local zun_experimental_service=$(get_or_create_service "zun-experimental" \ - "container-experimental" "Container As Service - Experimental") get_or_create_endpoint $zun_service \ "$REGION_NAME" \ "$zun_api_url/v1" \ "$zun_api_url/v1" \ "$zun_api_url/v1" - get_or_create_endpoint $zun_experimental_service \ - "$REGION_NAME" \ - "$zun_api_url/experimental" \ - "$zun_api_url/experimental" \ - "$zun_api_url/experimental" fi } diff --git a/doc/source/contributor/capsule.rst b/doc/source/contributor/capsule.rst index 9806a9246..48d3e83e6 100644 --- a/doc/source/contributor/capsule.rst +++ b/doc/source/contributor/capsule.rst @@ -46,32 +46,7 @@ The diagram below is an overview of the structure of ``capsule``. | | +-----------------------------------------------------------+ -Capsule API is currently in experimental phase, so you have to -specify ``--experimental-api`` option in each of the commands below. They will -be moved to stable API once they become stable. - -.. note:: - - Please make sure that every capsule commands have ``--experimental-api`` - flags in client side. - -Experimental API is a separated API. After users deploy Zun by devstack, -a separated set of API endpoints and service type will be created in -service catalog. Zun stable API endpoints will have service name ``zun`` and -service type ``container``, while Zun experimental API endpoints will have -service name ``zun-experimental`` and service type ``container-experimental``. -We can see the service and endpoint information as below:: - - +------------------+------------------------+---------+-----------+--------------------------------------+ - | Service Name | Service Type | Enabled | Interface | URL | - +------------------+------------------------+---------+-----------+--------------------------------------+ - | zun | container | True | public | http://***/container/v1 | - | zun | container | True | internal | http://***/container/v1 | - | zun | container | True | admin | http://***/container/v1 | - | zun-experimental | container-experimental | True | public | http://***/container/experimental | - | zun-experimental | container-experimental | True | internal | http://***/container/experimental | - | zun-experimental | container-experimental | True | admin | http://***/container/experimental | - +------------------+------------------------+---------+-----------+--------------------------------------+ +Capsule API is currently in v1 phase now. Now basic capsule functions are supported. Capsule API methods: @@ -175,7 +150,7 @@ Create capsule, it will create capsule based on capsule.yaml: .. code-block:: console $ source ~/devstack/openrc demo demo - $ zun --experimental-api capsule-create -f capsule.yaml + $ zun capsule-create -f capsule.yaml If you want to get access to the port, you need to set the security group rules for it. @@ -193,21 +168,21 @@ Delete capsule: .. code-block:: console - $ zun --experimental-api capsule-delete - $ zun --experimental-api capsule-delete + $ zun capsule-delete + $ zun capsule-delete List capsule: .. code-block:: console - $ zun --experimental-api capsule-list + $ zun capsule-list Describe capsule: .. code-block:: console - $ zun --experimental-api capsule-describe - $ zun --experimental-api capsule-describe + $ zun capsule-describe + $ zun capsule-describe To DO --------- diff --git a/zun/api/controllers/experimental/__init__.py b/zun/api/controllers/experimental/__init__.py deleted file mode 100644 index 13e9d071c..000000000 --- a/zun/api/controllers/experimental/__init__.py +++ /dev/null @@ -1,153 +0,0 @@ -# Copyright 2017 ARM Holdings. -# -# 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. - -""" -Experimental of the Zun API - -NOTE: IN PROGRESS AND NOT FULLY IMPLEMENTED. -""" - -from oslo_log import log as logging -import pecan - -from zun.api.controllers import base as controllers_base -from zun.api.controllers.experimental import capsules as capsule_controller -from zun.api.controllers import link -from zun.api.controllers import versions as ver -from zun.api import http_error -from zun.common.i18n import _ - -LOG = logging.getLogger(__name__) - - -BASE_VERSION = 1 - -MIN_VER_STR = '%s %s' % (ver.Version.service_string, ver.BASE_VER) - -MAX_VER_STR = '%s %s' % (ver.Version.service_string, ver.CURRENT_MAX_VER) - -MIN_VER = ver.Version({ver.Version.string: MIN_VER_STR}, - MIN_VER_STR, MAX_VER_STR) -MAX_VER = ver.Version({ver.Version.string: MAX_VER_STR}, - MIN_VER_STR, MAX_VER_STR) - - -class MediaType(controllers_base.APIBase): - """A media type representation.""" - - fields = ( - 'base', - 'type', - ) - - -class Experimental(controllers_base.APIBase): - """The representation of the version experimental of the API.""" - - fields = ( - 'id', - 'media_types', - 'links', - 'capsules' - ) - - @staticmethod - def convert(): - experimental = Experimental() - experimental.id = "experimental" - experimental.links = [link.make_link('self', pecan.request.host_url, - 'experimental', '', - bookmark=True), - link.make_link('describedby', - 'https://docs.openstack.org', - 'developer/zun/dev', - 'api-spec-v1.html', - bookmark=True, - type='text/html')] - experimental.media_types = \ - [MediaType(base='application/json', - type='application/vnd.openstack.' - 'zun.experimental+json')] - experimental.capsules = [link.make_link('self', - pecan.request.host_url, - 'experimental/capsules', '', - bookmark=True), - link.make_link('bookmark', - pecan.request.host_url, - 'capsules', '', - bookmark=True)] - return experimental - - -class Controller(controllers_base.Controller): - """Version expereimental API controller root.""" - - capsules = capsule_controller.CapsuleController() - - @pecan.expose('json') - def get(self): - return Experimental.convert() - - def _check_version(self, version, headers=None): - if headers is None: - headers = {} - # ensure that major version in the URL matches the header - if version.major != BASE_VERSION: - raise http_error.HTTPNotAcceptableAPIVersion(_( - "Mutually exclusive versions requested. Version %(ver)s " - "requested but not supported by this service. " - "The supported version range is: " - "[%(min)s, %(max)s].") % {'ver': version, - 'min': MIN_VER_STR, - 'max': MAX_VER_STR}, - headers=headers, - max_version=str(MAX_VER), - min_version=str(MIN_VER)) - # ensure the minor version is within the supported range - if version < MIN_VER or version > MAX_VER: - raise http_error.HTTPNotAcceptableAPIVersion(_( - "Version %(ver)s was requested but the minor version is not " - "supported by this service. The supported version range is: " - "[%(min)s, %(max)s].") % {'ver': version, 'min': MIN_VER_STR, - 'max': MAX_VER_STR}, - headers=headers, - max_version=str(MAX_VER), - min_version=str(MIN_VER)) - - @pecan.expose() - def _route(self, args): - version = ver.Version( - pecan.request.headers, MIN_VER_STR, MAX_VER_STR) - - # Always set the basic version headers - pecan.response.headers[ver.Version.min_string] = MIN_VER_STR - pecan.response.headers[ver.Version.max_string] = MAX_VER_STR - pecan.response.headers[ver.Version.string] = " ".join( - [ver.Version.service_string, str(version)]) - pecan.response.headers["vary"] = ver.Version.string - - # assert that requested version is supported - self._check_version(version, pecan.response.headers) - pecan.request.version = version - if pecan.request.body: - msg = ("Processing request: url: %(url)s, %(method)s, " - "body: %(body)s" % - {'url': pecan.request.url, - 'method': pecan.request.method, - 'body': pecan.request.body}) - LOG.debug(msg) - - return super(Controller, self)._route(args) - -__all__ = (Controller) diff --git a/zun/api/controllers/experimental/collection.py b/zun/api/controllers/experimental/collection.py deleted file mode 100644 index a2540078f..000000000 --- a/zun/api/controllers/experimental/collection.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2017 ARM Holdings. -# -# 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 pecan - -from zun.api.controllers import base -from zun.api.controllers import link - - -class Collection(base.APIBase): - - @property - def collection(self): - return getattr(self, self._type) - - def has_next(self, limit): - """Return whether collection has more items.""" - return len(self.collection) and len(self.collection) == limit - - def get_next(self, limit, url=None, **kwargs): - """Return a link to the next subset of the collection.""" - if not self.has_next(limit): - return None - - resource_url = url or self._type - q_args = ''.join(['%s=%s&' % (key, kwargs[key]) for key in kwargs]) - next_args = '?%(args)slimit=%(limit)d&marker=%(marker)s' % { - 'args': q_args, 'limit': limit, - 'marker': self.collection[-1]['uuid']} - - return link.make_link('next', pecan.request.host_url, - resource_url, next_args)['href'] diff --git a/zun/api/controllers/experimental/schemas/__init__.py b/zun/api/controllers/experimental/schemas/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/zun/api/controllers/experimental/views/__init__.py b/zun/api/controllers/experimental/views/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/zun/api/controllers/root.py b/zun/api/controllers/root.py index 700e744c3..12adfb91b 100644 --- a/zun/api/controllers/root.py +++ b/zun/api/controllers/root.py @@ -14,7 +14,6 @@ import pecan from pecan import rest from zun.api.controllers import base -from zun.api.controllers import experimental from zun.api.controllers import link from zun.api.controllers import v1 from zun.api.controllers import versions @@ -70,14 +69,13 @@ class Root(base.APIBase): class RootController(rest.RestController): - _versions = ['v1', 'experimental'] + _versions = ['v1'] """All supported API versions""" _default_version = 'v1' """The default API version""" v1 = v1.Controller() - experimental = experimental.Controller() @pecan.expose('json') def get(self): diff --git a/zun/api/controllers/v1/__init__.py b/zun/api/controllers/v1/__init__.py index 6cd86a708..a950201f2 100644 --- a/zun/api/controllers/v1/__init__.py +++ b/zun/api/controllers/v1/__init__.py @@ -22,8 +22,8 @@ from oslo_log import log as logging import pecan from zun.api.controllers import base as controllers_base -from zun.api.controllers.experimental import capsules as capsule_controller from zun.api.controllers import link +from zun.api.controllers.v1 import capsules as capsule_controller from zun.api.controllers.v1 import containers as container_controller from zun.api.controllers.v1 import hosts as host_controller from zun.api.controllers.v1 import images as image_controller @@ -66,7 +66,8 @@ class V1(controllers_base.APIBase): 'services', 'containers', 'images', - 'hosts' + 'hosts', + 'capsules' ) @staticmethod @@ -106,6 +107,12 @@ class V1(controllers_base.APIBase): pecan.request.host_url, 'hosts', '', bookmark=True)] + v1.capsules = [link.make_link('self', pecan.request.host_url, + 'capsules', ''), + link.make_link('bookmark', + pecan.request.host_url, + 'capsules', '', + bookmark=True)] return v1 diff --git a/zun/api/controllers/experimental/capsules.py b/zun/api/controllers/v1/capsules.py similarity index 98% rename from zun/api/controllers/experimental/capsules.py rename to zun/api/controllers/v1/capsules.py index d7d86e99b..fbaaab0e8 100644 --- a/zun/api/controllers/experimental/capsules.py +++ b/zun/api/controllers/v1/capsules.py @@ -13,14 +13,15 @@ # under the License. from oslo_log import log as logging + import pecan import six from zun.api.controllers import base -from zun.api.controllers.experimental import collection -from zun.api.controllers.experimental.schemas import capsules as schema -from zun.api.controllers.experimental.views import capsules_view as view from zun.api.controllers import link +from zun.api.controllers.v1 import collection +from zun.api.controllers.v1.schemas import capsules as schema +from zun.api.controllers.v1.views import capsules_view as view from zun.api import utils as api_utils from zun.common import consts from zun.common import exception diff --git a/zun/api/controllers/experimental/schemas/capsules.py b/zun/api/controllers/v1/schemas/capsules.py similarity index 100% rename from zun/api/controllers/experimental/schemas/capsules.py rename to zun/api/controllers/v1/schemas/capsules.py diff --git a/zun/api/controllers/experimental/views/capsules_view.py b/zun/api/controllers/v1/views/capsules_view.py similarity index 100% rename from zun/api/controllers/experimental/views/capsules_view.py rename to zun/api/controllers/v1/views/capsules_view.py diff --git a/zun/tests/unit/api/controllers/auth-experimental-access.ini b/zun/tests/unit/api/controllers/auth-experimental-access.ini deleted file mode 100644 index b9684fe26..000000000 --- a/zun/tests/unit/api/controllers/auth-experimental-access.ini +++ /dev/null @@ -1,19 +0,0 @@ -[pipeline:main] -pipeline = cors request_id authtoken api_experimental - -[app:api_experimental] -paste.app_factory = zun.api.app:app_factory - -[filter:authtoken] -acl_public_routes = /experimental -paste.filter_factory = zun.api.middleware.auth_token:AuthTokenMiddleware.factory - -[filter:request_id] -paste.filter_factory = oslo_middleware:RequestId.factory - -[filter:cors] -paste.filter_factory = oslo_middleware.cors:filter_factory -oslo_config_project = zun -latent_allow_methods = GET, PUT, POST, DELETE -latent_allow_headers = X-Auth-Token, X-Identity-Status, X-Roles, X-Service-Catalog, X-User-Id, X-Tenant-Id, X-OpenStack-Request-ID -latent_expose_headers = X-Auth-Token, X-Subject-Token, X-Service-Token, X-OpenStack-Request-ID diff --git a/zun/tests/unit/api/controllers/experimental/__init__.py b/zun/tests/unit/api/controllers/experimental/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/zun/tests/unit/api/controllers/test_root.py b/zun/tests/unit/api/controllers/test_root.py index 65bfbd32d..635634ca4 100644 --- a/zun/tests/unit/api/controllers/test_root.py +++ b/zun/tests/unit/api/controllers/test_root.py @@ -67,20 +67,8 @@ class TestRootController(api_base.FunctionalTest): 'images': [{'href': 'http://localhost/v1/images/', 'rel': 'self'}, {'href': 'http://localhost/images/', - 'rel': 'bookmark'}]} - - self.experimental_expected = { - 'media_types': - [{'base': 'application/json', - 'type': 'application/vnd.openstack.zun.experimental+json'}], - 'links': [{'href': 'http://localhost/experimental/', - 'rel': 'self'}, - {'href': - 'https://docs.openstack.org/developer' - '/zun/dev/api-spec-v1.html', - 'type': 'text/html', 'rel': 'describedby'}], - 'id': 'experimental', - 'capsules': [{'href': 'http://localhost/experimental/capsules/', + 'rel': 'bookmark'}], + 'capsules': [{'href': 'http://localhost/v1/capsules/', 'rel': 'self'}, {'href': 'http://localhost/capsules/', 'rel': 'bookmark'}]} @@ -98,10 +86,6 @@ class TestRootController(api_base.FunctionalTest): response = self.app.get('/v1/') self.assertEqual(self.v1_expected, response.json) - def test_experimental_controller(self): - response = self.app.get('/experimental/') - self.assertEqual(self.experimental_expected, response.json) - def test_get_not_found(self): response = self.app.get('/a/bogus/url', expect_errors=True) assert response.status_int == 404 @@ -159,14 +143,3 @@ class TestRootController(api_base.FunctionalTest): response = app.get('/v1/containers', expect_errors=True) self.assertEqual(401, response.status_int) - - def test_auth_with_experimental_access(self): - paste_file = \ - "zun/tests/unit/api/controllers/auth-experimental-access.ini" - app = self.make_app(paste_file) - - response = app.get('/', expect_errors=True) - self.assertEqual(401, response.status_int) - - response = app.get('/experimental/') - self.assertEqual(self.experimental_expected, response.json) diff --git a/zun/tests/unit/api/controllers/experimental/test_capsules.py b/zun/tests/unit/api/controllers/v1/test_capsules.py similarity index 97% rename from zun/tests/unit/api/controllers/experimental/test_capsules.py rename to zun/tests/unit/api/controllers/v1/test_capsules.py index 4f08882c9..52bcbf3ba 100644 --- a/zun/tests/unit/api/controllers/experimental/test_capsules.py +++ b/zun/tests/unit/api/controllers/v1/test_capsules.py @@ -42,7 +42,7 @@ class TestCapsuleController(api_base.FunctionalTest): ' "name": "capsule-example"}' ' }' '}') - response = self.post('/capsules/', + response = self.post('/v1/capsules/', params=params, content_type='application/json') return_value = response.json @@ -79,7 +79,7 @@ class TestCapsuleController(api_base.FunctionalTest): ' "name": "capsule-example"}' ' }' '}') - response = self.post('/capsules/', + response = self.post('/v1/capsules/', params=params, content_type='application/json') return_value = response.json @@ -156,7 +156,7 @@ class TestCapsuleController(api_base.FunctionalTest): '"name": "capsule-example"}}}') mock_check_template.side_effect = exception.InvalidCapsuleTemplate( "Container image is needed") - self.assertRaises(AppError, self.post, '/capsules/', + self.assertRaises(AppError, self.post, '/v1/capsules/', params=params, content_type='application/json') self.assertFalse(mock_capsule_create.called) @@ -193,7 +193,7 @@ class TestCapsuleController(api_base.FunctionalTest): ' "name": "capsule-example"}' ' }' '}') - response = self.post('/capsules/', + response = self.post('/v1/capsules/', params=params, content_type='application/json') return_value = response.json @@ -247,7 +247,7 @@ class TestCapsuleController(api_base.FunctionalTest): ' "name": "capsule-example"}' ' }' '}') - response = self.post('/capsules/', + response = self.post('/v1/capsules/', params=params, content_type='application/json') return_value = response.json @@ -310,7 +310,7 @@ class TestCapsuleController(api_base.FunctionalTest): ' "name": "capsule-example"}' ' }' '}') - response = self.post('/capsules/', + response = self.post('/v1/capsules/', params=params, content_type='application/json') return_value = response.json @@ -347,7 +347,7 @@ class TestCapsuleController(api_base.FunctionalTest): test_capsule_obj = objects.Capsule(self.context, **test_capsule) mock_capsule_get_by_uuid.return_value = test_capsule_obj - response = self.get('/capsules/%s/' % test_capsule['uuid']) + response = self.get('/v1/capsules/%s/' % test_capsule['uuid']) context = mock_capsule_get_by_uuid.call_args[0][0] self.assertIs(False, context.all_projects) @@ -399,7 +399,7 @@ class TestCapsuleController(api_base.FunctionalTest): mock_capsule_delete.return_value = True capsule_uuid = test_capsule.get('uuid') - response = self.app.delete('/capsules/%s' % capsule_uuid) + response = self.app.delete('/v1/capsules/%s' % capsule_uuid) self.assertTrue(mock_capsule_delete.called) self.assertEqual(204, response.status_int) @@ -431,7 +431,7 @@ class TestCapsuleController(api_base.FunctionalTest): capsule_uuid = test_capsule.get('uuid') response = self.app.delete( - '/capsules/%s/?all_projects=1' % capsule_uuid) + '/v1/capsules/%s/?all_projects=1' % capsule_uuid) self.assertTrue(mock_capsule_delete.called) self.assertEqual(204, response.status_int) @@ -463,7 +463,7 @@ class TestCapsuleController(api_base.FunctionalTest): mock_capsule_delete.return_value = True capsule_name = test_capsule.get('meta_name') - response = self.app.delete('/capsules/%s/' % + response = self.app.delete('/v1/capsules/%s/' % capsule_name) self.assertTrue(mock_capsule_delete.called) @@ -487,7 +487,7 @@ class TestCapsuleController(api_base.FunctionalTest): mock_capsule_list.return_value = [test_capsule_obj] mock_container_show.return_value = test_container_obj - response = self.app.get('/capsules/') + response = self.app.get('/v1/capsules/') mock_capsule_list.assert_called_once_with(mock.ANY, 1000, None, 'id', 'asc', @@ -517,7 +517,7 @@ class TestCapsuleController(api_base.FunctionalTest): mock_capsule_list.return_value = [test_capsule_obj] mock_container_show.return_value = test_container_obj - response = self.app.get('/capsules/?all_projects=1') + response = self.app.get('/v1/capsules/?all_projects=1') mock_capsule_list.assert_called_once_with(mock.ANY, 1000, None, 'id', 'asc', @@ -544,7 +544,7 @@ class TestCapsuleController(api_base.FunctionalTest): test_capsule_obj = objects.Capsule(self.context, **test_capsule) mock_capsule_list.return_value = [test_capsule_obj] - response = self.app.get('/capsules/') + response = self.app.get('/v1/capsules/') mock_capsule_list.assert_called_once_with(mock.ANY, 1000, None, 'id', 'asc', @@ -582,7 +582,7 @@ class TestCapsuleController(api_base.FunctionalTest): mock_container_show.return_value = capsule_list[-1] mock_capsule_save.return_value = True - response = self.app.get('/capsules/?limit=3&marker=%s' + response = self.app.get('/v1/capsules/?limit=3&marker=%s' % capsule_list[2].uuid) self.assertEqual(200, response.status_int)