Replace hard coded WSGI application creation
This change replaces the hard coded WSGI app creation with a pipeline of WSGI apps declared in a configuration file. Paste Deploy was used to create the pipeline since it is used by many other OpenStack projects and it is an active project with new contributors and supports Python 3. Dependency on Paste is localized so switching to another library would not be hard if OpenStack moves to another package in the future. The changes are small but the changes for the tests were large since many acl tests were assuming a hard coded WSGI app creation. blueprint declarative-filters Change-Id: I5ce05eab980271873269eca2945dc809f2923045
This commit is contained in:
parent
fd5d344b48
commit
feb409eda0
@ -19,29 +19,8 @@
|
||||
"""Access Control Lists (ACL's) control access the API server."""
|
||||
|
||||
from ceilometer.openstack.common import policy
|
||||
from keystoneclient.middleware import auth_token
|
||||
from oslo.config import cfg
|
||||
|
||||
|
||||
_ENFORCER = None
|
||||
OPT_GROUP_NAME = 'keystone_authtoken'
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
"""Register keystoneclient middleware options
|
||||
"""
|
||||
conf.register_opts(auth_token.opts,
|
||||
group=OPT_GROUP_NAME)
|
||||
auth_token.CONF = conf
|
||||
|
||||
|
||||
register_opts(cfg.CONF)
|
||||
|
||||
|
||||
def install(app, conf):
|
||||
"""Install ACL check on application."""
|
||||
return auth_token.AuthProtocol(app,
|
||||
conf=dict(conf.get(OPT_GROUP_NAME)))
|
||||
|
||||
|
||||
def get_limited_to(headers):
|
||||
|
@ -23,9 +23,9 @@ from wsgiref import simple_server
|
||||
|
||||
import netaddr
|
||||
from oslo.config import cfg
|
||||
from paste import deploy
|
||||
import pecan
|
||||
|
||||
from ceilometer.api import acl
|
||||
from ceilometer.api import config as api_config
|
||||
from ceilometer.api import hooks
|
||||
from ceilometer.api import middleware
|
||||
@ -35,12 +35,13 @@ from ceilometer import storage
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
auth_opts = [
|
||||
cfg.StrOpt('auth_strategy',
|
||||
default='keystone',
|
||||
help='The strategy to use for auth: noauth or keystone.'),
|
||||
cfg.BoolOpt('enable_v1_api',
|
||||
default=True,
|
||||
help='Deploy the deprecated v1 API.'),
|
||||
cfg.StrOpt('api_paste_config',
|
||||
default="api_paste.ini",
|
||||
help="Configuration file for WSGI definition of API."
|
||||
),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
@ -80,9 +81,6 @@ def setup_app(pecan_config=None, extra_hooks=None):
|
||||
guess_content_type_from_ext=False
|
||||
)
|
||||
|
||||
if getattr(pecan_config.app, 'enable_acl', True):
|
||||
return acl.install(app, cfg.CONF)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
@ -90,10 +88,9 @@ class VersionSelectorApplication(object):
|
||||
def __init__(self):
|
||||
pc = get_pecan_config()
|
||||
pc.app.debug = CONF.debug
|
||||
pc.app.enable_acl = (CONF.auth_strategy == 'keystone')
|
||||
if cfg.CONF.enable_v1_api:
|
||||
from ceilometer.api.v1 import app as v1app
|
||||
self.v1 = v1app.make_app(cfg.CONF, enable_acl=pc.app.enable_acl)
|
||||
self.v1 = v1app.make_app(cfg.CONF)
|
||||
else:
|
||||
def not_found(environ, start_response):
|
||||
start_response('404 Not Found', [])
|
||||
@ -136,14 +133,36 @@ def get_handler_cls():
|
||||
return CeilometerHandler
|
||||
|
||||
|
||||
def build_server():
|
||||
def load_app():
|
||||
# Build the WSGI app
|
||||
root = VersionSelectorApplication()
|
||||
cfg_file = cfg.CONF.api_paste_config
|
||||
LOG.info("WSGI config requested: %s" % cfg_file)
|
||||
if not os.path.exists(cfg_file):
|
||||
# this code is to work around chicken-egg dependency between
|
||||
# ceilometer gate jobs use of devstack and this change.
|
||||
# The gate job uses devstack to run tempest.
|
||||
# devstack does not copy api_paste.ini into /etc/ceilometer because it
|
||||
# is introduced in this change. Once this is merged, we will change
|
||||
# devstack to copy api_paste.ini and once that is merged will remove
|
||||
# this code.
|
||||
root = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||
'..', '..', 'etc', 'ceilometer'
|
||||
)
|
||||
)
|
||||
cfg_file = os.path.join(root, cfg_file)
|
||||
if not os.path.exists(cfg_file):
|
||||
raise Exception('api_paste_config file not found')
|
||||
LOG.info("Full WSGI config used: %s" % cfg_file)
|
||||
return deploy.loadapp("config:" + cfg_file)
|
||||
|
||||
|
||||
def build_server():
|
||||
app = load_app()
|
||||
# Create the WSGI server and start it
|
||||
host, port = cfg.CONF.api.host, cfg.CONF.api.port
|
||||
server_cls = get_server_cls(host)
|
||||
srv = simple_server.make_server(host, port, root,
|
||||
|
||||
srv = simple_server.make_server(host, port, app,
|
||||
server_cls, get_handler_cls())
|
||||
|
||||
LOG.info(_('Starting server in PID %s') % os.getpid())
|
||||
@ -157,4 +176,9 @@ def build_server():
|
||||
else:
|
||||
LOG.info(_("serving on http://%(host)s:%(port)s") % (
|
||||
{'host': host, 'port': port}))
|
||||
|
||||
return srv
|
||||
|
||||
|
||||
def app_factory(global_config, **local_conf):
|
||||
return VersionSelectorApplication()
|
||||
|
@ -20,11 +20,9 @@
|
||||
|
||||
See http://pecan.readthedocs.org/en/latest/deployment.html for details.
|
||||
"""
|
||||
|
||||
from ceilometer import service
|
||||
from ceilometer.api import app
|
||||
|
||||
# Initialize the oslo configuration library and logging
|
||||
service.prepare_service([])
|
||||
|
||||
application = app.VersionSelectorApplication()
|
||||
application = app.load_app()
|
||||
|
@ -20,7 +20,6 @@
|
||||
import flask
|
||||
from oslo.config import cfg
|
||||
|
||||
from ceilometer.api import acl
|
||||
from ceilometer.api.v1 import blueprint as v1_blueprint
|
||||
from ceilometer.openstack.common import jsonutils
|
||||
from ceilometer import storage
|
||||
@ -32,7 +31,7 @@ class JSONEncoder(flask.json.JSONEncoder):
|
||||
return jsonutils.to_primitive(o)
|
||||
|
||||
|
||||
def make_app(conf, enable_acl=True, attach_storage=True,
|
||||
def make_app(conf, attach_storage=True,
|
||||
sources_file='sources.json'):
|
||||
app = flask.Flask('ceilometer.api')
|
||||
app.register_blueprint(v1_blueprint.blueprint, url_prefix='/v1')
|
||||
@ -56,11 +55,7 @@ def make_app(conf, enable_acl=True, attach_storage=True,
|
||||
flask.request.storage_conn = \
|
||||
storage.get_connection(conf)
|
||||
|
||||
# Install the middleware wrapper
|
||||
if enable_acl:
|
||||
app.wsgi_app = acl.install(app.wsgi_app, conf)
|
||||
|
||||
return app
|
||||
|
||||
# For documentation
|
||||
app = make_app(cfg.CONF, enable_acl=False, attach_storage=False)
|
||||
app = make_app(cfg.CONF, attach_storage=False)
|
||||
|
@ -23,7 +23,6 @@ import pecan
|
||||
import pecan.testing
|
||||
from six.moves import urllib
|
||||
|
||||
from ceilometer.api import acl
|
||||
from ceilometer.api.v1 import app as v1_app
|
||||
from ceilometer.api.v1 import blueprint as v1_blueprint
|
||||
from ceilometer import messaging
|
||||
@ -31,6 +30,8 @@ from ceilometer.openstack.common import jsonutils
|
||||
from ceilometer import service
|
||||
from ceilometer.tests import db as db_test_base
|
||||
|
||||
OPT_GROUP_NAME = 'keystone_authtoken'
|
||||
|
||||
|
||||
class TestBase(db_test_base.TestBase):
|
||||
"""Use only for v1 API tests.
|
||||
@ -42,12 +43,11 @@ class TestBase(db_test_base.TestBase):
|
||||
self.addCleanup(messaging.cleanup)
|
||||
service.prepare_service([])
|
||||
self.CONF.set_override("auth_version",
|
||||
"v2.0", group=acl.OPT_GROUP_NAME)
|
||||
"v2.0", group=OPT_GROUP_NAME)
|
||||
self.CONF.set_override("policy_file",
|
||||
self.path_get('etc/ceilometer/policy.json'))
|
||||
sources_file = self.path_get('ceilometer/tests/sources.json')
|
||||
self.app = v1_app.make_app(self.CONF,
|
||||
enable_acl=False,
|
||||
attach_storage=False,
|
||||
sources_file=sources_file)
|
||||
|
||||
@ -90,7 +90,7 @@ class FunctionalTest(db_test_base.TestBase):
|
||||
self.addCleanup(messaging.cleanup)
|
||||
super(FunctionalTest, self).setUp()
|
||||
self.CONF.set_override("auth_version", "v2.0",
|
||||
group=acl.OPT_GROUP_NAME)
|
||||
group=OPT_GROUP_NAME)
|
||||
self.CONF.set_override("policy_file",
|
||||
self.path_get('etc/ceilometer/policy.json'))
|
||||
self.app = self._make_app()
|
||||
|
@ -19,13 +19,15 @@
|
||||
"""
|
||||
import os
|
||||
|
||||
from ceilometer.api import acl
|
||||
from keystoneclient.middleware import auth_token
|
||||
|
||||
from ceilometer.api.v1 import app
|
||||
from ceilometer import messaging
|
||||
from ceilometer.openstack.common import fileutils
|
||||
from ceilometer.openstack.common.fixture import config
|
||||
from ceilometer.openstack.common import test
|
||||
from ceilometer import service
|
||||
from ceilometer.tests import api as acl
|
||||
|
||||
|
||||
class TestApp(test.BaseTestCase):
|
||||
@ -44,7 +46,10 @@ class TestApp(test.BaseTestCase):
|
||||
self.CONF.set_override("auth_uri", None,
|
||||
group=acl.OPT_GROUP_NAME)
|
||||
api_app = app.make_app(self.CONF, attach_storage=False)
|
||||
self.assertTrue(api_app.wsgi_app.auth_uri.startswith('file'))
|
||||
conf = dict(self.CONF.get(acl.OPT_GROUP_NAME))
|
||||
api_app = auth_token.AuthProtocol(api_app,
|
||||
conf=conf)
|
||||
self.assertTrue(api_app.auth_uri.startswith('file'))
|
||||
|
||||
def test_keystone_middleware_parse_conffile(self):
|
||||
content = "[{0}]\nauth_protocol = file"\
|
||||
@ -55,5 +60,8 @@ class TestApp(test.BaseTestCase):
|
||||
service.prepare_service(['ceilometer-api',
|
||||
'--config-file=%s' % tmpfile])
|
||||
api_app = app.make_app(self.CONF, attach_storage=False)
|
||||
self.assertTrue(api_app.wsgi_app.auth_uri.startswith('file'))
|
||||
conf = dict(self.CONF.get(acl.OPT_GROUP_NAME))
|
||||
api_app = auth_token.AuthProtocol(api_app,
|
||||
conf=conf)
|
||||
self.assertTrue(api_app.auth_uri.startswith('file'))
|
||||
os.unlink(tmpfile)
|
||||
|
@ -20,11 +20,14 @@
|
||||
import datetime
|
||||
import json
|
||||
|
||||
from ceilometer.api import acl
|
||||
import webtest
|
||||
|
||||
from ceilometer.api import app
|
||||
from ceilometer.api.controllers import v2 as v2_api
|
||||
from ceilometer.openstack.common import timeutils
|
||||
from ceilometer.publisher import utils
|
||||
from ceilometer import sample
|
||||
from ceilometer.tests import api as acl
|
||||
from ceilometer.tests.api.v2 import FunctionalTest
|
||||
from ceilometer.tests import db as tests_db
|
||||
|
||||
@ -115,7 +118,9 @@ class TestAPIACL(FunctionalTest,
|
||||
|
||||
def _make_app(self):
|
||||
self.CONF.set_override("cache", "fake.cache", group=acl.OPT_GROUP_NAME)
|
||||
return super(TestAPIACL, self)._make_app(enable_acl=True)
|
||||
file_name = self.path_get('etc/ceilometer/api_paste.ini')
|
||||
self.CONF.set_override("api_paste_config", file_name)
|
||||
return webtest.TestApp(app.load_app())
|
||||
|
||||
def test_non_authenticated(self):
|
||||
response = self.get_json('/meters', expect_errors=True)
|
||||
|
@ -24,12 +24,12 @@ import os
|
||||
import mock
|
||||
import wsme
|
||||
|
||||
from ceilometer.api import acl
|
||||
from ceilometer.api import app
|
||||
from ceilometer.openstack.common import fileutils
|
||||
from ceilometer.openstack.common.fixture import config
|
||||
from ceilometer.openstack.common import gettextutils
|
||||
from ceilometer import service
|
||||
from ceilometer.tests import api as acl
|
||||
from ceilometer.tests.api.v2 import FunctionalTest
|
||||
from ceilometer.tests import base
|
||||
from ceilometer.tests import db as tests_db
|
||||
@ -50,18 +50,23 @@ class TestApp(base.BaseTestCase):
|
||||
self.path_get("etc/ceilometer/pipeline.yaml"))
|
||||
self.CONF.set_override('connection', "log://", group="database")
|
||||
self.CONF.set_override("auth_uri", None, group=acl.OPT_GROUP_NAME)
|
||||
file_name = self.path_get('etc/ceilometer/api_paste.ini')
|
||||
self.CONF.set_override("api_paste_config", file_name)
|
||||
|
||||
api_app = app.setup_app()
|
||||
api_app = app.load_app()
|
||||
self.assertTrue(api_app.auth_uri.startswith('file'))
|
||||
|
||||
def test_keystone_middleware_parse_conffile(self):
|
||||
pipeline_conf = self.path_get("etc/ceilometer/pipeline.yaml")
|
||||
api_conf = self.path_get('etc/ceilometer/api_paste.ini')
|
||||
content = "[DEFAULT]\n"\
|
||||
"rpc_backend = fake\n"\
|
||||
"pipeline_cfg_file = {0}\n"\
|
||||
"[{1}]\n"\
|
||||
"api_paste_config = {1}\n"\
|
||||
"[{2}]\n"\
|
||||
"auth_protocol = file\n"\
|
||||
"auth_version = v2.0\n".format(pipeline_conf,
|
||||
api_conf,
|
||||
acl.OPT_GROUP_NAME)
|
||||
|
||||
tmpfile = fileutils.write_to_tempfile(content=content,
|
||||
@ -70,7 +75,7 @@ class TestApp(base.BaseTestCase):
|
||||
service.prepare_service(['ceilometer-api',
|
||||
'--config-file=%s' % tmpfile])
|
||||
self.CONF.set_override('connection', "log://", group="database")
|
||||
api_app = app.setup_app()
|
||||
api_app = app.load_app()
|
||||
self.assertTrue(api_app.auth_uri.startswith('file'))
|
||||
os.unlink(tmpfile)
|
||||
|
||||
|
@ -105,6 +105,17 @@ class BinApiTestCase(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BinApiTestCase, self).setUp()
|
||||
# create api_paste.ini file without authentication
|
||||
content = \
|
||||
"[pipeline:main]\n"\
|
||||
"pipeline = api-server\n"\
|
||||
"[app:api-server]\n"\
|
||||
"paste.app_factory = ceilometer.api.app:app_factory\n"
|
||||
self.paste = fileutils.write_to_tempfile(content=content,
|
||||
prefix='api_paste',
|
||||
suffix='.ini')
|
||||
|
||||
# create ceilometer.conf file
|
||||
self.api_port = random.randint(10000, 11000)
|
||||
self.http = httplib2.Http()
|
||||
pipeline_cfg_file = self.path_get('etc/ceilometer/pipeline.yaml')
|
||||
@ -115,11 +126,13 @@ class BinApiTestCase(base.BaseTestCase):
|
||||
"debug=true\n"\
|
||||
"pipeline_cfg_file={0}\n"\
|
||||
"policy_file={1}\n"\
|
||||
"api_paste_config={2}\n"\
|
||||
"[api]\n"\
|
||||
"port={2}\n"\
|
||||
"port={3}\n"\
|
||||
"[database]\n"\
|
||||
"connection=log://localhost\n".format(pipeline_cfg_file,
|
||||
policy_file,
|
||||
self.paste,
|
||||
self.api_port)
|
||||
|
||||
self.tempfile = fileutils.write_to_tempfile(content=content,
|
||||
|
15
etc/ceilometer/api_paste.ini
Normal file
15
etc/ceilometer/api_paste.ini
Normal file
@ -0,0 +1,15 @@
|
||||
# Ceilometer API WSGI Pipeline
|
||||
# Define the filters that make up the pipeline for processing WSGI requests
|
||||
# Note: This pipeline is PasteDeploy's term rather than Ceilometer's pipeline
|
||||
# used for processing samples
|
||||
|
||||
# Remove authtoken from the pipeline if you don't want to use keystone authentication
|
||||
[pipeline:main]
|
||||
pipeline = authtoken api-server
|
||||
|
||||
[app:api-server]
|
||||
paste.app_factory = ceilometer.api.app:app_factory
|
||||
|
||||
[filter:authtoken]
|
||||
paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory
|
||||
|
@ -14,6 +14,7 @@ msgpack-python
|
||||
netaddr>=0.7.6
|
||||
oslo.config>=1.2.0
|
||||
oslo.vmware>=0.2 # Apache-2.0
|
||||
PasteDeploy>=1.5.0
|
||||
pbr>=0.6,<1.0
|
||||
pecan>=0.4.5
|
||||
posix_ipc
|
||||
|
Loading…
x
Reference in New Issue
Block a user