Merge "Load wsgi app(api) with paste.deploy"

This commit is contained in:
Jenkins 2016-03-09 17:36:45 +00:00 committed by Gerrit Code Review
commit 9bd983c3a7
15 changed files with 177 additions and 145 deletions

View File

@ -44,6 +44,7 @@ MAGNUM_AUTH_CACHE_DIR=${MAGNUM_AUTH_CACHE_DIR:-/var/cache/magnum}
MAGNUM_CONF_DIR=/etc/magnum
MAGNUM_CONF=$MAGNUM_CONF_DIR/magnum.conf
MAGNUM_POLICY_JSON=$MAGNUM_CONF_DIR/policy.json
MAGNUM_API_PASTE=$MAGNUM_CONF_DIR/api-paste.ini
if is_ssl_enabled_service "magnum" || is_service_enabled tls-proxy; then
MAGNUM_SERVICE_PROTOCOL="https"
@ -95,6 +96,8 @@ function configure_magnum {
# Rebuild the config file from scratch
create_magnum_conf
create_api_paste_conf
update_heat_policy
}
@ -206,6 +209,11 @@ function create_magnum_conf {
iniset $MAGNUM_CONF cinder_client region_name $REGION_NAME
}
function create_api_paste_conf {
# copy api_paste.ini
cp $MAGNUM_DIR/etc/magnum/api-paste.ini $MAGNUM_API_PASTE
}
function update_heat_policy {
# enable stacks globel_index search so that magnum can use
# list(global_tenant=True)

19
etc/magnum/api-paste.ini Normal file
View File

@ -0,0 +1,19 @@
[pipeline:main]
pipeline = cors request_id authtoken api_v1
[app:api_v1]
paste.app_factory = magnum.api.app:app_factory
[filter:authtoken]
acl_public_routes = /, /v1
paste.filter_factory = magnum.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 = magnum
latent_allow_methods = GET, PUT, POST, DELETE, PATCH
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

@ -9,14 +9,16 @@
# 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 os
from oslo_config import cfg
from oslo_middleware import cors
from oslo_log import log
from paste import deploy
import pecan
from magnum.api import auth
from magnum.api import config as api_config
from magnum.api import middleware
from magnum.i18n import _
# Register options for the service
API_SERVICE_OPTS = [
@ -29,7 +31,11 @@ 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
@ -38,6 +44,8 @@ opt_group = cfg.OptGroup(name='api',
CONF.register_group(opt_group)
CONF.register_opts(API_SERVICE_OPTS, opt_group)
LOG = log.getLogger(__name__)
def get_pecan_config():
# Set up the pecan configuration
@ -58,17 +66,22 @@ def setup_app(config=None):
**app_conf
)
app = auth.install(app, CONF, config.app.acl_public_routes)
# CORS must be the last one.
app = cors.CORS(app, CONF)
app.set_latent(
allow_headers=['X-Auth-Token', 'X-Identity-Status', 'X-Roles',
'X-Service-Catalog', 'X-User-Id', 'X-Tenant-Id',
'X-OpenStack-Request-ID', 'X-Server-Management-Url'],
allow_methods=['GET', 'PUT', 'POST', 'DELETE', 'PATCH'],
expose_headers=['X-Auth-Token', 'X-Subject-Token', 'X-Service-Token',
'X-OpenStack-Request-ID', 'X-Server-Management-Url']
)
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_paste_config])
LOG.info(_("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

@ -1,46 +0,0 @@
# Copyright 2012 New Dream Network, LLC (DreamHost)
#
# 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.
"""Access Control Lists (ACL's) control access the API server."""
from oslo_config import cfg
from magnum.api.middleware import auth_token
AUTH_OPTS = [
cfg.BoolOpt('enable_authentication',
default=True,
help='This option enables or disables user authentication '
'via Keystone. Default value is True.'),
]
CONF = cfg.CONF
CONF.register_opts(AUTH_OPTS)
def install(app, conf, public_routes):
"""Install ACL check on application.
:param app: A WSGI application.
:param conf: Settings. Dict'ified and passed to keystone middleware
:param public_routes: The list of the routes which will be allowed to
access without authentication.
:return: The same WSGI application with ACL installed.
"""
if not cfg.CONF.get('enable_authentication'):
return app
return auth_token.AuthTokenMiddleware(app,
conf=dict(conf),
public_api_routes=public_routes)

View File

@ -58,3 +58,12 @@ class AuthTokenMiddleware(auth_token.AuthProtocol):
return self._app(env, start_response)
return super(AuthTokenMiddleware, self).__call__(env, start_response)
@classmethod
def factory(cls, global_config, **local_conf):
public_routes = local_conf.get('acl_public_routes', '')
public_api_routes = [path.strip() for path in public_routes.split(',')]
def _factory(app):
return cls(app, global_config, public_api_routes=public_api_routes)
return _factory

View File

@ -40,7 +40,7 @@ def main():
# Enable object backporting via the conductor
base.MagnumObject.indirection_api = base.MagnumObjectIndirectionAPI()
app = api_app.setup_app()
app = api_app.load_app()
# Create the WSGI server and start it
host, port = cfg.CONF.api.host, cfg.CONF.api.port

View File

@ -16,7 +16,6 @@
import itertools
import magnum.api.app
import magnum.api.auth
import magnum.api.validation
import magnum.common.cert_manager
from magnum.common.cert_manager import local_cert_manager
@ -35,8 +34,7 @@ import magnum.db
def list_opts():
return [
('DEFAULT',
itertools.chain(magnum.api.auth.AUTH_OPTS,
magnum.common.paths.PATH_OPTS,
itertools.chain(magnum.common.paths.PATH_OPTS,
magnum.common.utils.UTILS_OPTS,
magnum.common.rpc_service.periodic_opts,
magnum.common.service.service_opts,

View File

@ -54,8 +54,6 @@ class FunctionalTest(base.DbTestCase):
'modules': ['magnum.api'],
'static_root': '%s/public' % root_dir,
'template_path': '%s/api/templates' % root_dir,
'enable_acl': False,
'acl_public_routes': ['/', '/v1'],
'hooks': [
hooks.ContextHook(),
hooks.RPCHook(),
@ -83,12 +81,10 @@ class FunctionalTest(base.DbTestCase):
for attr in attrs:
verify_method(attr, response)
def _make_app(self, config=None, enable_acl=False):
def _make_app(self, config=None):
if not config:
config = self.config
config["app"]["enable_acl"] = enable_acl
return pecan.testing.load_test_app(config)
def _request_json(self, path, params, expect_errors=False, headers=None,

View File

@ -0,0 +1,18 @@
[pipeline:main]
pipeline = cors request_id authtoken api_v1
[app:api_v1]
paste.app_factory = magnum.api.app:app_factory
[filter:authtoken]
paste.filter_factory = magnum.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 = magnum
latent_allow_methods = GET, PUT, POST, DELETE, PATCH
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 = magnum.api.app:app_factory
[filter:authtoken]
acl_public_routes = /
paste.filter_factory = magnum.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 = magnum
latent_allow_methods = GET, PUT, POST, DELETE, PATCH
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 = magnum.api.app:app_factory
[filter:authtoken]
acl_public_routes = /v1
paste.filter_factory = magnum.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 = magnum
latent_allow_methods = GET, PUT, POST, DELETE, PATCH
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 = magnum.api.app:app_factory
[filter:authtoken]
acl_public_routes = /
paste.filter_factory = magnum.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 = magnum
latent_allow_methods = GET, PUT, POST, DELETE, PATCH
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

@ -10,12 +10,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
import mock
from oslo_config import cfg
from webob import exc as webob_exc
import webtest
from magnum.api import app
from magnum.api.controllers import v1 as v1_api
from magnum.tests import base as test_base
from magnum.tests.unit.api import base as api_base
@ -84,6 +85,11 @@ class TestRootController(api_base.FunctionalTest):
{u'href': u'http://localhost/mservices/',
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)
@ -96,13 +102,10 @@ class TestRootController(api_base.FunctionalTest):
response = self.app.get('/a/bogus/url', expect_errors=True)
assert response.status_int == 404
def test_acl_access_with_all(self):
cfg.CONF.set_override("enable_authentication", True)
config = copy.deepcopy(self.config)
# Both / and /v1 and access without auth
config["app"]["acl_public_routes"] = ['/', '/v1']
app = self._make_app(config=config)
def test_noauth(self):
# Don't need to auth
paste_file = "magnum/tests/unit/api/controllers/noauth-paste.ini"
app = self.make_app(paste_file)
response = app.get('/')
self.assertEqual(self.root_expected, response.json)
@ -110,13 +113,24 @@ class TestRootController(api_base.FunctionalTest):
response = app.get('/v1/')
self.assertEqual(self.v1_expected, response.json)
def test_acl_access_with_root(self):
cfg.CONF.set_override("enable_authentication", True)
response = app.get('/v1/baymodels')
self.assertEqual(200, response.status_int)
config = copy.deepcopy(self.config)
def test_auth_with_no_public_routes(self):
# All apis need auth when access
paste_file = "magnum/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
config["app"]["acl_public_routes"] = ['/']
app = self._make_app(config=config)
paste_file = "magnum/tests/unit/api/controllers/auth-root-access.ini"
app = self.make_app(paste_file)
response = app.get('/')
self.assertEqual(self.root_expected, response.json)
@ -124,13 +138,13 @@ class TestRootController(api_base.FunctionalTest):
response = app.get('/v1/', expect_errors=True)
self.assertEqual(401, response.status_int)
def test_acl_access_with_v1(self):
cfg.CONF.set_override("enable_authentication", True)
response = app.get('/v1/baymodels', expect_errors=True)
self.assertEqual(401, response.status_int)
config = copy.deepcopy(self.config)
def test_auth_with_v1_access(self):
# Only /v1 can access without auth
config["app"]["acl_public_routes"] = ['/v1']
app = self._make_app(config=config)
paste_file = "magnum/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)
@ -138,17 +152,7 @@ class TestRootController(api_base.FunctionalTest):
response = app.get('/v1/')
self.assertEqual(self.v1_expected, response.json)
def test_acl_with_neither(self):
cfg.CONF.set_override("enable_authentication", True)
config = copy.deepcopy(self.config)
config["app"]["acl_public_routes"] = []
app = self._make_app(config=config)
response = app.get('/', expect_errors=True)
self.assertEqual(401, response.status_int)
response = app.get('/v1/', expect_errors=True)
response = app.get('/v1/baymodels', expect_errors=True)
self.assertEqual(401, response.status_int)

View File

@ -1,41 +0,0 @@
# Copyright 2014
# The Cloudscaling Group, Inc.
#
# 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 mock
from oslo_config import fixture
from magnum.api import auth
from magnum.tests import base
from magnum.tests import fakes
@mock.patch('magnum.api.middleware.auth_token.AuthTokenMiddleware',
new_callable=fakes.FakeAuthProtocol)
class TestAuth(base.BaseTestCase):
def setUp(self):
super(TestAuth, self).setUp()
self.CONF = self.useFixture(fixture.Config())
self.app = fakes.FakeApp()
def test_check_auth_option_enabled(self, mock_auth):
self.CONF.config(enable_authentication=True)
result = auth.install(self.app, self.CONF.conf, ['/'])
self.assertIsInstance(result, fakes.FakeAuthProtocol)
def test_check_auth_option_disabled(self, mock_auth):
self.CONF.config(enable_authentication=False)
result = auth.install(self.app, self.CONF.conf, ['/'])
self.assertIsInstance(result, fakes.FakeApp)

View File

@ -31,8 +31,6 @@ from magnum.tests import base
CONF = cfg.CONF
CONF.import_opt('enable_authentication', 'magnum.api.auth')
_DB_CACHE = None
@ -88,7 +86,6 @@ class Database(fixtures.Fixture):
class DbTestCase(base.TestCase):
def setUp(self):
cfg.CONF.set_override("enable_authentication", False)
super(DbTestCase, self).setUp()
self.dbapi = dbapi.get_instance()