diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rwxr-xr-x | bin/moniker-api | 25 | ||||
-rw-r--r-- | etc/moniker-api-paste.ini.sample | 29 | ||||
-rw-r--r-- | etc/moniker-api.conf.sample | 9 | ||||
-rw-r--r-- | moniker/api/__init__.py | 35 | ||||
-rw-r--r-- | moniker/api/app.py | 52 | ||||
-rw-r--r-- | moniker/api/auth.py | 53 | ||||
-rw-r--r-- | moniker/api/service.py | 44 | ||||
-rw-r--r-- | moniker/api/v1/__init__.py | 8 | ||||
-rw-r--r-- | moniker/api/v1/domains.py | 10 | ||||
-rw-r--r-- | moniker/api/v1/records.py | 10 | ||||
-rw-r--r-- | moniker/api/v1/servers.py | 10 | ||||
-rw-r--r-- | moniker/exceptions.py | 4 | ||||
-rw-r--r-- | moniker/openstack/common/wsgi.py | 2 | ||||
-rw-r--r-- | moniker/utils.py | 34 | ||||
-rw-r--r-- | moniker/wsgi.py | 27 |
16 files changed, 256 insertions, 97 deletions
@@ -11,5 +11,6 @@ venv *.sqlite var/* etc/*.conf +etc/*.ini AUTHORS ChangeLog diff --git a/bin/moniker-api b/bin/moniker-api index 412a1c53..0cfe6ca4 100755 --- a/bin/moniker-api +++ b/bin/moniker-api @@ -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') -config_files.append('./etc/moniker-api.conf') +eventlet.monkey_patch() -# 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') +config_files = cfg.find_config_files(project='moniker', + prog='moniker-api') +config_files.append('./etc/moniker-api.conf') 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 - -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) +serv = api_service.Service(host=cfg.CONF.api_host, port=cfg.CONF.api_port) -app.run(host=cfg.CONF.api_host, port=cfg.CONF.api_port) +launcher = service.launch(serv) +launcher.wait() diff --git a/etc/moniker-api-paste.ini.sample b/etc/moniker-api-paste.ini.sample new file mode 100644 index 00000000..b9d1c5ca --- /dev/null +++ b/etc/moniker-api-paste.ini.sample @@ -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% diff --git a/etc/moniker-api.conf.sample b/etc/moniker-api.conf.sample index 3de866c4..e9763731 100644 --- a/etc/moniker-api.conf.sample +++ b/etc/moniker-api.conf.sample @@ -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 diff --git a/moniker/api/__init__.py b/moniker/api/__init__.py index e69de29b..d0dce6fc 100644 --- a/moniker/api/__init__.py +++ b/moniker/api/__init__.py @@ -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 diff --git a/moniker/api/app.py b/moniker/api/app.py deleted file mode 100644 index 20ca1da6..00000000 --- a/moniker/api/app.py +++ /dev/null @@ -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) diff --git a/moniker/api/auth.py b/moniker/api/auth.py new file mode 100644 index 00000000..27935c19 --- /dev/null +++ b/moniker/api/auth.py @@ -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 diff --git a/moniker/api/service.py b/moniker/api/service.py new file mode 100644 index 00000000..2cb37a03 --- /dev/null +++ b/moniker/api/service.py @@ -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) diff --git a/moniker/api/v1/__init__.py b/moniker/api/v1/__init__.py index 1638196c..7ca0b9b8 100644 --- a/moniker/api/v1/__init__.py +++ b/moniker/api/v1/__init__.py @@ -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 diff --git a/moniker/api/v1/domains.py b/moniker/api/v1/domains.py index 7df2a51b..e240f19f 100644 --- a/moniker/api/v1/domains.py +++ b/moniker/api/v1/domains.py @@ -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) diff --git a/moniker/api/v1/records.py b/moniker/api/v1/records.py index 443531cf..6019e8e1 100644 --- a/moniker/api/v1/records.py +++ b/moniker/api/v1/records.py @@ -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) diff --git a/moniker/api/v1/servers.py b/moniker/api/v1/servers.py index fe41cf91..bcd8a14f 100644 --- a/moniker/api/v1/servers.py +++ b/moniker/api/v1/servers.py @@ -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) diff --git a/moniker/exceptions.py b/moniker/exceptions.py index 62f0659a..ee4a4518 100644 --- a/moniker/exceptions.py +++ b/moniker/exceptions.py @@ -19,6 +19,10 @@ class Base(Exception): pass +class ConfigNotFound(Base): + pass + + class InvalidObject(Base): pass diff --git a/moniker/openstack/common/wsgi.py b/moniker/openstack/common/wsgi.py index e7610723..84034d6c 100644 --- a/moniker/openstack/common/wsgi.py +++ b/moniker/openstack/common/wsgi.py @@ -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): diff --git a/moniker/utils.py b/moniker/utils.py index e5ddfd3c..122bd945 100644 --- a/moniker/utils.py +++ b/moniker/utils.py @@ -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)) diff --git a/moniker/wsgi.py b/moniker/wsgi.py new file mode 100644 index 00000000..94856d49 --- /dev/null +++ b/moniker/wsgi.py @@ -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 |