Add v2.1 API router and endpoint
Now we have decided Nova v3 API interfaces are disabled and we will change v3 interfaces to v2 ones for using v3 infrastractures as v2.1 API. The big difference between v2 and v3 that v2 API URLs contain project-id but v3 ones don't contain it. This patch adds both an API router and an endpoint of v2.1 API to support API URLs containing project-id. This patch does not remove v3 API router and endpoint because v3 API tests use it now. After moving all v3 code to v2.1, we can remove them. Partially implements blueprint v2-on-v3-api Change-Id: Iee75fbd8951c9f246f68a9c75d762335fa5c4b4b
This commit is contained in:
@@ -61,6 +61,7 @@ use = call:nova.api.openstack.urlmap:urlmap_factory
|
|||||||
/: oscomputeversions
|
/: oscomputeversions
|
||||||
/v1.1: openstack_compute_api_v2
|
/v1.1: openstack_compute_api_v2
|
||||||
/v2: openstack_compute_api_v2
|
/v2: openstack_compute_api_v2
|
||||||
|
/v2.1: openstack_compute_api_v21
|
||||||
/v3: openstack_compute_api_v3
|
/v3: openstack_compute_api_v3
|
||||||
|
|
||||||
[composite:openstack_compute_api_v2]
|
[composite:openstack_compute_api_v2]
|
||||||
@@ -69,8 +70,13 @@ noauth = compute_req_id faultwrap sizelimit noauth ratelimit osapi_compute_app_v
|
|||||||
keystone = compute_req_id faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute_app_v2
|
keystone = compute_req_id faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute_app_v2
|
||||||
keystone_nolimit = compute_req_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2
|
keystone_nolimit = compute_req_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2
|
||||||
|
|
||||||
|
[composite:openstack_compute_api_v21]
|
||||||
|
use = call:nova.api.auth:pipeline_factory_v21
|
||||||
|
noauth = request_id faultwrap sizelimit noauth osapi_compute_app_v21
|
||||||
|
keystone = request_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v21
|
||||||
|
|
||||||
[composite:openstack_compute_api_v3]
|
[composite:openstack_compute_api_v3]
|
||||||
use = call:nova.api.auth:pipeline_factory_v3
|
use = call:nova.api.auth:pipeline_factory_v21
|
||||||
noauth = request_id faultwrap sizelimit noauth_v3 osapi_compute_app_v3
|
noauth = request_id faultwrap sizelimit noauth_v3 osapi_compute_app_v3
|
||||||
keystone = request_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v3
|
keystone = request_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v3
|
||||||
|
|
||||||
@@ -98,6 +104,9 @@ paste.filter_factory = nova.api.sizelimit:RequestBodySizeLimiter.factory
|
|||||||
[app:osapi_compute_app_v2]
|
[app:osapi_compute_app_v2]
|
||||||
paste.app_factory = nova.api.openstack.compute:APIRouter.factory
|
paste.app_factory = nova.api.openstack.compute:APIRouter.factory
|
||||||
|
|
||||||
|
[app:osapi_compute_app_v21]
|
||||||
|
paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory
|
||||||
|
|
||||||
[app:osapi_compute_app_v3]
|
[app:osapi_compute_app_v3]
|
||||||
paste.app_factory = nova.api.openstack.compute:APIRouterV3.factory
|
paste.app_factory = nova.api.openstack.compute:APIRouterV3.factory
|
||||||
|
|
||||||
|
@@ -75,11 +75,15 @@ def pipeline_factory(loader, global_conf, **local_conf):
|
|||||||
return _load_pipeline(loader, pipeline)
|
return _load_pipeline(loader, pipeline)
|
||||||
|
|
||||||
|
|
||||||
def pipeline_factory_v3(loader, global_conf, **local_conf):
|
def pipeline_factory_v21(loader, global_conf, **local_conf):
|
||||||
"""A paste pipeline replica that keys off of auth_strategy."""
|
"""A paste pipeline replica that keys off of auth_strategy."""
|
||||||
return _load_pipeline(loader, local_conf[CONF.auth_strategy].split())
|
return _load_pipeline(loader, local_conf[CONF.auth_strategy].split())
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE(oomichi): This pipeline_factory_v3 is for passing check-grenade-dsvm.
|
||||||
|
pipeline_factory_v3 = pipeline_factory_v21
|
||||||
|
|
||||||
|
|
||||||
class InjectContext(wsgi.Middleware):
|
class InjectContext(wsgi.Middleware):
|
||||||
"""Add a 'nova.context' to WSGI environ."""
|
"""Add a 'nova.context' to WSGI environ."""
|
||||||
|
|
||||||
|
@@ -249,11 +249,13 @@ class APIRouter(base_wsgi.Router):
|
|||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
class APIRouterV3(base_wsgi.Router):
|
class APIRouterV21(base_wsgi.Router):
|
||||||
"""Routes requests on the OpenStack v3 API to the appropriate controller
|
"""Routes requests on the OpenStack v2.1 API to the appropriate controller
|
||||||
and method.
|
and method.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# TODO(oomichi): This namespace will be changed after moving all v3 APIs
|
||||||
|
# to v2.1.
|
||||||
API_EXTENSION_NAMESPACE = 'nova.api.v3.extensions'
|
API_EXTENSION_NAMESPACE = 'nova.api.v3.extensions'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -261,10 +263,12 @@ class APIRouterV3(base_wsgi.Router):
|
|||||||
"""Simple paste factory, :class:`nova.wsgi.Router` doesn't have one."""
|
"""Simple paste factory, :class:`nova.wsgi.Router` doesn't have one."""
|
||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
def __init__(self, init_only=None):
|
def __init__(self, init_only=None, v3mode=False):
|
||||||
# TODO(cyeoh): bp v3-api-extension-framework. Currently load
|
# TODO(cyeoh): bp v3-api-extension-framework. Currently load
|
||||||
# all extensions but eventually should be able to exclude
|
# all extensions but eventually should be able to exclude
|
||||||
# based on a config file
|
# based on a config file
|
||||||
|
# TODO(oomichi): We can remove v3mode argument after moving all v3 APIs
|
||||||
|
# to v2.1.
|
||||||
def _check_load_extension(ext):
|
def _check_load_extension(ext):
|
||||||
if (self.init_only is None or ext.obj.alias in
|
if (self.init_only is None or ext.obj.alias in
|
||||||
self.init_only) and isinstance(ext.obj,
|
self.init_only) and isinstance(ext.obj,
|
||||||
@@ -313,7 +317,11 @@ class APIRouterV3(base_wsgi.Router):
|
|||||||
invoke_on_load=True,
|
invoke_on_load=True,
|
||||||
invoke_kwds={"extension_info": self.loaded_extension_info})
|
invoke_kwds={"extension_info": self.loaded_extension_info})
|
||||||
|
|
||||||
mapper = PlainMapper()
|
if v3mode:
|
||||||
|
mapper = PlainMapper()
|
||||||
|
else:
|
||||||
|
mapper = ProjectMapper()
|
||||||
|
|
||||||
self.resources = {}
|
self.resources = {}
|
||||||
|
|
||||||
# NOTE(cyeoh) Core API support is rewritten as extensions
|
# NOTE(cyeoh) Core API support is rewritten as extensions
|
||||||
@@ -333,7 +341,7 @@ class APIRouterV3(base_wsgi.Router):
|
|||||||
raise exception.CoreAPIMissing(
|
raise exception.CoreAPIMissing(
|
||||||
missing_apis=missing_core_extensions)
|
missing_apis=missing_core_extensions)
|
||||||
|
|
||||||
super(APIRouterV3, self).__init__(mapper)
|
super(APIRouterV21, self).__init__(mapper)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_missing_core_extensions(extensions_loaded):
|
def get_missing_core_extensions(extensions_loaded):
|
||||||
|
@@ -128,13 +128,31 @@ class APIRouter(nova.api.openstack.APIRouter):
|
|||||||
conditions={"method": ['PUT']})
|
conditions={"method": ['PUT']})
|
||||||
|
|
||||||
|
|
||||||
class APIRouterV3(nova.api.openstack.APIRouterV3):
|
class APIRouterV21(nova.api.openstack.APIRouterV21):
|
||||||
"""Routes requests on the OpenStack API to the appropriate controller
|
"""Routes requests on the OpenStack API to the appropriate controller
|
||||||
and method.
|
and method.
|
||||||
"""
|
"""
|
||||||
def __init__(self, init_only=None):
|
def __init__(self, init_only=None):
|
||||||
self._loaded_extension_info = plugins.LoadedExtensionInfo()
|
self._loaded_extension_info = plugins.LoadedExtensionInfo()
|
||||||
super(APIRouterV3, self).__init__(init_only)
|
super(APIRouterV21, self).__init__(init_only)
|
||||||
|
|
||||||
|
def _register_extension(self, ext):
|
||||||
|
return self.loaded_extension_info.register_extension(ext.obj)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def loaded_extension_info(self):
|
||||||
|
return self._loaded_extension_info
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE(oomichi): Now v3 API tests use APIRouterV3. After moving all v3
|
||||||
|
# API extensions to v2.1 API, we can remove this class.
|
||||||
|
class APIRouterV3(nova.api.openstack.APIRouterV21):
|
||||||
|
"""Routes requests on the OpenStack API to the appropriate controller
|
||||||
|
and method.
|
||||||
|
"""
|
||||||
|
def __init__(self, init_only=None):
|
||||||
|
self._loaded_extension_info = plugins.LoadedExtensionInfo()
|
||||||
|
super(APIRouterV3, self).__init__(init_only, v3mode=True)
|
||||||
|
|
||||||
def _register_extension(self, ext):
|
def _register_extension(self, ext):
|
||||||
return self.loaded_extension_info.register_extension(ext.obj)
|
return self.loaded_extension_info.register_extension(ext.obj)
|
||||||
|
196
nova/tests/api/openstack/compute/test_v21_extensions.py
Normal file
196
nova/tests/api/openstack/compute/test_v21_extensions.py
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
# Copyright 2013 IBM Corp.
|
||||||
|
# Copyright 2014 NEC Corporation. All rights reserved.
|
||||||
|
#
|
||||||
|
# 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 oslo.config import cfg
|
||||||
|
import stevedore
|
||||||
|
import webob.exc
|
||||||
|
|
||||||
|
from nova.api import openstack
|
||||||
|
from nova.api.openstack import compute
|
||||||
|
from nova.api.openstack.compute import plugins
|
||||||
|
from nova.api.openstack import extensions
|
||||||
|
from nova import exception
|
||||||
|
from nova import test
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class fake_bad_extension(object):
|
||||||
|
name = "fake_bad_extension"
|
||||||
|
alias = "fake-bad"
|
||||||
|
|
||||||
|
|
||||||
|
class fake_stevedore_enabled_extensions(object):
|
||||||
|
def __init__(self, namespace, check_func, invoke_on_load=False,
|
||||||
|
invoke_args=(), invoke_kwds=None):
|
||||||
|
self.extensions = []
|
||||||
|
|
||||||
|
def map(self, func, *args, **kwds):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.extensions)
|
||||||
|
|
||||||
|
|
||||||
|
class fake_loaded_extension_info(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.extensions = {}
|
||||||
|
|
||||||
|
def register_extension(self, ext):
|
||||||
|
self.extensions[ext] = ext
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_extensions(self):
|
||||||
|
return {'core1': None, 'core2': None, 'noncore1': None}
|
||||||
|
|
||||||
|
|
||||||
|
class ExtensionLoadingTestCase(test.NoDBTestCase):
|
||||||
|
|
||||||
|
def _set_v21_core(self, core_extensions):
|
||||||
|
openstack.API_V3_CORE_EXTENSIONS = core_extensions
|
||||||
|
|
||||||
|
def test_extensions_loaded(self):
|
||||||
|
app = compute.APIRouterV21()
|
||||||
|
self.assertIn('servers', app._loaded_extension_info.extensions)
|
||||||
|
|
||||||
|
def test_check_bad_extension(self):
|
||||||
|
extension_info = plugins.LoadedExtensionInfo()
|
||||||
|
self.assertFalse(extension_info._check_extension(fake_bad_extension))
|
||||||
|
|
||||||
|
def test_extensions_blacklist(self):
|
||||||
|
app = compute.APIRouterV21()
|
||||||
|
self.assertIn('os-hosts', app._loaded_extension_info.extensions)
|
||||||
|
CONF.set_override('extensions_blacklist', ['os-hosts'], 'osapi_v3')
|
||||||
|
app = compute.APIRouterV21()
|
||||||
|
self.assertNotIn('os-hosts', app._loaded_extension_info.extensions)
|
||||||
|
|
||||||
|
def test_extensions_whitelist_accept(self):
|
||||||
|
# NOTE(maurosr): just to avoid to get an exception raised for not
|
||||||
|
# loading all core api.
|
||||||
|
v21_core = openstack.API_V3_CORE_EXTENSIONS
|
||||||
|
openstack.API_V3_CORE_EXTENSIONS = set(['servers'])
|
||||||
|
self.addCleanup(self._set_v21_core, v21_core)
|
||||||
|
|
||||||
|
app = compute.APIRouterV21()
|
||||||
|
self.assertIn('os-hosts', app._loaded_extension_info.extensions)
|
||||||
|
CONF.set_override('extensions_whitelist', ['servers', 'os-hosts'],
|
||||||
|
'osapi_v3')
|
||||||
|
app = compute.APIRouterV21()
|
||||||
|
self.assertIn('os-hosts', app._loaded_extension_info.extensions)
|
||||||
|
|
||||||
|
def test_extensions_whitelist_block(self):
|
||||||
|
# NOTE(maurosr): just to avoid to get an exception raised for not
|
||||||
|
# loading all core api.
|
||||||
|
v21_core = openstack.API_V3_CORE_EXTENSIONS
|
||||||
|
openstack.API_V3_CORE_EXTENSIONS = set(['servers'])
|
||||||
|
self.addCleanup(self._set_v21_core, v21_core)
|
||||||
|
|
||||||
|
app = compute.APIRouterV21()
|
||||||
|
self.assertIn('os-hosts', app._loaded_extension_info.extensions)
|
||||||
|
CONF.set_override('extensions_whitelist', ['servers'], 'osapi_v3')
|
||||||
|
app = compute.APIRouterV21()
|
||||||
|
self.assertNotIn('os-hosts', app._loaded_extension_info.extensions)
|
||||||
|
|
||||||
|
def test_blacklist_overrides_whitelist(self):
|
||||||
|
# NOTE(maurosr): just to avoid to get an exception raised for not
|
||||||
|
# loading all core api.
|
||||||
|
v21_core = openstack.API_V3_CORE_EXTENSIONS
|
||||||
|
openstack.API_V3_CORE_EXTENSIONS = set(['servers'])
|
||||||
|
self.addCleanup(self._set_v21_core, v21_core)
|
||||||
|
|
||||||
|
app = compute.APIRouterV21()
|
||||||
|
self.assertIn('os-hosts', app._loaded_extension_info.extensions)
|
||||||
|
CONF.set_override('extensions_whitelist', ['servers', 'os-hosts'],
|
||||||
|
'osapi_v3')
|
||||||
|
CONF.set_override('extensions_blacklist', ['os-hosts'], 'osapi_v3')
|
||||||
|
app = compute.APIRouterV21()
|
||||||
|
self.assertNotIn('os-hosts', app._loaded_extension_info.extensions)
|
||||||
|
self.assertIn('servers', app._loaded_extension_info.extensions)
|
||||||
|
self.assertEqual(1, len(app._loaded_extension_info.extensions))
|
||||||
|
|
||||||
|
def test_get_missing_core_extensions(self):
|
||||||
|
v21_core = openstack.API_V3_CORE_EXTENSIONS
|
||||||
|
openstack.API_V3_CORE_EXTENSIONS = set(['core1', 'core2'])
|
||||||
|
self.addCleanup(self._set_v21_core, v21_core)
|
||||||
|
self.assertEqual(0, len(
|
||||||
|
compute.APIRouterV21.get_missing_core_extensions(
|
||||||
|
['core1', 'core2', 'noncore1'])))
|
||||||
|
missing_core = compute.APIRouterV21.get_missing_core_extensions(
|
||||||
|
['core1'])
|
||||||
|
self.assertEqual(1, len(missing_core))
|
||||||
|
self.assertIn('core2', missing_core)
|
||||||
|
missing_core = compute.APIRouterV21.get_missing_core_extensions([])
|
||||||
|
self.assertEqual(2, len(missing_core))
|
||||||
|
self.assertIn('core1', missing_core)
|
||||||
|
self.assertIn('core2', missing_core)
|
||||||
|
missing_core = compute.APIRouterV21.get_missing_core_extensions(
|
||||||
|
['noncore1'])
|
||||||
|
self.assertEqual(2, len(missing_core))
|
||||||
|
self.assertIn('core1', missing_core)
|
||||||
|
self.assertIn('core2', missing_core)
|
||||||
|
|
||||||
|
def test_core_extensions_present(self):
|
||||||
|
self.stubs.Set(stevedore.enabled, 'EnabledExtensionManager',
|
||||||
|
fake_stevedore_enabled_extensions)
|
||||||
|
self.stubs.Set(plugins, 'LoadedExtensionInfo',
|
||||||
|
fake_loaded_extension_info)
|
||||||
|
v21_core = openstack.API_V3_CORE_EXTENSIONS
|
||||||
|
openstack.API_V3_CORE_EXTENSIONS = set(['core1', 'core2'])
|
||||||
|
self.addCleanup(self._set_v21_core, v21_core)
|
||||||
|
# if no core API extensions are missing then an exception will
|
||||||
|
# not be raised when creating an instance of compute.APIRouterV21
|
||||||
|
compute.APIRouterV21()
|
||||||
|
|
||||||
|
def test_core_extensions_missing(self):
|
||||||
|
self.stubs.Set(stevedore.enabled, 'EnabledExtensionManager',
|
||||||
|
fake_stevedore_enabled_extensions)
|
||||||
|
self.stubs.Set(plugins, 'LoadedExtensionInfo',
|
||||||
|
fake_loaded_extension_info)
|
||||||
|
self.assertRaises(exception.CoreAPIMissing, compute.APIRouterV21)
|
||||||
|
|
||||||
|
def test_extensions_expected_error(self):
|
||||||
|
@extensions.expected_errors(404)
|
||||||
|
def fake_func():
|
||||||
|
raise webob.exc.HTTPNotFound()
|
||||||
|
|
||||||
|
self.assertRaises(webob.exc.HTTPNotFound, fake_func)
|
||||||
|
|
||||||
|
def test_extensions_expected_error_from_list(self):
|
||||||
|
@extensions.expected_errors((404, 403))
|
||||||
|
def fake_func():
|
||||||
|
raise webob.exc.HTTPNotFound()
|
||||||
|
|
||||||
|
self.assertRaises(webob.exc.HTTPNotFound, fake_func)
|
||||||
|
|
||||||
|
def test_extensions_unexpected_error(self):
|
||||||
|
@extensions.expected_errors(404)
|
||||||
|
def fake_func():
|
||||||
|
raise webob.exc.HTTPConflict()
|
||||||
|
|
||||||
|
self.assertRaises(webob.exc.HTTPInternalServerError, fake_func)
|
||||||
|
|
||||||
|
def test_extensions_unexpected_error_from_list(self):
|
||||||
|
@extensions.expected_errors((404, 413))
|
||||||
|
def fake_func():
|
||||||
|
raise webob.exc.HTTPConflict()
|
||||||
|
|
||||||
|
self.assertRaises(webob.exc.HTTPInternalServerError, fake_func)
|
||||||
|
|
||||||
|
def test_extensions_unexpected_policy_not_authorized_error(self):
|
||||||
|
@extensions.expected_errors(404)
|
||||||
|
def fake_func():
|
||||||
|
raise exception.PolicyNotAuthorized(action="foo")
|
||||||
|
|
||||||
|
self.assertRaises(exception.PolicyNotAuthorized, fake_func)
|
@@ -173,9 +173,9 @@ class TestPipeLineFactory(test.NoDBTestCase):
|
|||||||
TestPipeLineFactory.FakeLoader(), None, noauth=fake_pipeline)
|
TestPipeLineFactory.FakeLoader(), None, noauth=fake_pipeline)
|
||||||
self._test_pipeline(fake_pipeline, app)
|
self._test_pipeline(fake_pipeline, app)
|
||||||
|
|
||||||
def test_pipeline_factory_v3(self):
|
def test_pipeline_factory_v21(self):
|
||||||
fake_pipeline = 'test1 test2 test3'
|
fake_pipeline = 'test1 test2 test3'
|
||||||
app = nova.api.auth.pipeline_factory_v3(
|
app = nova.api.auth.pipeline_factory_v21(
|
||||||
TestPipeLineFactory.FakeLoader(), None, noauth=fake_pipeline)
|
TestPipeLineFactory.FakeLoader(), None, noauth=fake_pipeline)
|
||||||
self._test_pipeline(fake_pipeline, app)
|
self._test_pipeline(fake_pipeline, app)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user