From 60a39fec35520688ea06b2d24451541a65af1cec Mon Sep 17 00:00:00 2001 From: Denis Makogon Date: Thu, 16 Feb 2017 16:19:47 +0200 Subject: [PATCH] Middleware and Notification examples Includes: - swift ceilometer middleware configuration and usage - picasso swift middleware for file post-processing that is being uploaded Change-Id: Id1ef827c62f4c43175f019b8761b7ca9d84bed84 --- .../openstack-alarms/swift-notifications.rst | 105 +++++++++ examples/python-picassomiddleware/.gitignore | 89 ++++++++ examples/python-picassomiddleware/LICENSE | 201 ++++++++++++++++++ examples/python-picassomiddleware/README.rst | 45 ++++ .../functions/__init__.py | 0 .../functions/middleware.py | 89 ++++++++ .../python-picassomiddleware/requirements.txt | 9 + examples/python-picassomiddleware/setup.cfg | 25 +++ examples/python-picassomiddleware/setup.py | 29 +++ 9 files changed, 592 insertions(+) create mode 100644 examples/openstack-alarms/swift-notifications.rst create mode 100644 examples/python-picassomiddleware/.gitignore create mode 100644 examples/python-picassomiddleware/LICENSE create mode 100644 examples/python-picassomiddleware/README.rst create mode 100644 examples/python-picassomiddleware/functions/__init__.py create mode 100644 examples/python-picassomiddleware/functions/middleware.py create mode 100644 examples/python-picassomiddleware/requirements.txt create mode 100644 examples/python-picassomiddleware/setup.cfg create mode 100644 examples/python-picassomiddleware/setup.py diff --git a/examples/openstack-alarms/swift-notifications.rst b/examples/openstack-alarms/swift-notifications.rst new file mode 100644 index 0000000..9da805e --- /dev/null +++ b/examples/openstack-alarms/swift-notifications.rst @@ -0,0 +1,105 @@ +Swift metering notification +=========================== + +DevStack plugins configuration +------------------------------ + +Required DevStack plugins:: + + Panko + Ceilometer + Aodh + Picasso + +local.conf:: + + [[local|localrc]] + + ADMIN_PASSWORD=root + DATABASE_PASSWORD=root + RABBIT_PASSWORD=root + SERVICE_PASSWORD=root + SERVICE_TOKEN=root + + enable_service s-proxy s-object s-container s-account + + enable_plugin panko https://git.openstack.org/openstack/panko + enable_plugin ceilometer https://git.openstack.org/openstack/ceilometer + enable_plugin aodh https://git.openstack.org/openstack/aodh + + enable_service aodh-evaluator aodh-notifier aodh-api + disable_service ceilometer-alarm-notifier ceilometer-alarm-evaluator + + enable_plugin picasso git@github.com:openstack/picasso.git + +By default Ceilometer DevStack plugin installs python-ceilometermiddleware for Swift as its proxy API middleware that sends notifications through message queue to a specific topic ("notifications" by default) + +Configuring Ceilometer event pipeline +------------------------------------- + +To enable this functionality, configure Ceilometer to be able to publish events to the queue the aodh-listener service listen on. +The event_alarm_topic config option of Aodh identifies which messaging topic the aodh-listener on, the default value is “alarm.all”. + +Ceilometer publisher needs to be configured using pipeline configuration file (**event_pipeline.yaml** as default) to send events "alarm.all" topic:: + + --- + sources: + - name: event_source + events: + - "*" + sinks: + - event_sink + sinks: + - name: event_sink + transformers: + publishers: + - notifier:// + - notifier://?topic=alarm.all + + +Collecting events from Swift +---------------------------- + +On each API request, Swift emits (through python-ceilometermiddleware) notifications to a message queue. +Those notifications are being transformed into Ceilometer/Panko events and available via Ceilometer API. + +Setting up alarms based on Swift notifications +---------------------------------------------- + +Once DevStack is installed, create a Picasso app:: + + openstack fn apps create alarm-notifier-app + +Create a route:: + + openstack fn routes create alarm-notifier-app-5a3e626d281 /hello async iron/hello --is-public + +After that setup Aodh alarm:: + + aodh alarm create --event-type objectstore.http.request \ + --description objectstore_request --period 600 --evaluation-periods 1 \ + --alarm-action $(openstack fn routes expose-url alarm-notifier-app-5a3e626d281 /hello) \ + --name objectstore_request \ + --type event --ok-action $(openstack fn routes expose-url alarm-notifier-app-5a3e626d281 /hello) + +This alarm would be triggered per each **objectstore.http.request** event in queue and one of the actions (alarm action or OK action) will be executed +by submitting POST request with event data. + +In Aodh (aodh-notifier) logs we can see that event alarm URL is being triggered:: + + 2017-02-16 15:45:14.802 29717 DEBUG aodh.notifier [-] Received 1 messages in batch. sample /opt/stack/aodh/aodh/notifier/__init__.py:98 + 2017-02-16 15:45:14.803 29717 DEBUG aodh.notifier [-] Notifying alarm 5a911a27-487c-49d4-a4da-68b209eda2cc with action SplitResult(scheme=u'http', netloc=u'192.168.0.114:10001', path=u'/r/alarm-notifier-app-5a3e626d281/hello', query='', fragment='') _handle_action /opt/stack/aodh/aodh/notifier/__init__.py:138 + 2017-02-16 15:45:14.804 29717 INFO aodh.notifier.rest [-] Notifying alarm objectstore_request 5a911a27-487c-49d4-a4da-68b209eda2cc with severity low from insufficient data to alarm with action SplitResult(scheme=u'http', netloc=u'192.168.0.114:10001', path=u'/r/alarm-notifier-app-5a3e626d281/hello', query='', fragment='') because Event hits the query .. request-id: req-cd265735-ef58-468d-aca3-65e3add9a03c + 2017-02-16 15:45:15.041 29717 INFO aodh.notifier.rest [-] Notifying alarm <5a911a27-487c-49d4-a4da-68b209eda2cc> gets response: 200 OK. + +Also (in aodh-listener):: + + 2017-02-16 15:56:57.542 29761 DEBUG aodh.evaluator.event [-] Evaluating event: event = {u'event_type': u'objectstore.http.request', u'traits': [[u'typeURI', 1, u'http://schemas.dmtf.org/cloud/audit/1.0/event'], [u'eventTime', 1, u'2017-02-16T13:56:54.322957'], [u'outcome', 1, u'success'], [u'user_id', 1, u'106a49d7d2fe4bc99792a3a95195b843'], [u'initiator_typeURI', 1, u'service/security/account/user'], [u'service', 1, u'ceilometermiddleware'], [u'target_id', 1, u'af2f24bca17e4d7f974c5a07012636db'], [u'observer_id', 1, u'target'], [u'initiator_id', 1, u'106a49d7d2fe4bc99792a3a95195b843'], [u'eventType', 1, u'activity'], [u'target_typeURI', 1, u'service/storage/object'], [u'action', 1, u'read'], [u'project_id', 1, u'04108819f6294723ba539b73b0c40a03'], [u'id', 1, u'b2552bc9-20ad-516c-a9ae-e58a7e016e82']], u'message_signature': u'364eec8d900cac2cc99679b5d171d279c32138114b93250e2954782d1f961c54', u'raw': {}, u'generated': u'2017-02-16T13:56:54.323449', u'message_id': u'6531d952-07f7-4534-aaf2-b6be3934f831'} evaluate_events /opt/stack/aodh/aodh/evaluator/event.py:167 + 2017-02-16 15:56:57.543 29761 DEBUG aodh.evaluator.event [-] Finished event alarm evaluation. evaluate_events /opt/stack/aodh/aodh/evaluator/event.py:184 + +However the data that is being sent to functions does not contain any useful information regarding actual resources that were modified, so no way to get the information on it. + +Alternative +----------- + +As part of Picasso distribution, Swift middleware is available, see https://github.com/openstack/picasso/tree/master/examples/python-picassomiddleware diff --git a/examples/python-picassomiddleware/.gitignore b/examples/python-picassomiddleware/.gitignore new file mode 100644 index 0000000..72364f9 --- /dev/null +++ b/examples/python-picassomiddleware/.gitignore @@ -0,0 +1,89 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# IPython Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# dotenv +.env + +# virtualenv +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject diff --git a/examples/python-picassomiddleware/LICENSE b/examples/python-picassomiddleware/LICENSE new file mode 100644 index 0000000..7020bcf --- /dev/null +++ b/examples/python-picassomiddleware/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016 Iron.io + + 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. diff --git a/examples/python-picassomiddleware/README.rst b/examples/python-picassomiddleware/README.rst new file mode 100644 index 0000000..6b80163 --- /dev/null +++ b/examples/python-picassomiddleware/README.rst @@ -0,0 +1,45 @@ +OpenStack Swift middleware for serverless functions +=================================================== +This guide assumes you have a working DevStack VM. +In DevStack local.conf:: + + enable_service s-proxy s-object s-container s-account + enable_plugin picasso git@github.com:openstack/picasso.git master + +this will enable Swift and Picasso inside DevStack + +Run the following commands:: + + $ git clone https://github.com/denismakogon/serverless-functions-middleware.git + $ pushd serverless-functions-middleware && sudo python setup.py install; popd + +Install latest Swift python client:: + + $ git clone https://github.com/openstack/python-swiftclient.git + $ pushd python-swiftclient && sudo python setup.py install; popd + +Modify Swift proxy conf by adding:: + + [filter:functions_middleware] + use = egg:functions#functions_middleware + +In **[pipeline:main]** section add **functions_middleware** to the list of other middleware in **pipeline** config option +Restart Swift proxy service by:: + + screen -x + restart s-proxy process + +Create a function:: + + $ openstack fn apps create testapp + $ openstack fn routes create testapp-1445cef51c68427da92621 /hello sync iron/hello --is-public + $ swift post test_container + $ + +When uploading a file to Swift, specify **X-FUNCTION-URL**. +The value can be retrieved using expose-url command against desired function:: + + $ openstack fn routes expose-url testapp-1445cef51c68427da92621 /hello + $ swift upload test_container file --header "X-FUNCTION-URL:$(openstack fn routes expose-url testapp-1445cef51c68427da92621 /hello)" + +A function that is being used would be supplied with X-AUTH-URL, X-PROJECT-ID and all relevant data about container and a file that is being uploaded. diff --git a/examples/python-picassomiddleware/functions/__init__.py b/examples/python-picassomiddleware/functions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/python-picassomiddleware/functions/middleware.py b/examples/python-picassomiddleware/functions/middleware.py new file mode 100644 index 0000000..10f48f9 --- /dev/null +++ b/examples/python-picassomiddleware/functions/middleware.py @@ -0,0 +1,89 @@ +# 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 json + +from swift.common.http import is_success +from swift.common.swob import Request +from swift.common.utils import split_path, get_logger + +from eventlet import Timeout +import six +if six.PY3: + from eventlet.green.urllib import request as urllib2 +else: + from eventlet.green import urllib2 + + +class FunctionsWebhookMiddleware(object): + + def __init__(self, app, conf): + self.app = app + self.logger = get_logger(conf, log_route='picasso_functions') + + def __call__(self, env, start_response): + req = Request(env) + resp = req.get_response(self.app) + self.logger.info("Picasso: available headers: {}".format(str(dict(req.headers)))) + try: + if "X-Function-URL" in req.headers: + version, account, container, obj = split_path(req.path_info, 4, 4, True) + self.logger.info("Picasso version {}, account {}, container {}, object {}" + .format(version, account, container, obj)) + if obj and is_success(resp.status_int) and req.method == 'PUT': + webhook = req.headers.get("X-Function-URL") + data = json.dumps({ + "x-auth-token": req.headers.get("X-Auth-Token"), + "version": version, + "account": account, + "container": container, + "object": obj, + "project_id": req.headers.get("X-Project-Id"), + }) + self.logger.info("Picasso data to send to a function {}" + .format(str(data))) + data_as_bytes = data.encode('utf-8') + webhook_req = urllib2.Request(webhook, data=data_as_bytes) + webhook_req.add_header('Content-Type', + 'application/json') + webhook_req.add_header( + 'Content-Length', len(data_as_bytes)) + self.logger.info("Picasso data to send as bytes {}" + .format(data_as_bytes)) + with Timeout(60): + try: + result = urllib2.urlopen(webhook_req).read() + self.logger.info( + "Picasso function worked fine. Result {}" + .format(str(result))) + except (Exception, Timeout) as ex: + self.logger.error( + 'Picasso failed POST to webhook {}, ' + 'error {}'.format(webhook, str(ex))) + else: + self.logger.info("Picasso skipping functions middleware " + "due to absence of function URL") + except ValueError: + # not an object request + pass + + return self.app(env, start_response) + + +def filter_factory(global_conf, **local_conf): + conf = global_conf.copy() + conf.update(local_conf) + + def webhook_filter(app): + return FunctionsWebhookMiddleware(app, conf) + return webhook_filter diff --git a/examples/python-picassomiddleware/requirements.txt b/examples/python-picassomiddleware/requirements.txt new file mode 100644 index 0000000..2fb3412 --- /dev/null +++ b/examples/python-picassomiddleware/requirements.txt @@ -0,0 +1,9 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. + +pbr>=1.9 +setuptools>=17.1 +Babel>=1.3 +eventlet>=0.17.4 # MIT +greenlet>=0.3.1 diff --git a/examples/python-picassomiddleware/setup.cfg b/examples/python-picassomiddleware/setup.cfg new file mode 100644 index 0000000..75000fd --- /dev/null +++ b/examples/python-picassomiddleware/setup.cfg @@ -0,0 +1,25 @@ +[metadata] +name = functions +summary = Middleware for OpenStack Swift based on Functions-as-a-Service +description-file = + README.rst +author = Denis Makogon +author-email = denis@iron.io +home-page = http://www.openstack.org/ +classifier = + Environment :: OpenStack + Intended Audience :: Information Technology + Intended Audience :: System Administrators + License :: OSI Approved :: Apache Software License + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + +[pbr] +skip_authors = True +skip_changelog = True + +[entry_points] +paste.filter_factory = + functions_middleware = functions.middleware:filter_factory diff --git a/examples/python-picassomiddleware/setup.py b/examples/python-picassomiddleware/setup.py new file mode 100644 index 0000000..291da5d --- /dev/null +++ b/examples/python-picassomiddleware/setup.py @@ -0,0 +1,29 @@ +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# 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. + +# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT +import setuptools + +# In python < 2.7.4, a lazy loading of package `pbr` will break +# setuptools if some other modules registered functions in `atexit`. +# solution from: http://bugs.python.org/issue15881#msg170215 +try: + import multiprocessing # noqa +except ImportError: + pass + +setuptools.setup( + setup_requires=['pbr>=1.9', 'setuptools>=17.1'], + pbr=True)