Switch from Flask's WSGI Server to OpenStack Commons + Use PasteDeploy for easier Keystone integration.
This commit is contained in:
parent
79dda034de
commit
5d9513349e
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,5 +11,6 @@ venv
|
||||
*.sqlite
|
||||
var/*
|
||||
etc/*.conf
|
||||
etc/*.ini
|
||||
AUTHORS
|
||||
ChangeLog
|
||||
|
@ -15,29 +15,24 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import sys
|
||||
import eventlet
|
||||
from moniker.openstack.common import cfg
|
||||
from moniker.openstack.common import log as logging
|
||||
from moniker.api.app import app
|
||||
from moniker.openstack.common import service
|
||||
from moniker.api import service as api_service
|
||||
|
||||
config_files = cfg.find_config_files(project='moniker', prog='moniker-api')
|
||||
eventlet.monkey_patch()
|
||||
|
||||
config_files = cfg.find_config_files(project='moniker',
|
||||
prog='moniker-api')
|
||||
config_files.append('./etc/moniker-api.conf')
|
||||
|
||||
# Jerry Rig the keystone middleware.
|
||||
from keystone.middleware import auth_token
|
||||
auth_token.CONF = cfg.CONF
|
||||
cfg.CONF.register_opts(auth_token.opts, group='keystone_authtoken')
|
||||
|
||||
cfg.CONF(sys.argv[1:], project='moniker', prog='moniker-api',
|
||||
default_config_files=config_files)
|
||||
|
||||
logging.setup('moniker')
|
||||
|
||||
if cfg.CONF.verbose or cfg.CONF.debug:
|
||||
app.debug = True
|
||||
serv = api_service.Service(host=cfg.CONF.api_host, port=cfg.CONF.api_port)
|
||||
|
||||
if cfg.CONF.enable_keystone:
|
||||
# Add Keystone Middleware
|
||||
middleware_conf = {'delay_auth_decision': False}
|
||||
app.wsgi_app = auth_token.AuthProtocol(app.wsgi_app, middleware_conf)
|
||||
|
||||
app.run(host=cfg.CONF.api_host, port=cfg.CONF.api_port)
|
||||
launcher = service.launch(serv)
|
||||
launcher.wait()
|
||||
|
29
etc/moniker-api-paste.ini.sample
Normal file
29
etc/moniker-api-paste.ini.sample
Normal file
@ -0,0 +1,29 @@
|
||||
[composite:osapi_dns]
|
||||
use = egg:Paste#urlmap
|
||||
/v1: osapi_dns_api_v1
|
||||
|
||||
[composite:osapi_dns_api_v1]
|
||||
use = call:moniker.api.auth:pipeline_factory
|
||||
noauth = noauth osapi_dns_app_v1
|
||||
keystone = authtoken keystonecontext osapi_dns_app_v1
|
||||
|
||||
[app:osapi_dns_app_v1]
|
||||
paste.app_factory = moniker.api.v1:factory
|
||||
|
||||
[filter:noauth]
|
||||
paste.filter_factory = moniker.api.auth:NoAuthMiddleware.factory
|
||||
|
||||
[filter:keystonecontext]
|
||||
paste.filter_factory = moniker.api.auth:KeystoneContextMiddleware.factory
|
||||
|
||||
[filter:authtoken]
|
||||
paste.filter_factory = keystone.middleware.auth_token:filter_factory
|
||||
service_protocol = http
|
||||
service_host = 127.0.0.1
|
||||
service_port = 5000
|
||||
auth_host = 127.0.0.1
|
||||
auth_port = 35357
|
||||
auth_protocol = http
|
||||
admin_tenant_name = %SERVICE_TENANT_NAME%
|
||||
admin_user = %SERVICE_USER%
|
||||
admin_password = %SERVICE_PASSWORD%
|
@ -20,10 +20,5 @@ control_exchange = moniker
|
||||
#
|
||||
allowed_rpc_exception_modules = moniker.exceptions, moniker.openstack.common.exception
|
||||
|
||||
[keystone_authtoken]
|
||||
auth_host = 127.0.0.1
|
||||
auth_port = 35357
|
||||
auth_protocol = http
|
||||
admin_tenant_name = admin
|
||||
admin_user = admin
|
||||
admin_password = password
|
||||
#
|
||||
auth_strategy = noauth
|
||||
|
@ -0,0 +1,35 @@
|
||||
# Copyright 2012 Managed I.T.
|
||||
#
|
||||
# Author: Kiall Mac Innes <kiall@managedit.ie>
|
||||
#
|
||||
# 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 flask
|
||||
from moniker.openstack.common import cfg
|
||||
from moniker.openstack.common import jsonutils
|
||||
|
||||
|
||||
cfg.CONF.register_opts([
|
||||
cfg.StrOpt('api_host', default='0.0.0.0',
|
||||
help='API Host'),
|
||||
cfg.IntOpt('api_port', default=9001,
|
||||
help='API Port Number'),
|
||||
cfg.StrOpt('api_paste_config', default='moniker-api-paste.ini',
|
||||
help='File name for the paste.deploy config for moniker-api'),
|
||||
cfg.StrOpt('auth_strategy', default='noauth',
|
||||
help='The strategy to use for auth. Supports noauth or '
|
||||
'keystone'),
|
||||
])
|
||||
|
||||
|
||||
# Allows us to serialize datetime's etc
|
||||
flask.helpers.json = jsonutils
|
@ -1,52 +0,0 @@
|
||||
# Copyright 2012 Managed I.T.
|
||||
#
|
||||
# Author: Kiall Mac Innes <kiall@managedit.ie>
|
||||
#
|
||||
# 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 flask
|
||||
from moniker.openstack.common import cfg
|
||||
from moniker.openstack.common import jsonutils
|
||||
from moniker.openstack.common.context import RequestContext
|
||||
from moniker import central
|
||||
from moniker.api import v1
|
||||
from moniker.api import debug
|
||||
|
||||
# Allows us to serialize datetime's etc
|
||||
flask.helpers.json = jsonutils
|
||||
|
||||
cfg.CONF.register_opts([
|
||||
cfg.StrOpt('api_host', default='0.0.0.0',
|
||||
help='API Host'),
|
||||
cfg.IntOpt('api_port', default=9001,
|
||||
help='API Port Number'),
|
||||
])
|
||||
|
||||
app = flask.Flask('moniker.api')
|
||||
|
||||
# Blueprints
|
||||
app.register_blueprint(v1.blueprint, url_prefix='/v1')
|
||||
app.register_blueprint(debug.blueprint, url_prefix='/debug')
|
||||
|
||||
|
||||
@app.before_request
|
||||
def attach_context():
|
||||
request = flask.request
|
||||
headers = request.headers
|
||||
|
||||
if cfg.CONF.enable_keystone:
|
||||
request.context = RequestContext(auth_tok=headers.get('X-Auth-Token'),
|
||||
user=headers.get('X-User-ID'),
|
||||
tenant=headers.get('X-Tenant-ID'))
|
||||
else:
|
||||
request.context = RequestContext(user=cfg.CONF.default_user,
|
||||
tenant=cfg.CONF.default_tenant)
|
53
moniker/api/auth.py
Normal file
53
moniker/api/auth.py
Normal file
@ -0,0 +1,53 @@
|
||||
# Copyright 2012 Managed I.T.
|
||||
#
|
||||
# Author: Kiall Mac Innes <kiall@managedit.ie>
|
||||
#
|
||||
# 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 moniker.openstack.common.context import RequestContext
|
||||
from moniker.openstack.common import cfg
|
||||
from moniker.openstack.common import log as logging
|
||||
from moniker import wsgi
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def pipeline_factory(loader, global_conf, **local_conf):
|
||||
"""
|
||||
A paste pipeline replica that keys off of auth_strategy.
|
||||
|
||||
Code nabbed from cinder.
|
||||
"""
|
||||
pipeline = local_conf[cfg.CONF.auth_strategy]
|
||||
pipeline = pipeline.split()
|
||||
filters = [loader.get_filter(n) for n in pipeline[:-1]]
|
||||
app = loader.get_app(pipeline[-1])
|
||||
filters.reverse()
|
||||
for filter in filters:
|
||||
app = filter(app)
|
||||
return app
|
||||
|
||||
|
||||
class KeystoneContextMiddleware(wsgi.Middleware):
|
||||
def process_request(self, request):
|
||||
headers = request.headers
|
||||
context = RequestContext(auth_tok=headers.get('X-Auth-Token'),
|
||||
user=headers.get('X-User-ID'),
|
||||
tenant=headers.get('X-Tenant-ID'))
|
||||
request.environ['context'] = context
|
||||
|
||||
|
||||
class NoAuthMiddleware(wsgi.Middleware):
|
||||
def process_request(self, request):
|
||||
context = RequestContext(user=cfg.CONF.default_user,
|
||||
tenant=cfg.CONF.default_tenant)
|
||||
request.environ['context'] = context
|
44
moniker/api/service.py
Normal file
44
moniker/api/service.py
Normal file
@ -0,0 +1,44 @@
|
||||
# Copyright 2012 Managed I.T.
|
||||
#
|
||||
# Author: Kiall Mac Innes <kiall@managedit.ie>
|
||||
#
|
||||
# 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 os
|
||||
from paste import deploy
|
||||
from moniker.openstack.common import log as logging
|
||||
from moniker.openstack.common import wsgi
|
||||
from moniker.openstack.common import cfg
|
||||
from moniker import utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Service(wsgi.Service):
|
||||
def __init__(self, port, host='0.0.0.0', backlog=128, threads=1000):
|
||||
super(Service, self).__init__(threads)
|
||||
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.backlog = backlog
|
||||
|
||||
config_path = cfg.CONF.api_paste_config
|
||||
config_path = utils.find_config(config_path)
|
||||
|
||||
self.application = deploy.loadapp("config:%s" % config_path,
|
||||
name='osapi_dns')
|
||||
|
||||
def start(self):
|
||||
return super(Service, self).start(application=self.application,
|
||||
port=self.port, host=self.host,
|
||||
backlog=self.backlog)
|
@ -20,3 +20,11 @@ blueprint = flask.Blueprint('v1', __name__)
|
||||
import moniker.api.v1.servers
|
||||
import moniker.api.v1.domains
|
||||
import moniker.api.v1.records
|
||||
|
||||
|
||||
def factory(global_config, **local_conf):
|
||||
import flask
|
||||
app = flask.Flask('moniker.api.v1')
|
||||
app.register_blueprint(blueprint)
|
||||
|
||||
return app
|
||||
|
@ -43,7 +43,7 @@ def get_domains_schema():
|
||||
|
||||
@blueprint.route('/domains', methods=['POST'])
|
||||
def create_domain():
|
||||
context = flask.request.context
|
||||
context = flask.request.environ.get('context')
|
||||
values = flask.request.json
|
||||
|
||||
try:
|
||||
@ -67,7 +67,7 @@ def create_domain():
|
||||
|
||||
@blueprint.route('/domains', methods=['GET'])
|
||||
def get_domains():
|
||||
context = flask.request.context
|
||||
context = flask.request.environ.get('context')
|
||||
|
||||
domains = central_api.get_domains(context)
|
||||
|
||||
@ -78,7 +78,7 @@ def get_domains():
|
||||
|
||||
@blueprint.route('/domains/<domain_id>', methods=['GET'])
|
||||
def get_domain(domain_id):
|
||||
context = flask.request.context
|
||||
context = flask.request.environ.get('context')
|
||||
|
||||
try:
|
||||
domain = central_api.get_domain(context, domain_id)
|
||||
@ -94,7 +94,7 @@ def get_domain(domain_id):
|
||||
|
||||
@blueprint.route('/domains/<domain_id>', methods=['PUT'])
|
||||
def update_domain(domain_id):
|
||||
context = flask.request.context
|
||||
context = flask.request.environ.get('context')
|
||||
values = flask.request.json
|
||||
|
||||
try:
|
||||
@ -116,7 +116,7 @@ def update_domain(domain_id):
|
||||
|
||||
@blueprint.route('/domains/<domain_id>', methods=['DELETE'])
|
||||
def delete_domain(domain_id):
|
||||
context = flask.request.context
|
||||
context = flask.request.environ.get('context')
|
||||
|
||||
try:
|
||||
central_api.delete_domain(context, domain_id)
|
||||
|
@ -44,7 +44,7 @@ def get_records_schema():
|
||||
|
||||
@blueprint.route('/domains/<domain_id>/records', methods=['POST'])
|
||||
def create_record(domain_id):
|
||||
context = flask.request.context
|
||||
context = flask.request.environ.get('context')
|
||||
values = flask.request.json
|
||||
|
||||
try:
|
||||
@ -69,7 +69,7 @@ def create_record(domain_id):
|
||||
|
||||
@blueprint.route('/domains/<domain_id>/records', methods=['GET'])
|
||||
def get_records(domain_id):
|
||||
context = flask.request.context
|
||||
context = flask.request.environ.get('context')
|
||||
|
||||
records = central_api.get_records(context, domain_id)
|
||||
|
||||
@ -78,7 +78,7 @@ def get_records(domain_id):
|
||||
|
||||
@blueprint.route('/domains/<domain_id>/records/<record_id>', methods=['GET'])
|
||||
def get_record(domain_id, record_id):
|
||||
context = flask.request.context
|
||||
context = flask.request.environ.get('context')
|
||||
|
||||
try:
|
||||
record = central_api.get_record(context, domain_id, record_id)
|
||||
@ -94,7 +94,7 @@ def get_record(domain_id, record_id):
|
||||
|
||||
@blueprint.route('/domains/<domain_id>/records/<record_id>', methods=['PUT'])
|
||||
def update_record(domain_id, record_id):
|
||||
context = flask.request.context
|
||||
context = flask.request.environ.get('context')
|
||||
values = flask.request.json
|
||||
|
||||
try:
|
||||
@ -118,7 +118,7 @@ def update_record(domain_id, record_id):
|
||||
@blueprint.route('/domains/<domain_id>/records/<record_id>',
|
||||
methods=['DELETE'])
|
||||
def delete_record(domain_id, record_id):
|
||||
context = flask.request.context
|
||||
context = flask.request.environ.get('context')
|
||||
|
||||
try:
|
||||
central_api.delete_record(context, domain_id, record_id)
|
||||
|
@ -42,7 +42,7 @@ def get_servers_schema():
|
||||
|
||||
@blueprint.route('/servers', methods=['POST'])
|
||||
def create_server():
|
||||
context = flask.request.context
|
||||
context = flask.request.environ.get('context')
|
||||
values = flask.request.json
|
||||
|
||||
try:
|
||||
@ -66,7 +66,7 @@ def create_server():
|
||||
|
||||
@blueprint.route('/servers', methods=['GET'])
|
||||
def get_servers():
|
||||
context = flask.request.context
|
||||
context = flask.request.environ.get('context')
|
||||
|
||||
servers = central_api.get_servers(context)
|
||||
|
||||
@ -77,7 +77,7 @@ def get_servers():
|
||||
|
||||
@blueprint.route('/servers/<server_id>', methods=['GET'])
|
||||
def get_server(server_id):
|
||||
context = flask.request.context
|
||||
context = flask.request.environ.get('context')
|
||||
|
||||
try:
|
||||
server = central_api.get_server(context, server_id)
|
||||
@ -93,7 +93,7 @@ def get_server(server_id):
|
||||
|
||||
@blueprint.route('/servers/<server_id>', methods=['PUT'])
|
||||
def update_server(server_id):
|
||||
context = flask.request.context
|
||||
context = flask.request.environ.get('context')
|
||||
values = flask.request.json
|
||||
|
||||
try:
|
||||
@ -115,7 +115,7 @@ def update_server(server_id):
|
||||
|
||||
@blueprint.route('/servers/<server_id>', methods=['DELETE'])
|
||||
def delete_server(server_id):
|
||||
context = flask.request.context
|
||||
context = flask.request.environ.get('context')
|
||||
|
||||
try:
|
||||
central_api.delete_server(context, server_id)
|
||||
|
@ -19,6 +19,10 @@ class Base(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ConfigNotFound(Base):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidObject(Base):
|
||||
pass
|
||||
|
||||
|
@ -56,7 +56,7 @@ class Service(service.Service):
|
||||
"""
|
||||
|
||||
def __init__(self, threads=1000):
|
||||
super(Service, self).start()
|
||||
super(Service, self).__init__()
|
||||
self.pool = eventlet.GreenPool(threads)
|
||||
|
||||
def start(self, application, port, host='0.0.0.0', backlog=128):
|
||||
|
@ -13,8 +13,10 @@
|
||||
# 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 moniker.openstack.common import rpc
|
||||
import os
|
||||
from moniker.openstack.common import cfg
|
||||
from moniker.openstack.common.notifier import api as notifier_api
|
||||
from moniker import exceptions
|
||||
|
||||
|
||||
def notify(context, service, event_type, payload):
|
||||
@ -24,10 +26,28 @@ def notify(context, service, event_type, payload):
|
||||
notifier_api.notify(context, publisher_id, event_type, priority, payload)
|
||||
|
||||
|
||||
def fanout_cast(context, topic, method, **kwargs):
|
||||
msg = {
|
||||
'method': method,
|
||||
'args': kwargs
|
||||
}
|
||||
def find_config(config_path):
|
||||
""" Find a configuration file using the given hint.
|
||||
|
||||
rpc.fanout_cast(context, topic, msg)
|
||||
Code nabbed from cinder.
|
||||
|
||||
:param config_path: Full or relative path to the config.
|
||||
:returns: Full path of the config, if it exists.
|
||||
:raises: `moniker.exceptions.ConfigNotFound`
|
||||
|
||||
"""
|
||||
possible_locations = [
|
||||
config_path,
|
||||
os.path.join("etc", "moniker", config_path),
|
||||
os.path.join("etc", config_path),
|
||||
os.path.join(cfg.CONF.state_path, "etc", "moniker", config_path),
|
||||
os.path.join(cfg.CONF.state_path, "etc", config_path),
|
||||
os.path.join(cfg.CONF.state_path, config_path),
|
||||
"/etc/moniker/%s" % config_path,
|
||||
]
|
||||
|
||||
for path in possible_locations:
|
||||
if os.path.exists(path):
|
||||
return os.path.abspath(path)
|
||||
|
||||
raise exceptions.ConfigNotFound(os.path.abspath(config_path))
|
||||
|
27
moniker/wsgi.py
Normal file
27
moniker/wsgi.py
Normal file
@ -0,0 +1,27 @@
|
||||
# Copyright 2012 Managed I.T.
|
||||
#
|
||||
# Author: Kiall Mac Innes <kiall@managedit.ie>
|
||||
#
|
||||
# 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 moniker.openstack.common import wsgi
|
||||
|
||||
|
||||
class Middleware(wsgi.Middleware):
|
||||
@classmethod
|
||||
def factory(cls, global_config, **local_conf):
|
||||
""" Used for paste app factories in paste.deploy config files """
|
||||
|
||||
def _factory(app):
|
||||
return cls(app, **local_conf)
|
||||
|
||||
return _factory
|
Loading…
x
Reference in New Issue
Block a user