From 17c06ad4ee95e35f329275cc6bf0945fc2072117 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 2 May 2019 13:51:00 -0600 Subject: [PATCH] Retire bilean This hasn't been touched in over two years. Time to close things off. Change-Id: I5b8bcba0a5639c6a79a1c0ebba9d11aafa763b8c Signed-off-by: Stephen Finucane --- .gitignore | 25 - .testr.conf | 6 - CONTRIBUTING.rst | 43 - LICENSE | 176 ---- README | 9 + README.rst | 36 - bilean/__init__.py | 0 bilean/api/__init__.py | 0 bilean/api/middleware/__init__.py | 0 bilean/api/middleware/context.py | 82 -- bilean/api/middleware/fault.py | 135 --- bilean/api/middleware/ssl.py | 38 - bilean/api/middleware/version_negotiation.py | 125 --- bilean/api/openstack/__init__.py | 36 - bilean/api/openstack/v1/__init__.py | 193 ---- bilean/api/openstack/v1/consumptions.py | 93 -- bilean/api/openstack/v1/events.py | 74 -- bilean/api/openstack/v1/policies.py | 173 ---- bilean/api/openstack/v1/resources.py | 120 --- bilean/api/openstack/v1/rules.py | 128 --- bilean/api/openstack/v1/users.py | 126 --- bilean/api/openstack/v1/util.py | 71 -- bilean/api/openstack/versions.py | 54 - bilean/api/validator.py | 202 ---- bilean/cmd/__init__.py | 0 bilean/cmd/api.py | 63 -- bilean/cmd/engine.py | 45 - bilean/cmd/manage.py | 91 -- bilean/cmd/notification.py | 39 - bilean/cmd/scheduler.py | 44 - bilean/common/__init__.py | 0 bilean/common/config.py | 167 --- bilean/common/consts.py | 128 --- bilean/common/context.py | 130 --- bilean/common/exception.py | 234 ----- bilean/common/i18n.py | 35 - bilean/common/messaging.py | 138 --- bilean/common/policy.py | 47 - bilean/common/schema.py | 427 -------- bilean/common/serializers.py | 41 - bilean/common/utils.py | 198 ---- bilean/common/wsgi.py | 920 ----------------- bilean/db/__init__.py | 0 bilean/db/api.py | 348 ------- bilean/db/sqlalchemy/__init__.py | 0 bilean/db/sqlalchemy/api.py | 967 ------------------ bilean/db/sqlalchemy/filters.py | 45 - bilean/db/sqlalchemy/migrate_repo/README | 4 - bilean/db/sqlalchemy/migrate_repo/__init__.py | 0 bilean/db/sqlalchemy/migrate_repo/manage.py | 5 - bilean/db/sqlalchemy/migrate_repo/migrate.cfg | 25 - .../migrate_repo/versions/001_bilean_init.py | 233 ----- .../versions/002_add_job_table.py | 57 -- .../migrate_repo/versions/__init__.py | 0 bilean/db/sqlalchemy/migration.py | 38 - bilean/db/sqlalchemy/models.py | 266 ----- bilean/db/sqlalchemy/types.py | 112 -- bilean/db/utils.py | 47 - bilean/drivers/__init__.py | 0 bilean/drivers/base.py | 54 - bilean/drivers/openstack/__init__.py | 22 - bilean/drivers/openstack/cinder_v2.py | 33 - bilean/drivers/openstack/keystone_v3.py | 67 -- bilean/drivers/openstack/neutron_v2.py | 125 --- bilean/drivers/openstack/nova_v2.py | 72 -- bilean/drivers/openstack/sdk.py | 114 --- bilean/engine/__init__.py | 0 bilean/engine/actions/__init__.py | 0 bilean/engine/actions/base.py | 407 -------- bilean/engine/actions/user_action.py | 169 --- bilean/engine/consumption.py | 117 --- bilean/engine/dispatcher.py | 112 -- bilean/engine/environment.py | 189 ---- bilean/engine/event.py | 209 ---- bilean/engine/flows/__init__.py | 0 bilean/engine/flows/flow.py | 294 ------ bilean/engine/lock.py | 108 -- bilean/engine/parser.py | 82 -- bilean/engine/policy.py | 137 --- bilean/engine/registry.py | 138 --- bilean/engine/service.py | 785 -------------- bilean/engine/user.py | 336 ------ bilean/notification/__init__.py | 0 bilean/notification/action.py | 108 -- bilean/notification/converter.py | 281 ----- bilean/notification/endpoint.py | 93 -- bilean/notification/notification.py | 91 -- bilean/notifier.py | 46 - bilean/plugins/__init__.py | 0 bilean/plugins/base.py | 453 -------- bilean/plugins/os/__init__.py | 0 bilean/plugins/os/cinder/__init__.py | 0 bilean/plugins/os/cinder/volume.py | 131 --- bilean/plugins/os/nova/__init__.py | 0 bilean/plugins/os/nova/server.py | 131 --- bilean/rpc/__init__.py | 0 bilean/rpc/client.py | 235 ----- bilean/scheduler/__init__.py | 51 - bilean/scheduler/cron_scheduler.py | 263 ----- bilean/scheduler/service.py | 85 -- bilean/tests/__init__.py | 24 - bilean/tests/common/__init__.py | 0 bilean/tests/common/base.py | 77 -- bilean/tests/common/fakes.py | 85 -- bilean/tests/common/utils.py | 80 -- bilean/tests/drivers/__init__.py | 0 bilean/tests/drivers/test_driver.py | 46 - bilean/tests/drivers/test_sdk.py | 211 ---- bilean/tests/rules/__init__.py | 0 bilean/tests/rules/test_rule_base.py | 267 ----- bilean/version.py | 17 - devstack/README.rst | 46 - devstack/lib/bilean | 176 ---- devstack/plugin.sh | 48 - devstack/settings | 41 - doc/source/conf.py | 89 -- doc/source/contributing.rst | 1 - doc/source/index.rst | 36 - doc/source/install.rst | 194 ---- doc/source/overview.rst | 69 -- etc/bilean/README-bilean.conf.txt | 4 - etc/bilean/api-paste.ini | 31 - etc/bilean/policy.json | 26 - etc/bilean/resource_definitions.yaml | 16 - requirements.txt | 27 - setup.cfg | 54 - setup.py | 30 - test-requirements.txt | 23 - tools/bilean-db-recreate | 16 - tools/config-generator.conf | 16 - tools/gen-config | 3 - tools/setup-service | 59 -- tox.ini | 42 - 133 files changed, 9 insertions(+), 14222 deletions(-) delete mode 100644 .gitignore delete mode 100644 .testr.conf delete mode 100644 CONTRIBUTING.rst delete mode 100644 LICENSE create mode 100644 README delete mode 100644 README.rst delete mode 100644 bilean/__init__.py delete mode 100644 bilean/api/__init__.py delete mode 100644 bilean/api/middleware/__init__.py delete mode 100644 bilean/api/middleware/context.py delete mode 100644 bilean/api/middleware/fault.py delete mode 100644 bilean/api/middleware/ssl.py delete mode 100644 bilean/api/middleware/version_negotiation.py delete mode 100644 bilean/api/openstack/__init__.py delete mode 100644 bilean/api/openstack/v1/__init__.py delete mode 100644 bilean/api/openstack/v1/consumptions.py delete mode 100644 bilean/api/openstack/v1/events.py delete mode 100644 bilean/api/openstack/v1/policies.py delete mode 100644 bilean/api/openstack/v1/resources.py delete mode 100644 bilean/api/openstack/v1/rules.py delete mode 100644 bilean/api/openstack/v1/users.py delete mode 100644 bilean/api/openstack/v1/util.py delete mode 100644 bilean/api/openstack/versions.py delete mode 100644 bilean/api/validator.py delete mode 100644 bilean/cmd/__init__.py delete mode 100644 bilean/cmd/api.py delete mode 100644 bilean/cmd/engine.py delete mode 100644 bilean/cmd/manage.py delete mode 100644 bilean/cmd/notification.py delete mode 100644 bilean/cmd/scheduler.py delete mode 100644 bilean/common/__init__.py delete mode 100644 bilean/common/config.py delete mode 100644 bilean/common/consts.py delete mode 100644 bilean/common/context.py delete mode 100644 bilean/common/exception.py delete mode 100644 bilean/common/i18n.py delete mode 100644 bilean/common/messaging.py delete mode 100644 bilean/common/policy.py delete mode 100644 bilean/common/schema.py delete mode 100644 bilean/common/serializers.py delete mode 100644 bilean/common/utils.py delete mode 100644 bilean/common/wsgi.py delete mode 100644 bilean/db/__init__.py delete mode 100644 bilean/db/api.py delete mode 100644 bilean/db/sqlalchemy/__init__.py delete mode 100644 bilean/db/sqlalchemy/api.py delete mode 100644 bilean/db/sqlalchemy/filters.py delete mode 100644 bilean/db/sqlalchemy/migrate_repo/README delete mode 100644 bilean/db/sqlalchemy/migrate_repo/__init__.py delete mode 100755 bilean/db/sqlalchemy/migrate_repo/manage.py delete mode 100644 bilean/db/sqlalchemy/migrate_repo/migrate.cfg delete mode 100644 bilean/db/sqlalchemy/migrate_repo/versions/001_bilean_init.py delete mode 100644 bilean/db/sqlalchemy/migrate_repo/versions/002_add_job_table.py delete mode 100644 bilean/db/sqlalchemy/migrate_repo/versions/__init__.py delete mode 100644 bilean/db/sqlalchemy/migration.py delete mode 100644 bilean/db/sqlalchemy/models.py delete mode 100644 bilean/db/sqlalchemy/types.py delete mode 100644 bilean/db/utils.py delete mode 100644 bilean/drivers/__init__.py delete mode 100644 bilean/drivers/base.py delete mode 100644 bilean/drivers/openstack/__init__.py delete mode 100644 bilean/drivers/openstack/cinder_v2.py delete mode 100644 bilean/drivers/openstack/keystone_v3.py delete mode 100644 bilean/drivers/openstack/neutron_v2.py delete mode 100644 bilean/drivers/openstack/nova_v2.py delete mode 100644 bilean/drivers/openstack/sdk.py delete mode 100644 bilean/engine/__init__.py delete mode 100644 bilean/engine/actions/__init__.py delete mode 100644 bilean/engine/actions/base.py delete mode 100644 bilean/engine/actions/user_action.py delete mode 100644 bilean/engine/consumption.py delete mode 100644 bilean/engine/dispatcher.py delete mode 100644 bilean/engine/environment.py delete mode 100644 bilean/engine/event.py delete mode 100644 bilean/engine/flows/__init__.py delete mode 100644 bilean/engine/flows/flow.py delete mode 100644 bilean/engine/lock.py delete mode 100644 bilean/engine/parser.py delete mode 100644 bilean/engine/policy.py delete mode 100644 bilean/engine/registry.py delete mode 100644 bilean/engine/service.py delete mode 100644 bilean/engine/user.py delete mode 100644 bilean/notification/__init__.py delete mode 100644 bilean/notification/action.py delete mode 100644 bilean/notification/converter.py delete mode 100644 bilean/notification/endpoint.py delete mode 100644 bilean/notification/notification.py delete mode 100644 bilean/notifier.py delete mode 100644 bilean/plugins/__init__.py delete mode 100644 bilean/plugins/base.py delete mode 100644 bilean/plugins/os/__init__.py delete mode 100644 bilean/plugins/os/cinder/__init__.py delete mode 100644 bilean/plugins/os/cinder/volume.py delete mode 100644 bilean/plugins/os/nova/__init__.py delete mode 100644 bilean/plugins/os/nova/server.py delete mode 100644 bilean/rpc/__init__.py delete mode 100644 bilean/rpc/client.py delete mode 100644 bilean/scheduler/__init__.py delete mode 100644 bilean/scheduler/cron_scheduler.py delete mode 100644 bilean/scheduler/service.py delete mode 100644 bilean/tests/__init__.py delete mode 100644 bilean/tests/common/__init__.py delete mode 100644 bilean/tests/common/base.py delete mode 100644 bilean/tests/common/fakes.py delete mode 100644 bilean/tests/common/utils.py delete mode 100644 bilean/tests/drivers/__init__.py delete mode 100644 bilean/tests/drivers/test_driver.py delete mode 100644 bilean/tests/drivers/test_sdk.py delete mode 100644 bilean/tests/rules/__init__.py delete mode 100644 bilean/tests/rules/test_rule_base.py delete mode 100644 bilean/version.py delete mode 100644 devstack/README.rst delete mode 100644 devstack/lib/bilean delete mode 100755 devstack/plugin.sh delete mode 100644 devstack/settings delete mode 100755 doc/source/conf.py delete mode 100644 doc/source/contributing.rst delete mode 100644 doc/source/index.rst delete mode 100644 doc/source/install.rst delete mode 100644 doc/source/overview.rst delete mode 100644 etc/bilean/README-bilean.conf.txt delete mode 100644 etc/bilean/api-paste.ini delete mode 100644 etc/bilean/policy.json delete mode 100644 etc/bilean/resource_definitions.yaml delete mode 100644 requirements.txt delete mode 100644 setup.cfg delete mode 100644 setup.py delete mode 100644 test-requirements.txt delete mode 100755 tools/bilean-db-recreate delete mode 100644 tools/config-generator.conf delete mode 100755 tools/gen-config delete mode 100755 tools/setup-service delete mode 100644 tox.ini diff --git a/.gitignore b/.gitignore deleted file mode 100644 index ef6c3fd..0000000 --- a/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -*.db -*.log -*.pyc -*.swp -.DS_Store -.coverage -.tox -.testrepository -AUTHORS -ChangeLog -bilean.egg-info/ -bilean/versioninfo -build/ -covhtml -dist/ -doc/build -doc/source/bilean.* -doc/source/modules.rst -etc/bilean.conf -nosetests.xml -pep8.txt -requirements.txt -tests/test.db.pristine -vendor -etc/bilean/bilean.conf.sample diff --git a/.testr.conf b/.testr.conf deleted file mode 100644 index 4b5a0a1..0000000 --- a/.testr.conf +++ /dev/null @@ -1,6 +0,0 @@ -[DEFAULT] -test_command= - PYTHON=$(echo ${PYTHON:-python} | sed 's/--source bilean//g') - ${PYTHON} -m subunit.run discover ${OS_TEST_PATH:-./bilean/tests} -t . $LISTOPT $IDOPTION -test_id_option=--load-list $IDFILE -test_list_option=--list diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index 7cc54e1..0000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1,43 +0,0 @@ -====================== -Contributing to Bilean -====================== - -If you're interested in contributing to the Bilean project, -the following will help get you started. - -Contributor License Agreement -============================= - -In order to contribute to the Bilean project, you need to have -signed OpenStack's contributor's agreement: - -* http://docs.openstack.org/infra/manual/developers.html -* http://wiki.openstack.org/CLA - - -Project Hosting Details -======================= - -* Bug trackers - * General bilean tracker: https://launchpad.net/bilean - - * Python client tracker: https://launchpad.net/python-bileanclient - -* Mailing list (prefix subjects with ``[Bilean]`` for faster responses) - http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev - -* Wiki - https://wiki.openstack.org/wiki/Bilean - -* IRC channel - * #openstack-bilean at FreeNode - -* Code Hosting - * https://git.openstack.org/cgit/openstack/bilean - - * https://git.openstack.org/cgit/openstack/python-bileanclient - -* Code Review - * https://review.openstack.org/#/q/bilean+AND+status:+open,n,z - - * http://docs.openstack.org/infra/manual/developers.html#development-workflow diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 68c771a..0000000 --- a/LICENSE +++ /dev/null @@ -1,176 +0,0 @@ - - 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. - diff --git a/README b/README new file mode 100644 index 0000000..eb99bcd --- /dev/null +++ b/README @@ -0,0 +1,9 @@ +This project is no longer maintained. + +The contents of this repository are still available in the Git +source code management system. To see the contents of this +repository before it reached its end of life, please check out the +previous commit with "git checkout HEAD^1". + +For any further questions, please email +openstack-discuss@lists.openstack.org. diff --git a/README.rst b/README.rst deleted file mode 100644 index bcf6633..0000000 --- a/README.rst +++ /dev/null @@ -1,36 +0,0 @@ -Bilean -====== - --------- -Overview --------- - -Bilean is a billing service for OpenStack clouds, it provides trigger-type -billing based on other OpenStack services' notification. - ---------- -Resources ---------- - -Launchpad Projects ------------------- -- Server: https://launchpad.net/bilean - -Blueprints ----------- -- Blueprints: https://blueprints.launchpad.net/bilean - -Bug Tracking ------------- -- Bugs: https://bugs.launchpad.net/bilean - -Documentation ------------- -- Documentation: http://bilean.readthedocs.io/en/latest - -IRC ---- -IRC Channel: #openstack-bilean on `Freenode`_. - - -.. _Freenode: http://freenode.net/ diff --git a/bilean/__init__.py b/bilean/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/api/__init__.py b/bilean/api/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/api/middleware/__init__.py b/bilean/api/middleware/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/api/middleware/context.py b/bilean/api/middleware/context.py deleted file mode 100644 index 979249d..0000000 --- a/bilean/api/middleware/context.py +++ /dev/null @@ -1,82 +0,0 @@ -# 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 oslo_config import cfg -from oslo_middleware import request_id as oslo_request_id -from oslo_utils import encodeutils - -from bilean.common import context -from bilean.common import exception -from bilean.common import wsgi - - -class ContextMiddleware(wsgi.Middleware): - - def process_request(self, req): - '''Build context from authentication info extracted from request.''' - - headers = req.headers - environ = req.environ - try: - auth_url = headers.get('X-Auth-Url') - if not auth_url: - # Use auth_url defined in bilean.conf - auth_url = cfg.CONF.authentication.auth_url - - auth_token = headers.get('X-Auth-Token') - auth_token_info = environ.get('keystone.token_info') - - project = headers.get('X-Project-Id') - project_name = headers.get('X-Project-Name') - project_domain = headers.get('X-Project-Domain-Id') - project_domain_name = headers.get('X-Project-Domain-Name') - - user = headers.get('X-User-Id') - user_name = headers.get('X-User-Name') - user_domain = headers.get('X-User-Domain-Id') - user_domain_name = headers.get('X-User-Domain-Name') - - domain = headers.get('X-Domain-Id') - domain_name = headers.get('X-Domain-Name') - - region_name = headers.get('X-Region-Name') - - roles = headers.get('X-Roles') - if roles is not None: - roles = roles.split(',') - - env_req_id = environ.get(oslo_request_id.ENV_REQUEST_ID) - if env_req_id is None: - request_id = None - else: - request_id = encodeutils.safe_decode(env_req_id) - - except Exception: - raise exception.NotAuthenticated() - - req.context = context.RequestContext( - auth_token=auth_token, - user=user, - project=project, - domain=domain, - user_domain=user_domain, - project_domain=project_domain, - request_id=request_id, - auth_url=auth_url, - user_name=user_name, - project_name=project_name, - domain_name=domain_name, - user_domain_name=user_domain_name, - project_domain_name=project_domain_name, - auth_token_info=auth_token_info, - region_name=region_name, - roles=roles) diff --git a/bilean/api/middleware/fault.py b/bilean/api/middleware/fault.py deleted file mode 100644 index 16f3e56..0000000 --- a/bilean/api/middleware/fault.py +++ /dev/null @@ -1,135 +0,0 @@ -# -*- coding: utf-8 -*- -# -# 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. - -''' -A middleware that turns exceptions into parsable string. -''' - -import traceback - -from oslo_config import cfg -import six -import webob - -from bilean.common import exception -from bilean.common import serializers -from bilean.common import wsgi - - -class Fault(object): - - def __init__(self, error): - self.error = error - - @webob.dec.wsgify(RequestClass=wsgi.Request) - def __call__(self, req): - serializer = serializers.JSONResponseSerializer() - resp = webob.Response(request=req) - default_webob_exc = webob.exc.HTTPInternalServerError() - resp.status_code = self.error.get('code', default_webob_exc.code) - serializer.default(resp, self.error) - return resp - - -class FaultWrapper(wsgi.Middleware): - """Replace error body with something the client can parse.""" - - error_map = { - 'Forbidden': webob.exc.HTTPForbidden, - 'InternalError': webob.exc.HTTPInternalServerError, - 'InvalidParameter': webob.exc.HTTPBadRequest, - 'InvalidSchemaError': webob.exc.HTTPBadRequest, - 'MultipleChoices': webob.exc.HTTPBadRequest, - 'UserNotFound': webob.exc.HTTPNotFound, - 'RuleNotFound': webob.exc.HTTPNotFound, - 'RuleTypeNotFound': webob.exc.HTTPNotFound, - 'RuleTypeNotMatch': webob.exc.HTTPBadRequest, - 'ReceiverNotFound': webob.exc.HTTPNotFound, - 'RequestLimitExceeded': webob.exc.HTTPBadRequest, - 'ResourceInUse': webob.exc.HTTPConflict, - 'BileanBadRequest': webob.exc.HTTPBadRequest, - 'SpecValidationFailed': webob.exc.HTTPBadRequest, - } - - def _map_exception_to_error(self, class_exception): - if class_exception == Exception: - return webob.exc.HTTPInternalServerError - - if class_exception.__name__ not in self.error_map: - return self._map_exception_to_error(class_exception.__base__) - - return self.error_map[class_exception.__name__] - - def _error(self, ex): - trace = None - traceback_marker = 'Traceback (most recent call last)' - webob_exc = None - if isinstance(ex, exception.HTTPExceptionDisguise): - # An HTTP exception was disguised so it could make it here - # let's remove the disguise and set the original HTTP exception - if cfg.CONF.debug: - trace = ''.join(traceback.format_tb(ex.tb)) - ex = ex.exc - webob_exc = ex - - ex_type = ex.__class__.__name__ - - is_remote = ex_type.endswith('_Remote') - if is_remote: - ex_type = ex_type[:-len('_Remote')] - - full_message = six.text_type(ex) - if '\n' in full_message and is_remote: - message, msg_trace = full_message.split('\n', 1) - elif traceback_marker in full_message: - message, msg_trace = full_message.split(traceback_marker, 1) - message = message.rstrip('\n') - msg_trace = traceback_marker + msg_trace - else: - if six.PY3: - msg_trace = traceback.format_exception(type(ex), ex, - ex.__traceback__) - else: - msg_trace = traceback.format_exc() - - message = full_message - - if isinstance(ex, exception.BileanException): - message = ex.message - - if cfg.CONF.debug and not trace: - trace = msg_trace - - if not webob_exc: - webob_exc = self._map_exception_to_error(ex.__class__) - - error = { - 'code': webob_exc.code, - 'title': webob_exc.title, - 'explanation': webob_exc.explanation, - 'error': { - 'code': webob_exc.code, - 'message': message, - 'type': ex_type, - 'traceback': trace, - } - } - - return error - - def process_request(self, req): - try: - return req.get_response(self.application) - except Exception as exc: - return req.get_response(Fault(self._error(exc))) diff --git a/bilean/api/middleware/ssl.py b/bilean/api/middleware/ssl.py deleted file mode 100644 index e4bfac2..0000000 --- a/bilean/api/middleware/ssl.py +++ /dev/null @@ -1,38 +0,0 @@ -# -# 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 oslo_config import cfg -from oslo_middleware import ssl - -ssl_middleware_opts = [ - cfg.StrOpt('secure_proxy_ssl_header', - default='X-Forwarded-Proto', - deprecated_group='DEFAULT', - help="The HTTP Header that will be used to determine which " - "the original request protocol scheme was, even if it was " - "removed by an SSL terminator proxy.") -] - - -class SSLMiddleware(ssl.SSLMiddleware): - - def __init__(self, application, *args, **kwargs): - # NOTE(cbrandily): calling super(ssl.SSLMiddleware, self).__init__ - # allows defining our opt (including a deprecation). - super(ssl.SSLMiddleware, self).__init__(application, *args, **kwargs) - self.oslo_conf.register_opts( - ssl_middleware_opts, group='oslo_middleware') - - -def list_opts(): - yield None, ssl_middleware_opts diff --git a/bilean/api/middleware/version_negotiation.py b/bilean/api/middleware/version_negotiation.py deleted file mode 100644 index 75da74a..0000000 --- a/bilean/api/middleware/version_negotiation.py +++ /dev/null @@ -1,125 +0,0 @@ -# -# 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. - -""" -A filter middleware that inspects the requested URI for a version string -and/or Accept headers and attempts to negotiate an API controller to -return -""" - -import re -import webob - -from bilean.common import wsgi - -from oslo_log import log as logging - - -LOG = logging.getLogger(__name__) - - -class VersionNegotiationFilter(wsgi.Middleware): - - def __init__(self, version_controller, app, conf, **local_conf): - self.versions_app = version_controller(conf) - self.version_uri_regex = re.compile(r"^v(\d+)\.?(\d+)?") - self.conf = conf - super(VersionNegotiationFilter, self).__init__(app) - - def process_request(self, req): - """Process Accept header or simply return correct API controller. - - If there is a version identifier in the URI, simply - return the correct API controller, otherwise, if we - find an Accept: header, process it - """ - # See if a version identifier is in the URI passed to - # us already. If so, simply return the right version - # API controller - msg = ("Processing request: %(method)s %(path)s Accept: " - "%(accept)s" % {'method': req.method, - 'path': req.path, 'accept': req.accept}) - LOG.debug(msg) - - # If the request is for /versions, just return the versions container - if req.path_info_peek() in ("versions", ""): - return self.versions_app - - match = self._match_version_string(req.path_info_peek(), req) - if match: - major_version = req.environ['api.major_version'] - minor_version = req.environ['api.minor_version'] - - if (major_version == 1 and minor_version == 0): - LOG.debug("Matched versioned URI. " - "Version: %(major_version)d.%(minor_version)d" - % {'major_version': major_version, - 'minor_version': minor_version}) - # Strip the version from the path - req.path_info_pop() - return None - else: - LOG.debug("Unknown version in versioned URI: " - "%(major_version)d.%(minor_version)d. " - "Returning version choices." - % {'major_version': major_version, - 'minor_version': minor_version}) - return self.versions_app - - accept = str(req.accept) - if accept.startswith('application/vnd.openstack.orchestration-'): - token_loc = len('application/vnd.openstack.orchestration-') - accept_version = accept[token_loc:] - match = self._match_version_string(accept_version, req) - if match: - major_version = req.environ['api.major_version'] - minor_version = req.environ['api.minor_version'] - if (major_version == 1 and minor_version == 0): - LOG.debug("Matched versioned media type. Version: " - "%(major_version)d.%(minor_version)d" - % {'major_version': major_version, - 'minor_version': minor_version}) - return None - else: - LOG.debug("Unknown version in accept header: " - "%(major_version)d.%(minor_version)d..." - "returning version choices." - % {'major_version': major_version, - 'minor_version': minor_version}) - return self.versions_app - else: - if req.accept not in ('*/*', ''): - LOG.debug("Unknown accept header: %s..." - "returning HTTP not found.", req.accept) - return webob.exc.HTTPNotFound() - return None - - def _match_version_string(self, subject, req): - """Given a subject, tries to match a major and/or minor version number. - - If found, sets the api.major_version and api.minor_version environ - variables. - - Returns True if there was a match, false otherwise. - - :param subject: The string to check - :param req: Webob.Request object - """ - match = self.version_uri_regex.match(subject) - if match: - major_version, minor_version = match.groups(0) - major_version = int(major_version) - minor_version = int(minor_version) - req.environ['api.major_version'] = major_version - req.environ['api.minor_version'] = minor_version - return match is not None diff --git a/bilean/api/openstack/__init__.py b/bilean/api/openstack/__init__.py deleted file mode 100644 index 4134abc..0000000 --- a/bilean/api/openstack/__init__.py +++ /dev/null @@ -1,36 +0,0 @@ -# -# 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 bilean.api.middleware import context -from bilean.api.middleware import fault -from bilean.api.middleware import ssl -from bilean.api.middleware import version_negotiation -from bilean.api.openstack import versions - - -def version_negotiation_filter(app, conf, **local_conf): - return version_negotiation.VersionNegotiationFilter(versions.Controller, - app, - conf, **local_conf) - - -def faultwrap_filter(app, conf, **local_conf): - return fault.FaultWrapper(app) - - -def sslmiddleware_filter(app, conf, **local_conf): - return ssl.SSLMiddleware(app) - - -def contextmiddleware_filter(app, conf, **local_conf): - return context.ContextMiddleware(app) diff --git a/bilean/api/openstack/v1/__init__.py b/bilean/api/openstack/v1/__init__.py deleted file mode 100644 index 508bfe5..0000000 --- a/bilean/api/openstack/v1/__init__.py +++ /dev/null @@ -1,193 +0,0 @@ -# -# 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 routes - -from bilean.api.openstack.v1 import consumptions -from bilean.api.openstack.v1 import events -from bilean.api.openstack.v1 import policies -from bilean.api.openstack.v1 import resources -from bilean.api.openstack.v1 import rules -from bilean.api.openstack.v1 import users -from bilean.common import wsgi - - -class API(wsgi.Router): - """WSGI router for Bilean v1 ReST API requests.""" - - def __init__(self, conf, **local_conf): - self.conf = conf - mapper = routes.Mapper() - - # Users - users_resource = users.create_resource(conf) - users_path = "/users" - with mapper.submapper(controller=users_resource, - path_prefix=users_path) as user_mapper: - - # User collection - user_mapper.connect("user_index", - "", - action="index", - conditions={'method': 'GET'}) - - # User detail - user_mapper.connect("user_get", - "/{user_id}", - action="get", - conditions={'method': 'GET'}) - - # Update user - user_mapper.connect("user_recharge", - "/{user_id}", - action="recharge", - conditions={'method': 'PUT'}) - - # Action - user_mapper.connect("user_action", - "/{user_id}/action", - action="action", - conditions={'method': 'POST'}) - - # Resources - res_resource = resources.create_resource(conf) - res_path = "/resources" - with mapper.submapper(controller=res_resource, - path_prefix=res_path) as res_mapper: - - # Resource collection - res_mapper.connect("resource_index", - "", - action="index", - conditions={'method': 'GET'}) - - # Resource detail - res_mapper.connect("resource_get", - "/{resource_id}", - action="get", - conditions={'method': 'GET'}) - - # Validate creation - res_mapper.connect("validate_creation", - "", - action="validate_creation", - conditions={'method': 'POST'}) - - # Rules - rule_resource = rules.create_resource(conf) - rule_path = "/rules" - with mapper.submapper(controller=rule_resource, - path_prefix=rule_path) as rule_mapper: - - # Rule collection - rule_mapper.connect("rule_index", - "", - action="index", - conditions={'method': 'GET'}) - - # Rule detail - rule_mapper.connect("rule_get", - "/{rule_id}", - action="get", - conditions={'method': 'GET'}) - - # Create rule - rule_mapper.connect("rule_create", - "", - action="create", - conditions={'method': 'POST'}) - - # Update rule - rule_mapper.connect("rule_update", - "/{rule_id}", - action="update", - conditions={'method': 'PUT'}) - - # Delete rule - rule_mapper.connect("rule_delete", - "/{rule_id}", - action="delete", - conditions={'method': 'DELETE'}) - - # Policies - policy_resource = policies.create_resource(conf) - policy_path = "/policies" - with mapper.submapper(controller=policy_resource, - path_prefix=policy_path) as policy_mapper: - - # Policy collection - policy_mapper.connect("policy_index", - "", - action="index", - conditions={'method': 'GET'}) - - # Policy detail - policy_mapper.connect("policy_get", - "/{policy_id}", - action="get", - conditions={'method': 'GET'}) - - # Create policy - policy_mapper.connect("policy_create", - "", - action="create", - conditions={'method': 'POST'}) - - # Update policy - policy_mapper.connect("policy_update", - "/{policy_id}", - action="update", - conditions={'method': 'PUT'}) - - # Delete policy - policy_mapper.connect("policy_delete", - "/{policy_id}", - action="delete", - conditions={'method': 'DELETE'}) - - # Action - policy_mapper.connect("policy_action", - "/{policy_id}/action", - action="action", - conditions={'method': 'POST'}) - - # Events - event_resource = events.create_resource(conf) - event_path = "/events" - with mapper.submapper(controller=event_resource, - path_prefix=event_path) as event_mapper: - - # Event collection - event_mapper.connect("event_index", - "", - action="index", - conditions={'method': 'GET'}) - - # Consumptions - cons_resource = consumptions.create_resource(conf) - cons_path = "/consumptions" - with mapper.submapper(controller=cons_resource, - path_prefix=cons_path) as cons_mapper: - - # Consumption collection - cons_mapper.connect("consumption_index", - "", - action="index", - conditions={'method': 'GET'}) - - cons_mapper.connect("consumption_statistics", - "/statistics", - action="statistics", - conditions={'method': 'GET'}) - - super(API, self).__init__(mapper) diff --git a/bilean/api/openstack/v1/consumptions.py b/bilean/api/openstack/v1/consumptions.py deleted file mode 100644 index 0e9b872..0000000 --- a/bilean/api/openstack/v1/consumptions.py +++ /dev/null @@ -1,93 +0,0 @@ -# -# 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 bilean.api.openstack.v1 import util -from bilean.common import consts -from bilean.common import serializers -from bilean.common import utils -from bilean.common import wsgi -from bilean.rpc import client as rpc_client - - -class ConsumptionController(object): - """WSGI controller for Consumptions in Bilean v1 API.""" - # Define request scope (must match what is in policy.json) - REQUEST_SCOPE = 'consumptions' - - def __init__(self, options): - self.options = options - self.rpc_client = rpc_client.EngineClient() - - @util.policy_enforce - def index(self, req): - """Lists all consumptions.""" - filter_whitelist = { - 'resource_type': 'mixed', - } - param_whitelist = { - 'user_id': 'single', - 'start_time': 'single', - 'end_time': 'single', - 'limit': 'single', - 'marker': 'single', - 'sort_dir': 'single', - 'sort_keys': 'multi', - } - params = util.get_allowed_params(req.params, param_whitelist) - filters = util.get_allowed_params(req.params, filter_whitelist) - - key = consts.PARAM_LIMIT - if key in params: - params[key] = utils.parse_int_param(key, params[key]) - - if not filters: - filters = None - consumptions = self.rpc_client.consumption_list(req.context, - filters=filters, - **params) - - return {'consumptions': consumptions} - - @util.policy_enforce - def statistics(self, req): - '''Consumptions statistics.''' - filter_whitelist = { - 'resource_type': 'mixed', - } - param_whitelist = { - 'user_id': 'single', - 'start_time': 'single', - 'end_time': 'single', - 'summary': 'single', - } - params = util.get_allowed_params(req.params, param_whitelist) - filters = util.get_allowed_params(req.params, filter_whitelist) - - key = consts.PARAM_SUMMARY - if key in params: - params[key] = utils.parse_bool_param(key, params[key]) - - if not filters: - filters = None - statistics = self.rpc_client.consumption_statistics(req.context, - filters=filters, - **params) - - return {'statistics': statistics} - - -def create_resource(ops): - """Consumption resource factory method.""" - deserializer = wsgi.JSONRequestDeserializer() - serializer = serializers.JSONResponseSerializer() - return wsgi.Resource(ConsumptionController(ops), deserializer, serializer) diff --git a/bilean/api/openstack/v1/events.py b/bilean/api/openstack/v1/events.py deleted file mode 100644 index 00b6aca..0000000 --- a/bilean/api/openstack/v1/events.py +++ /dev/null @@ -1,74 +0,0 @@ -# -# 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 bilean.api.openstack.v1 import util -from bilean.common import consts -from bilean.common import serializers -from bilean.common import utils -from bilean.common import wsgi -from bilean.rpc import client as rpc_client - - -class EventController(object): - """WSGI controller for Events in Bilean v1 API - - Implements the API actions - """ - # Define request scope (must match what is in policy.json) - REQUEST_SCOPE = 'events' - - def __init__(self, options): - self.options = options - self.rpc_client = rpc_client.EngineClient() - - @util.policy_enforce - def index(self, req): - """Lists summary information for all events""" - filter_whitelist = { - 'resource_type': 'mixed', - 'action': 'single', - } - param_whitelist = { - 'user_id': 'single', - 'start_time': 'single', - 'end_time': 'single', - 'limit': 'single', - 'marker': 'single', - 'sort_dir': 'single', - 'sort_keys': 'multi', - 'show_deleted': 'single', - } - params = util.get_allowed_params(req.params, param_whitelist) - filters = util.get_allowed_params(req.params, filter_whitelist) - - key = consts.PARAM_LIMIT - if key in params: - params[key] = utils.parse_int_param(key, params[key]) - - key = consts.PARAM_SHOW_DELETED - if key in params: - params[key] = utils.parse_bool_param(key, params[key]) - - if not filters: - filters = None - - events = self.rpc_client.event_list(req.context, filters=filters, - **params) - return {'events': events} - - -def create_resource(options): - """User resource factory method.""" - deserializer = wsgi.JSONRequestDeserializer() - serializer = serializers.JSONResponseSerializer() - return wsgi.Resource(EventController(options), deserializer, serializer) diff --git a/bilean/api/openstack/v1/policies.py b/bilean/api/openstack/v1/policies.py deleted file mode 100644 index 353295a..0000000 --- a/bilean/api/openstack/v1/policies.py +++ /dev/null @@ -1,173 +0,0 @@ -# -# 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 webob import exc - -from bilean.api.openstack.v1 import util -from bilean.api import validator -from bilean.common import consts -from bilean.common.i18n import _ -from bilean.common import serializers -from bilean.common import utils -from bilean.common import wsgi -from bilean.rpc import client as rpc_client - - -class PolicyData(object): - '''The data accompanying a POST/PUT request to create/update a policy.''' - - def __init__(self, data): - self.data = data - - def name(self): - if consts.POLICY_NAME not in self.data: - raise exc.HTTPBadRequest(_("No policy name specified")) - return self.data[consts.POLICY_NAME] - - def rules(self): - return self.data.get(consts.POLICY_RULES) - - def metadata(self): - return self.data.get(consts.RULE_METADATA) - - -class PolicyController(object): - """WSGI controller for Policys in Bilean v1 API - - Implements the API actions - """ - # Define request scope (must match what is in policy.json) - REQUEST_SCOPE = 'policies' - - SUPPORTED_ACTIONS = ( - ADD_RULES, - ) = ( - 'add_rules', - ) - - def __init__(self, options): - self.options = options - self.rpc_client = rpc_client.EngineClient() - - @util.policy_enforce - def index(self, req): - """List summary information for all policies""" - filter_whitelist = { - 'name': 'mixed', - 'metadata': 'mixed', - } - param_whitelist = { - 'limit': 'single', - 'marker': 'single', - 'sort_dir': 'single', - 'sort_keys': 'multi', - 'show_deleted': 'single', - } - params = util.get_allowed_params(req.params, param_whitelist) - filters = util.get_allowed_params(req.params, filter_whitelist) - - key = consts.PARAM_LIMIT - if key in params: - params[key] = utils.parse_int_param(key, params[key]) - - key = consts.PARAM_SHOW_DELETED - if key in params: - params[key] = utils.parse_bool_param(key, params[key]) - - if not filters: - filters = None - - policies = self.rpc_client.policy_list(req.context, filters=filters, - **params) - - return {'policies': policies} - - @util.policy_enforce - def get(self, req, policy_id): - """Get detailed information for a policy""" - policy = self.rpc_client.policy_get(req.context, - policy_id) - - return {'policy': policy} - - @util.policy_enforce - def create(self, req, body): - """Create a new policy""" - if not validator.is_valid_body(body): - raise exc.HTTPUnprocessableEntity() - - policy_data = body.get('policy') - if policy_data is None: - raise exc.HTTPBadRequest(_("Malformed request data, missing " - "'policy' key in request body.")) - - data = PolicyData(policy_data) - policy = self.rpc_client.policy_create(req.context, - data.name(), - data.rules(), - data.metadata()) - return {'policy': policy} - - @util.policy_enforce - def update(self, req, policy_id, body): - if not validator.is_valid_body(body): - raise exc.HTTPUnprocessableEntity() - - policy_data = body.get('policy') - if policy_data is None: - raise exc.HTTPBadRequest(_("Malformed request data, missing " - "'policy' key in request body.")) - - name = policy_data.get(consts.POLICY_NAME) - metadata = policy_data.get(consts.POLICY_METADATA) - is_default = policy_data.get(consts.POLICY_IS_DEFAULT) - - policy = self.rpc_client.policy_update(req.context, policy_id, name, - metadata, is_default) - return {'policy': policy} - - @util.policy_enforce - def delete(self, req, policy_id): - """Delete a policy with given policy_id""" - self.rpc_client.policy_delete(req.context, policy_id) - - @util.policy_enforce - def action(self, req, policy_id, body=None): - '''Perform specified action on a policy.''' - body = body or {} - if len(body) < 1: - raise exc.HTTPBadRequest(_('No action specified')) - - if len(body) > 1: - raise exc.HTTPBadRequest(_('Multiple actions specified')) - - action = list(body.keys())[0] - if action not in self.SUPPORTED_ACTIONS: - msg = _("Unrecognized action '%s' specified") % action - raise exc.HTTPBadRequest(msg) - - if action == self.ADD_RULES: - rules = body.get(action).get('rules') - if rules is None or not isinstance(rules, list) or len(rules) == 0: - raise exc.HTTPBadRequest(_('No rule to add')) - policy = self.rpc_client.policy_add_rules( - req.context, policy_id, rules) - - return {'policy': policy} - - -def create_resource(options): - """Policy resource factory method.""" - deserializer = wsgi.JSONRequestDeserializer() - serializer = serializers.JSONResponseSerializer() - return wsgi.Resource(PolicyController(options), deserializer, serializer) diff --git a/bilean/api/openstack/v1/resources.py b/bilean/api/openstack/v1/resources.py deleted file mode 100644 index 63af3e0..0000000 --- a/bilean/api/openstack/v1/resources.py +++ /dev/null @@ -1,120 +0,0 @@ -# -# 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 webob import exc - -from bilean.api.openstack.v1 import util -from bilean.api import validator -from bilean.common import consts -from bilean.common import exception -from bilean.common.i18n import _ -from bilean.common import serializers -from bilean.common import utils -from bilean.common import wsgi -from bilean.rpc import client as rpc_client - - -class ResourceController(object): - """WSGI controller for Resources in Bilean v1 API - - Implements the API actions, cause action 'create' and 'delete' is - triggered by notification, it's not necessary to provide here. - """ - # Define request scope (must match what is in policy.json) - REQUEST_SCOPE = 'resources' - - def __init__(self, options): - self.options = options - self.rpc_client = rpc_client.EngineClient() - - @util.policy_enforce - def index(self, req): - """Lists summary information for all resources""" - filter_whitelist = { - 'resource_type': 'mixed', - 'rule_id': 'mixed', - } - param_whitelist = { - 'user_id': 'single', - 'limit': 'single', - 'marker': 'single', - 'sort_dir': 'single', - 'sort_keys': 'multi', - 'show_deleted': 'single', - } - params = util.get_allowed_params(req.params, param_whitelist) - filters = util.get_allowed_params(req.params, filter_whitelist) - - key = consts.PARAM_LIMIT - if key in params: - params[key] = utils.parse_int_param(key, params[key]) - - key = consts.PARAM_SHOW_DELETED - if key in params: - params[key] = utils.parse_bool_param(key, params[key]) - - if not filters: - filters = None - resources = self.rpc_client.resource_list(req.context, filters=filters, - **params) - - return {'resources': resources} - - @util.policy_enforce - def get(self, req, resource_id): - """Gets detailed information for a resource""" - - resource = self.rpc_client.resource_get(req.context, resource_id) - - return {'resource': resource} - - @util.policy_enforce - def validate_creation(self, req, body): - """Validate resources creation - - :param user_id: Id of user to validate - :param body: dict body include resources and count - - :return True|False - """ - if not validator.is_valid_body(body): - raise exc.HTTPUnprocessableEntity() - - resources = body.get('resources') - if not resources: - msg = _("Resources is empty") - raise exc.HTTPBadRequest(explanation=msg) - count = body.get('count') - if count: - try: - validator.validate_integer(count, 'count', - consts.MIN_RESOURCE_NUM, - consts.MAX_RESOURCE_NUM) - except exception.InvalidInput as e: - raise exc.HTTPBadRequest(explanation=e.format_message()) - try: - for resource in resources: - validator.validate_resource(resource) - except exception.InvalidInput as e: - raise exc.HTTPBadRequest(explanation=e.format_message()) - except Exception as e: - raise exc.HTTPBadRequest(explanation=e) - - return self.rpc_client.validate_creation(req.context, body) - - -def create_resource(options): - """Resource resource factory method.""" - deserializer = wsgi.JSONRequestDeserializer() - serializer = serializers.JSONResponseSerializer() - return wsgi.Resource(ResourceController(options), deserializer, serializer) diff --git a/bilean/api/openstack/v1/rules.py b/bilean/api/openstack/v1/rules.py deleted file mode 100644 index ffd60de..0000000 --- a/bilean/api/openstack/v1/rules.py +++ /dev/null @@ -1,128 +0,0 @@ -# -# 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 webob import exc - -from bilean.api.openstack.v1 import util -from bilean.api import validator -from bilean.common import consts -from bilean.common.i18n import _ -from bilean.common import serializers -from bilean.common import utils -from bilean.common import wsgi -from bilean.rpc import client as rpc_client - - -class RuleData(object): - '''The data accompanying a POST/PUT request to create/update a rule.''' - - def __init__(self, data): - self.data = data - - def name(self): - if consts.RULE_NAME not in self.data: - raise exc.HTTPBadRequest(_("No rule name specified")) - return self.data[consts.RULE_NAME] - - def spec(self): - if consts.RULE_SPEC not in self.data: - raise exc.HTTPBadRequest(_("No rule spec provided")) - return self.data[consts.RULE_SPEC] - - def metadata(self): - return self.data.get(consts.RULE_METADATA) - - -class RuleController(object): - """WSGI controller for Rules in Bilean v1 API - - Implements the API actions - """ - # Define request scope (must match what is in policy.json) - REQUEST_SCOPE = 'rules' - - def __init__(self, options): - self.options = options - self.rpc_client = rpc_client.EngineClient() - - @util.policy_enforce - def index(self, req): - """List summary information for all rules""" - filter_whitelist = { - 'name': 'mixed', - 'type': 'mixed', - 'metadata': 'mixed', - } - param_whitelist = { - 'limit': 'single', - 'marker': 'single', - 'sort_dir': 'single', - 'sort_keys': 'multi', - 'show_deleted': 'single', - } - params = util.get_allowed_params(req.params, param_whitelist) - filters = util.get_allowed_params(req.params, filter_whitelist) - - key = consts.PARAM_LIMIT - if key in params: - params[key] = utils.parse_int_param(key, params[key]) - - key = consts.PARAM_SHOW_DELETED - if key in params: - params[key] = utils.parse_bool_param(key, params[key]) - - if not filters: - filters = None - - rules = self.rpc_client.rule_list(req.context, filters=filters, - **params) - - return {'rules': rules} - - @util.policy_enforce - def get(self, req, rule_id): - """Get detailed information for a rule""" - rule = self.rpc_client.rule_get(req.context, - rule_id) - - return {'rule': rule} - - @util.policy_enforce - def create(self, req, body): - """Create a new rule""" - if not validator.is_valid_body(body): - raise exc.HTTPUnprocessableEntity() - - rule_data = body.get('rule') - if rule_data is None: - raise exc.HTTPBadRequest(_("Malformed request data, missing " - "'rule' key in request body.")) - - data = RuleData(rule_data) - rule = self.rpc_client.rule_create(req.context, - data.name(), - data.spec(), - data.metadata()) - return {'rule': rule} - - @util.policy_enforce - def delete(self, req, rule_id): - """Delete a rule with given rule_id""" - self.rpc_client.rule_delete(req.context, rule_id) - - -def create_resource(options): - """Rule resource factory method.""" - deserializer = wsgi.JSONRequestDeserializer() - serializer = serializers.JSONResponseSerializer() - return wsgi.Resource(RuleController(options), deserializer, serializer) diff --git a/bilean/api/openstack/v1/users.py b/bilean/api/openstack/v1/users.py deleted file mode 100644 index ee5d436..0000000 --- a/bilean/api/openstack/v1/users.py +++ /dev/null @@ -1,126 +0,0 @@ -# -# 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 webob import exc - -from bilean.api.openstack.v1 import util -from bilean.api import validator -from bilean.common import consts -from bilean.common import exception -from bilean.common.i18n import _ -from bilean.common import serializers -from bilean.common import utils -from bilean.common import wsgi -from bilean.rpc import client as rpc_client - - -class UserController(object): - """WSGI controller for Users in Bilean v1 API - - Implements the API actions - """ - # Define request scope (must match what is in policy.json) - REQUEST_SCOPE = 'users' - - SUPPORTED_ACTIONS = ( - RECHARGE, ATTACH_POLICY, - ) = ( - 'recharge', 'attach_policy', - ) - - def __init__(self, options): - self.options = options - self.rpc_client = rpc_client.EngineClient() - - @util.policy_enforce - def index(self, req): - filter_whitelist = { - 'status': 'mixed', - } - param_whitelist = { - 'limit': 'single', - 'marker': 'single', - 'sort_dir': 'single', - 'sort_keys': 'multi', - 'show_deleted': 'single', - } - params = util.get_allowed_params(req.params, param_whitelist) - filters = util.get_allowed_params(req.params, filter_whitelist) - - key = consts.PARAM_LIMIT - if key in params: - params[key] = utils.parse_int_param(key, params[key]) - - key = consts.PARAM_SHOW_DELETED - if key in params: - params[key] = utils.parse_bool_param(key, params[key]) - - if not filters: - filters = None - - users = self.rpc_client.user_list(req.context, filters=filters, - **params) - - return {'users': users} - - @util.policy_enforce - def get(self, req, user_id): - """Get detailed information for a user""" - user = self.rpc_client.user_get(req.context, user_id) - return {'user': user} - - @util.policy_enforce - def action(self, req, user_id, body=None): - """Perform specified action on a user.""" - if not validator.is_valid_body(body): - raise exc.HTTPUnprocessableEntity() - - if len(body) < 1: - raise exc.HTTPBadRequest(_('No action specified')) - - if len(body) > 1: - raise exc.HTTPBadRequest(_('Multiple actions specified')) - - action = list(body.keys())[0] - if action not in self.SUPPORTED_ACTIONS: - msg = _("Unrecognized action '%s' specified") % action - raise exc.HTTPBadRequest(msg) - - if action == self.ATTACH_POLICY: - policy = body.get(action).get('policy') - if policy is None: - raise exc.HTTPBadRequest(_("Malformed request data, no policy " - "specified to attach.")) - user = self.rpc_client.user_attach_policy( - req.context, user_id, policy) - elif action == self.RECHARGE: - value = body.get(action).get('value') - if value is None: - raise exc.HTTPBadRequest(_("Malformed request data, missing " - "'value' key in request body.")) - try: - validator.validate_float(value, 'recharge_value', - consts.MIN_VALUE, consts.MAX_VALUE) - except exception.InvalidInput as e: - raise exc.HTTPBadRequest(explanation=e.format_message()) - - user = self.rpc_client.user_recharge(req.context, user_id, value) - - return {'user': user} - - -def create_resource(options): - """User resource factory method.""" - deserializer = wsgi.JSONRequestDeserializer() - serializer = serializers.JSONResponseSerializer() - return wsgi.Resource(UserController(options), deserializer, serializer) diff --git a/bilean/api/openstack/v1/util.py b/bilean/api/openstack/v1/util.py deleted file mode 100644 index 97437e9..0000000 --- a/bilean/api/openstack/v1/util.py +++ /dev/null @@ -1,71 +0,0 @@ -# -# 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 functools - -import six -from webob import exc - -from bilean.common import policy - - -def policy_enforce(handler): - """Decorator that enforces policies. - - Checks the path matches the request context and enforce policy defined in - policy.json. - - This is a handler method decorator. - """ - @functools.wraps(handler) - def handle_bilean_method(controller, req, **kwargs): - rule = "%s:%s" % (controller.REQUEST_SCOPE, handler.__name__) - allowed = policy.enforce(context=req.context, - rule=rule, target={}) - if not allowed: - raise exc.HTTPForbidden() - return handler(controller, req, **kwargs) - - return handle_bilean_method - - -def get_allowed_params(params, whitelist): - """Extract from ``params`` all entries listed in ``whitelist``. - - The returning dict will contain an entry for a key if, and only if, - there's an entry in ``whitelist`` for that key and at least one entry in - ``params``. If ``params`` contains multiple entries for the same key, it - will yield an array of values: ``{key: [v1, v2,...]}`` - - :param params: a NestedMultiDict from webob.Request.params - :param whitelist: an array of strings to whitelist - - :returns: a dict with {key: value} pairs - """ - allowed_params = {} - - for key, get_type in six.iteritems(whitelist): - value = None - if get_type == 'single': - value = params.get(key) - elif get_type == 'multi': - value = params.getall(key) - elif get_type == 'mixed': - value = params.getall(key) - if isinstance(value, list) and len(value) == 1: - value = value.pop() - - if value: - allowed_params[key] = value - - return allowed_params diff --git a/bilean/api/openstack/versions.py b/bilean/api/openstack/versions.py deleted file mode 100644 index 1563be2..0000000 --- a/bilean/api/openstack/versions.py +++ /dev/null @@ -1,54 +0,0 @@ -# -# 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. - -""" -Controller that returns information on the bilean API versions -""" - -import json -from six.moves import http_client -import webob.dec - - -class Controller(object): - - """A controller that produces information on the bilean API versions.""" - - def __init__(self, conf): - self.conf = conf - - @webob.dec.wsgify - def __call__(self, req): - """Respond to a request for all OpenStack API versions.""" - version_objs = [ - { - "id": "v1.0", - "status": "CURRENT", - "links": [ - { - "rel": "self", - "href": self.get_href(req) - }] - }] - - body = json.dumps(dict(versions=version_objs)) - - response = webob.Response(request=req, - status=http_client.MULTIPLE_CHOICES, - content_type='application/json') - response.body = body - - return response - - def get_href(self, req): - return "%s/v1/" % req.host_url diff --git a/bilean/api/validator.py b/bilean/api/validator.py deleted file mode 100644 index 213e4e7..0000000 --- a/bilean/api/validator.py +++ /dev/null @@ -1,202 +0,0 @@ -# Copyright 2011 Cloudscaling, Inc. -# All Rights Reserved. -# -# 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 six - -from bilean.common import consts -from bilean.common import exception -from bilean.common.i18n import _ - -from oslo_log import log as logging -from oslo_utils import uuidutils - - -LOG = logging.getLogger(__name__) - - -def _validate_uuid_format(uid): - return uuidutils.is_uuid_like(uid) - - -def is_valid_body(body, entity_name=None): - if entity_name is not None: - if not (body and entity_name in body): - return False - - def is_dict(d): - try: - d.get(None) - return True - except AttributeError: - return False - - return is_dict(body) - - -def validate(args, validator): - """Validate values of args against validators in validator. - - :param args: Dict of values to be validated. - :param validator: A dict where the keys map to keys in args - and the values are validators. - Applies each validator to ``args[key]`` - :returns: True if validation succeeds. Otherwise False. - - A validator should be a callable which accepts 1 argument and which - returns True if the argument passes validation. False otherwise. - A validator should not raise an exception to indicate validity of the - argument. - - Only validates keys which show up in both args and validator. - - """ - - for key in validator: - if key not in args: - continue - - f = validator[key] - assert callable(f) - - if not f(args[key]): - LOG.debug("%(key)s with value %(value)s failed" - " validator %(name)s", - {'key': key, 'value': args[key], 'name': f.__name__}) - return False - return True - - -def validate_string(value, name=None, min_length=0, max_length=None, - available_fields=None): - """Check the length of specified string - - :param value: the value of the string - :param name: the name of the string - :param min_length: the min_length of the string - :param max_length: the max_length of the string - """ - if not isinstance(value, six.string_types): - if name is None: - msg = _("The input is not a string or unicode") - else: - msg = _("%s is not a string or unicode") % name - raise exception.InvalidInput(message=msg) - - if name is None: - name = value - - if available_fields: - if value not in available_fields: - msg = _("%(name)s must be in %(fields)s") % { - 'name': name, 'fields': available_fields} - raise exception.InvalidInput(message=msg) - - if len(value) < min_length: - msg = _("%(name)s has a minimum character requirement of " - "%(min_length)s.") % {'name': name, 'min_length': min_length} - raise exception.InvalidInput(message=msg) - - if max_length and len(value) > max_length: - msg = _("%(name)s has more than %(max_length)s " - "characters.") % {'name': name, 'max_length': max_length} - raise exception.InvalidInput(message=msg) - - -def validate_resource(resource): - """Make sure that resource is valid""" - - if not is_valid_body(resource): - msg = _("%s is not a dict") % resource - raise exception.InvalidInput(message=msg) - if resource['resource_type']: - validate_string(resource['resource_type'], - available_fields=consts.RESOURCE_TYPES) - else: - msg = _('Expected resource_type field for resource') - raise exception.InvalidInput(reason=msg) - if resource['value']: - validate_integer(resource['value'], 'resource_value', min_value=1) - else: - msg = _('Expected resource value field for resource') - raise exception.InvalidInput(reason=msg) - - -def validate_integer(value, name, min_value=None, max_value=None): - """Make sure that value is a valid integer, potentially within range.""" - - try: - value = int(str(value)) - except (ValueError, UnicodeEncodeError): - msg = _('%(value_name)s must be an integer') - raise exception.InvalidInput(reason=( - msg % {'value_name': name})) - - if min_value is not None: - if value < min_value: - msg = _('%(value_name)s must be >= %(min_value)d') - raise exception.InvalidInput( - reason=(msg % {'value_name': name, - 'min_value': min_value})) - if max_value is not None: - if value > max_value: - msg = _('%(value_name)s must be <= %(max_value)d') - raise exception.InvalidInput( - reason=( - msg % {'value_name': name, - 'max_value': max_value}) - ) - return value - - -def validate_float(value, name, min_value=None, max_value=None): - """Make sure that value is a valid float, potentially within range.""" - - try: - value = float(str(value)) - except (ValueError, UnicodeEncodeError): - msg = _('%(value_name)s must be an float') - raise exception.InvalidInput(reason=( - msg % {'value_name': name})) - - if min_value is not None: - if value < min_value: - msg = _('%(value_name)s must be >= %(min_value)d') - raise exception.InvalidInput( - reason=(msg % {'value_name': name, - 'min_value': min_value})) - if max_value is not None: - if value > max_value: - msg = _('%(value_name)s must be <= %(max_value)d') - raise exception.InvalidInput( - reason=( - msg % {'value_name': name, - 'max_value': max_value})) - return value - - -def is_none_string(val): - """Check if a string represents a None value.""" - - if not isinstance(val, six.string_types): - return False - - return val.lower() == 'none' - - -def check_isinstance(obj, cls): - """Checks that obj is of type cls, and lets PyLint infer types.""" - if isinstance(obj, cls): - return obj - raise Exception(_('Expected object of type: %s') % (str(cls))) diff --git a/bilean/cmd/__init__.py b/bilean/cmd/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/cmd/api.py b/bilean/cmd/api.py deleted file mode 100644 index 804b285..0000000 --- a/bilean/cmd/api.py +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env python -# -# 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. - -""" -Bilean API Server. - -An OpenStack ReST API to Bilean -""" - -import eventlet -eventlet.monkey_patch(os=False) - -import sys - -from bilean.common import config -from bilean.common.i18n import _LI -from bilean.common import messaging -from bilean.common import wsgi -from bilean import version - -from oslo_config import cfg -import oslo_i18n as i18n -from oslo_log import log as logging -from oslo_service import systemd -import six - - -i18n.enable_lazy() - -LOG = logging.getLogger('bilean.api') - - -def main(): - try: - logging.register_options(cfg.CONF) - cfg.CONF(project='bilean', prog='bilean-api', - version=version.version_info.version_string()) - logging.setup(cfg.CONF, 'bilean-api') - messaging.setup() - - app = config.load_paste_app() - - port = cfg.CONF.bilean_api.bind_port - host = cfg.CONF.bilean_api.bind_host - LOG.info(_LI('Starting Bilean ReST API on %(host)s:%(port)s'), - {'host': host, 'port': port}) - server = wsgi.Server('bilean-api', cfg.CONF.bilean_api) - server.start(app, default_port=port) - systemd.notify_once() - server.wait() - except RuntimeError as ex: - sys.exit("ERROR: %s" % six.text_type(ex)) diff --git a/bilean/cmd/engine.py b/bilean/cmd/engine.py deleted file mode 100644 index 2f10b33..0000000 --- a/bilean/cmd/engine.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python -# -# 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. - -""" -Bilean Engine Server. -""" - -import eventlet -eventlet.monkey_patch() - -from bilean.common import consts -from bilean.common import messaging - -from oslo_config import cfg -from oslo_i18n import _lazy -from oslo_log import log as logging -from oslo_service import service - -_lazy.enable_lazy() - - -def main(): - logging.register_options(cfg.CONF) - cfg.CONF(project='bilean', prog='bilean-engine') - logging.setup(cfg.CONF, 'bilean-engine') - logging.set_defaults() - messaging.setup() - - from bilean.engine import service as engine - - srv = engine.EngineService(cfg.CONF.host, consts.ENGINE_TOPIC) - launcher = service.launch(cfg.CONF, srv, - workers=cfg.CONF.num_engine_workers) - launcher.wait() diff --git a/bilean/cmd/manage.py b/bilean/cmd/manage.py deleted file mode 100644 index fc9224a..0000000 --- a/bilean/cmd/manage.py +++ /dev/null @@ -1,91 +0,0 @@ -# -# All Rights Reserved. -# -# 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. - -""" - CLI interface for bilean management. -""" - -import sys - -from oslo_config import cfg -from oslo_log import log as logging - -from bilean.common.i18n import _ -from bilean.db import api -from bilean.db import utils -from bilean import version - - -CONF = cfg.CONF - - -def do_db_version(): - """Print database's current migration level.""" - print(api.db_version(api.get_engine())) - - -def do_db_sync(): - """Place a database under migration control and upgrade. - - Creating first if necessary. - """ - api.db_sync(api.get_engine(), CONF.command.version) - - -def purge_deleted(): - """Remove database records that have been previously soft deleted.""" - utils.purge_deleted(CONF.command.age, CONF.command.granularity) - - -def add_command_parsers(subparsers): - parser = subparsers.add_parser('db_version') - parser.set_defaults(func=do_db_version) - - parser = subparsers.add_parser('db_sync') - parser.set_defaults(func=do_db_sync) - parser.add_argument('version', nargs='?') - parser.add_argument('current_version', nargs='?') - - parser = subparsers.add_parser('purge_deleted') - parser.set_defaults(func=purge_deleted) - parser.add_argument('age', nargs='?', default='90', - help=_('How long to preserve deleted data.')) - parser.add_argument( - '-g', '--granularity', default='days', - choices=['days', 'hours', 'minutes', 'seconds'], - help=_('Granularity to use for age argument, defaults to days.')) - -command_opt = cfg.SubCommandOpt('command', - title='Commands', - help='Show available commands.', - handler=add_command_parsers) - - -def main(): - logging.register_options(CONF) - logging.setup(CONF, 'bilean-manage') - CONF.register_cli_opt(command_opt) - try: - default_config_files = cfg.find_config_files('bilean', 'bilean-engine') - CONF(sys.argv[1:], project='bilean', prog='bilean-manage', - version=version.version_info.version_string(), - default_config_files=default_config_files) - except RuntimeError as e: - sys.exit("ERROR: %s" % e) - - try: - CONF.command.func() - except Exception as e: - sys.exit("ERROR: %s" % e) diff --git a/bilean/cmd/notification.py b/bilean/cmd/notification.py deleted file mode 100644 index 2bebde4..0000000 --- a/bilean/cmd/notification.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python -# -# 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 eventlet -eventlet.monkey_patch() - -from oslo_config import cfg -from oslo_i18n import _lazy -from oslo_log import log as logging -from oslo_service import service - -from bilean.common import messaging - -_lazy.enable_lazy() - - -def main(): - logging.register_options(cfg.CONF) - cfg.CONF(project='bilean', prog='bilean-notification') - logging.setup(cfg.CONF, 'bilean-notification') - logging.set_defaults() - messaging.setup() - - from bilean.notification import notification - - srv = notification.NotificationService() - launcher = service.launch(cfg.CONF, srv) - launcher.wait() diff --git a/bilean/cmd/scheduler.py b/bilean/cmd/scheduler.py deleted file mode 100644 index 6b72603..0000000 --- a/bilean/cmd/scheduler.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python -# -# 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. - -""" -Bilean Scheduler Server. -""" - -import eventlet -eventlet.monkey_patch() - -from bilean.common import consts -from bilean.common import messaging - -from oslo_config import cfg -from oslo_i18n import _lazy -from oslo_log import log as logging -from oslo_service import service - -_lazy.enable_lazy() - - -def main(): - logging.register_options(cfg.CONF) - cfg.CONF(project='bilean', prog='bilean-scheduler') - logging.setup(cfg.CONF, 'bilean-scheduler') - logging.set_defaults() - messaging.setup() - - from bilean.scheduler import service as scheduler - - srv = scheduler.SchedulerService(cfg.CONF.host, consts.SCHEDULER_TOPIC) - launcher = service.launch(cfg.CONF, srv) - launcher.wait() diff --git a/bilean/common/__init__.py b/bilean/common/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/common/config.py b/bilean/common/config.py deleted file mode 100644 index 51496d3..0000000 --- a/bilean/common/config.py +++ /dev/null @@ -1,167 +0,0 @@ -# -# 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. - -"""Routines for configuring Bilean.""" -import logging as sys_logging -import os -import socket - -from oslo_concurrency import processutils -from oslo_config import cfg -from oslo_log import log as logging - -from bilean.common.i18n import _ -from bilean.common import wsgi - - -paste_deploy_group = cfg.OptGroup('paste_deploy') -paste_deploy_opts = [ - cfg.StrOpt('api_paste_config', default="api-paste.ini", - help=_("The API paste config file to use."))] - -service_opts = [ - cfg.IntOpt('periodic_interval', - default=60, - help=_('Seconds between running periodic tasks.')), - cfg.StrOpt('region_name_for_services', - help=_('Default region name used to get services endpoints.')), - cfg.IntOpt('max_response_size', - default=524288, - help=_('Maximum raw byte size of data from web response.')), - cfg.IntOpt('num_engine_workers', - default=processutils.get_worker_count(), - help=_('Number of heat-engine processes to fork and run.')), -] - -engine_opts = [ - cfg.StrOpt('environment_dir', - default='/etc/bilean/environments', - help=_('The directory to search for environment files.')), - cfg.IntOpt('default_action_timeout', - default=3600, - help=_('Timeout in seconds for actions.')), - cfg.IntOpt('lock_retry_times', - default=50, - help=_('Number of times trying to grab a lock.')), - cfg.IntOpt('lock_retry_interval', - default=1, - help=_('Number of seconds between lock retries.')), -] - -rpc_opts = [ - cfg.StrOpt('host', - default=socket.gethostname(), - help=_('Name of the engine node. ' - 'This can be an opaque identifier. ' - 'It is not necessarily a hostname, FQDN, ' - 'or IP address.'))] - -cloud_backend_opts = [ - cfg.StrOpt('cloud_backend', - default='openstack', - help=_('Default cloud backend to use.'))] - -authentication_group = cfg.OptGroup('authentication') -authentication_opts = [ - cfg.StrOpt('auth_url', default='', - help=_('Complete public identity V3 API endpoint.')), - cfg.StrOpt('service_username', default='bilean', - help=_('Bilean service user name')), - cfg.StrOpt('service_password', default='', - help=_('Password specified for the Bilean service user.')), - cfg.StrOpt('service_project_name', default='service', - help=_('Name of the service project.')), - cfg.StrOpt('service_user_domain', default='Default', - help=_('Name of the domain for the service user.')), - cfg.StrOpt('service_project_domain', default='Default', - help=_('Name of the domain for the service project.')), -] - -client_http_log_debug_opts = [ - cfg.BoolOpt('http_log_debug', - default=False, - help=_("Allow client's debug log output."))] - -revision_group = cfg.OptGroup('revision') -revision_opts = [ - cfg.StrOpt('bilean_api_revision', default='1.0', - help=_('Bilean API revision.')), - cfg.StrOpt('bilean_engine_revision', default='1.0', - help=_('Bilean engine revision.'))] - - -def list_opts(): - yield None, rpc_opts - yield None, engine_opts - yield None, service_opts - yield None, cloud_backend_opts - yield paste_deploy_group.name, paste_deploy_opts - yield authentication_group.name, authentication_opts - yield revision_group.name, revision_opts - - -cfg.CONF.register_group(paste_deploy_group) -cfg.CONF.register_group(authentication_group) -cfg.CONF.register_group(revision_group) - -for group, opts in list_opts(): - cfg.CONF.register_opts(opts, group=group) - - -def _get_deployment_config_file(): - """Retrieves the deployment_config_file config item. - - Item formatted as an absolute pathname. - """ - config_path = cfg.CONF.find_file( - cfg.CONF.paste_deploy['api_paste_config']) - if config_path is None: - return None - - return os.path.abspath(config_path) - - -def load_paste_app(app_name=None): - """Builds and returns a WSGI app from a paste config file. - - We assume the last config file specified in the supplied ConfigOpts - object is the paste config file. - - :param app_name: name of the application to load - - :raises RuntimeError when config file cannot be located or application - cannot be loaded from config file - """ - if app_name is None: - app_name = cfg.CONF.prog - - conf_file = _get_deployment_config_file() - if conf_file is None: - raise RuntimeError(_("Unable to locate config file [%s]") % - cfg.CONF.paste_deploy['api_paste_config']) - - try: - app = wsgi.paste_deploy_app(conf_file, app_name, cfg.CONF) - - # Log the options used when starting if we're in debug mode... - if cfg.CONF.debug: - cfg.CONF.log_opt_values(logging.getLogger(app_name), - sys_logging.DEBUG) - - return app - except (LookupError, ImportError) as e: - raise RuntimeError(_("Unable to load %(app_name)s from " - "configuration file %(conf_file)s." - "\nGot: %(e)r") % {'app_name': app_name, - 'conf_file': conf_file, - 'e': e}) diff --git a/bilean/common/consts.py b/bilean/common/consts.py deleted file mode 100644 index 18ea73c..0000000 --- a/bilean/common/consts.py +++ /dev/null @@ -1,128 +0,0 @@ -# -# 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. - -MIN_VALUE = 1 -MAX_VALUE = 100000000 - -MIN_RESOURCE_NUM = 1 -MAX_RESOURCE_NUM = 1000 - -RPC_ATTRS = ( - ENGINE_TOPIC, - SCHEDULER_TOPIC, - ENGINE_DISPATCHER_TOPIC, - RPC_API_VERSION, -) = ( - 'bilean-engine', - 'bilean-scheduler', - 'bilean_engine_dispatcher', - '1.0', -) - -USER_STATUSES = ( - USER_INIT, USER_FREE, USER_ACTIVE, USER_WARNING, USER_FREEZE, -) = ( - 'INIT', 'FREE', 'ACTIVE', 'WARNING', 'FREEZE', -) - -ACTION_NAMES = ( - USER_CREATE_RESOURCE, USER_UPDATE_RESOURCE, USER_DELETE_RESOURCE, - USER_SETTLE_ACCOUNT, -) = ( - 'USER_CREATE_RESOURCE', 'USER_UPDATE_RESOURCE', 'USER_DELETE_RESOURCE', - 'USER_SETTLE_ACCOUNT', -) - -ACTION_STATUSES = ( - ACTION_INIT, ACTION_WAITING, ACTION_READY, ACTION_RUNNING, - ACTION_SUCCEEDED, ACTION_FAILED, ACTION_CANCELLED -) = ( - 'INIT', 'WAITING', 'READY', 'RUNNING', - 'SUCCEEDED', 'FAILED', 'CANCELLED', -) - -RPC_PARAMS = ( - PARAM_SHOW_DELETED, PARAM_SHOW_NESTED, PARAM_LIMIT, PARAM_MARKER, - PARAM_GLOBAL_PROJECT, PARAM_SHOW_DETAILS, - PARAM_SORT_DIR, PARAM_SORT_KEYS, PARAM_SUMMARY, -) = ( - 'show_deleted', 'show_nested', 'limit', 'marker', - 'global_project', 'show_details', - 'sort_dir', 'sort_keys', 'summary', -) - -USER_KEYS = ( - USER_ID, USER_NAME, USER_POLICY_ID, USER_BALANCE, USER_RATE, USER_CREDIT, - USER_LAST_BILL, USER_STATUS, USER_STATUS_REASION, USER_CREATED_AT, - USER_UPDATED_AT, USER_DELETED_AT, -) = ( - 'id', 'name', 'policy_id', 'balance', 'rate', 'credit', - 'last_bill', 'status', 'status_reason', 'created_at', - 'updated_at', 'deleted_at', -) - -RESOURCE_KEYS = ( - RES_ID, RES_USER_ID, RES_RULE_ID, RES_RESOURCE_TYPE, RES_PROPERTIES, - RES_RATE, RES_LAST_BILL, RES_CREATED_AT, RES_UPDATED_AT, RES_DELETED_AT, -) = ( - 'id', 'user_id', 'rule_id', 'resource_type', 'properties', - 'rate', 'last_bill', 'created_at', 'updated_at', 'deleted_at', -) - -RULE_KEYS = ( - RULE_ID, RULE_NAME, RULE_TYPE, RULE_SPEC, RULE_METADATA, - RULE_UPDATED_AT, RULE_CREATED_AT, RULE_DELETED_AT, -) = ( - 'id', 'name', 'type', 'spec', 'metadata', - 'updated_at', 'created_at', 'deleted_at', -) - -EVENT_KEYS = ( - EVENT_ID, EVENT_TIMESTAMP, EVENT_OBJ_ID, EVENT_OBJ_TYPE, EVENT_ACTION, - EVENT_USER_ID, EVENT_LEVEL, EVENT_STATUS, EVENT_STATUS_REASON, - EVENT_METADATA, -) = ( - 'id', 'timestamp', 'obj_id', 'obj_type', 'action', - 'user_id', 'level', 'status', 'status_reason', 'metadata', -) - -POLICY_KEYS = ( - POLICY_ID, POLICY_NAME, POLICY_IS_DEFAULT, POLICY_RULES, POLICY_METADATA, - POLICY_CREATED_AT, POLICY_UPDATED_AT, POLICY_DELETED_AT, -) = ( - 'id', 'name', 'is_default', 'rules', 'metadata', - 'created_at', 'updated_at', 'deleted_at', -) - -CONSUMPTION_KEYS = ( - CONSUMPTION_ID, CONSUMPTION_USER_ID, CONSUMPTION_RESOURCE_ID, - CONSUMPTION_RESOURCE_TYPE, CONSUMPTION_START_TIME, CONSUMPTION_END_TIME, - CONSUMPTION_RATE, CONSUMPTION_COST, CONSUMPTION_METADATA, -) = ( - 'id', 'user_id', 'resource_id', - 'resource_type', 'start_time', 'end_time', - 'rate', 'cost', 'metadata', -) - -RECHARGE_KEYS = ( - RECHARGE_ID, RECHARGE_USER_ID, RECHARGE_TYPE, RECHARGE_TIMESTAMP, - RECHARGE_METADATA, -) = ( - 'id', 'user_id', 'type', 'timestamp', 'metadata', -) - -RECHARGE_TYPES = ( - SELF_RECHARGE, SYSTEM_BONUS, -) = ( - 'Recharge', 'System bonus', -) diff --git a/bilean/common/context.py b/bilean/common/context.py deleted file mode 100644 index 3e4c208..0000000 --- a/bilean/common/context.py +++ /dev/null @@ -1,130 +0,0 @@ -# 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 oslo_context import context as base_context -from oslo_utils import encodeutils - -from bilean.common import policy -from bilean.db import api as db_api -from bilean.drivers import base as driver_base - - -class RequestContext(base_context.RequestContext): - '''Stores information about the security context. - - The context encapsulates information related to the user accessing the - the system, as well as additional request information. - ''' - - def __init__(self, auth_token=None, user=None, project=None, - domain=None, user_domain=None, project_domain=None, - is_admin=None, read_only=False, show_deleted=False, - request_id=None, auth_url=None, trusts=None, - user_name=None, project_name=None, domain_name=None, - user_domain_name=None, project_domain_name=None, - auth_token_info=None, region_name=None, roles=None, - password=None, **kwargs): - - '''Initializer of request context.''' - # We still have 'tenant' param because oslo_context still use it. - super(RequestContext, self).__init__( - auth_token=auth_token, user=user, tenant=project, - domain=domain, user_domain=user_domain, - project_domain=project_domain, - read_only=read_only, show_deleted=show_deleted, - request_id=request_id) - - # request_id might be a byte array - self.request_id = encodeutils.safe_decode(self.request_id) - - # we save an additional 'project' internally for use - self.project = project - - # Session for DB access - self._session = None - - self.auth_url = auth_url - self.trusts = trusts - - self.user_name = user_name - self.project_name = project_name - self.domain_name = domain_name - self.user_domain_name = user_domain_name - self.project_domain_name = project_domain_name - - self.auth_token_info = auth_token_info - self.region_name = region_name - self.roles = roles or [] - self.password = password - - # Check user is admin or not - if is_admin is None: - self.is_admin = policy.enforce(self, 'context_is_admin', - target={'project': self.project}, - do_raise=False) - else: - self.is_admin = is_admin - - @property - def session(self): - if self._session is None: - self._session = db_api.get_session() - return self._session - - def to_dict(self): - return { - 'auth_url': self.auth_url, - 'auth_token': self.auth_token, - 'auth_token_info': self.auth_token_info, - 'user': self.user, - 'user_name': self.user_name, - 'user_domain': self.user_domain, - 'user_domain_name': self.user_domain_name, - 'project': self.project, - 'project_name': self.project_name, - 'project_domain': self.project_domain, - 'project_domain_name': self.project_domain_name, - 'domain': self.domain, - 'domain_name': self.domain_name, - 'trusts': self.trusts, - 'region_name': self.region_name, - 'roles': self.roles, - 'show_deleted': self.show_deleted, - 'is_admin': self.is_admin, - 'request_id': self.request_id, - 'password': self.password, - } - - @classmethod - def from_dict(cls, values): - return cls(**values) - - -def get_service_context(set_project_id=False, **kwargs): - '''An abstraction layer for getting service credential. - - There could be multiple cloud backends for bilean to use. This - abstraction layer provides an indirection for bilean to get the - credentials of 'bilean' user on the specific cloud. By default, - this credential refers to the credentials built for keystone middleware - in an OpenStack cloud. - ''' - identity_service = driver_base.BileanDriver().identity - service_creds = identity_service.get_service_credentials(**kwargs) - if set_project_id: - project = identity_service().conn.session.get_project_id() - service_creds.update(project=project) - return RequestContext(is_admin=True, **service_creds) - - -def get_admin_context(show_deleted=False): - return RequestContext(is_admin=True, show_deleted=show_deleted) diff --git a/bilean/common/exception.py b/bilean/common/exception.py deleted file mode 100644 index efdea38..0000000 --- a/bilean/common/exception.py +++ /dev/null @@ -1,234 +0,0 @@ -# -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# -# 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. - -''' -Bilean exception subclasses. -''' - -import sys - -from oslo_log import log as logging -import six - -from bilean.common.i18n import _, _LE - -_FATAL_EXCEPTION_FORMAT_ERRORS = False -LOG = logging.getLogger(__name__) - - -class BileanException(Exception): - '''Base Bilean Exception. - - To correctly use this class, inherit from it and define - a 'msg_fmt' property. That msg_fmt will get printf'd - with the keyword arguments provided to the constructor. - ''' - - message = _("An unknown exception occurred.") - - def __init__(self, **kwargs): - self.kwargs = kwargs - - try: - self.message = self.msg_fmt % kwargs - except KeyError: - # exc_info = sys.exc_info() - # if kwargs doesn't match a variable in the message - # log the issue and the kwargs - LOG.exception(_LE('Exception in string format operation')) - for name, value in six.iteritems(kwargs): - LOG.error("%s: %s" % (name, value)) # noqa - - if _FATAL_EXCEPTION_FORMAT_ERRORS: - raise - # raise exc_info[0], exc_info[1], exc_info[2] - - def __str__(self): - return six.text_type(self.message) - - def __unicode__(self): - return six.text_type(self.message) - - def __deepcopy__(self, memo): - return self.__class__(**self.kwargs) - - -class SIGHUPInterrupt(BileanException): - msg_fmt = _("System SIGHUP signal received.") - - -class NotAuthenticated(BileanException): - msg_fmt = _("You are not authenticated.") - - -class Forbidden(BileanException): - msg_fmt = _("You are not authorized to complete this action.") - - -class BileanBadRequest(BileanException): - msg_fmt = _("The request is malformed: %(msg)s") - - -class MultipleChoices(BileanException): - msg_fmt = _("Multiple results found matching the query criteria %(arg)s. " - "Please be more specific.") - - -class InvalidInput(BileanException): - msg_fmt = _("Invalid value '%(value)s' specified for '%(name)s'") - - -class InvalidParameter(BileanException): - msg_fmt = _("Invalid value '%(value)s' specified for '%(name)s'") - - -class PluginTypeNotFound(BileanException): - msg_fmt = _("Plugin type (%(plugin_type)s) is not found.") - - -class RuleTypeNotFound(BileanException): - msg_fmt = _("Rule type (%(rule_type)s) is not found.") - - -class RuleTypeNotMatch(BileanException): - msg_fmt = _("%(message)s") - - -class RuleNotFound(BileanException): - msg_fmt = _("The rule (%(rule)s) could not be found.") - - -class RuleNotSpecified(BileanException): - msg_fmt = _("Rule not specified.") - - -class ActionNotFound(BileanException): - msg_fmt = _("The action (%(action)s) could not be found.") - - -class PolicyNotFound(BileanException): - msg_fmt = _("The policy (%(policy)s) could not be found.") - - -class MultipleDefaultPolicy(BileanException): - msg_fmt = _("More than one default policies found.") - - -class UserNotFound(BileanException): - msg_fmt = _("The user (%(user)s) could not be found.") - - -class InvalidSchemaError(BileanException): - msg_fmt = _("%(message)s") - - -class SpecValidationFailed(BileanException): - msg_fmt = _("%(message)s") - - -class FeatureNotSupported(BileanException): - msg_fmt = _("%(feature)s is not supported.") - - -class Error(BileanException): - msg_fmt = "%(message)s" - - def __init__(self, msg): - super(Error, self).__init__(message=msg) - - -class ResourceInUse(BileanException): - msg_fmt = _("The %(resource_type)s (%(resource_id)s) is still in use.") - - -class InvalidContentType(BileanException): - msg_fmt = _("Invalid content type %(content_type)s") - - -class RequestLimitExceeded(BileanException): - msg_fmt = _('Request limit exceeded: %(message)s') - - -class EventNotFound(BileanException): - msg_fmt = _("The event (%(event)s) could not be found.") - - -class ConsumptionNotFound(BileanException): - msg_fmt = _("The consumption (%(consumption)s) could not be found.") - - -class InvalidResource(BileanException): - msg_fmt = _("%(msg)") - - -class InternalError(BileanException): - '''A base class for internal exceptions in bilean. - - The internal exception classes which inherit from :class:`InternalError` - class should be translated to a user facing exception type if need to be - made user visible. - ''' - msg_fmt = _('ERROR %(code)s happens for %(message)s.') - message = _('Internal error happens') - - def __init__(self, **kwargs): - super(InternalError, self).__init__(**kwargs) - if 'code' in kwargs.keys(): - self.code = kwargs.get('code', 500) - self.message = kwargs.get('message') - - -class ResourceBusyError(InternalError): - msg_fmt = _("The %(resource_type)s (%(resource_id)s) is busy now.") - - -class TrustNotFound(InternalError): - # Internal exception, not to be exposed to end user. - msg_fmt = _("The trust for trustor (%(trustor)s) could not be found.") - - -class ResourceDeletionFailure(InternalError): - # Used when deleting resources from other services - msg_fmt = _("Failed in deleting %(resource)s.") - - -class ResourceNotFound(InternalError): - msg_fmt = _("The resource (%(resource)s) could not be found.") - - -class ResourceStatusError(InternalError): - msg_fmt = _("The resource %(resource_id)s is in error status " - "- '%(status)s' due to '%(reason)s'.") - - -class InvalidPlugin(InternalError): - msg_fmt = _("%(message)s") - - -class InvalidSpec(InternalError): - msg_fmt = _("%(message)s") - - -class HTTPExceptionDisguise(Exception): - """Disguises HTTP exceptions. - - The purpose is to let them be handled by the webob fault application - in the wsgi pipeline. - """ - - def __init__(self, exception): - self.exc = exception - self.tb = sys.exc_info()[2] diff --git a/bilean/common/i18n.py b/bilean/common/i18n.py deleted file mode 100644 index 1bbf68f..0000000 --- a/bilean/common/i18n.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2014 IBM Corp. -# -# 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. - -# It's based on oslo.i18n usage in OpenStack Keystone project and -# recommendations from http://docs.openstack.org/developer/oslo.i18n/usage.html - -import oslo_i18n - - -_translators = oslo_i18n.TranslatorFactory(domain='bilean') - -# The primary translation function using the well-known name "_" -_ = _translators.primary - -# Translators for log levels. -# -# The abbreviated names are meant to reflect the usual use of a short -# name like '_'. The "L" is for "log" and the other letter comes from -# the level. -_LI = _translators.log_info -_LW = _translators.log_warning -_LE = _translators.log_error -_LC = _translators.log_critical diff --git a/bilean/common/messaging.py b/bilean/common/messaging.py deleted file mode 100644 index c59a8fd..0000000 --- a/bilean/common/messaging.py +++ /dev/null @@ -1,138 +0,0 @@ -# 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 eventlet - -from oslo_config import cfg -import oslo_messaging -from oslo_serialization import jsonutils - -from bilean.common import context - -NOTIFIER = None -TRANSPORTS = {} -TRANSPORT = None -DEFAULT_URL = "__default__" - - -class RequestContextSerializer(oslo_messaging.Serializer): - def __init__(self, base): - self._base = base - - def serialize_entity(self, ctxt, entity): - if not self._base: - return entity - return self._base.serialize_entity(ctxt, entity) - - def deserialize_entity(self, ctxt, entity): - if not self._base: - return entity - return self._base.deserialize_entity(ctxt, entity) - - @staticmethod - def serialize_context(ctxt): - return ctxt.to_dict() - - @staticmethod - def deserialize_context(ctxt): - return context.RequestContext.from_dict(ctxt) - - -class JsonPayloadSerializer(oslo_messaging.NoOpSerializer): - @classmethod - def serialize_entity(cls, context, entity): - return jsonutils.to_primitive(entity, convert_instances=True) - - -def setup(url=None, optional=False): - """Initialise the oslo_messaging layer.""" - global TRANSPORT, NOTIFIER - - if url and url.startswith("fake://"): - # NOTE(sileht): oslo_messaging fake driver uses time.sleep - # for task switch, so we need to monkey_patch it - eventlet.monkey_patch(time=True) - - if not TRANSPORT: - oslo_messaging.set_transport_defaults('bilean') - exmods = ['bilean.common.exception'] - try: - TRANSPORT = oslo_messaging.get_transport( - cfg.CONF, url, allowed_remote_exmods=exmods) - except oslo_messaging.InvalidTransportURL as e: - TRANSPORT = None - if not optional or e.url: - # NOTE(sileht): oslo_messaging is configured but unloadable - # so reraise the exception - raise - - if not NOTIFIER and TRANSPORT: - serializer = RequestContextSerializer(JsonPayloadSerializer()) - NOTIFIER = oslo_messaging.Notifier(TRANSPORT, serializer=serializer) - - -def cleanup(): - """Cleanup the oslo_messaging layer.""" - global TRANSPORT, TRANSPORTS, NOTIFIER - for url in TRANSPORTS: - TRANSPORTS[url].cleanup() - del TRANSPORTS[url] - TRANSPORT = NOTIFIER = None - - -def get_transport(url=None, optional=False, cache=True): - """Initialise the oslo_messaging layer.""" - global TRANSPORTS, DEFAULT_URL - cache_key = url or DEFAULT_URL - transport = TRANSPORTS.get(cache_key) - if not transport or not cache: - try: - transport = oslo_messaging.get_transport(cfg.CONF, url) - except oslo_messaging.InvalidTransportURL as e: - if not optional or e.url: - # NOTE(sileht): oslo_messaging is configured but unloadable - # so reraise the exception - raise - return None - else: - if cache: - TRANSPORTS[cache_key] = transport - return transport - - -def get_rpc_server(target, endpoint): - """Return a configured oslo_messaging rpc server.""" - serializer = RequestContextSerializer(JsonPayloadSerializer()) - return oslo_messaging.get_rpc_server(TRANSPORT, target, [endpoint], - executor='eventlet', - serializer=serializer) - - -def get_rpc_client(**kwargs): - """Return a configured oslo_messaging RPCClient.""" - target = oslo_messaging.Target(**kwargs) - serializer = RequestContextSerializer(JsonPayloadSerializer()) - return oslo_messaging.RPCClient(TRANSPORT, target, - serializer=serializer) - - -def get_notification_listener(transport, targets, endpoints, - allow_requeue=False): - """Return a configured oslo_messaging notification listener.""" - return oslo_messaging.get_notification_listener( - transport, targets, endpoints, executor='eventlet', - allow_requeue=allow_requeue) - - -def get_notifier(publisher_id): - """Return a configured oslo_messaging notifier.""" - return NOTIFIER.prepare(publisher_id=publisher_id) diff --git a/bilean/common/policy.py b/bilean/common/policy.py deleted file mode 100644 index 8696e45..0000000 --- a/bilean/common/policy.py +++ /dev/null @@ -1,47 +0,0 @@ -# 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. - -""" -Policy Engine For Bilean -""" - -from oslo_config import cfg -from oslo_policy import policy - -from bilean.common import exception - -POLICY_ENFORCER = None -CONF = cfg.CONF - - -def _get_enforcer(policy_file=None, rules=None, default_rule=None): - - global POLICY_ENFORCER - - if POLICY_ENFORCER is None: - POLICY_ENFORCER = policy.Enforcer(CONF, - policy_file=policy_file, - rules=rules, - default_rule=default_rule) - return POLICY_ENFORCER - - -def enforce(context, rule, target, do_raise=True, *args, **kwargs): - - enforcer = _get_enforcer() - credentials = context.to_dict() - target = target or {} - if do_raise: - kwargs.update(exc=exception.Forbidden) - - return enforcer.enforce(rule, target, credentials, do_raise, - *args, **kwargs) diff --git a/bilean/common/schema.py b/bilean/common/schema.py deleted file mode 100644 index 76276b0..0000000 --- a/bilean/common/schema.py +++ /dev/null @@ -1,427 +0,0 @@ -# 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 collections -import numbers -import six - -from oslo_utils import strutils - -from bilean.common import exception -from bilean.common.i18n import _ - - -class AnyIndexDict(collections.Mapping): - '''Convenience schema for a list.''' - def __init__(self, value): - self.value = value - - def __getitem__(self, key): - if key != '*' and not isinstance(key, six.integer_types): - raise KeyError(_('Invalid key %s') % str(key)) - - return self.value - - def __iter__(self): - yield '*' - - def __len__(self): - return 1 - - -class Schema(collections.Mapping): - '''Class for validating rule specifications.''' - - KEYS = ( - TYPE, DESCRIPTION, DEFAULT, REQUIRED, SCHEMA, UPDATABLE, - CONSTRAINTS, READONLY, - ) = ( - 'type', 'description', 'default', 'required', 'schema', 'updatable', - 'constraints', 'readonly', - ) - - TYPES = ( - INTEGER, STRING, NUMBER, BOOLEAN, MAP, LIST, - ) = ( - 'Integer', 'String', 'Number', 'Boolean', 'Map', 'List', - ) - - def __init__(self, description=None, default=None, - required=False, schema=None, updatable=False, - readonly=False, constraints=None): - - if schema is not None: - if type(self) not in (List, Map): - msg = _('Schema valid only for List or Map, not ' - '"%s"') % self[self.TYPE] - raise exception.InvalidSchemaError(message=msg) - - if self[self.TYPE] == self.LIST: - self.schema = AnyIndexDict(schema) - else: - self.schema = schema - - self.description = description - self.default = default - self.required = required - self.updatable = updatable - self.constraints = constraints or [] - self.readonly = readonly - self._len = None - - def has_default(self): - return self.default is not None - - def get_default(self): - return self.resolve(self.default) - - def _validate_default(self, context): - if self.default is None: - return - - try: - self.validate(self.default, context) - except (ValueError, TypeError) as exc: - raise exception.InvalidSchemaError( - message=_('Invalid default %(default)s (%(exc)s)') % - dict(default=self.default, exc=exc)) - - def validate(self, context=None): - '''Validates the schema. - - This method checks if the schema itself is valid. - ''' - self._validate_default(context) - - # validated nested schema: List or Map - if self.schema: - if isinstance(self.schema, AnyIndexDict): - self.schema.value.validate(context) - else: - for nested_schema in self.schema.values(): - nested_schema.validate(context) - - def validate_constraints(self, value, context=None, skipped=None): - if not skipped: - skipped = [] - - try: - for constraint in self.constraints: - if type(constraint) not in skipped: - constraint.validate(value, context) - except ValueError as ex: - raise exception.SpecValidationFailed(message=six.text_type(ex)) - - def __getitem__(self, key): - if key == self.DESCRIPTION: - if self.description is not None: - return self.description - elif key == self.DEFAULT: - if self.default is not None: - return self.default - elif key == self.SCHEMA: - if self.schema is not None: - return dict((n, dict(s)) for n, s in self.schema.items()) - elif key == self.REQUIRED: - return self.required - elif key == self.READONLY: - return self.readonly - elif key == self.CONSTRAINTS: - if self.constraints: - return [dict(c) for c in self.constraints] - - raise KeyError(key) - - def __iter__(self): - for k in self.KEYS: - try: - self[k] - except KeyError: - pass - else: - yield k - - def __len__(self): - if self._len is None: - self._len = len(list(iter(self))) - return self._len - - -class Boolean(Schema): - def __getitem__(self, key): - if key == self.TYPE: - return self.BOOLEAN - else: - return super(Boolean, self).__getitem__(key) - - def to_schema_type(self, value): - return strutils.bool_from_string(str(value), strict=True) - - def resolve(self, value): - if str(value).lower() not in ('true', 'false'): - msg = _('The value "%s" is not a valid Boolean') % value - raise exception.SpecValidationFailed(message=msg) - - return strutils.bool_from_string(value, strict=True) - - def validate(self, value, context=None): - if isinstance(value, bool): - return - - self.resolve(value) - - -class Integer(Schema): - def __getitem__(self, key): - if key == self.TYPE: - return self.INTEGER - else: - return super(Integer, self).__getitem__(key) - - def to_schema_type(self, value): - if isinstance(value, six.integer_types): - return value - try: - num = int(value) - except ValueError: - raise ValueError(_('%s is not an intger.') % num) - - return num - - def resolve(self, value): - try: - return int(value) - except (TypeError, ValueError): - msg = _('The value "%s" cannot be converted into an ' - 'integer.') % value - raise exception.SpecValidationFailed(message=msg) - - def validate(self, value, context=None): - if not isinstance(value, six.integer_types): - value = self.resolve(value) - - self.validate_constraints(value, self, context) - - -class String(Schema): - def __getitem__(self, key): - if key == self.TYPE: - return self.STRING - else: - return super(String, self).__getitem__(key) - - def to_schema_type(self, value): - return str(value) - - def resolve(self, value): - try: - return str(value) - except (TypeError, ValueError): - raise - - def validate(self, value, context=None): - if not isinstance(value, six.string_types): - msg = _('The value "%s" cannot be converted into a ' - 'string.') % value - raise exception.SpecValidationFailed(message=msg) - - self.resolve(value) - self.validate_constraints(value, self, context) - - -class Number(Schema): - def __getitem__(self, key): - if key == self.TYPE: - return self.NUMBER - else: - return super(Number, self).__getitem__(key) - - def to_schema_type(self, value): - if isinstance(value, numbers.Number): - return value - - try: - return int(value) - except ValueError: - return float(value) - - def resolve(self, value): - if isinstance(value, numbers.Number): - return value - - try: - return int(value) - except ValueError: - return float(value) - - def validate(self, value, context=None): - if isinstance(value, numbers.Number): - return - - self.resolve(value) - self.resolve_constraints(value, self, context) - - -class List(Schema): - def __getitem__(self, key): - if key == self.TYPE: - return self.LIST - else: - return super(List, self).__getitem__(key) - - def _get_children(self, values, keys, context): - sub_schema = self.schema - if sub_schema is not None: - # We have a child schema specified for list elements - # Fake a dict of array elements, since we have only one schema - schema_arr = dict((k, sub_schema[k]) for k in keys) - subspec = Spec(schema_arr, dict(values)) - subspec.validate() - - return ((k, subspec[k]) for k in keys) - else: - return values - - def get_default(self): - if not isinstance(self.default, collections.Sequence): - raise TypeError(_('"%s" is not a List') % self.default) - - return self.default - - def resolve(self, value, context=None): - if not isinstance(value, collections.Sequence): - raise TypeError(_('"%s" is not a List') % value) - - return [v[1] for v in self._get_children(enumerate(value), - list(range(len(value))), - context)] - - def validate(self, value, context=None): - if not isinstance(value, collections.Mapping): - raise TypeError(_('"%s" is not a Map') % value) - - for key, child in self.schema.items(): - item_value = value.get(key) - child.validate(item_value, context) - - -class Map(Schema): - def __getitem__(self, key): - if key == self.TYPE: - return self.MAP - else: - return super(Map, self).__getitem__(key) - - def _get_children(self, values, context=None): - # There are cases where the Map is not specified to the very detailed - # levels, we treat them as valid specs as well. - if self.schema is None: - return values - - sub_schema = self.schema - if sub_schema is not None: - # sub_schema shoud be a dict here - subspec = Spec(sub_schema, dict(values)) - subspec.validate() - - return ((k, subspec[k]) for k in sub_schema) - else: - return values - - def get_default(self): - if not isinstance(self.default, collections.Mapping): - raise TypeError(_('"%s" is not a Map') % self.default) - - return self.default - - def resolve(self, value, context=None): - if not isinstance(value, collections.Mapping): - raise TypeError(_('"%s" is not a Map') % value) - - return dict(self._get_children(six.iteritems(value), context)) - - def validate(self, value, context=None): - if not isinstance(value, collections.Mapping): - raise TypeError(_('"%s" is not a Map') % value) - - for key, child in self.schema.items(): - item_value = value.get(key) - child.validate(item_value, context) - - -class Spec(collections.Mapping): - '''A class that contains all spec items.''' - def __init__(self, schema, data): - self._schema = schema - self._data = data - - def validate(self): - '''Validate the schema.''' - for (k, s) in self._schema.items(): - try: - # validate through resolve - self.resolve_value(k) - except (TypeError, ValueError) as err: - msg = _('Spec validation error (%(key)s): %(err)s') % dict( - key=k, err=six.text_type(err)) - raise exception.SpecValidationFailed(message=msg) - - for key in self._data: - if key not in self._schema: - msg = _('Unrecognizable spec item "%s"') % key - raise exception.SpecValidationFailed(message=msg) - - def resolve_value(self, key): - if key not in self: - raise KeyError(_('Invalid spec item: "%s"') % key) - - schema_item = self._schema[key] - if key in self._data: - raw_value = self._data[key] - return schema_item.resolve(raw_value) - elif schema_item.has_default(): - return schema_item.get_default() - elif schema_item.required: - raise ValueError(_('Required spec item "%s" not assigned') % key) - - def __getitem__(self, key): - '''Lazy evaluation for spec items.''' - return self.resolve_value(key) - - def __len__(self): - '''Number of items in the spec. - - A spec always contain all keys though some may be not specified. - ''' - return len(self._schema) - - def __contains__(self, key): - return key in self._schema - - def __iter__(self): - return iter(self._schema) - - -def get_spec_version(spec): - if not isinstance(spec, dict): - msg = _('The provided spec is not a map.') - raise exception.SpecValidationFailed(message=msg) - - if 'type' not in spec: - msg = _("The 'type' key is missing from the provided spec map.") - raise exception.SpecValidationFailed(message=msg) - - if 'version' not in spec: - msg = _("The 'version' key is missing from the provided spec map.") - raise exception.SpecValidationFailed(message=msg) - - return (spec['type'], spec['version']) diff --git a/bilean/common/serializers.py b/bilean/common/serializers.py deleted file mode 100644 index d62f771..0000000 --- a/bilean/common/serializers.py +++ /dev/null @@ -1,41 +0,0 @@ -# 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. - -""" -Utility methods for serializing responses -""" - -import datetime - -from oslo_log import log as logging -from oslo_serialization import jsonutils -from oslo_utils import encodeutils -import six - -LOG = logging.getLogger(__name__) - - -class JSONResponseSerializer(object): - - def to_json(self, data): - def sanitizer(obj): - if isinstance(obj, datetime.datetime): - return obj.isoformat() - return six.text_type(obj) - - response = jsonutils.dumps(data, default=sanitizer, sort_keys=True) - LOG.debug("JSON response : %s" % response) - return response - - def default(self, response, result): - response.content_type = 'application/json' - response.body = encodeutils.safe_encode(self.to_json(result)) diff --git a/bilean/common/utils.py b/bilean/common/utils.py deleted file mode 100644 index 2d1d572..0000000 --- a/bilean/common/utils.py +++ /dev/null @@ -1,198 +0,0 @@ -# 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. - -''' -Utilities module. -''' - -import datetime -import decimal -import random -import six -import string - -from cryptography import fernet -import requests -from requests import exceptions -from six.moves import urllib - -from oslo_config import cfg -from oslo_log import log as logging -from oslo_utils import encodeutils -from oslo_utils import strutils -from oslo_utils import timeutils - -from bilean.common import exception -from bilean.common.i18n import _, _LI - -cfg.CONF.import_opt('max_response_size', 'bilean.common.config') -LOG = logging.getLogger(__name__) - - -class URLFetchError(exception.Error, IOError): - pass - - -def parse_int_param(name, value, allow_zero=True, allow_negative=False, - lower_limit=None, upper_limit=None): - if value is None: - return None - - if value in ('0', 0): - if allow_zero: - return int(value) - raise exception.InvalidParameter(name=name, value=value) - - try: - result = int(value) - except (TypeError, ValueError): - raise exception.InvalidParameter(name=name, value=value) - else: - if any([(allow_negative is False and result < 0), - (lower_limit and result < lower_limit), - (upper_limit and result > upper_limit)]): - raise exception.InvalidParameter(name=name, value=value) - - return result - - -def parse_bool_param(name, value): - if str(value).lower() not in ('true', 'false'): - raise exception.InvalidParameter(name=name, value=str(value)) - - return strutils.bool_from_string(value, strict=True) - - -def url_fetch(url, allowed_schemes=('http', 'https')): - '''Get the data at the specified URL. - - The URL must use the http: or https: schemes. - The file: scheme is also supported if you override - the allowed_schemes argument. - Raise an IOError if getting the data fails. - ''' - LOG.info(_LI('Fetching data from %s'), url) - - components = urllib.parse.urlparse(url) - - if components.scheme not in allowed_schemes: - raise URLFetchError(_('Invalid URL scheme %s') % components.scheme) - - if components.scheme == 'file': - try: - return urllib.request.urlopen(url).read() - except urllib.error.URLError as uex: - raise URLFetchError(_('Failed to retrieve data: %s') % uex) - - try: - resp = requests.get(url, stream=True) - resp.raise_for_status() - - # We cannot use resp.text here because it would download the entire - # file, and a large enough file would bring down the engine. The - # 'Content-Length' header could be faked, so it's necessary to - # download the content in chunks to until max_response_size is reached. - # The chunk_size we use needs to balance CPU-intensive string - # concatenation with accuracy (eg. it's possible to fetch 1000 bytes - # greater than max_response_size with a chunk_size of 1000). - reader = resp.iter_content(chunk_size=1000) - result = "" - for chunk in reader: - result += chunk - if len(result) > cfg.CONF.max_response_size: - raise URLFetchError("Data exceeds maximum allowed size (%s" - " bytes)" % cfg.CONF.max_response_size) - return result - - except exceptions.RequestException as ex: - raise URLFetchError(_('Failed to retrieve data: %s') % ex) - - -def encrypt(msg): - '''Encrypt message with random key. - - :param msg: message to be encrypted - :returns: encrypted msg and key to decrypt - ''' - password = fernet.Fernet.generate_key() - f = fernet.Fernet(password) - key = f.encrypt(encodeutils.safe_encode(msg)) - return encodeutils.safe_decode(password), encodeutils.safe_decode(key) - - -def decrypt(msg, key): - '''Decrypt message using provided key. - - :param msg: encrypted message - :param key: key used to decrypt - :returns: decrypted message string - ''' - f = fernet.Fernet(encodeutils.safe_encode(msg)) - msg = f.decrypt(encodeutils.safe_encode(key)) - - return encodeutils.safe_decode(msg) - - -def random_name(length=8): - if length <= 0: - return '' - - lead = random.choice(string.ascii_letters) - tail = ''.join(random.choice(string.ascii_letters + string.digits) - for i in range(length - 1)) - return lead + tail - - -def format_time(value): - """Cut microsecond and format to isoformat string.""" - if isinstance(value, datetime.datetime): - value = value.replace(microsecond=0) - value = value.isoformat() - return value - - -def format_time_to_seconds(t): - """Format datetime to seconds from 1970-01-01 00:00:00 UTC.""" - epoch = datetime.datetime.utcfromtimestamp(0) - if isinstance(t, datetime.datetime): - return (t - epoch).total_seconds() - if isinstance(t, six.string_types): - try: - dt = timeutils.parse_strtime(t) - except ValueError: - dt = timeutils.normalize_time(timeutils.parse_isotime(t)) - return (dt - epoch).total_seconds() - return t - - -def make_decimal(value): - """Format float to decimal.""" - if isinstance(value, decimal.Decimal): - return value - if isinstance(value, float): - return decimal.Decimal.from_float(value) - return decimal.Decimal(str(value)) - - -def format_decimal(value, num=8): - """Format decimal and keep num decimals.""" - if not isinstance(value, decimal.Decimal): - value = make_decimal(value) - dec = "0.%s" % ('0' * num) - return value.quantize(decimal.Decimal(dec)) - - -def dec2str(value): - """Decimal to str and keep 2 decimals.""" - if not isinstance(value, decimal.Decimal): - value = make_decimal(value) - return str(value.quantize(decimal.Decimal('0.00'))) diff --git a/bilean/common/wsgi.py b/bilean/common/wsgi.py deleted file mode 100644 index 4d3d782..0000000 --- a/bilean/common/wsgi.py +++ /dev/null @@ -1,920 +0,0 @@ -# -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# Copyright 2013 IBM Corp. -# All Rights Reserved. -# -# 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. - -""" -Utility methods for working with WSGI servers -""" - -import abc -import errno -import logging as std_logging -import os -import signal -import sys -import time - -import eventlet -from eventlet.green import socket -from eventlet.green import ssl -import eventlet.greenio -import eventlet.wsgi -import functools -from oslo_config import cfg -import oslo_i18n -from oslo_log import log as logging -from oslo_serialization import jsonutils -from oslo_utils import importutils -from paste import deploy -import routes -import routes.middleware -import six -import webob.dec -import webob.exc - -from bilean.common import exception -from bilean.common.i18n import _, _LE, _LI, _LW -from bilean.common import serializers - - -LOG = logging.getLogger(__name__) -URL_LENGTH_LIMIT = 50000 - -api_opts = [ - cfg.IPOpt('bind_host', default='0.0.0.0', - help=_('Address to bind the server. Useful when ' - 'selecting a particular network interface.')), - cfg.PortOpt('bind_port', default=8770, - help=_('The port on which the server will listen.')), - cfg.IntOpt('backlog', default=4096, - help=_("Number of backlog requests " - "to configure the socket with.")), - cfg.StrOpt('cert_file', - help=_("Location of the SSL certificate file " - "to use for SSL mode.")), - cfg.StrOpt('key_file', - help=_("Location of the SSL key file to use " - "for enabling SSL mode.")), - cfg.IntOpt('workers', default=0, - help=_("Number of workers for Bilean service.")), - cfg.IntOpt('max_header_line', default=16384, - help=_('Maximum line size of message headers to be accepted. ' - 'max_header_line may need to be increased when using ' - 'large tokens (typically those generated by the ' - 'Keystone v3 API with big service catalogs).')), - cfg.IntOpt('tcp_keepidle', default=600, - help=_('The value for the socket option TCP_KEEPIDLE. This is ' - 'the time in seconds that the connection must be idle ' - 'before TCP starts sending keepalive probes.')), -] -api_group = cfg.OptGroup('bilean_api') -cfg.CONF.register_group(api_group) -cfg.CONF.register_opts(api_opts, group=api_group) - -wsgi_eventlet_opts = [ - cfg.BoolOpt('wsgi_keep_alive', default=True, - help=_("If false, closes the client socket explicitly.")), - cfg.IntOpt('client_socket_timeout', default=900, - help=_("Timeout for client connections' socket operations. " - "If an incoming connection is idle for this number of " - "seconds it will be closed. A value of '0' indicates " - "waiting forever.")), -] -wsgi_eventlet_group = cfg.OptGroup('eventlet_opts') -cfg.CONF.register_group(wsgi_eventlet_group) -cfg.CONF.register_opts(wsgi_eventlet_opts, group=wsgi_eventlet_group) - -json_size_opt = cfg.IntOpt('max_json_body_size', default=1048576, - help=_('Maximum raw byte size of JSON request body.' - ' Should be larger than max_template_size.')) -cfg.CONF.register_opt(json_size_opt) - - -def list_opts(): - yield None, [json_size_opt] - yield 'bilean_api', api_opts - yield 'eventlet_opts', wsgi_eventlet_opts - - -def get_bind_addr(conf, default_port=None): - return (conf.bind_host, conf.bind_port or default_port) - - -def get_socket(conf, default_port): - '''Bind socket to bind ip:port in conf - - :param conf: a cfg.ConfigOpts object - :param default_port: port to bind to if none is specified in conf - - :returns : a socket object as returned from socket.listen or - ssl.wrap_socket if conf specifies cert_file - ''' - - bind_addr = get_bind_addr(conf, default_port) - - # TODO(jaypipes): eventlet's greened socket module does not actually - # support IPv6 in getaddrinfo(). We need to get around this in the - # future or monitor upstream for a fix - address_family = [addr[0] for addr in socket.getaddrinfo(bind_addr[0], - bind_addr[1], socket.AF_UNSPEC, socket.SOCK_STREAM) - if addr[0] in (socket.AF_INET, socket.AF_INET6)][0] - - cert_file = conf.cert_file - key_file = conf.key_file - use_ssl = cert_file or key_file - if use_ssl and (not cert_file or not key_file): - raise RuntimeError(_("When running server in SSL mode, you must " - "specify both a cert_file and key_file " - "option value in your configuration file")) - - sock = None - retry_until = time.time() + 30 - while not sock and time.time() < retry_until: - try: - sock = eventlet.listen(bind_addr, backlog=conf.backlog, - family=address_family) - except socket.error as err: - if err.args[0] != errno.EADDRINUSE: - raise - eventlet.sleep(0.1) - - if not sock: - raise RuntimeError(_("Could not bind to %(bind_addr)s after trying " - " 30 seconds") % {'bind_addr': bind_addr}) - return sock - - -class WritableLogger(object): - """A thin wrapper that responds to `write` and logs.""" - - def __init__(self, LOG, level=std_logging.DEBUG): - self.LOG = LOG - self.level = level - - def write(self, msg): - self.LOG.log(self.level, msg.rstrip("\n")) - - -class Server(object): - """Server class to manage multiple WSGI sockets and applications.""" - - def __init__(self, name, conf, threads=1000): - os.umask(0o27) # ensure files are created with the correct privileges - self._logger = logging.getLogger("eventlet.wsgi.server") - self._wsgi_logger = WritableLogger(self._logger) - self.name = name - self.threads = threads - self.children = set() - self.stale_children = set() - self.running = True - self.pgid = os.getpid() - self.conf = conf - try: - os.setpgid(self.pgid, self.pgid) - except OSError: - self.pgid = 0 - - def kill_children(self, *args): - """Kills the entire process group.""" - - LOG.error(_LE('SIGTERM received')) - signal.signal(signal.SIGTERM, signal.SIG_IGN) - signal.signal(signal.SIGINT, signal.SIG_IGN) - self.running = False - os.killpg(0, signal.SIGTERM) - - def hup(self, *args): - """Reloads configuration files with zero down time.""" - - LOG.error(_LE('SIGHUP received')) - signal.signal(signal.SIGHUP, signal.SIG_IGN) - raise exception.SIGHUPInterrupt - - def start(self, application, default_port): - """Run a WSGI server with the given application. - - :param application: The application to run in the WSGI server - :param conf: a cfg.ConfigOpts object - :param default_port: Port to bind to if none is specified in conf - """ - - eventlet.wsgi.MAX_HEADER_LINE = self.conf.max_header_line - self.application = application - self.default_port = default_port - self.configure_socket() - self.start_wsgi() - - def start_wsgi(self): - if self.conf.workers == 0: - # Useful for profiling, test, debug etc. - self.pool = eventlet.GreenPool(size=self.threads) - self.pool.spawn_n(self._single_run, self.application, self.sock) - return - - LOG.info(_LI("Starting %d workers") % self.conf.workers) - signal.signal(signal.SIGTERM, self.kill_children) - signal.signal(signal.SIGINT, self.kill_children) - signal.signal(signal.SIGHUP, self.hup) - while len(self.children) < self.conf.workers: - self.run_child() - - def wait_on_children(self): - """Wait on children exit.""" - - while self.running: - try: - pid, status = os.wait() - if os.WIFEXITED(status) or os.WIFSIGNALED(status): - self._remove_children(pid) - self._verify_and_respawn_children(pid, status) - except OSError as err: - if err.errno not in (errno.EINTR, errno.ECHILD): - raise - except KeyboardInterrupt: - LOG.info(_LI('Caught keyboard interrupt. Exiting.')) - os.killpg(0, signal.SIGTERM) - break - except exception.SIGHUPInterrupt: - self.reload() - continue - - eventlet.greenio.shutdown_safe(self.sock) - self.sock.close() - LOG.debug('Exited') - - def configure_socket(self, old_conf=None, has_changed=None): - """Ensure a socket exists and is appropriately configured. - - This function is called on start up, and can also be - called in the event of a configuration reload. - - When called for the first time a new socket is created. - If reloading and either bind_host or bind port have been - changed the existing socket must be closed and a new - socket opened (laws of physics). - - In all other cases (bind_host/bind_port have not changed) - the existing socket is reused. - - :param old_conf: Cached old configuration settings (if any) - :param has changed: callable to determine if a parameter has changed - """ - - new_sock = (old_conf is None or ( - has_changed('bind_host') or - has_changed('bind_port'))) - # check https - use_ssl = not (not self.conf.cert_file or not self.conf.key_file) - # Were we using https before? - old_use_ssl = (old_conf is not None and not ( - not old_conf.get('key_file') or - not old_conf.get('cert_file'))) - # Do we now need to perform an SSL wrap on the socket? - wrap_sock = use_ssl is True and (old_use_ssl is False or new_sock) - # Do we now need to perform an SSL unwrap on the socket? - unwrap_sock = use_ssl is False and old_use_ssl is True - - if new_sock: - self._sock = None - if old_conf is not None: - self.sock.close() - - _sock = get_socket(self.conf, self.default_port) - _sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - # sockets can hang around forever without keepalive - _sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) - self._sock = _sock - - if wrap_sock: - self.sock = ssl.wrap_socket(self._sock, - certfile=self.conf.cert_file, - keyfile=self.conf.key_file) - - if unwrap_sock: - self.sock = self._sock - - if new_sock and not use_ssl: - self.sock = self._sock - - # Pick up newly deployed certs - if old_conf is not None and use_ssl is True and old_use_ssl is True: - if has_changed('cert_file'): - self.sock.certfile = self.conf.cert_file - if has_changed('key_file'): - self.sock.keyfile = self.conf.key_file - - if new_sock or (old_conf is not None and has_changed('tcp_keepidle')): - # This option isn't available in the OS X version of eventlet - if hasattr(socket, 'TCP_KEEPIDLE'): - self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, - self.conf.tcp_keepidle) - - if old_conf is not None and has_changed('backlog'): - self.sock.listen(self.conf.backlog) - - def _remove_children(self, pid): - - if pid in self.children: - self.children.remove(pid) - LOG.info(_LI('Removed dead child %s'), pid) - elif pid in self.stale_children: - self.stale_children.remove(pid) - LOG.info(_LI('Removed stale child %s'), pid) - else: - LOG.warn(_LW('Unrecognised child %s'), pid) - - def _verify_and_respawn_children(self, pid, status): - if len(self.stale_children) == 0: - LOG.debug('No stale children') - - if os.WIFEXITED(status) and os.WEXITSTATUS(status) != 0: - LOG.error(_LE('Not respawning child %d, cannot ' - 'recover from termination'), pid) - if not self.children and not self.stale_children: - LOG.info(_LI('All workers have terminated. Exiting')) - self.running = False - else: - if len(self.children) < self.conf.workers: - self.run_child() - - def stash_conf_values(self): - """Make a copy of some of the current global CONF's settings. - - Allows determining if any of these values have changed - when the config is reloaded. - """ - conf = {} - conf['bind_host'] = self.conf.bind_host - conf['bind_port'] = self.conf.bind_port - conf['backlog'] = self.conf.backlog - conf['key_file'] = self.conf.key_file - conf['cert_file'] = self.conf.cert_file - return conf - - def reload(self): - """Reload and re-apply configuration settings. - - Existing child processes are sent a SIGHUP signal and will exit after - completing existing requests. New child processes, which will have the - updated configuration, are spawned. This allows preventing - interruption to the service. - """ - def _has_changed(old, new, param): - old = old.get(param) - new = getattr(new, param) - return (new != old) - - old_conf = self.stash_conf_values() - has_changed = functools.partial(_has_changed, old_conf, self.conf) - cfg.CONF.reload_config_files() - os.killpg(self.pgid, signal.SIGHUP) - self.stale_children = self.children - self.children = set() - - # Ensure any logging config changes are picked up - logging.setup(cfg.CONF, self.name) - - self.configure_socket(old_conf, has_changed) - self.start_wsgi() - - def wait(self): - """Wait until all servers have completed running.""" - try: - if self.children: - self.wait_on_children() - else: - self.pool.waitall() - except KeyboardInterrupt: - pass - - def run_child(self): - def child_hup(*args): - """Shuts down child processes, existing requests are handled.""" - signal.signal(signal.SIGHUP, signal.SIG_IGN) - eventlet.wsgi.is_accepting = False - self.sock.close() - - pid = os.fork() - if pid == 0: - signal.signal(signal.SIGHUP, child_hup) - signal.signal(signal.SIGTERM, signal.SIG_DFL) - # ignore the interrupt signal to avoid a race whereby - # a child worker receives the signal before the parent - # and is respawned unnecessarily as a result - signal.signal(signal.SIGINT, signal.SIG_IGN) - # The child has no need to stash the unwrapped - # socket, and the reference prevents a clean - # exit on sighup - self._sock = None - self.run_server() - LOG.info(_LI('Child %d exiting normally'), os.getpid()) - # self.pool.waitall() is now called in wsgi's server so - # it's safe to exit here - sys.exit(0) - else: - LOG.info(_LI('Started child %s'), pid) - self.children.add(pid) - - def run_server(self): - """Run a WSGI server.""" - - eventlet.wsgi.HttpProtocol.default_request_version = "HTTP/1.0" - eventlet.hubs.use_hub('poll') - eventlet.patcher.monkey_patch(all=False, socket=True) - self.pool = eventlet.GreenPool(size=self.threads) - socket_timeout = cfg.CONF.eventlet_opts.client_socket_timeout or None - - try: - eventlet.wsgi.server( - self.sock, self.application, - custom_pool=self.pool, - url_length_limit=URL_LENGTH_LIMIT, - log=self._wsgi_logger, - debug=cfg.CONF.debug, - keepalive=cfg.CONF.eventlet_opts.wsgi_keep_alive, - socket_timeout=socket_timeout) - except socket.error as err: - if err[0] != errno.EINVAL: - raise - - self.pool.waitall() - - def _single_run(self, application, sock): - """Start a WSGI server in a new green thread.""" - - LOG.info(_LI("Starting single process server")) - eventlet.wsgi.server(sock, application, custom_pool=self.pool, - url_length_limit=URL_LENGTH_LIMIT, - log=self._wsgi_logger, debug=cfg.CONF.debug) - - -class Middleware(object): - """Base WSGI middleware wrapper. - - These classes require an application to be initialized that will be called - next. By default the middleware will simply call its wrapped app, or you - can override __call__ to customize its behavior. - """ - - def __init__(self, application): - self.application = application - - def process_request(self, request): - """Called on each request. - - If this returns None, the next application down the stack will be - executed. If it returns a response then that response will be returned - and execution will stop here. - - :param request: A request object to be processed. - :returns: None. - """ - - return None - - def process_response(self, response): - """Customize the response.""" - return response - - @webob.dec.wsgify - def __call__(self, request): - response = self.process_request(request) - if response: - return response - response = request.get_response(self.application) - return self.process_response(response) - - -class Debug(Middleware): - """Helper class that can be inserted into any WSGI application chain.""" - - @webob.dec.wsgify - def __call__(self, req): - print(("*" * 40) + " REQUEST ENVIRON") - for key, value in req.environ.items(): - print(key, "=", value) - print('') - resp = req.get_response(self.application) - - print(("*" * 40) + " RESPONSE HEADERS") - for (key, value) in six.iteritems(resp.headers): - print(key, "=", value) - print('') - - resp.app_iter = self.print_generator(resp.app_iter) - - return resp - - @staticmethod - def print_generator(app_iter): - # Iterator that prints the contents of a wrapper string iterator - # when iterated. - print(("*" * 40) + " BODY") - for part in app_iter: - sys.stdout.write(part) - sys.stdout.flush() - yield part - print('') - - -def debug_filter(app, conf, **local_conf): - return Debug(app) - - -class DefaultMethodController(object): - """A default controller for handling requests. - - This controller handles the OPTIONS request method and any of the - HTTP methods that are not explicitly implemented by the application. - """ - - def options(self, req, allowed_methods, *args, **kwargs): - """Handler of the OPTIONS request method. - - Return a response that includes the 'Allow' header listing the methods - that are implemented. A 204 status code is used for this response. - """ - raise webob.exc.HTTPNoContent(headers=[('Allow', allowed_methods)]) - - def reject(self, req, allowed_methods, *args, **kwargs): - """Return a 405 method not allowed error. - - As a convenience, the 'Allow' header with the list of implemented - methods is included in the response as well. - """ - raise webob.exc.HTTPMethodNotAllowed( - headers=[('Allow', allowed_methods)]) - - -class Router(object): - """WSGI middleware that maps incoming requests to WSGI apps.""" - - def __init__(self, mapper): - """Create a router for the given routes.Mapper.""" - - self.map = mapper - self._router = routes.middleware.RoutesMiddleware(self._dispatch, - self.map) - - @webob.dec.wsgify - def __call__(self, req): - """Route the incoming request to a controller based on self.map.""" - - return self._router - - @staticmethod - @webob.dec.wsgify - def _dispatch(req): - """Private dispatch method. - - Called by self._router() after matching the incoming request to - a route and putting the information into req.environ. - :returns: Either returns 404 or the routed WSGI app's response. - """ - - match = req.environ['wsgiorg.routing_args'][1] - if not match: - return webob.exc.HTTPNotFound() - app = match['controller'] - return app - - -class Request(webob.Request): - """Add some OpenStack API-specific logic to the base webob.Request.""" - - def best_match_content_type(self): - """Determine the requested response content-type.""" - supported = ('application/json',) - bm = self.accept.best_match(supported) - return bm or 'application/json' - - def get_content_type(self, allowed_content_types): - """Determine content type of the request body.""" - if "Content-Type" not in self.headers: - raise exception.InvalidContentType(content_type=None) - - content_type = self.content_type - - if content_type not in allowed_content_types: - raise exception.InvalidContentType(content_type=content_type) - else: - return content_type - - def best_match_language(self): - """Determines best available locale from the Accept-Language header. - - :returns: the best language match or None if the 'Accept-Language' - header was not available in the request. - """ - if not self.accept_language: - return None - all_languages = oslo_i18n.get_available_languages('bilean') - return self.accept_language.best_match(all_languages) - - -def is_json_content_type(request): - - content_type = request.content_type - if not content_type or content_type.startswith('text/plain'): - content_type = 'application/json' - - if (content_type in ('JSON', 'application/json') and - request.body.startswith(b'{')): - return True - return False - - -class JSONRequestDeserializer(object): - - def has_body(self, request): - """Returns whether a Webob.Request object will possess an entity body. - - :param request: A Webob.Request object - """ - if request is None or request.content_length is None: - return False - - if request.content_length > 0 and is_json_content_type(request): - return True - - return False - - def from_json(self, datastring): - try: - if len(datastring) > cfg.CONF.max_json_body_size: - msg = _('JSON body size (%(len)s bytes) exceeds maximum ' - 'allowed size (%(limit)s bytes).' - ) % {'len': len(datastring), - 'limit': cfg.CONF.max_json_body_size} - raise exception.RequestLimitExceeded(message=msg) - return jsonutils.loads(datastring) - except ValueError as ex: - raise webob.exc.HTTPBadRequest(six.text_type(ex)) - - def default(self, request): - if self.has_body(request): - return {'body': self.from_json(request.body)} - else: - return {} - - -class Resource(object): - """WSGI app that handles (de)serialization and controller dispatch. - - Reads routing information supplied by RoutesMiddleware and calls - the requested action method upon its deserializer, controller, - and serializer. Those three objects may implement any of the basic - controller action methods (create, update, show, index, delete) - along with any that may be specified in the api router. A 'default' - method may also be implemented to be used in place of any - non-implemented actions. Deserializer methods must accept a request - argument and return a dictionary. Controller methods must accept a - request argument. Additionally, they must also accept keyword - arguments that represent the keys returned by the Deserializer. They - may raise a webob.exc exception or return a dict, which will be - serialized by requested content type. - """ - - def __init__(self, controller, deserializer, serializer=None): - """Initializer. - - :param controller: object that implement methods created by routes lib - :param deserializer: object that supports webob request deserialization - through controller-like actions - :param serializer: object that supports webob response serialization - through controller-like actions - """ - self.controller = controller - self.deserializer = deserializer - self.serializer = serializer - - @webob.dec.wsgify(RequestClass=Request) - def __call__(self, request): - """WSGI method that controls (de)serialization and method dispatch.""" - action_args = self.get_action_args(request.environ) - action = action_args.pop('action', None) - status_code = action_args.pop('success', None) - - try: - deserialized_request = self.dispatch(self.deserializer, - action, request) - action_args.update(deserialized_request) - - LOG.debug(('Calling %(controller)s : %(action)s'), - {'controller': self.controller, 'action': action}) - - action_result = self.dispatch(self.controller, action, - request, **action_args) - except TypeError as err: - LOG.error(_LE('Exception handling resource: %s') % err) - msg = _('The server could not comply with the request since ' - 'it is either malformed or otherwise incorrect.') - err = webob.exc.HTTPBadRequest(msg) - http_exc = translate_exception(err, request.best_match_language()) - # NOTE(luisg): We disguise HTTP exceptions, otherwise they will be - # treated by wsgi as responses ready to be sent back and they - # won't make it into the pipeline app that serializes errors - raise exception.HTTPExceptionDisguise(http_exc) - except webob.exc.HTTPException as err: - if not isinstance(err, webob.exc.HTTPError): - # Some HTTPException are actually not errors, they are - # responses ready to be sent back to the users, so we don't - # create error log, but disguise and translate them to meet - # openstacksdk's need. - http_exc = translate_exception(err, - request.best_match_language()) - raise exception.HTTPExceptionDisguise(http_exc) - if isinstance(err, webob.exc.HTTPServerError): - LOG.error( - _LE("Returning %(code)s to user: %(explanation)s"), - {'code': err.code, 'explanation': err.explanation}) - http_exc = translate_exception(err, request.best_match_language()) - raise exception.HTTPExceptionDisguise(http_exc) - except exception.BileanException as err: - raise translate_exception(err, request.best_match_language()) - except Exception as err: - log_exception(err, sys.exc_info()) - raise translate_exception(err, request.best_match_language()) - - serializer = self.serializer or serializers.JSONResponseSerializer() - try: - response = webob.Response(request=request) - # Customize status code if default (200) should be overridden - if status_code is not None: - response.status_code = int(status_code) - # Customize 'location' header if provided - if action_result and isinstance(action_result, dict): - location = action_result.pop('location', None) - if location: - response.location = '/v1%s' % location - if not action_result: - action_result = None - self.dispatch(serializer, action, response, action_result) - return response - - # return unserializable result (typically an exception) - except Exception: - return action_result - - def dispatch(self, obj, action, *args, **kwargs): - """Find action-specific method on self and call it.""" - try: - method = getattr(obj, action) - except AttributeError: - method = getattr(obj, 'default') - return method(*args, **kwargs) - - def get_action_args(self, request_environment): - """Parse dictionary created by routes library.""" - try: - args = request_environment['wsgiorg.routing_args'][1].copy() - except Exception: - return {} - - try: - del args['controller'] - except KeyError: - pass - - try: - del args['format'] - except KeyError: - pass - - return args - - -def log_exception(err, exc_info): - args = {'exc_info': exc_info} if cfg.CONF.verbose or cfg.CONF.debug else {} - LOG.error(_LE("Unexpected error occurred serving API: %s"), err, **args) - - -def translate_exception(exc, locale): - """Translates all translatable elements of the given exception.""" - if isinstance(exc, exception.BileanException): - exc.message = oslo_i18n.translate(exc.message, locale) - else: - exc.message = oslo_i18n.translate(six.text_type(exc), locale) - - if isinstance(exc, webob.exc.HTTPError): - exc.explanation = oslo_i18n.translate(exc.explanation, locale) - exc.detail = oslo_i18n.translate(getattr(exc, 'detail', ''), locale) - return exc - - -@six.add_metaclass(abc.ABCMeta) -class BasePasteFactory(object): - """A base class for paste app and filter factories. - - Sub-classes must override the KEY class attribute and provide - a __call__ method. - """ - - KEY = None - - def __init__(self, conf): - self.conf = conf - - @abc.abstractmethod - def __call__(self, global_conf, **local_conf): - return - - def _import_factory(self, local_conf): - """Import an app/filter class. - - Lookup the KEY from the PasteDeploy local conf and import the - class named there. This class can then be used as an app or - filter factory. - """ - class_name = local_conf[self.KEY].replace(':', '.').strip() - return importutils.import_class(class_name) - - -class AppFactory(BasePasteFactory): - """A Generic paste.deploy app factory. - - The WSGI app constructor must accept a ConfigOpts object and a local - config dict as its arguments. - """ - - KEY = 'bilean.app_factory' - - def __call__(self, global_conf, **local_conf): - - factory = self._import_factory(local_conf) - return factory(self.conf, **local_conf) - - -class FilterFactory(AppFactory): - """A Generic paste.deploy filter factory. - - This requires bilean.filter_factory to be set to a callable which returns - a WSGI filter when invoked. The WSGI filter constructor must accept a - WSGI app, a ConfigOpts object and a local config dict as its arguments. - """ - - KEY = 'bilean.filter_factory' - - def __call__(self, global_conf, **local_conf): - - factory = self._import_factory(local_conf) - - def filter(app): - return factory(app, self.conf, **local_conf) - - return filter - - -def setup_paste_factories(conf): - """Set up the generic paste app and filter factories. - - The app factories are constructed at runtime to allow us to pass a - ConfigOpts object to the WSGI classes. - - :param conf: a ConfigOpts object - """ - global app_factory, filter_factory - - app_factory = AppFactory(conf) - filter_factory = FilterFactory(conf) - - -def teardown_paste_factories(): - """Reverse the effect of setup_paste_factories().""" - global app_factory, filter_factory - - del app_factory - del filter_factory - - -def paste_deploy_app(paste_config_file, app_name, conf): - """Load a WSGI app from a PasteDeploy configuration. - - Use deploy.loadapp() to load the app from the PasteDeploy configuration, - ensuring that the supplied ConfigOpts object is passed to the app and - filter constructors. - - :param paste_config_file: a PasteDeploy config file - :param app_name: the name of the app/pipeline to load from the file - :param conf: a ConfigOpts object to supply to the app and its filters - :returns: the WSGI app - """ - setup_paste_factories(conf) - try: - return deploy.loadapp("config:%s" % paste_config_file, name=app_name) - finally: - teardown_paste_factories() diff --git a/bilean/db/__init__.py b/bilean/db/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/db/api.py b/bilean/db/api.py deleted file mode 100644 index 27863d0..0000000 --- a/bilean/db/api.py +++ /dev/null @@ -1,348 +0,0 @@ -# -# 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 oslo_config import cfg -from oslo_db import api - -CONF = cfg.CONF - - -_BACKEND_MAPPING = {'sqlalchemy': 'bilean.db.sqlalchemy.api'} - -IMPL = api.DBAPI.from_config(CONF, backend_mapping=_BACKEND_MAPPING) - - -def get_engine(): - return IMPL.get_engine() - - -def get_session(): - return IMPL.get_session() - - -def db_sync(engine, version=None): - """Migrate the database to `version` or the most recent version.""" - return IMPL.db_sync(engine, version=version) - - -def db_version(engine): - """Display the current database version.""" - return IMPL.db_version(engine) - - -# users -def user_get(context, user_id, show_deleted=False, project_safe=True): - return IMPL.user_get(context, user_id, - show_deleted=show_deleted, - project_safe=project_safe) - - -def user_update(context, user_id, values): - return IMPL.user_update(context, user_id, values) - - -def user_create(context, values): - return IMPL.user_create(context, values) - - -def user_delete(context, user_id): - return IMPL.user_delete(context, user_id) - - -def user_get_all(context, show_deleted=False, limit=None, - marker=None, sort_keys=None, sort_dir=None, - filters=None): - return IMPL.user_get_all(context, show_deleted=show_deleted, - limit=limit, marker=marker, - sort_keys=sort_keys, sort_dir=sort_dir, - filters=filters) - - -# rules -def rule_get(context, rule_id, show_deleted=False): - return IMPL.rule_get(context, rule_id, show_deleted=False) - - -def rule_get_all(context, show_deleted=False, limit=None, - marker=None, sort_keys=None, sort_dir=None, - filters=None): - return IMPL.rule_get_all(context, show_deleted=show_deleted, - limit=limit, marker=marker, - sort_keys=sort_keys, sort_dir=sort_dir, - filters=filters) - - -def rule_create(context, values): - return IMPL.rule_create(context, values) - - -def rule_update(context, rule_id, values): - return IMPL.rule_update(context, rule_id, values) - - -def rule_delete(context, rule_id): - return IMPL.rule_delete(context, rule_id) - - -# resources -def resource_get(context, resource_id, show_deleted=False, project_safe=True): - return IMPL.resource_get(context, resource_id, - show_deleted=show_deleted, - project_safe=project_safe) - - -def resource_get_all(context, user_id=None, show_deleted=False, - limit=None, marker=None, sort_keys=None, - sort_dir=None, filters=None, project_safe=True): - return IMPL.resource_get_all(context, user_id=user_id, - show_deleted=show_deleted, - limit=limit, marker=marker, - sort_keys=sort_keys, sort_dir=sort_dir, - filters=filters, project_safe=project_safe) - - -def resource_create(context, values): - return IMPL.resource_create(context, values) - - -def resource_update(context, resource_id, values): - return IMPL.resource_update(context, resource_id, values) - - -def resource_delete(context, resource_id, soft_delete=True): - IMPL.resource_delete(context, resource_id, soft_delete=soft_delete) - - -# events -def event_get(context, event_id, project_safe=True): - return IMPL.event_get(context, event_id, project_safe=project_safe) - - -def event_get_all(context, limit=None, marker=None, sort_keys=None, - sort_dir=None, filters=None, project_safe=True): - return IMPL.event_get_all(context, limit=limit, - marker=marker, - sort_keys=sort_keys, - sort_dir=sort_dir, - filters=filters, - project_safe=project_safe) - - -def event_create(context, values): - return IMPL.event_create(context, values) - - -def event_delete(context, event_id): - return IMPL.event_delete(context, event_id) - - -# jobs -def job_create(context, values): - return IMPL.job_create(context, values) - - -def job_get_all(context, scheduler_id=None): - return IMPL.job_get_all(context, scheduler_id=scheduler_id) - - -def job_delete(context, job_id): - return IMPL.job_delete(context, job_id) - - -# policies -def policy_get(context, policy_id, show_deleted=False): - return IMPL.policy_get(context, policy_id, show_deleted=False) - - -def policy_get_all(context, limit=None, marker=None, sort_keys=None, - sort_dir=None, filters=None, show_deleted=False): - return IMPL.policy_get_all(context, limit=limit, marker=marker, - sort_keys=sort_keys, sort_dir=sort_dir, - filters=filters, show_deleted=show_deleted) - - -def policy_create(context, values): - return IMPL.policy_create(context, values) - - -def policy_update(context, policy_id, values): - return IMPL.policy_update(context, policy_id, values) - - -def policy_delete(context, policy_id): - return IMPL.policy_delete(context, policy_id) - - -# locks -def user_lock_acquire(user_id, action_id): - return IMPL.user_lock_acquire(user_id, action_id) - - -def user_lock_release(user_id, action_id): - return IMPL.user_lock_release(user_id, action_id) - - -def user_lock_steal(user_id, action_id): - return IMPL.user_lock_steal(user_id, action_id) - - -# actions -def action_create(context, values): - return IMPL.action_create(context, values) - - -def action_update(context, action_id, values): - return IMPL.action_update(context, action_id, values) - - -def action_get(context, action_id, project_safe=True, refresh=False): - return IMPL.action_get(context, action_id, project_safe=project_safe, - refresh=refresh) - - -def action_get_all_by_owner(context, owner): - return IMPL.action_get_all_by_owner(context, owner) - - -def action_get_all(context, filters=None, limit=None, marker=None, sort=None, - project_safe=True): - return IMPL.action_get_all(context, filters=filters, sort=sort, - limit=limit, marker=marker, - project_safe=project_safe) - - -def action_check_status(context, action_id, timestamp): - return IMPL.action_check_status(context, action_id, timestamp) - - -def dependency_add(context, depended, dependent): - return IMPL.dependency_add(context, depended, dependent) - - -def dependency_get_depended(context, action_id): - return IMPL.dependency_get_depended(context, action_id) - - -def dependency_get_dependents(context, action_id): - return IMPL.dependency_get_dependents(context, action_id) - - -def action_mark_succeeded(context, action_id, timestamp): - return IMPL.action_mark_succeeded(context, action_id, timestamp) - - -def action_mark_failed(context, action_id, timestamp, reason=None): - return IMPL.action_mark_failed(context, action_id, timestamp, reason) - - -def action_mark_cancelled(context, action_id, timestamp): - return IMPL.action_mark_cancelled(context, action_id, timestamp) - - -def action_acquire(context, action_id, owner, timestamp): - return IMPL.action_acquire(context, action_id, owner, timestamp) - - -def action_acquire_first_ready(context, owner, timestamp): - return IMPL.action_acquire_first_ready(context, owner, timestamp) - - -def action_abandon(context, action_id): - return IMPL.action_abandon(context, action_id) - - -def action_lock_check(context, action_id, owner=None): - '''Check whether an action has been locked(by a owner).''' - return IMPL.action_lock_check(context, action_id, owner) - - -def action_signal(context, action_id, value): - '''Send signal to an action via DB.''' - return IMPL.action_signal(context, action_id, value) - - -def action_signal_query(context, action_id): - '''Query signal status for the sepcified action.''' - return IMPL.action_signal_query(context, action_id) - - -def action_delete(context, action_id, force=False): - return IMPL.action_delete(context, action_id, force) - - -# services -def service_create(context, host, binary, topic=None): - return IMPL.service_create(context, host, binary, topic=topic) - - -def service_update(context, service_id, values=None): - return IMPL.service_update(context, service_id, values=values) - - -def service_delete(context, service_id): - return IMPL.service_delete(context, service_id) - - -def service_get(context, service_id): - return IMPL.service_get(context, service_id) - - -def service_get_by_host_and_binary(context, host, binary): - return IMPL.service_get_by_host_and_binary(context, host, binary) - - -def service_get_all(context): - return IMPL.service_get_all(context) - - -# consumptions -def consumption_get(context, consumption_id, project_safe=True): - return IMPL.consumption_get(context, consumption_id, - project_safe=project_safe) - - -def consumption_get_all(context, user_id=None, limit=None, marker=None, - sort_keys=None, sort_dir=None, filters=None, - project_safe=True): - return IMPL.consumption_get_all(context, - user_id=user_id, - limit=limit, - marker=marker, - sort_keys=sort_keys, - sort_dir=sort_dir, - filters=filters, - project_safe=project_safe) - - -def consumption_create(context, values): - return IMPL.consumption_create(context, values) - - -# recharges -def recharge_create(context, values): - return IMPL.recharge_create(context, values) - - -def recharge_get(context, recharge_id, project_safe=True): - return IMPL.recharge_get(context, recharge_id, project_safe=project_safe) - - -def recharge_get_all(context, limit=None, marker=None, sort_keys=None, - sort_dir=None, filters=None, project_safe=True): - return IMPL.recharge_get_all(context, limit=limit, - marker=marker, - sort_keys=sort_keys, - sort_dir=sort_dir, - filters=filters, - project_safe=project_safe) diff --git a/bilean/db/sqlalchemy/__init__.py b/bilean/db/sqlalchemy/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/db/sqlalchemy/api.py b/bilean/db/sqlalchemy/api.py deleted file mode 100644 index 300555c..0000000 --- a/bilean/db/sqlalchemy/api.py +++ /dev/null @@ -1,967 +0,0 @@ -# -# 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. - -'''Implementation of SQLAlchemy backend.''' -import six -import sys - -from oslo_config import cfg -from oslo_db.sqlalchemy import session as db_session -from oslo_db.sqlalchemy import utils -from oslo_log import log as logging -from oslo_utils import timeutils - -from sqlalchemy.orm.session import Session - -from bilean.common import consts -from bilean.common import exception -from bilean.common.i18n import _ -from bilean.common.i18n import _LW -from bilean.db.sqlalchemy import filters as db_filters -from bilean.db.sqlalchemy import migration -from bilean.db.sqlalchemy import models - -LOG = logging.getLogger(__name__) -CONF = cfg.CONF - -_facade = None - - -def get_facade(): - global _facade - - if not _facade: - _facade = db_session.EngineFacade.from_config(CONF) - return _facade - -get_engine = lambda: get_facade().get_engine() -get_session = lambda: get_facade().get_session() - - -def get_backend(): - """The backend is this module itself.""" - return sys.modules[__name__] - - -def model_query(context, *args): - session = _session(context) - query = session.query(*args) - return query - - -def _get_sort_keys(sort_keys, mapping): - '''Returns an array containing only whitelisted keys - - :param sort_keys: an array of strings - :param mapping: a mapping from keys to DB column names - :returns: filtered list of sort keys - ''' - if isinstance(sort_keys, six.string_types): - sort_keys = [sort_keys] - return [mapping[key] for key in sort_keys or [] if key in mapping] - - -def _paginate_query(context, query, model, limit=None, marker=None, - sort_keys=None, sort_dir=None, default_sort_keys=None): - if not sort_keys: - sort_keys = default_sort_keys or [] - if not sort_dir: - sort_dir = 'asc' - - model_marker = None - if marker: - model_marker = model_query(context, model).get(marker) - try: - query = utils.paginate_query(query, model, limit, sort_keys, - model_marker, sort_dir) - except utils.InvalidSortKey: - raise exception.InvalidParameter(name='sort_keys', value=sort_keys) - return query - - -def soft_delete_aware_query(context, *args, **kwargs): - """Query helper that accounts for context's `show_deleted` field. - - :param show_deleted: if True, overrides context's show_deleted field. - """ - - query = model_query(context, *args) - show_deleted = kwargs.get('show_deleted') or context.show_deleted - - if not show_deleted: - query = query.filter_by(deleted_at=None) - - return query - - -def _session(context): - return (context and context.session) or get_session() - - -def db_sync(engine, version=None): - """Migrate the database to `version` or the most recent version.""" - return migration.db_sync(engine, version=version) - - -def db_version(engine): - """Display the current database version.""" - return migration.db_version(engine) - - -# users -def user_get(context, user_id, show_deleted=False, project_safe=True): - query = model_query(context, models.User) - user = query.get(user_id) - - deleted_ok = show_deleted or context.show_deleted - if user is None or user.deleted_at is not None and not deleted_ok: - return None - - if project_safe and context.project != user.id: - return None - - return user - - -def user_update(context, user_id, values): - user = user_get(context, user_id, project_safe=False) - - if user is None: - raise exception.UserNotFound(user=user_id) - - user.update(values) - user.save(_session(context)) - return user - - -def user_create(context, values): - user_ref = models.User() - user_ref.update(values) - user_ref.save(_session(context)) - return user_ref - - -def user_delete(context, user_id): - session = _session(context) - user = user_get(context, user_id) - if user is None: - return - - # Delete all related resource records - for resource in user.resources: - session.delete(resource) - - # Delete all related event records - for event in user.events: - session.delete(event) - - user.soft_delete(session=session) - session.flush() - - -def user_get_all(context, show_deleted=False, limit=None, - marker=None, sort_keys=None, sort_dir=None, - filters=None): - query = soft_delete_aware_query(context, models.User, - show_deleted=show_deleted) - - if filters is None: - filters = {} - - sort_key_map = { - consts.USER_CREATED_AT: models.User.created_at.key, - consts.USER_UPDATED_AT: models.User.updated_at.key, - consts.USER_NAME: models.User.name.key, - consts.USER_BALANCE: models.User.balance.key, - consts.USER_STATUS: models.User.status.key, - } - keys = _get_sort_keys(sort_keys, sort_key_map) - - query = db_filters.exact_filter(query, models.User, filters) - return _paginate_query(context, query, models.User, - limit=limit, marker=marker, - sort_keys=keys, sort_dir=sort_dir, - default_sort_keys=['id']).all() - - -# rules -def rule_get(context, rule_id, show_deleted=False): - query = model_query(context, models.Rule) - rule = query.filter_by(id=rule_id).first() - - deleted_ok = show_deleted or context.show_deleted - if rule is None or rule.deleted_at is not None and not deleted_ok: - return None - - return rule - - -def rule_get_all(context, show_deleted=False, limit=None, - marker=None, sort_keys=None, sort_dir=None, - filters=None): - query = soft_delete_aware_query(context, models.Rule, - show_deleted=show_deleted) - - if filters is None: - filters = {} - - sort_key_map = { - consts.RULE_NAME: models.Rule.name.key, - consts.RULE_TYPE: models.Rule.type.key, - consts.RULE_CREATED_AT: models.Rule.created_at.key, - consts.RULE_UPDATED_AT: models.Rule.updated_at.key, - } - keys = _get_sort_keys(sort_keys, sort_key_map) - - query = db_filters.exact_filter(query, models.Rule, filters) - return _paginate_query(context, query, models.Rule, - limit=limit, marker=marker, - sort_keys=keys, sort_dir=sort_dir, - default_sort_keys=['id']).all() - - -def rule_create(context, values): - rule_ref = models.Rule() - rule_ref.update(values) - rule_ref.save(_session(context)) - return rule_ref - - -def rule_update(context, rule_id, values): - rule = rule_get(context, rule_id) - - if rule is None: - raise exception.RuleNotFound(rule=rule_id) - - rule.update(values) - rule.save(_session(context)) - - -def rule_delete(context, rule_id): - rule = rule_get(context, rule_id) - - if rule is None: - return - - session = Session.object_session(rule) - rule.soft_delete(session=session) - session.flush() - - -# resources -def resource_get(context, resource_id, show_deleted=False, project_safe=True): - query = model_query(context, models.Resource) - resource = query.get(resource_id) - - deleted_ok = show_deleted or context.show_deleted - if resource is None or resource.deleted_at is not None and not deleted_ok: - return None - - if project_safe and context.project != resource.user_id: - return None - - return resource - - -def resource_get_all(context, user_id=None, show_deleted=False, - limit=None, marker=None, sort_keys=None, sort_dir=None, - filters=None, project_safe=True): - query = soft_delete_aware_query(context, models.Resource, - show_deleted=show_deleted) - - if project_safe: - query = query.filter_by(user_id=context.project) - - elif user_id: - query = query.filter_by(user_id=user_id) - - if filters is None: - filters = {} - - sort_key_map = { - consts.RES_CREATED_AT: models.Resource.created_at.key, - consts.RES_UPDATED_AT: models.Resource.updated_at.key, - consts.RES_RESOURCE_TYPE: models.Resource.resource_type.key, - consts.RES_USER_ID: models.Resource.user_id.key, - } - keys = _get_sort_keys(sort_keys, sort_key_map) - query = db_filters.exact_filter(query, models.Resource, filters) - return _paginate_query(context, query, models.Resource, - limit=limit, marker=marker, - sort_keys=keys, sort_dir=sort_dir, - default_sort_keys=['id']).all() - - -def resource_create(context, values): - resource_ref = models.Resource() - resource_ref.update(values) - resource_ref.save(_session(context)) - return resource_ref - - -def resource_update(context, resource_id, values): - project_safe = True - if context.is_admin: - project_safe = False - resource = resource_get(context, resource_id, show_deleted=True, - project_safe=project_safe) - - if resource is None: - raise exception.ResourceNotFound(resource=resource_id) - - resource.update(values) - resource.save(_session(context)) - return resource - - -def resource_delete(context, resource_id, soft_delete=True): - resource = resource_get(context, resource_id, project_safe=False) - - if resource is None: - return - - session = Session.object_session(resource) - if soft_delete: - resource.soft_delete(session=session) - else: - session.delete(resource) - session.flush() - - -# events -def event_get(context, event_id, project_safe=True): - query = model_query(context, models.Event) - event = query.get(event_id) - - if event is None: - return None - - if project_safe and context.project != event.user_id: - return None - - return event - - -def event_get_all(context, limit=None, marker=None, sort_keys=None, - sort_dir=None, filters=None, project_safe=True): - query = model_query(context, models.Event) - - if context.is_admin: - project_safe = False - if project_safe: - query = query.filter_by(user_id=context.project) - if filters is None: - filters = {} - - sort_key_map = { - consts.EVENT_LEVEL: models.Event.level.key, - consts.EVENT_TIMESTAMP: models.Event.timestamp.key, - consts.EVENT_USER_ID: models.Event.user_id.key, - consts.EVENT_STATUS: models.Event.status.key, - } - keys = _get_sort_keys(sort_keys, sort_key_map) - query = db_filters.exact_filter(query, models.Event, filters) - return _paginate_query(context, query, models.Event, - limit=limit, marker=marker, - sort_keys=keys, sort_dir=sort_dir, - default_sort_keys=['id']).all() - - -def event_create(context, values): - event_ref = models.Event() - event_ref.update(values) - event_ref.save(_session(context)) - return event_ref - - -# jobs -def job_create(context, values): - job_ref = models.Job() - job_ref.update(values) - job_ref.save(_session(context)) - return job_ref - - -def job_get_all(context, scheduler_id=None): - query = model_query(context, models.Job) - if scheduler_id: - query = query.filter_by(scheduler_id=scheduler_id) - - return query.all() - - -def job_delete(context, job_id): - job = model_query(context, models.Job).get(job_id) - - if job is None: - return - - session = Session.object_session(job) - session.delete(job) - session.flush() - - -# policies -def policy_get(context, policy_id, show_deleted=False): - query = model_query(context, models.Policy) - policy = query.get(policy_id) - - deleted_ok = show_deleted or context.show_deleted - if policy is None or policy.deleted_at is not None and not deleted_ok: - return None - - return policy - - -def policy_get_all(context, limit=None, marker=None, - sort_keys=None, sort_dir=None, - filters=None, show_deleted=False): - query = soft_delete_aware_query(context, models.Policy, - show_deleted=show_deleted) - - if filters is None: - filters = {} - - sort_key_map = { - consts.POLICY_NAME: models.Policy.name.key, - consts.POLICY_CREATED_AT: models.Policy.created_at.key, - consts.POLICY_UPDATED_AT: models.Policy.updated_at.key, - } - keys = _get_sort_keys(sort_keys, sort_key_map) - - query = db_filters.exact_filter(query, models.Policy, filters) - return _paginate_query(context, query, models.Policy, - limit=limit, marker=marker, - sort_keys=keys, sort_dir=sort_dir, - default_sort_keys=['id']).all() - - -def policy_create(context, values): - policy_ref = models.Policy() - policy_ref.update(values) - policy_ref.save(_session(context)) - return policy_ref - - -def policy_update(context, policy_id, values): - policy = policy_get(context, policy_id) - - if policy is None: - raise exception.PolicyNotFound(policy=policy_id) - - policy.update(values) - policy.save(_session(context)) - - -def policy_delete(context, policy_id): - policy = policy_get(context, policy_id) - - if policy is None: - return - - session = Session.object_session(policy) - policy.soft_delete(session=session) - session.flush() - - -# locks -def user_lock_acquire(user_id, action_id): - session = get_session() - session.begin() - - lock = session.query(models.UserLock).get(user_id) - if lock is None: - lock = models.UserLock(user_id=user_id, action_id=action_id) - session.add(lock) - - session.commit() - return lock.action_id - - -def user_lock_release(user_id, action_id): - session = get_session() - session.begin() - - success = False - lock = session.query(models.UserLock).get(user_id) - if lock is not None and lock.action_id == action_id: - session.delete(lock) - success = True - - session.commit() - return success - - -def user_lock_steal(user_id, action_id): - session = get_session() - session.begin() - lock = session.query(models.UserLock).get(user_id) - if lock is not None: - lock.action_id = action_id - lock.save(session) - else: - lock = models.UserLock(user_id=user_id, action_id=action_id) - session.add(lock) - session.commit() - return lock.action_id - - -# actions -def action_create(context, values): - action = models.Action() - action.update(values) - action.save(_session(context)) - return action - - -def action_update(context, action_id, values): - session = get_session() - action = session.query(models.Action).get(action_id) - if not action: - raise exception.ActionNotFound(action=action_id) - - action.update(values) - action.save(session) - - -def action_get(context, action_id, project_safe=True, refresh=False): - session = _session(context) - action = session.query(models.Action).get(action_id) - if action is None: - return None - - if not context.is_admin and project_safe: - if action.project != context.project: - return None - - session.refresh(action) - return action - - -def action_get_all_by_owner(context, owner_id): - query = model_query(context, models.Action).\ - filter_by(owner=owner_id) - return query.all() - - -def action_get_all(context, filters=None, limit=None, marker=None, - sort_keys=None, sort_dir=None): - - query = model_query(context, models.Action) - - if filters: - query = db_filters.exact_filter(query, models.Action, filters) - - sort_key_map = { - consts.ACTION_CREATED_AT: models.Action.created_at.key, - consts.ACTION_UPDATED_AT: models.Action.updated_at.key, - consts.ACTION_NAME: models.Action.name.key, - consts.ACTION_STATUS: models.Action.status.key, - } - keys = _get_sort_keys(sort_keys, sort_key_map) - - query = db_filters.exact_filter(query, models.Action, filters) - return _paginate_query(context, query, models.Action, - limit=limit, marker=marker, - sort_keys=keys, sort_dir=sort_dir, - default_sort_keys=['id']).all() - - -def action_check_status(context, action_id, timestamp): - session = _session(context) - q = session.query(models.ActionDependency) - count = q.filter_by(dependent=action_id).count() - if count > 0: - return consts.ACTION_WAITING - - action = session.query(models.Action).get(action_id) - if action.status == consts.ACTION_WAITING: - session.begin() - action.status = consts.ACTION_READY - action.status_reason = _('All depended actions completed.') - action.end_time = timestamp - action.save(session) - session.commit() - - return action.status - - -def dependency_get_depended(context, action_id): - session = _session(context) - q = session.query(models.ActionDependency).filter_by(dependent=action_id) - return [d.depended for d in q.all()] - - -def dependency_get_dependents(context, action_id): - session = _session(context) - q = session.query(models.ActionDependency).filter_by(depended=action_id) - return [d.dependent for d in q.all()] - - -def dependency_add(context, depended, dependent): - if isinstance(depended, list) and isinstance(dependent, list): - raise exception.NotSupport( - _('Multiple dependencies between lists not support')) - - session = _session(context) - - if isinstance(depended, list): - session.begin() - for d in depended: - r = models.ActionDependency(depended=d, dependent=dependent) - session.add(r) - - query = session.query(models.Action).filter_by(id=dependent) - query.update({'status': consts.ACTION_WAITING, - 'status_reason': _('Waiting for depended actions.')}, - synchronize_session=False) - session.commit() - return - - # Only dependent can be a list now, convert it to a list if it - # is not a list - if not isinstance(dependent, list): # e.g. B,C,D depend on A - dependents = [dependent] - else: - dependents = dependent - - session.begin() - for d in dependents: - r = models.ActionDependency(depended=depended, dependent=d) - session.add(r) - - q = session.query(models.Action).filter(models.Action.id.in_(dependents)) - q.update({'status': consts.ACTION_WAITING, - 'status_reason': _('Waiting for depended actions.')}, - synchronize_session=False) - session.commit() - - -def action_mark_succeeded(context, action_id, timestamp): - session = _session(context) - session.begin() - - query = session.query(models.Action).filter_by(id=action_id) - values = { - 'owner': None, - 'status': consts.ACTION_SUCCEEDED, - 'status_reason': _('Action completed successfully.'), - 'end_time': timestamp, - } - query.update(values, synchronize_session=False) - - subquery = session.query(models.ActionDependency).filter_by( - depended=action_id) - subquery.delete(synchronize_session=False) - session.commit() - - -def _mark_failed(session, action_id, timestamp, reason=None): - # mark myself as failed - query = session.query(models.Action).filter_by(id=action_id) - values = { - 'owner': None, - 'status': consts.ACTION_FAILED, - 'status_reason': (six.text_type(reason) if reason else - _('Action execution failed')), - 'end_time': timestamp, - } - query.update(values, synchronize_session=False) - - query = session.query(models.ActionDependency) - query = query.filter_by(depended=action_id) - dependents = [d.dependent for d in query.all()] - query.delete(synchronize_session=False) - - for d in dependents: - _mark_failed(session, d, timestamp) - - -def action_mark_failed(context, action_id, timestamp, reason=None): - session = _session(context) - session.begin() - _mark_failed(session, action_id, timestamp, reason) - session.commit() - - -def _mark_cancelled(session, action_id, timestamp, reason=None): - query = session.query(models.Action).filter_by(id=action_id) - values = { - 'owner': None, - 'status': consts.ACTION_CANCELLED, - 'status_reason': (six.text_type(reason) if reason else - _('Action execution failed')), - 'end_time': timestamp, - } - query.update(values, synchronize_session=False) - - query = session.query(models.ActionDependency) - query = query.filter_by(depended=action_id) - dependents = [d.dependent for d in query.all()] - query.delete(synchronize_session=False) - - for d in dependents: - _mark_cancelled(session, d, timestamp) - - -def action_mark_cancelled(context, action_id, timestamp, reason=None): - session = _session(context) - session.begin() - _mark_cancelled(session, action_id, timestamp, reason) - session.commit() - - -def action_acquire(context, action_id, owner, timestamp): - session = _session(context) - with session.begin(): - action = session.query(models.Action).get(action_id) - if not action: - return None - - if action.owner and action.owner != owner: - return None - - if action.status != consts.ACTION_READY: - msg = _LW('The action is not in an executable status: ' - '%s') % action.status - LOG.warn(msg) - return None - action.owner = owner - action.start_time = timestamp - action.status = consts.ACTION_RUNNING - action.status_reason = _('The action is being processed.') - - return action - - -def action_acquire_first_ready(context, owner, timestamp): - session = _session(context) - - with session.begin(): - action = session.query(models.Action).\ - filter_by(status=consts.ACTION_READY).\ - filter_by(owner=None).first() - - if action: - action.owner = owner - action.start_time = timestamp - action.status = consts.ACTION_RUNNING - action.status_reason = _('The action is being processed.') - - return action - - -def action_abandon(context, action_id): - '''Abandon an action for other workers to execute again. - - This API is always called with the action locked by the current - worker. There is no chance the action is gone or stolen by others. - ''' - - query = model_query(context, models.Action) - action = query.get(action_id) - - action.owner = None - action.start_time = None - action.status = consts.ACTION_READY - action.status_reason = _('The action was abandoned.') - action.save(query.session) - return action - - -def action_lock_check(context, action_id, owner=None): - action = model_query(context, models.Action).get(action_id) - if not action: - raise exception.ActionNotFound(action=action_id) - - if owner: - return owner if owner == action.owner else action.owner - else: - return action.owner if action.owner else None - - -def action_signal(context, action_id, value): - query = model_query(context, models.Action) - action = query.get(action_id) - if not action: - return - - action.control = value - action.save(query.session) - - -def action_signal_query(context, action_id): - action = model_query(context, models.Action).get(action_id) - if not action: - return None - - return action.control - - -def action_delete(context, action_id, force=False): - session = _session(context) - action = session.query(models.Action).get(action_id) - if not action: - return - if ((action.status == 'WAITING') or (action.status == 'RUNNING') or - (action.status == 'SUSPENDED')): - - raise exception.ResourceBusyError(resource_type='action', - resource_id=action_id) - session.begin() - session.delete(action) - session.commit() - session.flush() - - -# services -def service_create(context, host, binary, topic=None): - time_now = timeutils.utcnow() - svc = models.Service(host=host, binary=binary, - topic=topic, created_at=time_now, - updated_at=time_now) - svc.save(_session(context)) - return svc - - -def service_update(context, service_id, values=None): - - service = service_get(context, service_id) - if not service: - return - - if values is None: - values = {} - - values.update({'updated_at': timeutils.utcnow()}) - service.update(values) - service.save(_session(context)) - return service - - -def service_delete(context, service_id): - session = _session(context) - session.query(models.Service).filter_by( - id=service_id).delete(synchronize_session='fetch') - - -def service_get(context, service_id): - return model_query(context, models.Service).get(service_id) - - -def service_get_by_host_and_binary(context, host, binary): - query = model_query(context, models.Service) - return query.filter_by(host=host).filter_by(binary=binary).first() - - -def service_get_all(context): - return model_query(context, models.Service).all() - - -# consumptions -def consumption_get(context, consumption_id, project_safe=True): - query = model_query(context, models.Consumption) - consumption = query.get(consumption_id) - - if consumption is None: - return None - - if project_safe and context.project != consumption.user_id: - return None - - return consumption - - -def consumption_get_all(context, user_id=None, limit=None, marker=None, - sort_keys=None, sort_dir=None, filters=None, - project_safe=True): - query = model_query(context, models.Consumption) - - if context.is_admin: - project_safe = False - if project_safe: - query = query.filter_by(user_id=context.project) - elif user_id: - query = query.filter_by(user_id=user_id) - if filters is None: - filters = {} - - sort_key_map = { - consts.CONSUMPTION_USER_ID: models.Consumption.user_id.key, - consts.CONSUMPTION_RESOURCE_TYPE: models.Consumption.resource_type.key, - consts.CONSUMPTION_START_TIME: models.Consumption.start_time.key, - } - keys = _get_sort_keys(sort_keys, sort_key_map) - query = db_filters.exact_filter(query, models.Consumption, filters) - return _paginate_query(context, query, models.Consumption, - limit=limit, marker=marker, - sort_keys=keys, sort_dir=sort_dir, - default_sort_keys=['id']).all() - - -def consumption_create(context, values): - consumption_ref = models.Consumption() - consumption_ref.update(values) - consumption_ref.save(_session(context)) - return consumption_ref - - -def consumption_delete(context, consumption_id): - session = _session(context) - session.query(models.Consumption).filter_by( - id=consumption_id).delete(synchronize_session='fetch') - - -# recharges -def recharge_create(context, values): - recharge_ref = models.Recharge() - recharge_ref.update(values) - recharge_ref.save(_session(context)) - return recharge_ref - - -def recharge_get(context, recharge_id, project_safe=True): - query = model_query(context, models.Recharge) - recharge = query.get(recharge_id) - - if recharge is None: - return None - - if project_safe and context.project != recharge.user_id: - return None - - return recharge - - -def recharge_get_all(context, limit=None, marker=None, sort_keys=None, - sort_dir=None, filters=None, project_safe=True): - query = model_query(context, models.Recharge) - - if context.is_admin: - project_safe = False - if project_safe: - query = query.filter_by(user_id=context.project) - if filters is None: - filters = {} - - sort_key_map = { - consts.RECHARGE_USER_ID: models.Recharge.user_id.key, - consts.RECHARGE_TYPE: models.Recharge.type.key, - consts.RECHARGE_TIMESTAMP: models.Recharge.timestamp.key, - } - keys = _get_sort_keys(sort_keys, sort_key_map) - query = db_filters.exact_filter(query, models.Recharge, filters) - return _paginate_query(context, query, models.Recharge, - limit=limit, marker=marker, - sort_keys=keys, sort_dir=sort_dir, - default_sort_keys=['id']).all() diff --git a/bilean/db/sqlalchemy/filters.py b/bilean/db/sqlalchemy/filters.py deleted file mode 100644 index e5e4ea2..0000000 --- a/bilean/db/sqlalchemy/filters.py +++ /dev/null @@ -1,45 +0,0 @@ -# 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 six - - -def exact_filter(query, model, filters): - """Applies exact match filtering to a query. - - Returns the updated query. Modifies filters argument to remove - filters consumed. - - :param query: query to apply filters to - :param model: model object the query applies to, for IN-style - filtering - :param filters: dictionary of filters; values that are lists, - tuples, sets, or frozensets cause an 'IN' test to - be performed, while exact matching ('==' operator) - is used for other values - """ - - filter_dict = {} - if filters is None: - filters = {} - - for key, value in six.iteritems(filters): - if isinstance(value, (list, tuple, set, frozenset)): - column_attr = getattr(model, key) - query = query.filter(column_attr.in_(value)) - else: - filter_dict[key] = value - - if filter_dict: - query = query.filter_by(**filter_dict) - - return query diff --git a/bilean/db/sqlalchemy/migrate_repo/README b/bilean/db/sqlalchemy/migrate_repo/README deleted file mode 100644 index 6218f8c..0000000 --- a/bilean/db/sqlalchemy/migrate_repo/README +++ /dev/null @@ -1,4 +0,0 @@ -This is a database migration repository. - -More information at -http://code.google.com/p/sqlalchemy-migrate/ diff --git a/bilean/db/sqlalchemy/migrate_repo/__init__.py b/bilean/db/sqlalchemy/migrate_repo/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/db/sqlalchemy/migrate_repo/manage.py b/bilean/db/sqlalchemy/migrate_repo/manage.py deleted file mode 100755 index 39fa389..0000000 --- a/bilean/db/sqlalchemy/migrate_repo/manage.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python -from migrate.versioning.shell import main - -if __name__ == '__main__': - main(debug='False') diff --git a/bilean/db/sqlalchemy/migrate_repo/migrate.cfg b/bilean/db/sqlalchemy/migrate_repo/migrate.cfg deleted file mode 100644 index d52e06f..0000000 --- a/bilean/db/sqlalchemy/migrate_repo/migrate.cfg +++ /dev/null @@ -1,25 +0,0 @@ -[db_settings] -# Used to identify which repository this database is versioned under. -# You can use the name of your project. -repository_id=bilean - -# The name of the database table used to track the schema version. -# This name shouldn't already be used by your project. -# If this is changed once a database is under version control, you'll need to -# change the table name in each database too. -version_table=migrate_version - -# When committing a change script, Migrate will attempt to generate the -# sql for all supported databases; normally, if one of them fails - probably -# because you don't have that database installed - it is ignored and the -# commit continues, perhaps ending successfully. -# Databases in this list MUST compile successfully during a commit, or the -# entire commit will fail. List the databases your application will actually -# be using to ensure your updates to that database work properly. -# This must be a list; example: ['postgres','sqlite'] -required_dbs=[] - -# When creating new change scripts, Migrate will stamp the new script with -# a version number. By default this is latest_version + 1. You can set this -# to 'true' to tell Migrate to use the UTC timestamp instead. -use_timestamp_numbering=False diff --git a/bilean/db/sqlalchemy/migrate_repo/versions/001_bilean_init.py b/bilean/db/sqlalchemy/migrate_repo/versions/001_bilean_init.py deleted file mode 100644 index 7cfb62e..0000000 --- a/bilean/db/sqlalchemy/migrate_repo/versions/001_bilean_init.py +++ /dev/null @@ -1,233 +0,0 @@ -# -# 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 sqlalchemy - -from bilean.db.sqlalchemy import types - - -def upgrade(migrate_engine): - meta = sqlalchemy.MetaData() - meta.bind = migrate_engine - - user = sqlalchemy.Table( - 'user', meta, - sqlalchemy.Column('id', sqlalchemy.String(36), primary_key=True, - nullable=False), - sqlalchemy.Column('name', sqlalchemy.String(255)), - sqlalchemy.Column('policy_id', - sqlalchemy.String(36), - sqlalchemy.ForeignKey('policy.id'), - nullable=True), - sqlalchemy.Column('balance', sqlalchemy.Numeric(20, 8)), - sqlalchemy.Column('rate', sqlalchemy.Numeric(20, 8)), - sqlalchemy.Column('credit', sqlalchemy.Integer), - sqlalchemy.Column('last_bill', sqlalchemy.Numeric(24, 8)), - sqlalchemy.Column('status', sqlalchemy.String(255)), - sqlalchemy.Column('status_reason', sqlalchemy.Text), - sqlalchemy.Column('created_at', sqlalchemy.DateTime), - sqlalchemy.Column('updated_at', sqlalchemy.DateTime), - sqlalchemy.Column('deleted_at', sqlalchemy.DateTime), - mysql_engine='InnoDB', - mysql_charset='utf8' - ) - - rule = sqlalchemy.Table( - 'rule', meta, - sqlalchemy.Column('id', sqlalchemy.String(36), primary_key=True, - nullable=False), - sqlalchemy.Column('name', sqlalchemy.String(255)), - sqlalchemy.Column('type', sqlalchemy.String(255)), - sqlalchemy.Column('spec', types.Dict), - sqlalchemy.Column('meta_data', types.Dict), - sqlalchemy.Column('created_at', sqlalchemy.DateTime), - sqlalchemy.Column('updated_at', sqlalchemy.DateTime), - sqlalchemy.Column('deleted_at', sqlalchemy.DateTime), - mysql_engine='InnoDB', - mysql_charset='utf8' - ) - - policy = sqlalchemy.Table( - 'policy', meta, - sqlalchemy.Column('id', sqlalchemy.String(36), primary_key=True, - nullable=False), - sqlalchemy.Column('name', sqlalchemy.String(255)), - sqlalchemy.Column('rules', types.List), - sqlalchemy.Column('is_default', sqlalchemy.Boolean), - sqlalchemy.Column('meta_data', types.Dict), - sqlalchemy.Column('created_at', sqlalchemy.DateTime), - sqlalchemy.Column('updated_at', sqlalchemy.DateTime), - sqlalchemy.Column('deleted_at', sqlalchemy.DateTime), - mysql_engine='InnoDB', - mysql_charset='utf8' - ) - - resource = sqlalchemy.Table( - 'resource', meta, - sqlalchemy.Column('id', sqlalchemy.String(36), primary_key=True, - nullable=False), - sqlalchemy.Column('user_id', - sqlalchemy.String(36), - sqlalchemy.ForeignKey('user.id'), - nullable=False), - sqlalchemy.Column('rule_id', sqlalchemy.String(36), nullable=False), - sqlalchemy.Column('resource_type', sqlalchemy.String(36), - nullable=False), - sqlalchemy.Column('last_bill', sqlalchemy.Numeric(24, 8)), - sqlalchemy.Column('properties', types.Dict), - sqlalchemy.Column('rate', sqlalchemy.Numeric(20, 8), nullable=False), - sqlalchemy.Column('created_at', sqlalchemy.DateTime), - sqlalchemy.Column('updated_at', sqlalchemy.DateTime), - sqlalchemy.Column('deleted_at', sqlalchemy.DateTime), - mysql_engine='InnoDB', - mysql_charset='utf8' - ) - - event = sqlalchemy.Table( - 'event', meta, - sqlalchemy.Column('id', sqlalchemy.String(36), - primary_key=True, nullable=False), - sqlalchemy.Column('timestamp', sqlalchemy.DateTime), - sqlalchemy.Column('obj_id', sqlalchemy.String(36)), - sqlalchemy.Column('obj_type', sqlalchemy.String(36)), - sqlalchemy.Column('obj_name', sqlalchemy.String(255)), - sqlalchemy.Column('action', sqlalchemy.String(36)), - sqlalchemy.Column('user_id', sqlalchemy.String(36)), - sqlalchemy.Column('level', sqlalchemy.Integer), - sqlalchemy.Column('status', sqlalchemy.String(255)), - sqlalchemy.Column('status_reason', sqlalchemy.Text), - sqlalchemy.Column('meta_data', types.Dict), - mysql_engine='InnoDB', - mysql_charset='utf8' - ) - - consumption = sqlalchemy.Table( - 'consumption', meta, - sqlalchemy.Column('id', sqlalchemy.String(36), - primary_key=True, nullable=False), - sqlalchemy.Column('user_id', sqlalchemy.String(36)), - sqlalchemy.Column('resource_id', sqlalchemy.String(36)), - sqlalchemy.Column('resource_type', sqlalchemy.String(255)), - sqlalchemy.Column('start_time', sqlalchemy.Numeric(24, 8)), - sqlalchemy.Column('end_time', sqlalchemy.Numeric(24, 8)), - sqlalchemy.Column('rate', sqlalchemy.Numeric(20, 8)), - sqlalchemy.Column('cost', sqlalchemy.Numeric(20, 8)), - sqlalchemy.Column('meta_data', types.Dict), - mysql_engine='InnoDB', - mysql_charset='utf8' - ) - - recharge = sqlalchemy.Table( - 'recharge', meta, - sqlalchemy.Column('id', sqlalchemy.String(36), - primary_key=True, nullable=False), - sqlalchemy.Column('user_id', sqlalchemy.String(36)), - sqlalchemy.Column('type', sqlalchemy.String(255)), - sqlalchemy.Column('timestamp', sqlalchemy.DateTime), - sqlalchemy.Column('value', sqlalchemy.Numeric(20, 8)), - sqlalchemy.Column('meta_data', types.Dict), - mysql_engine='InnoDB', - mysql_charset='utf8' - ) - - action = sqlalchemy.Table( - 'action', meta, - sqlalchemy.Column('id', sqlalchemy.String(36), - primary_key=True, nullable=False), - sqlalchemy.Column('name', sqlalchemy.String(63)), - sqlalchemy.Column('context', types.Dict), - sqlalchemy.Column('target', sqlalchemy.String(36)), - sqlalchemy.Column('action', sqlalchemy.String(255)), - sqlalchemy.Column('cause', sqlalchemy.String(255)), - sqlalchemy.Column('owner', sqlalchemy.String(36)), - sqlalchemy.Column('start_time', sqlalchemy.Numeric(24, 8)), - sqlalchemy.Column('end_time', sqlalchemy.Numeric(24, 8)), - sqlalchemy.Column('timeout', sqlalchemy.Integer), - sqlalchemy.Column('inputs', types.Dict), - sqlalchemy.Column('outputs', types.Dict), - sqlalchemy.Column('data', types.Dict), - sqlalchemy.Column('status', sqlalchemy.String(255)), - sqlalchemy.Column('status_reason', sqlalchemy.Text), - sqlalchemy.Column('created_at', sqlalchemy.DateTime), - sqlalchemy.Column('updated_at', sqlalchemy.DateTime), - mysql_engine='InnoDB', - mysql_charset='utf8' - ) - - dependency = sqlalchemy.Table( - 'dependency', meta, - sqlalchemy.Column('id', sqlalchemy.String(36), - primary_key=True, nullable=False), - sqlalchemy.Column('depended', - sqlalchemy.String(36), - sqlalchemy.ForeignKey('action.id'), - nullable=False), - sqlalchemy.Column('dependent', - sqlalchemy.String(36), - sqlalchemy.ForeignKey('action.id'), - nullable=False), - mysql_engine='InnoDB', - mysql_charset='utf8' - ) - - user_lock = sqlalchemy.Table( - 'user_lock', meta, - sqlalchemy.Column('user_id', sqlalchemy.String(36), - primary_key=True, nullable=False), - sqlalchemy.Column('action_id', sqlalchemy.String(36)), - mysql_engine='InnoDB', - mysql_charset='utf8' - ) - - service = sqlalchemy.Table( - 'service', meta, - sqlalchemy.Column('id', sqlalchemy.String(36), - primary_key=True, nullable=False), - sqlalchemy.Column('host', sqlalchemy.String(255)), - sqlalchemy.Column('binary', sqlalchemy.String(255)), - sqlalchemy.Column('topic', sqlalchemy.String(255)), - sqlalchemy.Column('disabled', sqlalchemy.Boolean), - sqlalchemy.Column('disabled_reason', sqlalchemy.String(255)), - sqlalchemy.Column('created_at', sqlalchemy.DateTime), - sqlalchemy.Column('updated_at', sqlalchemy.DateTime), - mysql_engine='InnoDB', - mysql_charset='utf8' - ) - - tables = ( - policy, - user, - rule, - resource, - event, - consumption, - recharge, - action, - dependency, - user_lock, - service, - ) - - for index, table in enumerate(tables): - try: - table.create() - except Exception: - # If an error occurs, drop all tables created so far to return - # to the previously existing state. - meta.drop_all(tables=tables[:index]) - raise - - -def downgrade(migrate_engine): - raise NotImplementedError('Database downgrade not supported - ' - 'would drop all tables') diff --git a/bilean/db/sqlalchemy/migrate_repo/versions/002_add_job_table.py b/bilean/db/sqlalchemy/migrate_repo/versions/002_add_job_table.py deleted file mode 100644 index a39bc75..0000000 --- a/bilean/db/sqlalchemy/migrate_repo/versions/002_add_job_table.py +++ /dev/null @@ -1,57 +0,0 @@ -# -# 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 sqlalchemy - -from bilean.db.sqlalchemy import types - -from oslo_log import log as logging - -LOG = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - meta = sqlalchemy.MetaData() - meta.bind = migrate_engine - - job = sqlalchemy.Table( - 'job', meta, - sqlalchemy.Column('id', sqlalchemy.String(50), - primary_key=True, nullable=False), - sqlalchemy.Column('scheduler_id', sqlalchemy.String(36), - nullable=False), - sqlalchemy.Column('job_type', sqlalchemy.String(10), - nullable=False), - sqlalchemy.Column('parameters', types.Dict), - sqlalchemy.Column('created_at', sqlalchemy.DateTime), - sqlalchemy.Column('updated_at', sqlalchemy.DateTime), - mysql_engine='InnoDB', - mysql_charset='utf8' - ) - - try: - job.create() - except Exception: - LOG.error("Table |%s| not created!", repr(job)) - raise - - -def downgrade(migrate_engine): - meta = sqlalchemy.MetaData() - meta.bind = migrate_engine - job = sqlalchemy.Table('job', meta, autoload=True) - try: - job.drop() - except Exception: - LOG.error("Job table not dropped") - raise diff --git a/bilean/db/sqlalchemy/migrate_repo/versions/__init__.py b/bilean/db/sqlalchemy/migrate_repo/versions/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/db/sqlalchemy/migration.py b/bilean/db/sqlalchemy/migration.py deleted file mode 100644 index 0d2d624..0000000 --- a/bilean/db/sqlalchemy/migration.py +++ /dev/null @@ -1,38 +0,0 @@ -# -# 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 oslo_db.sqlalchemy import migration as oslo_migration - - -INIT_VERSION = 0 - - -def db_sync(engine, version=None): - path = os.path.join(os.path.abspath(os.path.dirname(__file__)), - 'migrate_repo') - return oslo_migration.db_sync(engine, path, version, - init_version=INIT_VERSION) - - -def db_version(engine): - path = os.path.join(os.path.abspath(os.path.dirname(__file__)), - 'migrate_repo') - return oslo_migration.db_version(engine, path, INIT_VERSION) - - -def db_version_control(engine, version=None): - path = os.path.join(os.path.abspath(os.path.dirname(__file__)), - 'migrate_repo') - return oslo_migration.db_version_control(engine, path, version) diff --git a/bilean/db/sqlalchemy/models.py b/bilean/db/sqlalchemy/models.py deleted file mode 100644 index 4ee0d7f..0000000 --- a/bilean/db/sqlalchemy/models.py +++ /dev/null @@ -1,266 +0,0 @@ -# -# 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. -""" -SQLAlchemy models for Bilean data. -""" - -import six - -from bilean.db.sqlalchemy import types - -from oslo_db.sqlalchemy import models -from oslo_utils import timeutils -from oslo_utils import uuidutils - -import sqlalchemy -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import backref -from sqlalchemy.orm import relationship -from sqlalchemy.orm.session import Session - -BASE = declarative_base() -UUID4 = uuidutils.generate_uuid - - -def get_session(): - from bilean.db.sqlalchemy import api as db_api - return db_api.get_session() - - -class BileanBase(models.ModelBase): - """Base class for Heat Models.""" - __table_args__ = {'mysql_engine': 'InnoDB'} - - def expire(self, session=None, attrs=None): - """Expire this object ().""" - if not session: - session = Session.object_session(self) - if not session: - session = get_session() - session.expire(self, attrs) - - def refresh(self, session=None, attrs=None): - """Refresh this object.""" - if not session: - session = Session.object_session(self) - if not session: - session = get_session() - session.refresh(self, attrs) - - def delete(self, session=None): - """Delete this object.""" - if not session: - session = Session.object_session(self) - if not session: - session = get_session() - session.delete(self) - session.flush() - - def update_and_save(self, values, session=None): - if not session: - session = Session.object_session(self) - if not session: - session = get_session() - session.begin() - for k, v in six.iteritems(values): - setattr(self, k, v) - session.commit() - - -class SoftDelete(object): - deleted_at = sqlalchemy.Column(sqlalchemy.DateTime) - - def soft_delete(self, session=None): - """Mark this object as deleted.""" - self.update_and_save({'deleted_at': timeutils.utcnow()}, - session=session) - - -class StateAware(object): - status = sqlalchemy.Column('status', sqlalchemy.String(255)) - status_reason = sqlalchemy.Column('status_reason', sqlalchemy.Text) - - -class User(BASE, BileanBase, SoftDelete, StateAware, models.TimestampMixin): - """Represents a user to record account""" - - __tablename__ = 'user' - - id = sqlalchemy.Column(sqlalchemy.String(36), primary_key=True) - name = sqlalchemy.Column(sqlalchemy.String(255)) - policy_id = sqlalchemy.Column( - sqlalchemy.String(36), - sqlalchemy.ForeignKey('policy.id'), - nullable=True) - balance = sqlalchemy.Column(sqlalchemy.Numeric(20, 8), default=0.0) - rate = sqlalchemy.Column(sqlalchemy.Numeric(20, 8), default=0.0) - credit = sqlalchemy.Column(sqlalchemy.Integer, default=0) - last_bill = sqlalchemy.Column(sqlalchemy.Numeric(24, 8)) - - -class Policy(BASE, BileanBase, SoftDelete, models.TimestampMixin): - """Represents a policy to collect rules.""" - - __tablename__ = 'policy' - - id = sqlalchemy.Column(sqlalchemy.String(36), primary_key=True, - default=lambda: UUID4()) - name = sqlalchemy.Column(sqlalchemy.String(255)) - rules = sqlalchemy.Column(types.List) - is_default = sqlalchemy.Column(sqlalchemy.Boolean, default=False) - meta_data = sqlalchemy.Column(types.Dict) - - -class Rule(BASE, BileanBase, SoftDelete, models.TimestampMixin): - """Represents a rule created to bill someone resource""" - - __tablename__ = 'rule' - id = sqlalchemy.Column(sqlalchemy.String(36), primary_key=True, - default=lambda: UUID4()) - name = sqlalchemy.Column(sqlalchemy.String(255)) - type = sqlalchemy.Column(sqlalchemy.String(255)) - spec = sqlalchemy.Column(types.Dict) - meta_data = sqlalchemy.Column(types.Dict) - - -class Resource(BASE, BileanBase, SoftDelete, models.TimestampMixin): - """Represents a meta resource with rate""" - - __tablename__ = 'resource' - id = sqlalchemy.Column(sqlalchemy.String(36), primary_key=True) - user_id = sqlalchemy.Column( - sqlalchemy.String(36), - sqlalchemy.ForeignKey('user.id'), - nullable=False) - rule_id = sqlalchemy.Column(sqlalchemy.String(36), nullable=True) - user = relationship(User, backref=backref('resources')) - resource_type = sqlalchemy.Column(sqlalchemy.String(36), nullable=False) - rate = sqlalchemy.Column(sqlalchemy.Numeric(20, 8), nullable=False) - last_bill = sqlalchemy.Column(sqlalchemy.Numeric(24, 8)) - properties = sqlalchemy.Column(types.Dict) - - -class Action(BASE, BileanBase, StateAware, models.TimestampMixin): - """Action objects.""" - - __tablename__ = 'action' - id = sqlalchemy.Column(sqlalchemy.String(36), primary_key=True, - default=lambda: UUID4()) - name = sqlalchemy.Column(sqlalchemy.String(63)) - context = sqlalchemy.Column(types.Dict) - target = sqlalchemy.Column(sqlalchemy.String(36)) - action = sqlalchemy.Column(sqlalchemy.String(255)) - cause = sqlalchemy.Column(sqlalchemy.String(255)) - owner = sqlalchemy.Column(sqlalchemy.String(36)) - start_time = sqlalchemy.Column(sqlalchemy.Numeric(24, 8)) - end_time = sqlalchemy.Column(sqlalchemy.Numeric(24, 8)) - timeout = sqlalchemy.Column(sqlalchemy.Integer) - inputs = sqlalchemy.Column(types.Dict) - outputs = sqlalchemy.Column(types.Dict) - data = sqlalchemy.Column(types.Dict) - - -class ActionDependency(BASE, BileanBase): - """Action dependencies.""" - __tablename__ = 'dependency' - - id = sqlalchemy.Column(sqlalchemy.String(36), primary_key=True, - default=lambda: UUID4()) - depended = sqlalchemy.Column(sqlalchemy.String(36), - sqlalchemy.ForeignKey('action.id'), - nullable=False) - dependent = sqlalchemy.Column(sqlalchemy.String(36), - sqlalchemy.ForeignKey('action.id'), - nullable=False) - - -class Event(BASE, BileanBase, StateAware): - """Represents an event generated by the bilean engine.""" - - __tablename__ = 'event' - - id = sqlalchemy.Column(sqlalchemy.String(36), primary_key=True, - default=lambda: UUID4()) - timestamp = sqlalchemy.Column(sqlalchemy.DateTime) - obj_id = sqlalchemy.Column(sqlalchemy.String(36)) - obj_type = sqlalchemy.Column(sqlalchemy.String(36)) - obj_name = sqlalchemy.Column(sqlalchemy.String(255)) - action = sqlalchemy.Column(sqlalchemy.String(36)) - user_id = sqlalchemy.Column(sqlalchemy.String(36)) - level = sqlalchemy.Column(sqlalchemy.Integer) - meta_data = sqlalchemy.Column(types.Dict) - - -class Consumption(BASE, BileanBase): - """Consumption objects.""" - - __tablename__ = 'consumption' - - id = sqlalchemy.Column(sqlalchemy.String(36), primary_key=True, - default=lambda: UUID4()) - user_id = sqlalchemy.Column(sqlalchemy.String(36)) - resource_id = sqlalchemy.Column(sqlalchemy.String(36)) - resource_type = sqlalchemy.Column(sqlalchemy.String(255)) - start_time = sqlalchemy.Column(sqlalchemy.Numeric(24, 8)) - end_time = sqlalchemy.Column(sqlalchemy.Numeric(24, 8)) - rate = sqlalchemy.Column(sqlalchemy.Numeric(20, 8)) - cost = sqlalchemy.Column(sqlalchemy.Numeric(20, 8)) - meta_data = sqlalchemy.Column(types.Dict) - - -class Recharge(BASE, BileanBase): - """Recharge history.""" - - __tablename__ = 'recharge' - - id = sqlalchemy.Column(sqlalchemy.String(36), primary_key=True, - default=lambda: UUID4()) - user_id = sqlalchemy.Column(sqlalchemy.String(36)) - type = sqlalchemy.Column(sqlalchemy.String(255)) - timestamp = sqlalchemy.Column(sqlalchemy.DateTime) - value = sqlalchemy.Column(sqlalchemy.Numeric(20, 8)) - meta_data = sqlalchemy.Column(types.Dict) - - -class Job(BASE, BileanBase): - """Represents a job for per user""" - - __tablename__ = 'job' - - id = sqlalchemy.Column(sqlalchemy.String(50), primary_key=True) - scheduler_id = sqlalchemy.Column(sqlalchemy.String(36)) - job_type = sqlalchemy.Column(sqlalchemy.String(10)) - parameters = sqlalchemy.Column(types.Dict) - - -class UserLock(BASE, BileanBase): - """User locks for engines.""" - - __tablename__ = 'user_lock' - - user_id = sqlalchemy.Column(sqlalchemy.String(36), primary_key=True) - action_id = sqlalchemy.Column(sqlalchemy.String(36)) - - -class Service(BASE, BileanBase, models.TimestampMixin): - """Service registry.""" - - __tablename__ = 'service' - - id = sqlalchemy.Column(sqlalchemy.String(36), primary_key=True, - default=lambda: UUID4()) - host = sqlalchemy.Column(sqlalchemy.String(255)) - binary = sqlalchemy.Column(sqlalchemy.String(255)) - topic = sqlalchemy.Column(sqlalchemy.String(255)) - disabled = sqlalchemy.Column(sqlalchemy.Boolean, default=False) - disabled_reason = sqlalchemy.Column(sqlalchemy.String(255)) diff --git a/bilean/db/sqlalchemy/types.py b/bilean/db/sqlalchemy/types.py deleted file mode 100644 index 5dcee5f..0000000 --- a/bilean/db/sqlalchemy/types.py +++ /dev/null @@ -1,112 +0,0 @@ -# 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 sqlalchemy.dialects import mysql -from sqlalchemy.ext import mutable -from sqlalchemy import types - - -class MutableList(mutable.Mutable, list): - @classmethod - def coerce(cls, key, value): - if not isinstance(value, MutableList): - if isinstance(value, list): - return MutableList(value) - return mutable.Mutable.coerce(key, value) - else: - return value - - def __init__(self, initval=None): - list.__init__(self, initval or []) - - def __getitem__(self, key): - value = list.__getitem__(self, key) - for obj, key in self._parents.items(): - value._parents[obj] = key - return value - - def __setitem__(self, key, value): - list.__setitem__(self, key, value) - self.changed() - - def __getstate__(self): - return list(self) - - def __setstate__(self, state): - self[:] = state - - def append(self, value): - list.append(self, value) - self.changed() - - def extend(self, iterable): - list.extend(self, iterable) - self.changed() - - def insert(self, index, item): - list.insert(self, index, item) - self.changed() - - def __setslice__(self, i, j, other): - list.__setslice__(self, i, j, other) - self.changed() - - def pop(self, index=-1): - item = list.pop(self, index) - self.changed() - return item - - def remove(self, value): - list.remove(self, value) - self.changed() - - -class Dict(types.TypeDecorator): - impl = types.Text - - def load_dialect_impl(self, dialect): - if dialect.name == 'mysql': - return dialect.type_descriptor(mysql.LONGTEXT()) - else: - return self.impl - - def process_bind_param(self, value, dialect): - return json.dumps(value) - - def process_result_value(self, value, dialect): - if value is None: - return None - return json.loads(value) - - -class List(types.TypeDecorator): - impl = types.Text - - def load_dialect_impl(self, dialect): - if dialect.name == 'mysql': - return dialect.type_descriptor(mysql.LONGTEXT()) - else: - return self.impl - - def process_bind_param(self, value, dialect): - return json.dumps(value) - - def process_result_value(self, value, dialect): - if value is None: - return None - return json.loads(value) - - -mutable.MutableDict.associate_with(Dict) -MutableList.associate_with(List) diff --git a/bilean/db/utils.py b/bilean/db/utils.py deleted file mode 100644 index 41b66f1..0000000 --- a/bilean/db/utils.py +++ /dev/null @@ -1,47 +0,0 @@ -# -# 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. - - -class LazyPluggable(object): - """A pluggable backend loaded lazily based on some value.""" - - def __init__(self, pivot, **backends): - self.__backends = backends - self.__pivot = pivot - self.__backend = None - - def __get_backend(self): - if not self.__backend: - backend_name = 'sqlalchemy' - backend = self.__backends[backend_name] - if isinstance(backend, tuple): - name = backend[0] - fromlist = backend[1] - else: - name = backend - fromlist = backend - - self.__backend = __import__(name, None, None, fromlist) - return self.__backend - - def __getattr__(self, key): - backend = self.__get_backend() - return getattr(backend, key) - - -IMPL = LazyPluggable('backend', - sqlalchemy='heat.db.sqlalchemy.api') - - -def purge_deleted(age, granularity='days'): - IMPL.purge_deleted(age, granularity) diff --git a/bilean/drivers/__init__.py b/bilean/drivers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/drivers/base.py b/bilean/drivers/base.py deleted file mode 100644 index 3922ce5..0000000 --- a/bilean/drivers/base.py +++ /dev/null @@ -1,54 +0,0 @@ -# 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 copy - -from oslo_config import cfg - -from bilean.engine import environment - -CONF = cfg.CONF -CONF.import_opt('cloud_backend', 'bilean.common.config') - - -class DriverBase(object): - '''Base class for all drivers.''' - - def __init__(self, params=None): - if params is None: - params = { - 'auth_url': CONF.authentication.auth_url, - 'username': CONF.authentication.service_username, - 'password': CONF.authentication.service_password, - 'project_name': CONF.authentication.service_project_name, - 'user_domain_name': - cfg.CONF.authentication.service_user_domain, - 'project_domain_name': - cfg.CONF.authentication.service_project_domain, - } - self.conn_params = copy.deepcopy(params) - - -class BileanDriver(object): - '''Generic driver class''' - - def __init__(self, backend_name=None): - - if backend_name is None: - backend_name = cfg.CONF.cloud_backend - - backend = environment.global_env().get_driver(backend_name) - - self.compute = backend.compute - self.network = backend.network - self.identity = backend.identity - self.block_store = backend.block_store diff --git a/bilean/drivers/openstack/__init__.py b/bilean/drivers/openstack/__init__.py deleted file mode 100644 index 029a004..0000000 --- a/bilean/drivers/openstack/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# 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 bilean.drivers.openstack import cinder_v2 -from bilean.drivers.openstack import keystone_v3 -from bilean.drivers.openstack import neutron_v2 -from bilean.drivers.openstack import nova_v2 - - -compute = nova_v2.NovaClient -identity = keystone_v3.KeystoneClient -network = neutron_v2.NeutronClient -block_store = cinder_v2.CinderClient diff --git a/bilean/drivers/openstack/cinder_v2.py b/bilean/drivers/openstack/cinder_v2.py deleted file mode 100644 index a5c2a68..0000000 --- a/bilean/drivers/openstack/cinder_v2.py +++ /dev/null @@ -1,33 +0,0 @@ -# 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 bilean.drivers import base -from bilean.drivers.openstack import sdk - - -class CinderClient(base.DriverBase): - '''Cinder V2 driver.''' - - def __init__(self, params=None): - super(CinderClient, self).__init__(params) - self.conn = sdk.create_connection(self.conn_params) - - @sdk.translate_exception - def volume_get(self, volume): - '''Get a single volume.''' - return self.conn.block_store.get_volume(volume) - - @sdk.translate_exception - def volume_delete(self, volume, ignore_missing=True): - '''Delete a volume.''' - self.conn.block_store.delete_volume(volume, - ignore_missing=ignore_missing) diff --git a/bilean/drivers/openstack/keystone_v3.py b/bilean/drivers/openstack/keystone_v3.py deleted file mode 100644 index 3398f2c..0000000 --- a/bilean/drivers/openstack/keystone_v3.py +++ /dev/null @@ -1,67 +0,0 @@ -# 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 oslo_config import cfg - -from bilean.drivers import base -from bilean.drivers.openstack import sdk - -CONF = cfg.CONF - - -class KeystoneClient(base.DriverBase): - '''Keystone V3 driver.''' - - def __init__(self, params=None): - super(KeystoneClient, self).__init__(params) - self.conn = sdk.create_connection(self.conn_params) - - @sdk.translate_exception - def project_find(self, name_or_id, ignore_missing=True): - '''Find a single project - - :param name_or_id: The name or ID of a project. - :param bool ignore_missing: When set to ``False`` - :class:`~openstack.exceptions.ResourceNotFound` will be - raised when the resource does not exist. - When set to ``True``, None will be returned when - attempting to find a nonexistent resource. - :returns: One :class:`~openstack.identity.v3.project.Project` or None - ''' - project = self.conn.identity.find_project( - name_or_id, ignore_missing=ignore_missing) - return project - - @sdk.translate_exception - def project_list(self, **queries): - '''Function to get project list.''' - return self.conn.identity.projects(**queries) - - @classmethod - def get_service_credentials(cls, **kwargs): - '''Bilean service credential to use with Keystone. - - :param kwargs: An additional keyword argument list that can be used - for customizing the default settings. - ''' - - creds = { - 'auth_url': CONF.authentication.auth_url, - 'username': CONF.authentication.service_username, - 'password': CONF.authentication.service_password, - 'project_name': CONF.authentication.service_project_name, - 'user_domain_name': cfg.CONF.authentication.service_user_domain, - 'project_domain_name': - cfg.CONF.authentication.service_project_domain, - } - creds.update(**kwargs) - return creds diff --git a/bilean/drivers/openstack/neutron_v2.py b/bilean/drivers/openstack/neutron_v2.py deleted file mode 100644 index ce9424f..0000000 --- a/bilean/drivers/openstack/neutron_v2.py +++ /dev/null @@ -1,125 +0,0 @@ -# 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 bilean.drivers import base -from bilean.drivers.openstack import sdk - - -class NeutronClient(base.DriverBase): - '''Neutron V2 driver.''' - - def __init__(self, params=None): - super(NeutronClient, self).__init__(params) - self.conn = sdk.create_connection(self.conn_params) - - @sdk.translate_exception - def network_get(self, name_or_id): - network = self.conn.network.find_network(name_or_id) - return network - - @sdk.translate_exception - def network_delete(self, network, ignore_missing=True): - self.conn.network.delete_network( - network, ignore_missing=ignore_missing) - return - - @sdk.translate_exception - def subnet_get(self, name_or_id): - subnet = self.conn.network.find_subnet(name_or_id) - return subnet - - @sdk.translate_exception - def subnet_delete(self, subnet, ignore_missing=True): - self.conn.network.delete_subnet( - subnet, ignore_missing=ignore_missing) - return - - @sdk.translate_exception - def loadbalancer_get(self, name_or_id): - lb = self.conn.network.find_load_balancer(name_or_id) - return lb - - @sdk.translate_exception - def loadbalancer_list(self): - lbs = [lb for lb in self.conn.network.load_balancers()] - return lbs - - @sdk.translate_exception - def loadbalancer_delete(self, lb_id, ignore_missing=True): - self.conn.network.delete_load_balancer( - lb_id, ignore_missing=ignore_missing) - return - - @sdk.translate_exception - def listener_get(self, name_or_id): - listener = self.conn.network.find_listener(name_or_id) - return listener - - @sdk.translate_exception - def listener_list(self): - listeners = [i for i in self.conn.network.listeners()] - return listeners - - @sdk.translate_exception - def listener_delete(self, listener_id, ignore_missing=True): - self.conn.network.delete_listener(listener_id, - ignore_missing=ignore_missing) - return - - @sdk.translate_exception - def pool_get(self, name_or_id): - pool = self.conn.network.find_pool(name_or_id) - return pool - - @sdk.translate_exception - def pool_list(self): - pools = [p for p in self.conn.network.pools()] - return pools - - @sdk.translate_exception - def pool_delete(self, pool_id, ignore_missing=True): - self.conn.network.delete_pool(pool_id, - ignore_missing=ignore_missing) - return - - @sdk.translate_exception - def pool_member_get(self, pool_id, name_or_id): - member = self.conn.network.find_pool_member(name_or_id, - pool_id) - return member - - @sdk.translate_exception - def pool_member_list(self, pool_id): - members = [m for m in self.conn.network.pool_members(pool_id)] - return members - - @sdk.translate_exception - def pool_member_delete(self, pool_id, member_id, ignore_missing=True): - self.conn.network.delete_pool_member( - member_id, pool_id, ignore_missing=ignore_missing) - return - - @sdk.translate_exception - def healthmonitor_get(self, name_or_id): - hm = self.conn.network.find_health_monitor(name_or_id) - return hm - - @sdk.translate_exception - def healthmonitor_list(self): - hms = [hm for hm in self.conn.network.health_monitors()] - return hms - - @sdk.translate_exception - def healthmonitor_delete(self, hm_id, ignore_missing=True): - self.conn.network.delete_health_monitor( - hm_id, ignore_missing=ignore_missing) - return diff --git a/bilean/drivers/openstack/nova_v2.py b/bilean/drivers/openstack/nova_v2.py deleted file mode 100644 index 49ff62e..0000000 --- a/bilean/drivers/openstack/nova_v2.py +++ /dev/null @@ -1,72 +0,0 @@ -# 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 oslo_config import cfg - -from bilean.drivers import base -from bilean.drivers.openstack import sdk - - -class NovaClient(base.DriverBase): - '''Nova V2 driver.''' - - def __init__(self, params=None): - super(NovaClient, self).__init__(params) - self.conn = sdk.create_connection(self.conn_params) - - @sdk.translate_exception - def flavor_find(self, name_or_id, ignore_missing=False): - return self.conn.compute.find_flavor(name_or_id, ignore_missing) - - @sdk.translate_exception - def flavor_list(self, details=True, **query): - return self.conn.compute.flavors(details, **query) - - @sdk.translate_exception - def image_find(self, name_or_id, ignore_missing=False): - return self.conn.compute.find_image(name_or_id, ignore_missing) - - @sdk.translate_exception - def image_list(self, details=True, **query): - return self.conn.compute.images(details, **query) - - @sdk.translate_exception - def image_delete(self, value, ignore_missing=True): - return self.conn.compute.delete_image(value, ignore_missing) - - @sdk.translate_exception - def server_get(self, value): - return self.conn.compute.get_server(value) - - @sdk.translate_exception - def server_list(self, details=True, **query): - return self.conn.compute.servers(details, **query) - - @sdk.translate_exception - def server_update(self, value, **attrs): - return self.conn.compute.update_server(value, **attrs) - - @sdk.translate_exception - def server_delete(self, value, ignore_missing=True): - return self.conn.compute.delete_server(value, ignore_missing) - - @sdk.translate_exception - def wait_for_server_delete(self, value, timeout=None): - '''Wait for server deleting complete''' - if timeout is None: - timeout = cfg.CONF.default_action_timeout - - server_obj = self.conn.compute.find_server(value, True) - if server_obj: - self.conn.compute.wait_for_delete(server_obj, wait=timeout) - - return diff --git a/bilean/drivers/openstack/sdk.py b/bilean/drivers/openstack/sdk.py deleted file mode 100644 index 2de6148..0000000 --- a/bilean/drivers/openstack/sdk.py +++ /dev/null @@ -1,114 +0,0 @@ -# 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. - -''' -SDK Client -''' -import functools -from oslo_log import log as logging -import six - -from openstack import connection -from openstack import exceptions as sdk_exc -from openstack import profile -from oslo_serialization import jsonutils -from requests import exceptions as req_exc - -from bilean.common import exception as bilean_exc - -USER_AGENT = 'bilean' -exc = sdk_exc -LOG = logging.getLogger(__name__) - - -def parse_exception(ex): - '''Parse exception code and yield useful information.''' - code = 500 - - if isinstance(ex, sdk_exc.HttpException): - # some exceptions don't contain status_code - if ex.http_status is not None: - code = ex.http_status - message = ex.message - data = {} - try: - data = jsonutils.loads(ex.details) - except Exception: - # Some exceptions don't have details record or - # are not in JSON format - pass - - # try dig more into the exception record - # usually 'data' has two types of format : - # type1: {"forbidden": {"message": "error message", "code": 403} - # type2: {"code": 404, "error": { "message": "not found"}} - if data: - code = data.get('code', code) - message = data.get('message', message) - error = data.get('error') - if error: - code = data.get('code', code) - message = data['error'].get('message', message) - else: - for value in data.values(): - code = value.get('code', code) - message = value.get('message', message) - - elif isinstance(ex, sdk_exc.SDKException): - # Besides HttpException there are some other exceptions like - # ResourceTimeout can be raised from SDK, handle them here. - message = ex.message - elif isinstance(ex, req_exc.RequestException): - # Exceptions that are not captured by SDK - code = ex.errno - message = six.text_type(ex) - elif isinstance(ex, Exception): - message = six.text_type(ex) - - raise bilean_exc.InternalError(code=code, message=message) - - -def translate_exception(func): - """Decorator for exception translation.""" - - @functools.wraps(func) - def invoke_with_catch(driver, *args, **kwargs): - try: - return func(driver, *args, **kwargs) - except Exception as ex: - LOG.exception(ex) - raise parse_exception(ex) - - return invoke_with_catch - - -def create_connection(params=None): - if params is None: - params = {} - - if params.get('token'): - auth_plugin = 'token' - else: - auth_plugin = 'password' - - prof = profile.Profile() - prof.set_version('identity', 'v3') - if 'region_name' in params: - prof.set_region(prof.ALL, params['region_name']) - params.pop('region_name') - try: - conn = connection.Connection(profile=prof, user_agent=USER_AGENT, - auth_plugin=auth_plugin, **params) - except Exception as ex: - raise parse_exception(ex) - - return conn diff --git a/bilean/engine/__init__.py b/bilean/engine/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/engine/actions/__init__.py b/bilean/engine/actions/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/engine/actions/base.py b/bilean/engine/actions/base.py deleted file mode 100644 index 6d7b00b..0000000 --- a/bilean/engine/actions/base.py +++ /dev/null @@ -1,407 +0,0 @@ -# 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 six -import time - -from oslo_config import cfg -from oslo_log import log as logging -from oslo_utils import timeutils - -from bilean.common import context as req_context -from bilean.common import exception -from bilean.common.i18n import _, _LE -from bilean.common import utils -from bilean.db import api as db_api -from bilean.engine import event as EVENT - -wallclock = time.time -LOG = logging.getLogger(__name__) - -# Action causes -CAUSES = ( - CAUSE_RPC, CAUSE_DERIVED, -) = ( - 'RPC Request', - 'Derived Action', -) - - -class Action(object): - '''An action can be performed on a user, rule or policy.''' - - RETURNS = ( - RES_OK, RES_ERROR, RES_RETRY, RES_CANCEL, RES_TIMEOUT, - ) = ( - 'OK', 'ERROR', 'RETRY', 'CANCEL', 'TIMEOUT', - ) - - # Action status definitions: - # INIT: Not ready to be executed because fields are being modified, - # or dependency with other actions are being analyzed. - # READY: Initialized and ready to be executed by a worker. - # RUNNING: Being executed by a worker thread. - # SUCCEEDED: Completed with success. - # FAILED: Completed with failure. - # CANCELLED: Action cancelled because worker thread was cancelled. - STATUSES = ( - INIT, WAITING, READY, RUNNING, SUSPENDED, - SUCCEEDED, FAILED, CANCELLED - ) = ( - 'INIT', 'WAITING', 'READY', 'RUNNING', 'SUSPENDED', - 'SUCCEEDED', 'FAILED', 'CANCELLED', - ) - - # Signal commands - COMMANDS = ( - SIG_CANCEL, SIG_SUSPEND, SIG_RESUME, - ) = ( - 'CANCEL', 'SUSPEND', 'RESUME', - ) - - def __new__(cls, target, action, context, **kwargs): - if (cls != Action): - return super(Action, cls).__new__(cls) - - target_type = action.split('_')[0] - if target_type == 'USER': - from bilean.engine.actions import user_action - ActionClass = user_action.UserAction - # elif target_type == 'RULE': - # from bilean.engine.actions import rule_action - # ActionClass = rule_action.RuleAction - # elif target_type == 'POLICY': - # from bilean.engine.actions import policy_action - # ActionClass = policy_action.PolicyAction - - return super(Action, cls).__new__(ActionClass) - - def __init__(self, target, action, context, **kwargs): - # context will be persisted into database so that any worker thread - # can pick the action up and execute it on behalf of the initiator - - self.id = kwargs.get('id', None) - self.name = kwargs.get('name', '') - - self.context = context - - self.action = action - self.target = target - - # Why this action is fired, it can be a UUID of another action - self.cause = kwargs.get('cause', '') - - # Owner can be an UUID format ID for the worker that is currently - # working on the action. It also serves as a lock. - self.owner = kwargs.get('owner', None) - - self.start_time = utils.make_decimal(kwargs.get('start_time', 0)) - self.end_time = utils.make_decimal(kwargs.get('end_time', 0)) - - # Timeout is a placeholder in case some actions may linger too long - self.timeout = kwargs.get('timeout', cfg.CONF.default_action_timeout) - - # Return code, useful when action is not automatically deleted - # after execution - self.status = kwargs.get('status', self.INIT) - self.status_reason = kwargs.get('status_reason', '') - - # All parameters are passed in using keyword arguments which is - # a dictionary stored as JSON in DB - self.inputs = kwargs.get('inputs', {}) - self.outputs = kwargs.get('outputs', {}) - - self.created_at = kwargs.get('created_at', None) - self.updated_at = kwargs.get('updated_at', None) - - self.data = kwargs.get('data', {}) - - def store(self, context): - """Store the action record into database table. - - :param context: An instance of the request context. - :return: The ID of the stored object. - """ - - timestamp = timeutils.utcnow() - - values = { - 'name': self.name, - 'context': self.context.to_dict(), - 'target': self.target, - 'action': self.action, - 'cause': self.cause, - 'owner': self.owner, - 'start_time': utils.format_decimal(self.start_time), - 'end_time': utils.format_decimal(self.end_time), - 'timeout': self.timeout, - 'status': self.status, - 'status_reason': self.status_reason, - 'inputs': self.inputs, - 'outputs': self.outputs, - 'created_at': self.created_at, - 'updated_at': self.updated_at, - 'data': self.data, - } - - if self.id: - self.updated_at = timestamp - values['updated_at'] = timestamp - db_api.action_update(context, self.id, values) - else: - self.created_at = timestamp - values['created_at'] = timestamp - action = db_api.action_create(context, values) - self.id = action.id - - return self.id - - @classmethod - def _from_db_record(cls, record): - """Construct a action object from database record. - - :param context: the context used for DB operations; - :param record: a DB action object that contains all fields. - :return: An `Action` object deserialized from the DB action object. - """ - context = req_context.RequestContext.from_dict(record.context) - kwargs = { - 'id': record.id, - 'name': record.name, - 'cause': record.cause, - 'owner': record.owner, - 'start_time': record.start_time, - 'end_time': record.end_time, - 'timeout': record.timeout, - 'status': record.status, - 'status_reason': record.status_reason, - 'inputs': record.inputs or {}, - 'outputs': record.outputs or {}, - 'created_at': record.created_at, - 'updated_at': record.updated_at, - 'data': record.data, - } - - return cls(record.target, record.action, context, **kwargs) - - @classmethod - def load(cls, context, action_id=None, db_action=None): - """Retrieve an action from database. - - :param context: Instance of request context. - :param action_id: An UUID for the action to deserialize. - :param db_action: An action object for the action to deserialize. - :return: A `Action` object instance. - """ - if db_action is None: - db_action = db_api.action_get(context, action_id) - if db_action is None: - raise exception.ActionNotFound(action=action_id) - - return cls._from_db_record(db_action) - - @classmethod - def load_all(cls, context, filters=None, limit=None, marker=None, - sort_keys=None, sort_dir=None): - """Retrieve all actions from database.""" - - records = db_api.action_get_all(context, filters=filters, - limit=limit, marker=marker, - sort_keys=sort_keys, - sort_dir=sort_dir) - - for record in records: - yield cls._from_db_record(record) - - @classmethod - def create(cls, context, target, action, **kwargs): - """Create an action object. - - :param context: The requesting context. - :param target: The ID of the target. - :param action: Name of the action. - :param dict kwargs: Other keyword arguments for the action. - :return: ID of the action created. - """ - params = { - 'user': context.user, - 'project': context.project, - 'domain': context.domain, - 'is_admin': context.is_admin, - 'request_id': context.request_id, - 'trusts': context.trusts, - } - ctx = req_context.RequestContext.from_dict(params) - obj = cls(target, action, ctx, **kwargs) - return obj.store(context) - - @classmethod - def delete(cls, context, action_id): - """Delete an action from database.""" - db_api.action_delete(context, action_id) - - def signal(self, cmd): - '''Send a signal to the action.''' - if cmd not in self.COMMANDS: - return - - if cmd == self.SIG_CANCEL: - expected_statuses = (self.INIT, self.WAITING, self.READY, - self.RUNNING) - elif cmd == self.SIG_SUSPEND: - expected_statuses = (self.RUNNING) - else: # SIG_RESUME - expected_statuses = (self.SUSPENDED) - - if self.status not in expected_statuses: - reason = _("Action (%(action)s) is in unexpected status " - "(%(actual)s) while expected status should be one of " - "(%(expected)s).") % dict(action=self.id, - expected=expected_statuses, - actual=self.status) - EVENT.error(self.context, self, cmd, status_reason=reason) - return - - db_api.action_signal(self.context, self.id, cmd) - - def execute(self, **kwargs): - '''Execute the action. - - In theory, the action encapsulates all information needed for - execution. 'kwargs' may specify additional parameters. - :param kwargs: additional parameters that may override the default - properties stored in the action record. - ''' - return NotImplemented - - def set_status(self, result, reason=None): - """Set action status based on return value from execute.""" - - timestamp = wallclock() - - if result == self.RES_OK: - status = self.SUCCEEDED - db_api.action_mark_succeeded(self.context, self.id, timestamp) - - elif result == self.RES_ERROR: - status = self.FAILED - db_api.action_mark_failed(self.context, self.id, timestamp, - reason=reason or 'ERROR') - - elif result == self.RES_TIMEOUT: - status = self.FAILED - db_api.action_mark_failed(self.context, self.id, timestamp, - reason=reason or 'TIMEOUT') - - elif result == self.RES_CANCEL: - status = self.CANCELLED - db_api.action_mark_cancelled(self.context, self.id, timestamp) - - else: # result == self.RES_RETRY: - status = self.READY - # Action failed at the moment, but can be retried - # We abandon it and then notify other dispatchers to execute it - db_api.action_abandon(self.context, self.id) - - if status == self.SUCCEEDED: - EVENT.info(self.context, self, self.action, status, reason) - elif status == self.READY: - EVENT.warning(self.context, self, self.action, status, reason) - else: - EVENT.error(self.context, self, self.action, status, reason) - - self.status = status - self.status_reason = reason - - def get_status(self): - timestamp = wallclock() - status = db_api.action_check_status(self.context, self.id, timestamp) - self.status = status - return status - - def is_timeout(self): - time_lapse = wallclock() - self.start_time - return time_lapse > self.timeout - - def _check_signal(self): - # Check timeout first, if true, return timeout message - if self.timeout is not None and self.is_timeout(): - EVENT.debug(self.context, self, self.action, 'TIMEOUT') - return self.RES_TIMEOUT - - result = db_api.action_signal_query(self.context, self.id) - return result - - def is_cancelled(self): - return self._check_signal() == self.SIG_CANCEL - - def is_suspended(self): - return self._check_signal() == self.SIG_SUSPEND - - def is_resumed(self): - return self._check_signal() == self.SIG_RESUME - - def to_dict(self): - if self.id: - dep_on = db_api.dependency_get_depended(self.context, self.id) - dep_by = db_api.dependency_get_dependents(self.context, self.id) - else: - dep_on = [] - dep_by = [] - action_dict = { - 'id': self.id, - 'name': self.name, - 'action': self.action, - 'target': self.target, - 'cause': self.cause, - 'owner': self.owner, - 'start_time': utils.dec2str(self.start_time), - 'end_time': utils.dec2str(self.end_time), - 'timeout': self.timeout, - 'status': self.status, - 'status_reason': self.status_reason, - 'inputs': self.inputs, - 'outputs': self.outputs, - 'depends_on': dep_on, - 'depended_by': dep_by, - 'created_at': self.created_at, - 'updated_at': self.updated_at, - 'data': self.data, - } - return action_dict - - -def ActionProc(context, action_id): - '''Action process.''' - - action = Action.load(context, action_id=action_id) - if action is None: - LOG.error(_LE('Action "%s" could not be found.'), action_id) - return False - - reason = 'Action completed' - success = True - try: - result, reason = action.execute() - except Exception as ex: - result = action.RES_ERROR - reason = six.text_type(ex) - LOG.exception(_('Unexpected exception occurred during action ' - '%(action)s (%(id)s) execution: %(reason)s'), - {'action': action.action, 'id': action.id, - 'reason': reason}) - success = False - finally: - # NOTE: locks on action is eventually released here by status update - action.set_status(result, reason) - - return success diff --git a/bilean/engine/actions/user_action.py b/bilean/engine/actions/user_action.py deleted file mode 100644 index 644e9bb..0000000 --- a/bilean/engine/actions/user_action.py +++ /dev/null @@ -1,169 +0,0 @@ -# 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 six - -from bilean.common import exception -from bilean.common.i18n import _, _LE, _LI -from bilean.engine.actions import base -from bilean.engine import event as EVENT -from bilean.engine.flows import flow as bilean_flow -from bilean.engine import lock as bilean_lock -from bilean.engine import user as user_mod -from bilean.plugins import base as plugin_base - -from oslo_log import log as logging - -LOG = logging.getLogger(__name__) - - -class UserAction(base.Action): - """An action that can be performed on a user.""" - - ACTIONS = ( - USER_CREATE_RESOURCE, USER_UPDATE_RESOUCE, USER_DELETE_RESOURCE, - USER_SETTLE_ACCOUNT, - ) = ( - 'USER_CREATE_RESOURCE', 'USER_UPDATE_RESOUCE', 'USER_DELETE_RESOURCE', - 'USER_SETTLE_ACCOUNT', - ) - - def __init__(self, target, action, context, **kwargs): - """Constructor for a user action object. - - :param target: ID of the target user object on which the action is to - be executed. - :param action: The name of the action to be executed. - :param context: The context used for accessing the DB layer. - :param dict kwargs: Additional parameters that can be passed to the - action. - """ - super(UserAction, self).__init__(target, action, context, **kwargs) - - try: - self.user = user_mod.User.load(self.context, user_id=self.target) - except Exception: - self.user = None - - def do_create_resource(self): - resource = plugin_base.Resource.from_dict(self.inputs) - try: - flow_engine = bilean_flow.get_create_resource_flow( - self.context, self.target, resource) - with bilean_flow.DynamicLogListener(flow_engine, logger=LOG): - flow_engine.run() - except Exception as ex: - LOG.error(_LE("Faied to execute action(%(action_id)s), error: " - "%(error_msg)s"), {"action_id": self.id, - "error_msg": six.text_type(ex)}) - return self.RES_ERROR, _('Resource creation failed.') - - return self.RES_OK, _('Resource creation successfully.') - - def do_update_resource(self): - try: - values = self.inputs - resource_id = values.pop('id', None) - resource = plugin_base.Resource.load( - self.context, resource_id=resource_id) - except exception.ResourceNotFound: - LOG.error(_LE('The resource(%s) trying to update not found.'), - resource_id) - return self.RES_ERROR, _('Resource not found.') - - try: - flow_engine = bilean_flow.get_update_resource_flow( - self.context, self.target, resource, values) - with bilean_flow.DynamicLogListener(flow_engine, logger=LOG): - flow_engine.run() - except Exception as ex: - LOG.error(_LE("Faied to execute action(%(action_id)s), error: " - "%(error_msg)s"), {"action_id": self.id, - "error_msg": six.text_type(ex)}) - return self.RES_ERROR, _('Resource update failed.') - - LOG.info(_LI('Successfully updated resource: %s'), resource.id) - return self.RES_OK, _('Resource update successfully.') - - def do_delete_resource(self): - try: - resource_id = self.inputs.get('resource_id') - resource = plugin_base.Resource.load( - self.context, resource_id=resource_id) - except exception.ResourceNotFound: - LOG.error(_LE('The resource(%s) trying to delete not found.'), - resource_id) - return self.RES_ERROR, _('Resource not found.') - - try: - flow_engine = bilean_flow.get_delete_resource_flow( - self.context, self.target, resource) - with bilean_flow.DynamicLogListener(flow_engine, logger=LOG): - flow_engine.run() - except Exception as ex: - LOG.error(_LE("Faied to execute action(%(action_id)s), error: " - "%(error_msg)s"), {"action_id": self.id, - "error_msg": six.text_type(ex)}) - return self.RES_ERROR, _('Resource deletion failed.') - - LOG.info(_LI('Successfully deleted resource: %s'), resource.id) - return self.RES_OK, _('Resource deletion successfully.') - - def do_settle_account(self): - try: - flow_engine = bilean_flow.get_settle_account_flow( - self.context, self.target, task=self.inputs.get('task')) - with bilean_flow.DynamicLogListener(flow_engine, logger=LOG): - flow_engine.run() - except Exception as ex: - LOG.error(_LE("Faied to execute action(%(action_id)s), error: " - "%(error_msg)s"), {"action_id": self.id, - "error_msg": six.text_type(ex)}) - return self.RES_ERROR, _('Settle account failed.') - - return self.RES_OK, _('Settle account successfully.') - - def _execute(self): - """Private function that finds out the handler and execute it.""" - - action_name = self.action.lower() - method_name = action_name.replace('user', 'do') - method = getattr(self, method_name, None) - - if method is None: - reason = _('Unsupported action: %s') % self.action - EVENT.error(self.context, self.user, self.action, 'Failed', reason) - return self.RES_ERROR, reason - - return method() - - def execute(self, **kwargs): - """Interface function for action execution. - - :param dict kwargs: Parameters provided to the action, if any. - :returns: A tuple containing the result and the related reason. - """ - - try: - res = bilean_lock.user_lock_acquire(self.context, self.target, - self.id, self.owner) - if not res: - LOG.error(_LE('Failed grabbing the lock for user: %s'), - self.target) - res = self.RES_ERROR - reason = _('Failed in locking user') - else: - res, reason = self._execute() - finally: - bilean_lock.user_lock_release(self.target, self.id) - - return res, reason diff --git a/bilean/engine/consumption.py b/bilean/engine/consumption.py deleted file mode 100644 index 4bf309d..0000000 --- a/bilean/engine/consumption.py +++ /dev/null @@ -1,117 +0,0 @@ -# -# 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 bilean.common import exception -from bilean.common import utils -from bilean.db import api as db_api - - -class Consumption(object): - """Class reference to consumption record.""" - - def __init__(self, user_id, **kwargs): - self.id = kwargs.get('id') - self.user_id = user_id - - self.resource_id = kwargs.get('resource_id') - self.resource_type = kwargs.get('resource_type') - - self.start_time = utils.make_decimal(kwargs.get('start_time', 0)) - self.end_time = utils.make_decimal(kwargs.get('end_time', 0)) - self.rate = utils.make_decimal(kwargs.get('rate', 0)) - self.cost = utils.make_decimal(kwargs.get('cost', 0)) - self.metadata = kwargs.get('metadata') - - @classmethod - def from_db_record(cls, record): - '''Construct a consumption object from a database record.''' - - kwargs = { - 'id': record.id, - 'resource_id': record.resource_id, - 'resource_type': record.resource_type, - 'start_time': record.start_time, - 'end_time': record.end_time, - 'rate': record.rate, - 'cost': record.cost, - 'metadata': record.meta_data, - } - return cls(record.user_id, **kwargs) - - @classmethod - def load(cls, context, db_consumption=None, consumption_id=None, - project_safe=True): - '''Retrieve a consumption record from database.''' - if db_consumption is not None: - return cls.from_db_record(db_consumption) - - record = db_api.consumption_get(context, consumption_id, - project_safe=project_safe) - if record is None: - raise exception.ConsumptionNotFound(consumption=consumption_id) - - return cls.from_db_record(record) - - @classmethod - def load_all(cls, context, user_id=None, limit=None, marker=None, - sort_keys=None, sort_dir=None, filters=None, - project_safe=True): - '''Retrieve all consumptions from database.''' - - records = db_api.consumption_get_all(context, - user_id=user_id, - limit=limit, - marker=marker, - filters=filters, - sort_keys=sort_keys, - sort_dir=sort_dir, - project_safe=project_safe) - - for record in records: - yield cls.from_db_record(record) - - def store(self, context): - '''Store the consumption into database and return its ID.''' - values = { - 'user_id': self.user_id, - 'resource_id': self.resource_id, - 'resource_type': self.resource_type, - 'start_time': utils.format_decimal(self.start_time), - 'end_time': utils.format_decimal(self.end_time), - 'rate': utils.format_decimal(self.rate), - 'cost': utils.format_decimal(self.cost), - 'meta_data': self.metadata, - } - - consumption = db_api.consumption_create(context, values) - self.id = consumption.id - - return self.id - - def delete(self, context): - '''Delete consumption from database.''' - db_api.consumption_delete(context, self.id) - - def to_dict(self): - consumption = { - 'id': self.id, - 'user_id': self.user_id, - 'resource_id': self.resource_id, - 'resource_type': self.resource_type, - 'start_time': utils.dec2str(self.start_time), - 'end_time': utils.dec2str(self.end_time), - 'rate': utils.dec2str(self.rate), - 'cost': utils.dec2str(self.cost), - 'metadata': self.metadata, - } - return consumption diff --git a/bilean/engine/dispatcher.py b/bilean/engine/dispatcher.py deleted file mode 100644 index 2f84fde..0000000 --- a/bilean/engine/dispatcher.py +++ /dev/null @@ -1,112 +0,0 @@ -# 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 oslo_context import context as oslo_context -from oslo_log import log as logging -import oslo_messaging -from oslo_service import service - -from bilean.common import consts -from bilean.common.i18n import _LI -from bilean.common import messaging as rpc_messaging - -LOG = logging.getLogger(__name__) - -OPERATIONS = ( - START_ACTION, CANCEL_ACTION, STOP -) = ( - 'start_action', 'cancel_action', 'stop' -) - - -class Dispatcher(service.Service): - '''Listen on an AMQP queue named for the engine. - - Receive notification from engine services and schedule actions. - ''' - def __init__(self, engine_service, topic, version, thread_group_mgr): - super(Dispatcher, self).__init__() - self.TG = thread_group_mgr - self.engine_id = engine_service.engine_id - self.topic = topic - self.version = version - - def start(self): - super(Dispatcher, self).start() - self.target = oslo_messaging.Target(server=self.engine_id, - topic=self.topic, - version=self.version) - server = rpc_messaging.get_rpc_server(self.target, self) - server.start() - - def listening(self, ctxt): - '''Respond affirmatively to confirm that engine is still alive.''' - return True - - def start_action(self, ctxt, action_id=None): - self.TG.start_action(self.engine_id, action_id) - - def cancel_action(self, ctxt, action_id): - '''Cancel an action.''' - self.TG.cancel_action(action_id) - - def suspend_action(self, ctxt, action_id): - '''Suspend an action.''' - self.TG.suspend_action(action_id) - - def resume_action(self, ctxt, action_id): - '''Resume an action.''' - self.TG.resume_action(action_id) - - def stop(self): - super(Dispatcher, self).stop() - # Wait for all action threads to be finished - LOG.info(_LI("Stopping all action threads of engine %s"), - self.engine_id) - # Stop ThreadGroup gracefully - self.TG.stop(True) - LOG.info(_LI("All action threads have been finished")) - - -def notify(method, engine_id=None, **kwargs): - '''Send notification to dispatcher - - :param method: remote method to call - :param engine_id: dispatcher to notify; None implies broadcast - ''' - - client = rpc_messaging.get_rpc_client(version=consts.RPC_API_VERSION) - - if engine_id: - # Notify specific dispatcher identified by engine_id - call_context = client.prepare( - version=consts.RPC_API_VERSION, - topic=consts.ENGINE_DISPATCHER_TOPIC, - server=engine_id) - else: - # Broadcast to all disptachers - call_context = client.prepare( - version=consts.RPC_API_VERSION, - topic=consts.ENGINE_DISPATCHER_TOPIC) - - try: - # We don't use ctext parameter in action progress - # actually. But since RPCClient.call needs this param, - # we use oslo current context here. - call_context.call(oslo_context.get_current(), method, **kwargs) - return True - except oslo_messaging.MessagingTimeout: - return False - - -def start_action(engine_id=None, **kwargs): - return notify(START_ACTION, engine_id, **kwargs) diff --git a/bilean/engine/environment.py b/bilean/engine/environment.py deleted file mode 100644 index f554621..0000000 --- a/bilean/engine/environment.py +++ /dev/null @@ -1,189 +0,0 @@ -# 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 glob -import os.path -import six -from stevedore import extension - -from oslo_config import cfg -from oslo_log import log as logging - -from bilean.common import exception -from bilean.common.i18n import _, _LE, _LI -from bilean.engine import parser -from bilean.engine import registry - -LOG = logging.getLogger(__name__) - -_environment = None - - -def global_env(): - global _environment - - if _environment is None: - initialize() - return _environment - - -class Environment(object): - '''An object that contains all plugins, drivers and customizations.''' - - SECTIONS = ( - PARAMETERS, CUSTOM_PLUGINS, - ) = ( - 'parameters', 'custom_plugins' - ) - - def __init__(self, env=None, is_global=False): - '''Create an Environment from a dict. - - :param env: the json environment - :param is_global: boolean indicating if this is a user created one. - ''' - self.params = {} - if is_global: - self.plugin_registry = registry.Registry('plugins') - self.driver_registry = registry.Registry('drivers') - else: - self.plugin_registry = registry.Registry( - 'plugins', global_env().plugin_registry) - self.driver_registry = registry.Registry( - 'drivers', global_env().driver_registry) - - if env is not None: - # Merge user specified keys with current environment - self.params = env.get(self.PARAMETERS, {}) - custom_plugins = env.get(self.CUSTOM_PLUGINS, {}) - self.plugin_registry.load(custom_plugins) - - def parse(self, env_str): - '''Parse a string format environment file into a dictionary.''' - - if env_str is None: - return {} - - env = parser.simple_parse(env_str) - - # Check unknown sections - for sect in env: - if sect not in self.SECTIONS: - msg = _('environment has unknown section "%s"') % sect - raise ValueError(msg) - - # Fill in default values for missing sections - for sect in self.SECTIONS: - if sect not in env: - env[sect] = {} - - return env - - def load(self, env_dict): - '''Load environment from the given dictionary.''' - - self.params.update(env_dict.get(self.PARAMETERS, {})) - self.plugin_registry.load(env_dict.get(self.CUSTOM_PLUGINS, {})) - - def _check_plugin_name(self, plugin_type, name): - if name is None or name == "": - msg = _('%s type name not specified') % plugin_type - raise exception.InvalidPlugin(message=msg) - elif not isinstance(name, six.string_types): - msg = _('%s type name is not a string') % plugin_type - raise exception.InvalidPlugin(message=msg) - - def register_plugin(self, name, plugin): - self._check_plugin_name('Plugin', name) - self.plugin_registry.register_plugin(name, plugin) - - def get_plugin(self, name): - self._check_plugin_name('Plugin', name) - plugin = self.plugin_registry.get_plugin(name) - if plugin is None: - raise exception.PluginTypeNotFound(plugin_type=name) - return plugin - - def get_plugins(self): - return self.plugin_registry.get_plugins() - - def get_plugin_types(self): - return self.plugin_registry.get_types() - - def register_driver(self, name, plugin): - self._check_plugin_name('Driver', name) - self.driver_registry.register_plugin(name, plugin) - - def get_driver(self, name): - self._check_plugin_name('Driver', name) - plugin = self.driver_registry.get_plugin(name) - if plugin is None: - msg = _('Driver plugin %(name)s is not found.') % {'name': name} - raise exception.InvalidPlugin(message=msg) - return plugin - - def get_driver_types(self): - return self.driver_registry.get_types() - - def read_global_environment(self): - '''Read and parse global environment files.''' - - cfg.CONF.import_opt('environment_dir', 'bilean.common.config') - env_dir = cfg.CONF.environment_dir - - try: - files = glob.glob(os.path.join(env_dir, '*')) - except OSError as ex: - LOG.error(_LE('Failed to read %s'), env_dir) - LOG.exception(ex) - return - - for fname in files: - try: - with open(fname) as f: - LOG.info(_LI('Loading environment from %s'), fname) - self.load(self.parse(f.read())) - except ValueError as vex: - LOG.error(_LE('Failed to parse %s'), fname) - LOG.exception(six.text_type(vex)) - except IOError as ioex: - LOG.error(_LE('Failed to read %s'), fname) - LOG.exception(six.text_type(ioex)) - - -def _get_mapping(namespace): - mgr = extension.ExtensionManager( - namespace=namespace, - invoke_on_load=False) - return [[name, mgr[name].plugin] for name in mgr.names()] - - -def initialize(): - - global _environment - - if _environment is not None: - return - - env = Environment(is_global=True) - - # Register global plugins when initialized - entries = _get_mapping('bilean.plugins') - for name, plugin in entries: - env.register_plugin(name, plugin) - - entries = _get_mapping('bilean.drivers') - for name, plugin in entries: - env.register_driver(name, plugin) - - env.read_global_environment() - _environment = env diff --git a/bilean/engine/event.py b/bilean/engine/event.py deleted file mode 100644 index c2c3624..0000000 --- a/bilean/engine/event.py +++ /dev/null @@ -1,209 +0,0 @@ -# -# 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 logging - -from bilean.common import exception -from bilean.common.i18n import _, _LC, _LE, _LI, _LW -from bilean.common import utils -from bilean.db import api as db_api - -from oslo_log import log -from oslo_utils import reflection -from oslo_utils import timeutils - -LOG = log.getLogger(__name__) - - -class Event(object): - '''capturing an interesting happening in Bilean.''' - - def __init__(self, timestamp, level, entity=None, **kwargs): - self.timestamp = timestamp - self.level = level - - self.id = kwargs.get('id') - self.user_id = kwargs.get('user_id') - - self.action = kwargs.get('action') - self.status = kwargs.get('status') - self.status_reason = kwargs.get('status_reason') - - self.obj_id = kwargs.get('obj_id') - self.obj_type = kwargs.get('obj_type') - self.obj_name = kwargs.get('obj_name') - self.metadata = kwargs.get('metadata') - - cntx = kwargs.get('context') - if cntx is not None: - self.user_id = cntx.project - - if entity is not None: - self.obj_id = entity.id - self.obj_name = entity.name - e_type = reflection.get_class_name(entity, fully_qualified=False) - self.obj_type = e_type.upper() - - @classmethod - def from_db_record(cls, record): - '''Construct an event object from a database record.''' - - kwargs = { - 'id': record.id, - 'user_id': record.user_id, - 'action': record.action, - 'status': record.status, - 'status_reason': record.status_reason, - 'obj_id': record.obj_id, - 'obj_type': record.obj_type, - 'obj_name': record.obj_name, - 'metadata': record.meta_data, - } - return cls(record.timestamp, record.level, **kwargs) - - @classmethod - def load(cls, context, db_event=None, event_id=None, project_safe=True): - '''Retrieve an event record from database.''' - if db_event is not None: - return cls.from_db_record(db_event) - - record = db_api.event_get(context, event_id, project_safe=project_safe) - if record is None: - raise exception.EventNotFound(event=event_id) - - return cls.from_db_record(record) - - @classmethod - def load_all(cls, context, limit=None, marker=None, sort_keys=None, - sort_dir=None, filters=None, project_safe=True): - '''Retrieve all events from database.''' - - records = db_api.event_get_all(context, limit=limit, - marker=marker, - filters=filters, - sort_keys=sort_keys, - sort_dir=sort_dir, - project_safe=project_safe) - - for record in records: - yield cls.from_db_record(record) - - def store(self, context): - '''Store the event into database and return its ID.''' - values = { - 'timestamp': self.timestamp, - 'level': self.level, - 'user_id': self.user_id, - 'action': self.action, - 'status': self.status, - 'status_reason': self.status_reason, - 'obj_id': self.obj_id, - 'obj_type': self.obj_type, - 'obj_name': self.obj_name, - 'meta_data': self.metadata, - } - - event = db_api.event_create(context, values) - self.id = event.id - - return self.id - - def to_dict(self): - evt = { - 'id': self.id, - 'level': self.level, - 'user_id': self.user_id, - 'action': self.action, - 'status': self.status, - 'status_reason': self.status_reason, - 'obj_id': self.obj_id, - 'obj_type': self.obj_type, - 'obj_name': self.obj_name, - 'timestamp': utils.format_time(self.timestamp), - 'metadata': self.metadata, - } - return evt - - -def critical(context, entity, action, status=None, status_reason=None, - timestamp=None): - timestamp = timestamp or timeutils.utcnow() - event = Event(timestamp, logging.CRITICAL, entity, - action=action, status=status, status_reason=status_reason, - user_id=context.project) - event.store(context) - LOG.critical(_LC('%(name)s [%(id)s] - %(status)s: %(reason)s'), - {'name': event.obj_name, - 'id': event.obj_id and event.obj_id[:8], - 'status': status, - 'reason': status_reason}) - - -def error(context, entity, action, status=None, status_reason=None, - timestamp=None): - timestamp = timestamp or timeutils.utcnow() - event = Event(timestamp, logging.ERROR, entity, - action=action, status=status, status_reason=status_reason, - user_id=context.project) - event.store(context) - LOG.error(_LE('%(name)s [%(id)s] %(action)s - %(status)s: %(reason)s'), - {'name': event.obj_name, - 'id': event.obj_id and event.obj_id[:8], - 'action': action, - 'status': status, - 'reason': status_reason}) - - -def warning(context, entity, action, status=None, status_reason=None, - timestamp=None): - timestamp = timestamp or timeutils.utcnow() - event = Event(timestamp, logging.WARNING, entity, - action=action, status=status, status_reason=status_reason, - user_id=context.project) - event.store(context) - LOG.warn(_LW('%(name)s [%(id)s] %(action)s - %(status)s: %(reason)s'), - {'name': event.obj_name, - 'id': event.obj_id and event.obj_id[:8], - 'action': action, - 'status': status, - 'reason': status_reason}) - - -def info(context, entity, action, status=None, status_reason=None, - timestamp=None): - timestamp = timestamp or timeutils.utcnow() - event = Event(timestamp, logging.INFO, entity, - action=action, status=status, status_reason=status_reason, - user_id=context.project) - event.store(context) - LOG.info(_LI('%(name)s [%(id)s] %(action)s - %(status)s: %(reason)s'), - {'name': event.obj_name, - 'id': event.obj_id and event.obj_id[:8], - 'action': action, - 'status': status, - 'reason': status_reason}) - - -def debug(context, entity, action, status=None, status_reason=None, - timestamp=None): - timestamp = timestamp or timeutils.utcnow() - event = Event(timestamp, logging.DEBUG, entity, - action=action, status=status, status_reason=status_reason, - user_id=context.project) - event.store(context) - LOG.debug(_('%(name)s [%(id)s] %(action)s - %(status)s: %(reason)s'), - {'name': event.obj_name, - 'id': event.obj_id and event.obj_id[:8], - 'action': action, - 'status': status, - 'reason': status_reason}) diff --git a/bilean/engine/flows/__init__.py b/bilean/engine/flows/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/engine/flows/flow.py b/bilean/engine/flows/flow.py deleted file mode 100644 index c5f25ce..0000000 --- a/bilean/engine/flows/flow.py +++ /dev/null @@ -1,294 +0,0 @@ -# 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 oslo_log import log as logging -import taskflow.engines -from taskflow.listeners import base -from taskflow.listeners import logging as logging_listener -from taskflow.patterns import linear_flow -from taskflow import task -from taskflow.types import failure as ft - -from bilean.common import exception -from bilean.common.i18n import _LE -from bilean.common import utils -from bilean.engine import policy as policy_mod -from bilean.engine import user as user_mod -from bilean.plugins import base as plugin_base -from bilean import scheduler as bilean_scheduler - -LOG = logging.getLogger(__name__) - - -class DynamicLogListener(logging_listener.DynamicLoggingListener): - """This is used to attach to taskflow engines while they are running. - - It provides a bunch of useful features that expose the actions happening - inside a taskflow engine, which can be useful for developers for debugging, - for operations folks for monitoring and tracking of the resource actions - and more... - """ - - #: Exception is an excepted case, don't include traceback in log if fails. - _NO_TRACE_EXCEPTIONS = (exception.InvalidInput) - - def __init__(self, engine, - task_listen_for=base.DEFAULT_LISTEN_FOR, - flow_listen_for=base.DEFAULT_LISTEN_FOR, - retry_listen_for=base.DEFAULT_LISTEN_FOR, - logger=LOG): - super(DynamicLogListener, self).__init__( - engine, - task_listen_for=task_listen_for, - flow_listen_for=flow_listen_for, - retry_listen_for=retry_listen_for, - log=logger) - - def _format_failure(self, fail): - if fail.check(*self._NO_TRACE_EXCEPTIONS) is not None: - exc_info = None - exc_details = '%s%s' % (os.linesep, fail.pformat(traceback=False)) - return (exc_info, exc_details) - else: - return super(DynamicLogListener, self)._format_failure(fail) - - -class CreateResourceTask(task.Task): - """Create resource and store to db.""" - - def execute(self, context, resource, **kwargs): - user = user_mod.User.load(context, user_id=resource.user_id) - pid = user.policy_id - try: - if pid: - policy = policy_mod.Policy.load(context, policy_id=pid) - else: - policy = policy_mod.Policy.load_default(context) - except exception.PolicyNotFound as e: - LOG.error(_LE("Error when find policy: %s"), e) - if policy is not None: - rule = policy.find_rule(context, resource.resource_type) - - # Update resource with rule_id and rate - resource.rule_id = rule.id - resource.rate = utils.make_decimal(rule.get_price(resource)) - resource.store(context) - - def revert(self, context, resource, result, **kwargs): - if isinstance(result, ft.Failure): - LOG.error(_LE("Error when creating resource: %s"), - resource.to_dict()) - return - - resource.delete(context, soft_delete=False) - - -class UpdateResourceTask(task.Task): - """Update resource.""" - - def execute(self, context, resource, values, resource_bak, **kwargs): - old_rate = resource.rate - resource.properties = values.get('properties') - rule = plugin_base.Rule.load(context, rule_id=resource.rule_id) - resource.rate = utils.make_decimal(rule.get_price(resource)) - resource.delta_rate = resource.rate - old_rate - resource.store(context) - - def revert(self, context, resource, resource_bak, result, **kwargs): - if isinstance(result, ft.Failure): - LOG.error(_LE("Error when updating resource: %s"), resource.id) - return - - # restore resource - res = plugin_base.Resource.from_dict(resource_bak) - res.store(context) - - -class DeleteResourceTask(task.Task): - """Delete resource from db.""" - - def execute(self, context, resource, **kwargs): - resource.delete(context) - - def revert(self, context, resource, result, **kwargs): - if isinstance(result, ft.Failure): - LOG.error(_LE("Error when deleting resource: %s"), resource.id) - return - - resource.deleted_at = None - resource.store(context) - - -class CreateConsumptionTask(task.Task): - """Generate consumption record and store to db.""" - - def execute(self, context, resource, *args, **kwargs): - consumption = resource.consumption - if consumption is not None: - consumption.store(context) - - def revert(self, context, resource, result, *args, **kwargs): - if isinstance(result, ft.Failure): - LOG.error(_LE("Error when storing consumption of resource: %s"), - resource.id) - return - - consumption = resource.consumption - if consumption is not None: - consumption.delete(context) - - -class LoadUserTask(task.Task): - """Load user from db.""" - - default_provides = set(['user_bak', 'user_obj']) - - def execute(self, context, user_id, **kwargs): - user_obj = user_mod.User.load(context, user_id=user_id) - return { - 'user_bak': user_obj.to_dict(), - 'user_obj': user_obj, - } - - -class SettleAccountTask(task.Task): - def execute(self, context, user_obj, user_bak, task, **kwargs): - user_obj.settle_account(context, task=task) - - def revert(self, context, user_bak, result, **kwargs): - if isinstance(result, ft.Failure): - LOG.error(_LE("Error when settling account for user: %s"), - user_bak.get('id')) - return - - # Restore user - user = user_mod.User.from_dict(user_bak) - user.store(context) - - -class UpdateUserRateTask(task.Task): - """Update user's rate .""" - - def execute(self, context, user_obj, user_bak, resource, *args, **kwargs): - user_obj.update_rate(context, resource.delta_rate, - timestamp=resource.last_bill) - - def revert(self, context, user_obj, user_bak, resource, result, - *args, **kwargs): - if isinstance(result, ft.Failure): - LOG.error(_LE("Error when updating user: %s"), user_obj.id) - return - - # Restore user - user = user_mod.User.from_dict(user_bak) - user.store(context) - - -class UpdateUserJobsTask(task.Task): - """Update user jobs.""" - - def execute(self, user_obj, **kwargs): - res = bilean_scheduler.notify(bilean_scheduler.UPDATE_JOBS, - user=user_obj.to_dict()) - if not res: - LOG.error(_LE("Error when updating user jobs: %s"), user_obj.id) - raise - - -def get_settle_account_flow(context, user_id, task=None): - """Constructs and returns settle account task flow.""" - - flow_name = user_id + '_settle_account' - flow = linear_flow.Flow(flow_name) - kwargs = { - 'context': context, - 'user_id': user_id, - 'task': task, - } - flow.add(LoadUserTask(), - SettleAccountTask()) - if task != 'freeze': - flow.add(UpdateUserJobsTask()) - return taskflow.engines.load(flow, store=kwargs) - - -def get_create_resource_flow(context, user_id, resource): - """Constructs and returns user task flow. - - :param context: The request context. - :param user_id: The ID of user. - :param resource: Object resource to create. - """ - - flow_name = user_id + '_create_resource' - flow = linear_flow.Flow(flow_name) - kwargs = { - 'context': context, - 'user_id': user_id, - 'resource': resource, - } - flow.add(CreateResourceTask(), - LoadUserTask(), - UpdateUserRateTask(), - UpdateUserJobsTask()) - return taskflow.engines.load(flow, store=kwargs) - - -def get_delete_resource_flow(context, user_id, resource): - """Constructs and returns user task flow. - - :param context: The request context. - :param user_id: The ID of user. - :param resource: Object resource to delete. - """ - - flow_name = user_id + '_delete_resource' - flow = linear_flow.Flow(flow_name) - kwargs = { - 'context': context, - 'user_id': user_id, - 'resource': resource, - } - flow.add(DeleteResourceTask(), - CreateConsumptionTask(), - LoadUserTask(), - UpdateUserRateTask(), - UpdateUserJobsTask()) - return taskflow.engines.load(flow, store=kwargs) - - -def get_update_resource_flow(context, user_id, resource, values): - """Constructs and returns user task flow. - - :param context: The request context. - :param user_id: The ID of user. - :param resource: Object resource to update. - :param values: The values to update. - """ - - flow_name = user_id + '_update_resource' - flow = linear_flow.Flow(flow_name) - kwargs = { - 'context': context, - 'user_id': user_id, - 'resource': resource, - 'resource_bak': resource.to_dict(), - 'values': values, - } - flow.add(UpdateResourceTask(), - CreateConsumptionTask(), - LoadUserTask(), - UpdateUserRateTask(), - UpdateUserJobsTask()) - return taskflow.engines.load(flow, store=kwargs) diff --git a/bilean/engine/lock.py b/bilean/engine/lock.py deleted file mode 100644 index 34a8942..0000000 --- a/bilean/engine/lock.py +++ /dev/null @@ -1,108 +0,0 @@ -# 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 eventlet -from oslo_config import cfg -from oslo_log import log as logging -from oslo_utils import timeutils -import time - -from bilean.common.i18n import _, _LE, _LI -from bilean.db import api as db_api - -CONF = cfg.CONF - -CONF.import_opt('lock_retry_times', 'bilean.common.config') -CONF.import_opt('lock_retry_interval', 'bilean.common.config') - -LOG = logging.getLogger(__name__) - - -def is_engine_dead(ctx, engine_id, period_time=None): - # if engine didn't report its status for peirod_time, will consider it - # as a dead engine. - if period_time is None: - period_time = 2 * CONF.periodic_interval - engine = db_api.service_get(ctx, engine_id) - if not engine: - return True - if (timeutils.utcnow() - engine.updated_at).total_seconds() > period_time: - return True - return False - - -def sleep(sleep_time): - '''Interface for sleeping.''' - - eventlet.sleep(sleep_time) - - -def user_lock_acquire(context, user_id, action_id, engine=None, - forced=False): - """Try to lock the specified user. - - :param context: the context used for DB operations; - :param user_id: ID of the user to be locked. - :param action_id: ID of the action that attempts to lock the user. - :param engine: ID of the engine that attempts to lock the user. - :param forced: set to True to cancel current action that owns the lock, - if any. - :returns: True if lock is acquired, or False otherwise. - """ - - owner = db_api.user_lock_acquire(user_id, action_id) - if action_id == owner: - return True - - retries = cfg.CONF.lock_retry_times - retry_interval = cfg.CONF.lock_retry_interval - - while retries > 0: - sleep(retry_interval) - LOG.debug(_('Acquire lock for user %s again'), user_id) - owner = db_api.user_lock_acquire(user_id, action_id) - if action_id == owner: - return True - retries = retries - 1 - - if forced: - owner = db_api.user_lock_steal(user_id, action_id) - return action_id == owner - - action = db_api.action_get(context, owner) - if (action and action.owner and action.owner != engine and - is_engine_dead(context, action.owner)): - LOG.info(_LI('The user %(u)s is locked by dead action %(a)s, ' - 'try to steal the lock.'), { - 'u': user_id, - 'a': owner - }) - reason = _('Engine died when executing this action.') - db_api.action_mark_failed(context, action.id, time.time(), - reason=reason) - db_api.user_lock_steal(user_id, action_id) - return True - - LOG.error(_LE('User is already locked by action %(old)s, ' - 'action %(new)s failed grabbing the lock'), - {'old': owner, 'new': action_id}) - - return False - - -def user_lock_release(user_id, action_id): - """Release the lock on the specified user. - - :param user_id: ID of the user to be released. - :param action_id: ID of the action which locked the user. - """ - return db_api.user_lock_release(user_id, action_id) diff --git a/bilean/engine/parser.py b/bilean/engine/parser.py deleted file mode 100644 index a4fc37d..0000000 --- a/bilean/engine/parser.py +++ /dev/null @@ -1,82 +0,0 @@ -# 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 oslo_serialization import jsonutils -import six -from six.moves import urllib -import yaml - -from bilean.common.i18n import _ - -# Try LibYAML if available -if hasattr(yaml, 'CSafeLoader'): - Loader = yaml.CSafeLoader -else: - Loader = yaml.SafeLoader - -if hasattr(yaml, 'CSafeDumper'): - Dumper = yaml.CSafeDumper -else: - Dumper = yaml.SafeDumper - - -class YamlLoader(Loader): - def normalise_file_path_to_url(self, path): - if urllib.parse.urlparse(path).scheme: - return path - path = os.path.abspath(path) - return urllib.parse.urljoin('file:', - urllib.request.pathname2url(path)) - - def include(self, node): - try: - url = self.normalise_file_path_to_url(self.construct_scalar(node)) - tmpl = urllib.request.urlopen(url).read() - return yaml.load(tmpl, Loader) - except urllib.error.URLError as ex: - raise IOError('Failed retrieving file %s: %s' % - (url, six.text_type(ex))) - - def process_unicode(self, node): - # Override the default string handling function to always return - # unicode objects - return self.construct_scalar(node) - - -YamlLoader.add_constructor('!include', YamlLoader.include) -YamlLoader.add_constructor(u'tag:yaml.org,2002:str', - YamlLoader.process_unicode) -YamlLoader.add_constructor(u'tag:yaml.org,2002:timestamp', - YamlLoader.process_unicode) - - -def simple_parse(in_str): - try: - out_dict = jsonutils.loads(in_str) - except ValueError: - try: - out_dict = yaml.load(in_str, Loader=YamlLoader) - except yaml.YAMLError as yea: - yea = six.text_type(yea) - msg = _('Error parsing input: %s') % yea - raise ValueError(msg) - else: - if out_dict is None: - out_dict = {} - - if not isinstance(out_dict, dict): - msg = _('The input is not a JSON object or YAML mapping.') - raise ValueError(msg) - - return out_dict diff --git a/bilean/engine/policy.py b/bilean/engine/policy.py deleted file mode 100644 index 6bb57d9..0000000 --- a/bilean/engine/policy.py +++ /dev/null @@ -1,137 +0,0 @@ -# -# 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 bilean.common import exception -from bilean.common import utils -from bilean.db import api as db_api -from bilean.plugins import base as plugin_base - - -class Policy(object): - """Policy object contains all policy operations""" - - def __init__(self, name, **kwargs): - self.name = name - - self.id = kwargs.get('id') - self.is_default = kwargs.get('is_default', False) - # rules schema like [{'id': 'xxx', 'type': 'os.nova.server'}] - self.rules = kwargs.get('rules', []) - self.metadata = kwargs.get('metadata') - - self.created_at = kwargs.get('created_at') - self.updated_at = kwargs.get('updated_at') - self.deleted_at = kwargs.get('deleted_at') - - def store(self, context): - """Store the policy record into database table.""" - - values = { - 'name': self.name, - 'rules': self.rules, - 'is_default': self.is_default, - 'meta_data': self.metadata, - 'created_at': self.created_at, - 'updated_at': self.updated_at, - 'deleted_at': self.deleted_at, - } - - if self.id: - db_api.policy_update(context, self.id, values) - else: - policy = db_api.policy_create(context, values) - self.id = policy.id - - return self.id - - @classmethod - def _from_db_record(cls, record): - '''Construct a policy object from database record. - - :param record: a DB policy object that contains all fields; - ''' - kwargs = { - 'id': record.id, - 'rules': record.rules, - 'is_default': record.is_default, - 'metadata': record.meta_data, - 'created_at': record.created_at, - 'updated_at': record.updated_at, - 'deleted_at': record.deleted_at, - } - - return cls(record.name, **kwargs) - - @classmethod - def load(cls, context, policy_id=None, policy=None, show_deleted=False): - '''Retrieve a policy from database.''' - if policy is None: - policy = db_api.policy_get(context, policy_id, - show_deleted=show_deleted) - if policy is None: - raise exception.PolicyNotFound(policy=policy_id) - - return cls._from_db_record(policy) - - @classmethod - def load_default(cls, context, show_deleted=False): - '''Retrieve default policy from database.''' - filters = {'is_default': True} - policies = cls.load_all(context, filters=filters, - show_deleted=show_deleted) - if len(policies) > 1: - raise exception.MultipleDefaultPolicy() - - policy = None if len(policies) < 1 else policies[0] - return policy - - @classmethod - def load_all(cls, context, limit=None, marker=None, - sort_keys=None, sort_dir=None, - filters=None, show_deleted=False): - '''Retrieve all policies of from database.''' - - records = db_api.policy_get_all(context, - limit=limit, marker=marker, - sort_keys=sort_keys, - sort_dir=sort_dir, - filters=filters, - show_deleted=show_deleted) - - return [cls._from_db_record(record) for record in records] - - def to_dict(self): - policy_dict = { - 'id': self.id, - 'name': self.name, - 'rules': self.rules, - 'is_default': self.is_default, - 'metadata': self.metadata, - 'created_at': utils.format_time(self.created_at), - 'updated_at': utils.format_time(self.updated_at), - 'deleted_at': utils.format_time(self.deleted_at), - } - return policy_dict - - def do_delete(self, context): - db_api.policy_delete(context, self.id) - return True - - def find_rule(self, context, rtype): - '''Find the exact rule from self.rules by rtype''' - - for rule in self.rules: - if rtype == rule['type'].split('-')[0]: - return plugin_base.Rule.load(context, rule_id=rule['id']) - - raise exception.RuleNotFound(rule_type=rtype) diff --git a/bilean/engine/registry.py b/bilean/engine/registry.py deleted file mode 100644 index 27a92f1..0000000 --- a/bilean/engine/registry.py +++ /dev/null @@ -1,138 +0,0 @@ -# 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 itertools -import six - -from oslo_log import log as logging - -from bilean.common.i18n import _LI, _LW - -LOG = logging.getLogger(__name__) - - -class PluginInfo(object): - '''Base mapping of plugin type to implementation.''' - - def __new__(cls, registry, name, plugin, **kwargs): - '''Create a new PluginInfo of the appropriate class. - - Placeholder for class hierarchy extensibility - ''' - return super(PluginInfo, cls).__new__(cls) - - def __init__(self, registry, name, plugin): - self.registry = registry - self.name = name - self.plugin = plugin - self.user_provided = True - - def __eq__(self, other): - if other is None: - return False - return (self.name == other.name and - self.plugin == other.plugin and - self.user_provided == other.user_provided) - - def __ne__(self, other): - return not self.__eq__(other) - - def __lt__(self, other): - if self.user_provided != other.user_provided: - # user provided ones must be sorted above system ones. - return self.user_provided > other.user_provided - if len(self.name) != len(other.name): - # more specific (longer) name must be sorted above system ones. - return len(self.name) > len(other.name) - return self.name < other.name - - def __gt__(self, other): - return other.__lt__(self) - - def __str__(self): - return '[Plugin](User:%s) %s -> %s' % (self.user_provided, - self.name, str(self.plugin)) - - -class Registry(object): - '''A registry for managing rule classes.''' - - def __init__(self, registry_name, global_registry=None): - self.registry_name = registry_name - self._registry = {} - self.is_global = False if global_registry else True - self.global_registry = global_registry - - def _register_info(self, name, info): - '''place the new info in the correct location in the registry. - - :param path: a string of plugin name. - :param info: reference to a PluginInfo data structure, deregister a - PluginInfo if specified as None. - ''' - registry = self._registry - if info is None: - # delete this entry. - LOG.warn(_LW('Removing %(item)s from registry'), {'item': name}) - registry.pop(name, None) - return - - if name in registry and isinstance(registry[name], PluginInfo): - if registry[name] == info: - return - details = { - 'name': name, - 'old': str(registry[name].plugin), - 'new': str(info.plugin) - } - LOG.warn(_LW('Changing %(name)s from %(old)s to %(new)s'), details) - else: - LOG.info(_LI('Registering %(name)s -> %(value)s'), { - 'name': name, 'value': str(info.plugin)}) - - info.user_provided = not self.is_global - registry[name] = info - - def register_plugin(self, name, plugin): - pi = PluginInfo(self, name, plugin) - self._register_info(name, pi) - - def load(self, json_snippet): - for k, v in iter(json_snippet.items()): - if v is None: - self._register_info(k, None) - else: - self.register_plugin(k, v) - - def iterable_by(self, name): - plugin = self._registry.get(name) - if plugin: - yield plugin - - def get_plugin(self, name): - giter = [] - if not self.is_global: - giter = self.global_registry.iterable_by(name) - - matches = itertools.chain(self.iterable_by(name), giter) - infoes = sorted(matches) - return infoes[0].plugin if infoes else None - - def get_plugins(self): - return [p.plugin for p in six.itervalues(self._registry)] - - def as_dict(self): - return dict((k, v.plugin) for k, v in self._registry.items()) - - def get_types(self): - '''Return a list of valid plugin types.''' - return [{'name': name} for name in six.iterkeys(self._registry)] diff --git a/bilean/engine/service.py b/bilean/engine/service.py deleted file mode 100644 index e8823ff..0000000 --- a/bilean/engine/service.py +++ /dev/null @@ -1,785 +0,0 @@ -# -# 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 functools -import six -import time - -import eventlet -from oslo_config import cfg -from oslo_log import log as logging -import oslo_messaging -from oslo_service import service -from oslo_service import threadgroup -from oslo_utils import timeutils - -from bilean.common import consts -from bilean.common import context as bilean_context -from bilean.common import exception -from bilean.common.i18n import _, _LE, _LI, _LW -from bilean.common import messaging as rpc_messaging -from bilean.common import schema -from bilean.common import utils -from bilean.db import api as db_api -from bilean.engine.actions import base as action_mod -from bilean.engine import consumption as cons_mod -from bilean.engine import dispatcher -from bilean.engine import environment -from bilean.engine import event as event_mod -from bilean.engine import policy as policy_mod -from bilean.engine import user as user_mod -from bilean.plugins import base as plugin_base -from bilean import scheduler as bilean_scheduler - -LOG = logging.getLogger(__name__) - - -def request_context(func): - @functools.wraps(func) - def wrapped(self, ctx, *args, **kwargs): - if ctx is not None and not isinstance(ctx, - bilean_context.RequestContext): - ctx = bilean_context.RequestContext.from_dict(ctx.to_dict()) - try: - return func(self, ctx, *args, **kwargs) - except exception.BileanException: - raise oslo_messaging.rpc.dispatcher.ExpectedException() - return wrapped - - -class ThreadGroupManager(object): - '''Thread group manager.''' - - def __init__(self): - super(ThreadGroupManager, self).__init__() - self.workers = {} - self.group = threadgroup.ThreadGroup() - - # Create dummy service task, because when there is nothing queued - # on self.tg the process exits - self.add_timer(cfg.CONF.periodic_interval, self._service_task) - - self.db_session = bilean_context.get_admin_context() - - def _service_task(self): - '''Dummy task which gets queued on the service.Service threadgroup. - - Without this service.Service sees nothing running i.e has nothing to - wait() on, so the process exits. - - ''' - pass - - def start(self, func, *args, **kwargs): - '''Run the given method in a thread.''' - - return self.group.add_thread(func, *args, **kwargs) - - def start_action(self, worker_id, action_id=None): - '''Run the given action in a sub-thread. - - Release the action lock when the thread finishes? - - :param workder_id: ID of the worker thread; we fake workers using - bilean engines at the moment. - :param action_id: ID of the action to be executed. None means the - 1st ready action will be scheduled to run. - ''' - def release(thread, action_id): - '''Callback function that will be passed to GreenThread.link().''' - # Remove action thread from thread list - self.workers.pop(action_id) - - timestamp = time.time() - if action_id is not None: - action = db_api.action_acquire(self.db_session, action_id, - worker_id, timestamp) - else: - action = db_api.action_acquire_first_ready(self.db_session, - worker_id, - timestamp) - if not action: - return - - th = self.start(action_mod.ActionProc, self.db_session, action.id) - self.workers[action.id] = th - th.link(release, action.id) - return th - - def cancel_action(self, action_id): - '''Cancel an action execution progress.''' - action = action_mod.Action.load(self.db_session, action_id) - action.signal(action.SIG_CANCEL) - - def suspend_action(self, action_id): - '''Suspend an action execution progress.''' - action = action_mod.Action.load(self.db_session, action_id) - action.signal(action.SIG_SUSPEND) - - def resume_action(self, action_id): - '''Resume an action execution progress.''' - action = action_mod.Action.load(self.db_session, action_id) - action.signal(action.SIG_RESUME) - - def add_timer(self, interval, func, *args, **kwargs): - '''Define a periodic task to be run in the thread group. - - The task will be executed in a separate green thread. - Interval is from cfg.CONF.periodic_interval - ''' - - self.group.add_timer(interval, func, *args, **kwargs) - - def stop_timers(self): - self.group.stop_timers() - - def stop(self, graceful=False): - '''Stop any active threads belong to this threadgroup.''' - # Try to stop all threads gracefully - self.group.stop(graceful) - self.group.wait() - - # Wait for link()ed functions (i.e. lock release) - threads = self.group.threads[:] - links_done = dict((th, False) for th in threads) - - def mark_done(gt, th): - links_done[th] = True - - for th in threads: - th.link(mark_done, th) - - while not all(links_done.values()): - eventlet.sleep() - - -class EngineService(service.Service): - """Manages the running instances from creation to destruction. - - All the methods in here are called from the RPC backend. This is - all done dynamically so if a call is made via RPC that does not - have a corresponding method here, an exception will be thrown when - it attempts to call into this class. Arguments to these methods - are also dynamically added and will be named as keyword arguments - by the RPC caller. - """ - - def __init__(self, host, topic, manager=None, context=None): - super(EngineService, self).__init__() - self.host = host - self.topic = topic - self.dispatcher_topic = consts.ENGINE_DISPATCHER_TOPIC - - self.engine_id = None - self.TG = None - self.target = None - self._rpc_server = None - - def _init_service(self): - admin_context = bilean_context.get_admin_context() - srv = db_api.service_get_by_host_and_binary(admin_context, - self.host, - 'bilean-engine') - if srv is None: - srv = db_api.service_create(admin_context, - host=self.host, - binary='bilean-engine', - topic=self.topic) - self.engine_id = srv.id - - def start(self): - self._init_service() - self.TG = ThreadGroupManager() - - # create a dispatcher RPC service for this engine. - self.dispatcher = dispatcher.Dispatcher(self, - self.dispatcher_topic, - consts.RPC_API_VERSION, - self.TG) - LOG.info(_LI("Starting dispatcher for engine %s"), self.engine_id) - self.dispatcher.start() - - LOG.info(_LI("Starting rpc server for engine: %s"), self.engine_id) - target = oslo_messaging.Target(version=consts.RPC_API_VERSION, - server=self.host, - topic=self.topic) - self.target = target - self._rpc_server = rpc_messaging.get_rpc_server(target, self) - self._rpc_server.start() - - self.TG.add_timer(cfg.CONF.periodic_interval, - self.service_manage_report) - - super(EngineService, self).start() - - def _stop_rpc_server(self): - # Stop RPC connection to prevent new requests - LOG.info(_LI("Stopping engine service...")) - try: - self._rpc_server.stop() - self._rpc_server.wait() - LOG.info(_LI('Engine service stopped successfully')) - except Exception as ex: - LOG.error(_LE('Failed to stop engine service: %s'), - six.text_type(ex)) - - def stop(self): - self._stop_rpc_server() - - # Notify dispatcher to stop all action threads it started. - LOG.info(_LI("Stopping dispatcher for engine %s"), self.engine_id) - self.dispatcher.stop() - - self.TG.stop() - super(EngineService, self).stop() - - def service_manage_report(self): - admin_context = bilean_context.get_admin_context() - try: - db_api.service_update(admin_context, self.engine_id) - except Exception as ex: - LOG.error(_LE('Service %(id)s update failed: %(error)s'), - {'id': self.engine_id, 'error': six.text_type(ex)}) - - @request_context - def user_list(self, cnxt, show_deleted=False, limit=None, - marker=None, sort_keys=None, sort_dir=None, - filters=None): - limit = utils.parse_int_param('limit', limit) - show_deleted = utils.parse_bool_param('show_deleted', show_deleted) - - users = user_mod.User.load_all(cnxt, show_deleted=show_deleted, - limit=limit, marker=marker, - sort_keys=sort_keys, sort_dir=sort_dir, - filters=filters) - - return [user.to_dict() for user in users] - - def user_create(self, cnxt, user_id, balance=None, credit=None, - status=None): - """Create a new user from notification.""" - if status is None: - status = consts.USER_INIT - user = user_mod.User(user_id, balance=balance, credit=credit, - status=status) - user.store(cnxt) - - return user.to_dict() - - @request_context - def user_get(self, cnxt, user_id): - """Show detailed info about a specify user. - - Realtime balance would be return. - """ - user = user_mod.User.load(cnxt, user_id=user_id, realtime=True) - return user.to_dict() - - @request_context - def user_recharge(self, cnxt, user_id, value, recharge_type=None, - timestamp=None, metadata=None): - """Do recharge for specify user.""" - try: - user = user_mod.User.load(cnxt, user_id=user_id) - except exception.UserNotFound as ex: - raise exception.BileanBadRequest(msg=six.text_type(ex)) - - recharge_type = recharge_type or consts.SELF_RECHARGE - timestamp = timestamp or timeutils.utcnow() - metadata = metadata or {} - user.do_recharge(cnxt, value, recharge_type=recharge_type, - timestamp=timestamp, metadata=metadata) - # As user has been updated, the billing job for the user - # should to be updated too. - bilean_scheduler.notify(bilean_scheduler.UPDATE_JOBS, - user=user.to_dict()) - - return user.to_dict() - - def user_delete(self, cnxt, user_id): - """Delete a specify user according to the notification.""" - LOG.info(_LI('Deleging user: %s'), user_id) - user = user_mod.User.load(cnxt, user_id=user_id) - if user.status in [user.ACTIVE, user.WARNING]: - LOG.error(_LE("User (%s) is in use, can not delete."), user_id) - return - user_mod.User.delete(cnxt, user_id=user_id) - bilean_scheduler.notify(bilean_scheduler.DELETE_JOBS, - user=user.to_dict()) - - @request_context - def user_attach_policy(self, cnxt, user_id, policy_id): - """Attach specified policy to user.""" - LOG.info(_LI("Attaching policy %(policy)s to user %(user)s."), - {'policy': policy_id, 'user': user_id}) - user = user_mod.User.load(cnxt, user_id=user_id) - if user.policy_id is not None: - msg = _("User %(user)s is using policy %(now_policy)s, can not " - "attach %(policy)s.") % {'user': user_id, - 'now_policy': user.policy_id, - 'policy': policy_id} - raise exception.BileanBadRequest(msg=msg) - - user.policy_id = policy_id - user.store(cnxt) - return user.to_dict() - - @request_context - def rule_create(self, cnxt, name, spec, metadata=None): - if len(plugin_base.Rule.load_all(cnxt, filters={'name': name})) > 0: - msg = _("The rule (%(name)s) already exists." - ) % {"name": name} - raise exception.BileanBadRequest(msg=msg) - - type_name, version = schema.get_spec_version(spec) - try: - plugin = environment.global_env().get_plugin(type_name) - except exception.RuleTypeNotFound: - msg = _("The specified rule type (%(type)s) is not supported." - ) % {"type": type_name} - raise exception.BileanBadRequest(msg=msg) - - LOG.info(_LI("Creating rule type: %(type)s, name: %(name)s."), - {'type': type_name, 'name': name}) - rule = plugin.RuleClass(name, spec, metadata=metadata) - try: - rule.validate() - except exception.InvalidSpec as ex: - msg = six.text_type(ex) - LOG.error(_LE("Failed in creating rule: %s"), msg) - raise exception.BileanBadRequest(msg=msg) - - rule.store(cnxt) - LOG.info(_LI("Rule %(name)s is created: %(id)s."), - {'name': name, 'id': rule.id}) - return rule.to_dict() - - @request_context - def rule_list(self, cnxt, limit=None, marker=None, sort_keys=None, - sort_dir=None, filters=None, show_deleted=False): - if limit is not None: - limit = utils.parse_int_param('limit', limit) - if show_deleted is not None: - show_deleted = utils.parse_bool_param('show_deleted', - show_deleted) - rules = plugin_base.Rule.load_all(cnxt, limit=limit, - marker=marker, - sort_keys=sort_keys, - sort_dir=sort_dir, - filters=filters, - show_deleted=show_deleted) - - return [rule.to_dict() for rule in rules] - - @request_context - def rule_get(self, cnxt, rule_id): - rule = plugin_base.Rule.load(cnxt, rule_id=rule_id) - return rule.to_dict() - - @request_context - def rule_update(self, cnxt, rule_id, values): - return NotImplemented - - @request_context - def rule_delete(self, cnxt, rule_id): - LOG.info(_LI("Deleting rule: '%s'."), rule_id) - plugin_base.Rule.delete(cnxt, rule_id) - - @request_context - def validate_creation(self, cnxt, resources): - """Validate resources creation. - - If user's balance is not enough for resources to keep 1 hour, - will fail to validate. - """ - user = user_mod.User.load(cnxt, user_id=cnxt.project) - policy = policy_mod.Policy.load(cnxt, policy_id=user.policy_id) - count = resources.get('count', 1) - total_rate = 0 - for resource in resources['resources']: - rule = policy.find_rule(cnxt, resource['resource_type']) - res = plugin_base.Resource('FAKE_ID', user.id, - resource['resource_type'], - resource['properties']) - total_rate += rule.get_price(res) - if count > 1: - total_rate = total_rate * count - # Pre 1 hour bill for resources - pre_bill = total_rate * 3600 - if pre_bill > user.balance: - return dict(validation=False) - return dict(validation=True) - - def resource_create(self, cnxt, resource_id, user_id, resource_type, - properties): - """Create resource by given data.""" - - resource = plugin_base.Resource(resource_id, user_id, resource_type, - properties) - - params = { - 'name': 'create_resource_%s' % resource_id, - 'cause': action_mod.CAUSE_RPC, - 'status': action_mod.Action.READY, - 'inputs': resource.to_dict(), - } - - action_id = action_mod.Action.create(cnxt, user_id, - consts.USER_CREATE_RESOURCE, - **params) - dispatcher.start_action(action_id=action_id) - LOG.info(_LI('Resource create action queued: %s'), action_id) - - @request_context - def resource_list(self, cnxt, user_id=None, limit=None, marker=None, - sort_keys=None, sort_dir=None, filters=None, - project_safe=True, show_deleted=False): - if limit is not None: - limit = utils.parse_int_param('limit', limit) - if show_deleted is not None: - show_deleted = utils.parse_bool_param('show_deleted', - show_deleted) - resources = plugin_base.Resource.load_all(cnxt, user_id=user_id, - limit=limit, marker=marker, - sort_keys=sort_keys, - sort_dir=sort_dir, - filters=filters, - project_safe=project_safe, - show_deleted=show_deleted) - return [r.to_dict() for r in resources] - - @request_context - def resource_get(self, cnxt, resource_id): - resource = plugin_base.Resource.load(cnxt, resource_id=resource_id) - return resource.to_dict() - - def resource_update(self, cnxt, user_id, resource): - """Do resource update.""" - - params = { - 'name': 'update_resource_%s' % resource.get('id'), - 'cause': action_mod.CAUSE_RPC, - 'status': action_mod.Action.READY, - 'inputs': resource, - } - - action_id = action_mod.Action.create(cnxt, user_id, - consts.USER_UPDATE_RESOURCE, - **params) - dispatcher.start_action(action_id=action_id) - LOG.info(_LI('Resource update action queued: %s'), action_id) - - def resource_delete(self, cnxt, user_id, resource_id): - """Delete a specific resource""" - - try: - plugin_base.Resource.load(cnxt, resource_id=resource_id) - except exception.ResourceNotFound: - LOG.warn(_LW('The resource(%s) trying to delete not found.'), - resource_id) - return - - params = { - 'name': 'delete_resource_%s' % resource_id, - 'cause': action_mod.CAUSE_RPC, - 'status': action_mod.Action.READY, - 'inputs': {'resource_id': resource_id}, - } - - action_id = action_mod.Action.create(cnxt, user_id, - consts.USER_DELETE_RESOURCE, - **params) - dispatcher.start_action(action_id=action_id) - LOG.info(_LI('Resource delete action queued: %s'), action_id) - - @request_context - def event_list(self, cnxt, user_id=None, limit=None, marker=None, - sort_keys=None, sort_dir=None, filters=None, - start_time=None, end_time=None, project_safe=True, - show_deleted=False): - if limit is not None: - limit = utils.parse_int_param('limit', limit) - if show_deleted is not None: - show_deleted = utils.parse_bool_param('show_deleted', - show_deleted) - - events = event_mod.Event.load_all(cnxt, user_id=user_id, - limit=limit, marker=marker, - sort_keys=sort_keys, - sort_dir=sort_dir, - filters=filters, - start_time=start_time, - end_time=end_time, - project_safe=project_safe, - show_deleted=show_deleted) - return [e.to_dict() for e in events] - - @request_context - def policy_create(self, cnxt, name, rule_ids=None, metadata=None): - """Create a new policy.""" - if len(policy_mod.Policy.load_all(cnxt, filters={'name': name})) > 0: - msg = _("The policy (%(name)s) already exists." - ) % {"name": name} - raise exception.BileanBadRequest(msg=msg) - - rules = [] - if rule_ids is not None: - type_cache = [] - for rule_id in rule_ids: - try: - rule = plugin_base.Rule.load(cnxt, rule_id=rule_id) - if rule.type not in type_cache: - rules.append({'id': rule_id, 'type': rule.type}) - type_cache.append(rule.type) - else: - msg = _("More than one rule in type: '%s', it's " - "not allowed.") % rule.type - raise exception.BileanBadRequest(msg=msg) - except exception.RuleNotFound as ex: - raise exception.BileanBadRequest(msg=six.text_type(ex)) - - kwargs = { - 'rules': rules, - 'metadata': metadata, - } - policy = policy_mod.Policy(name, **kwargs) - if not policy.is_default: - default_policy = policy_mod.Policy.load_default(cnxt) - if default_policy is None: - policy.is_default = True - policy.store(cnxt) - LOG.info(_LI("Successfully create policy (%s)."), policy.id) - return policy.to_dict() - - @request_context - def policy_list(self, cnxt, limit=None, marker=None, sort_keys=None, - sort_dir=None, filters=None, show_deleted=False): - if limit is not None: - limit = utils.parse_int_param('limit', limit) - if show_deleted is not None: - show_deleted = utils.parse_bool_param('show_deleted', - show_deleted) - policies = policy_mod.Policy.load_all(cnxt, limit=limit, - marker=marker, - sort_keys=sort_keys, - sort_dir=sort_dir, - filters=filters, - show_deleted=show_deleted) - - return [policy.to_dict() for policy in policies] - - @request_context - def policy_get(self, cnxt, policy_id): - policy = policy_mod.Policy.load(cnxt, policy_id=policy_id) - return policy.to_dict() - - @request_context - def policy_update(self, cnxt, policy_id, name=None, metadata=None, - is_default=None): - LOG.info(_LI("Updating policy: '%(id)s'"), {'id': policy_id}) - - policy = policy_mod.Policy.load(cnxt, policy_id=policy_id) - changed = False - if name is not None and name != policy.name: - policies = policy_mod.Policy.load_all(cnxt, filters={'name': name}) - if len(policies) > 0: - msg = _("The policy (%(name)s) already exists." - ) % {"name": name} - raise exception.BileanBadRequest(msg=msg) - policy.name = name - changed = True - if metadata is not None and metadata != policy.metadata: - policy.metadata = metadata - changed = True - if is_default is not None and is_default != policy.is_default: - is_default = utils.parse_bool_param('is_default', is_default) - if is_default: - # Set policy to default should unset old default policy. - policies = policy_mod.load_all(cnxt, - filters={'is_default': True}) - if len(policies) == 1: - default_policy = policies[0] - default_policy.is_default = False - default_policy.store(cnxt) - policy.is_default = is_default - changed = True - - if changed: - policy.store(cnxt) - - LOG.info(_LI("Policy '%(id)s' is updated."), {'id': policy_id}) - return policy.to_dict() - - @request_context - def policy_add_rules(self, cnxt, policy_id, rules): - - LOG.info(_LI("Adding rules '%(rules)s' to policy '%(policy)s'."), - {'policy': policy_id, 'rules': rules}) - policy = policy_mod.Policy.load(cnxt, policy_id=policy_id) - exist_types = [r['type'] for r in policy.rules] - - error_rules = [] - ok_rules = [] - not_found = [] - for rule in rules: - try: - db_rule = plugin_base.Rule.load(cnxt, rule_id=rule) - append_data = {'id': db_rule.id, 'type': db_rule.type} - if db_rule.type in exist_types: - error_rules.append(append_data) - else: - ok_rules.append(append_data) - except exception.RuleNotFound: - not_found.append(rule) - pass - - error = None - if len(error_rules) > 0: - error = _("Rule types of rules %(rules)s exist in policy " - "%(policy)s.") % {'rules': error_rules, - 'policy': policy_id} - if len(not_found) > 0: - error = _("Rules not found: %s") % not_found - - if error is not None: - LOG.error(error) - raise exception.BileanBadRequest(msg=error) - - policy.rules += ok_rules - policy.store(cnxt) - return policy.to_dict() - - @request_context - def policy_remove_rule(self, cnxt, policy_id, rule_ids): - return NotImplemented - - @request_context - def policy_delete(self, cnxt, policy_id): - LOG.info(_LI("Deleting policy: '%s'."), policy_id) - policy_mod.Policy.delete(cnxt, policy_id) - - def settle_account(self, cnxt, user_id, task=None): - params = { - 'name': 'settle_account_%s' % user_id, - 'cause': action_mod.CAUSE_RPC, - 'status': action_mod.Action.READY, - 'inputs': {'task': task}, - } - - action_id = action_mod.Action.create(cnxt, user_id, - consts.USER_SETTLE_ACCOUNT, - **params) - self.TG.start_action(self.engine_id, action_id=action_id) - - LOG.info(_LI('User settle_account action queued: %s'), action_id) - - @request_context - def consumption_list(self, cnxt, user_id=None, limit=None, - marker=None, sort_keys=None, sort_dir=None, - filters=None, project_safe=True): - user_id = user_id or cnxt.project - if limit is not None: - limit = utils.parse_int_param('limit', limit) - - consumptions = cons_mod.Consumption.load_all(cnxt, - user_id=user_id, - limit=limit, - marker=marker, - sort_keys=sort_keys, - sort_dir=sort_dir, - filters=filters, - project_safe=project_safe) - return [c.to_dict() for c in consumptions] - - @request_context - def consumption_statistics(self, cnxt, user_id=None, filters=None, - start_time=None, end_time=None, summary=False, - project_safe=True): - user_id = user_id or cnxt.project - result = {} - if start_time is None: - start_time = 0 - else: - start_time = utils.format_time_to_seconds(start_time) - start_time = utils.make_decimal(start_time) - - now_time = utils.format_time_to_seconds(timeutils.utcnow()) - now_time = utils.make_decimal(now_time) - if end_time is None: - end_time = now_time - else: - end_time = utils.format_time_to_seconds(end_time) - end_time = utils.make_decimal(end_time) - - consumptions = cons_mod.Consumption.load_all(cnxt, user_id=user_id, - filters=filters, - project_safe=project_safe) - for cons in consumptions: - if cons.start_time > end_time or cons.end_time < start_time: - continue - et = min(cons.end_time, end_time) - st = max(cons.start_time, start_time) - seconds = et - st - cost = cons.rate * seconds - if summary: - if cons.resource_type not in result: - result[cons.resource_type] = cost - else: - result[cons.resource_type] += cost - else: - if cons.resource_id not in result: - tmp = {'resource_type': cons.resource_type, - 'cost': cost} - result[cons.resource_id] = tmp - else: - result[cons.resource_id]['cost'] += cost - - resources = plugin_base.Resource.load_all(cnxt, user_id=user_id, - filters=filters, - project_safe=project_safe) - for res in resources: - if res.last_bill > end_time or now_time < start_time: - continue - et = min(now_time, end_time) - st = max(res.last_bill, start_time) - seconds = et - st - cost = res.rate * seconds - if summary: - if res.resource_type not in result: - result[res.resource_type] = cost - else: - result[res.resource_type] += cost - else: - if res.id not in result: - tmp = {'resource_type': res.resource_type, - 'cost': cost} - result[res.id] = tmp - else: - result[res.id]['cost'] += cost - - if summary: - for key in six.iterkeys(result): - result[key] = utils.dec2str(result[key]) - return result - else: - consumptions = [] - for key in six.iterkeys(result): - consumption = cons_mod.Consumption( - user_id, - resource_id=key, - resource_type=result[key]['resource_type'], - cost=result[key]['cost'], - start_time=start_time, - end_time=end_time) - consumptions.append(consumption.to_dict()) - return consumptions diff --git a/bilean/engine/user.py b/bilean/engine/user.py deleted file mode 100644 index 5a5c803..0000000 --- a/bilean/engine/user.py +++ /dev/null @@ -1,336 +0,0 @@ -# -# 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 six -import time - -from bilean.common import exception -from bilean.common.i18n import _, _LI -from bilean.common import utils -from bilean.db import api as db_api -from bilean.drivers import base as driver_base -from bilean import notifier as bilean_notifier -from bilean.plugins import base as plugin_base - -from oslo_config import cfg -from oslo_log import log as logging - -wallclock = time.time -LOG = logging.getLogger(__name__) - - -class User(object): - """User object contains all user operations""" - - statuses = ( - INIT, FREE, ACTIVE, WARNING, FREEZE, - ) = ( - 'INIT', 'FREE', 'ACTIVE', 'WARNING', 'FREEZE', - ) - - def __init__(self, user_id, **kwargs): - self.id = user_id - self.name = kwargs.get('name') - self.policy_id = kwargs.get('policy_id') - self.balance = utils.make_decimal(kwargs.get('balance', 0)) - self.rate = utils.make_decimal(kwargs.get('rate', 0)) - self.credit = kwargs.get('credit', 0) - self.last_bill = utils.make_decimal(kwargs.get('last_bill', 0)) - - self.status = kwargs.get('status', self.INIT) - self.status_reason = kwargs.get('status_reason', 'Init user') - - self.created_at = kwargs.get('created_at') - self.updated_at = kwargs.get('updated_at') - self.deleted_at = kwargs.get('deleted_at') - - if self.name is None: - self.name = self._retrieve_name(self.id) - - def store(self, context): - """Store the user record into database table.""" - - values = { - 'name': self.name, - 'policy_id': self.policy_id, - 'balance': utils.format_decimal(self.balance), - 'rate': utils.format_decimal(self.rate), - 'credit': self.credit, - 'last_bill': utils.format_decimal(self.last_bill), - 'status': self.status, - 'status_reason': self.status_reason, - 'created_at': self.created_at, - 'updated_at': self.updated_at, - 'deleted_at': self.deleted_at, - } - - if self.created_at: - db_api.user_update(context, self.id, values) - else: - values.update(id=self.id) - user = db_api.user_create(context, values) - self.created_at = user.created_at - - return self.id - - @classmethod - def init_users(cls, context): - """Init users from keystone.""" - keystoneclient = driver_base.BileanDriver().identity() - try: - projects = keystoneclient.project_list() - except exception.InternalError as ex: - LOG.exception(_('Failed in retrieving project list: %s'), - six.text_type(ex)) - return False - - users = cls.load_all(context) - user_ids = [user.id for user in users] - for project in projects: - if project.id not in user_ids: - user = cls(project.id, name=project.name, status=cls.INIT, - status_reason='Init from keystone') - user.store(context) - users.append(user) - return users - - def _retrieve_name(cls, user_id): - '''Get user name form keystone.''' - keystoneclient = driver_base.BileanDriver().identity() - try: - project = keystoneclient.project_find(user_id) - except exception.InternalError as ex: - LOG.exception(_('Failed in retrieving project: %s'), - six.text_type(ex)) - return None - return project.name - - @classmethod - def _from_db_record(cls, record): - '''Construct a user object from database record. - - :param record: a DB user object that contains all fields; - ''' - kwargs = { - 'name': record.name, - 'policy_id': record.policy_id, - 'balance': record.balance, - 'rate': record.rate, - 'credit': record.credit, - 'last_bill': record.last_bill, - 'status': record.status, - 'status_reason': record.status_reason, - 'created_at': record.created_at, - 'updated_at': record.updated_at, - 'deleted_at': record.deleted_at, - } - - return cls(record.id, **kwargs) - - @classmethod - def load(cls, context, user_id=None, user=None, realtime=False, - show_deleted=False, project_safe=True): - '''Retrieve a user from database.''' - if context.is_admin: - project_safe = False - if user is None: - user = db_api.user_get(context, user_id, - show_deleted=show_deleted, - project_safe=project_safe) - if user is None: - raise exception.UserNotFound(user=user_id) - - u = cls._from_db_record(user) - if not realtime: - return u - if u.rate > 0 and u.status != u.FREEZE: - seconds = utils.make_decimal(wallclock()) - u.last_bill - u.balance -= u.rate * seconds - return u - - @classmethod - def load_all(cls, context, show_deleted=False, limit=None, - marker=None, sort_keys=None, sort_dir=None, - filters=None): - '''Retrieve all users of from database.''' - - records = db_api.user_get_all(context, show_deleted=show_deleted, - limit=limit, marker=marker, - sort_keys=sort_keys, sort_dir=sort_dir, - filters=filters) - - return [cls._from_db_record(record) for record in records] - - @classmethod - def delete(cls, context, user_id=None, user=None): - '''Delete a user from database.''' - if user is not None: - db_api.user_delete(context, user_id=user.id) - return True - elif user_id is not None: - db_api.user_delete(context, user_id=user_id) - return True - return False - - @classmethod - def from_dict(cls, values): - id = values.pop('id', None) - return cls(id, **values) - - def to_dict(self): - user_dict = { - 'id': self.id, - 'name': self.name, - 'policy_id': self.policy_id, - 'balance': utils.dec2str(self.balance), - 'rate': utils.dec2str(self.rate), - 'credit': self.credit, - 'last_bill': utils.dec2str(self.last_bill), - 'status': self.status, - 'status_reason': self.status_reason, - 'created_at': utils.format_time(self.created_at), - 'updated_at': utils.format_time(self.updated_at), - 'deleted_at': utils.format_time(self.deleted_at), - } - return user_dict - - def set_status(self, context, status, reason=None): - '''Set status of the user.''' - self.status = status - if reason: - self.status_reason = reason - self.store(context) - - def update_rate(self, context, delta_rate, timestamp=None): - """Update user's rate and update user status. - - :param context: The request context. - :param delta_rate: Delta rate to change. - :param timestamp: The time that resource action occurs. - :param delayed_cost: User's action may be delayed by some reason, - adjust balance by delayed_cost. - """ - - # Settle account before update rate - self._settle_account(context, timestamp=timestamp) - - old_rate = self.rate - new_rate = old_rate + delta_rate - if old_rate == 0 and new_rate > 0: - # Set last_bill when status change to 'ACTIVE' from 'FREE' - self.last_bill = timestamp or wallclock() - reason = _("Status change to 'ACTIVE' cause resource creation.") - self.status = self.ACTIVE - self.status_reason = reason - elif delta_rate < 0: - if new_rate == 0 and self.balance >= 0: - reason = _("Status change to 'FREE' because of resource " - "deletion.") - self.status = self.FREE - self.status_reason = reason - elif self.status == self.WARNING and not self._notify_or_not(): - reason = _("Status change from 'WARNING' to 'ACTIVE' " - "because of resource deletion.") - self.status = self.ACTIVE - self.status_reason = reason - self.rate = new_rate - self.store(context) - - def do_recharge(self, context, value, recharge_type=None, timestamp=None, - metadata=None): - """Recharge for user and update status. - - param context: The request context. - param value: Recharge value. - param recharge_type: Rechage type, 'Recharge'|'System bonus'. - param timestamp: Record when recharge action occurs. - param metadata: Some other keyword. - """ - self.balance += utils.make_decimal(value) - if self.status == self.INIT and self.balance > 0: - self.status = self.FREE - self.status_reason = "Recharged" - elif self.status == self.FREEZE and self.balance > 0: - reason = _("Status change from 'FREEZE' to 'FREE' because " - "of recharge.") - self.status = self.FREE - self.status_reason = reason - elif self.status == self.WARNING: - if not self._notify_or_not(): - reason = _("Status change from 'WARNING' to 'ACTIVE' because " - "of recharge.") - self.status = self.ACTIVE - self.status_reason = reason - self.store(context) - - # Create recharge record - values = {'user_id': self.id, - 'value': value, - 'type': recharge_type, - 'timestamp': timestamp, - 'metadata': metadata} - db_api.recharge_create(context, values) - - def _notify_or_not(self): - '''Check if user should be notified.''' - cfg.CONF.import_opt('prior_notify_time', - 'bilean.scheduler.cron_scheduler', - group='scheduler') - prior_notify_time = cfg.CONF.scheduler.prior_notify_time * 3600 - rest_usage = utils.make_decimal(prior_notify_time) * self.rate - return self.balance < rest_usage - - def do_delete(self, context): - db_api.user_delete(context, self.id) - return True - - def _settle_account(self, context, timestamp=None): - if self.rate == 0: - LOG.info(_LI("Ignore settlement action because user is in '%s' " - "status."), self.status) - return - now = timestamp or utils.make_decimal(wallclock()) - usage_seconds = now - self.last_bill - cost = self.rate * usage_seconds - self.balance -= cost - self.last_bill = now - - def settle_account(self, context, task=None): - '''Settle account for user.''' - - notifier = bilean_notifier.Notifier() - timestamp = utils.make_decimal(wallclock()) - self._settle_account(context, timestamp=timestamp) - - if task == 'notify' and self._notify_or_not(): - self.status_reason = "The balance is almost used up" - self.status = self.WARNING - # Notify user - msg = {'user': self.id, 'notification': self.status_reason} - notifier.info('billing.notify', msg) - elif task == 'freeze' and self.balance <= 0: - reason = _("Balance overdraft") - LOG.info(_LI("Freeze user %(user_id)s, reason: %(reason)s"), - {'user_id': self.id, 'reason': reason}) - resources = plugin_base.Resource.load_all( - context, user_id=self.id, project_safe=False) - for resource in resources: - resource.do_delete(context, timestamp=timestamp) - self.rate = 0 - self.status = self.FREEZE - self.status_reason = reason - # Notify user - msg = {'user': self.id, 'notification': self.status_reason} - notifier.info('billing.notify', msg) - - self.store(context) diff --git a/bilean/notification/__init__.py b/bilean/notification/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/notification/action.py b/bilean/notification/action.py deleted file mode 100644 index f273be2..0000000 --- a/bilean/notification/action.py +++ /dev/null @@ -1,108 +0,0 @@ -# -# 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 bilean.common import exception -from bilean.common.i18n import _, _LE -from bilean.rpc import client as rpc_client - -from oslo_log import log as logging - -LOG = logging.getLogger(__name__) - - -class Action(object): - def __init__(self, cnxt, action, data): - self.rpc_client = rpc_client.EngineClient() - self.cnxt = cnxt - self.action = action - self.data = data - - def execute(self): - """Wrapper of action execution.""" - action_name = self.action.lower() - method_name = "do_" + action_name - method = getattr(self, method_name, None) - if method is None: - LOG.error(_LE('Unsupported action: %s.') % self.action) - return None - return method() - - def do_create(self): - return NotImplemented - - def do_update(self): - return NotImplemented - - def do_delete(self): - return NotImplemented - - -class ResourceAction(Action): - """Notification controller for Resources.""" - - def __init__(self, cnxt, action, data): - super(ResourceAction, self).__init__(cnxt, action, data) - - self.id = data.get('resource_ref') - self.user_id = data.get('user_id') - self.resource_type = data.get('resource_type') - self.properties = {} - self._parse_and_validate() - - def _parse_and_validate(self): - for key in self.data.keys(): - if key not in ['resource_ref', 'user_id', 'resource_type']: - self.properties[key] = self.data[key] - if not self.id: - msg = _("Id of resource can not be None") - raise exception.InvalidResource(msg=msg) - if not self.user_id: - msg = _("User_id of resource can not be None") - raise exception.InvalidResource(msg=msg) - if not self.resource_type: - msg = _("Resource_type of resource can not be None") - raise exception.InvalidResource(msg=msg) - if not self.properties: - msg = _("Properties of resource can not be empty") - raise exception.InvalidResource(msg=msg) - - def do_create(self): - """Create new resource""" - return self.rpc_client.resource_create(self.cnxt, self.id, - self.user_id, - self.resource_type, - self.properties) - - def do_update(self): - """Update a resource""" - return self.rpc_client.resource_update(self.cnxt, - self.data.pop('user_id'), - self.data) - - def do_delete(self): - """Delete a resource""" - return self.rpc_client.resource_delete(self.cnxt, - self.user_id, - self.id) - - -class UserAction(Action): - """Notification controller for Users.""" - - def do_create(self): - """Create a new user""" - return self.rpc_client.user_create(self.cnxt, user_id=self.data) - - def do_delete(self): - """Delete a user""" - return self.rpc_client.delete_user(self.cnxt, user_id=self.data) diff --git a/bilean/notification/converter.py b/bilean/notification/converter.py deleted file mode 100644 index d2b24f1..0000000 --- a/bilean/notification/converter.py +++ /dev/null @@ -1,281 +0,0 @@ -# -# 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 fnmatch -import jsonpath_rw -import os -import six -import yaml - -from bilean.common.i18n import _ -from bilean.common.i18n import _LI -from oslo_config import cfg -from oslo_log import log as logging -from oslo_utils import timeutils - -resource_definition_opts = [ - cfg.StrOpt('definitions_cfg_file', - default="resource_definitions.yaml", - help="Configuration file for resource definitions." - ), - cfg.BoolOpt('drop_unmatched_notifications', - default=True, - help='Drop notifications if no resource definition matches. ' - '(Otherwise, we convert them with just the default traits)'), - -] - -resource_group = cfg.OptGroup('resource_definition') -cfg.CONF.register_group(resource_group) -cfg.CONF.register_opts(resource_definition_opts, group=resource_group) - -LOG = logging.getLogger(__name__) - - -def get_config_file(): - config_file = cfg.CONF.resource_definition.definitions_cfg_file - if not os.path.exists(config_file): - config_file = cfg.CONF.find_file(config_file) - return config_file - - -def setup_resources(): - """Setup the resource definitions from yaml config file.""" - config_file = get_config_file() - if config_file is not None: - LOG.debug(_("Resource Definitions configuration file: %s") % - config_file) - - with open(config_file) as cf: - config = cf.read() - - try: - resources_config = yaml.safe_load(config) - except yaml.YAMLError as err: - if hasattr(err, 'problem_mark'): - mark = err.problem_mark - errmg = (_("Invalid YAML syntax in Resource Definitions " - "file %(file)s at line: %(line)s, column: " - "%(column)s.") % dict(file=config_file, - line=mark.line + 1, - column=mark.column + 1)) - else: - errmg = (_("YAML error reading Resource Definitions file " - "%(file)s") % dict(file=config_file)) - LOG.error(errmg) - raise - - else: - LOG.debug(_("No Resource Definitions configuration file found!" - " Using default config.")) - resources_config = [] - - LOG.info(_LI("Resource Definitions: %s"), resources_config) - - allow_drop = cfg.CONF.resource_definition.drop_unmatched_notifications - return NotificationResourcesConverter(resources_config, - add_catchall=not allow_drop) - - -class NotificationResourcesConverter(object): - """Notification Resource Converter.""" - - def __init__(self, resources_config, add_catchall=True): - self.definitions = [ - EventDefinition(event_def) - for event_def in reversed(resources_config)] - if add_catchall and not any(d.is_catchall for d in self.definitions): - event_def = dict(event_type='*', resources={}) - self.definitions.append(EventDefinition(event_def)) - - def to_resources(self, notification_body): - event_type = notification_body['event_type'] - edef = None - for d in self.definitions: - if d.match_type(event_type): - edef = d - break - - if edef is None: - msg = (_('Dropping Notification %(type)s') - % dict(type=event_type)) - if cfg.CONF.resource_definition.drop_unmatched_notifications: - LOG.debug(msg) - else: - # If drop_unmatched_notifications is False, this should - # never happen. (mdragon) - LOG.error(msg) - return None - - return edef.to_resources(notification_body) - - -class EventDefinition(object): - - def __init__(self, definition_cfg): - self._included_types = [] - self._excluded_types = [] - self.cfg = definition_cfg - - try: - event_type = definition_cfg['event_type'] - self.resources = [ResourceDefinition(resource_def) - for resource_def in definition_cfg['resources']] - except KeyError as err: - raise EventDefinitionException( - _("Required field %s not specified") % err.args[0], self.cfg) - - if isinstance(event_type, six.string_types): - event_type = [event_type] - - for t in event_type: - if t.startswith('!'): - self._excluded_types.append(t[1:]) - else: - self._included_types.append(t) - - if self._excluded_types and not self._included_types: - self._included_types.append('*') - - def included_type(self, event_type): - for t in self._included_types: - if fnmatch.fnmatch(event_type, t): - return True - return False - - def excluded_type(self, event_type): - for t in self._excluded_types: - if fnmatch.fnmatch(event_type, t): - return True - return False - - def match_type(self, event_type): - return (self.included_type(event_type) and - not self.excluded_type(event_type)) - - @property - def is_catchall(self): - return '*' in self._included_types and not self._excluded_types - - def to_resources(self, notification_body): - resources = [] - for resource in self.resources: - resources.append(resource.to_resource(notification_body)) - return resources - - -class ResourceDefinition(object): - - DEFAULT_TRAITS = dict( - user_id=dict(type='string', fields='payload.tenant_id'), - ) - - def __init__(self, definition_cfg): - self.traits = dict() - - try: - self.resource_type = definition_cfg['resource_type'] - traits = definition_cfg['traits'] - except KeyError as err: - raise EventDefinitionException( - _("Required field %s not specified") % err.args[0], self.cfg) - - for trait_name in self.DEFAULT_TRAITS: - self.traits[trait_name] = TraitDefinition( - trait_name, - self.DEFAULT_TRAITS[trait_name]) - for trait_name in traits: - self.traits[trait_name] = TraitDefinition( - trait_name, - traits[trait_name]) - - def to_resource(self, notification_body): - traits = (self.traits[t].to_trait(notification_body) - for t in self.traits) - # Only accept non-None value traits ... - traits = [trait for trait in traits if trait is not None] - resource = {"resource_type": self.resource_type} - for trait in traits: - resource.update(trait) - if 'created_at' not in resource: - resource['created_at'] = timeutils.utcnow() - return resource - - -class TraitDefinition(object): - - def __init__(self, name, trait_cfg): - self.cfg = trait_cfg - self.name = name - - type_name = trait_cfg.get('type', 'string') - - if 'fields' not in trait_cfg: - raise EventDefinitionException( - _("Required field in trait definition not specified: " - "'%s'") % 'fields', - self.cfg) - - fields = trait_cfg['fields'] - if not isinstance(fields, six.string_types): - # NOTE(mdragon): if not a string, we assume a list. - if len(fields) == 1: - fields = fields[0] - else: - fields = '|'.join('(%s)' % path for path in fields) - try: - self.fields = jsonpath_rw.parse(fields) - except Exception as e: - raise EventDefinitionException( - _("Parse error in JSONPath specification " - "'%(jsonpath)s' for %(trait)s: %(err)s") - % dict(jsonpath=fields, trait=name, err=e), self.cfg) - self.trait_type = type_name - if self.trait_type is None: - raise EventDefinitionException( - _("Invalid trait type '%(type)s' for trait %(trait)s") - % dict(type=type_name, trait=name), self.cfg) - - def to_trait(self, notification_body): - values = [match for match in self.fields.find(notification_body) - if match.value is not None] - - value = values[0].value if values else None - - if value is None: - return None - - if self.trait_type != 'string' and value == '': - return None - - if self.trait_type is "int": - value = int(value) - elif self.trait_type is "float": - value = float(value) - elif self.trait_type is "string": - value = str(value) - return {self.name: value} - - -class EventDefinitionException(Exception): - def __init__(self, message, definition_cfg): - super(EventDefinitionException, self).__init__(message) - self.definition_cfg = definition_cfg - - def __str__(self): - return '%s %s: %s' % (self.__class__.__name__, - self.definition_cfg, self.message) - - -def list_opts(): - yield resource_group.name, resource_definition_opts diff --git a/bilean/notification/endpoint.py b/bilean/notification/endpoint.py deleted file mode 100644 index 2f48f86..0000000 --- a/bilean/notification/endpoint.py +++ /dev/null @@ -1,93 +0,0 @@ -# -# 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 bilean.common import context -from bilean.common.i18n import _, _LE, _LI -from bilean.notification import action as notify_action -from bilean.notification import converter - -from oslo_log import log as logging -import oslo_messaging - -LOG = logging.getLogger(__name__) - -KEYSTONE_EVENTS = ['identity.project.created', - 'identity.project.deleted'] - - -class EventsNotificationEndpoint(object): - def __init__(self): - self.resource_converter = converter.setup_resources() - self.cnxt = context.get_service_context(set_project_id=True) - super(EventsNotificationEndpoint, self).__init__() - - def info(self, ctxt, publisher_id, event_type, payload, metadata): - """Convert message to Billing Event. - - :param ctxt: oslo_messaging context - :param publisher_id: publisher of the notification - :param event_type: type of notification - :param payload: notification payload - :param metadata: metadata about the notification - """ - notification = dict(event_type=event_type, - payload=payload, - metadata=metadata) - LOG.debug(_("Receive notification: %s") % notification) - if event_type in KEYSTONE_EVENTS: - return self.process_identity_notification(notification) - return self.process_resource_notification(notification) - - def process_identity_notification(self, notification): - """Convert notification to user.""" - user_id = notification['payload'].get('resource_info') - if not user_id: - LOG.error(_LE("Cannot retrieve user_id from notification: %s"), - notification) - return oslo_messaging.NotificationResult.HANDLED - action = self._get_action(notification['event_type']) - if action: - act = notify_action.UserAction(self.cnxt, action, user_id) - LOG.info(_LI("Notify engine to %(action)s user: %(user)s") % - {'action': action, 'user': user_id}) - act.execute() - - return oslo_messaging.NotificationResult.HANDLED - - def process_resource_notification(self, notification): - """Convert notification to resources.""" - resources = self.resource_converter.to_resources(notification) - if not resources: - LOG.info('Ignore notification because no matched resources ' - 'found from notification.') - return oslo_messaging.NotificationResult.HANDLED - - action = self._get_action(notification['event_type']) - if action: - for resource in resources: - act = notify_action.ResourceAction( - self.cnxt, action, resource) - LOG.info(_LI("Notify engine to %(action)s resource: " - "%(resource)s") % {'action': action, - 'resource': resource}) - act.execute() - - return oslo_messaging.NotificationResult.HANDLED - - def _get_action(self, event_type): - available_actions = ['create', 'delete', 'update'] - for action in available_actions: - if action in event_type: - return action - LOG.info(_LI("Can not get action info in event_type: %s") % event_type) - return None diff --git a/bilean/notification/notification.py b/bilean/notification/notification.py deleted file mode 100644 index fcbac78..0000000 --- a/bilean/notification/notification.py +++ /dev/null @@ -1,91 +0,0 @@ -# -# 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 oslo_config import cfg -from oslo_log import log as logging -import oslo_messaging -from oslo_service import service - -from bilean.common.i18n import _LE -from bilean.common import messaging as bilean_messaging -from bilean.engine import environment -from bilean.notification import endpoint - -LOG = logging.getLogger(__name__) - -listener_opts = [ - cfg.IntOpt('workers', - default=1, - min=1, - help='Number of workers for notification service. A single ' - 'notification agent is enabled by default.'), - cfg.StrOpt('notifications_pool', - default='bilean-listener', - help='Use an oslo.messaging pool, which can be an alternative ' - 'to multiple topics. ') -] - -CONF = cfg.CONF -CONF.register_opts(listener_opts, group="listener") - - -class NotificationService(service.Service): - - def __init__(self, *args, **kwargs): - super(NotificationService, self).__init__(*args, **kwargs) - self.listeners = [] - self.topics_exchanges_set = self.topics_and_exchanges() - - def topics_and_exchanges(self): - topics_exchanges = set() - plugins = environment.global_env().get_plugins() - for plugin in plugins: - try: - topic_exchanges = plugin.get_notification_topics_exchanges() - for plugin_topic in topic_exchanges: - if isinstance(plugin_topic, basestring): - raise Exception( - _LE("Plugin %s should return a list of topic " - "exchange pairs") % plugin.__class__.__name__) - topics_exchanges.add(plugin_topic) - except Exception as e: - LOG.error(_LE("Failed to retrieve notification topic(s) " - "and exchanges from bilean plugin " - "%(ext)s: %(e)s") % - {'ext': plugin.__name__, 'e': e}) - - return topics_exchanges - - def start(self): - super(NotificationService, self).start() - transport = bilean_messaging.get_transport() - targets = [ - oslo_messaging.Target(topic=tp, exchange=eg) - for tp, eg in self.topics_exchanges_set - ] - endpoints = [endpoint.EventsNotificationEndpoint()] - listener = oslo_messaging.get_notification_listener( - transport, - targets, - endpoints, - pool=CONF.listener.notifications_pool) - - listener.start() - self.listeners.append(listener) - - # Add a dummy thread to have wait() working - self.tg.add_timer(604800, lambda: None) - - def stop(self): - map(lambda x: x.stop(), self.listeners) - super(NotificationService, self).stop() diff --git a/bilean/notifier.py b/bilean/notifier.py deleted file mode 100644 index 545fce7..0000000 --- a/bilean/notifier.py +++ /dev/null @@ -1,46 +0,0 @@ -# -# 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 oslo_config import cfg -import oslo_messaging - -notifier_opts = [ - cfg.StrOpt('default_publisher_id', default="billing.localhost", - help='Default publisher_id for outgoing notifications.'), -] - -CONF = cfg.CONF -CONF.register_opts(notifier_opts) - - -def get_transport(): - return oslo_messaging.get_transport(CONF) - - -class Notifier(object): - """Uses a notification strategy to send out messages about events.""" - - def __init__(self): - publisher_id = CONF.default_publisher_id - self._transport = get_transport() - self._notifier = oslo_messaging.Notifier( - self._transport, publisher_id=publisher_id) - - def warn(self, event_type, payload): - self._notifier.warn({}, event_type, payload) - - def info(self, event_type, payload): - self._notifier.info({}, event_type, payload) - - def error(self, event_type, payload): - self._notifier.error({}, event_type, payload) diff --git a/bilean/plugins/__init__.py b/bilean/plugins/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/plugins/base.py b/bilean/plugins/base.py deleted file mode 100644 index 73b956d..0000000 --- a/bilean/plugins/base.py +++ /dev/null @@ -1,453 +0,0 @@ -# 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 time - -from oslo_config import cfg -from oslo_utils import timeutils - -from bilean.common import exception -from bilean.common.i18n import _ -from bilean.common import schema -from bilean.common import utils -from bilean.db import api as db_api -from bilean.engine import consumption as consumption_mod -from bilean.engine import environment - -wallclock = time.time - - -resource_opts = [ - cfg.StrOpt('notifications_topic', default="notifications", - help="The default messaging notifications topic"), -] - -CONF = cfg.CONF -CONF.register_opts(resource_opts, group='resource_plugin') - - -class Plugin(object): - '''Base class for plugins.''' - - RuleClass = None - ResourceClass = None - notification_exchanges = [] - - @classmethod - def get_notification_topics_exchanges(cls): - """Returns a list of (topic,exchange), (topic,exchange)..).""" - - return [(CONF.resource_plugin.notifications_topic, exchange) - for exchange in cls.notification_exchanges] - - -class Rule(object): - '''Base class for rules.''' - - KEYS = ( - TYPE, VERSION, PROPERTIES, - ) = ( - 'type', 'version', 'properties', - ) - - spec_schema = { - TYPE: schema.String( - _('Name of the rule type.'), - required=True, - ), - VERSION: schema.String( - _('Version number of the rule type.'), - required=True, - ), - PROPERTIES: schema.Map( - _('Properties for the rule.'), - required=True, - ) - } - - properties_schema = {} - - def __new__(cls, name, spec, **kwargs): - """Create a new rule of the appropriate class. - - :param name: The name for the rule. - :param spec: A dictionary containing the spec for the rule. - :param kwargs: Keyword arguments for rule creation. - :returns: An instance of a specific sub-class of Rule. - """ - type_name, version = schema.get_spec_version(spec) - - if cls != Rule: - RuleClass = cls - else: - PluginClass = environment.global_env().get_plugin(type_name) - RuleClass = PluginClass.RuleClass - - return super(Rule, cls).__new__(RuleClass) - - def __init__(self, name, spec, **kwargs): - """Initialize a rule instance. - - :param name: A string that specifies the name for the rule. - :param spec: A dictionary containing the detailed rule spec. - :param kwargs: Keyword arguments for initializing the rule. - :returns: An instance of a specific sub-class of Rule. - """ - - type_name, version = schema.get_spec_version(spec) - - self.name = name - self.spec = spec - - self.id = kwargs.get('id') - self.type = kwargs.get('type', '%s-%s' % (type_name, version)) - - self.metadata = kwargs.get('metadata', {}) - - self.created_at = kwargs.get('created_at') - self.updated_at = kwargs.get('updated_at') - self.deleted_at = kwargs.get('deleted_at') - - self.spec_data = schema.Spec(self.spec_schema, self.spec) - self.properties = schema.Spec(self.properties_schema, - self.spec.get(self.PROPERTIES, {})) - - @classmethod - def from_db_record(cls, record): - '''Construct a rule object from database record. - - :param record: a DB Profle object that contains all required fields. - ''' - kwargs = { - 'id': record.id, - 'type': record.type, - 'metadata': record.meta_data, - 'created_at': record.created_at, - 'updated_at': record.updated_at, - 'deleted_at': record.deleted_at, - } - - return cls(record.name, record.spec, **kwargs) - - @classmethod - def load(cls, context, rule_id=None, rule=None, show_deleted=False): - '''Retrieve a rule object from database.''' - if rule is None: - rule = db_api.rule_get(context, rule_id, - show_deleted=show_deleted) - if rule is None: - raise exception.RuleNotFound(rule=rule_id) - - return cls.from_db_record(rule) - - @classmethod - def load_all(cls, context, limit=None, marker=None, sort_keys=None, - sort_dir=None, filters=None, show_deleted=False): - '''Retrieve all rules from database.''' - - records = db_api.rule_get_all(context, limit=limit, - marker=marker, - sort_keys=sort_keys, - sort_dir=sort_dir, - filters=filters, - show_deleted=show_deleted) - - return [cls.from_db_record(record) for record in records] - - @classmethod - def delete(cls, context, rule_id): - db_api.rule_delete(context, rule_id) - - def store(self, context): - '''Store the rule into database and return its ID.''' - timestamp = timeutils.utcnow() - - values = { - 'name': self.name, - 'type': self.type, - 'spec': self.spec, - 'meta_data': self.metadata, - } - - if self.id: - self.updated_at = timestamp - values['updated_at'] = timestamp - db_api.rule_update(context, self.id, values) - else: - self.created_at = timestamp - values['created_at'] = timestamp - rule = db_api.rule_create(context, values) - self.id = rule.id - - return self.id - - def validate(self): - '''Validate the schema and the data provided.''' - # general validation - self.spec_data.validate() - self.properties.validate() - - @classmethod - def get_schema(cls): - return dict((name, dict(schema)) - for name, schema in cls.properties_schema.items()) - - def get_price(self, resource): - '''For subclass to override.''' - - return NotImplemented - - def to_dict(self): - rule_dict = { - 'id': self.id, - 'name': self.name, - 'type': self.type, - 'spec': self.spec, - 'metadata': self.metadata, - 'created_at': utils.format_time(self.created_at), - 'updated_at': utils.format_time(self.updated_at), - 'deleted_at': utils.format_time(self.deleted_at), - } - return rule_dict - - @classmethod - def from_dict(cls, **kwargs): - type_name = kwargs.pop('type') - name = kwargs.pop('name') - return cls(type_name, name, kwargs) - - -class Resource(object): - """A resource is an object that refers to a physical resource. - - The resource comes from other openstack component such as nova, - cinder, neutron and so on, it can be an instance or volume or - something else. - """ - - def __new__(cls, id, user_id, res_type, properties, **kwargs): - """Create a new resource of the appropriate class. - - :param id: The resource ID comes same as the real resource. - :param user_id: The user ID the resource belongs to. - :param properties: The properties of resource. - :param dict kwargs: Other keyword arguments for the resource. - """ - if cls != Resource: - ResourceClass = cls - else: - PluginClass = environment.global_env().get_plugin(res_type) - ResourceClass = PluginClass.ResourceClass - - return super(Resource, cls).__new__(ResourceClass) - - def __init__(self, id, user_id, resource_type, properties, **kwargs): - self.id = id - self.user_id = user_id - self.resource_type = resource_type - self.properties = properties - - self.rule_id = kwargs.get('rule_id') - self.rate = utils.make_decimal(kwargs.get('rate', 0)) - self.last_bill = utils.make_decimal(kwargs.get('last_bill', 0)) - - self.created_at = kwargs.get('created_at') - self.updated_at = kwargs.get('updated_at') - self.deleted_at = kwargs.get('deleted_at') - - # Properties pass to user to help settle account, not store to db - self.delta_rate = 0 - self.consumption = None - - def store(self, context): - """Store the resource record into database table.""" - - values = { - 'user_id': self.user_id, - 'resource_type': self.resource_type, - 'properties': self.properties, - 'rule_id': self.rule_id, - 'rate': utils.format_decimal(self.rate), - 'last_bill': utils.format_decimal(self.last_bill), - 'created_at': self.created_at, - 'updated_at': self.updated_at, - 'deleted_at': self.deleted_at, - } - - if self.created_at: - self._update(context, values) - else: - values.update(id=self.id) - self._create(context, values) - - return self.id - - def delete(self, context, timestamp=None, soft_delete=True): - '''Delete resource from db.''' - self._delete(context, timestamp=timestamp, soft_delete=soft_delete) - - def _create(self, context, values): - self.delta_rate = self.rate - if self.delta_rate == 0: - resource = db_api.resource_create(context, values) - self.created_at = resource.created_at - return - - self.last_bill = utils.make_decimal(wallclock()) - create_time = self.properties.get('created_at') - if create_time is not None: - sec = utils.format_time_to_seconds(create_time) - self.last_bill = utils.make_decimal(sec) - - values.update(last_bill=utils.format_decimal(self.last_bill)) - resource = db_api.resource_create(context, values) - self.created_at = resource.created_at - - def _update(self, context, values): - if self.delta_rate == 0: - db_api.resource_update(context, self.id, values) - return - - update_time = self.properties.get('updated_at') - updated_at = utils.make_decimal(wallclock()) - if update_time is not None: - sec = utils.format_time_to_seconds(update_time) - updated_at = utils.make_decimal(sec) - - # Generate consumption between lass bill and update time - old_rate = self.rate - self.delta_rate - cost = (updated_at - self.last_bill) * old_rate - params = {'resource_id': self.id, - 'resource_type': self.resource_type, - 'start_time': self.last_bill, - 'end_time': updated_at, - 'rate': old_rate, - 'cost': cost, - 'metadata': {'cause': 'Resource update'}} - self.consumption = consumption_mod.Consumption(self.user_id, **params) - - self.last_bill = updated_at - values.update(last_bill=utils.format_decimal(updated_at)) - db_api.resource_update(context, self.id, values) - - def _delete(self, context, timestamp=None, soft_delete=True): - self.delta_rate = - self.rate - if self.delta_rate == 0: - db_api.resource_delete(context, self.id, soft_delete=soft_delete) - return - - deleted_at = timestamp or utils.make_decimal(wallclock()) - delete_time = self.properties.get('deleted_at') - if delete_time is not None: - sec = utils.format_time_to_seconds(delete_time) - deleted_at = utils.make_decimal(sec) - - # Generate consumption between lass bill and delete time - cost = (deleted_at - self.last_bill) * self.rate - params = {'resource_id': self.id, - 'resource_type': self.resource_type, - 'start_time': self.last_bill, - 'end_time': deleted_at, - 'rate': self.rate, - 'cost': cost, - 'metadata': {'cause': 'Resource deletion'}} - self.consumption = consumption_mod.Consumption(self.user_id, **params) - - self.last_bill = deleted_at - db_api.resource_delete(context, self.id, soft_delete=soft_delete) - - @classmethod - def _from_db_record(cls, record): - '''Construct a resource object from database record. - - :param record: a DB user object that contains all fields; - ''' - kwargs = { - 'rule_id': record.rule_id, - 'rate': record.rate, - 'last_bill': record.last_bill, - 'created_at': record.created_at, - 'updated_at': record.updated_at, - 'deleted_at': record.deleted_at, - } - - return cls(record.id, record.user_id, record.resource_type, - record.properties, **kwargs) - - @classmethod - def load(cls, context, resource_id=None, resource=None, - show_deleted=False, project_safe=True): - '''Retrieve a resource from database.''' - if context.is_admin: - project_safe = False - if resource is None: - resource = db_api.resource_get(context, resource_id, - show_deleted=show_deleted, - project_safe=project_safe) - if resource is None: - raise exception.ResourceNotFound(resource=resource_id) - - return cls._from_db_record(resource) - - @classmethod - def load_all(cls, context, user_id=None, show_deleted=False, - limit=None, marker=None, sort_keys=None, sort_dir=None, - filters=None, project_safe=True): - '''Retrieve all users from database.''' - - records = db_api.resource_get_all(context, user_id=user_id, - show_deleted=show_deleted, - limit=limit, marker=marker, - sort_keys=sort_keys, - sort_dir=sort_dir, - filters=filters, - project_safe=project_safe) - - return [cls._from_db_record(record) for record in records] - - @classmethod - def from_dict(cls, values): - id = values.pop('id', None) - user_id = values.pop('user_id', None) - resource_type = values.pop('resource_type', None) - properties = values.pop('properties', {}) - return cls(id, user_id, resource_type, properties, **values) - - def to_dict(self): - resource_dict = { - 'id': self.id, - 'user_id': self.user_id, - 'resource_type': self.resource_type, - 'properties': self.properties, - 'rule_id': self.rule_id, - 'rate': utils.dec2str(self.rate), - 'last_bill': utils.dec2str(self.last_bill), - 'created_at': utils.format_time(self.created_at), - 'updated_at': utils.format_time(self.updated_at), - 'deleted_at': utils.format_time(self.deleted_at), - } - return resource_dict - - @classmethod - def do_check(cls, context, user): - '''Communicate with other services and check user's resources. - - This would be a period job of user to check if there are any missing - actions, and then make correction. - ''' - - return NotImplemented - - def do_delete(self, context, ignore_missing=True, timeout=None): - '''Delete resource from other services.''' - - return NotImplemented diff --git a/bilean/plugins/os/__init__.py b/bilean/plugins/os/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/plugins/os/cinder/__init__.py b/bilean/plugins/os/cinder/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/plugins/os/cinder/volume.py b/bilean/plugins/os/cinder/volume.py deleted file mode 100644 index 6058c6f..0000000 --- a/bilean/plugins/os/cinder/volume.py +++ /dev/null @@ -1,131 +0,0 @@ -# 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 six - -from oslo_log import log as logging - -from bilean.common import exception -from bilean.common.i18n import _, _LE -from bilean.common import schema -from bilean.drivers import base as driver_base -from bilean.plugins import base - -LOG = logging.getLogger(__name__) - - -class VolumeRule(base.Rule): - '''Rule for an OpenStack Cinder volume.''' - - KEYS = ( - PRICE_MAPPING, UNIT, - ) = ( - 'price_mapping', 'unit', - ) - - PM_KEYS = ( - START, END, PRICE, - ) = ( - 'start', 'end', 'price', - ) - - AVAILABLE_UNIT = ( - PER_HOUR, PER_SEC, - ) = ( - 'per_hour', 'per_sec', - ) - - properties_schema = { - PRICE_MAPPING: schema.List( - _('A list specifying the prices.'), - schema=schema.Map( - _('A map specifying the pricce of each volume capacity ' - 'interval.'), - schema={ - START: schema.Integer( - _('Start volume capacity.'), - ), - END: schema.Integer( - _('End volume capacity.'), - ), - PRICE: schema.Integer( - _('Price of this interval.'), - ), - } - ), - required=True, - updatable=True, - ), - UNIT: schema.String( - _('Unit of price, per_hour or per_sec.'), - default='per_hour', - ), - } - - def get_price(self, resource): - '''Get the price of resource in seconds. - - If no exact price found, 0 will be returned. - - :param: resource: Resource object to find price. - ''' - size = int(resource.properties.get('size')) - if not size: - raise exception.Error(msg='Size of volume should be provided to ' - 'get price.') - p_mapping = self.properties.get(self.PRICE_MAPPING) - for pm in p_mapping: - if size >= pm.get(self.START) and size <= pm.get(self.END): - price = pm.get(self.PRICE) - if self.PER_HOUR == self.properties.get(self.UNIT) and price > 0: - price = price * 1.0 / 3600 - return price - - -class VolumeResource(base.Resource): - '''Resource for an OpenStack Cinder volume.''' - - @classmethod - def do_check(context, user): - '''Communicate with other services and check user's resources. - - This would be a period job of user to check if there are any missing - actions, and then make correction. - ''' - # TODO(ldb) - return NotImplemented - - def do_delete(self, context, timestamp=None, ignore_missing=True, - timeout=None): - '''Delete resource from other services.''' - - # Delete resource from db and generate consumption - self.delete(context, timestamp=timestamp) - self.consumption.store(context) - - # Delete resource from cinder - cinderclient = driver_base.BileanDriver().block_store() - try: - cinderclient.volume_delete(self.id, ignore_missing=ignore_missing) - except Exception as ex: - LOG.error(_LE('Error: %s'), six.text_type(ex)) - return False - - return True - - -class VolumePlugin(base.Plugin): - '''Plugin for Openstack Nova server.''' - - RuleClass = VolumeRule - ResourceClass = VolumeResource - notification_exchanges = ['openstack'] diff --git a/bilean/plugins/os/nova/__init__.py b/bilean/plugins/os/nova/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/plugins/os/nova/server.py b/bilean/plugins/os/nova/server.py deleted file mode 100644 index 2d11011..0000000 --- a/bilean/plugins/os/nova/server.py +++ /dev/null @@ -1,131 +0,0 @@ -# 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 six - -from oslo_log import log as logging - -from bilean.common import exception -from bilean.common.i18n import _, _LE -from bilean.common import schema -from bilean.drivers import base as driver_base -from bilean.plugins import base - -LOG = logging.getLogger(__name__) - - -class ServerRule(base.Rule): - '''Rule for an OpenStack Nova server.''' - - KEYS = ( - PRICE_MAPPING, UNIT, - ) = ( - 'price_mapping', 'unit', - ) - - PM_KEYS = ( - FLAVOR, PRICE, - ) = ( - 'flavor', 'price', - ) - - AVAILABLE_UNIT = ( - PER_HOUR, PER_SEC, - ) = ( - 'per_hour', 'per_sec', - ) - - properties_schema = { - PRICE_MAPPING: schema.List( - _('A list specifying the price of each flavor'), - schema=schema.Map( - _('A map specifying the pricce of a flavor.'), - schema={ - FLAVOR: schema.String( - _('Flavor id to set price.'), - ), - PRICE: schema.Integer( - _('Price of this flavor.'), - ), - } - ), - required=True, - updatable=True, - ), - UNIT: schema.String( - _('Unit of price, per_hour or per_sec.'), - default='per_hour', - ), - } - - def get_price(self, resource): - '''Get the price of resource in seconds. - - If no exact price found, it shows that rule of the server's flavor - has not been set, will return 0 as the price notify admin to set - it. - - :param: resource: Resource object to find price. - ''' - flavor = resource.properties.get('flavor') - if not flavor: - raise exception.Error(msg='Flavor should be provided to get ' - 'the price of server.') - p_mapping = self.properties.get(self.PRICE_MAPPING) - price = 0 - for pm in p_mapping: - if flavor == pm.get(self.FLAVOR): - price = pm.get(self.PRICE) - if self.PER_HOUR == self.properties.get(self.UNIT) and price > 0: - price = price * 1.0 / 3600 - return price - - -class ServerResource(base.Resource): - '''Resource for an OpenStack Nova server.''' - - @classmethod - def do_check(context, user): - '''Communicate with other services and check user's resources. - - This would be a period job of user to check if there are any missing - actions, and then make correction. - ''' - # TODO(ldb) - return NotImplemented - - def do_delete(self, context, timestamp=None, ignore_missing=True, - timeout=None): - '''Delete resource from other services.''' - - # Delete resource from db and generate consumption - self.delete(context, timestamp=timestamp) - self.consumption.store(context) - - # Delete resource from nova - novaclient = driver_base.BileanDriver().compute() - try: - novaclient.server_delete(self.id, ignore_missing=ignore_missing) - novaclient.wait_for_server_delete(self.id, timeout=timeout) - except Exception as ex: - LOG.error(_LE('Error: %s'), six.text_type(ex)) - return False - - return True - - -class ServerPlugin(base.Plugin): - '''Plugin for Openstack Nova server.''' - - RuleClass = ServerRule - ResourceClass = ServerResource - notification_exchanges = ['nova'] diff --git a/bilean/rpc/__init__.py b/bilean/rpc/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/rpc/client.py b/bilean/rpc/client.py deleted file mode 100644 index f9848f9..0000000 --- a/bilean/rpc/client.py +++ /dev/null @@ -1,235 +0,0 @@ -# -# Copyright 2012, Red Hat, 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. - -""" -Client side of the bilean engine RPC API. -""" - -from bilean.common import consts -from bilean.common import messaging - -from oslo_config import cfg - - -class EngineClient(object): - '''Client side of the bilean engine rpc API.''' - - BASE_RPC_API_VERSION = '1.0' - - def __init__(self): - cfg.CONF.import_opt('host', 'bilean.common.config') - self._client = messaging.get_rpc_client( - topic=consts.ENGINE_TOPIC, - server=cfg.CONF.host, - version=self.BASE_RPC_API_VERSION) - - @staticmethod - def make_msg(method, **kwargs): - return method, kwargs - - def call(self, ctxt, msg, version=None): - method, kwargs = msg - if version is not None: - client = self._client.prepare(version=version) - else: - client = self._client - return client.call(ctxt, method, **kwargs) - - def cast(self, ctxt, msg, version=None): - method, kwargs = msg - if version is not None: - client = self._client.prepare(version=version) - else: - client = self._client - return client.cast(ctxt, method, **kwargs) - - # users - def user_list(self, ctxt, show_deleted=False, limit=None, - marker=None, sort_keys=None, sort_dir=None, - filters=None): - return self.call(ctxt, - self.make_msg('user_list', - show_deleted=show_deleted, - limit=limit, marker=marker, - sort_keys=sort_keys, sort_dir=sort_dir, - filters=filters)) - - def user_get(self, ctxt, user_id): - return self.call(ctxt, self.make_msg('user_get', - user_id=user_id)) - - def user_create(self, ctxt, user_id, balance=None, credit=None, - status=None): - return self.call(ctxt, self.make_msg('user_create', user_id=user_id, - balance=balance, credit=credit, - status=status)) - - def user_recharge(self, ctxt, user_id, value): - return self.call(ctxt, self.make_msg('user_recharge', - user_id=user_id, - value=value)) - - def user_delete(self, ctxt, user_id): - return self.call(ctxt, self.make_msg('user_delete', - user_id=user_id)) - - def user_attach_policy(self, ctxt, user_id, policy_id): - return self.call(ctxt, self.make_msg('user_attach_policy', - user_id=user_id, - policy_id=policy_id)) - - # rules - def rule_list(self, ctxt, limit=None, marker=None, sort_keys=None, - sort_dir=None, filters=None, show_deleted=False): - return self.call(ctxt, self.make_msg('rule_list', limit=limit, - marker=marker, - sort_keys=sort_keys, - sort_dir=sort_dir, - filters=filters, - show_deleted=show_deleted)) - - def rule_get(self, ctxt, rule_id): - return self.call(ctxt, self.make_msg('rule_get', - rule_id=rule_id)) - - def rule_create(self, ctxt, name, spec, metadata): - return self.call(ctxt, self.make_msg('rule_create', name=name, - spec=spec, metadata=metadata)) - - def rule_update(self, ctxt, values): - return self.call(ctxt, self.make_msg('rule_update', - values=values)) - - def rule_delete(self, ctxt, rule_id): - return self.call(ctxt, self.make_msg('rule_delete', - rule_id=rule_id)) - - # resources - def resource_list(self, ctxt, user_id=None, limit=None, marker=None, - sort_keys=None, sort_dir=None, filters=None, - project_safe=True, show_deleted=False): - return self.call(ctxt, self.make_msg('resource_list', user_id=user_id, - limit=limit, marker=marker, - sort_keys=sort_keys, - sort_dir=sort_dir, - filters=filters, - project_safe=project_safe, - show_deleted=show_deleted)) - - def resource_get(self, ctxt, resource_id): - return self.call(ctxt, self.make_msg('resource_get', - resource_id=resource_id)) - - def resource_create(self, ctxt, resource_id, user_id, - resource_type, properties): - return self.call(ctxt, self.make_msg('resource_create', - resource_id=resource_id, - user_id=user_id, - resource_type=resource_type, - properties=properties)) - - def resource_update(self, ctxt, user_id, resource): - return self.call(ctxt, self.make_msg('resource_update', - user_id=user_id, - resource=resource)) - - def resource_delete(self, ctxt, user_id, resource_id): - return self.call(ctxt, self.make_msg('resource_delete', - user_id=user_id, - resource_id=resource_id)) - - # events - def event_list(self, ctxt, user_id=None, limit=None, marker=None, - sort_keys=None, sort_dir=None, filters=None, - start_time=None, end_time=None, project_safe=True, - show_deleted=False): - return self.call(ctxt, self.make_msg('event_list', user_id=user_id, - limit=limit, marker=marker, - sort_keys=sort_keys, - sort_dir=sort_dir, - filters=filters, - start_time=start_time, - end_time=end_time, - project_safe=project_safe, - show_deleted=show_deleted)) - - def validate_creation(self, cnxt, resources): - return self.call(cnxt, self.make_msg('validate_creation', - resources=resources)) - - # policies - def policy_list(self, ctxt, limit=None, marker=None, sort_keys=None, - sort_dir=None, filters=None, show_deleted=False): - return self.call(ctxt, self.make_msg('policy_list', limit=limit, - marker=marker, - sort_keys=sort_keys, - sort_dir=sort_dir, - filters=filters, - show_deleted=show_deleted)) - - def policy_get(self, ctxt, policy_id): - return self.call(ctxt, self.make_msg('policy_get', - policy_id=policy_id)) - - def policy_create(self, ctxt, name, rules=None, metadata=None): - return self.call(ctxt, self.make_msg('policy_create', - name=name, - rule_ids=rules, - metadata=metadata)) - - def policy_update(self, ctxt, policy_id, name=None, metadata=None, - is_default=None): - return self.call(ctxt, self.make_msg('policy_update', - policy_id=policy_id, - name=name, - metadata=metadata, - is_default=is_default)) - - def policy_delete(self, ctxt, policy_id): - return self.call(ctxt, self.make_msg('policy_delete', - policy_id=policy_id)) - - def policy_add_rules(self, ctxt, policy_id, rules): - return self.call(ctxt, self.make_msg('policy_add_rules', - policy_id=policy_id, - rules=rules)) - - def settle_account(self, ctxt, user_id, task=None): - return self.call(ctxt, self.make_msg('settle_account', - user_id=user_id, - task=task)) - - # consumptions - def consumption_list(self, ctxt, user_id=None, limit=None, marker=None, - sort_keys=None, sort_dir=None, filters=None, - project_safe=True): - return self.call(ctxt, self.make_msg('consumption_list', - user_id=user_id, - limit=limit, marker=marker, - sort_keys=sort_keys, - sort_dir=sort_dir, - filters=filters, - project_safe=project_safe)) - - def consumption_statistics(self, ctxt, user_id=None, filters=None, - start_time=None, end_time=None, summary=False, - project_safe=True): - return self.call(ctxt, self.make_msg('consumption_statistics', - user_id=user_id, - filters=filters, - start_time=start_time, - end_time=end_time, - summary=summary, - project_safe=project_safe)) diff --git a/bilean/scheduler/__init__.py b/bilean/scheduler/__init__.py deleted file mode 100644 index 14b55dd..0000000 --- a/bilean/scheduler/__init__.py +++ /dev/null @@ -1,51 +0,0 @@ -# -# Copyright 2012, Red Hat, 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. - -from bilean.common import consts -from bilean.common import messaging - -from oslo_context import context as oslo_context -import oslo_messaging - - -supported_actions = ( - UPDATE_JOBS, DELETE_JOBS, -) = ( - 'update_jobs', 'delete_jobs', -) - - -def notify(method, scheduler_id=None, **kwargs): - '''Send notification to scheduler - - :param method: remote method to call - :param scheduler_id: specify scheduler to notify; None implies broadcast - ''' - if scheduler_id: - # Notify specific scheduler identified by scheduler_id - client = messaging.get_rpc_client( - topic=consts.SCHEDULER_TOPIC, - server=scheduler_id, - version=consts.RPC_API_VERSION) - else: - # Broadcast to all schedulers - client = messaging.get_rpc_client( - topic=consts.SCHEDULER_TOPIC, - version=consts.RPC_API_VERSION) - try: - client.call(oslo_context.get_current(), method, **kwargs) - return True - except oslo_messaging.MessagingTimeout: - return False diff --git a/bilean/scheduler/cron_scheduler.py b/bilean/scheduler/cron_scheduler.py deleted file mode 100644 index 4d19442..0000000 --- a/bilean/scheduler/cron_scheduler.py +++ /dev/null @@ -1,263 +0,0 @@ -# -# 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 bilean.common import context as bilean_context -from bilean.common import exception -from bilean.common.i18n import _, _LI, _LW -from bilean.common import utils -from bilean.db import api as db_api -from bilean.engine import user as user_mod -from bilean.rpc import client as rpc_client - -from oslo_config import cfg -from oslo_log import log as logging -from oslo_utils import timeutils - -from apscheduler.schedulers import background -from datetime import timedelta -import random -import six - -scheduler_opts = [ - cfg.StrOpt('time_zone', - default='utc', - help=_('The time zone of job, default is utc')), - cfg.IntOpt('prior_notify_time', - default=3, - help=_('Time in hours before notify user when the balance of ' - 'user is almost used up.')), - cfg.IntOpt('misfire_grace_time', - default=3600, - help=_('Seconds after the designated run time that the job is ' - 'still allowed to be run.')), - cfg.BoolOpt('store_ap_job', - default=False, - help=_('Allow bilean to store apscheduler job.')), - cfg.StrOpt('backend', - default='sqlalchemy', - help='The backend to use for db'), - cfg.StrOpt('connection', - help='The SQLAlchemy connection string used to connect to the ' - 'database') -] - -scheduler_group = cfg.OptGroup('scheduler') -cfg.CONF.register_group(scheduler_group) -cfg.CONF.register_opts(scheduler_opts, group=scheduler_group) - -LOG = logging.getLogger(__name__) - - -class CronScheduler(object): - """Cron scheduler based on apscheduler""" - - job_types = ( - NOTIFY, DAILY, FREEZE, - ) = ( - 'notify', 'daily', 'freeze', - ) - trigger_types = (DATE, CRON) = ('date', 'cron') - - def __init__(self, **kwargs): - super(CronScheduler, self).__init__() - self._scheduler = background.BackgroundScheduler() - self.scheduler_id = kwargs.get('scheduler_id') - self.rpc_client = rpc_client.EngineClient() - if cfg.CONF.scheduler.store_ap_job: - self._scheduler.add_jobstore(cfg.CONF.scheduler.backend, - url=cfg.CONF.scheduler.connection) - - def start(self): - LOG.info(_('Starting Cron scheduler')) - self._scheduler.start() - - def stop(self): - LOG.info(_('Stopping Cron scheduler')) - self._scheduler.shutdown() - - def init_scheduler(self): - """Init all jobs related to the engine from db.""" - admin_context = bilean_context.get_admin_context() - jobs = [] or db_api.job_get_all(admin_context, - scheduler_id=self.scheduler_id) - for job in jobs: - if self._is_exist(job.id): - continue - LOG.info(_LI("Add job '%(job_id)s' to scheduler '%(id)s'."), - {'job_id': job.id, 'id': self.scheduler_id}) - self._add_job(job.id, job.job_type, **job.parameters) - - LOG.info(_LI("Initialise users from keystone.")) - users = user_mod.User.init_users(admin_context) - - # Init daily job for all users - if users: - for user in users: - job_id = self._generate_job_id(user.id, self.DAILY) - if self._is_exist(job_id): - continue - self._add_daily_job(user) - - def _add_job(self, job_id, task_type, **kwargs): - """Add a job to scheduler by given data. - - :param str|unicode user_id: used as job_id - :param datetime alarm_time: when to first run the job - - """ - mg_time = cfg.CONF.scheduler.misfire_grace_time - job_time_zone = cfg.CONF.scheduler.time_zone - user_id = job_id.split('-')[1] - trigger_type = self.CRON if task_type == self.DAILY else self.DATE - - if trigger_type == self.DATE: - run_date = kwargs.get('run_date') - if run_date is None: - msg = "Param run_date cannot be None for trigger type 'date'." - raise exception.InvalidInput(reason=msg) - self._scheduler.add_job(self._task, 'date', - timezone=job_time_zone, - run_date=run_date, - args=[user_id, task_type], - id=job_id, - misfire_grace_time=mg_time) - return - - # Add a cron type job - hour = kwargs.get('hour') - minute = kwargs.get('minute') - if not hour or not minute: - hour, minute = self._generate_timer() - self._scheduler.add_job(self._task, 'cron', - timezone=job_time_zone, - hour=hour, - minute=minute, - args=[user_id, task_type], - id=job_id, - misfire_grace_time=mg_time) - - def _remove_job(self, job_id): - """Removes a job, preventing it from being run any more. - - :param str|unicode job_id: the identifier of the job - """ - - self._scheduler.remove_job(job_id) - - def _is_exist(self, job_id): - """Returns if the Job exists that matches the given ``job_id``. - - :param str|unicode job_id: the identifier of the job - :return: True|False - """ - - job = self._scheduler.get_job(job_id) - return job is not None - - def _task(self, user_id, task_type): - admin_context = bilean_context.get_admin_context() - self.rpc_client.settle_account( - admin_context, user_id, task=task_type) - if task_type != self.DAILY: - try: - db_api.job_delete( - admin_context, self._generate_job_id(user_id, task_type)) - except exception.NotFound as e: - LOG.warn(_LW("Failed in deleting job: %s") % six.text_type(e)) - - def _add_notify_job(self, user): - if user.rate == 0: - return False - total_seconds = float(user.balance / user.rate) - prior_notify_time = cfg.CONF.scheduler.prior_notify_time * 3600 - notify_seconds = total_seconds - prior_notify_time - notify_seconds = notify_seconds if notify_seconds > 0 else 0 - run_date = timeutils.utcnow() + timedelta(seconds=notify_seconds) - job_params = {'run_date': run_date} - job_id = self._generate_job_id(user.id, self.NOTIFY) - self._add_job(job_id, self.NOTIFY, **job_params) - # Save jobs to database - job = {'id': job_id, - 'job_type': self.NOTIFY, - 'scheduler_id': self.scheduler_id, - 'parameters': {'run_date': utils.format_time(run_date)}} - admin_context = bilean_context.get_admin_context() - db_api.job_create(admin_context, job) - - def _add_freeze_job(self, user): - if user.rate == 0: - return False - total_seconds = float(user.balance / user.rate) - run_date = timeutils.utcnow() + timedelta(seconds=total_seconds) - job_params = {'run_date': run_date} - job_id = self._generate_job_id(user.id, self.FREEZE) - self._add_job(job_id, self.FREEZE, **job_params) - # Save jobs to database - job = {'id': job_id, - 'job_type': self.FREEZE, - 'scheduler_id': self.scheduler_id, - 'parameters': {'run_date': utils.format_time(run_date)}} - admin_context = bilean_context.get_admin_context() - db_api.job_create(admin_context, job) - return True - - def _add_daily_job(self, user): - job_id = self._generate_job_id(user.id, self.DAILY) - job_params = {'hour': random.randint(0, 23), - 'minute': random.randint(0, 59)} - self._add_job(job_id, self.DAILY, **job_params) - return True - - def _generate_timer(self): - """Generate a random timer include hour and minute.""" - hour = random.randint(0, 23) - minute = random.randint(0, 59) - return hour, minute - - def _generate_job_id(self, user_id, job_type): - """Generate job id by given user_id and job type""" - return "%s-%s" % (job_type, user_id) - - def update_jobs(self, user): - """Update user's billing job""" - # Delete all jobs except daily job - admin_context = bilean_context.get_admin_context() - for job_type in self.NOTIFY, self.FREEZE: - job_id = self._generate_job_id(user.id, job_type) - try: - if self._is_exist(job_id): - self._remove_job(job_id) - db_api.job_delete(admin_context, job_id) - except Exception as e: - LOG.warn(_LW("Failed in deleting job: %s") % six.text_type(e)) - - if user.status == user.ACTIVE: - self._add_notify_job(user) - elif user.status == user.WARNING: - self._add_freeze_job(user) - - def delete_jobs(self, user): - """Delete all jobs related the specific user.""" - admin_context = bilean_context.get_admin_context() - for job_type in self.job_types: - job_id = self._generate_job_id(user.id, job_type) - try: - if self._is_exist(job_id): - self._remove_job(job_id) - db_api.job_delete(admin_context, job_id) - except Exception as e: - LOG.warn(_LW("Failed in deleting job: %s") % six.text_type(e)) - - -def list_opts(): - yield scheduler_group.name, scheduler_opts diff --git a/bilean/scheduler/service.py b/bilean/scheduler/service.py deleted file mode 100644 index 3bfcdda..0000000 --- a/bilean/scheduler/service.py +++ /dev/null @@ -1,85 +0,0 @@ -# -# 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 six -import socket - -from oslo_log import log as logging -import oslo_messaging -from oslo_service import service - -from bilean.common import consts -from bilean.common.i18n import _LE, _LI -from bilean.common import messaging as rpc_messaging -from bilean.engine import user as user_mod -from bilean.scheduler import cron_scheduler - -LOG = logging.getLogger(__name__) - - -class SchedulerService(service.Service): - - def __init__(self, host, topic, manager=None, context=None): - super(SchedulerService, self).__init__() - self.host = host - self.topic = topic - - self.scheduler_id = None - self.scheduler = None - self.target = None - self._rpc_server = None - - def start(self): - self.scheduler_id = socket.gethostname() - - self.scheduler = cron_scheduler.CronScheduler( - scheduler_id=self.scheduler_id) - LOG.info(_LI("Starting billing scheduler")) - self.scheduler.init_scheduler() - self.scheduler.start() - - LOG.info(_LI("Starting rpc server for bilean scheduler service")) - self.target = oslo_messaging.Target(version=consts.RPC_API_VERSION, - server=self.scheduler_id, - topic=self.topic) - self._rpc_server = rpc_messaging.get_rpc_server(self.target, self) - self._rpc_server.start() - - super(SchedulerService, self).start() - - def _stop_rpc_server(self): - # Stop RPC connection to prevent new requests - LOG.info(_LI("Stopping scheduler service...")) - try: - self._rpc_server.stop() - self._rpc_server.wait() - LOG.info(_LI('Scheduler service stopped successfully')) - except Exception as ex: - LOG.error(_LE('Failed to stop scheduler service: %s'), - six.text_type(ex)) - - def stop(self): - self._stop_rpc_server() - - LOG.info(_LI("Stopping billing scheduler")) - self.scheduler.stop() - - super(SchedulerService, self).stop() - - def update_jobs(self, ctxt, user): - user_obj = user_mod.User.from_dict(user) - self.scheduler.update_jobs(user_obj) - - def delete_jobs(self, ctxt, user): - user_obj = user_mod.User.from_dict(user) - self.scheduler.delete_jobs(user_obj) diff --git a/bilean/tests/__init__.py b/bilean/tests/__init__.py deleted file mode 100644 index fed5dba..0000000 --- a/bilean/tests/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# 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 oslo_i18n - - -def fake_translate_msgid(msgid, domain, desired_locale=None): - return msgid - -oslo_i18n.enable_lazy() - -# To ensure messages don't really get translated while running tests. -# As there are lots of places where matching is expected when comparing -# exception message(translated) with raw message. -oslo_i18n._translate_msgid = fake_translate_msgid diff --git a/bilean/tests/common/__init__.py b/bilean/tests/common/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/tests/common/base.py b/bilean/tests/common/base.py deleted file mode 100644 index c365fde..0000000 --- a/bilean/tests/common/base.py +++ /dev/null @@ -1,77 +0,0 @@ -# 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 logging -import os - -import fixtures -import testscenarios -import testtools - -from bilean.common import messaging -from bilean.tests.common import utils - - -TEST_DEFAULT_LOGLEVELS = {'migrate': logging.WARN, - 'sqlalchemy': logging.WARN} -_LOG_FORMAT = "%(levelname)8s [%(name)s] %(message)s" -_TRUE_VALUES = ('True', 'true', '1', 'yes') - - -class FakeLogMixin(object): - def setup_logging(self): - # Assign default logs to self.LOG so we can still - # assert on bilean logs. - default_level = logging.INFO - if os.environ.get('OS_DEBUG') in _TRUE_VALUES: - default_level = logging.DEBUG - - self.LOG = self.useFixture( - fixtures.FakeLogger(level=default_level, format=_LOG_FORMAT)) - base_list = set([nlog.split('.')[0] - for nlog in logging.Logger.manager.loggerDict]) - for base in base_list: - if base in TEST_DEFAULT_LOGLEVELS: - self.useFixture(fixtures.FakeLogger( - level=TEST_DEFAULT_LOGLEVELS[base], - name=base, format=_LOG_FORMAT)) - elif base != 'bilean': - self.useFixture(fixtures.FakeLogger( - name=base, format=_LOG_FORMAT)) - - -class BileanTestCase(testscenarios.WithScenarios, - testtools.TestCase, FakeLogMixin): - - def setUp(self): - super(BileanTestCase, self).setUp() - self.setup_logging() - self.useFixture(fixtures.MonkeyPatch( - 'bilean.common.exception._FATAL_EXCEPTION_FORMAT_ERRORS', - True)) - - messaging.setup("fake://", optional=True) - self.addCleanup(messaging.cleanup) - - utils.setup_dummy_db() - self.addCleanup(utils.reset_dummy_db) - - def patchobject(self, obj, attr, **kwargs): - mockfixture = self.useFixture(fixtures.MockPatchObject(obj, attr, - **kwargs)) - return mockfixture.mock - - # NOTE(pshchelo): this overrides the testtools.TestCase.patch method - # that does simple monkey-patching in favor of mock's patching - def patch(self, target, **kwargs): - mockfixture = self.useFixture(fixtures.MockPatch(target, **kwargs)) - return mockfixture.mock diff --git a/bilean/tests/common/fakes.py b/bilean/tests/common/fakes.py deleted file mode 100644 index 1511d52..0000000 --- a/bilean/tests/common/fakes.py +++ /dev/null @@ -1,85 +0,0 @@ -# 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. - -""" -A fake server that "responds" to API methods with pre-canned responses. -""" - - -class FakeClient(object): - def assert_called(self, method, url, body=None, pos=-1): - # Assert than an API method was just called. - expected = (method, url) - called = self.client.callstack[pos][0:2] - - assert self.client.callstack, \ - "Expected %s %s but no calls were made." % expected - - assert expected == called, 'Expected %s %s; got %s %s' % \ - (expected + called) - - if body is not None: - assert self.client.callstack[pos][2] == body - - def assert_called_anytime(self, method, url, body=None): - # Assert than an API method was called anytime in the test. - expected = (method, url) - - assert self.client.callstack, \ - "Expected %s %s but no calls were made." % expected - - found = False - for entry in self.client.callstack: - if expected == entry[0:2]: - found = True - break - - assert found, 'Expected %s %s; got %s' % \ - (expected, self.client.callstack) - if body is not None: - try: - assert entry[2] == body - except AssertionError: - print(entry[2]) - print("!=") - print(body) - raise - - self.client.callstack = [] - - def clear_callstack(self): - self.client.callstack = [] - - def authenticate(self): - pass - - -class FakeKeystoneClient(object): - def __init__(self, username='test_user', password='apassword', - user_id='1234', access='4567', secret='8901', - credential_id='abcdxyz', auth_token='abcd1234', - only_services=None): - self.username = username - self.password = password - self.user_id = user_id - self.access = access - self.secret = secret - self.credential_id = credential_id - self.only_services = only_services - - class FakeCred(object): - id = self.credential_id - access = self.access - secret = self.secret - self.creds = FakeCred() - - self.auth_token = auth_token diff --git a/bilean/tests/common/utils.py b/bilean/tests/common/utils.py deleted file mode 100644 index 0ff0c35..0000000 --- a/bilean/tests/common/utils.py +++ /dev/null @@ -1,80 +0,0 @@ -# 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 random -import string -import uuid - -from oslo_config import cfg -from oslo_db import options -import sqlalchemy - -from bilean.common import context -from bilean.db import api as db_api - -get_engine = db_api.get_engine - - -class UUIDStub(object): - def __init__(self, value): - self.value = value - - def __enter__(self): - self.uuid4 = uuid.uuid4 - uuid_stub = lambda: self.value - uuid.uuid4 = uuid_stub - - def __exit__(self, *exc_info): - uuid.uuid4 = self.uuid4 - - -def random_name(): - return ''.join(random.choice(string.ascii_uppercase) - for x in range(10)) - - -def setup_dummy_db(): - options.cfg.set_defaults(options.database_opts, sqlite_synchronous=False) - options.set_defaults(cfg.CONF, connection="sqlite://") - engine = get_engine() - db_api.db_sync(engine) - engine.connect() - - -def reset_dummy_db(): - engine = get_engine() - meta = sqlalchemy.MetaData() - meta.reflect(bind=engine) - - for table in reversed(meta.sorted_tables): - if table.name == 'migrate_version': - continue - engine.execute(table.delete()) - - -def dummy_context(user='test_username', project='test_project_id', - password='password', roles=None, user_id='test_user_id', - trust_id=None, region_name=None, domain=None): - roles = roles or [] - return context.RequestContext.from_dict({ - 'project': project, - 'user': user_id, - 'user_name': user, - 'password': password, - 'roles': roles, - 'is_admin': False, - 'auth_url': 'http://server.test:5000/v2.0', - 'auth_token': 'abcd1234', - 'trust_id': trust_id, - 'region_name': region_name, - 'domain': domain - }) diff --git a/bilean/tests/drivers/__init__.py b/bilean/tests/drivers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/tests/drivers/test_driver.py b/bilean/tests/drivers/test_driver.py deleted file mode 100644 index e1760f6..0000000 --- a/bilean/tests/drivers/test_driver.py +++ /dev/null @@ -1,46 +0,0 @@ -# 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 cfg - -from bilean.drivers import base as driver_base -from bilean.engine import environment -from bilean.tests.common import base - - -class TestBileanDriver(base.BileanTestCase): - - def test_init_using_default_cloud_backend(self): - plugin1 = mock.Mock() - plugin1.compute = 'Compute1' - plugin1.network = 'Network1' - env = environment.global_env() - env.register_driver('cloud_backend_1', plugin1) - - # Using default cloud backend defined in configure file - cfg.CONF.set_override('cloud_backend', 'cloud_backend_1') - bd = driver_base.BileanDriver() - self.assertEqual('Compute1', bd.compute) - self.assertEqual('Network1', bd.network) - - def test_init_using_specified_cloud_backend(self): - plugin2 = mock.Mock() - plugin2.compute = 'Compute2' - plugin2.network = 'Network2' - env = environment.global_env() - env.register_driver('cloud_backend_2', plugin2) - - # Using specified cloud backend - bd = driver_base.BileanDriver('cloud_backend_2') - self.assertEqual('Compute2', bd.compute) - self.assertEqual('Network2', bd.network) diff --git a/bilean/tests/drivers/test_sdk.py b/bilean/tests/drivers/test_sdk.py deleted file mode 100644 index 584fbde..0000000 --- a/bilean/tests/drivers/test_sdk.py +++ /dev/null @@ -1,211 +0,0 @@ -# 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 openstack import connection -from openstack import profile -from oslo_serialization import jsonutils -from requests import exceptions as req_exc -import six - -from bilean.common import exception as bilean_exc -from bilean.drivers.openstack import sdk -from bilean.tests.common import base - - -class OpenStackSDKTest(base.BileanTestCase): - - def setUp(self): - super(OpenStackSDKTest, self).setUp() - - def test_parse_exception_http_exception_with_details(self): - details = jsonutils.dumps({ - 'error': { - 'code': 404, - 'message': 'Resource BAR is not found.' - } - }) - raw = sdk.exc.ResourceNotFound('A message', details, http_status=404) - ex = self.assertRaises(bilean_exc.InternalError, - sdk.parse_exception, raw) - - self.assertEqual(404, ex.code) - self.assertEqual('Resource BAR is not found.', six.text_type(ex)) - # key name is not 'error' case - details = jsonutils.dumps({ - 'forbidden': { - 'code': 403, - 'message': 'Quota exceeded for instances.' - } - }) - raw = sdk.exc.ResourceNotFound('A message', details, 403) - ex = self.assertRaises(bilean_exc.InternalError, - sdk.parse_exception, raw) - - self.assertEqual(403, ex.code) - self.assertEqual('Quota exceeded for instances.', six.text_type(ex)) - - def test_parse_exception_http_exception_no_details(self): - details = "An error message" - - raw = sdk.exc.ResourceNotFound('A message.', details, http_status=404) - ex = self.assertRaises(bilean_exc.InternalError, - sdk.parse_exception, raw) - - self.assertEqual(404, ex.code) - self.assertEqual('A message.', six.text_type(ex)) - - def test_parse_exception_http_exception_code_displaced(self): - details = jsonutils.dumps({ - 'code': 400, - 'error': { - 'message': 'Resource BAR is in error state.' - } - }) - - raw = sdk.exc.HttpException(message='A message.', details=details, - http_status=400) - ex = self.assertRaises(bilean_exc.InternalError, - sdk.parse_exception, raw) - - self.assertEqual(400, ex.code) - self.assertEqual('Resource BAR is in error state.', six.text_type(ex)) - - def test_parse_exception_sdk_exception(self): - raw = sdk.exc.InvalidResponse('INVALID') - - ex = self.assertRaises(bilean_exc.InternalError, - sdk.parse_exception, raw) - - self.assertEqual(500, ex.code) - self.assertEqual('InvalidResponse', six.text_type(ex)) - - def test_parse_exception_request_exception(self): - raw = req_exc.HTTPError(401, 'ERROR') - - ex = self.assertRaises(bilean_exc.InternalError, - sdk.parse_exception, raw) - - self.assertEqual(401, ex.code) - self.assertEqual('[Errno 401] ERROR', ex.message) - - def test_parse_exception_other_exceptions(self): - raw = Exception('Unknown Error') - - ex = self.assertRaises(bilean_exc.InternalError, - sdk.parse_exception, raw) - - self.assertEqual(500, ex.code) - self.assertEqual('Unknown Error', six.text_type(ex)) - - def test_translate_exception_wrapper(self): - - test_func = mock.Mock() - test_func.__name__ = 'test_func' - - res = sdk.translate_exception(test_func) - self.assertEqual('function', res.__class__.__name__) - - def test_translate_exception_with_exception(self): - - @sdk.translate_exception - def test_func(driver): - raise(Exception('test exception')) - - error = bilean_exc.InternalError(code=500, message='BOOM') - self.patchobject(sdk, 'parse_exception', side_effect=error) - ex = self.assertRaises(bilean_exc.InternalError, - test_func, mock.Mock()) - - self.assertEqual(500, ex.code) - self.assertEqual('BOOM', ex.message) - - @mock.patch.object(profile, 'Profile') - @mock.patch.object(connection, 'Connection') - def test_create_connection_token(self, mock_conn, mock_profile): - x_profile = mock.Mock() - mock_profile.return_value = x_profile - x_conn = mock.Mock() - mock_conn.return_value = x_conn - - res = sdk.create_connection({'token': 'TOKEN', 'foo': 'bar'}) - - self.assertEqual(x_conn, res) - mock_profile.assert_called_once_with() - x_profile.set_version.assert_called_once_with('identity', 'v3') - mock_conn.assert_called_once_with(profile=x_profile, - user_agent=sdk.USER_AGENT, - auth_plugin='token', - token='TOKEN', - foo='bar') - - @mock.patch.object(profile, 'Profile') - @mock.patch.object(connection, 'Connection') - def test_create_connection_password(self, mock_conn, mock_profile): - x_profile = mock.Mock() - mock_profile.return_value = x_profile - x_conn = mock.Mock() - mock_conn.return_value = x_conn - - res = sdk.create_connection({'user_id': '123', 'password': 'abc', - 'foo': 'bar'}) - - self.assertEqual(x_conn, res) - mock_profile.assert_called_once_with() - x_profile.set_version.assert_called_once_with('identity', 'v3') - mock_conn.assert_called_once_with(profile=x_profile, - user_agent=sdk.USER_AGENT, - auth_plugin='password', - user_id='123', - password='abc', - foo='bar') - - @mock.patch.object(profile, 'Profile') - @mock.patch.object(connection, 'Connection') - def test_create_connection_with_region(self, mock_conn, mock_profile): - x_profile = mock.Mock() - mock_profile.return_value = x_profile - x_conn = mock.Mock() - mock_conn.return_value = x_conn - - res = sdk.create_connection({'region_name': 'REGION_ONE'}) - - self.assertEqual(x_conn, res) - mock_profile.assert_called_once_with() - x_profile.set_region.assert_called_once_with(x_profile.ALL, - 'REGION_ONE') - mock_conn.assert_called_once_with(profile=x_profile, - user_agent=sdk.USER_AGENT, - auth_plugin='password') - - @mock.patch.object(profile, 'Profile') - @mock.patch.object(connection, 'Connection') - @mock.patch.object(sdk, 'parse_exception') - def test_create_connection_with_exception(self, mock_parse, mock_conn, - mock_profile): - x_profile = mock.Mock() - mock_profile.return_value = x_profile - ex_raw = Exception('Whatever') - mock_conn.side_effect = ex_raw - mock_parse.side_effect = bilean_exc.InternalError(code=123, - message='BOOM') - - ex = self.assertRaises(bilean_exc.InternalError, - sdk.create_connection) - - mock_profile.assert_called_once_with() - mock_conn.assert_called_once_with(profile=x_profile, - user_agent=sdk.USER_AGENT, - auth_plugin='password') - mock_parse.assert_called_once_with(ex_raw) - self.assertEqual(123, ex.code) - self.assertEqual('BOOM', ex.message) diff --git a/bilean/tests/rules/__init__.py b/bilean/tests/rules/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bilean/tests/rules/test_rule_base.py b/bilean/tests/rules/test_rule_base.py deleted file mode 100644 index b1fd58c..0000000 --- a/bilean/tests/rules/test_rule_base.py +++ /dev/null @@ -1,267 +0,0 @@ -# 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 -import six - -from bilean.common import exception -from bilean.common import schema -from bilean.common import utils as common_utils -from bilean.db import api as db_api -from bilean.engine import environment -from bilean.plugins import base as plugin_base -from bilean.tests.common import base -from bilean.tests.common import utils - - -class DummyRule(plugin_base.Rule): - VERSION = '1.0' - - properties_schema = { - 'key1': schema.String( - 'First key', - default='value1' - ), - 'key2': schema.Integer( - 'Second key', - required=True, - ), - } - - def __init__(self, name, spec, **kwargs): - super(DummyRule, self).__init__(name, spec, **kwargs) - - -class DummyPlugin(plugin_base.Plugin): - RuleClass = DummyRule - - -class TestRuleBase(base.BileanTestCase): - - def setUp(self): - super(TestRuleBase, self).setUp() - - self.context = utils.dummy_context() - environment.global_env().register_plugin('bilean.dummy', DummyPlugin) - self.spec = { - 'type': 'bilean.dummy', - 'version': '1.0', - 'properties': { - 'key1': 'value1', - 'key2': 2, - } - } - - def _create_rule(self, rule_name, rule_id=None): - rule = plugin_base.Rule(rule_name, self.spec) - if rule_id: - rule.id = rule_id - - return rule - - def _create_db_rule(self, **kwargs): - values = { - 'name': 'test-rule', - 'type': 'bilean.dummy-1.0', - 'spec': self.spec, - 'metadata': {} - } - - values.update(kwargs) - return db_api.rule_create(self.context, values) - - def test_init(self): - name = utils.random_name() - rule = self._create_rule(name) - - self.assertIsNone(rule.id) - self.assertEqual(name, rule.name) - self.assertEqual('bilean.dummy-1.0', rule.type) - self.assertEqual(self.spec, rule.spec) - self.assertEqual({}, rule.metadata) - self.assertIsNone(rule.created_at) - self.assertIsNone(rule.updated_at) - self.assertIsNone(rule.deleted_at) - - spec_data = rule.spec_data - self.assertEqual('bilean.dummy', spec_data['type']) - self.assertEqual('1.0', spec_data['version']) - self.assertEqual({'key1': 'value1', 'key2': 2}, - spec_data['properties']) - self.assertEqual({'key1': 'value1', 'key2': 2}, rule.properties) - - def test_rule_type_not_found(self): - bad_spec = { - 'type': 'bad-type', - 'version': '1.0', - 'properties': '', - } - - self.assertRaises(exception.PluginTypeNotFound, - plugin_base.Rule, - 'test-rule', bad_spec) - - def test_load(self): - rule = self._create_db_rule() - result = plugin_base.Rule.load(self.context, rule.id) - - self.assertEqual(rule.id, result.id) - self.assertEqual(rule.name, result.name) - self.assertEqual(rule.type, result.type) - self.assertEqual(rule.spec, result.spec) - self.assertEqual(rule.meta_data, result.metadata) - self.assertEqual({'key1': 'value1', 'key2': 2}, result.properties) - - self.assertEqual(rule.created_at, result.created_at) - self.assertEqual(rule.updated_at, result.updated_at) - - def test_load_not_found(self): - ex = self.assertRaises(exception.RuleNotFound, - plugin_base.Rule.load, - self.context, 'fake-rule', None) - self.assertEqual('The rule (fake-rule) could not be found.', - six.text_type(ex)) - - ex = self.assertRaises(exception.RuleNotFound, - plugin_base.Rule.load, - self.context, None, None) - self.assertEqual('The rule (None) could not be found.', - six.text_type(ex)) - - def test_load_all(self): - result = plugin_base.Rule.load_all(self.context) - self.assertEqual([], list(result)) - - rule1 = self._create_db_rule(name='rule-1', id='ID1') - rule2 = self._create_db_rule(name='rule-2', id='ID2') - - result = plugin_base.Rule.load_all(self.context) - rules = list(result) - self.assertEqual(2, len(rules)) - self.assertEqual(rule1.id, rules[0].id) - self.assertEqual(rule2.id, rules[1].id) - - @mock.patch.object(db_api, 'rule_get_all') - def test_load_all_with_params(self, mock_get_all): - mock_get_all.return_value = [] - - res = list(plugin_base.Rule.load_all(self.context)) - self.assertEqual([], res) - mock_get_all.assert_called_once_with(self.context, limit=None, - marker=None, sort_keys=None, - sort_dir=None, filters=None, - show_deleted=False) - mock_get_all.reset_mock() - - res = list(plugin_base.Rule.load_all(self.context, limit=1, - marker='MARKER', - sort_keys=['K1'], - sort_dir='asc', - filters={'name': 'fake-name'})) - self.assertEqual([], res) - mock_get_all.assert_called_once_with(self.context, limit=1, - marker='MARKER', - sort_keys=['K1'], - sort_dir='asc', - filters={'name': 'fake-name'}, - show_deleted=False) - - def test_delete(self): - rule = self._create_db_rule() - rule_id = rule.id - - res = plugin_base.Rule.delete(self.context, rule_id) - self.assertIsNone(res) - self.assertRaises(exception.RuleNotFound, - plugin_base.Rule.load, - self.context, rule_id, None) - - def test_delete_not_found(self): - result = plugin_base.Rule.delete(self.context, 'fake-rule') - self.assertIsNone(result) - - def test_store_for_create(self): - rule = self._create_rule('test-rule') - self.assertIsNone(rule.id) - - rule_id = rule.store(self.context) - self.assertIsNotNone(rule_id) - self.assertEqual(rule_id, rule.id) - - result = db_api.rule_get(self.context, rule_id) - - self.assertIsNotNone(result) - self.assertEqual('test-rule', result.name) - self.assertEqual(rule_id, result.id) - self.assertEqual(rule.type, result.type) - self.assertEqual(rule.spec, result.spec) - self.assertEqual(rule.metadata, result.meta_data) - - self.assertIsNotNone(result.created_at) - self.assertIsNone(result.updated_at) - - def test_store_for_update(self): - rule = self._create_rule('test-rule') - self.assertIsNone(rule.id) - rule_id = rule.store(self.context) - self.assertIsNotNone(rule_id) - self.assertEqual(rule_id, rule.id) - - rule.name = 'test-rule-1' - rule.metadata = {'key': 'value'} - - new_id = rule.store(self.context) - self.assertEqual(rule_id, new_id) - - result = db_api.rule_get(self.context, rule_id) - self.assertIsNotNone(result) - self.assertEqual('test-rule-1', result.name) - self.assertEqual({'key': 'value'}, result.meta_data) - self.assertIsNotNone(rule.created_at) - self.assertIsNotNone(rule.updated_at) - - def test_to_dict(self): - rule = self._create_rule('test-rule') - rule_id = rule.store(self.context) - self.assertIsNotNone(rule_id) - expected = { - 'id': rule_id, - 'name': rule.name, - 'type': rule.type, - 'spec': rule.spec, - 'metadata': rule.metadata, - 'created_at': common_utils.format_time(rule.created_at), - 'updated_at': None, - 'deleted_at': None, - } - - result = plugin_base.Rule.load(self.context, rule_id=rule.id) - self.assertEqual(expected, result.to_dict()) - - def test_get_schema(self): - expected = { - 'key1': { - 'default': 'value1', - 'description': 'First key', - 'readonly': False, - 'required': False, - 'type': 'String' - }, - 'key2': { - 'description': 'Second key', - 'readonly': False, - 'required': True, - 'type': 'Integer' - }, - } - res = DummyRule.get_schema() - self.assertEqual(expected, res) diff --git a/bilean/version.py b/bilean/version.py deleted file mode 100644 index 7ad12b1..0000000 --- a/bilean/version.py +++ /dev/null @@ -1,17 +0,0 @@ -# -# 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 pbr.version - -version_info = pbr.version.VersionInfo('bilean') diff --git a/devstack/README.rst b/devstack/README.rst deleted file mode 100644 index b4af760..0000000 --- a/devstack/README.rst +++ /dev/null @@ -1,46 +0,0 @@ -=========================== -Enabling Bilean in DevStack -=========================== - -1. Download DevStack. - - :: - - git clone https://git.openstack.org/openstack-dev/devstack - cd devstack - -2. Add this repo as an external repository. - - :: - - cat > /opt/stack/devstack/local.conf << END - [[local|localrc]] - enable_plugin bilean https://github.com/openstack/bilean master - END - -3. Add Plugin Configuration Hooks. - - Bilean service is driven using a plugin mechanism for integrating to other - services. Each integrated service may require additional configuration - settings. Typically, you will need to set the notifications driver in each - service's configuration. - - For example, to enable nova service, edit `/etc/nova/nova.conf` and add - following configuration:: - - [oslo_messaging_notifications] - driver = messaging - - Or add following configurations to post config section in `local.conf` to - make devstack automaticlly configure the settings above:: - - [[post-config|$NOVA_CONF]] - [oslo_messaging_notifications] - driver = messaging - -4. Then run devstack normally. - - :: - - cd /opt/stack/devstack - ./stack.sh diff --git a/devstack/lib/bilean b/devstack/lib/bilean deleted file mode 100644 index a0abfe0..0000000 --- a/devstack/lib/bilean +++ /dev/null @@ -1,176 +0,0 @@ -#!/bin/bash -# -# lib/bilean -# Functions to control the configuration and operation of the **bilean** service - -# To enable, add the following to local.conf -# -# [[local|localrc]] -# enable_plugin bilean https://git.openstack.org/openstack/bilean - -# Dependencies: -# -# - ``functions`` file -# - ``DEST``, ``DATA_DIR``, ``STACK_USER`` must be defined -# - ``SERVICE_{TENANT_NAME|PASSWORD}`` must be defined - -# ``stack.sh`` calls the entry points in this order: -# -# - install_bilean -# - install_bileanclient -# - configure_bilean -# - init_bilean -# - start_bilean -# - stop_bilean -# - cleanup_bilean - -# Save trace setting -XTRACE=$(set +o | grep xtrace) -set +o xtrace - -# Functions -# --------- - -# Test if any bilean services are enabled -function is_bilean_enabled { - [[ ,${ENABLED_SERVICES} =~ ,"m-" ]] && return 0 - return 1 -} - -# cleanup_bilean() - Remove residual data files, anything left over from previous -# runs that a clean run would need to clean up -function cleanup_bilean { - sudo rm -rf $BILEAN_STATE_PATH $BILEAN_AUTH_CACHE_DIR - sudo rm -rf $BILEAN_CONF_DIR -} - -# configure_bilean() - Set config files, create data dirs, etc -function configure_bilean { - # Put config files in ``/etc/bilean`` for everyone to find - if [[ ! -d $BILEAN_CONF_DIR ]]; then - sudo mkdir -p $BILEAN_CONF_DIR - fi - sudo install -d -o $STACK_USER $BILEAN_CONF_DIR - - BILEAN_API_PASTE_FILE=$BILEAN_CONF_DIR/api-paste.ini - BILEAN_POLICY_FILE=$BILEAN_CONF_DIR/policy.json - BILEAN_RES_DEF_FILE=$BILEAN_CONF_DIR/resource_definitions.yaml - - cp $BILEAN_DIR/etc/bilean/api-paste.ini $BILEAN_API_PASTE_FILE - cp $BILEAN_DIR/etc/bilean/policy.json $BILEAN_POLICY_FILE - cp $BILEAN_DIR/etc/bilean/resource_definitions.yaml $BILEAN_RES_DEF_FILE - - # common options - iniset $BILEAN_CONF DEFAULT debug "$ENABLE_DEBUG_LOG_LEVEL" - iniset $BILEAN_CONF DEFAULT use_syslog $SYSLOG - if [ "$LOG_COLOR" == "True" ] && [ "$SYSLOG" == "False" ]; then - # Add color to logging output - setup_colorized_logging $BILEAN_CONF DEFAULT - fi - - # rpc - iniset_rpc_backend bilean $BILEAN_CONF - - # OpenStack API - iniset $BILEAN_CONF bilean_api bilean_host $BILEAN_SERVICE_HOST - iniset $BILEAN_CONF bilean_api bind_port $BILEAN_SERVICE_PORT - - # Database connection - iniset $BILEAN_CONF database connection `database_connection_url bilean` - - # Keystone authtoken middleware - #configure_auth_token_middleware $BILEAN_CONF bilean $BILEAN_AUTH_CACHE_DIR - iniset $BILEAN_CONF keystone_authtoken identity_uri $KEYSTONE_AUTH_URI - iniset $BILEAN_CONF keystone_authtoken cafile $SSL_BUNDLE_FILE - iniset $BILEAN_CONF keystone_authtoken signing_dir $BILEAN_AUTH_CACHE_DIR - iniset $BILEAN_CONF keystone_authtoken auth_uri $KEYSTONE_AUTH_URI/v3 - iniset $BILEAN_CONF keystone_authtoken admin_user bilean - iniset $BILEAN_CONF keystone_authtoken admin_password $SERVICE_PASSWORD - iniset $BILEAN_CONF keystone_authtoken admin_tenant_name $SERVICE_TENANT_NAME - - # Bilean service credentials - iniset $BILEAN_CONF authentication auth_url $KEYSTONE_AUTH_URI/v3 - iniset $BILEAN_CONF authentication service_username bilean - iniset $BILEAN_CONF authentication service_password $SERVICE_PASSWORD - iniset $BILEAN_CONF authentication service_project_name $SERVICE_TENANT_NAME -} - -# create_bilean_accounts() - Set up common required bilean accounts -# -# Project User Roles -# ------------------------------------------------------------------ -# SERVICE_TENANT_NAME bilean service -function create_bilean_accounts { - - create_service_user "bilean" "admin" - - if [[ "$KEYSTONE_CATALOG_BACKEND" = 'sql' ]]; then - - local bilean_service=$(get_or_create_service "bilean" \ - "billing" "Billing Service") - get_or_create_endpoint $bilean_service \ - "$REGION_NAME" \ - "$BILEAN_SERVICE_PROTOCOL://$BILEAN_SERVICE_HOST:$BILEAN_SERVICE_PORT/v1" \ - "$BILEAN_SERVICE_PROTOCOL://$BILEAN_SERVICE_HOST:$BILEAN_SERVICE_PORT/v1" \ - "$BILEAN_SERVICE_PROTOCOL://$BILEAN_SERVICE_HOST:$BILEAN_SERVICE_PORT/v1" - fi - -} - - -# create_bilean_cache_dir() - Part of the init_bilean() process -function create_bilean_cache_dir { - # Create cache dir - sudo mkdir -p $BILEAN_AUTH_CACHE_DIR - sudo chown $STACK_USER $BILEAN_AUTH_CACHE_DIR - rm -f $BILEAN_AUTH_CACHE_DIR/* -} - -# init_bilean() - Initialize databases, etc. -function init_bilean { - - # (Re)create bilean database - recreate_database bilean - - # Migrate bilean database - $BILEAN_BIN_DIR/bilean-manage db_sync - create_bilean_cache_dir -} - -# install_bileanclient() - Collect source and prepare -function install_bileanclient { - git_clone $BILEANCLIENT_REPO $BILEANCLIENT_DIR $BILEANCLIENT_BRANCH - setup_develop $BILEANCLIENT_DIR -} - -# install_bilean() - Collect source and prepare -function install_bilean { - git_clone $BILEAN_REPO $BILEAN_DIR $BILEAN_BRANCH - setup_develop $BILEAN_DIR -} - -# start_bilean() - Start running processes, including screen -function start_bilean { - - # ``run_process`` checks ``is_service_enabled``, it is not needed here - run_process bl-api "$BILEAN_BIN_DIR/bilean-api --config-file=$BILEAN_CONF" - run_process bl-eng "$BILEAN_BIN_DIR/bilean-engine --config-file=$BILEAN_CONF" - run_process bl-sch "$BILEAN_BIN_DIR/bilean-scheduler --config-file=$BILEAN_CONF" - run_process bl-noti "$BILEAN_BIN_DIR/bilean-notification --config-file=$BILEAN_CONF" -} - -# stop_bilean() - Stop running processes (non-screen) -function stop_bilean { - for serv in bl-api bl-eng bl-sch bl-noti; do - stop_process $serv - done -} - - -# Restore xtrace -$XTRACE - -# Tell emacs to use shell-script-mode -## Local variables: -## mode: shell-script -## End: diff --git a/devstack/plugin.sh b/devstack/plugin.sh deleted file mode 100755 index 4db0f83..0000000 --- a/devstack/plugin.sh +++ /dev/null @@ -1,48 +0,0 @@ -# bilean.sh - Devstack extras script to install bilean - -# Save trace setting -XTRACE=$(set +o | grep xtrace) -set -o xtrace - -echo_summary "bilean's plugin.sh was called..." -source $DEST/bilean/devstack/lib/bilean -(set -o posix; set) - -if is_service_enabled bl-api bl-eng bl-sch bl-noti; then - if [[ "$1" == "stack" && "$2" == "pre-install" ]]; then - echo_summary "Before Installing bilean" - mkdir -p $SCREEN_LOGDIR - elif [[ "$1" == "stack" && "$2" == "install" ]]; then - echo_summary "Installing bilean" - install_bilean - echo_summary "Installing bileanclient" - install_bileanclient - cleanup_bilean - elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then - echo_summary "Configuring bilean" - configure_bilean - - if is_service_enabled key; then - create_bilean_accounts - fi - - elif [[ "$1" == "stack" && "$2" == "extra" ]]; then - # Initialize bilean - init_bilean - - # Start the bilean API and bilean taskmgr components - echo_summary "Starting bilean" - start_bilean - fi - - if [[ "$1" == "unstack" ]]; then - stop_bilean - fi - - if [[ "$1" == "clean" ]]; then - cleanup_bilean - fi -fi - -# Restore xtrace -$XTRACE diff --git a/devstack/settings b/devstack/settings deleted file mode 100644 index c65b315..0000000 --- a/devstack/settings +++ /dev/null @@ -1,41 +0,0 @@ -# Turn on all bilean services by default -# API service -enable_service bl-api -# Engine -enable_service bl-eng -# Scheduler service -enable_service bl-sch -# Notification service -enable_service bl-noti - -# Set up default directories -BILEAN_REPO=${BILEAN_REPO:-${GIT_BASE}/openstack/bilean.git} -BILEAN_BRANCH=${BILEAN_BRANCH:-master} -BILEAN_DIR=$DEST/bilean - -BILEANCLIENT_REPO=${BILEANCLIENT_REPO:-${GIT_BASE}/openstack/python-bileanclient.git} -BILEANCLIENT_BRANCH=${BILEANCLIENT_BRANCH:-master} -BILEANCLIENT_DIR=$DEST/python-bileanclient - -BILEAN_STATE_PATH=${BILEAN_STATE_PATH:=$DATA_DIR/bilean} -BILEAN_AUTH_CACHE_DIR=${BILEAN_AUTH_CACHE_DIR:-/var/cache/bilean} - -BILEAN_CONF_DIR=/etc/bilean -BILEAN_CONF=$BILEAN_CONF_DIR/bilean.conf -BILEAN_POLICY_JSON=$BILEAN_CONF_DIR/policy.json - -# Public facing bits -BILEAN_SERVICE_HOST=${BILEAN_SERVICE_HOST:-$SERVICE_HOST} -BILEAN_SERVICE_PORT=${BILEAN_SERVICE_PORT:-8770} -BILEAN_SERVICE_PROTOCOL=http - -# Support entry points installation of console scripts -if [[ -d $BILEAN_DIR/bin ]]; then - BILEAN_BIN_DIR=$BILEAN_DIR/bin -else - BILEAN_BIN_DIR=$(get_python_exec_prefix) -fi - -# Log all output to files -LOGFILE=$HOME/devstack.log -SCREEN_LOGDIR=$HOME/logs diff --git a/doc/source/conf.py b/doc/source/conf.py deleted file mode 100755 index 997b413..0000000 --- a/doc/source/conf.py +++ /dev/null @@ -1,89 +0,0 @@ -# -*- coding: utf-8 -*- -# 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 datetime -import os -import sys - -sys.path.insert(0, os.path.abspath('../..')) -# -- General configuration ---------------------------------------------------- - -on_rtd = os.environ.get('READTHEDOCS') == 'True' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.doctest", - "sphinx.ext.todo", - "sphinx.ext.coverage", - "sphinx.ext.ifconfig", - "sphinx.ext.viewcode", -] - -# autodoc generation is a bit aggressive and a nuisance when doing heavy -# text edit cycles. -# execute "export SPHINX_DEBUG=1" in your terminal to disable - -# The suffix of source filenames. -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'bilean' -copyright = u'%d, OpenStack Foundation' % datetime.datetime.now().year - -# If true, '()' will be appended to :func: etc. cross-reference text. -add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -add_module_names = True - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# -- Options for HTML output -------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. Major themes that come with -# Sphinx are currently 'default' and 'sphinxdoc'. -# html_theme_path = ["."] -# html_theme = '_theme' -# html_static_path = ['static'] -if not on_rtd: - import oslosphinx - theme_dir = os.path.join(os.path.dirname(oslosphinx.__file__), 'theme') - html_theme_path = [theme_dir] - html_theme = "openstack" -else: - html_theme_path = [] - html_theme = 'default' - -# Output file base name for HTML help builder. -htmlhelp_basename = '%sdoc' % project - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass -# [howto/manual]). -latex_documents = [ - ('index', - '%s.tex' % project, - u'%s Documentation' % project, - u'OpenStack Foundation', 'manual'), -] - -# Example configuration for intersphinx: refer to the Python standard library. -# intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst deleted file mode 100644 index ac7b6bc..0000000 --- a/doc/source/contributing.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../CONTRIBUTING.rst diff --git a/doc/source/index.rst b/doc/source/index.rst deleted file mode 100644 index a0c0a23..0000000 --- a/doc/source/index.rst +++ /dev/null @@ -1,36 +0,0 @@ -.. - 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. - -==================================== -Welcome to the Bilean documentation! -==================================== - -Bilean is a billing service for OpenStack cloud, it provides trigger-type -billing based on other OpenStack services' notification. - -This documentation offers information on how Bilean works and how to -contribute to the project. - -.. toctree:: - :maxdepth: 1 - - overview - install - contributing - -Indices and tables -~~~~~~~~~~~~~~~~~~ - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/doc/source/install.rst b/doc/source/install.rst deleted file mode 100644 index 8ed35b8..0000000 --- a/doc/source/install.rst +++ /dev/null @@ -1,194 +0,0 @@ -.. - 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. - -.. _guide-install: - -============ -Installation -============ - -There are in general two ways to isntall Bilean service: install via DevStack -or install manually. - -Install via DevStack -~~~~~~~~~~~~~~~~~~~~ - -This is the recommended way to install the Bilean service. Please refer to -following detailed instructions. - -1. Download DevStack. - - :: - - git clone https://git.openstack.org/openstack-dev/devstack - cd devstack - -2. Add this repo as an external repository. - - :: - - cat > /opt/stack/devstack/local.conf << END - [[local|localrc]] - enable_plugin bilean https://github.com/openstack/bilean master - END - -3. Add Plugin Configuration Hooks. - - Bilean service is driven using a plugin mechanism for integrating to other - services. Each integrated service may require additional configuration - settings. Typically, you will need to set the notifications driver in each - service's configuration. - - For example, to enable nova service, edit `/etc/nova/nova.conf` and add - following configuration:: - - [oslo_messaging_notifications] - driver = messaging - - Or add following configurations to post config section in `local.conf` to - make devstack automaticlly configure the settings above:: - - [[post-config|$NOVA_CONF]] - [oslo_messaging_notifications] - driver = messaging - -4. Then run devstack normally. - - :: - - cd /opt/stack/devstack - ./stack.sh - -Manual Installation -~~~~~~~~~~~~~~~~~~~ - -Install Bilean Server ---------------------- - -1. Get Bilean source code from Github. - - :: - - $ cd /opt/stack - $ git clone https://github.com/lvdongbing/bilean.git - -2. Install Bilean with required packages. - - :: - - $ cd /opt/stack/bilean - $ sudo pip install -e . - -3. Register Bilean service with keystone. - - This can be done using the :command:`setup-service` script under the - :file:`tools` folder:: - - $ source ~/devstack/openrc admin - $ cd /opt/stack/bilean/tools - $ ./setup-service - -4. Generate configuration file for the Bilean service. - - :: - - $ cd /opt/stack/bilean - $ tox -e genconfig - $ sudo mkdir /etc/bilean - $ sudo cp etc/bilean/api-paste.ini /etc/bilean - $ sudo cp etc/bilean/policy.json /etc/bilean - $ sudo cp etc/bilean/resource_definitions.yaml /etc/bilean - $ sudo cp etc/bilean/bilean.conf.sample /etc/bilean/bilean.conf - -5. Modify configuration file. - - Edit file :file:`/etc/bilean/bilean.conf` according to your system settings. - The most common options to be customized include:: - - [database] - connection = mysql://root:@127.0.0.1/bilean?charset=utf8 - - [keystone_authtoken] - auth_uri = http://:5000/v3 - auth_version = 3 - cafile = /opt/stack/data/ca-bundle.pem - identity_uri = http://:35357 - admin_user = bilean - admin_password = - admin_tenant_name = service - - [authentication] - auth_url = http://:5000/v3 - service_username = bilean - service_password = - service_project_name = service - - [oslo_messaging_rabbit] - rabbit_userid = - rabbit_hosts = - rabbit_password = - -6. Create Bilean Database - - Create Bilean database using the :command:`bilean-db-recreate` script under - the :file:`tools` subdirectory:: - - $ cd /opt/stack/bilean/tools - $ ./bilean-db-recreate - -7. Start bilean services. - - You may need four consoles for the services each:: - - $ bilean-engine --config-file /etc/bilean/bilean.conf - $ bilean-api --config-file /etc/bilean/bilean.conf - $ bilean-scheduler --config-file /etc/bilean/bilean.conf - $ bilean-notification --config-file /etc/bilean/bilean.conf - -Install Bilean Client ---------------------- - -1. Get Bilean client code from OpenStack git repository. - - :: - - $ cd /opt/stack - $ git clone https://git.openstack.org/openstack/python-bileanclient.git - -2. Install Bilean client. - - :: - - $ cd python-bileanclient - $ sudo pip install -e . - -Verify Installation -------------------- - -To check whether Bilean server and Bilean client have been installed -successfully, run command ``bilean user-list`` in a console. The installation -is successful if the command output looks similar to the following. - -:: - - $ bilean user-list - +----------------------------------+--------------------+---------+--------+------+--------+ - | id | name | balance | credit | rate | status | - +----------------------------------+--------------------+---------+--------+------+--------+ - | 675f42b2dd3a456c9890350403bce8cf | admin | 0.0 | 0 | 0.0 | INIT | - | 927fef3da8194718a9179f4775f5f5ce | service | 0.0 | 0 | 0.0 | INIT | - | c688c64711a64d06b90c2b3c5d513dde | demo | 0.0 | 0 | 0.0 | INIT | - | e0504e51bd0d4e8886d06bb3cc3e6e80 | alt_demo | 0.0 | 0 | 0.0 | INIT | - | e9950cf337be47e68a21c9b20b291142 | invisible_to_admin | 0.0 | 0 | 0.0 | INIT | - +----------------------------------+--------------------+---------+--------+------+--------+ - diff --git a/doc/source/overview.rst b/doc/source/overview.rst deleted file mode 100644 index 3aa20cc..0000000 --- a/doc/source/overview.rst +++ /dev/null @@ -1,69 +0,0 @@ -.. - 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. - -.. _guide-overview: - -======== -Overview -======== - -The mission for Bilean project is to provide a generic billing service for -an OpenStack cloud, it implements trigger-type billing based on other -OpenStack services' notification. - -Components -~~~~~~~~~~ - -The developers are focusing on creating an OpenStack style project using -OpenStack design tenets, implemented in Python. We have started with a close -interaction with Heat project. - -bilean ------- - -The :program:`bilean` tool is A CLI communicates with the :program:`bilean-api` -to manage rules, policies, users, resources, jobs and events. End developers -could also use the Bilean REST API directly. - -bilean-api ----------- - -The :program:`bilean-api` component provides an OpenStack-native REST API that -processes API requests by sending them to the :program:`bilean-engine` over RPC. - -bilean-scheduler ----------------- - -The :program:`bilean-scheduler` manages jobs related to users and scheduler job -to engine to execute it. - -bilean-engine -------------- - -The :program:`bilean-engine` does the main billing work, operates all users, -rules, policies, resources, jobs and events. - -bilean-notification -------------------- - -The :program:`bilean-notification` component monitors the message bus for data -provided by other OpenStack components such as Nova, then converts notifications -into billing resources and sends to :program:`bilean-engine` over AMQP. - - -Installation -~~~~~~~~~~~~ - -You will need to make sure you have a suitable environment for deploying -Bilean. Please refer to :ref:`Installation ` for detailed -instructions on setting up an environment to use the Bilean service. diff --git a/etc/bilean/README-bilean.conf.txt b/etc/bilean/README-bilean.conf.txt deleted file mode 100644 index af0be9a..0000000 --- a/etc/bilean/README-bilean.conf.txt +++ /dev/null @@ -1,4 +0,0 @@ -To generate the sample bilean.conf file, run the following -command from the top level of the bilean directory: - -tox -egenconfig diff --git a/etc/bilean/api-paste.ini b/etc/bilean/api-paste.ini deleted file mode 100644 index 475019b..0000000 --- a/etc/bilean/api-paste.ini +++ /dev/null @@ -1,31 +0,0 @@ -# bilean-api pipeline -[pipeline:bilean-api] -pipeline = request_id faultwrap ssl versionnegotiation authtoken context apiv1app - -[app:apiv1app] -paste.app_factory = bilean.common.wsgi:app_factory -bilean.app_factory = bilean.api.openstack.v1:API - -[filter:versionnegotiation] -paste.filter_factory = bilean.common.wsgi:filter_factory -bilean.filter_factory = bilean.api.openstack:version_negotiation_filter - -[filter:faultwrap] -paste.filter_factory = bilean.common.wsgi:filter_factory -bilean.filter_factory = bilean.api.openstack:faultwrap_filter - -[filter:context] -paste.filter_factory = bilean.common.wsgi:filter_factory -bilean.filter_factory = bilean.api.openstack:contextmiddleware_filter - -[filter:ssl] -paste.filter_factory = bilean.common.wsgi:filter_factory -bilean.filter_factory = bilean.api.openstack:sslmiddleware_filter - -# Auth middleware that validates token against keystone -[filter:authtoken] -paste.filter_factory = keystonemiddleware.auth_token:filter_factory - -# Middleware to set x-openstack-request-id in http response header -[filter:request_id] -paste.filter_factory = oslo_middleware.request_id:RequestId.factory diff --git a/etc/bilean/policy.json b/etc/bilean/policy.json deleted file mode 100644 index d4760fa..0000000 --- a/etc/bilean/policy.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "context_is_admin": "role:admin", - - "users:index": "role:admin", - "users:update": "role:admin", - "users:get": "", - "users:action": "role:admin", - - "resources:index": "", - "resources:get": "", - "resources:validate_creation": "role:admin", - - "rules:index": "role:admin", - "rules:get": "role:admin", - "rules:create": "role:admin", - "rules:update": "role:admin", - "rules:delete": "role:admin", - - "policies:index": "role:admin", - "policies:create": "role:admin", - "policies:get": "role:admin", - "policies:action": "role:admin", - - "consumptions:index": "", - "consumptions:statistics": "", -} diff --git a/etc/bilean/resource_definitions.yaml b/etc/bilean/resource_definitions.yaml deleted file mode 100644 index 8fb3799..0000000 --- a/etc/bilean/resource_definitions.yaml +++ /dev/null @@ -1,16 +0,0 @@ -- event_type: compute.instance.*.end - resources: - - resource_type: os.nova.server - traits: - flavor: - fields: payload.instance_flavor_id - resource_ref: - fields: payload.instance_id -- event_type: volume.*.end - resources: - - resource_type: os.cinder.volume - traits: - size: - fields: payload.size - resource_ref: - fields: payload.volume_id diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 858c5ea..0000000 --- a/requirements.txt +++ /dev/null @@ -1,27 +0,0 @@ -# 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. - -apscheduler>=3.0.1 -cryptography>=1.0 # Apache-2.0 -eventlet>=0.17.4 -jsonpath-rw-ext>=0.1.9 -keystonemiddleware!=4.1.0,>=4.0.0 # Apache-2.0 -openstacksdk>=0.7.4 # Apache-2.0 -oslo.config>=2.7.0 # Apache-2.0 -oslo.context>=0.2.0 # Apache-2.0 -oslo.db>=4.1.0 # Apache-2.0 -oslo.i18n>=1.5.0 # Apache-2.0 -oslo.log>=1.12.0 # Apache-2.0 -oslo.messaging!=2.8.0,!=3.1.0,>2.6.1 # Apache-2.0 -oslo.middleware>=3.0.0 # Apache-2.0 -oslo.policy>=0.5.0 # Apache-2.0 -oslo.serialization>=1.10.0 # Apache-2.0 -oslo.service>=1.0.0 # Apache-2.0 -oslo.utils!=3.1.0,>=2.8.0 # Apache-2.0 -pbr>=1.6 -six>=1.9.0 -SQLAlchemy<1.1.0,>=0.9.9 -sqlalchemy-migrate>=0.9.6 -stevedore>=1.5.0 -WebOb>=1.2.3 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index b7ad1ca..0000000 --- a/setup.cfg +++ /dev/null @@ -1,54 +0,0 @@ -[metadata] -name = bilean -summary = OpenStack Billing Service -description-file = - README.rst -author = OpenStack -author-email = openstack-dev@lists.openstack.org -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 - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.5 - -[files] -packages = - bilean - -[entry_points] -console_scripts = - bilean-api = bilean.cmd.api:main - bilean-engine = bilean.cmd.engine:main - bilean-scheduler = bilean.cmd.scheduler:main - bilean-notification = bilean.cmd.notification:main - bilean-manage = bilean.cmd.manage:main - -oslo.config.opts = - bilean.common.config = bilean.common.config:list_opts - bilean.common.wsgi = bilean.common.wsgi:list_opts - bilean.api.middleware.ssl = bilean.api.middleware.ssl:list_opts - bilean.scheduler.cron_scheduler = bilean.scheduler.cron_scheduler:list_opts - bilean.notification.converter = bilean.notification.converter:list_opts - -bilean.drivers = - openstack = bilean.drivers.openstack - -bilean.plugins = - os.nova.server = bilean.plugins.os.nova.server:ServerPlugin - os.cinder.volume = bilean.plugins.os.cinder.volume:VolumePlugin - -[global] -setup-hooks = - pbr.hooks.setup_hook - -[build_sphinx] -all_files = 1 -build-dir = doc/build -source-dir = doc/source diff --git a/setup.py b/setup.py deleted file mode 100644 index 7363757..0000000 --- a/setup.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python -# 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'], - pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index 334a923..0000000 --- a/test-requirements.txt +++ /dev/null @@ -1,23 +0,0 @@ -# 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. - -# Hacking already pins down pep8, pyflakes and flake8 -coverage>=3.6 -flake8<=2.4.1,>=2.2.4 -hacking<0.11,>=0.10.2 -mock>=1.2 -mox>=0.5.3 -mox3>=0.7.0 -openstackdocstheme>=1.0.3 -oslotest>=1.10.0 # Apache-2.0 -os-testr>=0.4.1 -PyMySQL>=0.6.2 # MIT License -testrepository>=0.0.18 -testscenarios>=0.4 -testtools>=1.4.0 - -# Documentation -oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 -sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 -reno>=0.1.1 # Apache2 diff --git a/tools/bilean-db-recreate b/tools/bilean-db-recreate deleted file mode 100755 index ff1ce5e..0000000 --- a/tools/bilean-db-recreate +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -if [ $# -ne 1 ]; then - echo "Usage: `basename $0` " - exit -fi - -MYSQL_PASSWORD=$1 - -echo "Recreating 'bilean' database." -cat << EOF | mysql -u root --password=${MYSQL_PASSWORD} -DROP DATABASE IF EXISTS bilean; -CREATE DATABASE bilean DEFAULT CHARACTER SET utf8; -EOF - -bilean-manage db_sync diff --git a/tools/config-generator.conf b/tools/config-generator.conf deleted file mode 100644 index 417db4c..0000000 --- a/tools/config-generator.conf +++ /dev/null @@ -1,16 +0,0 @@ -[DEFAULT] -output_file = etc/bilean/bilean.conf.sample -wrap_width = 79 -namespace = bilean.common.config -namespace = bilean.common.wsgi -namespace = bilean.engine.scheduler -namespace = bilean.notification.converter -namespace = bilean.api.middleware.ssl -namespace = keystonemiddleware.auth_token -namespace = oslo.db -namespace = oslo.log -namespace = oslo.messaging -namespace = oslo.policy -namespace = oslo.service.periodic_task -namespace = oslo.service.service -namespace = oslo.service.sslutils diff --git a/tools/gen-config b/tools/gen-config deleted file mode 100755 index 61eb6b3..0000000 --- a/tools/gen-config +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -oslo-config-generator --config-file=tools/config-generator.conf diff --git a/tools/setup-service b/tools/setup-service deleted file mode 100755 index 2484b28..0000000 --- a/tools/setup-service +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/bash - -if [[ -z $OS_AUTH_URL ]]; then - echo "This script must have proper environment variables exported." - echo "Please check if you have sourced bileanrc file or openrc file if " - echo "you are using devstack." - exit -1 -fi - -if [ $OS_USERNAME != 'admin' ]; then - echo "This script has to be executed as an 'admin' user." - echo "Please set environment variable OS_USERNAME to 'admin'." - exit -1 -fi - -if [ $# -ne 2 ]; then - echo "Usage: `basename $0` " - exit -1 -fi - -PORT=8770 -HOST=$1 # Put your host IP here -SVC_PASSWD=$2 - -SERVICE_ID=$(openstack service show bilean -f value -cid 2>/dev/null) -if [[ -z $SERVICE_ID ]]; then - SERVICE_ID=$(openstack service create \ - --name bilean \ - --description 'Billing Service V1' \ - -f value -cid \ - billing) -fi - -if [[ -z $SERVICE_ID ]]; then - exit -fi - -openstack endpoint create \ - --adminurl "http://$HOST:$PORT/v1" \ - --publicurl "http://$HOST:$PORT/v1" \ - --internalurl "http://$HOST:$PORT/v1" \ - --region RegionOne \ - bilean - -openstack user create \ - --password "$SVC_PASSWD" \ - --project service \ - bilean - -openstack role add \ - admin \ - --user bilean \ - --project service - -# make sure 'bilean' has 'service' role in 'demo' project -openstack role add \ - service \ - --user bilean \ - --project demo diff --git a/tox.ini b/tox.ini deleted file mode 100644 index a13966e..0000000 --- a/tox.ini +++ /dev/null @@ -1,42 +0,0 @@ -[tox] -minversion = 1.6 -envlist = py35,py27,pep8 -skipsdist = True - -[testenv] -usedevelop = True -setenv = VIRTUAL_ENV={envdir} - PYTHONHASHSEED=0 -deps = -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt - -install_command = pip install {opts} {packages} -commands = - find . -type f -name "*.pyc" -delete - ostestr --slowest {posargs} - -whitelist_externals = bash - find - -[testenv:pep8] -commands = - flake8 {posargs} - -[testenv:genconfig] -envdir = {toxworkdir}/venv -commands = - {toxinidir}/tools/gen-config - -[testenv:venv] -commands = {posargs} - -[testenv:docs] -commands = python setup.py build_sphinx - -[flake8] -ignore = E731, E402 -exclude = .venv,.git,.tox,cover,dist,*lib/python*,*egg,tools,build -max-complexity=20 - -[hacking] -import_exceptions = bilean.common.i18n