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:
Ken'ichi Ohmichi
2014-08-11 12:22:09 +00:00
parent 3ab81c9b3e
commit e6a642f076
6 changed files with 246 additions and 11 deletions

View File

@@ -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

View File

@@ -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."""

View File

@@ -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):

View File

@@ -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)

View 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)

View File

@@ -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)