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:
Wenzhi Yu 2016-08-19 13:19:10 +08:00
parent e91ecb39bc
commit 7b6f108821
12 changed files with 278 additions and 2 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View 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

View 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

View 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

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