Load wsgi apps with paste.deploy
This change replaces the hard coded WSGI app creation with a pipeline of WSGI apps declared in a configuration file. Change-Id: I1969e3eec7be1c28b637451c71cfe450c91b959f Partially-Implements: blueprint support-multi-tenancy
This commit is contained in:
parent
e91ecb39bc
commit
7b6f108821
@ -0,0 +1,16 @@
|
||||
[pipeline:main]
|
||||
pipeline = cors request_id authtoken api_v1
|
||||
|
||||
[app:api_v1]
|
||||
paste.app_factory = zun.api.app:app_factory
|
||||
|
||||
[filter:authtoken]
|
||||
acl_public_routes = /, /v1
|
||||
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
|
@ -14,6 +14,7 @@ oslo.log>=1.14.0 # Apache-2.0
|
||||
oslo.concurrency>=3.8.0 # Apache-2.0
|
||||
oslo.config>=3.14.0 # Apache-2.0
|
||||
oslo.messaging>=5.2.0 # Apache-2.0
|
||||
oslo.middleware>=3.0.0 # Apache-2.0
|
||||
oslo.policy>=1.9.0 # Apache-2.0
|
||||
oslo.serialization>=1.10.0 # Apache-2.0
|
||||
oslo.service>=1.10.0 # Apache-2.0
|
||||
|
@ -54,6 +54,9 @@ console_scripts =
|
||||
oslo.config.opts =
|
||||
zun = zun.opts:list_opts
|
||||
|
||||
oslo.config.opts.defaults =
|
||||
zun = zun.common.config.set_cors_middleware_defaults
|
||||
|
||||
zun.database.migration_backend =
|
||||
sqlalchemy = zun.db.sqlalchemy.migration
|
||||
|
||||
|
@ -8,6 +8,7 @@ namespace = oslo.concurrency
|
||||
namespace = oslo.db
|
||||
namespace = oslo.log
|
||||
namespace = oslo.messaging
|
||||
namespace = oslo.middleware.cors
|
||||
namespace = oslo.policy
|
||||
namespace = oslo.service.periodic_task
|
||||
namespace = oslo.service.service
|
||||
|
@ -10,13 +10,18 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from paste import deploy
|
||||
import pecan
|
||||
|
||||
from zun.api import config as api_config
|
||||
from zun.api import middleware
|
||||
from zun.common import config as common_config
|
||||
from zun.common.i18n import _
|
||||
from zun.common.i18n import _LI
|
||||
|
||||
|
||||
# Register options for the service
|
||||
@ -41,7 +46,10 @@ API_SERVICE_OPTS = [
|
||||
cfg.IntOpt('max_limit',
|
||||
default=1000,
|
||||
help='The maximum number of items returned in a single '
|
||||
'response from a collection resource.')
|
||||
'response from a collection resource.'),
|
||||
cfg.StrOpt('api_paste_config',
|
||||
default="api-paste.ini",
|
||||
help="Configuration file for WSGI definition of API.")
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
@ -64,6 +72,7 @@ def setup_app(config=None):
|
||||
config = get_pecan_config()
|
||||
|
||||
app_conf = dict(config.app)
|
||||
common_config.set_config_defaults()
|
||||
|
||||
app = pecan.make_app(
|
||||
app_conf.pop('root'),
|
||||
@ -73,3 +82,21 @@ def setup_app(config=None):
|
||||
)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def load_app():
|
||||
cfg_file = None
|
||||
cfg_path = cfg.CONF.api.api_paste_config
|
||||
if not os.path.isabs(cfg_path):
|
||||
cfg_file = CONF.find_file(cfg_path)
|
||||
elif os.path.exists(cfg_path):
|
||||
cfg_file = cfg_path
|
||||
|
||||
if not cfg_file:
|
||||
raise cfg.ConfigFilesNotFoundError([cfg.CONF.api.api_paste_config])
|
||||
LOG.info(_LI("Full WSGI config used: %s"), cfg_file)
|
||||
return deploy.loadapp("config:" + cfg_file)
|
||||
|
||||
|
||||
def app_factory(global_config, **local_conf):
|
||||
return setup_app()
|
||||
|
@ -16,6 +16,7 @@
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_middleware import cors
|
||||
|
||||
from zun.common import rpc
|
||||
from zun import version
|
||||
@ -28,3 +29,31 @@ def parse_args(argv, default_config_files=None):
|
||||
version=version.version_info.release_string(),
|
||||
default_config_files=default_config_files)
|
||||
rpc.init(cfg.CONF)
|
||||
|
||||
|
||||
def set_config_defaults():
|
||||
"""This method updates all configuration default values."""
|
||||
set_cors_middleware_defaults()
|
||||
|
||||
|
||||
def set_cors_middleware_defaults():
|
||||
"""Update default configuration options for oslo.middleware."""
|
||||
cfg.set_defaults(cors.CORS_OPTS,
|
||||
allow_headers=['X-Auth-Token',
|
||||
'X-Identity-Status',
|
||||
'X-Roles',
|
||||
'X-Service-Catalog',
|
||||
'X-User-Id',
|
||||
'X-Project-Id',
|
||||
'X-OpenStack-Request-ID',
|
||||
'X-Server-Management-Url'],
|
||||
expose_headers=['X-Auth-Token',
|
||||
'X-Subject-Token',
|
||||
'X-Service-Token',
|
||||
'X-OpenStack-Request-ID',
|
||||
'X-Server-Management-Url'],
|
||||
allow_methods=['GET',
|
||||
'PUT',
|
||||
'POST',
|
||||
'DELETE',
|
||||
'PATCH'])
|
||||
|
@ -49,6 +49,7 @@ def prepare_service(argv=None):
|
||||
argv = []
|
||||
log.register_options(CONF)
|
||||
config.parse_args(argv)
|
||||
config.set_config_defaults()
|
||||
log.setup(CONF, 'zun')
|
||||
# TODO(yuanying): Uncomment after objects are implemented
|
||||
# objects.register_all()
|
||||
@ -69,7 +70,7 @@ class WSGIService(service.ServiceBase):
|
||||
:returns: None
|
||||
"""
|
||||
self.name = name
|
||||
self.app = app.setup_app()
|
||||
self.app = app.load_app()
|
||||
self.workers = (CONF.api.workers or processutils.get_worker_count())
|
||||
if self.workers and self.workers < 1:
|
||||
raise exception.ConfigInvalid(
|
||||
|
18
zun/tests/unit/api/controllers/auth-paste.ini
Normal file
18
zun/tests/unit/api/controllers/auth-paste.ini
Normal file
@ -0,0 +1,18 @@
|
||||
[pipeline:main]
|
||||
pipeline = cors request_id authtoken api_v1
|
||||
|
||||
[app:api_v1]
|
||||
paste.app_factory = zun.api.app:app_factory
|
||||
|
||||
[filter:authtoken]
|
||||
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
|
19
zun/tests/unit/api/controllers/auth-root-access.ini
Normal file
19
zun/tests/unit/api/controllers/auth-root-access.ini
Normal file
@ -0,0 +1,19 @@
|
||||
[pipeline:main]
|
||||
pipeline = cors request_id authtoken api_v1
|
||||
|
||||
[app:api_v1]
|
||||
paste.app_factory = zun.api.app:app_factory
|
||||
|
||||
[filter:authtoken]
|
||||
acl_public_routes = /
|
||||
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
|
19
zun/tests/unit/api/controllers/auth-v1-access.ini
Normal file
19
zun/tests/unit/api/controllers/auth-v1-access.ini
Normal file
@ -0,0 +1,19 @@
|
||||
[pipeline:main]
|
||||
pipeline = cors request_id authtoken api_v1
|
||||
|
||||
[app:api_v1]
|
||||
paste.app_factory = zun.api.app:app_factory
|
||||
|
||||
[filter:authtoken]
|
||||
acl_public_routes = /v1
|
||||
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
|
19
zun/tests/unit/api/controllers/noauth-paste.ini
Normal file
19
zun/tests/unit/api/controllers/noauth-paste.ini
Normal file
@ -0,0 +1,19 @@
|
||||
[pipeline:main]
|
||||
pipeline = cors request_id api_v1
|
||||
|
||||
[app:api_v1]
|
||||
paste.app_factory = zun.api.app:app_factory
|
||||
|
||||
[filter:authtoken]
|
||||
acl_public_routes = /
|
||||
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
|
123
zun/tests/unit/api/controllers/test_root.py
Normal file
123
zun/tests/unit/api/controllers/test_root.py
Normal file
@ -0,0 +1,123 @@
|
||||
# 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 webtest
|
||||
|
||||
from zun.api import app
|
||||
from zun.tests.unit.api import base as api_base
|
||||
|
||||
|
||||
class TestRootController(api_base.FunctionalTest):
|
||||
def setUp(self):
|
||||
super(TestRootController, self).setUp()
|
||||
self.root_expected = {
|
||||
u'default_version':
|
||||
{u'id': u'v1', u'links':
|
||||
[{u'href': u'http://localhost/v1/', u'rel': u'self'}]},
|
||||
u'description': u'Zun is an OpenStack project which '
|
||||
'aims to provide container management.',
|
||||
u'versions': [{u'id': u'v1',
|
||||
u'links':
|
||||
[{u'href': u'http://localhost/v1/',
|
||||
u'rel': u'self'}]}]}
|
||||
|
||||
self.v1_expected = {
|
||||
u'media_types':
|
||||
[{u'base': u'application/json',
|
||||
u'type': u'application/vnd.openstack.zun.v1+json'}],
|
||||
u'links': [{u'href': u'http://localhost/v1/',
|
||||
u'rel': u'self'},
|
||||
{u'href':
|
||||
u'http://docs.openstack.org/developer'
|
||||
'/zun/dev/api-spec-v1.html',
|
||||
u'type': u'text/html', u'rel': u'describedby'}],
|
||||
u'services': [{u'href': u'http://localhost/v1/services/',
|
||||
u'rel': u'self'},
|
||||
{u'href': u'http://localhost/services/',
|
||||
u'rel': u'bookmark'}],
|
||||
u'id': u'v1',
|
||||
u'containers': [{u'href': u'http://localhost/v1/containers/',
|
||||
u'rel': u'self'},
|
||||
{u'href': u'http://localhost/containers/',
|
||||
u'rel': u'bookmark'}]}
|
||||
|
||||
def make_app(self, paste_file):
|
||||
file_name = self.get_path(paste_file)
|
||||
cfg.CONF.set_override("api_paste_config", file_name, group="api")
|
||||
return webtest.TestApp(app.load_app())
|
||||
|
||||
def test_version(self):
|
||||
response = self.app.get('/')
|
||||
self.assertEqual(self.root_expected, response.json)
|
||||
|
||||
def test_v1_controller(self):
|
||||
response = self.app.get('/v1/')
|
||||
self.assertEqual(self.v1_expected, response.json)
|
||||
|
||||
def test_get_not_found(self):
|
||||
response = self.app.get('/a/bogus/url', expect_errors=True)
|
||||
assert response.status_int == 404
|
||||
|
||||
def test_noauth(self):
|
||||
# Don't need to auth
|
||||
paste_file = "zun/tests/unit/api/controllers/noauth-paste.ini"
|
||||
app = self.make_app(paste_file)
|
||||
|
||||
response = app.get('/')
|
||||
self.assertEqual(self.root_expected, response.json)
|
||||
|
||||
response = app.get('/v1/')
|
||||
self.assertEqual(self.v1_expected, response.json)
|
||||
|
||||
response = app.get('/v1/containers/')
|
||||
self.assertEqual(200, response.status_int)
|
||||
|
||||
def test_auth_with_no_public_routes(self):
|
||||
# All apis need auth when access
|
||||
paste_file = "zun/tests/unit/api/controllers/auth-paste.ini"
|
||||
app = self.make_app(paste_file)
|
||||
|
||||
response = app.get('/', expect_errors=True)
|
||||
self.assertEqual(401, response.status_int)
|
||||
|
||||
response = app.get('/v1/', expect_errors=True)
|
||||
self.assertEqual(401, response.status_int)
|
||||
|
||||
def test_auth_with_root_access(self):
|
||||
# Only / can access without auth
|
||||
paste_file = "zun/tests/unit/api/controllers/auth-root-access.ini"
|
||||
app = self.make_app(paste_file)
|
||||
|
||||
response = app.get('/')
|
||||
self.assertEqual(self.root_expected, response.json)
|
||||
|
||||
response = app.get('/v1/', expect_errors=True)
|
||||
self.assertEqual(401, response.status_int)
|
||||
|
||||
response = app.get('/v1/containers', expect_errors=True)
|
||||
self.assertEqual(401, response.status_int)
|
||||
|
||||
def test_auth_with_v1_access(self):
|
||||
# Only /v1 can access without auth
|
||||
paste_file = "zun/tests/unit/api/controllers/auth-v1-access.ini"
|
||||
app = self.make_app(paste_file)
|
||||
|
||||
response = app.get('/', expect_errors=True)
|
||||
self.assertEqual(401, response.status_int)
|
||||
|
||||
response = app.get('/v1/')
|
||||
self.assertEqual(self.v1_expected, response.json)
|
||||
|
||||
response = app.get('/v1/containers', expect_errors=True)
|
||||
self.assertEqual(401, response.status_int)
|
Loading…
Reference in New Issue
Block a user