From cc6639ad63e80f50012b24695619e5d771462031 Mon Sep 17 00:00:00 2001 From: Julien Danjou Date: Wed, 22 Nov 2017 10:08:58 +0100 Subject: Retire ceilometerclient Depends-On: Ic2947fd066a2df685d52539d0756cd981cc16113 Change-Id: I65738a55db82053643f7580eee20b63df52b957f --- .gitignore | 16 - .gitreview | 4 - .testr.conf | 4 - CONTRIBUTING.rst | 16 - LICENSE | 175 -- README | 10 + README.rst | 28 - ceilometerclient/__init__.py | 21 - ceilometerclient/apiclient/__init__.py | 0 ceilometerclient/apiclient/auth.py | 231 -- ceilometerclient/apiclient/base.py | 535 ----- ceilometerclient/apiclient/client.py | 388 ---- ceilometerclient/apiclient/exceptions.py | 477 ----- ceilometerclient/apiclient/fake_client.py | 190 -- ceilometerclient/apiclient/utils.py | 100 - ceilometerclient/client.py | 480 ----- ceilometerclient/common/__init__.py | 0 ceilometerclient/common/base.py | 106 - ceilometerclient/common/utils.py | 204 -- ceilometerclient/exc.py | 144 -- ceilometerclient/i18n.py | 27 - ceilometerclient/shell.py | 287 --- ceilometerclient/tests/__init__.py | 0 ceilometerclient/tests/functional/__init__.py | 0 ceilometerclient/tests/functional/base.py | 38 - .../tests/functional/hooks/post_test_hook.sh | 50 - .../tests/functional/test_readonly_ceilometer.py | 67 - ceilometerclient/tests/unit/__init__.py | 0 ceilometerclient/tests/unit/test_client.py | 430 ---- ceilometerclient/tests/unit/test_exc.py | 88 - .../tests/unit/test_openstack_common.py | 45 - ceilometerclient/tests/unit/test_shell.py | 287 --- ceilometerclient/tests/unit/test_utils.py | 298 --- ceilometerclient/tests/unit/utils.py | 24 - ceilometerclient/tests/unit/v2/__init__.py | 0 ceilometerclient/tests/unit/v2/test_alarms.py | 559 ----- .../tests/unit/v2/test_capabilities.py | 58 - ceilometerclient/tests/unit/v2/test_event_types.py | 50 - ceilometerclient/tests/unit/v2/test_events.py | 198 -- ceilometerclient/tests/unit/v2/test_options.py | 253 --- .../tests/unit/v2/test_query_alarm_history.py | 64 - .../tests/unit/v2/test_query_alarms.py | 74 - .../tests/unit/v2/test_query_samples.py | 67 - ceilometerclient/tests/unit/v2/test_resources.py | 147 -- ceilometerclient/tests/unit/v2/test_samples.py | 247 --- ceilometerclient/tests/unit/v2/test_shell.py | 1977 ------------------ ceilometerclient/tests/unit/v2/test_statistics.py | 224 -- .../tests/unit/v2/test_trait_descriptions.py | 56 - ceilometerclient/tests/unit/v2/test_traits.py | 62 - ceilometerclient/v2/__init__.py | 16 - ceilometerclient/v2/alarms.py | 185 -- ceilometerclient/v2/capabilities.py | 29 - ceilometerclient/v2/client.py | 132 -- ceilometerclient/v2/event_types.py | 30 - ceilometerclient/v2/events.py | 42 - ceilometerclient/v2/meters.py | 38 - ceilometerclient/v2/options.py | 129 -- ceilometerclient/v2/query.py | 54 - ceilometerclient/v2/resources.py | 45 - ceilometerclient/v2/samples.py | 106 - ceilometerclient/v2/shell.py | 1285 ------------ ceilometerclient/v2/statistics.py | 58 - ceilometerclient/v2/trait_descriptions.py | 28 - ceilometerclient/v2/traits.py | 28 - doc/ext/gen_ref.py | 54 - doc/source/api.rst | 47 - doc/source/cli.rst | 2202 -------------------- doc/source/conf.py | 85 - doc/source/index.rst | 56 - doc/source/shell.rst | 61 - releasenotes/notes/.placeholder | 0 .../notes/alarm_deprecated-74363d70d48a20e2.yaml | 3 - .../notes/deprecation-44ae455f4ef3a81e.yaml | 6 - .../notes/panko-redirect-9d03598dbf51f8fd.yaml | 7 - releasenotes/source/_static/.placeholder | 0 releasenotes/source/_templates/.placeholder | 0 releasenotes/source/conf.py | 284 --- releasenotes/source/index.rst | 21 - releasenotes/source/liberty.rst | 6 - releasenotes/source/mitaka.rst | 6 - releasenotes/source/newton.rst | 6 - releasenotes/source/ocata.rst | 6 - releasenotes/source/pike.rst | 6 - releasenotes/source/unreleased.rst | 5 - requirements.txt | 13 - setup.cfg | 42 - setup.py | 29 - test-requirements.txt | 14 - tools/ceilometer.bash_completion | 27 - tox.ini | 45 - 90 files changed, 10 insertions(+), 14032 deletions(-) delete mode 100644 .gitignore delete mode 100644 .gitreview 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 ceilometerclient/__init__.py delete mode 100644 ceilometerclient/apiclient/__init__.py delete mode 100644 ceilometerclient/apiclient/auth.py delete mode 100644 ceilometerclient/apiclient/base.py delete mode 100644 ceilometerclient/apiclient/client.py delete mode 100644 ceilometerclient/apiclient/exceptions.py delete mode 100644 ceilometerclient/apiclient/fake_client.py delete mode 100644 ceilometerclient/apiclient/utils.py delete mode 100644 ceilometerclient/client.py delete mode 100644 ceilometerclient/common/__init__.py delete mode 100644 ceilometerclient/common/base.py delete mode 100644 ceilometerclient/common/utils.py delete mode 100644 ceilometerclient/exc.py delete mode 100644 ceilometerclient/i18n.py delete mode 100644 ceilometerclient/shell.py delete mode 100644 ceilometerclient/tests/__init__.py delete mode 100644 ceilometerclient/tests/functional/__init__.py delete mode 100644 ceilometerclient/tests/functional/base.py delete mode 100755 ceilometerclient/tests/functional/hooks/post_test_hook.sh delete mode 100644 ceilometerclient/tests/functional/test_readonly_ceilometer.py delete mode 100644 ceilometerclient/tests/unit/__init__.py delete mode 100644 ceilometerclient/tests/unit/test_client.py delete mode 100644 ceilometerclient/tests/unit/test_exc.py delete mode 100644 ceilometerclient/tests/unit/test_openstack_common.py delete mode 100644 ceilometerclient/tests/unit/test_shell.py delete mode 100644 ceilometerclient/tests/unit/test_utils.py delete mode 100644 ceilometerclient/tests/unit/utils.py delete mode 100644 ceilometerclient/tests/unit/v2/__init__.py delete mode 100644 ceilometerclient/tests/unit/v2/test_alarms.py delete mode 100644 ceilometerclient/tests/unit/v2/test_capabilities.py delete mode 100644 ceilometerclient/tests/unit/v2/test_event_types.py delete mode 100644 ceilometerclient/tests/unit/v2/test_events.py delete mode 100644 ceilometerclient/tests/unit/v2/test_options.py delete mode 100644 ceilometerclient/tests/unit/v2/test_query_alarm_history.py delete mode 100644 ceilometerclient/tests/unit/v2/test_query_alarms.py delete mode 100644 ceilometerclient/tests/unit/v2/test_query_samples.py delete mode 100644 ceilometerclient/tests/unit/v2/test_resources.py delete mode 100644 ceilometerclient/tests/unit/v2/test_samples.py delete mode 100644 ceilometerclient/tests/unit/v2/test_shell.py delete mode 100644 ceilometerclient/tests/unit/v2/test_statistics.py delete mode 100644 ceilometerclient/tests/unit/v2/test_trait_descriptions.py delete mode 100644 ceilometerclient/tests/unit/v2/test_traits.py delete mode 100644 ceilometerclient/v2/__init__.py delete mode 100644 ceilometerclient/v2/alarms.py delete mode 100644 ceilometerclient/v2/capabilities.py delete mode 100644 ceilometerclient/v2/client.py delete mode 100644 ceilometerclient/v2/event_types.py delete mode 100644 ceilometerclient/v2/events.py delete mode 100644 ceilometerclient/v2/meters.py delete mode 100644 ceilometerclient/v2/options.py delete mode 100644 ceilometerclient/v2/query.py delete mode 100644 ceilometerclient/v2/resources.py delete mode 100644 ceilometerclient/v2/samples.py delete mode 100644 ceilometerclient/v2/shell.py delete mode 100644 ceilometerclient/v2/statistics.py delete mode 100644 ceilometerclient/v2/trait_descriptions.py delete mode 100644 ceilometerclient/v2/traits.py delete mode 100644 doc/ext/gen_ref.py delete mode 100644 doc/source/api.rst delete mode 100644 doc/source/cli.rst delete mode 100644 doc/source/conf.py delete mode 100644 doc/source/index.rst delete mode 100644 doc/source/shell.rst delete mode 100644 releasenotes/notes/.placeholder delete mode 100644 releasenotes/notes/alarm_deprecated-74363d70d48a20e2.yaml delete mode 100644 releasenotes/notes/deprecation-44ae455f4ef3a81e.yaml delete mode 100644 releasenotes/notes/panko-redirect-9d03598dbf51f8fd.yaml delete mode 100644 releasenotes/source/_static/.placeholder delete mode 100644 releasenotes/source/_templates/.placeholder delete mode 100644 releasenotes/source/conf.py delete mode 100644 releasenotes/source/index.rst delete mode 100644 releasenotes/source/liberty.rst delete mode 100644 releasenotes/source/mitaka.rst delete mode 100644 releasenotes/source/newton.rst delete mode 100644 releasenotes/source/ocata.rst delete mode 100644 releasenotes/source/pike.rst delete mode 100644 releasenotes/source/unreleased.rst delete mode 100644 requirements.txt delete mode 100644 setup.cfg delete mode 100644 setup.py delete mode 100644 test-requirements.txt delete mode 100644 tools/ceilometer.bash_completion delete mode 100644 tox.ini diff --git a/.gitignore b/.gitignore deleted file mode 100644 index f7e0f31..0000000 --- a/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -*.pyc -*.egg-info -build -.coverage -.tox -cover -.testrepository -.venv -doc/build -releasenotes/build -doc/source/ref -subunit.log -AUTHORS -ChangeLog -dist -*.egg diff --git a/.gitreview b/.gitreview deleted file mode 100644 index c4b5cc4..0000000 --- a/.gitreview +++ /dev/null @@ -1,4 +0,0 @@ -[gerrit] -host=review.openstack.org -port=29418 -project=openstack/python-ceilometerclient.git diff --git a/.testr.conf b/.testr.conf deleted file mode 100644 index 77a2a70..0000000 --- a/.testr.conf +++ /dev/null @@ -1,4 +0,0 @@ -[DEFAULT] -test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./ceilometerclient/tests/unit} $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 b5abcbd..0000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1,16 +0,0 @@ -If you would like to contribute to the development of OpenStack, -you must follow the steps documented at: - - https://docs.openstack.org/infra/manual/developers.html#development-workflow - -Once those steps have been completed, changes to OpenStack -should be submitted for review via the Gerrit tool, following -the workflow documented at: - - https://docs.openstack.org/infra/manual/developers.html#development-workflow - -Pull requests submitted through GitHub will be ignored. - -Bugs should be filed on Launchpad, not GitHub: - - https://bugs.launchpad.net/python-ceilometerclient diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 67db858..0000000 --- a/LICENSE +++ /dev/null @@ -1,175 +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..d98af48 --- /dev/null +++ b/README @@ -0,0 +1,10 @@ +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-dev@lists.openstack.org or join #openstack-dev on +Freenode. diff --git a/README.rst b/README.rst deleted file mode 100644 index d1dccd5..0000000 --- a/README.rst +++ /dev/null @@ -1,28 +0,0 @@ -Python bindings to the Ceilometer API -===================================== - -.. image:: https://img.shields.io/pypi/v/python-ceilometerclient.svg - :target: https://pypi.python.org/pypi/python-ceilometerclient/ - :alt: Latest Version - -.. image:: https://img.shields.io/pypi/dm/python-ceilometerclient.svg - :target: https://pypi.python.org/pypi/python-ceilometerclient/ - :alt: Downloads - -This is a client library for Ceilometer built on the Ceilometer API. It -provides a Python API (the ``ceilometerclient`` module) and a command-line tool -(``ceilometer``). - -* `PyPi`_ - package installation -* `Online Documentation`_ -* `Launchpad project`_ - release management -* `Blueprints`_ - feature specifications -* `Bugs`_ - issue tracking -* `Source`_ - -.. _PyPi: https://pypi.python.org/pypi/python-ceilometerclient -.. _Online Documentation: https://docs.openstack.org/python-ceilometerclient/latest/ -.. _Launchpad project: https://launchpad.net/python-ceilometerclient -.. _Blueprints: https://blueprints.launchpad.net/python-ceilometerclient -.. _Bugs: https://bugs.launchpad.net/python-ceilometerclient -.. _Source: https://git.openstack.org/cgit/openstack/python-ceilometerclient diff --git a/ceilometerclient/__init__.py b/ceilometerclient/__init__.py deleted file mode 100644 index d6efb69..0000000 --- a/ceilometerclient/__init__.py +++ /dev/null @@ -1,21 +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. - -__all__ = ['__version__'] - -import pbr.version - -version_info = pbr.version.VersionInfo('python-ceilometerclient') -try: - __version__ = version_info.version_string() -except AttributeError: - __version__ = None diff --git a/ceilometerclient/apiclient/__init__.py b/ceilometerclient/apiclient/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ceilometerclient/apiclient/auth.py b/ceilometerclient/apiclient/auth.py deleted file mode 100644 index 41d118f..0000000 --- a/ceilometerclient/apiclient/auth.py +++ /dev/null @@ -1,231 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# Copyright 2013 Spanish National Research Council. -# 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. - -# E0202: An attribute inherited from %s hide this method -# pylint: disable=E0202 - -######################################################################## -# -# THIS MODULE IS DEPRECATED -# -# Please refer to -# https://etherpad.openstack.org/p/kilo-ceilometerclient-library-proposals for -# the discussion leading to this deprecation. -# -# We recommend checking out the python-openstacksdk project -# (https://launchpad.net/python-openstacksdk) instead. -# -######################################################################## - -import abc -import argparse -import os - -import six -from stevedore import extension - -from ceilometerclient.apiclient import exceptions - - -_discovered_plugins = {} - - -def discover_auth_systems(): - """Discover the available auth-systems. - - This won't take into account the old style auth-systems. - """ - global _discovered_plugins - _discovered_plugins = {} - - def add_plugin(ext): - _discovered_plugins[ext.name] = ext.plugin - - ep_namespace = "ceilometerclient.apiclient.auth" - mgr = extension.ExtensionManager(ep_namespace) - mgr.map(add_plugin) - - -def load_auth_system_opts(parser): - """Load options needed by the available auth-systems into a parser. - - This function will try to populate the parser with options from the - available plugins. - """ - group = parser.add_argument_group("Common auth options") - BaseAuthPlugin.add_common_opts(group) - for name, auth_plugin in six.iteritems(_discovered_plugins): - group = parser.add_argument_group( - "Auth-system '%s' options" % name, - conflict_handler="resolve") - auth_plugin.add_opts(group) - - -def load_plugin(auth_system): - try: - plugin_class = _discovered_plugins[auth_system] - except KeyError: - raise exceptions.AuthSystemNotFound(auth_system) - return plugin_class(auth_system=auth_system) - - -def load_plugin_from_args(args): - """Load required plugin and populate it with options. - - Try to guess auth system if it is not specified. Systems are tried in - alphabetical order. - - :type args: argparse.Namespace - :raises: AuthPluginOptionsMissing - """ - auth_system = args.os_auth_system - if auth_system: - plugin = load_plugin(auth_system) - plugin.parse_opts(args) - plugin.sufficient_options() - return plugin - - for plugin_auth_system in sorted(six.iterkeys(_discovered_plugins)): - plugin_class = _discovered_plugins[plugin_auth_system] - plugin = plugin_class() - plugin.parse_opts(args) - try: - plugin.sufficient_options() - except exceptions.AuthPluginOptionsMissing: - continue - return plugin - raise exceptions.AuthPluginOptionsMissing(["auth_system"]) - - -@six.add_metaclass(abc.ABCMeta) -class BaseAuthPlugin(object): - """Base class for authentication plugins. - - An authentication plugin needs to override at least the authenticate - method to be a valid plugin. - """ - - auth_system = None - opt_names = [] - common_opt_names = [ - "auth_system", - "username", - "password", - "tenant_name", - "token", - "auth_url", - ] - - def __init__(self, auth_system=None, **kwargs): - self.auth_system = auth_system or self.auth_system - self.opts = dict((name, kwargs.get(name)) - for name in self.opt_names) - - @staticmethod - def _parser_add_opt(parser, opt): - """Add an option to parser in two variants. - - :param opt: option name (with underscores) - """ - dashed_opt = opt.replace("_", "-") - env_var = "OS_%s" % opt.upper() - arg_default = os.environ.get(env_var, "") - arg_help = "Defaults to env[%s]." % env_var - parser.add_argument( - "--os-%s" % dashed_opt, - metavar="<%s>" % dashed_opt, - default=arg_default, - help=arg_help) - parser.add_argument( - "--os_%s" % opt, - metavar="<%s>" % dashed_opt, - help=argparse.SUPPRESS) - - @classmethod - def add_opts(cls, parser): - """Populate the parser with the options for this plugin.""" - for opt in cls.opt_names: - # use `BaseAuthPlugin.common_opt_names` since it is never - # changed in child classes - if opt not in BaseAuthPlugin.common_opt_names: - cls._parser_add_opt(parser, opt) - - @classmethod - def add_common_opts(cls, parser): - """Add options that are common for several plugins.""" - for opt in cls.common_opt_names: - cls._parser_add_opt(parser, opt) - - @staticmethod - def get_opt(opt_name, args): - """Return option name and value. - - :param opt_name: name of the option, e.g., "username" - :param args: parsed arguments - """ - return (opt_name, getattr(args, "os_%s" % opt_name, None)) - - def parse_opts(self, args): - """Parse the actual auth-system options if any. - - This method is expected to populate the attribute `self.opts` with a - dict containing the options and values needed to make authentication. - """ - self.opts.update(dict(self.get_opt(opt_name, args) - for opt_name in self.opt_names)) - - def authenticate(self, http_client): - """Authenticate using plugin defined method. - - The method usually analyses `self.opts` and performs - a request to authentication server. - - :param http_client: client object that needs authentication - :type http_client: HTTPClient - :raises: AuthorizationFailure - """ - self.sufficient_options() - self._do_authenticate(http_client) - - @abc.abstractmethod - def _do_authenticate(self, http_client): - """Protected method for authentication.""" - - def sufficient_options(self): - """Check if all required options are present. - - :raises: AuthPluginOptionsMissing - """ - missing = [opt - for opt in self.opt_names - if not self.opts.get(opt)] - if missing: - raise exceptions.AuthPluginOptionsMissing(missing) - - @abc.abstractmethod - def token_and_endpoint(self, endpoint_type, service_type): - """Return token and endpoint. - - :param service_type: Service type of the endpoint - :type service_type: string - :param endpoint_type: Type of endpoint. - Possible values: public or publicURL, - internal or internalURL, - admin or adminURL - :type endpoint_type: string - :returns: tuple of token and endpoint strings - :raises: EndpointException - """ diff --git a/ceilometerclient/apiclient/base.py b/ceilometerclient/apiclient/base.py deleted file mode 100644 index 6e9990a..0000000 --- a/ceilometerclient/apiclient/base.py +++ /dev/null @@ -1,535 +0,0 @@ -# Copyright 2010 Jacob Kaplan-Moss -# Copyright 2011 OpenStack Foundation -# Copyright 2012 Grid Dynamics -# Copyright 2013 OpenStack Foundation -# 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. - -""" -Base utilities to build API operation managers and objects on top of. -""" - -######################################################################## -# -# THIS MODULE IS DEPRECATED -# -# Please refer to -# https://etherpad.openstack.org/p/kilo-ceilometerclient-library-proposals for -# the discussion leading to this deprecation. -# -# We recommend checking out the python-openstacksdk project -# (https://launchpad.net/python-openstacksdk) instead. -# -######################################################################## - - -# E1102: %s is not callable -# pylint: disable=E1102 - -import abc -import copy - -from oslo_utils import reflection -from oslo_utils import strutils -import six -from six.moves.urllib import parse - -from ceilometerclient.apiclient import exceptions -from ceilometerclient.i18n import _ - - -def getid(obj): - """Return id if argument is a Resource. - - Abstracts the common pattern of allowing both an object or an object's ID - (UUID) as a parameter when dealing with relationships. - """ - try: - if obj.uuid: - return obj.uuid - except AttributeError: - pass - try: - return obj.id - except AttributeError: - return obj - - -# TODO(aababilov): call run_hooks() in HookableMixin's child classes -class HookableMixin(object): - """Mixin so classes can register and run hooks.""" - _hooks_map = {} - - @classmethod - def add_hook(cls, hook_type, hook_func): - """Add a new hook of specified type. - - :param cls: class that registers hooks - :param hook_type: hook type, e.g., '__pre_parse_args__' - :param hook_func: hook function - """ - if hook_type not in cls._hooks_map: - cls._hooks_map[hook_type] = [] - - cls._hooks_map[hook_type].append(hook_func) - - @classmethod - def run_hooks(cls, hook_type, *args, **kwargs): - """Run all hooks of specified type. - - :param cls: class that registers hooks - :param hook_type: hook type, e.g., '__pre_parse_args__' - :param args: args to be passed to every hook function - :param kwargs: kwargs to be passed to every hook function - """ - hook_funcs = cls._hooks_map.get(hook_type) or [] - for hook_func in hook_funcs: - hook_func(*args, **kwargs) - - -class BaseManager(HookableMixin): - """Basic manager type providing common operations. - - Managers interact with a particular type of API (servers, flavors, images, - etc.) and provide CRUD operations for them. - """ - resource_class = None - - def __init__(self, client): - """Initializes BaseManager with `client`. - - :param client: instance of BaseClient descendant for HTTP requests - """ - super(BaseManager, self).__init__() - self.client = client - - def _list(self, url, response_key=None, obj_class=None, json=None): - """List the collection. - - :param url: a partial URL, e.g., '/servers' - :param response_key: the key to be looked up in response dictionary, - e.g., 'servers'. If response_key is None - all response body - will be used. - :param obj_class: class for constructing the returned objects - (self.resource_class will be used by default) - :param json: data that will be encoded as JSON and passed in POST - request (GET will be sent by default) - """ - if json: - body = self.client.post(url, json=json).json() - else: - body = self.client.get(url).json() - - if obj_class is None: - obj_class = self.resource_class - - data = body[response_key] if response_key is not None else body - # NOTE(ja): keystone returns values as list as {'values': [ ... ]} - # unlike other services which just return the list... - try: - data = data['values'] - except (KeyError, TypeError): - pass - - return [obj_class(self, res, loaded=True) for res in data if res] - - def _get(self, url, response_key=None): - """Get an object from collection. - - :param url: a partial URL, e.g., '/servers' - :param response_key: the key to be looked up in response dictionary, - e.g., 'server'. If response_key is None - all response body - will be used. - """ - body = self.client.get(url).json() - data = body[response_key] if response_key is not None else body - return self.resource_class(self, data, loaded=True) - - def _head(self, url): - """Retrieve request headers for an object. - - :param url: a partial URL, e.g., '/servers' - """ - resp = self.client.head(url) - return resp.status_code == 204 - - def _post(self, url, json, response_key=None, return_raw=False): - """Create an object. - - :param url: a partial URL, e.g., '/servers' - :param json: data that will be encoded as JSON and passed in POST - request (GET will be sent by default) - :param response_key: the key to be looked up in response dictionary, - e.g., 'server'. If response_key is None - all response body - will be used. - :param return_raw: flag to force returning raw JSON instead of - Python object of self.resource_class - """ - body = self.client.post(url, json=json).json() - data = body[response_key] if response_key is not None else body - if return_raw: - return data - return self.resource_class(self, data) - - def _put(self, url, json=None, response_key=None): - """Update an object with PUT method. - - :param url: a partial URL, e.g., '/servers' - :param json: data that will be encoded as JSON and passed in POST - request (GET will be sent by default) - :param response_key: the key to be looked up in response dictionary, - e.g., 'servers'. If response_key is None - all response body - will be used. - """ - resp = self.client.put(url, json=json) - # PUT requests may not return a body - if resp.content: - body = resp.json() - if response_key is not None: - return self.resource_class(self, body[response_key]) - else: - return self.resource_class(self, body) - - def _patch(self, url, json=None, response_key=None): - """Update an object with PATCH method. - - :param url: a partial URL, e.g., '/servers' - :param json: data that will be encoded as JSON and passed in POST - request (GET will be sent by default) - :param response_key: the key to be looked up in response dictionary, - e.g., 'servers'. If response_key is None - all response body - will be used. - """ - body = self.client.patch(url, json=json).json() - if response_key is not None: - return self.resource_class(self, body[response_key]) - else: - return self.resource_class(self, body) - - def _delete(self, url): - """Delete an object. - - :param url: a partial URL, e.g., '/servers/my-server' - """ - return self.client.delete(url) - - -@six.add_metaclass(abc.ABCMeta) -class ManagerWithFind(BaseManager): - """Manager with additional `find()`/`findall()` methods.""" - - @abc.abstractmethod - def list(self): - pass - - def find(self, **kwargs): - """Find a single item with attributes matching ``**kwargs``. - - This isn't very efficient: it loads the entire list then filters on - the Python side. - """ - matches = self.findall(**kwargs) - num_matches = len(matches) - if num_matches == 0: - msg = _("No %(name)s matching %(args)s.") % { - 'name': self.resource_class.__name__, - 'args': kwargs - } - raise exceptions.NotFound(msg) - elif num_matches > 1: - raise exceptions.NoUniqueMatch() - else: - return matches[0] - - def findall(self, **kwargs): - """Find all items with attributes matching ``**kwargs``. - - This isn't very efficient: it loads the entire list then filters on - the Python side. - """ - found = [] - searches = kwargs.items() - - for obj in self.list(): - try: - if all(getattr(obj, attr) == value - for (attr, value) in searches): - found.append(obj) - except AttributeError: - continue - - return found - - -class CrudManager(BaseManager): - """Base manager class for manipulating entities. - - Children of this class are expected to define a `collection_key` and `key`. - - - `collection_key`: Usually a plural noun by convention (e.g. `entities`); - used to refer collections in both URL's (e.g. `/v3/entities`) and JSON - objects containing a list of member resources (e.g. `{'entities': [{}, - {}, {}]}`). - - `key`: Usually a singular noun by convention (e.g. `entity`); used to - refer to an individual member of the collection. - - """ - collection_key = None - key = None - - def build_url(self, base_url=None, **kwargs): - """Builds a resource URL for the given kwargs. - - Given an example collection where `collection_key = 'entities'` and - `key = 'entity'`, the following URL's could be generated. - - By default, the URL will represent a collection of entities, e.g.:: - - /entities - - If kwargs contains an `entity_id`, then the URL will represent a - specific member, e.g.:: - - /entities/{entity_id} - - :param base_url: if provided, the generated URL will be appended to it - """ - url = base_url if base_url is not None else '' - - url += '/%s' % self.collection_key - - # do we have a specific entity? - entity_id = kwargs.get('%s_id' % self.key) - if entity_id is not None: - url += '/%s' % entity_id - - return url - - def _filter_kwargs(self, kwargs): - """Drop null values and handle ids.""" - for key, ref in six.iteritems(kwargs.copy()): - if ref is None: - kwargs.pop(key) - else: - if isinstance(ref, Resource): - kwargs.pop(key) - kwargs['%s_id' % key] = getid(ref) - return kwargs - - def create(self, **kwargs): - kwargs = self._filter_kwargs(kwargs) - return self._post( - self.build_url(**kwargs), - {self.key: kwargs}, - self.key) - - def get(self, **kwargs): - kwargs = self._filter_kwargs(kwargs) - return self._get( - self.build_url(**kwargs), - self.key) - - def head(self, **kwargs): - kwargs = self._filter_kwargs(kwargs) - return self._head(self.build_url(**kwargs)) - - def list(self, base_url=None, **kwargs): - """List the collection. - - :param base_url: if provided, the generated URL will be appended to it - """ - kwargs = self._filter_kwargs(kwargs) - - return self._list( - '%(base_url)s%(query)s' % { - 'base_url': self.build_url(base_url=base_url, **kwargs), - 'query': '?%s' % parse.urlencode(kwargs) if kwargs else '', - }, - self.collection_key) - - def put(self, base_url=None, **kwargs): - """Update an element. - - :param base_url: if provided, the generated URL will be appended to it - """ - kwargs = self._filter_kwargs(kwargs) - - return self._put(self.build_url(base_url=base_url, **kwargs)) - - def update(self, **kwargs): - kwargs = self._filter_kwargs(kwargs) - params = kwargs.copy() - params.pop('%s_id' % self.key) - - return self._patch( - self.build_url(**kwargs), - {self.key: params}, - self.key) - - def delete(self, **kwargs): - kwargs = self._filter_kwargs(kwargs) - - return self._delete( - self.build_url(**kwargs)) - - def find(self, base_url=None, **kwargs): - """Find a single item with attributes matching ``**kwargs``. - - :param base_url: if provided, the generated URL will be appended to it - """ - kwargs = self._filter_kwargs(kwargs) - - rl = self._list( - '%(base_url)s%(query)s' % { - 'base_url': self.build_url(base_url=base_url, **kwargs), - 'query': '?%s' % parse.urlencode(kwargs) if kwargs else '', - }, - self.collection_key) - num = len(rl) - - if num == 0: - msg = _("No %(name)s matching %(args)s.") % { - 'name': self.resource_class.__name__, - 'args': kwargs - } - raise exceptions.NotFound(404, msg) - elif num > 1: - raise exceptions.NoUniqueMatch - else: - return rl[0] - - -class Extension(HookableMixin): - """Extension descriptor.""" - - SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__') - manager_class = None - - def __init__(self, name, module): - super(Extension, self).__init__() - self.name = name - self.module = module - self._parse_extension_module() - - def _parse_extension_module(self): - self.manager_class = None - for attr_name, attr_value in self.module.__dict__.items(): - if attr_name in self.SUPPORTED_HOOKS: - self.add_hook(attr_name, attr_value) - else: - try: - if issubclass(attr_value, BaseManager): - self.manager_class = attr_value - except TypeError: - pass - - def __repr__(self): - return "" % self.name - - -class Resource(object): - """Base class for OpenStack resources (tenant, user, etc.). - - This is pretty much just a bag for attributes. - """ - - HUMAN_ID = False - NAME_ATTR = 'name' - - def __init__(self, manager, info, loaded=False): - """Populate and bind to a manager. - - :param manager: BaseManager object - :param info: dictionary representing resource attributes - :param loaded: prevent lazy-loading if set to True - """ - self.manager = manager - self._info = info - self._add_details(info) - self._loaded = loaded - - def __repr__(self): - reprkeys = sorted(k - for k in self.__dict__.keys() - if k[0] != '_' and k != 'manager') - info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) - self_cls_name = reflection.get_class_name(self, - fully_qualified=False) - return "<%s %s>" % (self_cls_name, info) - - @property - def human_id(self): - """Human-readable ID which can be used for bash completion.""" - if self.HUMAN_ID: - name = getattr(self, self.NAME_ATTR, None) - if name is not None: - return strutils.to_slug(name) - return None - - def _add_details(self, info): - for (k, v) in six.iteritems(info): - try: - setattr(self, k, v) - self._info[k] = v - except AttributeError: - # In this case we already defined the attribute on the class - pass - - def __getattr__(self, k): - if k not in self.__dict__: - # NOTE(bcwaldon): disallow lazy-loading if already loaded once - if not self.is_loaded(): - self.get() - return self.__getattr__(k) - - raise AttributeError(k) - else: - return self.__dict__[k] - - def get(self): - """Support for lazy loading details. - - Some clients, such as novaclient have the option to lazy load the - details, details which can be loaded with this function. - """ - # set_loaded() first ... so if we have to bail, we know we tried. - self.set_loaded(True) - if not hasattr(self.manager, 'get'): - return - - new = self.manager.get(self.id) - if new: - self._add_details(new._info) - self._add_details( - {'x_request_id': self.manager.client.last_request_id}) - - def __eq__(self, other): - if not isinstance(other, Resource): - return NotImplemented - # two resources of different types are not equal - if not isinstance(other, self.__class__): - return False - return self._info == other._info - - def __ne__(self, other): - return not self.__eq__(other) - - def is_loaded(self): - return self._loaded - - def set_loaded(self, val): - self._loaded = val - - def to_dict(self): - return copy.deepcopy(self._info) diff --git a/ceilometerclient/apiclient/client.py b/ceilometerclient/apiclient/client.py deleted file mode 100644 index df33159..0000000 --- a/ceilometerclient/apiclient/client.py +++ /dev/null @@ -1,388 +0,0 @@ -# Copyright 2010 Jacob Kaplan-Moss -# Copyright 2011 OpenStack Foundation -# Copyright 2011 Piston Cloud Computing, Inc. -# Copyright 2013 Alessio Ababilov -# Copyright 2013 Grid Dynamics -# Copyright 2013 OpenStack Foundation -# 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. - -""" -OpenStack Client interface. Handles the REST calls and responses. -""" - -# E0202: An attribute inherited from %s hide this method -# pylint: disable=E0202 - -import hashlib -import logging -import time - -try: - import simplejson as json -except ImportError: - import json - -from oslo_utils import encodeutils -from oslo_utils import importutils -import requests - -from ceilometerclient.apiclient import exceptions -from ceilometerclient.i18n import _ - -_logger = logging.getLogger(__name__) -SENSITIVE_HEADERS = ('X-Auth-Token', 'X-Subject-Token',) - - -class HTTPClient(object): - """This client handles sending HTTP requests to OpenStack servers. - - Features: - - - share authentication information between several clients to different - services (e.g., for compute and image clients); - - reissue authentication request for expired tokens; - - encode/decode JSON bodies; - - raise exceptions on HTTP errors; - - pluggable authentication; - - store authentication information in a keyring; - - store time spent for requests; - - register clients for particular services, so one can use - `http_client.identity` or `http_client.compute`; - - log requests and responses in a format that is easy to copy-and-paste - into terminal and send the same request with curl. - """ - - user_agent = "ceilometerclient.apiclient" - - def __init__(self, - auth_plugin, - region_name=None, - endpoint_type="publicURL", - original_ip=None, - verify=True, - cert=None, - timeout=None, - timings=False, - keyring_saver=None, - debug=False, - user_agent=None, - http=None): - self.auth_plugin = auth_plugin - - self.endpoint_type = endpoint_type - self.region_name = region_name - - self.original_ip = original_ip - self.timeout = timeout - self.verify = verify - self.cert = cert - - self.keyring_saver = keyring_saver - self.debug = debug - self.user_agent = user_agent or self.user_agent - - self.times = [] # [("item", starttime, endtime), ...] - self.timings = timings - - # requests within the same session can reuse TCP connections from pool - self.http = http or requests.Session() - - self.cached_token = None - self.last_request_id = None - - def _safe_header(self, name, value): - if name in SENSITIVE_HEADERS: - # because in python3 byte string handling is ... ug - v = value.encode('utf-8') - h = hashlib.sha1(v) - d = h.hexdigest() - return encodeutils.safe_decode(name), "{SHA1}%s" % d - else: - return (encodeutils.safe_decode(name), - encodeutils.safe_decode(value)) - - def _http_log_req(self, method, url, kwargs): - if not self.debug: - return - - string_parts = [ - "curl -g -i", - "-X '%s'" % method, - "'%s'" % url, - ] - - for element in kwargs['headers']: - header = ("-H '%s: %s'" % - self._safe_header(element, kwargs['headers'][element])) - string_parts.append(header) - - _logger.debug("REQ: %s", " ".join(string_parts)) - if 'data' in kwargs: - _logger.debug("REQ BODY: %s\n", kwargs['data']) - - def _http_log_resp(self, resp): - if not self.debug: - return - _logger.debug( - "RESP: [%s] %s\n", - resp.status_code, - resp.headers) - if resp._content_consumed: - _logger.debug( - "RESP BODY: %s\n", - resp.text) - - def serialize(self, kwargs): - if kwargs.get('json') is not None: - kwargs['headers']['Content-Type'] = 'application/json' - kwargs['data'] = json.dumps(kwargs['json']) - try: - del kwargs['json'] - except KeyError: - pass - - def get_timings(self): - return self.times - - def reset_timings(self): - self.times = [] - - def request(self, method, url, **kwargs): - """Send an http request with the specified characteristics. - - Wrapper around `requests.Session.request` to handle tasks such as - setting headers, JSON encoding/decoding, and error handling. - - :param method: method of HTTP request - :param url: URL of HTTP request - :param kwargs: any other parameter that can be passed to - requests.Session.request (such as `headers`) or `json` - that will be encoded as JSON and used as `data` argument - """ - kwargs.setdefault("headers", {}) - kwargs["headers"]["User-Agent"] = self.user_agent - if self.original_ip: - kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % ( - self.original_ip, self.user_agent) - if self.timeout is not None: - kwargs.setdefault("timeout", self.timeout) - kwargs.setdefault("verify", self.verify) - if self.cert is not None: - kwargs.setdefault("cert", self.cert) - self.serialize(kwargs) - - self._http_log_req(method, url, kwargs) - if self.timings: - start_time = time.time() - resp = self.http.request(method, url, **kwargs) - if self.timings: - self.times.append(("%s %s" % (method, url), - start_time, time.time())) - self._http_log_resp(resp) - - self.last_request_id = resp.headers.get('x-openstack-request-id') - - if resp.status_code >= 400: - _logger.debug( - "Request returned failure status: %s", - resp.status_code) - raise exceptions.from_response(resp, method, url) - - return resp - - @staticmethod - def concat_url(endpoint, url): - """Concatenate endpoint and final URL. - - E.g., "http://keystone/v2.0/" and "/tokens" are concatenated to - "http://keystone/v2.0/tokens". - - :param endpoint: the base URL - :param url: the final URL - """ - return "%s/%s" % (endpoint.rstrip("/"), url.strip("/")) - - def client_request(self, client, method, url, **kwargs): - """Send an http request using `client`'s endpoint and specified `url`. - - If request was rejected as unauthorized (possibly because the token is - expired), issue one authorization attempt and send the request once - again. - - :param client: instance of BaseClient descendant - :param method: method of HTTP request - :param url: URL of HTTP request - :param kwargs: any other parameter that can be passed to - `HTTPClient.request` - """ - - filter_args = { - "endpoint_type": client.endpoint_type or self.endpoint_type, - "service_type": client.service_type, - } - token, endpoint = (self.cached_token, client.cached_endpoint) - just_authenticated = False - if not (token and endpoint): - try: - token, endpoint = self.auth_plugin.token_and_endpoint( - **filter_args) - except exceptions.EndpointException: - pass - if not (token and endpoint): - self.authenticate() - just_authenticated = True - token, endpoint = self.auth_plugin.token_and_endpoint( - **filter_args) - if not (token and endpoint): - raise exceptions.AuthorizationFailure( - _("Cannot find endpoint or token for request")) - - old_token_endpoint = (token, endpoint) - kwargs.setdefault("headers", {})["X-Auth-Token"] = token - self.cached_token = token - client.cached_endpoint = endpoint - # Perform the request once. If we get Unauthorized, then it - # might be because the auth token expired, so try to - # re-authenticate and try again. If it still fails, bail. - try: - return self.request( - method, self.concat_url(endpoint, url), **kwargs) - except exceptions.Unauthorized as unauth_ex: - if just_authenticated: - raise - self.cached_token = None - client.cached_endpoint = None - if self.auth_plugin.opts.get('token'): - self.auth_plugin.opts['token'] = None - if self.auth_plugin.opts.get('endpoint'): - self.auth_plugin.opts['endpoint'] = None - self.authenticate() - try: - token, endpoint = self.auth_plugin.token_and_endpoint( - **filter_args) - except exceptions.EndpointException: - raise unauth_ex - if (not (token and endpoint) or - old_token_endpoint == (token, endpoint)): - raise unauth_ex - self.cached_token = token - client.cached_endpoint = endpoint - kwargs["headers"]["X-Auth-Token"] = token - return self.request( - method, self.concat_url(endpoint, url), **kwargs) - - def add_client(self, base_client_instance): - """Add a new instance of :class:`BaseClient` descendant. - - `self` will store a reference to `base_client_instance`. - - Example: - - >>> def test_clients(): - ... from keystoneclient.auth import keystone - ... from openstack.common.apiclient import client - ... auth = keystone.KeystoneAuthPlugin( - ... username="user", password="pass", tenant_name="tenant", - ... auth_url="http://auth:5000/v2.0") - ... openstack_client = client.HTTPClient(auth) - ... # create nova client - ... from novaclient.v1_1 import client - ... client.Client(openstack_client) - ... # create keystone client - ... from keystoneclient.v2_0 import client - ... client.Client(openstack_client) - ... # use them - ... openstack_client.identity.tenants.list() - ... openstack_client.compute.servers.list() - """ - service_type = base_client_instance.service_type - if service_type and not hasattr(self, service_type): - setattr(self, service_type, base_client_instance) - - def authenticate(self): - self.auth_plugin.authenticate(self) - # Store the authentication results in the keyring for later requests - if self.keyring_saver: - self.keyring_saver.save(self) - - -class BaseClient(object): - """Top-level object to access the OpenStack API. - - This client uses :class:`HTTPClient` to send requests. :class:`HTTPClient` - will handle a bunch of issues such as authentication. - """ - - service_type = None - endpoint_type = None # "publicURL" will be used - cached_endpoint = None - - def __init__(self, http_client, extensions=None): - self.http_client = http_client - http_client.add_client(self) - - # Add in any extensions... - if extensions: - for extension in extensions: - if extension.manager_class: - setattr(self, extension.name, - extension.manager_class(self)) - - def client_request(self, method, url, **kwargs): - return self.http_client.client_request( - self, method, url, **kwargs) - - @property - def last_request_id(self): - return self.http_client.last_request_id - - def head(self, url, **kwargs): - return self.client_request("HEAD", url, **kwargs) - - def get(self, url, **kwargs): - return self.client_request("GET", url, **kwargs) - - def post(self, url, **kwargs): - return self.client_request("POST", url, **kwargs) - - def put(self, url, **kwargs): - return self.client_request("PUT", url, **kwargs) - - def delete(self, url, **kwargs): - return self.client_request("DELETE", url, **kwargs) - - def patch(self, url, **kwargs): - return self.client_request("PATCH", url, **kwargs) - - @staticmethod - def get_class(api_name, version, version_map): - """Returns the client class for the requested API version - - :param api_name: the name of the API, e.g. 'compute', 'image', etc - :param version: the requested API version - :param version_map: a dict of client classes keyed by version - :rtype: a client class for the requested API version - """ - try: - client_path = version_map[str(version)] - except (KeyError, ValueError): - msg = _("Invalid %(api_name)s client version '%(version)s'. " - "Must be one of: %(version_map)s") % { - 'api_name': api_name, - 'version': version, - 'version_map': ', '.join(version_map.keys())} - raise exceptions.UnsupportedVersion(msg) - - return importutils.import_class(client_path) diff --git a/ceilometerclient/apiclient/exceptions.py b/ceilometerclient/apiclient/exceptions.py deleted file mode 100644 index 68b98ca..0000000 --- a/ceilometerclient/apiclient/exceptions.py +++ /dev/null @@ -1,477 +0,0 @@ -# Copyright 2010 Jacob Kaplan-Moss -# Copyright 2011 Nebula, Inc. -# Copyright 2013 Alessio Ababilov -# Copyright 2013 OpenStack Foundation -# 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. - -""" -Exception definitions. -""" - -######################################################################## -# -# THIS MODULE IS DEPRECATED -# -# Please refer to -# https://etherpad.openstack.org/p/kilo-ceilometerclient-library-proposals for -# the discussion leading to this deprecation. -# -# We recommend checking out the python-openstacksdk project -# (https://launchpad.net/python-openstacksdk) instead. -# -######################################################################## - -import inspect -import sys - -import six - -from ceilometerclient.i18n import _ - - -class ClientException(Exception): - """The base exception class for all exceptions this library raises.""" - pass - - -class ValidationError(ClientException): - """Error in validation on API client side.""" - pass - - -class UnsupportedVersion(ClientException): - """User is trying to use an unsupported version of the API.""" - pass - - -class CommandError(ClientException): - """Error in CLI tool.""" - pass - - -class AuthorizationFailure(ClientException): - """Cannot authorize API client.""" - pass - - -class ConnectionError(ClientException): - """Cannot connect to API service.""" - pass - - -class ConnectionRefused(ConnectionError): - """Connection refused while trying to connect to API service.""" - pass - - -class AuthPluginOptionsMissing(AuthorizationFailure): - """Auth plugin misses some options.""" - def __init__(self, opt_names): - super(AuthPluginOptionsMissing, self).__init__( - _("Authentication failed. Missing options: %s") % - ", ".join(opt_names)) - self.opt_names = opt_names - - -class AuthSystemNotFound(AuthorizationFailure): - """User has specified an AuthSystem that is not installed.""" - def __init__(self, auth_system): - super(AuthSystemNotFound, self).__init__( - _("AuthSystemNotFound: %s") % repr(auth_system)) - self.auth_system = auth_system - - -class NoUniqueMatch(ClientException): - """Multiple entities found instead of one.""" - pass - - -class EndpointException(ClientException): - """Something is rotten in Service Catalog.""" - pass - - -class EndpointNotFound(EndpointException): - """Could not find requested endpoint in Service Catalog.""" - pass - - -class AmbiguousEndpoints(EndpointException): - """Found more than one matching endpoint in Service Catalog.""" - def __init__(self, endpoints=None): - super(AmbiguousEndpoints, self).__init__( - _("AmbiguousEndpoints: %s") % repr(endpoints)) - self.endpoints = endpoints - - -class HttpError(ClientException): - """The base exception class for all HTTP exceptions.""" - http_status = 0 - message = _("HTTP Error") - - def __init__(self, message=None, details=None, - response=None, request_id=None, - url=None, method=None, http_status=None): - self.http_status = http_status or self.http_status - self.message = message or self.message - self.details = details - self.request_id = request_id - self.response = response - self.url = url - self.method = method - formatted_string = "%s (HTTP %s)" % (self.message, self.http_status) - if request_id: - formatted_string += " (Request-ID: %s)" % request_id - super(HttpError, self).__init__(formatted_string) - - -class HTTPRedirection(HttpError): - """HTTP Redirection.""" - message = _("HTTP Redirection") - - -class HTTPClientError(HttpError): - """Client-side HTTP error. - - Exception for cases in which the client seems to have erred. - """ - message = _("HTTP Client Error") - - -class HttpServerError(HttpError): - """Server-side HTTP error. - - Exception for cases in which the server is aware that it has - erred or is incapable of performing the request. - """ - message = _("HTTP Server Error") - - -class MultipleChoices(HTTPRedirection): - """HTTP 300 - Multiple Choices. - - Indicates multiple options for the resource that the client may follow. - """ - - http_status = 300 - message = _("Multiple Choices") - - -class BadRequest(HTTPClientError): - """HTTP 400 - Bad Request. - - The request cannot be fulfilled due to bad syntax. - """ - http_status = 400 - message = _("Bad Request") - - -class Unauthorized(HTTPClientError): - """HTTP 401 - Unauthorized. - - Similar to 403 Forbidden, but specifically for use when authentication - is required and has failed or has not yet been provided. - """ - http_status = 401 - message = _("Unauthorized") - - -class PaymentRequired(HTTPClientError): - """HTTP 402 - Payment Required. - - Reserved for future use. - """ - http_status = 402 - message = _("Payment Required") - - -class Forbidden(HTTPClientError): - """HTTP 403 - Forbidden. - - The request was a valid request, but the server is refusing to respond - to it. - """ - http_status = 403 - message = _("Forbidden") - - -class NotFound(HTTPClientError): - """HTTP 404 - Not Found. - - The requested resource could not be found but may be available again - in the future. - """ - http_status = 404 - message = _("Not Found") - - -class MethodNotAllowed(HTTPClientError): - """HTTP 405 - Method Not Allowed. - - A request was made of a resource using a request method not supported - by that resource. - """ - http_status = 405 - message = _("Method Not Allowed") - - -class NotAcceptable(HTTPClientError): - """HTTP 406 - Not Acceptable. - - The requested resource is only capable of generating content not - acceptable according to the Accept headers sent in the request. - """ - http_status = 406 - message = _("Not Acceptable") - - -class ProxyAuthenticationRequired(HTTPClientError): - """HTTP 407 - Proxy Authentication Required. - - The client must first authenticate itself with the proxy. - """ - http_status = 407 - message = _("Proxy Authentication Required") - - -class RequestTimeout(HTTPClientError): - """HTTP 408 - Request Timeout. - - The server timed out waiting for the request. - """ - http_status = 408 - message = _("Request Timeout") - - -class Conflict(HTTPClientError): - """HTTP 409 - Conflict. - - Indicates that the request could not be processed because of conflict - in the request, such as an edit conflict. - """ - http_status = 409 - message = _("Conflict") - - -class Gone(HTTPClientError): - """HTTP 410 - Gone. - - Indicates that the resource requested is no longer available and will - not be available again. - """ - http_status = 410 - message = _("Gone") - - -class LengthRequired(HTTPClientError): - """HTTP 411 - Length Required. - - The request did not specify the length of its content, which is - required by the requested resource. - """ - http_status = 411 - message = _("Length Required") - - -class PreconditionFailed(HTTPClientError): - """HTTP 412 - Precondition Failed. - - The server does not meet one of the preconditions that the requester - put on the request. - """ - http_status = 412 - message = _("Precondition Failed") - - -class RequestEntityTooLarge(HTTPClientError): - """HTTP 413 - Request Entity Too Large. - - The request is larger than the server is willing or able to process. - """ - http_status = 413 - message = _("Request Entity Too Large") - - def __init__(self, *args, **kwargs): - try: - self.retry_after = int(kwargs.pop('retry_after')) - except (KeyError, ValueError): - self.retry_after = 0 - - super(RequestEntityTooLarge, self).__init__(*args, **kwargs) - - -class RequestUriTooLong(HTTPClientError): - """HTTP 414 - Request-URI Too Long. - - The URI provided was too long for the server to process. - """ - http_status = 414 - message = _("Request-URI Too Long") - - -class UnsupportedMediaType(HTTPClientError): - """HTTP 415 - Unsupported Media Type. - - The request entity has a media type which the server or resource does - not support. - """ - http_status = 415 - message = _("Unsupported Media Type") - - -class RequestedRangeNotSatisfiable(HTTPClientError): - """HTTP 416 - Requested Range Not Satisfiable. - - The client has asked for a portion of the file, but the server cannot - supply that portion. - """ - http_status = 416 - message = _("Requested Range Not Satisfiable") - - -class ExpectationFailed(HTTPClientError): - """HTTP 417 - Expectation Failed. - - The server cannot meet the requirements of the Expect request-header field. - """ - http_status = 417 - message = _("Expectation Failed") - - -class UnprocessableEntity(HTTPClientError): - """HTTP 422 - Unprocessable Entity. - - The request was well-formed but was unable to be followed due to semantic - errors. - """ - http_status = 422 - message = _("Unprocessable Entity") - - -class InternalServerError(HttpServerError): - """HTTP 500 - Internal Server Error. - - A generic error message, given when no more specific message is suitable. - """ - http_status = 500 - message = _("Internal Server Error") - - -# NotImplemented is a python keyword. -class HttpNotImplemented(HttpServerError): - """HTTP 501 - Not Implemented. - - The server either does not recognize the request method, or it lacks - the ability to fulfill the request. - """ - http_status = 501 - message = _("Not Implemented") - - -class BadGateway(HttpServerError): - """HTTP 502 - Bad Gateway. - - The server was acting as a gateway or proxy and received an invalid - response from the upstream server. - """ - http_status = 502 - message = _("Bad Gateway") - - -class ServiceUnavailable(HttpServerError): - """HTTP 503 - Service Unavailable. - - The server is currently unavailable. - """ - http_status = 503 - message = _("Service Unavailable") - - -class GatewayTimeout(HttpServerError): - """HTTP 504 - Gateway Timeout. - - The server was acting as a gateway or proxy and did not receive a timely - response from the upstream server. - """ - http_status = 504 - message = _("Gateway Timeout") - - -class HttpVersionNotSupported(HttpServerError): - """HTTP 505 - HttpVersion Not Supported. - - The server does not support the HTTP protocol version used in the request. - """ - http_status = 505 - message = _("HTTP Version Not Supported") - - -# _code_map contains all the classes that have http_status attribute. -_code_map = dict( - (getattr(obj, 'http_status', None), obj) - for name, obj in six.iteritems(vars(sys.modules[__name__])) - if inspect.isclass(obj) and getattr(obj, 'http_status', False) -) - - -def from_response(response, method, url): - """Returns an instance of :class:`HttpError` or subclass based on response. - - :param response: instance of `requests.Response` class - :param method: HTTP method used for request - :param url: URL used for request - """ - - req_id = response.headers.get("x-openstack-request-id") - # NOTE(hdd) true for older versions of nova and cinder - if not req_id: - req_id = response.headers.get("x-compute-request-id") - kwargs = { - "http_status": response.status_code, - "response": response, - "method": method, - "url": url, - "request_id": req_id, - } - if "retry-after" in response.headers: - kwargs["retry_after"] = response.headers["retry-after"] - - content_type = response.headers.get("Content-Type", "") - if content_type.startswith("application/json"): - try: - body = response.json() - except ValueError: - pass - else: - if isinstance(body, dict): - error = body.get(list(body)[0]) - if isinstance(error, dict): - kwargs["message"] = (error.get("message") or - error.get("faultstring")) - kwargs["details"] = (error.get("details") or - six.text_type(body)) - elif content_type.startswith("text/"): - kwargs["details"] = response.text - - try: - cls = _code_map[response.status_code] - except KeyError: - if 500 <= response.status_code < 600: - cls = HttpServerError - elif 400 <= response.status_code < 500: - cls = HTTPClientError - else: - cls = HttpError - return cls(**kwargs) diff --git a/ceilometerclient/apiclient/fake_client.py b/ceilometerclient/apiclient/fake_client.py deleted file mode 100644 index d897cc5..0000000 --- a/ceilometerclient/apiclient/fake_client.py +++ /dev/null @@ -1,190 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# 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. - -""" -A fake server that "responds" to API methods with pre-canned responses. - -All of these responses come from the spec, so if for some reason the spec's -wrong the tests might raise AssertionError. I've indicated in comments the -places where actual behavior differs from the spec. -""" - -######################################################################## -# -# THIS MODULE IS DEPRECATED -# -# Please refer to -# https://etherpad.openstack.org/p/kilo-ceilometerclient-library-proposals for -# the discussion leading to this deprecation. -# -# We recommend checking out the python-openstacksdk project -# (https://launchpad.net/python-openstacksdk) instead. -# -######################################################################## - -# W0102: Dangerous default value %s as argument -# pylint: disable=W0102 - -import json - -import requests -import six -from six.moves.urllib import parse - -from ceilometerclient.apiclient import client - - -def assert_has_keys(dct, required=None, optional=None): - required = required or [] - optional = optional or [] - for k in required: - try: - assert k in dct - except AssertionError: - extra_keys = set(dct.keys()).difference(set(required + optional)) - raise AssertionError("found unexpected keys: %s" % - list(extra_keys)) - - -class TestResponse(requests.Response): - """Wrap requests.Response and provide a convenient initialization.""" - - def __init__(self, data): - super(TestResponse, self).__init__() - self._content_consumed = True - if isinstance(data, dict): - self.status_code = data.get('status_code', 200) - # Fake the text attribute to streamline Response creation - text = data.get('text', "") - if isinstance(text, (dict, list)): - self._content = json.dumps(text) - default_headers = { - "Content-Type": "application/json", - } - else: - self._content = text - default_headers = {} - if six.PY3 and isinstance(self._content, six.string_types): - self._content = self._content.encode('utf-8', 'strict') - self.headers = data.get('headers') or default_headers - else: - self.status_code = data - - def __eq__(self, other): - return (self.status_code == other.status_code and - self.headers == other.headers and - self._content == other._content) - - def __ne__(self, other): - return not self.__eq__(other) - - -class FakeHTTPClient(client.HTTPClient): - - def __init__(self, *args, **kwargs): - self.callstack = [] - self.fixtures = kwargs.pop("fixtures", None) or {} - if not args and "auth_plugin" not in kwargs: - args = (None, ) - super(FakeHTTPClient, self).__init__(*args, **kwargs) - - def assert_called(self, method, url, body=None, pos=-1): - """Assert than an API method was just called.""" - expected = (method, url) - called = self.callstack[pos][0:2] - assert self.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: - if self.callstack[pos][3] != body: - raise AssertionError('%r != %r' % - (self.callstack[pos][3], 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.callstack, \ - "Expected %s %s but no calls were made." % expected - - found = False - entry = None - for entry in self.callstack: - if expected == entry[0:2]: - found = True - break - - assert found, 'Expected %s %s; got %s' % \ - (method, url, self.callstack) - if body is not None: - assert entry[3] == body, "%s != %s" % (entry[3], body) - - self.callstack = [] - - def clear_callstack(self): - self.callstack = [] - - def authenticate(self): - pass - - def client_request(self, client, method, url, **kwargs): - # Check that certain things are called correctly - if method in ["GET", "DELETE"]: - assert "json" not in kwargs - - # Note the call - self.callstack.append( - (method, - url, - kwargs.get("headers") or {}, - kwargs.get("json") or kwargs.get("data"))) - try: - fixture = self.fixtures[url][method] - except KeyError: - pass - else: - return TestResponse({"headers": fixture[0], - "text": fixture[1]}) - - # Call the method - args = parse.parse_qsl(parse.urlparse(url)[4]) - kwargs.update(args) - munged_url = url.rsplit('?', 1)[0] - munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_') - munged_url = munged_url.replace('-', '_') - - callback = "%s_%s" % (method.lower(), munged_url) - - if not hasattr(self, callback): - raise AssertionError('Called unknown API method: %s %s, ' - 'expected fakes method name: %s' % - (method, url, callback)) - - resp = getattr(self, callback)(**kwargs) - if len(resp) == 3: - status, headers, body = resp - else: - status, body = resp - headers = {} - self.last_request_id = headers.get('x-openstack-request-id', - 'req-test') - return TestResponse({ - "status_code": status, - "text": body, - "headers": headers, - }) diff --git a/ceilometerclient/apiclient/utils.py b/ceilometerclient/apiclient/utils.py deleted file mode 100644 index 2f45b92..0000000 --- a/ceilometerclient/apiclient/utils.py +++ /dev/null @@ -1,100 +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. - -######################################################################## -# -# THIS MODULE IS DEPRECATED -# -# Please refer to -# https://etherpad.openstack.org/p/kilo-ceilometerclient-library-proposals for -# the discussion leading to this deprecation. -# -# We recommend checking out the python-openstacksdk project -# (https://launchpad.net/python-openstacksdk) instead. -# -######################################################################## - -from oslo_utils import encodeutils -from oslo_utils import uuidutils -import six - -from ceilometerclient.apiclient import exceptions -from ceilometerclient.i18n import _ - - -def find_resource(manager, name_or_id, **find_args): - """Look for resource in a given manager. - - Used as a helper for the _find_* methods. - Example: - - .. code-block:: python - - def _find_hypervisor(cs, hypervisor): - #Get a hypervisor by name or ID. - return cliutils.find_resource(cs.hypervisors, hypervisor) - """ - # first try to get entity as integer id - try: - return manager.get(int(name_or_id)) - except (TypeError, ValueError, exceptions.NotFound): - pass - - # now try to get entity as uuid - try: - if six.PY2: - tmp_id = encodeutils.safe_encode(name_or_id) - else: - tmp_id = encodeutils.safe_decode(name_or_id) - - if uuidutils.is_uuid_like(tmp_id): - return manager.get(tmp_id) - except (TypeError, ValueError, exceptions.NotFound): - pass - - # for str id which is not uuid - if getattr(manager, 'is_alphanum_id_allowed', False): - try: - return manager.get(name_or_id) - except exceptions.NotFound: - pass - - try: - try: - return manager.find(human_id=name_or_id, **find_args) - except exceptions.NotFound: - pass - - # finally try to find entity by name - try: - resource = getattr(manager, 'resource_class', None) - name_attr = resource.NAME_ATTR if resource else 'name' - kwargs = {name_attr: name_or_id} - kwargs.update(find_args) - return manager.find(**kwargs) - except exceptions.NotFound: - msg = _("No %(name)s with a name or " - "ID of '%(name_or_id)s' exists.") % \ - { - "name": manager.resource_class.__name__.lower(), - "name_or_id": name_or_id - } - raise exceptions.CommandError(msg) - except exceptions.NoUniqueMatch: - msg = _("Multiple %(name)s matches found for " - "'%(name_or_id)s', use an ID to be more specific.") % \ - { - "name": manager.resource_class.__name__.lower(), - "name_or_id": name_or_id - } - raise exceptions.CommandError(msg) diff --git a/ceilometerclient/client.py b/ceilometerclient/client.py deleted file mode 100644 index 2c61f34..0000000 --- a/ceilometerclient/client.py +++ /dev/null @@ -1,480 +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 contextlib -import time - -from keystoneauth1 import adapter -from keystoneauth1 import discover -from keystoneauth1 import exceptions as ka_exc -from keystoneauth1.identity import v2 as v2_auth -from keystoneauth1.identity import v3 as v3_auth -from keystoneauth1 import session -from oslo_utils import importutils -from oslo_utils import strutils -import six.moves.urllib.parse as urlparse - -from ceilometerclient.apiclient import auth -from ceilometerclient.apiclient import client -from ceilometerclient.apiclient import exceptions -from ceilometerclient import exc - - -def _discover_auth_versions(session, auth_url): - # discover the API versions the server is supporting based on the - # given URL - v2_auth_url = None - v3_auth_url = None - try: - ks_discover = discover.Discover(session=session, url=auth_url) - v2_auth_url = ks_discover.url_for('2.0') - v3_auth_url = ks_discover.url_for('3.0') - except ka_exc.DiscoveryFailure: - raise - except exceptions.ClientException: - # Identity service may not support discovery. In that case, - # try to determine version from auth_url - url_parts = urlparse.urlparse(auth_url) - (scheme, netloc, path, params, query, fragment) = url_parts - path = path.lower() - if path.startswith('/v3'): - v3_auth_url = auth_url - elif path.startswith('/v2'): - v2_auth_url = auth_url - else: - raise exc.CommandError('Unable to determine the Keystone ' - 'version to authenticate with ' - 'using the given auth_url.') - return v2_auth_url, v3_auth_url - - -def _get_keystone_session(**kwargs): - # TODO(fabgia): the heavy lifting here should be really done by Keystone. - # Unfortunately Keystone does not support a richer method to perform - # discovery and return a single viable URL. A bug against Keystone has - # been filed: https://bugs.launchpad.net/python-keystoneclient/+bug/1330677 - - # first create a Keystone session - cacert = kwargs.pop('cacert', None) - cert = kwargs.pop('cert', None) - key = kwargs.pop('key', None) - insecure = kwargs.pop('insecure', False) - auth_url = kwargs.pop('auth_url', None) - project_id = kwargs.pop('project_id', None) - project_name = kwargs.pop('project_name', None) - token = kwargs['token'] - timeout = kwargs.get('timeout') - - if insecure: - verify = False - else: - verify = cacert or True - - if cert and key: - # passing cert and key together is deprecated in favour of the - # requests lib form of having the cert and key as a tuple - cert = (cert, key) - - # create the keystone client session - ks_session = session.Session(verify=verify, cert=cert, timeout=timeout) - v2_auth_url, v3_auth_url = _discover_auth_versions(ks_session, auth_url) - username = kwargs.pop('username', None) - user_id = kwargs.pop('user_id', None) - user_domain_name = kwargs.pop('user_domain_name', None) - user_domain_id = kwargs.pop('user_domain_id', None) - project_domain_name = kwargs.pop('project_domain_name', None) - project_domain_id = kwargs.pop('project_domain_id', None) - if v3_auth_url: - if not user_domain_name: - user_domain_name = 'Default' - if not project_domain_name: - project_domain_name = 'Default' - auth = None - - use_domain = (user_domain_id or user_domain_name or - project_domain_id or project_domain_name) - use_v3 = v3_auth_url and (use_domain or (not v2_auth_url)) - use_v2 = v2_auth_url and not use_domain - - if use_v3 and token: - auth = v3_auth.Token( - v3_auth_url, - token=token, - project_name=project_name, - project_id=project_id, - project_domain_name=project_domain_name, - project_domain_id=project_domain_id) - elif use_v2 and token: - auth = v2_auth.Token( - v2_auth_url, - token=token, - tenant_id=project_id, - tenant_name=project_name) - elif use_v3: - # the auth_url as v3 specified - # e.g. http://no.where:5000/v3 - # Keystone will return only v3 as viable option - auth = v3_auth.Password( - v3_auth_url, - username=username, - password=kwargs.pop('password', None), - user_id=user_id, - user_domain_name=user_domain_name, - user_domain_id=user_domain_id, - project_name=project_name, - project_id=project_id, - project_domain_name=project_domain_name, - project_domain_id=project_domain_id) - elif use_v2: - # the auth_url as v2 specified - # e.g. http://no.where:5000/v2.0 - # Keystone will return only v2 as viable option - auth = v2_auth.Password( - v2_auth_url, - username, - kwargs.pop('password', None), - tenant_id=project_id, - tenant_name=project_name) - - else: - raise exc.CommandError('Unable to determine the Keystone version ' - 'to authenticate with using the given ' - 'auth_url.') - - ks_session.auth = auth - return ks_session - - -def _get_endpoint(ks_session, **kwargs): - """Get an endpoint using the provided keystone session.""" - - # set service specific endpoint types - endpoint_type = kwargs.get('endpoint_type') or 'publicURL' - service_type = kwargs.get('service_type') or 'metering' - - endpoint = ks_session.get_endpoint(service_type=service_type, - interface=endpoint_type, - region_name=kwargs.get('region_name')) - - return endpoint - - -class AuthPlugin(auth.BaseAuthPlugin): - opt_names = ['tenant_id', 'region_name', 'auth_token', - 'service_type', 'endpoint_type', 'cacert', - 'auth_url', 'insecure', 'cert_file', 'key_file', - 'cert', 'key', 'tenant_name', 'project_name', - 'project_id', 'project_domain_id', 'project_domain_name', - 'user_id', 'user_domain_id', 'user_domain_name', - 'password', 'username', 'endpoint'] - - def __init__(self, auth_system=None, **kwargs): - self.opt_names.extend(self.common_opt_names) - super(AuthPlugin, self).__init__(auth_system, **kwargs) - # NOTE(sileht): backward compat - if self.opts.get('auth_token') and not self.opts.get('token'): - self.opts['token'] = self.opts.get('auth_token') - - def _do_authenticate(self, http_client): - token = self.opts.get('token') - endpoint = self.opts.get('endpoint') - if not (endpoint and token): - ks_kwargs = self._get_ks_kwargs(http_timeout=http_client.timeout) - ks_session = _get_keystone_session(**ks_kwargs) - if not token: - token = lambda: ks_session.get_token() - if not endpoint: - endpoint = _get_endpoint(ks_session, **ks_kwargs) - self.opts['token'] = token - self.opts['endpoint'] = endpoint - - def _get_ks_kwargs(self, http_timeout): - project_id = (self.opts.get('project_id') or - self.opts.get('tenant_id')) - project_name = (self.opts.get('project_name') or - self.opts.get('tenant_name')) - token = self.opts.get('token') - ks_kwargs = { - 'username': self.opts.get('username'), - 'password': self.opts.get('password'), - 'user_id': self.opts.get('user_id'), - 'user_domain_id': self.opts.get('user_domain_id'), - 'user_domain_name': self.opts.get('user_domain_name'), - 'project_id': project_id, - 'project_name': project_name, - 'project_domain_name': self.opts.get('project_domain_name'), - 'project_domain_id': self.opts.get('project_domain_id'), - 'auth_url': self.opts.get('auth_url'), - 'cacert': self.opts.get('cacert'), - 'cert': self.opts.get('cert'), - 'key': self.opts.get('key'), - 'insecure': strutils.bool_from_string( - self.opts.get('insecure')), - 'endpoint_type': self.opts.get('endpoint_type'), - 'service_type': self.opts.get('service_type'), - 'region_name': self.opts.get('region_name'), - 'timeout': http_timeout, - 'token': token() if callable(token) else token, - } - return ks_kwargs - - def token_and_endpoint(self, endpoint_type, service_type): - token = self.opts.get('token') - if callable(token): - token = token() - return token, self.opts.get('endpoint') - - def sufficient_options(self): - """Check if all required options are present. - - :raises: AuthPluginOptionsMissing - """ - has_token = self.opts.get('token') - has_project_domain_or_tenant = (self.opts.get('project_id') or - (self.opts.get('project_name') and - (self.opts.get('user_domain_name') or - self.opts.get('user_domain_id'))) or - (self.opts.get('tenant_id') or - self.opts.get('tenant_name'))) - has_credential = (self.opts.get('username') - and has_project_domain_or_tenant - and self.opts.get('password') - and self.opts.get('auth_url')) - missing = not (has_token or has_credential) - if missing: - missing_opts = [] - opts = ['token', 'endpoint', 'username', 'password', 'auth_url', - 'tenant_id', 'tenant_name'] - for opt in opts: - if not self.opts.get(opt): - missing_opts.append(opt) - raise exceptions.AuthPluginOptionsMissing(missing_opts) - - -def _adjust_kwargs(kwargs): - client_kwargs = { - 'username': kwargs.get('os_username'), - 'password': kwargs.get('os_password'), - 'tenant_id': kwargs.get('os_tenant_id'), - 'tenant_name': kwargs.get('os_tenant_name'), - 'auth_url': kwargs.get('os_auth_url'), - 'region_name': kwargs.get('os_region_name'), - 'service_type': kwargs.get('os_service_type'), - 'endpoint_type': kwargs.get('os_endpoint_type'), - 'insecure': kwargs.get('os_insecure'), - 'cacert': kwargs.get('os_cacert'), - 'cert_file': kwargs.get('os_cert'), - 'key_file': kwargs.get('os_key'), - 'token': kwargs.get('os_token') or kwargs.get('os_auth_token'), - 'user_domain_name': kwargs.get('os_user_domain_name'), - 'user_domain_id': kwargs.get('os_user_domain_id'), - 'project_domain_name': kwargs.get('os_project_domain_name'), - 'project_domain_id': kwargs.get('os_project_domain_id'), - } - - client_kwargs.update(kwargs) - client_kwargs['token'] = (client_kwargs.get('token') or - kwargs.get('token') or - kwargs.get('auth_token')) - - timeout = kwargs.get('timeout') - if timeout is not None: - timeout = int(timeout) - if timeout <= 0: - timeout = None - - insecure = strutils.bool_from_string(client_kwargs.get('insecure')) - verify = kwargs.get('verify') - if verify is None: - if insecure: - verify = False - else: - verify = client_kwargs.get('cacert') or True - - cert = client_kwargs.get('cert_file') - key = client_kwargs.get('key_file') - if cert and key: - cert = cert, key - - client_kwargs.update({'verify': verify, 'cert': cert, 'timeout': timeout}) - return client_kwargs - - -def Client(version, *args, **kwargs): - client_kwargs = _adjust_kwargs(kwargs) - - module = importutils.import_versioned_module('ceilometerclient', - version, 'client') - client_class = getattr(module, 'Client') - return client_class(*args, **client_kwargs) - - -def get_client(version, **kwargs): - """Get an authenticated client, based on the credentials in the kwargs. - - :param version: the API version to use ('1' or '2') - :param kwargs: keyword args containing credentials, either: - - * session: a keystoneauth session object - * service_type: The default service_type for URL discovery - * service_name: The default service_name for URL discovery - * interface: The default interface for URL discovery - (Default: public) - * region_name: The default region_name for URL discovery - * endpoint_override: Always use this endpoint URL for requests - for this ceiloclient - * auth: An auth plugin to use instead of the session one - * user_agent: The User-Agent string to set - (Default is python-ceilometer-client) - * connect_retries: the maximum number of retries that should be - attempted for connection errors - * logger: A logging object - - or (DEPRECATED): - - * os_auth_token: (DEPRECATED) pre-existing token to re-use, - use os_token instead - * os_token: pre-existing token to re-use - * ceilometer_url: (DEPRECATED) Ceilometer API endpoint, - use os_endpoint instead - * os_endpoint: Ceilometer API endpoint - - or (DEPRECATED): - - * os_username: name of user - * os_password: user's password - * os_user_id: user's id - * os_user_domain_id: the domain id of the user - * os_user_domain_name: the domain name of the user - * os_project_id: the user project id - * os_tenant_id: V2 alternative to os_project_id - * os_project_name: the user project name - * os_tenant_name: V2 alternative to os_project_name - * os_project_domain_name: domain name for the user project - * os_project_domain_id: domain id for the user project - * os_auth_url: endpoint to authenticate against - * os_cert|os_cacert: path of CA TLS certificate - * os_key: SSL private key - * os_insecure: allow insecure SSL (no cert verification) - """ - endpoint = kwargs.get('os_endpoint') or kwargs.get('ceilometer_url') - - return Client(version, endpoint, **kwargs) - - -def get_auth_plugin(endpoint, **kwargs): - auth_plugin = AuthPlugin( - auth_url=kwargs.get('auth_url'), - service_type=kwargs.get('service_type'), - token=kwargs.get('token'), - endpoint_type=kwargs.get('endpoint_type'), - insecure=kwargs.get('insecure'), - region_name=kwargs.get('region_name'), - cacert=kwargs.get('cacert'), - tenant_id=kwargs.get('project_id') or kwargs.get('tenant_id'), - endpoint=endpoint, - username=kwargs.get('username'), - password=kwargs.get('password'), - tenant_name=kwargs.get('project_name') or kwargs.get('tenant_name'), - user_domain_name=kwargs.get('user_domain_name'), - user_domain_id=kwargs.get('user_domain_id'), - project_domain_name=kwargs.get('project_domain_name'), - project_domain_id=kwargs.get('project_domain_id') - ) - return auth_plugin - - -LEGACY_OPTS = ('auth_plugin', 'auth_url', 'token', 'insecure', 'cacert', - 'tenant_id', 'project_id', 'username', 'password', - 'project_name', 'tenant_name', - 'user_domain_name', 'user_domain_id', - 'project_domain_name', 'project_domain_id', - 'key_file', 'cert_file', 'verify', 'timeout', 'cert') - - -def _construct_http_client(**kwargs): - kwargs = kwargs.copy() - if kwargs.get('session') is not None: - # Drop legacy options - for opt in LEGACY_OPTS: - kwargs.pop(opt, None) - # Drop redirect endpoints from kwargs - kwargs.pop('aodh_endpoint', None) - kwargs.pop('panko_endpoint', None) - - return SessionClient( - session=kwargs.pop('session'), - service_type=kwargs.pop('service_type', 'metering') or 'metering', - interface=kwargs.pop('interface', kwargs.pop('endpoint_type', - 'publicURL')), - region_name=kwargs.pop('region_name', None), - user_agent=kwargs.pop('user_agent', 'python-ceilometerclient'), - auth=kwargs.get('auth'), - timings=kwargs.pop('timings', None), - **kwargs) - else: - return client.BaseClient(client.HTTPClient( - auth_plugin=kwargs.get('auth_plugin'), - region_name=kwargs.get('region_name'), - endpoint_type=kwargs.get('endpoint_type'), - original_ip=kwargs.get('original_ip'), - verify=kwargs.get('verify'), - cert=kwargs.get('cert'), - timeout=kwargs.get('timeout'), - timings=kwargs.get('timings'), - keyring_saver=kwargs.get('keyring_saver'), - debug=kwargs.get('debug'), - user_agent=kwargs.get('user_agent'), - http=kwargs.get('http') - )) - - -@contextlib.contextmanager -def record_time(times, enabled, *args): - """Record the time of a specific action. - - :param times: A list of tuples holds time data. - :type times: list - :param enabled: Whether timing is enabled. - :type enabled: bool - :param args: Other data to be stored besides time data, these args - will be joined to a string. - """ - if not enabled: - yield - else: - start = time.time() - yield - end = time.time() - times.append((' '.join(args), start, end)) - - -class SessionClient(adapter.LegacyJsonAdapter): - def __init__(self, *args, **kwargs): - self.times = [] - self.timings = kwargs.pop('timings', False) - super(SessionClient, self).__init__(*args, **kwargs) - - def request(self, url, method, **kwargs): - kwargs.setdefault('headers', kwargs.get('headers', {})) - # NOTE(sileht): The standard call raises errors from - # keystoneauth, where we need to raise the ceilometerclient errors. - raise_exc = kwargs.pop('raise_exc', True) - with record_time(self.times, self.timings, method, url): - resp, body = super(SessionClient, self).request(url, - method, - raise_exc=False, - **kwargs) - - if raise_exc and resp.status_code >= 400: - raise exc.from_response(resp, body) - return resp diff --git a/ceilometerclient/common/__init__.py b/ceilometerclient/common/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ceilometerclient/common/base.py b/ceilometerclient/common/base.py deleted file mode 100644 index 5df8344..0000000 --- a/ceilometerclient/common/base.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright 2012 OpenStack Foundation -# 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. - -""" -Base utilities to build API operation managers and objects on top of. -""" - -import copy - -from ceilometerclient.apiclient import base -from ceilometerclient.apiclient import exceptions -from ceilometerclient import exc - - -def getid(obj): - """Extracts object ID. - - Abstracts the common pattern of allowing both an object or an - object's ID (UUID) as a parameter when dealing with relationships. - """ - try: - return obj.id - except AttributeError: - return obj - - -class Manager(object): - """Managers interact with a particular type of API. - - It works with samples, meters, alarms, etc. and provide CRUD operations for - them. - """ - resource_class = None - - def __init__(self, api): - self.api = api - - @property - def client(self): - """Compatible with latest oslo-incubator.apiclient code.""" - return self.api - - def _create(self, url, body): - body = self.api.post(url, json=body).json() - if body: - return self.resource_class(self, body) - - def _list(self, url, response_key=None, obj_class=None, body=None, - expect_single=False): - try: - resp = self.api.get(url) - except exceptions.NotFound: - raise exc.HTTPNotFound - if not resp.content: - raise exc.HTTPNotFound - body = resp.json() - - if obj_class is None: - obj_class = self.resource_class - - if response_key: - try: - data = body[response_key] - except KeyError: - return [] - else: - data = body - if expect_single: - data = [data] - return [obj_class(self, res, loaded=True) for res in data if res] - - def _update(self, url, body, response_key=None): - body = self.api.put(url, json=body).json() - # PUT requests may not return a body - if body: - return self.resource_class(self, body) - - def _delete(self, url): - self.api.delete(url) - - -class Resource(base.Resource): - """A resource represents a particular instance of an object. - - Resource might be tenant, user, etc. - This is pretty much just a bag for attributes. - - :param manager: Manager object - :param info: dictionary representing resource attributes - :param loaded: prevent lazy-loading if set to True - """ - - def to_dict(self): - return copy.deepcopy(self._info) diff --git a/ceilometerclient/common/utils.py b/ceilometerclient/common/utils.py deleted file mode 100644 index 720691d..0000000 --- a/ceilometerclient/common/utils.py +++ /dev/null @@ -1,204 +0,0 @@ -# Copyright 2012 OpenStack Foundation -# 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. - -from __future__ import print_function - -import os -import textwrap - -from oslo_serialization import jsonutils -from oslo_utils import encodeutils -import prettytable -import six - -from ceilometerclient import exc - - -# Decorator for cli-args -def arg(*args, **kwargs): - def _decorator(func): - if 'help' in kwargs: - if 'default' in kwargs: - kwargs['help'] += " Defaults to %s." % kwargs['default'] - required = kwargs.get('required', False) - if required: - kwargs['help'] += " Required." - - # Because of the sematics of decorator composition if we just append - # to the options list positional options will appear to be backwards. - func.__dict__.setdefault('arguments', []).insert(0, (args, kwargs)) - return func - return _decorator - - -def print_list(objs, fields, field_labels, formatters=None, sortby=0): - """Print a list of objects as a table, one row per object. - - :param objs: Iterable of :class:`Resource` - :param fields: Attributes that correspond to columns, in order - :param field_labels: Labels to use in the heading of the table, default to - fields. - :param formatters: `dict` of callables for field formatting - :param sortby: Index of the field for sorting table rows - """ - formatters = formatters or {} - - if len(field_labels) != len(fields): - raise ValueError(("Field labels list %(labels)s has different number " - "of elements than fields list %(fields)s"), - {'labels': field_labels, 'fields': fields}) - - def _make_default_formatter(field): - return lambda o: getattr(o, field, '') - - new_formatters = {} - for field, field_label in six.moves.zip(fields, field_labels): - if field in formatters: - new_formatters[field_label] = formatters[field] - else: - new_formatters[field_label] = _make_default_formatter(field) - - kwargs = {} if sortby is None else {'sortby': field_labels[sortby]} - pt = prettytable.PrettyTable(field_labels) - pt.align = 'l' - - for o in objs: - row = [] - for field in field_labels: - if field in new_formatters: - row.append(new_formatters[field](o)) - else: - field_name = field.lower().replace(' ', '_') - data = getattr(o, field_name, '') - row.append(data) - pt.add_row(row) - - if six.PY3: - print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode()) - else: - print(encodeutils.safe_encode(pt.get_string(**kwargs))) - - -def nested_list_of_dict_formatter(field, column_names): - # (TMaddox) Because the formatting scheme actually drops the whole object - # into the formatter, rather than just the specified field, we have to - # extract it and then pass the value. - return lambda o: format_nested_list_of_dict(getattr(o, field), - column_names) - - -def format_nested_list_of_dict(l, column_names): - pt = prettytable.PrettyTable(caching=False, print_empty=False, - header=True, hrules=prettytable.FRAME, - field_names=column_names) - # Sort by values of first column - if l is not None: - l.sort(key=lambda k: k.get(column_names[0])) - for d in l: - pt.add_row(list(map(lambda k: d[k], column_names))) - return pt.get_string() - - -def print_dict(d, dict_property="Property", wrap=0): - pt = prettytable.PrettyTable([dict_property, 'Value'], print_empty=False) - pt.align = 'l' - for k, v in sorted(six.iteritems(d)): - # convert dict to str to check length - if isinstance(v, (list, dict)): - v = jsonutils.dumps(v) - # if value has a newline, add in multiple rows - # e.g. fault with stacktrace - if v and isinstance(v, six.string_types) and r'\n' in v: - lines = v.strip().split(r'\n') - col1 = k - for line in lines: - if wrap > 0: - line = textwrap.fill(six.text_type(line), wrap) - pt.add_row([col1, line]) - col1 = '' - else: - if wrap > 0: - v = textwrap.fill(six.text_type(v), wrap) - pt.add_row([k, v]) - encoded = encodeutils.safe_encode(pt.get_string()) - # FIXME(gordc): https://bugs.launchpad.net/oslo-incubator/+bug/1370710 - if six.PY3: - encoded = encoded.decode() - print(encoded) - - -def args_array_to_dict(kwargs, key_to_convert): - values_to_convert = kwargs.get(key_to_convert) - if values_to_convert: - try: - kwargs[key_to_convert] = dict(v.split("=", 1) - for v in values_to_convert) - except ValueError: - raise exc.CommandError( - '%s must be a list of key=value not "%s"' % ( - key_to_convert, values_to_convert)) - return kwargs - - -def args_array_to_list_of_dicts(kwargs, key_to_convert): - """Converts ['a=1;b=2','c=3;d=4'] to [{a:1,b:2},{c:3,d:4}].""" - values_to_convert = kwargs.get(key_to_convert) - if values_to_convert: - try: - kwargs[key_to_convert] = [] - for lst in values_to_convert: - pairs = lst.split(";") - dct = dict() - for pair in pairs: - kv = pair.split("=", 1) - dct[kv[0]] = kv[1].strip(" \"'") # strip spaces and quotes - kwargs[key_to_convert].append(dct) - except Exception: - raise exc.CommandError( - '%s must be a list of key1=value1;key2=value2;... not "%s"' % ( - key_to_convert, values_to_convert)) - return kwargs - - -def key_with_slash_to_nested_dict(kwargs): - nested_kwargs = {} - for k in list(kwargs): - keys = k.split('/', 1) - if len(keys) == 2: - nested_kwargs.setdefault(keys[0], {})[keys[1]] = kwargs[k] - del kwargs[k] - kwargs.update(nested_kwargs) - return kwargs - - -def merge_nested_dict(dest, source, depth=0): - for (key, value) in six.iteritems(source): - if isinstance(value, dict) and depth: - merge_nested_dict(dest[key], value, - depth=(depth - 1)) - else: - dest[key] = value - - -def env(*args, **kwargs): - """Returns the first environment variable set. - - If all are empty, defaults to '' or keyword arg `default`. - """ - for arg in args: - value = os.environ.get(arg) - if value: - return value - return kwargs.get('default', '') diff --git a/ceilometerclient/exc.py b/ceilometerclient/exc.py deleted file mode 100644 index 8d56038..0000000 --- a/ceilometerclient/exc.py +++ /dev/null @@ -1,144 +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 -import sys - - -class BaseException(Exception): - """An error occurred.""" - def __init__(self, message=None): - self.message = message - - def __str__(self): - return self.message or self.__class__.__doc__ - - -class CommandError(BaseException): - """Invalid usage of CLI.""" - - -class InvalidEndpoint(BaseException): - """The provided endpoint is invalid.""" - - -class CommunicationError(BaseException): - """Unable to communicate with server.""" - - -class HTTPException(BaseException): - """Base exception for all HTTP-derived exceptions.""" - code = 'N/A' - - def __init__(self, details=None): - self.details = details - - def __str__(self): - message = "" - if self.details: - message = self.details - try: - data = json.loads(self.details) - message = data.get("error_message", "") - if isinstance(message, dict) and "faultstring" in message: - message = "ERROR %s" % message["faultstring"] - except (ValueError, TypeError, AttributeError): - pass - - if message: - message = " %s" % message - return "%s (HTTP %s)%s" % (self.__class__.__name__, self.code, - message) - - -class HTTPMultipleChoices(HTTPException): - code = 300 - - def __str__(self): - self.details = ("Requested version of OpenStack Images API is not" - "available.") - return "%s (HTTP %s) %s" % (self.__class__.__name__, self.code, - self.details) - - -class HTTPBadRequest(HTTPException): - code = 400 - - -class HTTPUnauthorized(HTTPException): - code = 401 - - -class HTTPForbidden(HTTPException): - code = 403 - - -class HTTPNotFound(HTTPException): - code = 404 - - -class HTTPMethodNotAllowed(HTTPException): - code = 405 - - -class HTTPConflict(HTTPException): - code = 409 - - -class HTTPOverLimit(HTTPException): - code = 413 - - -class HTTPInternalServerError(HTTPException): - code = 500 - - -class HTTPNotImplemented(HTTPException): - code = 501 - - -class HTTPBadGateway(HTTPException): - code = 502 - - -class HTTPServiceUnavailable(HTTPException): - code = 503 - - -# NOTE(bcwaldon): Build a mapping of HTTP codes to corresponding exception -# classes -_code_map = {} -for obj_name in dir(sys.modules[__name__]): - if obj_name.startswith('HTTP'): - obj = getattr(sys.modules[__name__], obj_name) - _code_map[obj.code] = obj - - -def from_response(response, details=None): - """Return an instance of an HTTPException based on http response.""" - if hasattr(response, "status"): - # it is response from HTTPClient (httplib) - code = response.status - elif hasattr(response, "status_code"): - # it is response from SessionClient (requests) - code = response.status_code - else: - # it is something unexpected - raise TypeError("Function 'from_response' expects only response object" - " from httplib or requests libraries.") - cls = _code_map.get(code) - if cls is None: - exc = HTTPException(details) - exc.code = code - return exc - else: - return cls(details) diff --git a/ceilometerclient/i18n.py b/ceilometerclient/i18n.py deleted file mode 100644 index a567932..0000000 --- a/ceilometerclient/i18n.py +++ /dev/null @@ -1,27 +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. - -"""oslo.i18n integration module. - -See https://docs.openstack.org/oslo.i18n/latest/user/usage.html. - -""" - -import oslo_i18n - - -_translators = oslo_i18n.TranslatorFactory(domain='ceilometerclient') - -# The primary translation function using the well-known name "_" -_ = _translators.primary diff --git a/ceilometerclient/shell.py b/ceilometerclient/shell.py deleted file mode 100644 index d096f3b..0000000 --- a/ceilometerclient/shell.py +++ /dev/null @@ -1,287 +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. - -""" -Command-line interface to the OpenStack Telemetry API. -""" - -from __future__ import print_function - -import argparse -import logging -import sys -import warnings - -from oslo_utils import encodeutils -from oslo_utils import importutils -import six - -import ceilometerclient -from ceilometerclient import client as ceiloclient -from ceilometerclient.common import utils -from ceilometerclient import exc - - -def _positive_non_zero_int(argument_value): - if argument_value is None: - return None - try: - value = int(argument_value) - except ValueError: - msg = "%s must be an integer" % argument_value - raise argparse.ArgumentTypeError(msg) - if value <= 0: - msg = "%s must be greater than 0" % argument_value - raise argparse.ArgumentTypeError(msg) - return value - - -class CeilometerShell(object): - - def __init__(self): - self.auth_plugin = ceiloclient.AuthPlugin() - - def get_base_parser(self): - parser = argparse.ArgumentParser( - prog='ceilometer', - description=__doc__.strip(), - epilog='See "ceilometer help COMMAND" ' - 'for help on a specific command.', - add_help=False, - formatter_class=HelpFormatter, - ) - - # Global arguments - parser.add_argument('-h', '--help', - action='store_true', - help=argparse.SUPPRESS, - ) - - parser.add_argument('--version', - action='version', - version=ceilometerclient.__version__) - - parser.add_argument('-d', '--debug', - default=bool(utils.env('CEILOMETERCLIENT_DEBUG') - ), - action='store_true', - help='Defaults to env[CEILOMETERCLIENT_DEBUG].') - - parser.add_argument('-v', '--verbose', - default=False, action="store_true", - help="Print more verbose output.") - - parser.add_argument('--timeout', - default=600, - type=_positive_non_zero_int, - help='Number of seconds to wait for a response.') - - parser.add_argument('--ceilometer-url', metavar='', - dest='os_endpoint', - default=utils.env('CEILOMETER_URL'), - help=("DEPRECATED, use --os-endpoint instead. " - "Defaults to env[CEILOMETER_URL].")) - - parser.add_argument('--ceilometer_url', - dest='os_endpoint', - help=argparse.SUPPRESS) - - parser.add_argument('--ceilometer-api-version', - default=utils.env( - 'CEILOMETER_API_VERSION', default='2'), - help='Defaults to env[CEILOMETER_API_VERSION] ' - 'or 2.') - - parser.add_argument('--ceilometer_api_version', - help=argparse.SUPPRESS) - - self.auth_plugin.add_opts(parser) - self.auth_plugin.add_common_opts(parser) - - return parser - - def get_subcommand_parser(self, version): - parser = self.get_base_parser() - - self.subcommands = {} - subparsers = parser.add_subparsers(metavar='') - submodule = importutils.import_versioned_module('ceilometerclient', - version, 'shell') - self._find_actions(subparsers, submodule) - self._find_actions(subparsers, self) - - return parser - - def _find_actions(self, subparsers, actions_module): - for attr in (a for a in dir(actions_module) if a.startswith('do_')): - # I prefer to be hypen-separated instead of underscores. - command = attr[3:].replace('_', '-') - callback = getattr(actions_module, attr) - desc = callback.__doc__ or '' - help = desc.strip().split('\n')[0] - arguments = getattr(callback, 'arguments', []) - - subparser = subparsers.add_parser(command, help=help, - description=desc, - add_help=False, - formatter_class=HelpFormatter) - subparser.add_argument('-h', '--help', action='help', - help=argparse.SUPPRESS) - self.subcommands[command] = subparser - for (args, kwargs) in arguments: - subparser.add_argument(*args, **kwargs) - subparser.set_defaults(func=callback) - - @staticmethod - def _setup_logging(debug): - format = '%(levelname)s (%(module)s) %(message)s' - if debug: - logging.basicConfig(format=format, level=logging.DEBUG) - else: - logging.basicConfig(format=format, level=logging.WARN) - logging.getLogger('iso8601').setLevel(logging.WARNING) - logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING) - - def parse_args(self, argv): - # Parse args once to find version - parser = self.get_base_parser() - (options, args) = parser.parse_known_args(argv) - self.auth_plugin.parse_opts(options) - self._setup_logging(options.debug) - - # build available subcommands based on version - api_version = options.ceilometer_api_version - subcommand_parser = self.get_subcommand_parser(api_version) - self.parser = subcommand_parser - - # Handle top-level --help/-h before attempting to parse - # a command off the command line - if options.help or not argv: - self.do_help(options) - return 0 - - # Return parsed args - return api_version, subcommand_parser.parse_args(argv) - - def main(self, argv): - warnings.warn( - "ceilometerclient is now deprecated as the Ceilometer API has " - "been deprecated. Please use either aodhclient, pankoclient or " - "gnocchiclient.") - parsed = self.parse_args(argv) - if parsed == 0: - return 0 - api_version, args = parsed - - # Short-circuit and deal with help command right away. - if args.func == self.do_help: - self.do_help(args) - return 0 - elif args.func == self.do_bash_completion: - self.do_bash_completion(args) - return 0 - - if not ((self.auth_plugin.opts.get('token') - or self.auth_plugin.opts.get('auth_token')) - and self.auth_plugin.opts['endpoint']): - if not self.auth_plugin.opts['username']: - raise exc.CommandError("You must provide a username via " - "either --os-username or via " - "env[OS_USERNAME]") - - if not self.auth_plugin.opts['password']: - raise exc.CommandError("You must provide a password via " - "either --os-password or via " - "env[OS_PASSWORD]") - - if not (args.os_project_id or args.os_project_name - or args.os_tenant_id or args.os_tenant_name): - # steer users towards Keystone V3 API - raise exc.CommandError("You must provide a project_id " - "(or name) via either --os-project-id " - "or via env[OS_PROJECT_ID]") - - if not self.auth_plugin.opts['auth_url']: - raise exc.CommandError("You must provide an auth url via " - "either --os-auth-url or via " - "env[OS_AUTH_URL]") - - client_kwargs = vars(args) - client_kwargs.update(self.auth_plugin.opts) - client_kwargs['auth_plugin'] = self.auth_plugin - client = ceiloclient.get_client(api_version, **client_kwargs) - # call whatever callback was selected - try: - args.func(client, args) - except exc.HTTPUnauthorized: - raise exc.CommandError("Invalid OpenStack Identity credentials.") - - def do_bash_completion(self, args): - """Prints all of the commands and options to stdout. - - The ceilometer.bash_completion script doesn't have to hard code them. - """ - commands = set() - options = set() - for sc_str, sc in self.subcommands.items(): - commands.add(sc_str) - for option in list(sc._optionals._option_string_actions): - options.add(option) - - commands.remove('bash-completion') - print(' '.join(commands | options)) - - @utils.arg('command', metavar='', nargs='?', - help='Display help for ') - def do_help(self, args): - """Display help about this program or one of its subcommands.""" - if getattr(args, 'command', None): - if args.command in self.subcommands: - self.subcommands[args.command].print_help() - else: - raise exc.CommandError("'%s' is not a valid subcommand" % - args.command) - else: - self.parser.print_help() - - -class HelpFormatter(argparse.HelpFormatter): - def __init__(self, prog, indent_increment=2, max_help_position=32, - width=None): - super(HelpFormatter, self).__init__(prog, indent_increment, - max_help_position, width) - - def start_section(self, heading): - # Title-case the headings - heading = '%s%s' % (heading[0].upper(), heading[1:]) - super(HelpFormatter, self).start_section(heading) - - -def main(args=None): - try: - if args is None: - args = sys.argv[1:] - - CeilometerShell().main(args) - - except Exception as e: - if '--debug' in args or '-d' in args: - raise - else: - print(encodeutils.safe_encode(six.text_type(e)), file=sys.stderr) - sys.exit(1) - except KeyboardInterrupt: - print("Stopping Ceilometer Client", file=sys.stderr) - sys.exit(130) - -if __name__ == "__main__": - main() diff --git a/ceilometerclient/tests/__init__.py b/ceilometerclient/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ceilometerclient/tests/functional/__init__.py b/ceilometerclient/tests/functional/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ceilometerclient/tests/functional/base.py b/ceilometerclient/tests/functional/base.py deleted file mode 100644 index 524114c..0000000 --- a/ceilometerclient/tests/functional/base.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 tempest.lib.cli import base - - -class ClientTestBase(base.ClientTestBase): - """Base class for ceilometerclient tests. - - Establishes the ceilometer client and retrieves the essential environment - information. - """ - - def _get_clients(self): - cli_dir = os.environ.get( - 'OS_CEILOMETER_CLIENT_EXEC_DIR', - os.path.join(os.path.abspath('.'), '.tox/functional/bin')) - - return base.CLIClient( - username=os.environ.get('OS_USERNAME'), - password=os.environ.get('OS_PASSWORD'), - tenant_name=os.environ.get('OS_TENANT_NAME'), - uri=os.environ.get('OS_AUTH_URL'), - cli_dir=cli_dir) - - def ceilometer(self, *args, **kwargs): - return self.clients.ceilometer(*args, **kwargs) diff --git a/ceilometerclient/tests/functional/hooks/post_test_hook.sh b/ceilometerclient/tests/functional/hooks/post_test_hook.sh deleted file mode 100755 index 385d314..0000000 --- a/ceilometerclient/tests/functional/hooks/post_test_hook.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash -xe - -# 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 script is executed inside post_test_hook function in devstack gate. - -function generate_testr_results { - if [ -f .testrepository/0 ]; then - sudo .tox/functional/bin/testr last --subunit > $WORKSPACE/testrepository.subunit - sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit - sudo /usr/os-testr-env/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html - sudo gzip -9 $BASE/logs/testrepository.subunit - sudo gzip -9 $BASE/logs/testr_results.html - sudo chown $USER:$USER $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz - sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz - fi -} - -export CEILOMETERCLIENT_DIR="$BASE/new/python-ceilometerclient" - -# Get admin credentials -cd $BASE/new/devstack -source openrc admin admin - -# Go to the ceilometerclient dir -cd $CEILOMETERCLIENT_DIR - -sudo chown -R $USER:stack $CEILOMETERCLIENT_DIR - -# Run tests -echo "Running ceilometerclient functional test suite" -set +e -# Preserve env for OS_ credentials -sudo -E -H -u $USER tox -efunctional -EXIT_CODE=$? -set -e - -# Collect and parse result -generate_testr_results -exit $EXIT_CODE diff --git a/ceilometerclient/tests/functional/test_readonly_ceilometer.py b/ceilometerclient/tests/functional/test_readonly_ceilometer.py deleted file mode 100644 index 53e2192..0000000 --- a/ceilometerclient/tests/functional/test_readonly_ceilometer.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 ceilometerclient.tests.functional import base -import re - - -class SimpleReadOnlyCeilometerClientTest(base.ClientTestBase): - """Basic, read-only tests for Ceilometer CLI client. - - Checks return values and output of read-only commands. - These tests do not presume any content, nor do they create - their own. They only verify the structure of output if present. - """ - - def test_ceilometer_meter_list(self): - result = self.ceilometer('meter-list') - meters = self.parser.listing(result) - self.assertTableStruct(meters, ['Name', 'Type', 'Unit', - 'Resource ID', 'Project ID']) - - def test_ceilometer_resource_list(self): - result = self.ceilometer('resource-list') - resources = self.parser.listing(result) - self.assertTableStruct(resources, ['Resource ID', 'Source', - 'User ID', 'Project ID']) - - def test_ceilometer_alarm_list(self): - result = self.ceilometer('alarm-list') - alarm = self.parser.listing(result) - self.assertTableStruct(alarm, ['Alarm ID', 'Name', 'State', - 'Enabled', 'Continuous']) - - def test_admin_help(self): - help_text = self.ceilometer('help') - lines = help_text.split('\n') - self.assertFirstLineStartsWith(lines, 'usage: ceilometer') - - commands = [] - cmds_start = lines.index('Positional arguments:') - cmds_end = lines.index('Optional arguments:') - command_pattern = re.compile('^ {4}([a-z0-9\-\_]+)') - for line in lines[cmds_start:cmds_end]: - match = command_pattern.match(line) - if match: - commands.append(match.group(1)) - commands = set(commands) - wanted_commands = set(('alarm-combination-create', 'alarm-create', - 'help', 'alarm-delete', 'event-list')) - self.assertFalse(wanted_commands - commands) - - def test_ceilometer_bash_completion(self): - self.ceilometer('bash-completion') - - # Optional arguments - - def test_ceilometer_debug_list(self): - self.ceilometer('meter-list', flags='--debug') diff --git a/ceilometerclient/tests/unit/__init__.py b/ceilometerclient/tests/unit/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ceilometerclient/tests/unit/test_client.py b/ceilometerclient/tests/unit/test_client.py deleted file mode 100644 index 400e997..0000000 --- a/ceilometerclient/tests/unit/test_client.py +++ /dev/null @@ -1,430 +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 types - -from keystoneauth1 import exceptions as ks_exc -from keystoneauth1.identity import v2 as v2_auth -from keystoneauth1.identity import v3 as v3_auth -from keystoneauth1 import session as ks_session -import mock -import requests - -from ceilometerclient.apiclient import exceptions -from ceilometerclient import client -from ceilometerclient import exc -from ceilometerclient.tests.unit import utils -from ceilometerclient.v2 import client as v2client - -FAKE_ENV = { - 'username': 'username', - 'password': 'password', - 'tenant_name': 'tenant_name', - 'auth_url': 'http://no.where', - 'auth_plugin': mock.Mock(), - 'ceilometer_url': 'http://no.where', - 'token': '1234', - 'user_domain_name': 'default', - 'project_domain_name': 'default', -} - - -class ClientTest(utils.BaseTestCase): - @staticmethod - def create_client(env, api_version=2, endpoint=None, exclude=[]): - env = dict((k, v) for k, v in env.items() - if k not in exclude) - with mock.patch( - 'ceilometerclient.v2.client.Client._get_redirect_client', - return_value=None): - return client.get_client(api_version, **env) - - def test_client_v2_with_session(self): - resp = mock.Mock(status_code=200, text=b'') - resp.json.return_value = [] - session = mock.Mock() - session.request.return_value = resp - c = client.get_client(2, session=session) - c.resources.list() - self.assertTrue(session.request.called) - self.assertTrue(resp.json.called) - - def test_client_version(self): - c2 = self.create_client(env=FAKE_ENV, api_version=2) - self.assertIsInstance(c2, v2client.Client) - - def test_client_auth_lambda(self): - env = FAKE_ENV.copy() - env['token'] = lambda: env['token'] - self.assertIsInstance(env['token'], - types.FunctionType) - c2 = self.create_client(env) - self.assertIsInstance(c2, v2client.Client) - - def test_client_auth_non_lambda(self): - env = FAKE_ENV.copy() - env['token'] = "1234" - self.assertIsInstance(env['token'], str) - c2 = self.create_client(env) - self.assertIsInstance(c2, v2client.Client) - - def test_client_without_auth_plugin(self): - env = FAKE_ENV.copy() - del env['auth_plugin'] - c = self.create_client(env, api_version=2, endpoint='fake_endpoint') - self.assertIsInstance(c.auth_plugin, client.AuthPlugin) - - def test_client_without_auth_plugin_keystone_v3(self): - env = FAKE_ENV.copy() - del env['auth_plugin'] - expected = { - 'username': 'username', - 'endpoint': 'http://no.where', - 'tenant_name': 'tenant_name', - 'service_type': None, - 'token': '1234', - 'endpoint_type': None, - 'region_name': None, - 'auth_url': 'http://no.where', - 'tenant_id': None, - 'insecure': None, - 'cacert': None, - 'password': 'password', - 'user_domain_name': 'default', - 'user_domain_id': None, - 'project_domain_name': 'default', - 'project_domain_id': None, - } - with mock.patch('ceilometerclient.client.AuthPlugin') as auth_plugin: - self.create_client(env, api_version=2, endpoint='http://no.where') - self.assertEqual(mock.call(**expected), - auth_plugin.mock_calls[0]) - - def test_v2_client_timeout_invalid_value(self): - env = FAKE_ENV.copy() - env['timeout'] = 'abc' - self.assertRaises(ValueError, self.create_client, env) - env['timeout'] = '1.5' - self.assertRaises(ValueError, self.create_client, env) - - def _test_v2_client_timeout_integer(self, timeout, expected_value): - env = FAKE_ENV.copy() - env['timeout'] = timeout - expected = { - 'auth_plugin': mock.ANY, - 'timeout': expected_value, - 'original_ip': None, - 'http': None, - 'region_name': None, - 'verify': True, - 'timings': None, - 'keyring_saver': None, - 'cert': None, - 'endpoint_type': None, - 'user_agent': None, - 'debug': None, - } - cls = 'ceilometerclient.apiclient.client.HTTPClient' - with mock.patch(cls) as mocked: - self.create_client(env) - mocked.assert_called_with(**expected) - - def test_v2_client_timeout_zero(self): - self._test_v2_client_timeout_integer(0, None) - - def test_v2_client_timeout_valid_value(self): - self._test_v2_client_timeout_integer(30, 30) - - @mock.patch.object(ks_session, 'Session') - def test_v2_client_timeout_keystone_session(self, mocked_session): - mocked_session.side_effect = RuntimeError('Stop!') - env = FAKE_ENV.copy() - env['timeout'] = 5 - del env['auth_plugin'] - del env['token'] - client = self.create_client(env) - self.assertRaises(RuntimeError, client.alarms.list) - args, kwargs = mocked_session.call_args - self.assertEqual(5, kwargs['timeout']) - - def test_v2_client_cacert_in_verify(self): - env = FAKE_ENV.copy() - env['cacert'] = '/path/to/cacert' - client = self.create_client(env) - self.assertEqual('/path/to/cacert', - client.http_client.http_client.verify) - - def test_v2_client_certfile_and_keyfile(self): - env = FAKE_ENV.copy() - env['cert_file'] = '/path/to/cert' - env['key_file'] = '/path/to/keycert' - client = self.create_client(env) - self.assertEqual(('/path/to/cert', '/path/to/keycert'), - client.http_client.http_client.cert) - - def test_v2_client_insecure(self): - env = FAKE_ENV.copy() - env.pop('auth_plugin') - env['os_insecure'] = 'True' - client = self.create_client(env) - self.assertIn('insecure', client.auth_plugin.opts) - self.assertEqual('True', client.auth_plugin.opts['insecure']) - - -class ClientTest2(ClientTest): - @staticmethod - def create_client(env, api_version=2, endpoint=None, exclude=[]): - env = dict((k, v) for k, v in env.items() - if k not in exclude) - with mock.patch( - 'ceilometerclient.v2.client.Client._get_redirect_client', - return_value=None): - return client.Client(api_version, endpoint, **env) - - -class ClientTestWithAodh(ClientTest): - @staticmethod - def create_client(env, api_version=2, endpoint=None, exclude=[]): - env = dict((k, v) for k, v in env.items() - if k not in exclude) - with mock.patch('ceilometerclient.apiclient.client.' - 'HTTPClient.client_request', - return_value=mock.MagicMock()): - return client.get_client(api_version, **env) - - def test_client_without_auth_plugin(self): - env = FAKE_ENV.copy() - del env['auth_plugin'] - c = self.create_client(env, api_version=2, endpoint='fake_endpoint') - self.assertIsInstance(c.alarm_client.http_client.auth_plugin, - client.AuthPlugin) - - def test_v2_client_insecure(self): - env = FAKE_ENV.copy() - env.pop('auth_plugin') - env['insecure'] = 'True' - client = self.create_client(env) - self.assertIn('insecure', - client.alarm_client.http_client.auth_plugin.opts) - self.assertEqual('True', (client.alarm_client.http_client. - auth_plugin.opts['insecure'])) - - def test_ceilometerclient_available_without_aodh_services_running(self): - env = FAKE_ENV.copy() - env.pop('auth_plugin', None) - with mock.patch('ceilometerclient.apiclient.client.' - 'HTTPClient.client_request') as mocked_request: - mocked_request.side_effect = requests.exceptions.ConnectionError - ceiloclient = client.get_client(2, **env) - self.assertIsInstance(ceiloclient, v2client.Client) - - @mock.patch('ceilometerclient.client.SessionClient') - def test_http_client_with_session_and_aodh(self, mock_sc): - session = mock.Mock() - kwargs = {"session": session, - "service_type": "metering", - "user_agent": "python-ceilometerclient"} - expected = { - "auth": None, - "interface": 'publicURL', - "region_name": None, - "timings": None, - "session": session, - "service_type": "metering", - "user_agent": "python-ceilometerclient"} - kwargs['aodh_endpoint'] = 'http://aodh.where' - client._construct_http_client(**kwargs) - mock_sc.assert_called_with(**expected) - - -class ClientAuthTest(utils.BaseTestCase): - - @staticmethod - def create_client(env, api_version=2, endpoint=None, exclude=[]): - env = dict((k, v) for k, v in env.items() - if k not in exclude) - with mock.patch('ceilometerclient.apiclient.client.' - 'HTTPClient.client_request', - return_value=mock.MagicMock()): - return client.get_client(api_version, **env) - - @mock.patch('keystoneauth1.discover.Discover') - @mock.patch('keystoneauth1.session.Session') - def test_discover_auth_versions(self, session, discover_mock): - env = FAKE_ENV.copy() - env.pop('auth_plugin', None) - - mock_session_instance = mock.MagicMock() - session.return_value = mock_session_instance - - client = self.create_client(env) - client.auth_plugin.opts.pop('token', None) - client.auth_plugin._do_authenticate(mock.MagicMock()) - - self.assertEqual([mock.call(url='http://no.where', - session=mock_session_instance)], - discover_mock.call_args_list) - self.assertIsInstance(mock_session_instance.auth, v3_auth.Password) - - @mock.patch('keystoneauth1.discover.Discover') - @mock.patch('keystoneauth1.session.Session') - def test_discover_auth_versions_v2_only(self, session, discover): - env = FAKE_ENV.copy() - env.pop('auth_plugin', None) - env.pop('user_domain_name', None) - env.pop('user_domain_id', None) - env.pop('project_domain_name', None) - env.pop('project_domain_id', None) - - session_instance_mock = mock.MagicMock() - session.return_value = session_instance_mock - - discover_instance_mock = mock.MagicMock() - discover_instance_mock.url_for.side_effect = (lambda v: v - if v == '2.0' else None) - discover.return_value = discover_instance_mock - - client = self.create_client(env) - client.auth_plugin.opts.pop('token', None) - client.auth_plugin._do_authenticate(mock.MagicMock()) - self.assertEqual([mock.call(url='http://no.where', - session=session_instance_mock)], - discover.call_args_list) - - self.assertIsInstance(session_instance_mock.auth, v2_auth.Password) - - @mock.patch('keystoneauth1.discover.Discover') - @mock.patch('keystoneauth1.session.Session') - def test_discover_auth_versions_raise_discovery_failure(self, - session, - discover): - env = FAKE_ENV.copy() - env.pop('auth_plugin', None) - env.pop('token', None) - - session_instance_mock = mock.MagicMock() - session.return_value = session_instance_mock - - discover_instance_mock = mock.MagicMock() - discover_instance_mock.url_for.side_effect = (lambda v: v - if v == '2.0' else None) - discover.side_effect = ks_exc.DiscoveryFailure - client = self.create_client(env) - self.assertRaises(ks_exc.DiscoveryFailure, - client.auth_plugin._do_authenticate, - mock.Mock()) - discover.side_effect = mock.MagicMock() - client = self.create_client(env) - discover.side_effect = ks_exc.DiscoveryFailure - client.auth_plugin.opts.pop('token', None) - - self.assertRaises(ks_exc.DiscoveryFailure, - client.auth_plugin._do_authenticate, - mock.Mock()) - self.assertEqual([mock.call(url='http://no.where', - session=session_instance_mock), - mock.call(url='http://no.where', - session=session_instance_mock)], - discover.call_args_list) - - @mock.patch('ceilometerclient.client._get_keystone_session') - def test_get_endpoint(self, session): - env = FAKE_ENV.copy() - env.pop('auth_plugin', None) - env.pop('endpoint', None) - - session_instance_mock = mock.MagicMock() - session.return_value = session_instance_mock - - client = self.create_client(env) - client.auth_plugin.opts.pop('endpoint') - client.auth_plugin.opts.pop('token', None) - alarm_auth_plugin = client.alarm_client.http_client.auth_plugin - alarm_auth_plugin.opts.pop('endpoint') - alarm_auth_plugin.opts.pop('token', None) - - self.assertNotEqual(client.auth_plugin, alarm_auth_plugin) - - client.auth_plugin._do_authenticate(mock.MagicMock()) - alarm_auth_plugin._do_authenticate(mock.MagicMock()) - - self.assertEqual([ - mock.call(interface='publicURL', region_name=None, - service_type='metering'), - mock.call(interface='publicURL', region_name=None, - service_type='alarming'), - ], session_instance_mock.get_endpoint.mock_calls) - - def test_http_client_with_session(self): - session = mock.Mock() - session.request.return_value = mock.Mock(status_code=404, - text=b'') - env = {"session": session, - "service_type": "metering", - "user_agent": "python-ceilometerclient"} - c = client.SessionClient(**env) - self.assertRaises(exc.HTTPException, c.get, "/") - - def test_get_aodh_endpoint_without_auth_url(self): - env = FAKE_ENV.copy() - env.pop('auth_plugin', None) - env.pop('endpoint', None) - env.pop('auth_url', None) - client = self.create_client(env, endpoint='fake_endpoint') - self.assertEqual(client.alarm_client.http_client.auth_plugin.opts, - client.auth_plugin.opts) - - @mock.patch('ceilometerclient.client._get_keystone_session') - def test_get_different_endpoint_type(self, session): - env = FAKE_ENV.copy() - env.pop('auth_plugin', None) - env.pop('endpoint', None) - env['endpoint_type'] = 'internal' - - session_instance_mock = mock.MagicMock() - session.return_value = session_instance_mock - - client = self.create_client(env) - client.auth_plugin.opts.pop('endpoint') - client.auth_plugin.opts.pop('token', None) - alarm_auth_plugin = client.alarm_client.http_client.auth_plugin - alarm_auth_plugin.opts.pop('endpoint') - alarm_auth_plugin.opts.pop('token', None) - - self.assertNotEqual(client.auth_plugin, alarm_auth_plugin) - - client.auth_plugin._do_authenticate(mock.MagicMock()) - alarm_auth_plugin._do_authenticate(mock.MagicMock()) - - self.assertEqual([ - mock.call(interface='internal', region_name=None, - service_type='metering'), - mock.call(interface='internal', region_name=None, - service_type='alarming'), - ], session_instance_mock.get_endpoint.mock_calls) - - @mock.patch('ceilometerclient.client._get_keystone_session') - def test_get_sufficient_options_missing(self, session): - env = FAKE_ENV.copy() - env.pop('auth_plugin', None) - env.pop('password', None) - env.pop('endpoint', None) - env.pop('auth_token', None) - env.pop('tenant_name', None) - env.pop('username', None) - - session_instance_mock = mock.MagicMock() - session.return_value = session_instance_mock - client = self.create_client(env) - client.auth_plugin.opts.pop('token', None) - self.assertRaises(exceptions.AuthPluginOptionsMissing, - client.auth_plugin.sufficient_options) diff --git a/ceilometerclient/tests/unit/test_exc.py b/ceilometerclient/tests/unit/test_exc.py deleted file mode 100644 index e67cfc8..0000000 --- a/ceilometerclient/tests/unit/test_exc.py +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright 2013 eNovance -# 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 json - -from ceilometerclient import exc -from ceilometerclient.tests.unit import utils - -HTTPEXCEPTIONS = {'HTTPBadRequest': exc.HTTPBadRequest, - 'HTTPUnauthorized': exc.HTTPUnauthorized, - 'HTTPForbidden': exc.HTTPForbidden, - 'HTTPNotFound': exc.HTTPNotFound, - 'HTTPMethodNotAllowed': exc.HTTPMethodNotAllowed, - 'HTTPConflict': exc.HTTPConflict, - 'HTTPOverLimit': exc.HTTPOverLimit, - 'HTTPInternalServerError': exc.HTTPInternalServerError, - 'HTTPNotImplemented': exc.HTTPNotImplemented, - 'HTTPBadGateway': exc.HTTPBadGateway, - 'HTTPServiceUnavailable': exc.HTTPServiceUnavailable} - - -class HTTPExceptionsTest(utils.BaseTestCase): - def test_str_no_details(self): - for k, v in HTTPEXCEPTIONS.items(): - exception = v() - ret_str = k + " (HTTP " + str(exception.code) + ")" - self.assertEqual(ret_str, str(exception)) - - def test_str_no_json(self): - for k, v in HTTPEXCEPTIONS.items(): - exception = v(details="foo") - ret_str = k + " (HTTP " + str(exception.code) + ") foo" - self.assertEqual(ret_str, str(exception)) - - def test_str_no_error_message(self): - for k, v in HTTPEXCEPTIONS.items(): - exception = v(details=json.dumps({})) - ret_str = k + " (HTTP " + str(exception.code) + ")" - self.assertEqual(ret_str, str(exception)) - - def test_str_no_faultstring(self): - for k, v in HTTPEXCEPTIONS.items(): - exception = v( - details=json.dumps({"error_message": {"foo": "bar"}})) - ret_str = (k + " (HTTP " + str(exception.code) + ") " + - str({u'foo': u'bar'})) - self.assertEqual(ret_str, str(exception)) - - def test_str_error_message_unknown_format(self): - for k, v in HTTPEXCEPTIONS.items(): - exception = v(details=json.dumps({"error_message": "oops"})) - ret_str = k + " (HTTP " + str(exception.code) + ") oops" - self.assertEqual(ret_str, str(exception)) - - def test_str_faultstring(self): - for k, v in HTTPEXCEPTIONS.items(): - exception = v(details=json.dumps( - {"error_message": {"faultstring": "oops"}})) - ret_str = k + " (HTTP " + str(exception.code) + ") ERROR oops" - self.assertEqual(ret_str, str(exception)) - - def test_from_response(self): - class HTTPLibLikeResponse(object): - status = 400 - - class RequestsLikeResponse(object): - status_code = 401 - - class UnexpectedResponse(object): - code = 200 - - self.assertEqual(HTTPLibLikeResponse.status, - exc.from_response(HTTPLibLikeResponse).code) - self.assertEqual(RequestsLikeResponse.status_code, - exc.from_response(RequestsLikeResponse).code) - self.assertRaises(TypeError, exc.from_response, UnexpectedResponse) diff --git a/ceilometerclient/tests/unit/test_openstack_common.py b/ceilometerclient/tests/unit/test_openstack_common.py deleted file mode 100644 index 04a94e2..0000000 --- a/ceilometerclient/tests/unit/test_openstack_common.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2015 Huawei. -# 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. - -from ceilometerclient.common import base -from ceilometerclient.tests.unit import utils -from ceilometerclient.v2 import events - - -class BaseTest(utils.BaseTestCase): - - def test_two_resources_with_same_id_are_not_equal(self): - # Two resources with same ID: never equal if their info is not equal - r1 = base.Resource(None, {'id': 1, 'name': 'hi'}) - r2 = base.Resource(None, {'id': 1, 'name': 'hello'}) - self.assertNotEqual(r1, r2) - - def test_two_resources_with_same_id_and_info_are_equal(self): - # Two resources with same ID: equal if their info is equal - r1 = base.Resource(None, {'id': 1, 'name': 'hello'}) - r2 = base.Resource(None, {'id': 1, 'name': 'hello'}) - self.assertEqual(r1, r2) - - def test_two_resources_with_diff_type_are_not_equal(self): - # Two resources of different types: never equal - r1 = base.Resource(None, {'id': 1}) - r2 = events.Event(None, {'id': 1}) - self.assertNotEqual(r1, r2) - - def test_two_resources_with_no_id_are_equal(self): - # Two resources with no ID: equal if their info is equal - r1 = base.Resource(None, {'name': 'joe', 'age': 12}) - r2 = base.Resource(None, {'name': 'joe', 'age': 12}) - self.assertEqual(r1, r2) diff --git a/ceilometerclient/tests/unit/test_shell.py b/ceilometerclient/tests/unit/test_shell.py deleted file mode 100644 index d666594..0000000 --- a/ceilometerclient/tests/unit/test_shell.py +++ /dev/null @@ -1,287 +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 re -import sys - -import fixtures -from keystoneauth1 import session as ks_session -import mock -import six -from testtools import matchers - -from ceilometerclient.apiclient import client as api_client -from ceilometerclient import client -from ceilometerclient import exc -from ceilometerclient import shell as ceilometer_shell -from ceilometerclient.tests.unit import utils - -FAKE_V2_ENV = {'OS_USERNAME': 'username', - 'OS_PASSWORD': 'password', - 'OS_TENANT_NAME': 'tenant_name', - 'OS_AUTH_URL': 'http://localhost:5000/v2.0'} - -FAKE_V3_ENV = {'OS_USERNAME': 'username', - 'OS_PASSWORD': 'password', - 'OS_USER_DOMAIN_NAME': 'domain_name', - 'OS_PROJECT_ID': '1234567890', - 'OS_AUTH_URL': 'http://localhost:5000/v3'} - - -class ShellTestBase(utils.BaseTestCase): - - @mock.patch('sys.stdout', new=six.StringIO()) - @mock.patch.object(ks_session, 'Session', mock.MagicMock()) - @mock.patch.object(client.client.HTTPClient, - 'client_request', mock.MagicMock()) - def shell(self, argstr): - try: - _shell = ceilometer_shell.CeilometerShell() - _shell.main(argstr.split()) - except SystemExit: - exc_type, exc_value, exc_traceback = sys.exc_info() - self.assertEqual(0, exc_value.code) - - return sys.stdout.getvalue() - - # Patch os.environ to avoid required auth info. - def make_env(self, env_version, exclude=None): - env = dict((k, v) for k, v in env_version.items() if k != exclude) - self.useFixture(fixtures.MonkeyPatch('os.environ', env)) - - -class ShellHelpTest(ShellTestBase): - RE_OPTIONS = re.DOTALL | re.MULTILINE - - def test_help_unknown_command(self): - self.assertRaises(exc.CommandError, self.shell, 'help foofoo') - - def test_help(self): - required = [ - '.*?^usage: ceilometer', - '.*?^See "ceilometer help COMMAND" ' - 'for help on a specific command', - ] - for argstr in ['--help', 'help']: - help_text = self.shell(argstr) - for r in required: - self.assertThat(help_text, - matchers.MatchesRegex(r, - self.RE_OPTIONS)) - - def test_help_on_subcommand(self): - required = [ - '.*?^usage: ceilometer meter-list', - ".*?^List the user's meter", - ] - argstrings = [ - 'help meter-list', - ] - for argstr in argstrings: - help_text = self.shell(argstr) - for r in required: - self.assertThat(help_text, - matchers.MatchesRegex(r, self.RE_OPTIONS)) - - def test_get_base_parser(self): - standalone_shell = ceilometer_shell.CeilometerShell() - parser = standalone_shell.get_base_parser() - self.assertEqual(600, parser.get_default('timeout')) - - -class ShellBashCompletionTest(ShellTestBase): - - def test_bash_completion(self): - completion_commands = self.shell("bash-completion") - options = completion_commands.split(' ') - self.assertNotIn('bash_completion', options) - for option in options: - self.assertThat(option, - matchers.MatchesRegex(r'[a-z0-9-]')) - - -class ShellKeystoneV2Test(ShellTestBase): - - @mock.patch.object(ks_session, 'Session') - @mock.patch('ceilometerclient.v2.client.Client._get_redirect_client', - mock.Mock(return_value=None)) - def test_debug_switch_raises_error(self, mock_ksclient): - mock_ksclient.side_effect = exc.HTTPUnauthorized - self.make_env(FAKE_V2_ENV) - args = ['--debug', 'event-list'] - self.assertRaises(exc.CommandError, ceilometer_shell.main, args) - - @mock.patch.object(ks_session, 'Session') - @mock.patch('ceilometerclient.v2.client.Client._get_redirect_client', - mock.Mock(return_value=None)) - def test_dash_d_switch_raises_error(self, mock_ksclient): - mock_ksclient.side_effect = exc.CommandError("FAIL") - self.make_env(FAKE_V2_ENV) - args = ['-d', 'event-list'] - self.assertRaises(exc.CommandError, ceilometer_shell.main, args) - - @mock.patch('sys.stderr') - @mock.patch.object(ks_session, 'Session') - def test_no_debug_switch_no_raises_errors(self, mock_ksclient, __): - mock_ksclient.side_effect = exc.HTTPUnauthorized("FAIL") - self.make_env(FAKE_V2_ENV) - args = ['event-list'] - self.assertRaises(SystemExit, ceilometer_shell.main, args) - - -class ShellKeystoneV3Test(ShellTestBase): - - @mock.patch.object(ks_session, 'Session') - @mock.patch('ceilometerclient.v2.client.Client._get_redirect_client', - mock.Mock(return_value=None)) - def test_debug_switch_raises_error(self, mock_ksclient): - mock_ksclient.side_effect = exc.HTTPUnauthorized - self.make_env(FAKE_V3_ENV) - args = ['--debug', 'event-list'] - self.assertRaises(exc.CommandError, ceilometer_shell.main, args) - - @mock.patch.object(ks_session, 'Session') - def test_dash_d_switch_raises_error(self, mock_ksclient): - mock_ksclient.side_effect = exc.CommandError("FAIL") - self.make_env(FAKE_V3_ENV) - args = ['-d', 'event-list'] - self.assertRaises(exc.CommandError, ceilometer_shell.main, args) - - @mock.patch('sys.stderr') - @mock.patch.object(ks_session, 'Session') - def test_no_debug_switch_no_raises_errors(self, mock_ksclient, __): - mock_ksclient.side_effect = exc.HTTPUnauthorized("FAIL") - self.make_env(FAKE_V3_ENV) - args = ['event-list'] - self.assertRaises(SystemExit, ceilometer_shell.main, args) - - -class ShellTimeoutTest(ShellTestBase): - - @mock.patch('sys.stderr', new=six.StringIO()) - def _test_timeout(self, timeout, expected_msg): - args = ['--timeout', timeout, 'alarm-list'] - self.assertRaises(SystemExit, ceilometer_shell.main, args) - self.assertEqual(expected_msg, sys.stderr.getvalue().splitlines()[-1]) - - def test_timeout_invalid_value(self): - expected_msg = ('ceilometer: error: argument --timeout: ' - 'abc must be an integer') - self._test_timeout('abc', expected_msg) - - def test_timeout_negative_value(self): - expected_msg = ('ceilometer: error: argument --timeout: ' - '-1 must be greater than 0') - self._test_timeout('-1', expected_msg) - - def test_timeout_float_value(self): - expected_msg = ('ceilometer: error: argument --timeout: ' - '1.5 must be an integer') - self._test_timeout('1.5', expected_msg) - - def test_timeout_zero(self): - expected_msg = ('ceilometer: error: argument --timeout: ' - '0 must be greater than 0') - self._test_timeout('0', expected_msg) - - @mock.patch.object(ks_session, 'Session') - @mock.patch('ceilometerclient.v2.client.Client._get_redirect_client', - mock.Mock(return_value=None)) - def test_timeout_keystone_session(self, mocked_session): - mocked_session.side_effect = exc.HTTPUnauthorized("FAIL") - self.make_env(FAKE_V2_ENV) - args = ['--debug', '--timeout', '5', 'alarm-list'] - self.assertRaises(exc.CommandError, ceilometer_shell.main, args) - args, kwargs = mocked_session.call_args - self.assertEqual(5, kwargs.get('timeout')) - - -class ShellInsecureTest(ShellTestBase): - - @mock.patch.object(api_client, 'HTTPClient') - @mock.patch('ceilometerclient.v2.client.Client._get_redirect_client', - mock.Mock(return_value=None)) - def test_insecure_true_ceilometer(self, mocked_client): - self.make_env(FAKE_V2_ENV) - args = ['--debug', '--os-insecure', 'true', 'alarm-list'] - self.assertIsNone(ceilometer_shell.main(args)) - args, kwargs = mocked_client.call_args - self.assertFalse(kwargs.get('verify')) - - @mock.patch.object(ks_session, 'Session') - @mock.patch('ceilometerclient.v2.client.Client._get_redirect_client', - mock.Mock(return_value=None)) - def test_insecure_true_keystone(self, mocked_session): - mocked_session.side_effect = exc.HTTPUnauthorized("FAIL") - self.make_env(FAKE_V2_ENV) - args = ['--debug', '--os-insecure', 'true', 'alarm-list'] - self.assertRaises(exc.CommandError, ceilometer_shell.main, args) - args, kwargs = mocked_session.call_args - self.assertFalse(kwargs.get('verify')) - - @mock.patch.object(api_client, 'HTTPClient') - @mock.patch('ceilometerclient.v2.client.Client._get_redirect_client', - mock.Mock(return_value=None)) - def test_insecure_false_ceilometer(self, mocked_client): - self.make_env(FAKE_V2_ENV) - args = ['--debug', '--os-insecure', 'false', 'alarm-list'] - self.assertIsNone(ceilometer_shell.main(args)) - args, kwargs = mocked_client.call_args - self.assertTrue(kwargs.get('verify')) - - @mock.patch.object(ks_session, 'Session') - @mock.patch('ceilometerclient.v2.client.Client._get_redirect_client', - mock.Mock(return_value=None)) - def test_insecure_false_keystone(self, mocked_session): - mocked_session.side_effect = exc.HTTPUnauthorized("FAIL") - self.make_env(FAKE_V2_ENV) - args = ['--debug', '--os-insecure', 'false', 'alarm-list'] - self.assertRaises(exc.CommandError, ceilometer_shell.main, args) - args, kwargs = mocked_session.call_args - self.assertTrue(kwargs.get('verify')) - - -class ShellEndpointTest(ShellTestBase): - - @mock.patch('ceilometerclient.v2.client.Client') - def _test_endpoint_and_token(self, token_name, endpoint_name, mocked): - args = ['--debug', token_name, 'fake-token', - endpoint_name, 'http://fake-url', 'alarm-list'] - self.assertIsNone(ceilometer_shell.main(args)) - args, kwargs = mocked.call_args - self.assertEqual('http://fake-url', kwargs.get('endpoint')) - self.assertEqual('fake-token', kwargs.get('token')) - - def test_endpoint_and_token(self): - self._test_endpoint_and_token('--os-auth-token', '--ceilometer-url') - self._test_endpoint_and_token('--os-auth-token', '--os-endpoint') - self._test_endpoint_and_token('--os-token', '--ceilometer-url') - self._test_endpoint_and_token('--os-token', '--os-endpoint') - - -class ShellAlarmUpdateRepeatAction(ShellTestBase): - @mock.patch('ceilometerclient.v2.alarms.AlarmManager.update') - @mock.patch('ceilometerclient.v2.client.Client._get_redirect_client', - mock.Mock()) - def test_repeat_action_not_specified(self, mocked): - self.make_env(FAKE_V2_ENV) - - def _test(method): - args = ['--debug', method, '--state', 'alarm', '123'] - ceilometer_shell.main(args) - args, kwargs = mocked.call_args - self.assertIsNone(kwargs.get('repeat_actions')) - - _test('alarm-update') - _test('alarm-threshold-update') - _test('alarm-combination-update') - _test('alarm-event-update') diff --git a/ceilometerclient/tests/unit/test_utils.py b/ceilometerclient/tests/unit/test_utils.py deleted file mode 100644 index 5b20437..0000000 --- a/ceilometerclient/tests/unit/test_utils.py +++ /dev/null @@ -1,298 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# 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 itertools - -import mock -import six - -from ceilometerclient.common import utils -from ceilometerclient.tests.unit import utils as test_utils - - -class UtilsTest(test_utils.BaseTestCase): - - def test_prettytable(self): - class Struct(object): - def __init__(self, **entries): - self.__dict__.update(entries) - - # test that the prettytable output is wellformatted (left-aligned) - with mock.patch('sys.stdout', new=six.StringIO()) as stdout: - utils.print_dict({'K': 'k', 'Key': 'Value'}) - self.assertEqual('''\ -+----------+-------+ -| Property | Value | -+----------+-------+ -| K | k | -| Key | Value | -+----------+-------+ -''', stdout.getvalue()) - - with mock.patch('sys.stdout', new=six.StringIO()) as stdout: - utils.print_dict({'alarm_id': '262567fd-d79a-4bbb-a9d0-59d879b6', - 'name': u'\u6d4b\u8bd5', - 'description': u'\u6d4b\u8bd5', - 'state': 'insufficient data', - 'repeat_actions': 'False', - 'type': 'threshold', - 'threshold': '1.0', - 'statistic': 'avg', - 'alarm_actions': [u'http://something/alarm1', - u'http://something/alarm2'], - 'ok_actions': [{"get_attr1": - [u"web_server_scaleup_policy1", - u"alarm_url1"]}, - {"get_attr2": - [u"web_server_scaleup_policy2", - u"alarm_url2"]}], - 'time_constraints': '[{name: c1,' - '\\n description: test,' - '\\n start: 0 18 * * *,' - '\\n duration: 1,' - '\\n timezone: US}]'}, - wrap=72) - expected = u'''\ -+------------------+-------------------------------------------------------\ ---------+ -| Property | Value \ - | -+------------------+-------------------------------------------------------\ ---------+ -| alarm_actions | ["http://something/alarm1", "http://something/alarm2"]\ - | -| alarm_id | 262567fd-d79a-4bbb-a9d0-59d879b6 \ - | -| description | \u6d4b\u8bd5 \ - | -| name | \u6d4b\u8bd5 \ - | -| ok_actions | [{"get_attr1": ["web_server_scaleup_policy1", "alarm_u\ -rl1"]}, | -| | {"get_attr2": ["web_server_scaleup_policy2", "alarm_ur\ -l2"]}] | -| repeat_actions | False \ - | -| state | insufficient data \ - | -| statistic | avg \ - | -| threshold | 1.0 \ - | -| time_constraints | [{name: c1, \ - | -| | description: test, \ - | -| | start: 0 18 * * *, \ - | -| | duration: 1, \ - | -| | timezone: US}] \ - | -| type | threshold \ - | -+------------------+-------------------------------------------------------\ ---------+ -''' - # py2 prints str type, py3 prints unicode type - if six.PY2: - expected = expected.encode('utf-8') - self.assertEqual(expected, stdout.getvalue()) - - def test_print_list(self): - class Foo(object): - def __init__(self, one, two, three): - self.one = one - self.two = two - self.three = three - - foo_list = [ - Foo(10, 'a', 'B'), - Foo(8, 'c', 'c'), - Foo(12, '0', 'Z')] - - def do_print_list(sortby): - with mock.patch('sys.stdout', new=six.StringIO()) as stdout: - utils.print_list(foo_list, - ['one', 'two', 'three'], - ['1st', '2nd', '3rd'], - {'one': lambda o: o.one * 10}, - sortby) - return stdout.getvalue() - - printed = do_print_list(None) - self.assertEqual('''\ -+-----+-----+-----+ -| 1st | 2nd | 3rd | -+-----+-----+-----+ -| 100 | a | B | -| 80 | c | c | -| 120 | 0 | Z | -+-----+-----+-----+ -''', printed) - - printed = do_print_list(0) - self.assertEqual('''\ -+-----+-----+-----+ -| 1st | 2nd | 3rd | -+-----+-----+-----+ -| 80 | c | c | -| 100 | a | B | -| 120 | 0 | Z | -+-----+-----+-----+ -''', printed) - - printed = do_print_list(1) - self.assertEqual('''\ -+-----+-----+-----+ -| 1st | 2nd | 3rd | -+-----+-----+-----+ -| 120 | 0 | Z | -| 100 | a | B | -| 80 | c | c | -+-----+-----+-----+ -''', printed) - - def test_args_array_to_dict(self): - my_args = { - 'matching_metadata': ['metadata.key=metadata_value'], - 'other': 'value' - } - cleaned_dict = utils.args_array_to_dict(my_args, - "matching_metadata") - self.assertEqual({ - 'matching_metadata': {'metadata.key': 'metadata_value'}, - 'other': 'value' - }, cleaned_dict) - - def test_args_array_to_list_of_dicts(self): - starts = ['0 11 * * *', '"0 11 * * *"', '\'0 11 * * *\''] - timezones = [None, 'US/Eastern', '"US/Eastern"', '\'US/Eastern\''] - descs = [None, 'de sc', '"de sc"', '\'de sc\''] - for start, tz, desc in itertools.product(starts, timezones, descs): - my_args = { - 'time_constraints': ['name=const1;start=%s;duration=1' - % start], - 'other': 'value' - } - expected = { - 'time_constraints': [dict(name='const1', - start='0 11 * * *', - duration='1')], - 'other': 'value' - } - if tz: - my_args['time_constraints'][0] += ';timezone=%s' % tz - expected['time_constraints'][0]['timezone'] = 'US/Eastern' - if desc: - my_args['time_constraints'][0] += ';description=%s' % desc - expected['time_constraints'][0]['description'] = 'de sc' - - cleaned = utils.args_array_to_list_of_dicts(my_args, - 'time_constraints') - self.assertEqual(expected, cleaned) - - def test_key_with_slash_to_nested_dict(self): - my_args = { - 'combination_rule/alarm_ids': ['id1', 'id2'], - 'combination_rule/operator': 'and', - 'threshold_rule/threshold': 400, - 'threshold_rule/statictic': 'avg', - 'threshold_rule/comparison_operator': 'or', - } - nested_dict = utils.key_with_slash_to_nested_dict(my_args) - self.assertEqual({ - 'combination_rule': {'alarm_ids': ['id1', 'id2'], - 'operator': 'and'}, - 'threshold_rule': {'threshold': 400, - 'statictic': 'avg', - 'comparison_operator': 'or'}, - }, nested_dict) - - def test_arg(self): - @utils.arg(help="not_required_no_default.") - def not_required_no_default(): - pass - _, args = not_required_no_default.__dict__['arguments'][0] - self.assertEqual("not_required_no_default.", args['help']) - - @utils.arg(required=True, help="required_no_default.") - def required_no_default(): - pass - _, args = required_no_default.__dict__['arguments'][0] - self.assertEqual("required_no_default. Required.", args['help']) - - @utils.arg(default=42, help="not_required_default.") - def not_required_default(): - pass - _, args = not_required_default.__dict__['arguments'][0] - self.assertEqual("not_required_default. Defaults to 42.", args['help']) - - def test_merge_nested_dict(self): - dest = {'key': 'value', - 'nested': {'key2': 'value2', - 'key3': 'value3', - 'nested2': {'key': 'value', - 'some': 'thing'}}} - source = {'key': 'modified', - 'nested': {'key3': 'modified3', - 'nested2': {'key5': 'value5'}}} - utils.merge_nested_dict(dest, source, depth=1) - - self.assertEqual({'key': 'modified', - 'nested': {'key2': 'value2', - 'key3': 'modified3', - 'nested2': {'key5': 'value5'}}}, dest) - - def test_merge_nested_dict_no_depth(self): - dest = {'key': 'value', - 'nested': {'key2': 'value2', - 'key3': 'value3', - 'nested2': {'key': 'value', - 'some': 'thing'}}} - source = {'key': 'modified', - 'nested': {'key3': 'modified3', - 'nested2': {'key5': 'value5'}}} - utils.merge_nested_dict(dest, source) - - self.assertEqual({'key': 'modified', - 'nested': {'key3': 'modified3', - 'nested2': {'key5': 'value5'}}}, dest) - - @mock.patch('prettytable.PrettyTable') - def test_format_nested_list_of_dict(self, pt_mock): - actual_rows = [] - - def mock_add_row(row): - actual_rows.append(row) - - table = mock.Mock() - table.add_row = mock_add_row - table.get_string.return_value = "the table" - - test_data = [ - {'column_1': 'value_c', 'column_2': 'value_23'}, - {'column_1': 'value_b', 'column_2': 'value_22'}, - {'column_1': 'value_a', 'column_2': 'value_21'} - ] - columns = ['column_1', 'column_2'] - pt_mock.return_value = table - - rval = utils.format_nested_list_of_dict(test_data, columns) - self.assertEqual("the table", rval) - self.assertEqual([['value_a', 'value_21'], - ['value_b', 'value_22'], - ['value_c', 'value_23']], - actual_rows) diff --git a/ceilometerclient/tests/unit/utils.py b/ceilometerclient/tests/unit/utils.py deleted file mode 100644 index 57bc276..0000000 --- a/ceilometerclient/tests/unit/utils.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright 2012 OpenStack Foundation -# 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 fixtures -import testtools - - -class BaseTestCase(testtools.TestCase): - - def setUp(self): - super(BaseTestCase, self).setUp() - self.useFixture(fixtures.FakeLogger()) diff --git a/ceilometerclient/tests/unit/v2/__init__.py b/ceilometerclient/tests/unit/v2/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ceilometerclient/tests/unit/v2/test_alarms.py b/ceilometerclient/tests/unit/v2/test_alarms.py deleted file mode 100644 index 20fa118..0000000 --- a/ceilometerclient/tests/unit/v2/test_alarms.py +++ /dev/null @@ -1,559 +0,0 @@ -# -# Copyright 2013 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. - -import copy - -import six -from six.moves import xrange # noqa -import testtools - -from ceilometerclient.apiclient import client -from ceilometerclient.apiclient import fake_client -from ceilometerclient import exc -from ceilometerclient.v2 import alarms - -AN_ALARM = {u'alarm_actions': [u'http://site:8000/alarm'], - u'ok_actions': [u'http://site:8000/ok'], - u'description': u'An alarm', - u'type': u'threshold', - u'severity': 'low', - u'threshold_rule': { - u'meter_name': u'storage.objects', - u'query': [{u'field': u'key_name', - u'op': u'eq', - u'value': u'key_value'}], - u'evaluation_periods': 2, - u'period': 240.0, - u'statistic': u'avg', - u'threshold': 200.0, - u'comparison_operator': 'gt'}, - u'time_constraints': [ - { - u'name': u'cons1', - u'description': u'desc1', - u'start': u'0 11 * * *', - u'duration': 300, - u'timezone': u''}, - { - u'name': u'cons2', - u'description': u'desc2', - u'start': u'0 23 * * *', - u'duration': 600, - u'timezone': ''}], - u'timestamp': u'2013-05-09T13:41:23.085000', - u'enabled': True, - u'alarm_id': u'alarm-id', - u'state': u'ok', - u'insufficient_data_actions': [u'http://site:8000/nodata'], - u'user_id': u'user-id', - u'project_id': u'project-id', - u'state_timestamp': u'2013-05-09T13:41:23.085000', - u'repeat_actions': False, - u'name': 'SwiftObjectAlarm'} -CREATE_ALARM = copy.deepcopy(AN_ALARM) -del CREATE_ALARM['timestamp'] -del CREATE_ALARM['state_timestamp'] -del CREATE_ALARM['alarm_id'] -CREATE_ALARM_WITHOUT_TC = copy.deepcopy(CREATE_ALARM) -del CREATE_ALARM_WITHOUT_TC['time_constraints'] -DELTA_ALARM = {u'alarm_actions': ['url1', 'url2']} -DELTA_ALARM_RULE = {u'comparison_operator': u'lt', - u'threshold': 42.1, - u'meter_name': u'foobar', - u'query': [{u'field': u'key_name', - u'op': u'eq', - u'value': u'key_value'}]} -DELTA_ALARM_TC = [{u'name': u'cons1', - u'duration': 500}] -DELTA_ALARM['time_constraints'] = DELTA_ALARM_TC -DELTA_ALARM['user_id'] = u'new-user-id' -UPDATED_ALARM = copy.deepcopy(AN_ALARM) -UPDATED_ALARM.update(DELTA_ALARM) -UPDATED_ALARM['threshold_rule'].update(DELTA_ALARM_RULE) -DELTA_ALARM['remove_time_constraints'] = 'cons2' -UPDATED_ALARM['time_constraints'] = [{u'name': u'cons1', - u'description': u'desc1', - u'start': u'0 11 * * *', - u'duration': 500, - u'timezone': u''}] -DELTA_ALARM['threshold_rule'] = DELTA_ALARM_RULE -UPDATE_ALARM = copy.deepcopy(UPDATED_ALARM) -UPDATE_ALARM['remove_time_constraints'] = 'cons2' -UPDATE_ALARM['user_id'] = u'new-user-id' -del UPDATE_ALARM['project_id'] -del UPDATE_ALARM['name'] -del UPDATE_ALARM['alarm_id'] -del UPDATE_ALARM['timestamp'] -del UPDATE_ALARM['state_timestamp'] - -AN_LEGACY_ALARM = {u'alarm_actions': [u'http://site:8000/alarm'], - u'ok_actions': [u'http://site:8000/ok'], - u'description': u'An alarm', - u'matching_metadata': {u'key_name': u'key_value'}, - u'evaluation_periods': 2, - u'timestamp': u'2013-05-09T13:41:23.085000', - u'enabled': True, - u'meter_name': u'storage.objects', - u'period': 240.0, - u'alarm_id': u'alarm-id', - u'state': u'ok', - u'severity': u'low', - u'insufficient_data_actions': [u'http://site:8000/nodata'], - u'statistic': u'avg', - u'threshold': 200.0, - u'user_id': u'user-id', - u'project_id': u'project-id', - u'state_timestamp': u'2013-05-09T13:41:23.085000', - u'comparison_operator': 'gt', - u'repeat_actions': False, - u'name': 'SwiftObjectAlarm'} -CREATE_LEGACY_ALARM = copy.deepcopy(AN_LEGACY_ALARM) -del CREATE_LEGACY_ALARM['timestamp'] -del CREATE_LEGACY_ALARM['state_timestamp'] -del CREATE_LEGACY_ALARM['alarm_id'] -DELTA_LEGACY_ALARM = {u'alarm_actions': ['url1', 'url2'], - u'comparison_operator': u'lt', - u'meter_name': u'foobar', - u'threshold': 42.1} -DELTA_LEGACY_ALARM['time_constraints'] = [{u'name': u'cons1', - u'duration': 500}] -DELTA_LEGACY_ALARM['user_id'] = u'new-user-id' -DELTA_LEGACY_ALARM['remove_time_constraints'] = 'cons2' -UPDATED_LEGACY_ALARM = copy.deepcopy(AN_LEGACY_ALARM) -UPDATED_LEGACY_ALARM.update(DELTA_LEGACY_ALARM) -UPDATE_LEGACY_ALARM = copy.deepcopy(UPDATED_LEGACY_ALARM) -UPDATE_LEGACY_ALARM['user_id'] = u'new-user-id' -del UPDATE_LEGACY_ALARM['project_id'] -del UPDATE_LEGACY_ALARM['name'] -del UPDATE_LEGACY_ALARM['alarm_id'] -del UPDATE_LEGACY_ALARM['timestamp'] -del UPDATE_LEGACY_ALARM['state_timestamp'] - -FULL_DETAIL = ('{"alarm_actions": [], ' - '"user_id": "8185aa72421a4fd396d4122cba50e1b5", ' - '"name": "scombo", ' - '"timestamp": "2013-10-03T08:58:33.647912", ' - '"enabled": true, ' - '"state_timestamp": "2013-10-03T08:58:33.647912", ' - '"rule": {"operator": "or", "alarm_ids": ' - '["062cc907-3a9f-4867-ab3b-fa83212b39f7"]}, ' - '"alarm_id": "alarm-id, ' - '"state": "insufficient data", ' - '"insufficient_data_actions": [], ' - '"repeat_actions": false, ' - '"ok_actions": [], ' - '"project_id": "57d04f24d0824b78b1ea9bcecedbda8f", ' - '"type": "combination", ' - '"description": "Combined state of alarms ' - '062cc907-3a9f-4867-ab3b-fa83212b39f7"}') -ALARM_HISTORY = [{'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', - 'user_id': '8185aa72421a4fd396d4122cba50e1b5', - 'event_id': 'c74a8611-6553-4764-a860-c15a6aabb5d0', - 'timestamp': '2013-10-03T08:59:28.326000', - 'detail': '{"state": "alarm"}', - 'alarm_id': 'alarm-id', - 'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', - 'type': 'state transition'}, - {'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', - 'user_id': '8185aa72421a4fd396d4122cba50e1b5', - 'event_id': 'c74a8611-6553-4764-a860-c15a6aabb5d0', - 'timestamp': '2013-10-03T08:59:28.326000', - 'detail': '{"description": "combination of one"}', - 'alarm_id': 'alarm-id', - 'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', - 'type': 'rule change'}, - {'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', - 'user_id': '8185aa72421a4fd396d4122cba50e1b5', - 'event_id': '4fd7df9e-190d-4471-8884-dc5a33d5d4bb', - 'timestamp': '2013-10-03T08:58:33.647000', - 'detail': FULL_DETAIL, - 'alarm_id': 'alarm-id', - 'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', - 'type': 'creation'}] - -fixtures = { - '/v2/alarms': - { - 'GET': ( - {}, - [AN_ALARM], - ), - 'POST': ( - {}, - CREATE_ALARM, - ), - }, - '/v2/alarms/alarm-id': - { - 'GET': ( - {}, - AN_ALARM, - ), - 'PUT': ( - {}, - UPDATED_ALARM, - ), - 'DELETE': ( - {}, - None, - ), - }, - '/v2/alarms/unk-alarm-id': - { - 'GET': ( - {}, - None, - ), - 'PUT': ( - {}, - None, - ), - }, - '/v2/alarms/alarm-id/state': - { - 'PUT': ( - {}, - {'alarm': 'alarm'} - ), - 'GET': ( - {}, - {'alarm': 'alarm'} - ), - - }, - '/v2/alarms?q.field=project_id&q.field=name&q.op=&q.op=' - '&q.type=&q.type=&q.value=project-id&q.value=SwiftObjectAlarm': - { - 'GET': ( - {}, - [AN_ALARM], - ), - }, - '/v2/alarms/victim-id': - { - 'DELETE': ( - {}, - None, - ), - }, - '/v2/alarms/alarm-id/history': - { - 'GET': ( - {}, - ALARM_HISTORY, - ), - }, - '/v2/alarms/alarm-id/history?q.field=timestamp&q.op=&q.type=&q.value=NOW': - { - 'GET': ( - {}, - ALARM_HISTORY, - ), - }, -} - - -class AlarmManagerTest(testtools.TestCase): - - def setUp(self): - super(AlarmManagerTest, self).setUp() - self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) - self.api = client.BaseClient(self.http_client) - self.mgr = alarms.AlarmManager(self.api) - - def test_list_all(self): - alarms = list(self.mgr.list()) - expect = [ - 'GET', '/v2/alarms' - ] - self.http_client.assert_called(*expect) - self.assertEqual(1, len(alarms)) - self.assertEqual('alarm-id', alarms[0].alarm_id) - - def test_list_with_query(self): - alarms = list(self.mgr.list(q=[{"field": "project_id", - "value": "project-id"}, - {"field": "name", - "value": "SwiftObjectAlarm"}])) - expect = [ - 'GET', - '/v2/alarms?q.field=project_id&q.field=name&q.op=&q.op=' - '&q.type=&q.type=&q.value=project-id&q.value=SwiftObjectAlarm', - ] - self.http_client.assert_called(*expect) - self.assertEqual(1, len(alarms)) - self.assertEqual('alarm-id', alarms[0].alarm_id) - - def test_get(self): - alarm = self.mgr.get(alarm_id='alarm-id') - expect = [ - 'GET', '/v2/alarms/alarm-id' - ] - self.http_client.assert_called(*expect) - self.assertIsNotNone(alarm) - self.assertEqual('alarm-id', alarm.alarm_id) - self.assertEqual(alarm.rule, alarm.threshold_rule) - - def test_create(self): - alarm = self.mgr.create(**CREATE_ALARM) - expect = [ - 'POST', '/v2/alarms' - ] - self.http_client.assert_called(*expect, body=CREATE_ALARM) - self.assertIsNotNone(alarm) - - def test_update(self): - alarm = self.mgr.update(alarm_id='alarm-id', **UPDATE_ALARM) - expect_get = [ - 'GET', '/v2/alarms/alarm-id' - ] - expect_put = [ - 'PUT', '/v2/alarms/alarm-id', UPDATED_ALARM - ] - self.http_client.assert_called(*expect_get, pos=0) - self.http_client.assert_called(*expect_put, pos=1) - self.assertIsNotNone(alarm) - self.assertEqual('alarm-id', alarm.alarm_id) - for (key, value) in six.iteritems(UPDATED_ALARM): - self.assertEqual(getattr(alarm, key), value) - - def test_update_delta(self): - alarm = self.mgr.update(alarm_id='alarm-id', **DELTA_ALARM) - expect_get = [ - 'GET', '/v2/alarms/alarm-id' - ] - expect_put = [ - 'PUT', '/v2/alarms/alarm-id', UPDATED_ALARM - ] - self.http_client.assert_called(*expect_get, pos=0) - self.http_client.assert_called(*expect_put, pos=1) - self.assertIsNotNone(alarm) - self.assertEqual('alarm-id', alarm.alarm_id) - for (key, value) in six.iteritems(UPDATED_ALARM): - self.assertEqual(getattr(alarm, key), value) - - def test_set_state(self): - state = self.mgr.set_state(alarm_id='alarm-id', state='alarm') - expect = [ - 'PUT', '/v2/alarms/alarm-id/state' - ] - self.http_client.assert_called(*expect, body='alarm') - self.assertEqual({'alarm': 'alarm'}, state) - - def test_get_state(self): - state = self.mgr.get_state(alarm_id='alarm-id') - expect = [ - 'GET', '/v2/alarms/alarm-id/state' - ] - self.http_client.assert_called(*expect) - self.assertEqual({'alarm': 'alarm'}, state) - - def test_delete(self): - deleted = self.mgr.delete(alarm_id='victim-id') - expect = [ - 'DELETE', '/v2/alarms/victim-id' - ] - self.http_client.assert_called(*expect) - self.assertIsNone(deleted) - - def test_get_from_alarm_class(self): - alarm = self.mgr.get(alarm_id='alarm-id') - self.assertIsNotNone(alarm) - alarm.get() - expect = [ - 'GET', '/v2/alarms/alarm-id' - ] - self.http_client.assert_called(*expect, pos=0) - self.http_client.assert_called(*expect, pos=1) - self.assertEqual('alarm-id', alarm.alarm_id) - self.assertEqual(alarm.threshold_rule, alarm.rule) - - def test_get_state_from_alarm_class(self): - alarm = self.mgr.get(alarm_id='alarm-id') - self.assertIsNotNone(alarm) - state = alarm.get_state() - expect_get_1 = [ - 'GET', '/v2/alarms/alarm-id' - ] - expect_get_2 = [ - 'GET', '/v2/alarms/alarm-id/state' - ] - self.http_client.assert_called(*expect_get_1, pos=0) - self.http_client.assert_called(*expect_get_2, pos=1) - self.assertEqual('alarm', state) - - def test_update_missing(self): - alarm = None - try: - alarm = self.mgr.update(alarm_id='unk-alarm-id', **UPDATE_ALARM) - except exc.CommandError: - pass - self.assertIsNone(alarm) - - def test_delete_from_alarm_class(self): - alarm = self.mgr.get(alarm_id='alarm-id') - self.assertIsNotNone(alarm) - deleted = alarm.delete() - expect_get = [ - 'GET', '/v2/alarms/alarm-id' - ] - expect_delete = [ - 'DELETE', '/v2/alarms/alarm-id' - ] - self.http_client.assert_called(*expect_get, pos=0) - self.http_client.assert_called(*expect_delete, pos=1) - self.assertIsNone(deleted) - - def _do_test_get_history(self, q, url): - history = self.mgr.get_history(q=q, alarm_id='alarm-id') - expect = ['GET', url] - self.http_client.assert_called(*expect) - for i in xrange(len(history)): - change = history[i] - self.assertIsInstance(change, alarms.AlarmChange) - for k, v in six.iteritems(ALARM_HISTORY[i]): - self.assertEqual(getattr(change, k), v) - - def test_get_all_history(self): - url = '/v2/alarms/alarm-id/history' - self._do_test_get_history(None, url) - - def test_get_constrained_history(self): - q = [dict(field='timestamp', value='NOW')] - url = ('/v2/alarms/alarm-id/history?q.field=timestamp' - '&q.op=&q.type=&q.value=NOW') - self._do_test_get_history(q, url) - - -class AlarmLegacyManagerTest(testtools.TestCase): - - def setUp(self): - super(AlarmLegacyManagerTest, self).setUp() - self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) - self.api = client.BaseClient(self.http_client) - self.mgr = alarms.AlarmManager(self.api) - - def test_create(self): - alarm = self.mgr.create(**CREATE_LEGACY_ALARM) - expect = [ - 'POST', '/v2/alarms', CREATE_ALARM_WITHOUT_TC, - ] - self.http_client.assert_called(*expect) - self.assertIsNotNone(alarm) - - def test_create_counter_name(self): - create = {} - create.update(CREATE_LEGACY_ALARM) - create['counter_name'] = CREATE_LEGACY_ALARM['meter_name'] - del create['meter_name'] - alarm = self.mgr.create(**create) - expect = [ - 'POST', '/v2/alarms', CREATE_ALARM_WITHOUT_TC, - ] - self.http_client.assert_called(*expect) - self.assertIsNotNone(alarm) - - def test_update(self): - alarm = self.mgr.update(alarm_id='alarm-id', **DELTA_LEGACY_ALARM) - expect_put = [ - 'PUT', '/v2/alarms/alarm-id', UPDATED_ALARM - ] - self.http_client.assert_called(*expect_put) - self.assertIsNotNone(alarm) - self.assertEqual('alarm-id', alarm.alarm_id) - for (key, value) in six.iteritems(UPDATED_ALARM): - self.assertEqual(getattr(alarm, key), value) - - def test_update_counter_name(self): - updated = {} - updated.update(UPDATE_LEGACY_ALARM) - updated['counter_name'] = UPDATED_LEGACY_ALARM['meter_name'] - del updated['meter_name'] - alarm = self.mgr.update(alarm_id='alarm-id', **updated) - expect_put = [ - 'PUT', '/v2/alarms/alarm-id', UPDATED_ALARM - ] - self.http_client.assert_called(*expect_put) - self.assertIsNotNone(alarm) - self.assertEqual('alarm-id', alarm.alarm_id) - for (key, value) in six.iteritems(UPDATED_ALARM): - self.assertEqual(getattr(alarm, key), value) - - -class AlarmTimeConstraintTest(testtools.TestCase): - - def setUp(self): - super(AlarmTimeConstraintTest, self).setUp() - self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) - self.api = client.BaseClient(self.http_client) - self.mgr = alarms.AlarmManager(self.api) - - def test_add_new(self): - new_constraint = dict(name='cons3', - start='0 0 * * *', - duration=500) - kwargs = dict(time_constraints=[new_constraint]) - self.mgr.update(alarm_id='alarm-id', **kwargs) - body = copy.deepcopy(AN_ALARM) - body[u'time_constraints'] = \ - AN_ALARM[u'time_constraints'] + [new_constraint] - expect = [ - 'PUT', '/v2/alarms/alarm-id', body - ] - self.http_client.assert_called(*expect) - - def test_update_existing(self): - updated_constraint = dict(name='cons2', - duration=500) - kwargs = dict(time_constraints=[updated_constraint]) - self.mgr.update(alarm_id='alarm-id', **kwargs) - body = copy.deepcopy(AN_ALARM) - body[u'time_constraints'][1] = dict(name='cons2', - description='desc2', - start='0 23 * * *', - duration=500, - timezone='') - - expect = [ - 'PUT', '/v2/alarms/alarm-id', body - ] - self.http_client.assert_called(*expect) - - def test_update_time_constraint_no_name(self): - updated_constraint = { - 'start': '0 23 * * *', - 'duration': 500 - } - kwargs = dict(time_constraints=[updated_constraint]) - self.mgr.update(alarm_id='alarm-id', **kwargs) - body = copy.deepcopy(AN_ALARM) - body[u'time_constraints'].append({ - 'start': '0 23 * * *', - 'duration': 500, - }) - expect = [ - 'PUT', '/v2/alarms/alarm-id', body - ] - self.http_client.assert_called(*expect) - - def test_remove(self): - kwargs = dict(remove_time_constraints=['cons2']) - self.mgr.update(alarm_id='alarm-id', **kwargs) - body = copy.deepcopy(AN_ALARM) - body[u'time_constraints'] = AN_ALARM[u'time_constraints'][:1] - expect = [ - 'PUT', '/v2/alarms/alarm-id', body - ] - self.http_client.assert_called(*expect) diff --git a/ceilometerclient/tests/unit/v2/test_capabilities.py b/ceilometerclient/tests/unit/v2/test_capabilities.py deleted file mode 100644 index 884a34f..0000000 --- a/ceilometerclient/tests/unit/v2/test_capabilities.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright 2014 Huawei Technologies Co., Ltd. -# -# 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 testtools - -from ceilometerclient.apiclient import client -from ceilometerclient.apiclient import fake_client -from ceilometerclient.v2 import capabilities - - -CAPABILITIES = { - "alarm_storage": { - "storage:production_ready": True - }, - "api": { - "alarms:query:complex": True, - "alarms:query:simple": True - }, - "event_storage": { - "storage:production_ready": True - }, - "storage": { - "storage:production_ready": True - }, -} - -FIXTURES = { - '/v2/capabilities': { - 'GET': ( - {}, - CAPABILITIES - ), - }, -} - - -class CapabilitiesManagerTest(testtools.TestCase): - def setUp(self): - super(CapabilitiesManagerTest, self).setUp() - self.http_client = fake_client.FakeHTTPClient(fixtures=FIXTURES) - self.api = client.BaseClient(self.http_client) - self.mgr = capabilities.CapabilitiesManager(self.api) - - def test_capabilities_get(self): - capabilities = self.mgr.get() - self.http_client.assert_called('GET', '/v2/capabilities') - self.assertTrue(capabilities.api['alarms:query:complex']) diff --git a/ceilometerclient/tests/unit/v2/test_event_types.py b/ceilometerclient/tests/unit/v2/test_event_types.py deleted file mode 100644 index 3bccbae..0000000 --- a/ceilometerclient/tests/unit/v2/test_event_types.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright 2014 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. - - -from ceilometerclient.apiclient import client -from ceilometerclient.apiclient import fake_client -from ceilometerclient.tests.unit import utils -import ceilometerclient.v2.event_types - - -fixtures = { - '/v2/event_types': { - 'GET': ( - {}, - ['Foo', 'Bar', 'Sna', 'Fu'] - ), - } -} - - -class EventTypesManagerTest(utils.BaseTestCase): - - def setUp(self): - super(EventTypesManagerTest, self).setUp() - self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) - self.api = client.BaseClient(self.http_client) - self.mgr = ceilometerclient.v2.event_types.EventTypeManager(self.api) - - def test_list(self): - event_types = list(self.mgr.list()) - expect = [ - 'GET', '/v2/event_types' - ] - self.http_client.assert_called(*expect) - self.assertEqual(4, len(event_types)) - self.assertEqual("Foo", event_types[0].event_type) - self.assertEqual("Bar", event_types[1].event_type) - self.assertEqual("Sna", event_types[2].event_type) - self.assertEqual("Fu", event_types[3].event_type) diff --git a/ceilometerclient/tests/unit/v2/test_events.py b/ceilometerclient/tests/unit/v2/test_events.py deleted file mode 100644 index 9cc94fd..0000000 --- a/ceilometerclient/tests/unit/v2/test_events.py +++ /dev/null @@ -1,198 +0,0 @@ -# Copyright 2014 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. -from ceilometerclient.apiclient import client -from ceilometerclient.apiclient import fake_client -from ceilometerclient.tests.unit import utils -import ceilometerclient.v2.events - - -fixtures = { - '/v2/events': { - 'GET': ( - {}, - [ - { - 'message_id': '1', - 'event_type': 'Foo', - 'generated': '1970-01-01T00:00:00', - 'traits': {'trait_A': 'abc'}, - }, - { - 'message_id': '2', - 'event_type': 'Foo', - 'generated': '1970-01-01T00:00:00', - 'traits': {'trait_A': 'def'}, - }, - { - 'message_id': '3', - 'event_type': 'Bar', - 'generated': '1970-01-01T00:00:00', - 'traits': {'trait_B': 'bartrait'}, - }, - ] - ), - }, - '/v2/events?q.field=hostname&q.op=&q.type=string&q.value=localhost': - { - 'GET': ( - {}, - [ - { - 'message_id': '1', - 'event_type': 'Foo', - 'generated': '1970-01-01T00:00:00', - 'traits': {'trait_A': 'abc', - 'hostname': 'localhost'}, - }, - { - 'message_id': '2', - 'event_type': 'Foo', - 'generated': '1970-01-01T00:00:00', - 'traits': {'trait_A': 'def', - 'hostname': 'localhost'}, - } - ] - ), - }, - '/v2/events?q.field=hostname&q.op=&q.type=&q.value=foreignhost': - { - 'GET': ( - {}, - [ - { - 'message_id': '1', - 'event_type': 'Foo', - 'generated': '1970-01-01T00:00:00', - 'traits': {'trait_A': 'abc', - 'hostname': 'foreignhost'}, - }, - { - 'message_id': '2', - 'event_type': 'Foo', - 'generated': '1970-01-01T00:00:00', - 'traits': {'trait_A': 'def', - 'hostname': 'foreignhost'}, - } - ] - ), - }, - '/v2/events?q.field=hostname&q.field=num_cpus&q.op=&q.op=' - '&q.type=&q.type=integer&q.value=localhost&q.value=5': - { - 'GET': ( - {}, - [ - { - 'message_id': '1', - 'event_type': 'Bar', - 'generated': '1970-01-01T00:00:00', - 'traits': {'trait_A': 'abc', - 'hostname': 'localhost', - 'num_cpus': '5'}, - }, - ] - ), - }, - - '/v2/events/2': - { - 'GET': ( - {}, - { - 'message_id': '2', - 'event_type': 'Foo', - 'generated': '1970-01-01T00:00:00', - 'traits': {'trait_A': 'def', - 'intTrait': '42'}, - } - ), - }, -} - - -class EventManagerTest(utils.BaseTestCase): - - def setUp(self): - super(EventManagerTest, self).setUp() - self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) - self.api = client.BaseClient(self.http_client) - self.mgr = ceilometerclient.v2.events.EventManager(self.api) - - def test_list_all(self): - events = list(self.mgr.list()) - expect = [ - 'GET', '/v2/events' - ] - self.http_client.assert_called(*expect) - self.assertEqual(3, len(events)) - self.assertEqual('Foo', events[0].event_type) - self.assertEqual('Foo', events[1].event_type) - self.assertEqual('Bar', events[2].event_type) - - def test_list_one(self): - event = self.mgr.get(2) - expect = [ - 'GET', '/v2/events/2' - ] - self.http_client.assert_called(*expect) - self.assertIsNotNone(event) - self.assertEqual('Foo', event.event_type) - - def test_list_with_query(self): - events = list(self.mgr.list(q=[{"field": "hostname", - "value": "localhost", - "type": "string"}])) - expect = [ - 'GET', '/v2/events?q.field=hostname&q.op=&q.type=string' - '&q.value=localhost' - ] - self.http_client.assert_called(*expect) - self.assertEqual(2, len(events)) - self.assertEqual('Foo', events[0].event_type) - - def test_list_with_query_no_type(self): - events = list(self.mgr.list(q=[{"field": "hostname", - "value": "foreignhost"}])) - expect = [ - 'GET', '/v2/events?q.field=hostname&q.op=' - '&q.type=&q.value=foreignhost' - ] - self.http_client.assert_called(*expect) - self.assertEqual(2, len(events)) - self.assertEqual('Foo', events[0].event_type) - - def test_list_with_multiple_filters(self): - events = list(self.mgr.list(q=[{"field": "hostname", - "value": "localhost"}, - {"field": "num_cpus", - "value": "5", - "type": "integer"}])) - - expect = [ - 'GET', '/v2/events?q.field=hostname&q.field=num_cpus&q.op=&q.op=' - '&q.type=&q.type=integer&q.value=localhost&q.value=5' - ] - self.http_client.assert_called(*expect) - self.assertEqual(1, len(events)) - - def test_get_from_event_class(self): - event = self.mgr.get(2) - self.assertIsNotNone(event) - event.get() - expect = [ - 'GET', '/v2/events/2' - ] - self.http_client.assert_called(*expect, pos=0) - self.http_client.assert_called(*expect, pos=1) - self.assertEqual('Foo', event.event_type) diff --git a/ceilometerclient/tests/unit/v2/test_options.py b/ceilometerclient/tests/unit/v2/test_options.py deleted file mode 100644 index 8de17d4..0000000 --- a/ceilometerclient/tests/unit/v2/test_options.py +++ /dev/null @@ -1,253 +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 ceilometerclient.tests.unit import utils -from ceilometerclient.v2 import options - - -class BuildUrlTest(utils.BaseTestCase): - - def test_one(self): - url = options.build_url('/', [{'field': 'this', - 'op': 'gt', - 'value': 43}]) - self.assertEqual(url, '/?q.field=this&q.op=gt&q.type=&q.value=43') - - def test_two(self): - url = options.build_url('/', [{'field': 'this', - 'op': 'gt', - 'value': 43}, - {'field': 'that', - 'op': 'lt', - 'value': 88}]) - ops = 'q.op=gt&q.op=lt' - vals = 'q.value=43&q.value=88' - types = 'q.type=&q.type=' - fields = 'q.field=this&q.field=that' - self.assertEqual(url, '/?%s&%s&%s&%s' % (fields, ops, types, vals)) - - def test_default_op(self): - url = options.build_url('/', [{'field': 'this', - 'value': 43}]) - self.assertEqual(url, '/?q.field=this&q.op=&q.type=&q.value=43') - - def test_one_param(self): - url = options.build_url('/', None, ['period=60']) - self.assertEqual(url, '/?period=60') - - def test_two_params(self): - url = options.build_url('/', None, ['period=60', - 'others=value']) - self.assertEqual(url, '/?period=60&others=value') - - def test_with_data_type(self): - url = options.build_url('/', [{'field': 'f1', - 'value': '10', - 'type': 'integer'}]) - - self.assertEqual('/?q.field=f1&q.op=&q.type=integer&q.value=10', url) - - -class CliTest(utils.BaseTestCase): - - def test_one(self): - ar = options.cli_to_array('this<=34') - self.assertEqual(ar, [{'field': 'this', 'op': 'le', - 'value': '34', 'type': ''}]) - - def test_two(self): - ar = options.cli_to_array('this<=34;that!=foo') - self.assertEqual(ar, [{'field': 'this', 'op': 'le', - 'value': '34', 'type': ''}, - {'field': 'that', 'op': 'ne', - 'value': 'foo', 'type': ''}]) - - def test_negative(self): - ar = options.cli_to_array('this>=-783') - self.assertEqual(ar, [{'field': 'this', 'op': 'ge', - 'value': '-783', 'type': ''}]) - - def test_float(self): - ar = options.cli_to_array('this<=283.347') - self.assertEqual(ar, [{'field': 'this', - 'op': 'le', 'value': '283.347', - 'type': ''}]) - - def test_comma(self): - ar = options.cli_to_array('this=2.4,fooo=doof') - self.assertEqual([{'field': 'this', - 'op': 'eq', - 'value': '2.4,fooo=doof', - 'type': ''}], - ar) - - def test_special_character(self): - ar = options.cli_to_array('key~123=value!123') - self.assertEqual([{'field': 'key~123', - 'op': 'eq', - 'value': 'value!123', - 'type': ''}], - ar) - - def _do_test_typed_float_op(self, op, op_str): - ar = options.cli_to_array('that%sfloat::283.347' % op) - self.assertEqual([{'field': 'that', - 'type': 'float', - 'value': '283.347', - 'op': op_str}], - ar) - - def test_typed_float_eq(self): - self._do_test_typed_float_op('<', 'lt') - - def test_typed_float_le(self): - self._do_test_typed_float_op('<=', 'le') - - def test_typed_string_whitespace(self): - ar = options.cli_to_array('state=string::insufficient data') - self.assertEqual([{'field': 'state', - 'op': 'eq', - 'type': 'string', - 'value': 'insufficient data'}], - ar) - - def test_typed_string_whitespace_complex(self): - ar = options.cli_to_array( - 'that>=float::99.9999;state=string::insufficient data' - ) - self.assertEqual([{'field': 'that', - 'op': 'ge', - 'type': 'float', - 'value': '99.9999'}, - {'field': 'state', - 'op': 'eq', - 'type': 'string', - 'value': 'insufficient data'}], - ar) - - def test_invalid_operator(self): - self.assertRaises(ValueError, options.cli_to_array, - 'this=2.4;fooo-doof') - - def test_with_dot(self): - ar = options.cli_to_array('metadata.this<=34') - self.assertEqual(ar, [{'field': 'metadata.this', - 'op': 'le', 'value': '34', - 'type': ''}]) - - def test_single_char_field_or_value(self): - ar = options.cli_to_array('m<=34;large.thing>s;x!=y') - self.assertEqual([{'field': 'm', - 'op': 'le', - 'value': '34', - 'type': ''}, - {'field': 'large.thing', - 'op': 'gt', - 'value': 's', - 'type': ''}, - {'field': 'x', - 'op': 'ne', - 'value': 'y', - 'type': ''}], - ar) - - def test_without_data_type(self): - ar = options.cli_to_array('hostname=localhost') - self.assertEqual(ar, [{'field': 'hostname', - 'op': 'eq', - 'value': 'localhost', - 'type': ''}]) - - def test_with_string_data_type(self): - ar = options.cli_to_array('hostname=string::localhost') - self.assertEqual(ar, [{'field': 'hostname', - 'op': 'eq', - 'type': 'string', - 'value': 'localhost'}]) - - def test_with_int_data_type(self): - ar = options.cli_to_array('port=integer::1234') - self.assertEqual(ar, [{'field': 'port', - 'op': 'eq', - 'type': 'integer', - 'value': '1234'}]) - - def test_with_bool_data_type(self): - ar = options.cli_to_array('port=boolean::true') - self.assertEqual(ar, [{'field': 'port', - 'op': 'eq', - 'type': 'boolean', - 'value': 'true'}]) - - def test_with_float_data_type(self): - ar = options.cli_to_array('average=float::1234.5678') - self.assertEqual(ar, [{'field': 'average', - 'op': 'eq', - 'type': 'float', - 'value': '1234.5678'}]) - - def test_with_datetime_data_type(self): - ar = options.cli_to_array('timestamp=datetime::sometimestamp') - self.assertEqual(ar, [{'field': 'timestamp', - 'op': 'eq', - 'type': 'datetime', - 'value': 'sometimestamp'}]) - - def test_with_incorrect_type(self): - ar = options.cli_to_array('timestamp=invalid::sometimestamp') - self.assertEqual(ar, [{'field': 'timestamp', - 'op': 'eq', - 'type': '', - 'value': 'invalid::sometimestamp'}]) - - def test_with_single_colon(self): - ar = options.cli_to_array('timestamp=datetime:sometimestamp') - self.assertEqual(ar, [{'field': 'timestamp', - 'op': 'eq', - 'type': '', - 'value': 'datetime:sometimestamp'}]) - - def test_missing_key(self): - self.assertRaises(ValueError, options.cli_to_array, - 'average=float::1234.0;>=string::hello') - - def test_missing_value(self): - self.assertRaises(ValueError, options.cli_to_array, - 'average=float::1234.0;house>=') - - def test_timestamp_value(self): - ar = options.cli_to_array( - 'project=cow;timestamp>=datetime::2014-03-11T16:02:58' - ) - self.assertEqual([{'field': 'project', - 'op': 'eq', - 'type': '', - 'value': 'cow'}, - {'field': 'timestamp', - 'op': 'ge', - 'type': 'datetime', - 'value': '2014-03-11T16:02:58'}], - ar) - - def test_with_whitespace(self): - ar = options.cli_to_array('start_timestamp= 2015-01-01T00:00:00;' - ' end_timestamp =2015-06-20T14:01:59 ') - - self.assertEqual([{'field': 'start_timestamp', - 'op': 'eq', - 'type': '', - 'value': '2015-01-01T00:00:00'}, - {'field': 'end_timestamp', - 'op': 'eq', - 'type': '', - 'value': '2015-06-20T14:01:59'}], - ar) diff --git a/ceilometerclient/tests/unit/v2/test_query_alarm_history.py b/ceilometerclient/tests/unit/v2/test_query_alarm_history.py deleted file mode 100644 index b37eb5d..0000000 --- a/ceilometerclient/tests/unit/v2/test_query_alarm_history.py +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright Ericsson AB 2014. 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. - -from ceilometerclient.apiclient import client -from ceilometerclient.apiclient import fake_client -from ceilometerclient.tests.unit import utils -from ceilometerclient.v2 import query - - -ALARMCHANGE = {"alarm_id": "e8ff32f772a44a478182c3fe1f7cad6a", - "event_id": "c74a8611-6553-4764-a860-c15a6aabb5d0", - "detail": "{\"threshold\": 42.0, \"evaluation_periods\": 4}", - "on_behalf_of": "92159030020611e3b26dde429e99ee8c", - "project_id": "b6f16144010811e387e4de429e99ee8c", - "timestamp": "2014-03-11T16:02:58.376261", - "type": "rule change", - "user_id": "3e5d11fda79448ac99ccefb20be187ca" - } - -QUERY = {"filter": {"and": [{">": {"timestamp": "2014-03-11T16:02:58"}}, - {"=": {"type": "rule change"}}]}, - "orderby": [{"timestamp": "desc"}], - "limit": 10} - -base_url = '/v2/query/alarms/history' -fixtures = { - base_url: - { - 'POST': ( - {}, - [ALARMCHANGE], - ), - }, -} - - -class QueryAlarmsManagerTest(utils.BaseTestCase): - - def setUp(self): - super(QueryAlarmsManagerTest, self).setUp() - self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) - self.api = client.BaseClient(self.http_client) - self.mgr = query.QueryAlarmHistoryManager(self.api) - - def test_query(self): - alarm_history = self.mgr.query(**QUERY) - expect = [ - - 'POST', '/v2/query/alarms/history', QUERY, - - ] - self.http_client.assert_called(*expect) - self.assertEqual(1, len(alarm_history)) diff --git a/ceilometerclient/tests/unit/v2/test_query_alarms.py b/ceilometerclient/tests/unit/v2/test_query_alarms.py deleted file mode 100644 index 495bbd4..0000000 --- a/ceilometerclient/tests/unit/v2/test_query_alarms.py +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright Ericsson AB 2014. 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. - -from ceilometerclient.apiclient import client -from ceilometerclient.apiclient import fake_client -from ceilometerclient.tests.unit import utils -from ceilometerclient.v2 import query - - -ALARM = {"alarm_actions": ["http://site:8000/alarm"], - "alarm_id": None, - "combination_rule": { - "alarm_ids": [ - "739e99cb-c2ec-4718-b900-332502355f38", - "153462d0-a9b8-4b5b-8175-9e4b05e9b856"], - "operator": "or"}, - "description": "An alarm", - "enabled": True, - "insufficient_data_actions": ["http://site:8000/nodata"], - "name": "SwiftObjectAlarm", - "ok_actions": ["http://site:8000/ok"], - "project_id": "c96c887c216949acbdfbd8b494863567", - "repeat_actions": False, - "state": "ok", - "state_timestamp": "2014-02-20T10:37:15.589860", - "threshold_rule": None, - "timestamp": "2014-02-20T10:37:15.589856", - "type": "combination", - "user_id": "c96c887c216949acbdfbd8b494863567"} - -QUERY = {"filter": {"and": [{"!=": {"state": "ok"}}, - {"=": {"type": "combination"}}]}, - "orderby": [{"state_timestamp": "desc"}], - "limit": 10} - -base_url = '/v2/query/alarms' -fixtures = { - base_url: - { - 'POST': ( - {}, - [ALARM], - ), - }, -} - - -class QueryAlarmsManagerTest(utils.BaseTestCase): - - def setUp(self): - super(QueryAlarmsManagerTest, self).setUp() - self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) - self.api = client.BaseClient(self.http_client) - self.mgr = query.QueryAlarmsManager(self.api) - - def test_query(self): - alarms = self.mgr.query(**QUERY) - expect = [ - 'POST', '/v2/query/alarms', QUERY, - ] - - self.http_client.assert_called(*expect) - self.assertEqual(1, len(alarms)) diff --git a/ceilometerclient/tests/unit/v2/test_query_samples.py b/ceilometerclient/tests/unit/v2/test_query_samples.py deleted file mode 100644 index 217c35a..0000000 --- a/ceilometerclient/tests/unit/v2/test_query_samples.py +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright Ericsson AB 2014. 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. - -from ceilometerclient.apiclient import client -from ceilometerclient.apiclient import fake_client -from ceilometerclient.tests.unit import utils -from ceilometerclient.v2 import query - - -SAMPLE = {u'id': u'b55d1526-9929-11e3-a3f6-02163e5df1e6', - u'metadata': { - u'name1': u'value1', - u'name2': u'value2'}, - u'meter': 'instance', - u'project_id': u'35b17138-b364-4e6a-a131-8f3099c5be68', - u'resource_id': u'bd9431c1-8d69-4ad3-803a-8d4a6b89fd36', - u'source': u'openstack', - u'timestamp': u'2014-02-19T05:50:16.673604', - u'type': u'gauge', - u'unit': u'instance', - u'volume': 1, - u'user_id': 'efd87807-12d2-4b38-9c70-5f5c2ac427ff'} - -QUERY = {"filter": {"and": [{"=": {"source": "openstack"}}, - {">": {"timestamp": "2014-02-19T05:50:16"}}]}, - "orderby": [{"timestamp": "desc"}, {"volume": "asc"}], - "limit": 10} - -base_url = '/v2/query/samples' -fixtures = { - base_url: - { - 'POST': ( - {}, - [SAMPLE], - ), - }, -} - - -class QuerySamplesManagerTest(utils.BaseTestCase): - - def setUp(self): - super(QuerySamplesManagerTest, self).setUp() - self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) - self.api = client.BaseClient(self.http_client) - self.mgr = query.QuerySamplesManager(self.api) - - def test_query(self): - samples = self.mgr.query(**QUERY) - expect = [ - - 'POST', '/v2/query/samples', QUERY, - ] - self.http_client.assert_called(*expect) - self.assertEqual(1, len(samples)) diff --git a/ceilometerclient/tests/unit/v2/test_resources.py b/ceilometerclient/tests/unit/v2/test_resources.py deleted file mode 100644 index ca88aa5..0000000 --- a/ceilometerclient/tests/unit/v2/test_resources.py +++ /dev/null @@ -1,147 +0,0 @@ -# Copyright 2012 OpenStack Foundation -# 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. -from ceilometerclient.apiclient import client -from ceilometerclient.apiclient import fake_client -from ceilometerclient.tests.unit import utils -import ceilometerclient.v2.resources - - -fixtures = { - '/v2/resources?meter_links=0': { - 'GET': ( - {}, - [ - { - 'resource_id': 'a', - 'project_id': 'project_bla', - 'user_id': 'freddy', - 'metadata': {'zxc_id': 'bla'}, - }, - { - 'resource_id': 'b', - 'project_id': 'dig_the_ditch', - 'user_id': 'joey', - 'metadata': {'zxc_id': 'foo'}, - }, - ] - ), - }, - '/v2/resources?q.field=resource_id&q.op=&q.type=&q.value=a&meter_links=0': - { - 'GET': ( - {}, - [ - { - 'resource_id': 'a', - 'project_id': 'project_bla', - 'user_id': 'freddy', - 'metadata': {'zxc_id': 'bla'}, - }, - ] - ), - }, - '/v2/resources?meter_links=1': { - 'GET': ( - {}, - [ - { - 'resource_id': 'c', - 'project_id': 'project_blah', - 'user_id': 'fred', - 'metadata': {'zxc_id': 'blah'}, - }, - { - 'resource_id': 'd', - 'project_id': 'bury_the_ditch', - 'user_id': 'jack', - 'metadata': {'zxc_id': 'foobar'}, - }, - ] - ), - }, - '/v2/resources/a': - { - 'GET': ( - {}, - { - 'resource_id': 'a', - 'project_id': 'project_bla', - 'user_id': 'freddy', - 'metadata': {'zxc_id': 'bla'}, - }, - ), - }, -} - - -class ResourceManagerTest(utils.BaseTestCase): - - def setUp(self): - super(ResourceManagerTest, self).setUp() - self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) - self.api = client.BaseClient(self.http_client) - self.mgr = ceilometerclient.v2.resources.ResourceManager(self.api) - - def test_list_all(self): - resources = list(self.mgr.list()) - expect = [ - 'GET', '/v2/resources?meter_links=0' - ] - self.http_client.assert_called(*expect) - self.assertEqual(2, len(resources)) - self.assertEqual('a', resources[0].resource_id) - self.assertEqual('b', resources[1].resource_id) - - def test_list_all_with_links_enabled(self): - resources = list(self.mgr.list(links=True)) - expect = [ - 'GET', '/v2/resources?meter_links=1' - ] - self.http_client.assert_called(*expect) - self.assertEqual(2, len(resources)) - self.assertEqual('c', resources[0].resource_id) - self.assertEqual('d', resources[1].resource_id) - - def test_list_one(self): - resource = self.mgr.get(resource_id='a') - expect = [ - 'GET', '/v2/resources/a' - ] - self.http_client.assert_called(*expect) - self.assertIsNotNone(resource) - self.assertEqual('a', resource.resource_id) - - def test_list_by_query(self): - resources = list(self.mgr.list(q=[{"field": "resource_id", - "value": "a"}, - ])) - expect = [ - 'GET', '/v2/resources?q.field=resource_id&q.op=' - '&q.type=&q.value=a&meter_links=0' - ] - self.http_client.assert_called(*expect) - self.assertEqual(1, len(resources)) - self.assertEqual('a', resources[0].resource_id) - - def test_get_from_resource_class(self): - resource = self.mgr.get(resource_id='a') - self.assertIsNotNone(resource) - resource.get() - expect = [ - 'GET', '/v2/resources/a' - ] - self.http_client.assert_called(*expect, pos=0) - self.http_client.assert_called(*expect, pos=1) - self.assertEqual('a', resource.resource_id) diff --git a/ceilometerclient/tests/unit/v2/test_samples.py b/ceilometerclient/tests/unit/v2/test_samples.py deleted file mode 100644 index 864ac36..0000000 --- a/ceilometerclient/tests/unit/v2/test_samples.py +++ /dev/null @@ -1,247 +0,0 @@ -# Copyright 2012 OpenStack Foundation -# 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 copy - -from ceilometerclient.apiclient import client -from ceilometerclient.apiclient import fake_client -from ceilometerclient.tests.unit import utils -import ceilometerclient.v2.samples - -GET_OLD_SAMPLE = {u'counter_name': u'instance', - u'user_id': u'user-id', - u'resource_id': u'resource-id', - u'timestamp': u'2012-07-02T10:40:00', - u'source': u'test_source', - u'message_id': u'54558a1c-6ef3-11e2-9875-5453ed1bbb5f', - u'counter_unit': u'', - u'counter_volume': 1.0, - u'project_id': u'project1', - u'resource_metadata': {u'tag': u'self.counter', - u'display_name': u'test-server'}, - u'counter_type': u'cumulative'} -CREATE_SAMPLE = copy.deepcopy(GET_OLD_SAMPLE) -del CREATE_SAMPLE['message_id'] -del CREATE_SAMPLE['source'] -CREATE_LIST_SAMPLE = copy.deepcopy(CREATE_SAMPLE) -CREATE_LIST_SAMPLE['counter_name'] = 'image' - -GET_SAMPLE = { - "user_id": None, - "resource_id": "9b651dfd-7d30-402b-972e-212b2c4bfb05", - "timestamp": "2014-11-03T13:37:46", - "meter": "image", - "volume": 1.0, - "source": "openstack", - "recorded_at": "2014-11-03T13:37:46.994458", - "project_id": "2cc3a7bb859b4bacbeab0aa9ca673033", - "type": "gauge", - "id": "98b5f258-635e-11e4-8bdd-0025647390c1", - "unit": "image", - "resource_metadata": {}, -} - -METER_URL = '/v2/meters/instance' -METER_URL_DIRECT = '/v2/meters/instance?direct=True' -SECOND_METER_URL = '/v2/meters/image' -SECOND_METER_URL_DIRECT = '/v2/meters/image?direct=True' -SAMPLE_URL = '/v2/samples' -QUERIES = ('q.field=resource_id&q.field=source&q.op=&q.op=' - '&q.type=&q.type=&q.value=foo&q.value=bar') -LIMIT = 'limit=1' - -OLD_SAMPLE_FIXTURES = { - METER_URL: { - 'GET': ( - {}, - [GET_OLD_SAMPLE] - ), - 'POST': ( - {}, - [CREATE_SAMPLE], - ), - }, - METER_URL_DIRECT: { - 'POST': ( - {}, - [CREATE_SAMPLE], - ) - }, - SECOND_METER_URL: { - 'POST': ( - {}, - [CREATE_LIST_SAMPLE] * 10, - ), - }, - SECOND_METER_URL_DIRECT: { - 'POST': ( - {}, - [CREATE_LIST_SAMPLE] * 10, - ) - }, - '%s?%s' % (METER_URL, QUERIES): { - 'GET': ( - {}, - [], - ), - }, - '%s?%s' % (METER_URL, LIMIT): { - 'GET': ( - {}, - [GET_OLD_SAMPLE] - ), - } -} -SAMPLE_FIXTURES = { - SAMPLE_URL: { - 'GET': ( - (), - [GET_SAMPLE] - ), - }, - '%s?%s' % (SAMPLE_URL, QUERIES): { - 'GET': ( - {}, - [], - ), - }, - '%s?%s' % (SAMPLE_URL, LIMIT): { - 'GET': ( - {}, - [GET_SAMPLE], - ), - }, - '%s/%s' % (SAMPLE_URL, GET_SAMPLE['id']): { - 'GET': ( - {}, - GET_SAMPLE, - ), - }, -} - - -class OldSampleManagerTest(utils.BaseTestCase): - - def setUp(self): - super(OldSampleManagerTest, self).setUp() - self.http_client = fake_client.FakeHTTPClient( - fixtures=OLD_SAMPLE_FIXTURES) - self.api = client.BaseClient(self.http_client) - self.mgr = ceilometerclient.v2.samples.OldSampleManager(self.api) - - def test_list_by_meter_name(self): - samples = list(self.mgr.list(meter_name='instance')) - expect = [ - 'GET', '/v2/meters/instance' - ] - self.http_client.assert_called(*expect) - self.assertEqual(1, len(samples)) - self.assertEqual('resource-id', samples[0].resource_id) - - def test_list_by_meter_name_extended(self): - samples = list(self.mgr.list(meter_name='instance', - q=[ - {"field": "resource_id", - "value": "foo"}, - {"field": "source", - "value": "bar"}, - ])) - expect = ['GET', '%s?%s' % (METER_URL, QUERIES)] - self.http_client.assert_called(*expect) - self.assertEqual(0, len(samples)) - - def test_create(self): - sample = self.mgr.create(**CREATE_SAMPLE) - expect = [ - 'POST', '/v2/meters/instance' - ] - self.http_client.assert_called(*expect, body=[CREATE_SAMPLE]) - self.assertIsNotNone(sample) - - def test_create_directly(self): - sample = self.mgr.create(direct=True, **CREATE_SAMPLE) - expect = [ - 'POST', '/v2/meters/instance?direct=True' - ] - self.http_client.assert_called(*expect, body=[CREATE_SAMPLE]) - self.assertIsNotNone(sample) - - def test_create_list(self): - test_samples = [CREATE_LIST_SAMPLE] * 10 - samples = self.mgr.create_list(test_samples) - expect = [ - 'POST', '/v2/meters/image' - ] - self.http_client.assert_called(*expect, body=test_samples) - self.assertEqual(10, len(samples)) - - def test_create_list_directly(self): - test_samples = [CREATE_LIST_SAMPLE] * 10 - samples = self.mgr.create_list(test_samples, direct=True) - expect = [ - 'POST', '/v2/meters/image?direct=True' - ] - self.http_client.assert_called(*expect, body=test_samples) - self.assertEqual(10, len(samples)) - - def test_limit(self): - samples = list(self.mgr.list(meter_name='instance', limit=1)) - expect = ['GET', '/v2/meters/instance?limit=1'] - self.http_client.assert_called(*expect) - self.assertEqual(1, len(samples)) - - -class SampleManagerTest(utils.BaseTestCase): - - def setUp(self): - super(SampleManagerTest, self).setUp() - self.http_client = fake_client.FakeHTTPClient( - fixtures=SAMPLE_FIXTURES) - self.api = client.BaseClient(self.http_client) - self.mgr = ceilometerclient.v2.samples.SampleManager(self.api) - - def test_sample_list(self): - samples = list(self.mgr.list()) - expect = [ - 'GET', '/v2/samples' - ] - self.http_client.assert_called(*expect) - self.assertEqual(1, len(samples)) - self.assertEqual('9b651dfd-7d30-402b-972e-212b2c4bfb05', - samples[0].resource_id) - - def test_sample_list_with_queries(self): - queries = [ - {"field": "resource_id", - "value": "foo"}, - {"field": "source", - "value": "bar"}, - ] - samples = list(self.mgr.list(q=queries)) - expect = ['GET', '%s?%s' % (SAMPLE_URL, QUERIES)] - self.http_client.assert_called(*expect) - self.assertEqual(0, len(samples)) - - def test_sample_list_with_limit(self): - samples = list(self.mgr.list(limit=1)) - expect = ['GET', '/v2/samples?limit=1'] - self.http_client.assert_called(*expect) - self.assertEqual(1, len(samples)) - - def test_sample_get(self): - sample = self.mgr.get(GET_SAMPLE['id']) - expect = ['GET', '/v2/samples/' + GET_SAMPLE['id']] - self.http_client.assert_called(*expect) - self.assertEqual(GET_SAMPLE, sample.to_dict()) diff --git a/ceilometerclient/tests/unit/v2/test_shell.py b/ceilometerclient/tests/unit/v2/test_shell.py deleted file mode 100644 index eca15e3..0000000 --- a/ceilometerclient/tests/unit/v2/test_shell.py +++ /dev/null @@ -1,1977 +0,0 @@ -# Copyright Ericsson AB 2014. 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 json -import re -import sys - -import mock -import six -from testtools import matchers - -from ceilometerclient import exc -from ceilometerclient import shell as base_shell -from ceilometerclient.tests.unit import test_shell -from ceilometerclient.tests.unit import utils -from ceilometerclient.v2 import alarms -from ceilometerclient.v2 import capabilities -from ceilometerclient.v2 import event_types -from ceilometerclient.v2 import events -from ceilometerclient.v2 import meters -from ceilometerclient.v2 import resources -from ceilometerclient.v2 import samples -from ceilometerclient.v2 import shell as ceilometer_shell -from ceilometerclient.v2 import statistics -from ceilometerclient.v2 import trait_descriptions -from ceilometerclient.v2 import traits -from keystoneauth1 import exceptions - - -class ShellAlarmStateCommandsTest(utils.BaseTestCase): - - ALARM_ID = 'foobar' - - def setUp(self): - super(ShellAlarmStateCommandsTest, self).setUp() - self.cc = mock.Mock() - self.cc.alarms = mock.Mock() - self.args = mock.Mock() - self.args.alarm_id = self.ALARM_ID - - def test_alarm_state_get(self): - ceilometer_shell.do_alarm_state_get(self.cc, self.args) - self.cc.alarms.get_state.assert_called_once_with(self.ALARM_ID) - self.assertFalse(self.cc.alarms.set_state.called) - - def test_alarm_state_set(self): - self.args.state = 'ok' - ceilometer_shell.do_alarm_state_set(self.cc, self.args) - self.cc.alarms.set_state.assert_called_once_with(self.ALARM_ID, 'ok') - self.assertFalse(self.cc.alarms.get_state.called) - - -class ShellAlarmHistoryCommandTest(utils.BaseTestCase): - - ALARM_ID = '768ff714-8cfb-4db9-9753-d484cb33a1cc' - FULL_DETAIL = ('{"alarm_actions": [], ' - '"user_id": "8185aa72421a4fd396d4122cba50e1b5", ' - '"name": "scombo", ' - '"timestamp": "2013-10-03T08:58:33.647912", ' - '"enabled": true, ' - '"state_timestamp": "2013-10-03T08:58:33.647912", ' - '"rule": {"operator": "or", "alarm_ids": ' - '["062cc907-3a9f-4867-ab3b-fa83212b39f7"]}, ' - '"alarm_id": "768ff714-8cfb-4db9-9753-d484cb33a1cc", ' - '"state": "insufficient data", ' - '"insufficient_data_actions": [], ' - '"repeat_actions": false, ' - '"ok_actions": [], ' - '"project_id": "57d04f24d0824b78b1ea9bcecedbda8f", ' - '"type": "combination", ' - '"severity": "low", ' - '"description": "Combined state of alarms ' - '062cc907-3a9f-4867-ab3b-fa83212b39f7"}') - ALARM_HISTORY = [{'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', - 'user_id': '8185aa72421a4fd396d4122cba50e1b5', - 'event_id': 'c74a8611-6553-4764-a860-c15a6aabb5d0', - 'timestamp': '2013-10-03T08:59:28.326000', - 'detail': '{"state": "alarm"}', - 'alarm_id': '768ff714-8cfb-4db9-9753-d484cb33a1cc', - 'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', - 'type': 'state transition'}, - {'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', - 'user_id': '8185aa72421a4fd396d4122cba50e1b5', - 'event_id': 'c74a8611-6553-4764-a860-c15a6aabb5d0', - 'timestamp': '2013-10-03T08:59:28.326000', - 'detail': '{"description": "combination of one"}', - 'alarm_id': '768ff714-8cfb-4db9-9753-d484cb33a1cc', - 'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', - 'type': 'rule change'}, - {'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', - 'user_id': '8185aa72421a4fd396d4122cba50e1b5', - 'event_id': '4fd7df9e-190d-4471-8884-dc5a33d5d4bb', - 'timestamp': '2013-10-03T08:58:33.647000', - 'detail': FULL_DETAIL, - 'alarm_id': '768ff714-8cfb-4db9-9753-d484cb33a1cc', - 'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', - 'type': 'creation'}] - TIMESTAMP_RE = (' +\| (\d{4})-(\d{2})-(\d{2})T' - '(\d{2})\:(\d{2})\:(\d{2})\.(\d{6}) \| +') - - def setUp(self): - super(ShellAlarmHistoryCommandTest, self).setUp() - self.cc = mock.Mock() - self.cc.alarms = mock.Mock() - self.args = mock.Mock() - self.args.alarm_id = self.ALARM_ID - - @mock.patch('sys.stdout', new=six.StringIO()) - def _do_test_alarm_history(self, raw_query=None, parsed_query=None): - self.args.query = raw_query - history = [alarms.AlarmChange(mock.Mock(), change) - for change in self.ALARM_HISTORY] - self.cc.alarms.get_history.return_value = history - - ceilometer_shell.do_alarm_history(self.cc, self.args) - self.cc.alarms.get_history.assert_called_once_with( - q=parsed_query, - alarm_id=self.ALARM_ID - ) - out = sys.stdout.getvalue() - required = [ - '.*creation%sname: scombo.*' % self.TIMESTAMP_RE, - '.*rule change%sdescription: combination of one.*' % - self.TIMESTAMP_RE, - '.*state transition%sstate: alarm.*' % self.TIMESTAMP_RE, - ] - for r in required: - self.assertThat(out, matchers.MatchesRegex(r, re.DOTALL)) - - def test_alarm_all_history(self): - self._do_test_alarm_history() - - def test_alarm_constrained_history(self): - parsed_query = [dict(field='timestamp', - value='2013-10-03T08:59:28', - op='gt', - type='')] - self._do_test_alarm_history(raw_query='timestamp>2013-10-03T08:59:28', - parsed_query=parsed_query) - - -class ShellAlarmCommandTest(utils.BaseTestCase): - - ALARM_ID = '768ff714-8cfb-4db9-9753-d484cb33a1cc' - ALARM = {"alarm_actions": ["log://"], - "ok_actions": [], - "description": "instance running hot", - "timestamp": "2013-11-20T10:38:42.206952", - "enabled": True, - "state_timestamp": "2013-11-19T17:20:44", - "threshold_rule": {"meter_name": "cpu_util", - "evaluation_periods": 3, - "period": 600, - "statistic": "avg", - "threshold": 99.0, - "query": [{"field": "resource_id", - "value": "INSTANCE_ID", - "op": "eq"}], - "comparison_operator": "gt"}, - "time_constraints": [{"name": "cons1", - "description": "desc1", - "start": "0 11 * * *", - "duration": 300, - "timezone": ""}, - {"name": "cons2", - "description": "desc2", - "start": "0 23 * * *", - "duration": 600, - "timezone": ""}], - "alarm_id": ALARM_ID, - "state": "insufficient data", - "severity": "low", - "insufficient_data_actions": [], - "repeat_actions": True, - "user_id": "528d9b68fa774689834b5c04b4564f8a", - "project_id": "ed9d4e2be2a748bc80108053cf4598f5", - "type": "threshold", - "name": "cpu_high"} - - THRESHOLD_ALARM_CLI_ARGS = [ - '--name', 'cpu_high', - '--description', 'instance running hot', - '--meter-name', 'cpu_util', - '--threshold', '70.0', - '--comparison-operator', 'gt', - '--statistic', 'avg', - '--period', '600', - '--evaluation-periods', '3', - '--alarm-action', 'log://', - '--alarm-action', 'http://example.com/alarm/state', - '--query', 'resource_id=INSTANCE_ID' - ] - - def setUp(self): - super(ShellAlarmCommandTest, self).setUp() - self.cc = mock.Mock() - self.cc.alarms = mock.Mock() - self.args = mock.Mock() - self.args.alarm_id = self.ALARM_ID - - @mock.patch('sys.stdout', new=six.StringIO()) - def _do_test_alarm_update_repeat_actions(self, method, repeat_actions): - self.args.threshold = 42.0 - if repeat_actions is not None: - self.args.repeat_actions = repeat_actions - alarm = [alarms.Alarm(mock.Mock(), self.ALARM)] - self.cc.alarms.get.return_value = alarm - self.cc.alarms.update.return_value = alarm[0] - - method(self.cc, self.args) - args, kwargs = self.cc.alarms.update.call_args - self.assertEqual(self.ALARM_ID, args[0]) - self.assertEqual(42.0, kwargs.get('threshold')) - if repeat_actions is not None: - self.assertEqual(repeat_actions, kwargs.get('repeat_actions')) - else: - self.assertNotIn('repeat_actions', kwargs) - - def test_alarm_update_repeat_actions_untouched(self): - method = ceilometer_shell.do_alarm_update - self._do_test_alarm_update_repeat_actions(method, None) - - def test_alarm_update_repeat_actions_set(self): - method = ceilometer_shell.do_alarm_update - self._do_test_alarm_update_repeat_actions(method, True) - - def test_alarm_update_repeat_actions_clear(self): - method = ceilometer_shell.do_alarm_update - self._do_test_alarm_update_repeat_actions(method, False) - - def test_alarm_combination_update_repeat_actions_untouched(self): - method = ceilometer_shell.do_alarm_combination_update - self._do_test_alarm_update_repeat_actions(method, None) - - def test_alarm_combination_update_repeat_actions_set(self): - method = ceilometer_shell.do_alarm_combination_update - self._do_test_alarm_update_repeat_actions(method, True) - - def test_alarm_combination_update_repeat_actions_clear(self): - method = ceilometer_shell.do_alarm_combination_update - self._do_test_alarm_update_repeat_actions(method, False) - - def test_alarm_threshold_update_repeat_actions_untouched(self): - method = ceilometer_shell.do_alarm_threshold_update - self._do_test_alarm_update_repeat_actions(method, None) - - def test_alarm_threshold_update_repeat_actions_set(self): - method = ceilometer_shell.do_alarm_threshold_update - self._do_test_alarm_update_repeat_actions(method, True) - - def test_alarm_threshold_update_repeat_actions_clear(self): - method = ceilometer_shell.do_alarm_threshold_update - self._do_test_alarm_update_repeat_actions(method, False) - - def test_alarm_event_upadte_repeat_action_untouched(self): - method = ceilometer_shell.do_alarm_event_update - self._do_test_alarm_update_repeat_actions(method, None) - - def test_alarm_event_upadte_repeat_action_set(self): - method = ceilometer_shell.do_alarm_event_update - self._do_test_alarm_update_repeat_actions(method, True) - - def test_alarm_event_upadte_repeat_action_clear(self): - method = ceilometer_shell.do_alarm_event_update - self._do_test_alarm_update_repeat_actions(method, False) - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_alarm_threshold_create_args(self): - argv = ['alarm-threshold-create'] + self.THRESHOLD_ALARM_CLI_ARGS - self._test_alarm_threshold_action_args('create', argv) - - def test_alarm_threshold_update_args(self): - argv = ['alarm-threshold-update', 'x'] + self.THRESHOLD_ALARM_CLI_ARGS - self._test_alarm_threshold_action_args('update', argv) - - @mock.patch('sys.stdout', new=six.StringIO()) - def _test_alarm_threshold_action_args(self, action, argv): - shell = base_shell.CeilometerShell() - _, args = shell.parse_args(argv) - - alarm = alarms.Alarm(mock.Mock(), self.ALARM) - getattr(self.cc.alarms, action).return_value = alarm - - func = getattr(ceilometer_shell, 'do_alarm_threshold_' + action) - func(self.cc, args) - _, kwargs = getattr(self.cc.alarms, action).call_args - self._check_alarm_threshold_args(kwargs) - - def _check_alarm_threshold_args(self, kwargs): - self.assertEqual('cpu_high', kwargs.get('name')) - self.assertEqual('instance running hot', kwargs.get('description')) - actions = ['log://', 'http://example.com/alarm/state'] - self.assertEqual(actions, kwargs.get('alarm_actions')) - self.assertIn('threshold_rule', kwargs) - rule = kwargs['threshold_rule'] - self.assertEqual('cpu_util', rule.get('meter_name')) - self.assertEqual(70.0, rule.get('threshold')) - self.assertEqual('gt', rule.get('comparison_operator')) - self.assertEqual('avg', rule.get('statistic')) - self.assertEqual(600, rule.get('period')) - self.assertEqual(3, rule.get('evaluation_periods')) - query = dict(field='resource_id', type='', - value='INSTANCE_ID', op='eq') - self.assertEqual([query], rule['query']) - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_alarm_create_time_constraints(self): - shell = base_shell.CeilometerShell() - argv = ['alarm-threshold-create', - '--name', 'cpu_high', - '--meter-name', 'cpu_util', - '--threshold', '70.0', - '--time-constraint', - 'name=cons1;start="0 11 * * *";duration=300', - '--time-constraint', - 'name=cons2;start="0 23 * * *";duration=600', - ] - _, args = shell.parse_args(argv) - - alarm = alarms.Alarm(mock.Mock(), self.ALARM) - self.cc.alarms.create.return_value = alarm - - ceilometer_shell.do_alarm_threshold_create(self.cc, args) - _, kwargs = self.cc.alarms.create.call_args - time_constraints = [dict(name='cons1', start='0 11 * * *', - duration='300'), - dict(name='cons2', start='0 23 * * *', - duration='600')] - self.assertEqual(time_constraints, kwargs['time_constraints']) - - -class ShellAlarmGnocchiCommandTest(test_shell.ShellTestBase): - - ALARM_ID = 'b69ecdb9-f19b-4fb5-950f-5eb53938b718' - TIME_CONSTRAINTS = [{ - u'duration': 300, - u'start': u'0 11 * * *', - u'description': u'desc1', - u'name': u'cons1', - u'timezone': u''}, { - u'duration': 600, - u'start': u'0 23 * * *', - u'name': u'cons2', - u'description': u'desc2', - u'timezone': u''}] - - ALARM1 = { - u'name': u'name_gnocchi_alarm', - u'description': u'description_gnocchi_alarm', - u'enabled': True, - u'ok_actions': [u'http://something/ok'], - u'alarm_actions': [u'http://something/alarm'], - u'timestamp': u'2015-12-21T03:10:32.305133', - u'state_timestamp': u'2015-12-21T03:10:32.305133', - u'gnocchi_resources_threshold_rule': { - u'evaluation_periods': 3, - u'metric': u'cpu_util', - u'resource_id': u'768ff714-8cfb-4db9-9753-d484cb33a1cc', - u'threshold': 70.0, - u'granularity': 60, - u'aggregation_method': u'count', - u'comparison_operator': u'le', - u'resource_type': u'instance', - }, - u'time_constraints': TIME_CONSTRAINTS, - u'alarm_id': ALARM_ID, - u'state': u'ok', - u'insufficient_data_actions': [u'http://something/insufficient'], - u'repeat_actions': True, - u'user_id': u'f28735621ee84f329144eb467c91fce6', - u'project_id': u'97fcad0402ce4f65ac3bd42a0c6a7e74', - u'type': u'gnocchi_resources_threshold', - u'severity': u'critical', - } - - ALARM2 = { - u'name': u'name_gnocchi_alarm', - u'description': u'description_gnocchi_alarm', - u'enabled': True, - u'ok_actions': [u'http://something/ok'], - u'alarm_actions': [u'http://something/alarm'], - u'timestamp': u'2015-12-21T03:10:32.305133', - u'state_timestamp': u'2015-12-21T03:10:32.305133', - u'gnocchi_aggregation_by_metrics_threshold_rule': { - u'evaluation_periods': 3, - u'metrics': [u'b3d9d8ab-05e8-439f-89ad-5e978dd2a5eb', - u'009d4faf-c275-46f0-8f2d-670b15bac2b0'], - u'threshold': 70.0, - u'granularity': 60, - u'aggregation_method': u'count', - u'comparison_operator': u'le', - }, - u'time_constraints': TIME_CONSTRAINTS, - u'alarm_id': ALARM_ID, - u'state': u'ok', - u'insufficient_data_actions': [u'http://something/insufficient'], - u'repeat_actions': True, - u'user_id': u'f28735621ee84f329144eb467c91fce6', - u'project_id': u'97fcad0402ce4f65ac3bd42a0c6a7e74', - u'type': u'gnocchi_aggregation_by_metrics_threshold', - u'severity': u'critical', - } - - ALARM3 = { - u'name': u'name_gnocchi_alarm', - u'description': u'description_gnocchi_alarm', - u'enabled': True, - u'ok_actions': [u'http://something/ok'], - u'alarm_actions': [u'http://something/alarm'], - u'timestamp': u'2015-12-21T03:10:32.305133', - u'state_timestamp': u'2015-12-21T03:10:32.305133', - u'gnocchi_aggregation_by_resources_threshold_rule': { - u'evaluation_periods': 3, - u'metric': u'cpu_util', - u'threshold': 70.0, - u'granularity': 60, - u'aggregation_method': u'count', - u'comparison_operator': u'le', - u'resource_type': u'instance', - u'query': u'{"=": {"server_group":"my_autoscaling_group"}}', - }, - u'time_constraints': TIME_CONSTRAINTS, - u'alarm_id': ALARM_ID, - u'state': u'ok', - u'insufficient_data_actions': [u'http://something/insufficient'], - u'repeat_actions': True, - u'user_id': u'f28735621ee84f329144eb467c91fce6', - u'project_id': u'97fcad0402ce4f65ac3bd42a0c6a7e74', - u'type': u'gnocchi_aggregation_by_resources_threshold', - u'severity': u'critical', - } - - COMMON_CLI_ARGS = [ - '--name', 'name_gnocchi_alarm', - '--description', 'description_gnocchi_alarm', - '--enabled', 'True', - '--state', 'ok', - '--severity', 'critical', - '--ok-action', 'http://something/ok', - '--alarm-action', 'http://something/alarm', - '--insufficient-data-action', 'http://something/insufficient', - '--repeat-actions', 'True', - '--comparison-operator', 'le', - '--aggregation-method', 'count', - '--threshold', '70', - '--evaluation-periods', '3', - '--granularity', '60', - '--time-constraint', - 'name=cons1;start="0 11 * * *";duration=300;description="desc1"', - '--time-constraint', - 'name=cons2;start="0 23 * * *";duration=600;description="desc2"', - '--user-id', 'f28735621ee84f329144eb467c91fce6', - '--project-id', '97fcad0402ce4f65ac3bd42a0c6a7e74', - ] - - GNOCCHI_RESOURCES_CLI_ARGS = COMMON_CLI_ARGS + [ - '--metric', 'cpu_util', - '--resource-type', 'instance', - '--resource-id', '768ff714-8cfb-4db9-9753-d484cb33a1cc', - ] - - GNOCCHI_AGGR_BY_METRICS_CLI_ARGS = COMMON_CLI_ARGS + [ - '-m', 'b3d9d8ab-05e8-439f-89ad-5e978dd2a5eb', - '-m', '009d4faf-c275-46f0-8f2d-670b15bac2b0', - ] - - GNOCCHI_AGGR_BY_RESOURCES_CLI_ARGS = COMMON_CLI_ARGS + [ - '--metric', 'cpu_util', - '--resource-type', 'instance', - '--query', '{"=": {"server_group":"my_autoscaling_group"}}' - ] - - def setUp(self): - super(ShellAlarmGnocchiCommandTest, self).setUp() - self.cc = mock.Mock() - self.cc.alarms = mock.Mock() - self.args = mock.Mock() - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_do_alarm_gnocchi_resources_threshold_create(self): - - alarm = alarms.Alarm(mock.Mock(), self.ALARM1) - self.cc.alarms.create.return_value = alarm - ceilometer_shell.do_alarm_gnocchi_resources_threshold_create(self.cc, - self.args) - self.assertEqual('''\ -+---------------------------+--------------------------------------+ -| Property | Value | -+---------------------------+--------------------------------------+ -| aggregation_method | count | -| alarm_actions | ["http://something/alarm"] | -| alarm_id | b69ecdb9-f19b-4fb5-950f-5eb53938b718 | -| comparison_operator | le | -| description | description_gnocchi_alarm | -| enabled | True | -| evaluation_periods | 3 | -| granularity | 60 | -| insufficient_data_actions | ["http://something/insufficient"] | -| metric | cpu_util | -| name | name_gnocchi_alarm | -| ok_actions | ["http://something/ok"] | -| project_id | 97fcad0402ce4f65ac3bd42a0c6a7e74 | -| repeat_actions | True | -| resource_id | 768ff714-8cfb-4db9-9753-d484cb33a1cc | -| resource_type | instance | -| severity | critical | -| state | ok | -| threshold | 70.0 | -| time_constraints | [{name: cons1, | -| | description: desc1, | -| | start: 0 11 * * *, | -| | duration: 300}, | -| | {name: cons2, | -| | description: desc2, | -| | start: 0 23 * * *, | -| | duration: 600}] | -| type | gnocchi_resources_threshold | -| user_id | f28735621ee84f329144eb467c91fce6 | -+---------------------------+--------------------------------------+ -''', sys.stdout.getvalue()) - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_do_alarm_gnocchi_aggr_by_metrics_threshold_create(self): - - alarm = alarms.Alarm(mock.Mock(), self.ALARM2) - self.cc.alarms.create.return_value = alarm - ceilometer_shell.\ - do_alarm_gnocchi_aggregation_by_metrics_threshold_create( - self.cc, self.args) - stdout = sys.stdout.getvalue() - self.assertIn("b69ecdb9-f19b-4fb5-950f-5eb53938b718", stdout) - self.assertIn("[\"http://something/alarm\"]", stdout) - self.assertIn("description_gnocchi_alarm", stdout) - self.assertIn("gnocchi_aggregation_by_metrics_threshold", stdout) - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_do_alarm_gnocchi_aggr_by_resources_threshold_create(self): - - alarm = alarms.Alarm(mock.Mock(), self.ALARM3) - self.cc.alarms.create.return_value = alarm - ceilometer_shell.\ - do_alarm_gnocchi_aggregation_by_resources_threshold_create( - self.cc, self.args) - self.assertEqual('''\ -+---------------------------+------------------------------------------------+ -| Property | Value | -+---------------------------+------------------------------------------------+ -| aggregation_method | count | -| alarm_actions | ["http://something/alarm"] | -| alarm_id | b69ecdb9-f19b-4fb5-950f-5eb53938b718 | -| comparison_operator | le | -| description | description_gnocchi_alarm | -| enabled | True | -| evaluation_periods | 3 | -| granularity | 60 | -| insufficient_data_actions | ["http://something/insufficient"] | -| metric | cpu_util | -| name | name_gnocchi_alarm | -| ok_actions | ["http://something/ok"] | -| project_id | 97fcad0402ce4f65ac3bd42a0c6a7e74 | -| query | {"=": {"server_group":"my_autoscaling_group"}} | -| repeat_actions | True | -| resource_type | instance | -| severity | critical | -| state | ok | -| threshold | 70.0 | -| time_constraints | [{name: cons1, | -| | description: desc1, | -| | start: 0 11 * * *, | -| | duration: 300}, | -| | {name: cons2, | -| | description: desc2, | -| | start: 0 23 * * *, | -| | duration: 600}] | -| type | gnocchi_aggregation_by_resources_threshold | -| user_id | f28735621ee84f329144eb467c91fce6 | -+---------------------------+------------------------------------------------+ -''', sys.stdout.getvalue()) - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_do_alarm_gnocchi_resources_threshold_create_args(self): - argv = ['alarm-gnocchi-resources-threshold-create'] - argv.extend(self.GNOCCHI_RESOURCES_CLI_ARGS) - self._test_alarm_gnocchi_resources_arguments('create', argv) - - def test_do_alarm_gnocchi_resources_threshold_update_args(self): - argv = ['alarm-gnocchi-resources-threshold-update'] - argv.extend(self.GNOCCHI_RESOURCES_CLI_ARGS) - argv.append(self.ALARM_ID) - self._test_alarm_gnocchi_resources_arguments('update', argv) - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_do_alarm_gnocchi_aggr_by_metrics_threshold_create_args(self): - argv = ['alarm-gnocchi-aggregation-by-metrics-threshold-create'] - argv.extend(self.GNOCCHI_AGGR_BY_METRICS_CLI_ARGS) - self._test_alarm_gnocchi_aggr_by_metrics_arguments('create', argv) - - def test_do_alarm_gnocchi_aggr_by_metrics_threshold_update_args(self): - argv = ['alarm-gnocchi-aggregation-by-metrics-threshold-update'] - argv.extend(self.GNOCCHI_AGGR_BY_METRICS_CLI_ARGS) - argv.append(self.ALARM_ID) - self._test_alarm_gnocchi_aggr_by_metrics_arguments('update', argv) - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_do_alarm_gnocchi_aggr_by_resources_threshold_create_args(self): - argv = ['alarm-gnocchi-aggregation-by-resources-threshold-create'] - argv.extend(self.GNOCCHI_AGGR_BY_RESOURCES_CLI_ARGS) - self._test_alarm_gnocchi_aggr_by_resources_arguments('create', argv) - - def test_do_alarm_gnocchi_aggr_by_resources_threshold_update_args(self): - argv = ['alarm-gnocchi-aggregation-by-resources-threshold-update'] - argv.extend(self.GNOCCHI_AGGR_BY_RESOURCES_CLI_ARGS) - argv.append(self.ALARM_ID) - self._test_alarm_gnocchi_aggr_by_resources_arguments('update', argv) - - @mock.patch('sys.stdout', new=six.StringIO()) - def _test_common_alarm_gnocchi_arguments(self, kwargs): - self.assertEqual('97fcad0402ce4f65ac3bd42a0c6a7e74', - kwargs.get('project_id')) - self.assertEqual('f28735621ee84f329144eb467c91fce6', - kwargs.get('user_id')) - self.assertEqual('name_gnocchi_alarm', kwargs.get('name')) - self.assertEqual('description_gnocchi_alarm', - kwargs.get('description')) - self.assertEqual(['http://something/alarm'], - kwargs.get('alarm_actions')) - self.assertEqual(['http://something/ok'], kwargs.get('ok_actions')) - self.assertEqual(['http://something/insufficient'], - kwargs.get('insufficient_data_actions')) - self.assertEqual('critical', kwargs.get('severity')) - self.assertEqual('ok', kwargs.get('state')) - self.assertEqual(True, kwargs.get('enabled')) - self.assertEqual(True, kwargs.get('repeat_actions')) - time_constraints = [dict(name='cons1', start='0 11 * * *', - duration='300', description='desc1'), - dict(name='cons2', start='0 23 * * *', - duration='600', description='desc2')] - self.assertEqual(time_constraints, kwargs['time_constraints']) - - def _test_alarm_gnocchi_resources_arguments(self, action, argv): - self.make_env(test_shell.FAKE_V2_ENV) - with mock.patch.object(alarms.AlarmManager, action) as mocked: - with mock.patch('ceilometerclient.apiclient.' - 'client.HTTPClient.client_request') as request: - request.site_effect = exceptions.EndpointNotFound - base_shell.main(argv) - args, kwargs = mocked.call_args - self.assertEqual('gnocchi_resources_threshold', kwargs.get('type')) - self.assertIn('gnocchi_resources_threshold_rule', kwargs) - rule = kwargs['gnocchi_resources_threshold_rule'] - self.assertEqual('cpu_util', rule.get('metric')) - self.assertEqual(70.0, rule.get('threshold')) - self.assertEqual(60, rule.get('granularity')) - self.assertEqual('count', rule.get('aggregation_method')) - self.assertEqual('le', rule.get('comparison_operator')) - self.assertEqual(3, rule.get('evaluation_periods')) - self.assertEqual('768ff714-8cfb-4db9-9753-d484cb33a1cc', - rule.get('resource_id')) - self.assertEqual('instance', rule.get('resource_type')) - self._test_common_alarm_gnocchi_arguments(kwargs) - - def _test_alarm_gnocchi_aggr_by_metrics_arguments(self, action, argv): - self.make_env(test_shell.FAKE_V2_ENV) - with mock.patch.object(alarms.AlarmManager, action) as mocked: - with mock.patch('ceilometerclient.apiclient.' - 'client.HTTPClient.client_request') as request: - request.site_effect = exceptions.EndpointNotFound - base_shell.main(argv) - args, kwargs = mocked.call_args - self.assertEqual('gnocchi_aggregation_by_metrics_threshold', - kwargs.get('type')) - self.assertIn('gnocchi_aggregation_by_metrics_threshold_rule', kwargs) - rule = kwargs['gnocchi_aggregation_by_metrics_threshold_rule'] - self.assertEqual(['b3d9d8ab-05e8-439f-89ad-5e978dd2a5eb', - '009d4faf-c275-46f0-8f2d-670b15bac2b0'], - rule.get('metrics')) - self.assertEqual(70.0, rule.get('threshold')) - self.assertEqual(60, rule.get('granularity')) - self.assertEqual('count', rule.get('aggregation_method')) - self.assertEqual('le', rule.get('comparison_operator')) - self.assertEqual(3, rule.get('evaluation_periods')) - self._test_common_alarm_gnocchi_arguments(kwargs) - - def _test_alarm_gnocchi_aggr_by_resources_arguments(self, action, argv): - self.make_env(test_shell.FAKE_V2_ENV) - with mock.patch.object(alarms.AlarmManager, action) as mocked: - with mock.patch('ceilometerclient.apiclient.' - 'client.HTTPClient.client_request') as request: - request.site_effect = exceptions.EndpointNotFound - base_shell.main(argv) - args, kwargs = mocked.call_args - self.assertEqual('gnocchi_aggregation_by_resources_threshold', - kwargs.get('type')) - self.assertIn('gnocchi_aggregation_by_resources_threshold_rule', - kwargs) - rule = kwargs['gnocchi_aggregation_by_resources_threshold_rule'] - self.assertEqual('cpu_util', rule.get('metric')) - self.assertEqual(70.0, rule.get('threshold')) - self.assertEqual(60, rule.get('granularity')) - self.assertEqual('count', rule.get('aggregation_method')) - self.assertEqual('le', rule.get('comparison_operator')) - self.assertEqual(3, rule.get('evaluation_periods')) - self.assertEqual('instance', rule.get('resource_type')) - self.assertEqual('{"=": {"server_group":"my_autoscaling_group"}}', - rule.get('query')) - self._test_common_alarm_gnocchi_arguments(kwargs) - - -class ShellSampleListCommandTest(utils.BaseTestCase): - - METER = 'cpu_util' - SAMPLE_VALUES = ( - ("cpu_util", - "5dcf5537-3161-4e25-9235-407e1385bd35", - "2013-10-15T05:50:30", - "%", - 0.261666666667, - "gauge", - "86536501-b2c9-48f6-9c6a-7a5b14ba7482"), - ("cpu_util", - "87d197e9-9cf6-4c25-bc66-1b1f4cedb52f", - "2013-10-15T05:50:29", - "%", - 0.261666666667, - "gauge", - "fe2a91ec-602b-4b55-8cba-5302ce3b916e",), - ("cpu_util", - "5dcf5537-3161-4e25-9235-407e1385bd35", - "2013-10-15T05:40:30", - "%", - 0.251247920133, - "gauge", - "52768bcb-b4e9-4db9-a30c-738c758b6f43"), - ("cpu_util", - "87d197e9-9cf6-4c25-bc66-1b1f4cedb52f", - "2013-10-15T05:40:29", - "%", - 0.26, - "gauge", - "31ae614a-ac6b-4fb9-b106-4667bae03308"), - ) - - OLD_SAMPLES = [ - dict(counter_name=s[0], - resource_id=s[1], - timestamp=s[2], - counter_unit=s[3], - counter_volume=s[4], - counter_type=s[5]) - for s in SAMPLE_VALUES - ] - - SAMPLES = [ - dict(meter=s[0], - resource_id=s[1], - timestamp=s[2], - unit=s[3], - volume=s[4], - type=s[5], - id=s[6]) - for s in SAMPLE_VALUES - ] - - def setUp(self): - super(ShellSampleListCommandTest, self).setUp() - self.cc = mock.Mock() - self.cc.samples = mock.Mock() - self.cc.new_samples = mock.Mock() - self.args = mock.Mock() - self.args.query = None - self.args.limit = None - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_old_sample_list(self): - self.args.meter = self.METER - sample_list = [samples.OldSample(mock.Mock(), sample) - for sample in self.OLD_SAMPLES] - self.cc.samples.list.return_value = sample_list - - ceilometer_shell.do_sample_list(self.cc, self.args) - self.cc.samples.list.assert_called_once_with( - meter_name=self.METER, - q=None, - limit=None) - - self.assertEqual('''\ -+--------------------------------------+----------+-------+----------------\ -+------+---------------------+ -| Resource ID | Name | Type | Volume \ -| Unit | Timestamp | -+--------------------------------------+----------+-------+----------------\ -+------+---------------------+ -| 5dcf5537-3161-4e25-9235-407e1385bd35 | cpu_util | gauge | 0.261666666667 \ -| % | 2013-10-15T05:50:30 | -| 87d197e9-9cf6-4c25-bc66-1b1f4cedb52f | cpu_util | gauge | 0.261666666667 \ -| % | 2013-10-15T05:50:29 | -| 5dcf5537-3161-4e25-9235-407e1385bd35 | cpu_util | gauge | 0.251247920133 \ -| % | 2013-10-15T05:40:30 | -| 87d197e9-9cf6-4c25-bc66-1b1f4cedb52f | cpu_util | gauge | 0.26 \ -| % | 2013-10-15T05:40:29 | -+--------------------------------------+----------+-------+----------------\ -+------+---------------------+ -''', sys.stdout.getvalue()) - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_sample_list(self): - self.args.meter = None - sample_list = [samples.Sample(mock.Mock(), sample) - for sample in self.SAMPLES] - self.cc.new_samples.list.return_value = sample_list - - ceilometer_shell.do_sample_list(self.cc, self.args) - self.cc.new_samples.list.assert_called_once_with( - q=None, - limit=None) - - self.assertEqual('''\ -+--------------------------------------+--------------------------------------\ -+----------+-------+----------------+------+---------------------+ -| ID | Resource ID \ -| Name | Type | Volume | Unit | Timestamp | -+--------------------------------------+--------------------------------------\ -+----------+-------+----------------+------+---------------------+ -| 86536501-b2c9-48f6-9c6a-7a5b14ba7482 | 5dcf5537-3161-4e25-9235-407e1385bd35 \ -| cpu_util | gauge | 0.261666666667 | % | 2013-10-15T05:50:30 | -| fe2a91ec-602b-4b55-8cba-5302ce3b916e | 87d197e9-9cf6-4c25-bc66-1b1f4cedb52f \ -| cpu_util | gauge | 0.261666666667 | % | 2013-10-15T05:50:29 | -| 52768bcb-b4e9-4db9-a30c-738c758b6f43 | 5dcf5537-3161-4e25-9235-407e1385bd35 \ -| cpu_util | gauge | 0.251247920133 | % | 2013-10-15T05:40:30 | -| 31ae614a-ac6b-4fb9-b106-4667bae03308 | 87d197e9-9cf6-4c25-bc66-1b1f4cedb52f \ -| cpu_util | gauge | 0.26 | % | 2013-10-15T05:40:29 | -+--------------------------------------+--------------------------------------\ -+----------+-------+----------------+------+---------------------+ -''', sys.stdout.getvalue()) - - -class ShellSampleShowCommandTest(utils.BaseTestCase): - - SAMPLE = { - "user_id": None, - "resource_id": "9b651dfd-7d30-402b-972e-212b2c4bfb05", - "timestamp": "2014-11-03T13:37:46", - "meter": "image", - "volume": 1.0, - "source": "openstack", - "recorded_at": "2014-11-03T13:37:46.994458", - "project_id": "2cc3a7bb859b4bacbeab0aa9ca673033", - "type": "gauge", - "id": "98b5f258-635e-11e4-8bdd-0025647390c1", - "unit": "image", - "metadata": { - "name": "cirros-0.3.2-x86_64-uec", - } - } - - def setUp(self): - super(ShellSampleShowCommandTest, self).setUp() - self.cc = mock.Mock() - self.cc.new_samples = mock.Mock() - self.args = mock.Mock() - self.args.sample_id = "98b5f258-635e-11e4-8bdd-0025647390c1" - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_sample_show(self): - sample = samples.Sample(mock.Mock(), self.SAMPLE) - self.cc.new_samples.get.return_value = sample - - ceilometer_shell.do_sample_show(self.cc, self.args) - self.cc.new_samples.get.assert_called_once_with( - "98b5f258-635e-11e4-8bdd-0025647390c1") - - self.assertEqual('''\ -+-------------+--------------------------------------+ -| Property | Value | -+-------------+--------------------------------------+ -| id | 98b5f258-635e-11e4-8bdd-0025647390c1 | -| metadata | {"name": "cirros-0.3.2-x86_64-uec"} | -| meter | image | -| project_id | 2cc3a7bb859b4bacbeab0aa9ca673033 | -| recorded_at | 2014-11-03T13:37:46.994458 | -| resource_id | 9b651dfd-7d30-402b-972e-212b2c4bfb05 | -| source | openstack | -| timestamp | 2014-11-03T13:37:46 | -| type | gauge | -| unit | image | -| user_id | None | -| volume | 1.0 | -+-------------+--------------------------------------+ -''', sys.stdout.getvalue()) - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_sample_show_raises_command_err(self): - self.cc.new_samples.get.side_effect = exc.HTTPNotFound - - self.assertRaises(exc.CommandError, ceilometer_shell.do_sample_show, - self.cc, self.args) - - -class ShellSampleCreateCommandTest(utils.BaseTestCase): - - METER = 'instance' - METER_TYPE = 'gauge' - RESOURCE_ID = '0564c64c-3545-4e34-abfb-9d18e5f2f2f9' - SAMPLE_VOLUME = '1' - METER_UNIT = 'instance' - SAMPLE = [{ - u'counter_name': u'instance', - u'user_id': u'21b442b8101d407d8242b6610e0ed0eb', - u'resource_id': u'0564c64c-3545-4e34-abfb-9d18e5f2f2f9', - u'timestamp': u'2014-01-10T03: 05: 33.951170', - u'message_id': u'1247cbe6-79a4-11e3-a296-000c294c58e2', - u'source': u'384260c6987b451d8290e66e1f108082: openstack', - u'counter_unit': u'instance', - u'counter_volume': 1.0, - u'project_id': u'384260c6987b451d8290e66e1f108082', - u'counter_type': u'gauge', - u'resource_metadata': {u'display_name': u'test_name'} - }] - - def setUp(self): - super(ShellSampleCreateCommandTest, self).setUp() - self.cc = mock.Mock() - self.cc.samples = mock.Mock() - self.args = mock.Mock() - self.args.meter_name = self.METER - self.args.meter_type = self.METER_TYPE - self.args.meter_unit = self.METER_UNIT - self.args.resource_id = self.RESOURCE_ID - self.args.sample_volume = self.SAMPLE_VOLUME - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_sample_create(self): - ret_sample = [samples.OldSample(mock.Mock(), sample) - for sample in self.SAMPLE] - self.cc.samples.create.return_value = ret_sample - - ceilometer_shell.do_sample_create(self.cc, self.args) - - self.assertEqual('''\ -+-------------------+---------------------------------------------+ -| Property | Value | -+-------------------+---------------------------------------------+ -| message_id | 1247cbe6-79a4-11e3-a296-000c294c58e2 | -| name | instance | -| project_id | 384260c6987b451d8290e66e1f108082 | -| resource_id | 0564c64c-3545-4e34-abfb-9d18e5f2f2f9 | -| resource_metadata | {"display_name": "test_name"} | -| source | 384260c6987b451d8290e66e1f108082: openstack | -| timestamp | 2014-01-10T03: 05: 33.951170 | -| type | gauge | -| unit | instance | -| user_id | 21b442b8101d407d8242b6610e0ed0eb | -| volume | 1.0 | -+-------------------+---------------------------------------------+ -''', sys.stdout.getvalue()) - - def test_sample_create_with_invalid_resource_metadata(self): - self.args.resource_metadata = 'foo=bar' - with mock.patch('ceilometerclient.exc.CommandError') as e: - e.return_value = exc.BaseException() - self.assertRaises(exc.BaseException, - ceilometer_shell.do_sample_create, - self.cc, self.args) - e.assert_called_with('Invalid resource metadata, it should be a' - ' json string, like: \'{"foo":"bar"}\'') - - -class ShellSampleCreateListCommandTest(utils.BaseTestCase): - - SAMPLE = { - u'counter_name': u'image', - u'user_id': u'21b442b8101d407d8242b6610e0ed0eb', - u'resource_id': u'0564c64c-3545-4e34-abfb-9d18e5f2f2f9', - u'timestamp': u'2015-05-19T12:00:08.368574', - u'source': u'384260c6987b451d8290e66e1f108082: openstack', - u'counter_unit': u'image', - u'counter_volume': 1.0, - u'project_id': u'384260c6987b451d8290e66e1f108082', - u'resource_metadata': {}, - u'counter_type': u'cumulative' - } - - def setUp(self): - super(ShellSampleCreateListCommandTest, self).setUp() - self.cc = mock.Mock() - self.cc.samples = mock.Mock() - self.cc.samples.create_list = mock.Mock() - self.args = mock.Mock() - self.samples = [self.SAMPLE] * 5 - self.args.samples_list = json.dumps(self.samples) - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_sample_create_list(self): - ret_samples = [samples.OldSample(mock.Mock(), - sample) for sample in self.samples] - self.cc.samples.create_list.return_value = ret_samples - ceilometer_shell.do_sample_create_list(self.cc, self.args) - self.cc.samples.create_list.assert_called_with(self.samples, - direct=mock.ANY) - self.assertEqual('''\ -+--------------------------------------+-------+------------+--------+-------\ -+----------------------------+ -| Resource ID | Name | Type | Volume | Unit \ -| Timestamp | -+--------------------------------------+-------+------------+--------+-------\ -+----------------------------+ -| 0564c64c-3545-4e34-abfb-9d18e5f2f2f9 | image | cumulative | 1.0 | image \ -| 2015-05-19T12:00:08.368574 | -| 0564c64c-3545-4e34-abfb-9d18e5f2f2f9 | image | cumulative | 1.0 | image \ -| 2015-05-19T12:00:08.368574 | -| 0564c64c-3545-4e34-abfb-9d18e5f2f2f9 | image | cumulative | 1.0 | image \ -| 2015-05-19T12:00:08.368574 | -| 0564c64c-3545-4e34-abfb-9d18e5f2f2f9 | image | cumulative | 1.0 | image \ -| 2015-05-19T12:00:08.368574 | -| 0564c64c-3545-4e34-abfb-9d18e5f2f2f9 | image | cumulative | 1.0 | image \ -| 2015-05-19T12:00:08.368574 | -+--------------------------------------+-------+------------+--------+-------\ -+----------------------------+ -''', sys.stdout.getvalue()) - - -class ShellQuerySamplesCommandTest(utils.BaseTestCase): - - SAMPLE = [{u'id': u'b55d1526-9929-11e3-a3f6-02163e5df1e6', - u'metadata': { - u'name1': u'value1', - u'name2': u'value2'}, - u'meter': 'instance', - u'project_id': u'35b17138-b364-4e6a-a131-8f3099c5be68', - u'resource_id': u'bd9431c1-8d69-4ad3-803a-8d4a6b89fd36', - u'source': u'openstack', - u'timestamp': u'2014-02-19T05:50:16.673604', - u'type': u'gauge', - u'unit': u'instance', - u'volume': 1, - u'user_id': 'efd87807-12d2-4b38-9c70-5f5c2ac427ff'}] - - QUERY = {"filter": {"and": [{"=": {"source": "openstack"}}, - {">": {"timestamp": "2014-02-19T05:50:16"}}]}, - "orderby": [{"timestamp": "desc"}, {"volume": "asc"}], - "limit": 10} - - def setUp(self): - super(ShellQuerySamplesCommandTest, self).setUp() - self.cc = mock.Mock() - self.args = mock.Mock() - self.args.filter = self.QUERY["filter"] - self.args.orderby = self.QUERY["orderby"] - self.args.limit = self.QUERY["limit"] - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_query(self): - ret_sample = [samples.Sample(mock.Mock(), sample) - for sample in self.SAMPLE] - self.cc.query_samples.query.return_value = ret_sample - - ceilometer_shell.do_query_samples(self.cc, self.args) - - self.assertEqual('''\ -+--------------------------------------+--------------------------------------\ -+----------+-------+--------+----------+----------------------------+ -| ID | Resource ID \ -| Meter | Type | Volume | Unit | Timestamp | -+--------------------------------------+--------------------------------------\ -+----------+-------+--------+----------+----------------------------+ -| b55d1526-9929-11e3-a3f6-02163e5df1e6 | bd9431c1-8d69-4ad3-803a-8d4a6b89fd36 \ -| instance | gauge | 1 | instance | 2014-02-19T05:50:16.673604 | -+--------------------------------------+--------------------------------------\ -+----------+-------+--------+----------+----------------------------+ -''', sys.stdout.getvalue()) - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_query_raises_command_error(self): - self.cc.query_samples.query.side_effect = exc.HTTPNotFound - - self.assertRaises(exc.CommandError, - ceilometer_shell.do_query_samples, - self.cc, self.args) - - -class ShellQueryAlarmsCommandTest(utils.BaseTestCase): - - ALARM = [{"alarm_actions": ["http://site:8000/alarm"], - "alarm_id": "768ff714-8cfb-4db9-9753-d484cb33a1cc", - "combination_rule": { - "alarm_ids": [ - "739e99cb-c2ec-4718-b900-332502355f38", - "153462d0-a9b8-4b5b-8175-9e4b05e9b856"], - "operator": "or"}, - "description": "An alarm", - "enabled": True, - "insufficient_data_actions": ["http://site:8000/nodata"], - "name": "SwiftObjectAlarm", - "ok_actions": ["http://site:8000/ok"], - "project_id": "c96c887c216949acbdfbd8b494863567", - "repeat_actions": False, - "state": "ok", - "severity": "critical", - "state_timestamp": "2014-02-20T10:37:15.589860", - "threshold_rule": None, - "timestamp": "2014-02-20T10:37:15.589856", - "time_constraints": [{"name": "test", "start": "0 23 * * *", - "duration": 10800}], - "type": "combination", - "user_id": "c96c887c216949acbdfbd8b494863567"}] - - QUERY = {"filter": {"and": [{"!=": {"state": "ok"}}, - {"=": {"type": "combination"}}]}, - "orderby": [{"state_timestamp": "desc"}], - "limit": 10} - - def setUp(self): - super(ShellQueryAlarmsCommandTest, self).setUp() - self.cc = mock.Mock() - self.args = mock.Mock() - self.args.filter = self.QUERY["filter"] - self.args.orderby = self.QUERY["orderby"] - self.args.limit = self.QUERY["limit"] - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_query(self): - ret_alarm = [alarms.Alarm(mock.Mock(), alarm) - for alarm in self.ALARM] - self.cc.query_alarms.query.return_value = ret_alarm - - ceilometer_shell.do_query_alarms(self.cc, self.args) - - self.assertEqual('''\ -+--------------------------------------+------------------+-------+----------+\ ----------+------------+-------------------------------------------------------\ ------------------------------------------------+-------------------------------\ --+ -| Alarm ID | Name | State | Severity \ -| Enabled | Continuous | Alarm condition \ - | Time constraints \ - | -+--------------------------------------+------------------+-------+----------+\ ----------+------------+-------------------------------------------------------\ ------------------------------------------------+--------------------------------+ -| 768ff714-8cfb-4db9-9753-d484cb33a1cc | SwiftObjectAlarm | ok | critical \ -| True | False | combinated states (OR) of \ -739e99cb-c2ec-4718-b900-332502355f38, 153462d0-a9b8-4b5b-8175-9e4b05e9b856 |\ - test at 0 23 * * * for 10800s | -+--------------------------------------+------------------+-------+----------+\ ----------+------------+-------------------------------------------------------\ ------------------------------------------------+------------------------------\ ---+ -''', sys.stdout.getvalue()) - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_time_constraints_compatibility(self): - # client should be backwards compatible - alarm_without_tc = dict(self.ALARM[0]) - del alarm_without_tc['time_constraints'] - - # NOTE(nsaje): Since we're accessing a nonexisting key in the resource, - # the resource is looking it up with the manager (which is a mock). - manager_mock = mock.Mock() - del manager_mock.get - ret_alarm = [alarms.Alarm(manager_mock, alarm_without_tc)] - self.cc.query_alarms.query.return_value = ret_alarm - - ceilometer_shell.do_query_alarms(self.cc, self.args) - - self.assertEqual('''\ -+--------------------------------------+------------------+-------+----------+\ ----------+------------+-------------------------------------------------------\ ------------------------------------------------+------------------+ -| Alarm ID | Name | State | Severity \ -| Enabled | Continuous | Alarm condition \ - | Time constraints | -+--------------------------------------+------------------+-------+----------+\ ----------+------------+-------------------------------------------------------\ ------------------------------------------------+------------------+ -| 768ff714-8cfb-4db9-9753-d484cb33a1cc | SwiftObjectAlarm | ok | critical \ -| True | False | combinated states (OR) of \ -739e99cb-c2ec-4718-b900-332502355f38, 153462d0-a9b8-4b5b-8175-9e4b05e9b856 \ -| None | -+--------------------------------------+------------------+-------+----------+\ ----------+------------+-------------------------------------------------------\ ------------------------------------------------+------------------+ -''', sys.stdout.getvalue()) - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_query_raises_command_err(self): - self.cc.query_alarms.query.side_effect = exc.HTTPNotFound - self.assertRaises(exc.CommandError, - ceilometer_shell.do_query_alarms, - self.cc, self.args) - - -class ShellQueryAlarmHistoryCommandTest(utils.BaseTestCase): - - ALARM_HISTORY = [{"alarm_id": "e8ff32f772a44a478182c3fe1f7cad6a", - "event_id": "c74a8611-6553-4764-a860-c15a6aabb5d0", - "detail": - "{\"threshold\": 42.0, \"evaluation_periods\": 4}", - "on_behalf_of": "92159030020611e3b26dde429e99ee8c", - "project_id": "b6f16144010811e387e4de429e99ee8c", - "timestamp": "2014-03-11T16:02:58.376261", - "type": "rule change", - "user_id": "3e5d11fda79448ac99ccefb20be187ca" - }] - - QUERY = {"filter": {"and": [{">": {"timestamp": "2014-03-11T16:02:58"}}, - {"=": {"type": "rule change"}}]}, - "orderby": [{"timestamp": "desc"}], - "limit": 10} - - def setUp(self): - super(ShellQueryAlarmHistoryCommandTest, self).setUp() - self.cc = mock.Mock() - self.args = mock.Mock() - self.args.filter = self.QUERY["filter"] - self.args.orderby = self.QUERY["orderby"] - self.args.limit = self.QUERY["limit"] - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_query(self): - ret_alarm_history = [alarms.AlarmChange(mock.Mock(), alarm_history) - for alarm_history in self.ALARM_HISTORY] - self.cc.query_alarm_history.query.return_value = ret_alarm_history - - ceilometer_shell.do_query_alarm_history(self.cc, self.args) - - self.assertEqual('''\ -+----------------------------------+--------------------------------------+-\ -------------+----------------------------------------------+----------------\ -------------+ -| Alarm ID | Event ID | \ -Type | Detail | Timestamp \ - | -+----------------------------------+--------------------------------------+-\ -------------+----------------------------------------------+----------------\ -------------+ -| e8ff32f772a44a478182c3fe1f7cad6a | c74a8611-6553-4764-a860-c15a6aabb5d0 | \ -rule change | {"threshold": 42.0, "evaluation_periods": 4} | 2014-03-11T16:0\ -2:58.376261 | -+----------------------------------+--------------------------------------+-\ -------------+----------------------------------------------+----------------\ -------------+ -''', sys.stdout.getvalue()) - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_query_raises_command_err(self): - self.cc.query_alarm_history.query.side_effect = exc.HTTPNotFound - self.assertRaises(exc.CommandError, - ceilometer_shell.do_query_alarm_history, - self.cc, self.args) - - -class ShellStatisticsTest(utils.BaseTestCase): - def setUp(self): - super(ShellStatisticsTest, self).setUp() - self.cc = mock.Mock() - self.displays = { - 'duration': 'Duration', - 'duration_end': 'Duration End', - 'duration_start': 'Duration Start', - 'period': 'Period', - 'period_end': 'Period End', - 'period_start': 'Period Start', - 'groupby': 'Group By', - 'avg': 'Avg', - 'count': 'Count', - 'max': 'Max', - 'min': 'Min', - 'sum': 'Sum', - 'stddev': 'Standard deviation', - 'cardinality': 'Cardinality' - } - self.args = mock.Mock() - self.args.meter_name = 'instance' - self.args.aggregate = [] - self.args.groupby = None - self.args.query = None - - def test_statistics_list_simple(self): - samples = [ - {u'count': 135, - u'duration_start': u'2013-02-04T10:51:42', - u'min': 1.0, - u'max': 1.0, - u'duration_end': - u'2013-02-05T15:46:09', - u'duration': 1734.0, - u'avg': 1.0, - u'sum': 135.0}, - ] - fields = [ - 'period', - 'period_start', - 'period_end', - 'max', - 'min', - 'avg', - 'sum', - 'count', - 'duration', - 'duration_start', - 'duration_end', - ] - statistics_ret = [ - statistics.Statistics(mock.Mock(), sample) for sample in samples - ] - self.cc.statistics.list.return_value = statistics_ret - with mock.patch('ceilometerclient.v2.shell.utils.print_list') as pmock: - ceilometer_shell.do_statistics(self.cc, self.args) - pmock.assert_called_with( - statistics_ret, - fields, - [self.displays[f] for f in fields] - ) - - def test_statistics_list_groupby(self): - samples = [ - {u'count': 135, - u'duration_start': u'2013-02-04T10:51:42', - u'min': 1.0, - u'max': 1.0, - u'duration_end': - u'2013-02-05T15:46:09', - u'duration': 1734.0, - u'avg': 1.0, - u'sum': 135.0, - u'groupby': {u'resource_id': u'foo'} - }, - {u'count': 12, - u'duration_start': u'2013-02-04T10:51:42', - u'min': 1.0, - u'max': 1.0, - u'duration_end': - u'2013-02-05T15:46:09', - u'duration': 1734.0, - u'avg': 1.0, - u'sum': 12.0, - u'groupby': {u'resource_id': u'bar'} - }, - ] - fields = [ - 'period', - 'period_start', - 'period_end', - 'groupby', - 'max', - 'min', - 'avg', - 'sum', - 'count', - 'duration', - 'duration_start', - 'duration_end', - ] - self.args.groupby = 'resource_id' - statistics_ret = [ - statistics.Statistics(mock.Mock(), sample) for sample in samples - ] - self.cc.statistics.list.return_value = statistics_ret - with mock.patch('ceilometerclient.v2.shell.utils.print_list') as pmock: - ceilometer_shell.do_statistics(self.cc, self.args) - pmock.assert_called_with( - statistics_ret, - fields, - [self.displays[f] for f in fields], - ) - - def test_statistics_list_aggregates(self): - samples = [ - {u'aggregate': {u'cardinality/resource_id': 4.0, u'count': 2.0}, - u'count': 2, - u'duration': 0.442451, - u'duration_end': u'2014-03-12T14:00:21.774154', - u'duration_start': u'2014-03-12T14:00:21.331703', - u'groupby': None, - u'period': 0, - u'period_end': u'2014-03-12T14:00:21.774154', - u'period_start': u'2014-03-12T14:00:21.331703', - u'unit': u'instance', - }, - ] - fields = [ - 'period', - 'period_start', - 'period_end', - 'count', - 'cardinality/resource_id', - 'duration', - 'duration_start', - 'duration_end', - ] - self.args.aggregate = ['count', 'cardinality<-resource_id'] - statistics_ret = [ - statistics.Statistics(mock.Mock(), sample) for sample in samples - ] - self.cc.statistics.list.return_value = statistics_ret - with mock.patch('ceilometerclient.v2.shell.utils.print_list') as pmock: - ceilometer_shell.do_statistics(self.cc, self.args) - pmock.assert_called_with( - statistics_ret, - fields, - [self.displays.get(f, f) for f in fields], - ) - - -class ShellEmptyIdTest(utils.BaseTestCase): - """Test empty field which will cause calling incorrect rest uri.""" - - def _test_entity_action_with_empty_values(self, entity, - *args, **kwargs): - positional = kwargs.pop('positional', False) - for value in ('', ' ', ' ', '\t'): - self._test_entity_action_with_empty_value(entity, value, - positional, *args) - - def _test_entity_action_with_empty_value(self, entity, value, - positional, *args): - new_args = [value] if positional else ['--%s' % entity, value] - argv = list(args) + new_args - shell = base_shell.CeilometerShell() - with mock.patch('ceilometerclient.exc.CommandError') as e: - e.return_value = exc.BaseException() - self.assertRaises(exc.BaseException, shell.parse_args, argv) - entity = entity.replace('-', '_') - e.assert_called_with('%s should not be empty' % entity) - - def _test_alarm_action_with_empty_ids(self, method, *args): - args = [method] + list(args) - self._test_entity_action_with_empty_values('alarm_id', - positional=True, *args) - - def test_alarm_show_with_empty_id(self): - self._test_alarm_action_with_empty_ids('alarm-show') - - def test_alarm_update_with_empty_id(self): - self._test_alarm_action_with_empty_ids('alarm-update') - - def test_alarm_threshold_update_with_empty_id(self): - self._test_alarm_action_with_empty_ids('alarm-threshold-update') - - def test_alarm_combination_update_with_empty_id(self): - self._test_alarm_action_with_empty_ids('alarm-combination-update') - - def test_alarm_gnocchi_resources_update_with_empty_id(self): - self._test_alarm_action_with_empty_ids( - 'alarm-gnocchi-resources-threshold-update') - - def test_alarm_gnocchi_aggr_by_resources_update_with_empty_id(self): - self._test_alarm_action_with_empty_ids( - 'alarm-gnocchi-aggregation-by-resources-threshold-update') - - def test_alarm_gnocchi_aggr_by_metrics_update_with_empty_id(self): - self._test_alarm_action_with_empty_ids( - 'alarm-gnocchi-aggregation-by-metrics-threshold-update') - - def test_alarm_delete_with_empty_id(self): - self._test_alarm_action_with_empty_ids('alarm-delete') - - def test_alarm_state_get_with_empty_id(self): - self._test_alarm_action_with_empty_ids('alarm-state-get') - - def test_alarm_state_set_with_empty_id(self): - args = ['alarm-state-set', '--state', 'ok'] - self._test_alarm_action_with_empty_ids(*args) - - def test_alarm_history_with_empty_id(self): - self._test_alarm_action_with_empty_ids('alarm-history') - - def test_event_show_with_empty_message_id(self): - args = ['event-show'] - self._test_entity_action_with_empty_values('message_id', *args) - - def test_resource_show_with_empty_id(self): - args = ['resource-show'] - self._test_entity_action_with_empty_values('resource_id', *args) - - def test_sample_list_with_empty_meter(self): - args = ['sample-list'] - self._test_entity_action_with_empty_values('meter', *args) - - def test_sample_create_with_empty_meter(self): - args = ['sample-create', '-r', 'x', '--meter-type', 'gauge', - '--meter-unit', 'B', '--sample-volume', '1'] - self._test_entity_action_with_empty_values('meter-name', *args) - - def test_statistics_with_empty_meter(self): - args = ['statistics'] - self._test_entity_action_with_empty_values('meter', *args) - - def test_trait_description_list_with_empty_event_type(self): - args = ['trait-description-list'] - self._test_entity_action_with_empty_values('event_type', *args) - - def test_trait_list_with_empty_event_type(self): - args = ['trait-list', '--trait_name', 'x'] - self._test_entity_action_with_empty_values('event_type', *args) - - def test_trait_list_with_empty_trait_name(self): - args = ['trait-list', '--event_type', 'x'] - self._test_entity_action_with_empty_values('trait_name', *args) - - -class ShellObsoletedArgsTest(utils.BaseTestCase): - """Test arguments that have been obsoleted.""" - - def _test_entity_obsoleted(self, entity, value, positional, *args): - new_args = [value] if positional else ['--%s' % entity, value] - argv = list(args) + new_args - shell = base_shell.CeilometerShell() - with mock.patch('sys.stdout', new_callable=six.StringIO) as stdout: - shell.parse_args(argv) - self.assertIn('obsolete', stdout.getvalue()) - - def test_obsolete_alarm_id(self): - for method in ['alarm-show', 'alarm-update', 'alarm-threshold-update', - 'alarm-combination-update', 'alarm-delete', - 'alarm-state-get', 'alarm-history']: - self._test_entity_obsoleted('alarm_id', 'abcde', False, method) - - -class ShellEventListCommandTest(utils.BaseTestCase): - - EVENTS = [ - { - "generated": "2015-01-12T04:03:25.741471", - "message_id": "fb2bef58-88af-4380-8698-e0f18fcf452d", - "event_type": "compute.instance.create.start", - "traits": [{ - "name": "state", - "type": "string", - "value": "building", - }], - }, - { - "generated": "2015-01-12T04:03:28.452495", - "message_id": "9b20509a-576b-4995-acfa-1a24ee5cf49f", - "event_type": "compute.instance.create.end", - "traits": [{ - "name": "state", - "type": "string", - "value": "active", - }], - }, - ] - - def setUp(self): - super(ShellEventListCommandTest, self).setUp() - self.cc = mock.Mock() - self.args = mock.Mock() - self.args.query = None - self.args.no_traits = None - self.args.limit = None - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_event_list(self): - ret_events = [events.Event(mock.Mock(), event) - for event in self.EVENTS] - self.cc.events.list.return_value = ret_events - ceilometer_shell.do_event_list(self.cc, self.args) - self.assertEqual('''\ -+--------------------------------------+-------------------------------+\ -----------------------------+-------------------------------+ -| Message ID | Event Type |\ - Generated | Traits | -+--------------------------------------+-------------------------------+\ -----------------------------+-------------------------------+ -| fb2bef58-88af-4380-8698-e0f18fcf452d | compute.instance.create.start |\ - 2015-01-12T04:03:25.741471 | +-------+--------+----------+ | -| | |\ - | | name | type | value | | -| | |\ - | +-------+--------+----------+ | -| | |\ - | | state | string | building | | -| | |\ - | +-------+--------+----------+ | -| 9b20509a-576b-4995-acfa-1a24ee5cf49f | compute.instance.create.end |\ - 2015-01-12T04:03:28.452495 | +-------+--------+--------+ | -| | |\ - | | name | type | value | | -| | |\ - | +-------+--------+--------+ | -| | |\ - | | state | string | active | | -| | |\ - | +-------+--------+--------+ | -+--------------------------------------+-------------------------------+\ -----------------------------+-------------------------------+ -''', sys.stdout.getvalue()) - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_event_list_no_traits(self): - self.args.no_traits = True - ret_events = [events.Event(mock.Mock(), event) - for event in self.EVENTS] - self.cc.events.list.return_value = ret_events - ceilometer_shell.do_event_list(self.cc, self.args) - self.assertEqual('''\ -+--------------------------------------+-------------------------------\ -+----------------------------+ -| Message ID | Event Type \ -| Generated | -+--------------------------------------+-------------------------------\ -+----------------------------+ -| fb2bef58-88af-4380-8698-e0f18fcf452d | compute.instance.create.start \ -| 2015-01-12T04:03:25.741471 | -| 9b20509a-576b-4995-acfa-1a24ee5cf49f | compute.instance.create.end \ -| 2015-01-12T04:03:28.452495 | -+--------------------------------------+-------------------------------\ -+----------------------------+ -''', sys.stdout.getvalue()) - - -class ShellShadowedArgsTest(test_shell.ShellTestBase): - - def _test_shadowed_args_alarm(self, command, args, method): - self.make_env(test_shell.FAKE_V2_ENV) - cli_args = [ - '--os-project-id', '0ba30185ddf44834914a0b859d244c56', - '--os-user-id', '85f59b3b17484ccb974c50596023bf8c', - '--debug', command, - '--project-id', 'the-project-id-i-want-to-set', - '--user-id', 'the-user-id-i-want-to-set', - '--name', 'project-id-test'] + args - with mock.patch.object(alarms.AlarmManager, method) as mocked: - with mock.patch('ceilometerclient.apiclient.' - 'client.HTTPClient.client_request') as request: - request.site_effect = exceptions.EndpointNotFound - base_shell.main(cli_args) - args, kwargs = mocked.call_args - self.assertEqual('the-project-id-i-want-to-set', - kwargs.get('project_id')) - self.assertEqual('the-user-id-i-want-to-set', - kwargs.get('user_id')) - - def test_shadowed_args_threshold_alarm(self): - cli_args = ['--meter-name', 'cpu', '--threshold', '90'] - self._test_shadowed_args_alarm('alarm-create', cli_args, 'create') - self._test_shadowed_args_alarm('alarm-threshold-create', - cli_args, 'create') - cli_args += ['--alarm_id', '437b7ed0-3733-4054-a877-e9a297b8be85'] - self._test_shadowed_args_alarm('alarm-update', cli_args, 'update') - self._test_shadowed_args_alarm('alarm-threshold-update', - cli_args, 'update') - - def test_shadowed_args_combination_alarm(self): - cli_args = ['--alarm_ids', 'fb16a05a-669d-414e-8bbe-93aa381df6a8', - '--alarm_ids', 'b189bcca-0a7b-49a9-a244-a927ac291881'] - self._test_shadowed_args_alarm('alarm-combination-create', - cli_args, 'create') - cli_args += ['--alarm_id', '437b7ed0-3733-4054-a877-e9a297b8be85'] - self._test_shadowed_args_alarm('alarm-combination-update', - cli_args, 'update') - - def test_shadowed_args_gnocchi_resources_threshold_alarm(self): - cli_args = [ - '--metric', 'cpu', - '--threshold', '80', - '--resource-type', 'instance', - '--resource-id', 'fb16a05a-669d-414e-8bbe-93aa381df6a8', - '--aggregation-method', 'last', - ] - self._test_shadowed_args_alarm('alarm-gnocchi-resources-' - 'threshold-create', - cli_args, 'create') - cli_args += ['--alarm_id', '437b7ed0-3733-4054-a877-e9a297b8be85'] - self._test_shadowed_args_alarm('alarm-gnocchi-resources-' - 'threshold-update', - cli_args, 'update') - - def test_shadowed_args_gnocchi_aggr_by_resources_threshold_alarm(self): - cli_args = [ - '--metric', 'cpu', - '--threshold', '80', - '--resource-type', 'instance', - '--aggregation-method', 'last', - '--query', '"server_group":"my_autoscaling_group"', - ] - self._test_shadowed_args_alarm('alarm-gnocchi-aggregation-' - 'by-resources-threshold-create', - cli_args, 'create') - cli_args += ['--alarm_id', '437b7ed0-3733-4054-a877-e9a297b8be85'] - self._test_shadowed_args_alarm('alarm-gnocchi-aggregation-' - 'by-resources-threshold-update', - cli_args, 'update') - - def test_shadowed_args_gnocchi_aggr_by_metrics_threshold_alarm(self): - cli_args = [ - '-m', 'b3d9d8ab-05e8-439f-89ad-5e978dd2a5eb', - '-m', '009d4faf-c275-46f0-8f2d-670b15bac2b0', - '--threshold', '80', - '--aggregation-method', 'last', - ] - self._test_shadowed_args_alarm('alarm-gnocchi-aggregation-' - 'by-metrics-threshold-create', - cli_args, 'create') - cli_args += ['--alarm_id', '437b7ed0-3733-4054-a877-e9a297b8be85'] - self._test_shadowed_args_alarm('alarm-gnocchi-aggregation-' - 'by-metrics-threshold-update', - cli_args, 'update') - - @mock.patch.object(samples.OldSampleManager, 'create') - def test_shadowed_args_sample_create(self, mocked): - self.make_env(test_shell.FAKE_V2_ENV) - cli_args = [ - '--os-project-id', '0ba30185ddf44834914a0b859d244c56', - '--os-user-id', '85f59b3b17484ccb974c50596023bf8c', - '--debug', 'sample-create', - '--project-id', 'the-project-id-i-want-to-set', - '--user-id', 'the-user-id-i-want-to-set', - '--resource-id', 'b666633d-9bb6-4e05-89c0-ee5a8752fb0b', - '--meter-name', 'cpu', - '--meter-type', 'cumulative', - '--meter-unit', 'ns', - '--sample-volume', '10086', - ] - with mock.patch('ceilometerclient.apiclient.client.' - 'HTTPClient.client_request') as client_request: - client_request.site_effect = exceptions.EndpointNotFound - base_shell.main(cli_args) - args, kwargs = mocked.call_args - self.assertEqual('the-project-id-i-want-to-set', - kwargs.get('project_id')) - self.assertEqual('the-user-id-i-want-to-set', - kwargs.get('user_id')) - - -class ShellCapabilityShowTest(utils.BaseTestCase): - - CAPABILITIES = { - "alarm_storage": { - "storage:production_ready": True - }, - "api": { - "alarms:query:complex": True, - "alarms:query:simple": True - }, - "event_storage": { - "storage:production_ready": True - }, - "storage": { - "storage:production_ready": True - }, - } - - def setUp(self): - super(ShellCapabilityShowTest, self).setUp() - self.cc = mock.Mock() - self.args = mock.Mock() - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_capability_show(self): - _cap = capabilities.Capabilities(mock.Mock, self.CAPABILITIES) - self.cc.capabilities.get.return_value = _cap - - ceilometer_shell.do_capabilities(self.cc, self.args) - self.assertEqual('''\ -+---------------+----------------------------------+ -| Property | Value | -+---------------+----------------------------------+ -| alarm_storage | "storage:production_ready": true | -| api | "alarms:query:complex": true, | -| | "alarms:query:simple": true | -| event_storage | "storage:production_ready": true | -| storage | "storage:production_ready": true | -+---------------+----------------------------------+ -''', sys.stdout.getvalue()) - - -class ShellMeterListCommandTest(utils.BaseTestCase): - - METER = { - "name": 'image', - "resource_id": "resource-id", - "meter": "image", - "project_id": "project", - "type": "gauge", - "unit": "image", - } - - def setUp(self): - super(ShellMeterListCommandTest, self).setUp() - self.cc = mock.Mock() - self.cc.meters.list = mock.Mock() - self.args = mock.MagicMock() - self.args.limit = None - self.args.unique = False - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_meter_list(self): - meter = meters.Meter(mock.Mock(), self.METER) - self.cc.meters.list.return_value = [meter] - - ceilometer_shell.do_meter_list(self.cc, self.args) - self.cc.meters.list.assert_called_once_with(q=[], limit=None, - unique=False) - - self.assertEqual('''\ -+-------+-------+-------+-------------+---------+------------+ -| Name | Type | Unit | Resource ID | User ID | Project ID | -+-------+-------+-------+-------------+---------+------------+ -| image | gauge | image | resource-id | | project | -+-------+-------+-------+-------------+---------+------------+ -''', sys.stdout.getvalue()) - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_unique_meter_list(self): - self.args.unique = True - meter = meters.Meter(mock.Mock(), self.METER) - self.cc.meters.list.return_value = [meter] - - ceilometer_shell.do_meter_list(self.cc, self.args) - self.cc.meters.list.assert_called_once_with(q=[], limit=None, - unique=True) - - self.assertEqual('''\ -+-------+-------+-------+-------------+---------+------------+ -| Name | Type | Unit | Resource ID | User ID | Project ID | -+-------+-------+-------+-------------+---------+------------+ -| image | gauge | image | resource-id | | project | -+-------+-------+-------+-------------+---------+------------+ -''', sys.stdout.getvalue()) - - -class ShellResourceListCommandTest(utils.BaseTestCase): - - RESOURCE = { - "source": "openstack", - "resource_id": "resource-id", - "project_id": "project", - "user_id": "user" - } - - def setUp(self): - super(ShellResourceListCommandTest, self).setUp() - self.cc = mock.Mock() - self.cc.resources.list = mock.Mock() - self.args = mock.MagicMock() - self.args.limit = None - self.args.meter_links = None - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_resource_list(self): - resource = resources.Resource(mock.Mock(), self.RESOURCE) - self.cc.resources.list.return_value = [resource] - ceilometer_shell.do_resource_list(self.cc, self.args) - self.cc.resources.list.assert_called_once_with(q=[], - limit=None) - - self.assertEqual('''\ -+-------------+-----------+---------+------------+ -| Resource ID | Source | User ID | Project ID | -+-------------+-----------+---------+------------+ -| resource-id | openstack | user | project | -+-------------+-----------+---------+------------+ -''', sys.stdout.getvalue()) - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_resource_list_with_links(self): - resource = resources.Resource(mock.Mock(), self.RESOURCE) - self.cc.resources.list.return_value = [resource] - ceilometer_shell.do_resource_list(self.cc, self.args) - self.cc.resources.list.assert_called_once_with(q=[], - limit=None) - self.assertEqual('''\ -+-------------+-----------+---------+------------+ -| Resource ID | Source | User ID | Project ID | -+-------------+-----------+---------+------------+ -| resource-id | openstack | user | project | -+-------------+-----------+---------+------------+ -''', sys.stdout.getvalue()) - - -class ShellEventTypeListCommandTest(utils.BaseTestCase): - - EVENT_TYPE = { - "event_type": "test_event" - } - - def setUp(self): - super(ShellEventTypeListCommandTest, self).setUp() - self.cc = mock.Mock() - self.cc.event_types.list = mock.Mock() - self.args = mock.Mock() - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_sample_show(self): - event_type = event_types.EventType(mock.Mock(), self.EVENT_TYPE) - self.cc.event_types.list.return_value = [event_type] - - ceilometer_shell.do_event_type_list(self.cc, self.args) - self.cc.event_types.list.assert_called_once_with() - - self.assertEqual('''\ -+------------+ -| Event Type | -+------------+ -| test_event | -+------------+ -''', sys.stdout.getvalue()) - - -class ShellTraitsListCommandTest(utils.BaseTestCase): - - TRAIT = { - "name": "test", - "value": "test", - "type": "string", - } - - def setUp(self): - super(ShellTraitsListCommandTest, self).setUp() - self.cc = mock.Mock() - self.cc.traits.list = mock.Mock() - self.args = mock.Mock() - self.args.event_type = "test" - self.args.trait_name = "test" - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_trait_list(self): - trait = traits.Trait(mock.Mock(), self.TRAIT) - self.cc.traits.list.return_value = [trait] - - ceilometer_shell.do_trait_list(self.cc, self.args) - self.cc.traits.list.assert_called_once_with(self.args.event_type, - self.args.trait_name) - - self.assertEqual('''\ -+------------+-------+-----------+ -| Trait Name | Value | Data Type | -+------------+-------+-----------+ -| test | test | string | -+------------+-------+-----------+ -''', sys.stdout.getvalue()) - - -class ShellTraitsDescriptionListCommandTest(utils.BaseTestCase): - - TRAIT_DESCRIPTION = { - "name": "test", - "type": "string", - } - - def setUp(self): - super(ShellTraitsDescriptionListCommandTest, self).setUp() - self.cc = mock.Mock() - self.cc.trait_descriptions.list = mock.Mock() - self.args = mock.Mock() - self.args.event_type = "test" - - @mock.patch('sys.stdout', new=six.StringIO()) - def test_traits_description_list(self): - trait_desc = trait_descriptions.TraitDescription( - mock.Mock(), self.TRAIT_DESCRIPTION) - self.cc.trait_descriptions.list.return_value = [trait_desc] - - ceilometer_shell.do_trait_description_list(self.cc, self.args) - self.cc.trait_descriptions.list.assert_called_once_with( - self.args.event_type) - - self.assertEqual('''\ -+------------+-----------+ -| Trait Name | Data Type | -+------------+-----------+ -| test | string | -+------------+-----------+ -''', sys.stdout.getvalue()) diff --git a/ceilometerclient/tests/unit/v2/test_statistics.py b/ceilometerclient/tests/unit/v2/test_statistics.py deleted file mode 100644 index b7924cc..0000000 --- a/ceilometerclient/tests/unit/v2/test_statistics.py +++ /dev/null @@ -1,224 +0,0 @@ -# Copyright 2012 OpenStack Foundation -# 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. -from ceilometerclient.apiclient import client -from ceilometerclient.apiclient import fake_client -from ceilometerclient.tests.unit import utils -import ceilometerclient.v2.statistics - -base_url = '/v2/meters/instance/statistics' -qry = ('q.field=resource_id&q.field=source&q.op=&q.op=' - '&q.type=&q.type=&q.value=foo&q.value=bar') -period = '&period=60' -groupby = '&groupby=resource_id' -aggregate_query = ("aggregate.func=cardinality&aggregate.param=resource_id" - "&aggregate.func=count") -samples = [ - {u'count': 135, - u'duration_start': u'2013-02-04T10:51:42', - u'min': 1.0, - u'max': 1.0, - u'duration_end': - u'2013-02-05T15:46:09', - u'duration': 1734.0, - u'avg': 1.0, - u'sum': 135.0}, -] -groupby_samples = [ - {u'count': 135, - u'duration_start': u'2013-02-04T10:51:42', - u'min': 1.0, - u'max': 1.0, - u'duration_end': - u'2013-02-05T15:46:09', - u'duration': 1734.0, - u'avg': 1.0, - u'sum': 135.0, - u'groupby': {u'resource_id': u'foo'} - }, - {u'count': 12, - u'duration_start': u'2013-02-04T10:51:42', - u'min': 1.0, - u'max': 1.0, - u'duration_end': - u'2013-02-05T15:46:09', - u'duration': 1734.0, - u'avg': 1.0, - u'sum': 12.0, - u'groupby': {u'resource_id': u'bar'} - }, -] -aggregate_samples = [ - {u'aggregate': {u'cardinality/resource_id': 4.0, u'count': 2.0}, - u'count': 2, - u'duration': 0.442451, - u'duration_end': u'2014-03-12T14:00:21.774154', - u'duration_start': u'2014-03-12T14:00:21.331703', - u'groupby': None, - u'period': 0, - u'period_end': u'2014-03-12T14:00:21.774154', - u'period_start': u'2014-03-12T14:00:21.331703', - u'unit': u'instance', - }, -] -fixtures = { - base_url: - { - 'GET': ( - {}, - samples - ), - }, - '%s?%s' % (base_url, qry): - { - 'GET': ( - {}, - samples - ), - }, - '%s?%s%s' % (base_url, qry, period): - { - 'GET': ( - {}, - samples - ), - }, - '%s?%s%s' % (base_url, qry, groupby): - { - 'GET': ( - {}, - groupby_samples - ), - }, - '%s?%s' % (base_url, aggregate_query): - { - 'GET': ( - {}, - aggregate_samples - ), - } -} - - -class StatisticsManagerTest(utils.BaseTestCase): - - def setUp(self): - super(StatisticsManagerTest, self).setUp() - self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) - self.api = client.BaseClient(self.http_client) - self.mgr = ceilometerclient.v2.statistics.StatisticsManager(self.api) - - def test_list_by_meter_name(self): - stats = list(self.mgr.list(meter_name='instance')) - expect = [ - 'GET', '/v2/meters/instance/statistics' - ] - self.http_client.assert_called(*expect) - self.assertEqual(1, len(stats)) - self.assertEqual(135, stats[0].count) - - def test_list_by_meter_name_extended(self): - stats = list(self.mgr.list(meter_name='instance', - q=[ - {"field": "resource_id", - "value": "foo"}, - {"field": "source", - "value": "bar"}, - ])) - expect = [ - 'GET', '%s?%s' % (base_url, qry) - ] - self.http_client.assert_called(*expect) - self.assertEqual(1, len(stats)) - self.assertEqual(135, stats[0].count) - - def test_list_by_meter_name_with_period(self): - stats = list(self.mgr.list(meter_name='instance', - q=[ - {"field": "resource_id", - "value": "foo"}, - {"field": "source", - "value": "bar"}, - ], - period=60)) - expect = [ - 'GET', '%s?%s%s' % (base_url, qry, period) - ] - self.http_client.assert_called(*expect) - self.assertEqual(1, len(stats)) - self.assertEqual(135, stats[0].count) - - def test_list_by_meter_name_with_groupby(self): - stats = list(self.mgr.list(meter_name='instance', - q=[ - {"field": "resource_id", - "value": "foo"}, - {"field": "source", - "value": "bar"}, - ], - groupby=['resource_id'])) - expect = [ - 'GET', - '%s?%s%s' % (base_url, qry, groupby) - ] - self.http_client.assert_called(*expect) - self.assertEqual(2, len(stats)) - self.assertEqual(135, stats[0].count) - self.assertEqual(12, stats[1].count) - self.assertEqual('foo', stats[0].groupby.get('resource_id')) - self.assertEqual('bar', stats[1].groupby.get('resource_id')) - - def test_list_by_meter_name_with_groupby_as_str(self): - stats = list(self.mgr.list(meter_name='instance', - q=[ - {"field": "resource_id", - "value": "foo"}, - {"field": "source", - "value": "bar"}, - ], - groupby='resource_id')) - expect = [ - 'GET', - '%s?%s%s' % (base_url, qry, groupby) - ] - self.http_client.assert_called(*expect) - self.assertEqual(2, len(stats)) - self.assertEqual(135, stats[0].count) - self.assertEqual(12, stats[1].count) - self.assertEqual('foo', stats[0].groupby.get('resource_id')) - self.assertEqual('bar', stats[1].groupby.get('resource_id')) - - def test_list_by_meter_name_with_aggregates(self): - aggregates = [ - { - 'func': 'count', - }, - { - 'func': 'cardinality', - 'param': 'resource_id', - }, - ] - stats = list(self.mgr.list(meter_name='instance', - aggregates=aggregates)) - expect = [ - 'GET', - '%s?%s' % (base_url, aggregate_query) - ] - self.http_client.assert_called(*expect) - self.assertEqual(1, len(stats)) - self.assertEqual(2, stats[0].count) - self.assertEqual(2.0, stats[0].aggregate.get('count')) - self.assertEqual(4.0, stats[0].aggregate.get( - 'cardinality/resource_id', - )) diff --git a/ceilometerclient/tests/unit/v2/test_trait_descriptions.py b/ceilometerclient/tests/unit/v2/test_trait_descriptions.py deleted file mode 100644 index cd3e4fe..0000000 --- a/ceilometerclient/tests/unit/v2/test_trait_descriptions.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright 2014 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. -from ceilometerclient.apiclient import client -from ceilometerclient.apiclient import fake_client -from ceilometerclient.tests.unit import utils -import ceilometerclient.v2.trait_descriptions - - -fixtures = { - '/v2/event_types/Foo/traits': { - 'GET': ( - {}, - [ - {'name': 'trait_1', 'type': 'string'}, - {'name': 'trait_2', 'type': 'integer'}, - {'name': 'trait_3', 'type': 'datetime'} - ] - ), - } -} - - -class TraitDescriptionManagerTest(utils.BaseTestCase): - - def setUp(self): - super(TraitDescriptionManagerTest, self).setUp() - self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) - self.api = client.BaseClient(self.http_client) - self.mgr = (ceilometerclient.v2.trait_descriptions. - TraitDescriptionManager(self.api)) - - def test_list(self): - trait_descriptions = list(self.mgr.list('Foo')) - expect = [ - 'GET', '/v2/event_types/Foo/traits' - ] - self.http_client.assert_called(*expect) - self.assertEqual(3, len(trait_descriptions)) - for i, vals in enumerate([('trait_1', 'string'), - ('trait_2', 'integer'), - ('trait_3', 'datetime')]): - - name, type = vals - self.assertEqual(trait_descriptions[i].name, name) - self.assertEqual(trait_descriptions[i].type, type) diff --git a/ceilometerclient/tests/unit/v2/test_traits.py b/ceilometerclient/tests/unit/v2/test_traits.py deleted file mode 100644 index 351760b..0000000 --- a/ceilometerclient/tests/unit/v2/test_traits.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2014 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. -from ceilometerclient.apiclient import client -from ceilometerclient.apiclient import fake_client -from ceilometerclient.tests.unit import utils -import ceilometerclient.v2.traits - - -fixtures = { - '/v2/event_types/Foo/traits/trait_1': { - 'GET': ( - {}, - [ - {'name': 'trait_1', - 'type': 'datetime', - 'value': '2014-01-07T17:22:10.925553'}, - {'name': 'trait_1', - 'type': 'datetime', - 'value': '2014-01-07T17:23:10.925553'} - ] - ), - } -} - - -class TraitManagerTest(utils.BaseTestCase): - - def setUp(self): - super(TraitManagerTest, self).setUp() - self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) - self.api = client.BaseClient(self.http_client) - self.mgr = ceilometerclient.v2.traits.TraitManager(self.api) - - def test_list(self): - traits = list(self.mgr.list('Foo', 'trait_1')) - expect = [ - 'GET', '/v2/event_types/Foo/traits/trait_1' - ] - self.http_client.assert_called(*expect) - self.assertEqual(2, len(traits)) - for i, vals in enumerate([('trait_1', - 'datetime', - '2014-01-07T17:22:10.925553'), - ('trait_1', - 'datetime', - '2014-01-07T17:23:10.925553')]): - - name, type, value = vals - self.assertEqual(traits[i].name, name) - self.assertEqual(traits[i].type, type) - self.assertEqual(traits[i].value, value) diff --git a/ceilometerclient/v2/__init__.py b/ceilometerclient/v2/__init__.py deleted file mode 100644 index e8f350d..0000000 --- a/ceilometerclient/v2/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright 2012 OpenStack Foundation -# 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. - -from ceilometerclient.v2.client import Client # noqa diff --git a/ceilometerclient/v2/alarms.py b/ceilometerclient/v2/alarms.py deleted file mode 100644 index f669624..0000000 --- a/ceilometerclient/v2/alarms.py +++ /dev/null @@ -1,185 +0,0 @@ -# -# Copyright 2013 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. - -import warnings - -from ceilometerclient.common import base -from ceilometerclient.common import utils -from ceilometerclient import exc -from ceilometerclient.v2 import options - - -UPDATABLE_ATTRIBUTES = [ - 'name', - 'description', - 'type', - 'state', - 'severity', - 'enabled', - 'alarm_actions', - 'ok_actions', - 'insufficient_data_actions', - 'repeat_actions', - 'project_id', - 'user_id' -] -CREATION_ATTRIBUTES = UPDATABLE_ATTRIBUTES + ['time_constraints'] - - -class Alarm(base.Resource): - def __repr__(self): - return "" % self._info - - def __getattr__(self, k): - # Alias to have the Alarm client object - # that look like the Alarm storage object - if k == 'rule': - k = '%s_rule' % self.type - if k == 'id': - return self.alarm_id - return super(Alarm, self).__getattr__(k) - - def delete(self): - return self.manager.delete(self.alarm_id) - - def get_state(self): - state = self.manager.get_state(self.alarm_id) - return state.get('alarm') - - -class AlarmChange(base.Resource): - def __repr__(self): - return "" % self._info - - def __getattr__(self, k): - return super(AlarmChange, self).__getattr__(k) - - -class AlarmManager(base.Manager): - resource_class = Alarm - - def _path(self, id=None): - return '/v2/alarms/%s' % id if id else '/v2/alarms' - - def list(self, q=None): - return self._list(options.build_url(self._path(), q)) - - def get(self, alarm_id): - try: - return self._list(self._path(alarm_id), expect_single=True)[0] - except IndexError: - return None - - except exc.HTTPNotFound: - # When we try to get a deleted alarm, or - # when an alarm doesn't exist, HTTPNotFound exception occurs. - # Since scenario tests at the time of cleanUp() will not know - # how to handle it, we only return None. - return None - - @classmethod - def _compat_legacy_alarm_kwargs(cls, kwargs, create=False): - cls._compat_counter_rename_kwargs(kwargs, create) - cls._compat_alarm_before_rule_type_kwargs(kwargs, create) - - @staticmethod - def _compat_counter_rename_kwargs(kwargs, create=False): - # NOTE(jd) Compatibility with Havana-2 API - if 'counter_name' in kwargs: - warnings.warn("counter_name has been renamed to meter_name", - DeprecationWarning) - kwargs['meter_name'] = kwargs['counter_name'] - - @staticmethod - def _compat_alarm_before_rule_type_kwargs(kwargs, create=False): - # NOTE(sileht) Compatibility with Havana-3 API - if create and 'type' not in kwargs: - warnings.warn("alarm without type set is deprecated", - DeprecationWarning) - kwargs['type'] = 'threshold' - - for field in ['period', 'evaluation_periods', 'threshold', - 'statistic', 'comparison_operator', 'meter_name']: - if field in kwargs: - kwargs.setdefault('threshold_rule', {})[field] = kwargs[field] - del kwargs[field] - - if 'matching_metadata' in kwargs: - query = [] - for key in kwargs['matching_metadata']: - query.append({'field': key, - 'op': 'eq', - 'value': kwargs['matching_metadata'][key]}) - del kwargs['matching_metadata'] - kwargs['threshold_rule']['query'] = query - - @staticmethod - def _merge_time_constraints(existing_tcs, kwargs): - new_tcs = kwargs.get('time_constraints', []) - if not existing_tcs: - updated_tcs = new_tcs - else: - updated_tcs = [dict(tc) for tc in existing_tcs] - for tc in new_tcs: - for i, old_tc in enumerate(updated_tcs): - # if names match, merge - if old_tc['name'] == tc.get('name'): - utils.merge_nested_dict(updated_tcs[i], tc) - break - else: - updated_tcs.append(tc) - tcs_to_remove = kwargs.get('remove_time_constraints', []) - for tc in updated_tcs: - if tc.get('name') in tcs_to_remove: - updated_tcs.remove(tc) - return updated_tcs - - def create(self, **kwargs): - self._compat_legacy_alarm_kwargs(kwargs, create=True) - new = dict((key, value) for (key, value) in kwargs.items() - if (key in CREATION_ATTRIBUTES - or key.endswith('_rule'))) - return self._create(self._path(), new) - - def update(self, alarm_id, **kwargs): - self._compat_legacy_alarm_kwargs(kwargs) - alarm = self.get(alarm_id) - if alarm is None: - raise exc.CommandError('Alarm not found: %s' % alarm_id) - updated = alarm.to_dict() - updated['time_constraints'] = self._merge_time_constraints( - updated.get('time_constraints', []), kwargs) - kwargs = dict((k, v) for k, v in kwargs.items() - if k in updated and (k in UPDATABLE_ATTRIBUTES - or k.endswith('_rule'))) - utils.merge_nested_dict(updated, kwargs, depth=1) - return self._update(self._path(alarm_id), updated) - - def delete(self, alarm_id): - return self._delete(self._path(alarm_id)) - - def set_state(self, alarm_id, state): - body = self.api.put("%s/state" % self._path(alarm_id), - json=state).json() - return body - - def get_state(self, alarm_id): - body = self.api.get("%s/state" % self._path(alarm_id)).json() - return body - - def get_history(self, alarm_id, q=None): - path = '%s/history' % self._path(alarm_id) - url = options.build_url(path, q) - return self._list(url, obj_class=AlarmChange) diff --git a/ceilometerclient/v2/capabilities.py b/ceilometerclient/v2/capabilities.py deleted file mode 100644 index 9a0f528..0000000 --- a/ceilometerclient/v2/capabilities.py +++ /dev/null @@ -1,29 +0,0 @@ -# -# Copyright 2014 Huawei, 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 ceilometerclient.common import base - - -class Capabilities(base.Resource): - def __repr__(self): - return "" % self._info - - -class CapabilitiesManager(base.Manager): - resource_class = Capabilities - - def get(self): - path = "/v2/capabilities" - return Capabilities(self, self.api.get(path).json()) diff --git a/ceilometerclient/v2/client.py b/ceilometerclient/v2/client.py deleted file mode 100644 index e61e43f..0000000 --- a/ceilometerclient/v2/client.py +++ /dev/null @@ -1,132 +0,0 @@ -# Copyright Ericsson AB 2014. 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 copy - -import requests - -from ceilometerclient import client as ceiloclient -from ceilometerclient.v2 import alarms -from ceilometerclient.v2 import capabilities -from ceilometerclient.v2 import event_types -from ceilometerclient.v2 import events -from ceilometerclient.v2 import meters -from ceilometerclient.v2 import query -from ceilometerclient.v2 import resources -from ceilometerclient.v2 import samples -from ceilometerclient.v2 import statistics -from ceilometerclient.v2 import trait_descriptions -from ceilometerclient.v2 import traits -from keystoneauth1 import exceptions as ka_exc - - -class Client(object): - """Client for the Ceilometer v2 API. - - :param session: a keystoneauth session object - :type session: keystoneauth1.session.Session - :param str service_type: The default service_type for URL discovery - :param str service_name: The default service_name for URL discovery - :param str interface: The default interface for URL discovery - (Default: public) - :param str region_name: The default region_name for URL discovery - :param str endpoint_override: Always use this endpoint URL for requests - for this ceiloclient - :param auth: An auth plugin to use instead of the session one - :type auth: keystoneauth1.plugin.BaseAuthPlugin - :param str user_agent: The User-Agent string to set - (Default is python-ceilometer-client) - :param int connect_retries: the maximum number of retries that should be - attempted for connection errors - :param logger: A logging object - :type logger: logging.Logger - """ - - def __init__(self, *args, **kwargs): - """Initialize a new client for the Ceilometer v2 API.""" - - if not kwargs.get('auth_plugin') and not kwargs.get('session'): - kwargs['auth_plugin'] = ceiloclient.get_auth_plugin(*args, - **kwargs) - - self.auth_plugin = kwargs.get('auth_plugin') - - self.http_client = ceiloclient._construct_http_client(**kwargs) - self.alarm_client = self._get_redirect_client( - 'alarming', 'aodh', **kwargs) - aodh_enabled = self.alarm_client is not None - if not aodh_enabled: - self.alarm_client = self.http_client - self.event_client = self._get_redirect_client( - 'event', 'panko', **kwargs) - panko_enabled = self.event_client is not None - if not panko_enabled: - self.event_client = self.http_client - - self.meters = meters.MeterManager(self.http_client) - self.samples = samples.OldSampleManager(self.http_client) - self.new_samples = samples.SampleManager(self.http_client) - self.statistics = statistics.StatisticsManager(self.http_client) - self.resources = resources.ResourceManager(self.http_client) - self.alarms = alarms.AlarmManager(self.alarm_client) - self.events = events.EventManager(self.event_client) - self.event_types = event_types.EventTypeManager(self.event_client) - self.traits = traits.TraitManager(self.event_client) - self.trait_descriptions = trait_descriptions.\ - TraitDescriptionManager(self.event_client) - - self.query_samples = query.QuerySamplesManager( - self.http_client) - self.query_alarms = query.QueryAlarmsManager(self.alarm_client) - self.query_alarm_history = query.QueryAlarmHistoryManager( - self.alarm_client) - self.capabilities = capabilities.CapabilitiesManager(self.http_client) - - @staticmethod - def _get_redirect_client(new_service_type, new_service, **ceilo_kwargs): - """Get client for new service manager to redirect to.""" - # NOTE(sileht): the auth_plugin/keystone session cannot be copied - # because they rely on threading module. - auth_plugin = ceilo_kwargs.pop('auth_plugin', None) - session = ceilo_kwargs.pop('session', None) - - kwargs = copy.deepcopy(ceilo_kwargs) - kwargs["service_type"] = new_service_type - endpoint = ceilo_kwargs.get('%s_endpoint' % new_service) - - if session: - # keystone session can be shared between client - ceilo_kwargs['session'] = kwargs['session'] = session - if endpoint: - kwargs['endpoint_override'] = endpoint - elif auth_plugin and kwargs.get('auth_url'): - ceilo_kwargs['auth_plugin'] = auth_plugin - kwargs.pop('endpoint', None) - kwargs['auth_plugin'] = ceiloclient.get_auth_plugin( - endpoint, **kwargs) - else: - # Users may just provide ceilometer endpoint and token, and no - # auth_url, in this case, we need 'aodh_endpoint' also to be - # provided, otherwise we cannot get aodh endpoint from - # keystone, and assume aodh is unavailable. Same applies to panko. - return None - - try: - # NOTE(sileht): try to use redirect - c = ceiloclient._construct_http_client(**kwargs) - c.get("/") - return c - except ka_exc.EndpointNotFound: - return None - except requests.exceptions.ConnectionError: - return None diff --git a/ceilometerclient/v2/event_types.py b/ceilometerclient/v2/event_types.py deleted file mode 100644 index 0f44931..0000000 --- a/ceilometerclient/v2/event_types.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright 2014 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. - -from ceilometerclient.common import base - - -class EventType(base.Resource): - def __repr__(self): - return "" % self._info - - -def object_class_str(mgr, value, loaded): - return EventType(mgr, {"event_type": value}, loaded) - - -class EventTypeManager(base.Manager): - - def list(self): - return self._list('/v2/event_types', obj_class=object_class_str) diff --git a/ceilometerclient/v2/events.py b/ceilometerclient/v2/events.py deleted file mode 100644 index 6cd46d2..0000000 --- a/ceilometerclient/v2/events.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2014 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. - -from ceilometerclient.common import base -from ceilometerclient.v2 import options - - -class Event(base.Resource): - def __repr__(self): - return "" % self._info - - def __getattr__(self, k): - if k == 'id': - return self.message_id - return super(Event, self).__getattr__(k) - - -class EventManager(base.Manager): - resource_class = Event - - def list(self, q=None, limit=None): - path = '/v2/events' - params = ['limit=%s' % limit] if limit else None - return self._list(options.build_url(path, q, params)) - - def get(self, message_id): - path = '/v2/events/%s' - try: - return self._list(path % message_id, expect_single=True)[0] - except IndexError: - return None diff --git a/ceilometerclient/v2/meters.py b/ceilometerclient/v2/meters.py deleted file mode 100644 index 015c04c..0000000 --- a/ceilometerclient/v2/meters.py +++ /dev/null @@ -1,38 +0,0 @@ -# -# Copyright 2013 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 ceilometerclient.common import base -from ceilometerclient.v2 import options - - -class Meter(base.Resource): - def __repr__(self): - return "" % self._info - - -class MeterManager(base.Manager): - resource_class = Meter - - def list(self, q=None, limit=None, unique=False): - path = '/v2/meters' - params = [] - - if limit: - params.append('limit=%s' % limit) - - if unique: - params.append('unique=%s' % str(unique)) - - return self._list(options.build_url(path, q, params)) diff --git a/ceilometerclient/v2/options.py b/ceilometerclient/v2/options.py deleted file mode 100644 index b586542..0000000 --- a/ceilometerclient/v2/options.py +++ /dev/null @@ -1,129 +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 re - -from six.moves import urllib - -OP_LOOKUP = {'!=': 'ne', - '>=': 'ge', - '<=': 'le', - '>': 'gt', - '<': 'lt', - '=': 'eq'} - -OP_LOOKUP_KEYS = '|'.join(sorted(OP_LOOKUP.keys(), key=len, reverse=True)) -OP_SPLIT_RE = re.compile(r'(%s)' % OP_LOOKUP_KEYS) - -DATA_TYPE_RE = re.compile(r'^(string|integer|float|datetime|boolean)(::)(.+)$') - - -def build_url(path, q, params=None): - """Convert list of dicts and a list of params to query url format. - - This will convert the following: - "[{field=this,op=le,value=34}, - {field=that,op=eq,value=foo,type=string}], - ['foo=bar','sna=fu']" - to: - "?q.field=this&q.field=that& - q.op=le&q.op=eq& - q.type=&q.type=string& - q.value=34&q.value=foo& - foo=bar&sna=fu" - """ - if q: - query_params = {'q.field': [], - 'q.value': [], - 'q.op': [], - 'q.type': []} - - for query in q: - for name in ['field', 'op', 'value', 'type']: - query_params['q.%s' % name].append(query.get(name, '')) - - # Transform the dict to a sequence of two-element tuples in fixed - # order, then the encoded string will be consistent in Python 2&3. - new_qparams = sorted(query_params.items(), key=lambda x: x[0]) - path += "?" + urllib.parse.urlencode(new_qparams, doseq=True) - - if params: - for p in params: - path += '&%s' % p - elif params: - path += '?%s' % params[0] - for p in params[1:]: - path += '&%s' % p - return path - - -def cli_to_array(cli_query): - """Convert CLI list of queries to the Python API format. - - This will convert the following: - "this<=34;that=string::foo" - to - "[{field=this,op=le,value=34,type=''}, - {field=that,op=eq,value=foo,type=string}]" - - """ - - if cli_query is None: - return None - - def split_by_op(query): - """Split a single query string to field, operator, value.""" - - def _value_error(message): - raise ValueError('invalid query %(query)s: missing %(message)s' % - {'query': query, 'message': message}) - - try: - field, operator, value = OP_SPLIT_RE.split(query, maxsplit=1) - except ValueError: - _value_error('operator') - - if not len(field): - _value_error('field') - - if not len(value): - _value_error('value') - - return field.strip(), operator, value.strip() - - def split_by_data_type(query_value): - frags = DATA_TYPE_RE.match(query_value) - - # The second match is the separator. Return a list without it if - # a type identifier was found. - return frags.group(1, 3) if frags else None - - opts = [] - queries = cli_query.split(';') - for q in queries: - query = split_by_op(q) - opt = {} - opt['field'] = query[0] - opt['op'] = OP_LOOKUP[query[1]] - - # Allow the data type of the value to be specified via ::, - # where type can be one of integer, string, float, datetime, boolean - value_frags = split_by_data_type(query[2]) - if not value_frags: - opt['value'] = query[2] - opt['type'] = '' - else: - opt['type'] = value_frags[0] - opt['value'] = value_frags[1] - opts.append(opt) - return opts diff --git a/ceilometerclient/v2/query.py b/ceilometerclient/v2/query.py deleted file mode 100644 index 9d540b2..0000000 --- a/ceilometerclient/v2/query.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright Ericsson AB 2014. 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. - -from ceilometerclient.common import base -from ceilometerclient.v2 import alarms -from ceilometerclient.v2 import samples - - -class QueryManager(base.Manager): - path_suffix = None - - def query(self, filter=None, orderby=None, limit=None): - query = {} - if filter: - query["filter"] = filter - if orderby: - query["orderby"] = orderby - if limit: - query["limit"] = limit - - url = '/v2/query%s' % self.path_suffix - - body = self.api.post(url, json=query).json() - - if body: - return [self.resource_class(self, b) for b in body] - else: - return [] - - -class QuerySamplesManager(QueryManager): - resource_class = samples.Sample - path_suffix = '/samples' - - -class QueryAlarmsManager(QueryManager): - resource_class = alarms.Alarm - path_suffix = '/alarms' - - -class QueryAlarmHistoryManager(QueryManager): - resource_class = alarms.AlarmChange - path_suffix = '/alarms/history' diff --git a/ceilometerclient/v2/resources.py b/ceilometerclient/v2/resources.py deleted file mode 100644 index d516ee1..0000000 --- a/ceilometerclient/v2/resources.py +++ /dev/null @@ -1,45 +0,0 @@ -# -# Copyright 2013 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 ceilometerclient.common import base -from ceilometerclient.v2 import options - - -class Resource(base.Resource): - def __repr__(self): - return "" % self._info - - def __getattr__(self, k): - if k == 'id': - return self.resource_id - return super(Resource, self).__getattr__(k) - - -class ResourceManager(base.Manager): - resource_class = Resource - - def list(self, q=None, links=None, limit=None): - path = '/v2/resources' - params = ['meter_links=%d' % (1 if links else 0)] - if limit: - params.append('limit=%s' % limit) - return self._list(options.build_url(path, q, params)) - - def get(self, resource_id): - path = '/v2/resources/%s' % resource_id - try: - return self._list(path, expect_single=True)[0] - except IndexError: - return None diff --git a/ceilometerclient/v2/samples.py b/ceilometerclient/v2/samples.py deleted file mode 100644 index 8752a0d..0000000 --- a/ceilometerclient/v2/samples.py +++ /dev/null @@ -1,106 +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 ceilometerclient.common import base -from ceilometerclient.v2 import options - -CREATION_ATTRIBUTES = ('source', - 'counter_name', - 'counter_type', - 'counter_unit', - 'counter_volume', - 'user_id', - 'project_id', - 'resource_id', - 'timestamp', - 'resource_metadata') - - -class OldSample(base.Resource): - """Represents API v2 OldSample object. - - Model definition: - https://docs.openstack.org/ceilometer/latest/webapi/v2.html#OldSample - """ - def __repr__(self): - return "" % self._info - - -class OldSampleManager(base.Manager): - resource_class = OldSample - - @staticmethod - def _path(counter_name=None): - return '/v2/meters/%s' % counter_name if counter_name else '/v2/meters' - - def list(self, meter_name=None, q=None, limit=None): - path = self._path(counter_name=meter_name) - params = ['limit=%s' % str(limit)] if limit else None - return self._list(options.build_url(path, q, params)) - - def create(self, **kwargs): - direct = kwargs.pop('direct', False) - new = dict((key, value) for (key, value) in kwargs.items() - if key in CREATION_ATTRIBUTES) - url = self._path(counter_name=kwargs['counter_name'])+( - '?direct=%s' % (str(direct)) if direct else '') - body = self.api.post(url, json=[new]).json() - if body: - return [OldSample(self, b) for b in body] - - def create_list(self, sample_list=None, **kwargs): - sample_dict = {} - direct = kwargs.pop('direct', False) - - for sample_body in sample_list: - sample = dict((key, value) for (key, value) in sample_body.items() - if key in CREATION_ATTRIBUTES) - sample_dict.setdefault( - sample_body["counter_name"], [] - ).append(sample) - - sample_return_list = [] - - for (counter_name, sample_body) in sample_dict.items(): - url = self._path(counter_name=counter_name)+( - '?direct=%s' % (str(direct)) if direct else '') - body = self.api.post(url, json=sample_body).json() - - if body: - sample_return_list.extend([OldSample(self, b) for b in body]) - return sample_return_list - - -class Sample(base.Resource): - """Represents API v2 Sample object. - - Model definition: - https://docs.openstack.org/ceilometer/latest/webapi/v2.html#Sample - """ - def __repr__(self): - return "" % self._info - - -class SampleManager(base.Manager): - resource_class = Sample - - def list(self, q=None, limit=None): - params = ['limit=%s' % str(limit)] if limit else None - return self._list(options.build_url("/v2/samples", q, params)) - - def get(self, sample_id): - path = "/v2/samples/" + sample_id - try: - return self._list(path, expect_single=True)[0] - except IndexError: - return None diff --git a/ceilometerclient/v2/shell.py b/ceilometerclient/v2/shell.py deleted file mode 100644 index ca9e264..0000000 --- a/ceilometerclient/v2/shell.py +++ /dev/null @@ -1,1285 +0,0 @@ -# -# Copyright 2013-2016 Red Hat, Inc -# Copyright Ericsson AB 2014. 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 argparse -import functools -import json -import warnings - -from oslo_serialization import jsonutils -from oslo_utils import strutils -import six -from six import moves - -from ceilometerclient.common import utils -from ceilometerclient import exc -from ceilometerclient.v2 import options - - -ALARM_STATES = ['ok', 'alarm', 'insufficient data'] -ALARM_SEVERITY = ['low', 'moderate', 'critical'] -ALARM_OPERATORS = ['lt', 'le', 'eq', 'ne', 'ge', 'gt'] -ALARM_COMBINATION_OPERATORS = ['and', 'or'] -STATISTICS = ['max', 'min', 'avg', 'sum', 'count'] -GNOCCHI_AGGREGATION = ['last', 'min', 'median', 'sum', - 'std', 'first', 'mean', 'count', - 'moving-average', 'max'] -GNOCCHI_AGGREGATION.extend(['%spct' % num for num in moves.xrange(1, 100)]) - -AGGREGATES = {'avg': 'Avg', - 'count': 'Count', - 'max': 'Max', - 'min': 'Min', - 'sum': 'Sum', - 'stddev': 'Standard deviation', - 'cardinality': 'Cardinality'} -OPERATORS_STRING = dict(gt='>', ge='>=', - lt='<', le="<=", - eq='==', ne='!=') -ORDER_DIRECTIONS = ['asc', 'desc'] -COMPLEX_OPERATORS = ['and', 'or'] -SIMPLE_OPERATORS = ["=", "!=", "<", "<=", '>', '>='] - -DEFAULT_API_LIMIT = ('API server limits result to ' - 'rows if no limit provided. Option is configured in ' - 'ceilometer.conf [api] group') - - -class NotEmptyAction(argparse.Action): - def __call__(self, parser, namespace, values, option_string=None): - values = values or getattr(namespace, self.dest) - if not values or values.isspace(): - raise exc.CommandError('%s should not be empty' % self.dest) - setattr(namespace, self.dest, values) - - -def obsoleted_by(new_dest): - class ObsoletedByAction(argparse.Action): - def __call__(self, parser, namespace, values, option_string=None): - old_dest = option_string or self.dest - print('%s is obsolete! See help for more details.' % old_dest) - setattr(namespace, new_dest, values) - return ObsoletedByAction - - -@utils.arg('-q', '--query', metavar='', - help='key[op]data_type::value; list. data_type is optional, ' - 'but if supplied must be string, integer, float, or boolean.') -@utils.arg('-m', '--meter', metavar='', required=True, - action=NotEmptyAction, help='Name of meter to list statistics for.') -@utils.arg('-p', '--period', metavar='', - help='Period in seconds over which to group samples.') -@utils.arg('-g', '--groupby', metavar='', action='append', - help='Field for group by.') -@utils.arg('-a', '--aggregate', metavar='[<-]', action='append', - default=[], help=('Function for data aggregation. ' - 'Available aggregates are: ' - '%s.' % ", ".join(AGGREGATES.keys()))) -def do_statistics(cc, args): - """List the statistics for a meter.""" - aggregates = [] - for a in args.aggregate: - aggregates.append(dict(zip(('func', 'param'), a.split("<-")))) - api_args = {'meter_name': args.meter, - 'q': options.cli_to_array(args.query), - 'period': args.period, - 'groupby': args.groupby, - 'aggregates': aggregates} - try: - statistics = cc.statistics.list(**api_args) - except exc.HTTPNotFound: - raise exc.CommandError('Samples not found: %s' % args.meter) - else: - fields_display = {'duration': 'Duration', - 'duration_end': 'Duration End', - 'duration_start': 'Duration Start', - 'period': 'Period', - 'period_end': 'Period End', - 'period_start': 'Period Start', - 'groupby': 'Group By'} - fields_display.update(AGGREGATES) - fields = ['period', 'period_start', 'period_end'] - if args.groupby: - fields.append('groupby') - if args.aggregate: - for a in aggregates: - if 'param' in a: - fields.append("%(func)s/%(param)s" % a) - else: - fields.append(a['func']) - for stat in statistics: - stat.__dict__.update(stat.aggregate) - else: - fields.extend(['max', 'min', 'avg', 'sum', 'count']) - fields.extend(['duration', 'duration_start', 'duration_end']) - cols = [fields_display.get(f, f) for f in fields] - utils.print_list(statistics, fields, cols) - - -@utils.arg('-q', '--query', metavar='', - help='key[op]data_type::value; list. data_type is optional, ' - 'but if supplied must be string, integer, float, or boolean.') -@utils.arg('-m', '--meter', metavar='', - action=NotEmptyAction, help='Name of meter to show samples for.') -@utils.arg('-l', '--limit', metavar='', - help='Maximum number of samples to return. %s' % DEFAULT_API_LIMIT) -def do_sample_list(cc, args): - """List the samples (return OldSample objects if -m/--meter is set).""" - if not args.meter: - return _do_sample_list(cc, args) - else: - return _do_old_sample_list(cc, args) - - -def _do_old_sample_list(cc, args): - fields = {'meter_name': args.meter, - 'q': options.cli_to_array(args.query), - 'limit': args.limit} - try: - samples = cc.samples.list(**fields) - except exc.HTTPNotFound: - raise exc.CommandError('Samples not found: %s' % args.meter) - else: - field_labels = ['Resource ID', 'Name', 'Type', 'Volume', 'Unit', - 'Timestamp'] - fields = ['resource_id', 'counter_name', 'counter_type', - 'counter_volume', 'counter_unit', 'timestamp'] - utils.print_list(samples, fields, field_labels, sortby=None) - - -def _do_sample_list(cc, args): - fields = { - 'q': options.cli_to_array(args.query), - 'limit': args.limit - } - samples = cc.new_samples.list(**fields) - field_labels = ['ID', 'Resource ID', 'Name', 'Type', 'Volume', 'Unit', - 'Timestamp'] - fields = ['id', 'resource_id', 'meter', 'type', 'volume', 'unit', - 'timestamp'] - utils.print_list(samples, fields, field_labels, sortby=None) - - -@utils.arg('sample_id', metavar='', action=NotEmptyAction, - help='ID (aka message ID) of the sample to show.') -def do_sample_show(cc, args): - '''Show a sample.''' - try: - sample = cc.new_samples.get(args.sample_id) - except exc.HTTPNotFound: - raise exc.CommandError('Sample not found: %s' % args.sample_id) - - fields = ['id', 'meter', 'volume', 'type', 'unit', 'source', - 'resource_id', 'user_id', 'project_id', - 'timestamp', 'recorded_at', 'metadata'] - data = dict((f, getattr(sample, f, '')) for f in fields) - utils.print_dict(data, wrap=72) - - -def _restore_shadowed_arg(shadowed, observed): - def wrapper(func): - @functools.wraps(func) - def wrapped(cc, args): - v = getattr(args, observed, None) - setattr(args, shadowed, v) - return func(cc, args) - return wrapped - return wrapper - - -@utils.arg('--project-id', metavar='', - dest='sample_project_id', - help='Tenant to associate with sample ' - '(configurable by admin users only).') -@utils.arg('--user-id', metavar='', - dest='sample_user_id', - help='User to associate with sample ' - '(configurable by admin users only).') -@utils.arg('-r', '--resource-id', metavar='', required=True, - help='ID of the resource.') -@utils.arg('-m', '--meter-name', metavar='', required=True, - action=NotEmptyAction, help='The meter name.') -@utils.arg('--meter-type', metavar='', required=True, - help='The meter type.') -@utils.arg('--meter-unit', metavar='', required=True, - help='The meter unit.') -@utils.arg('--sample-volume', metavar='', required=True, - help='The sample volume.') -@utils.arg('--resource-metadata', metavar='', - help='Resource metadata. Provided value should be a set of ' - 'key-value pairs e.g. {"key":"value"}.') -@utils.arg('--timestamp', metavar='', - help='The sample timestamp.') -@utils.arg('--direct', metavar='', default=False, - help='Post sample to storage directly.') -@_restore_shadowed_arg('project_id', 'sample_project_id') -@_restore_shadowed_arg('user_id', 'sample_user_id') -def do_sample_create(cc, args={}): - """Create a sample.""" - arg_to_field_mapping = { - 'meter_name': 'counter_name', - 'meter_unit': 'counter_unit', - 'meter_type': 'counter_type', - 'sample_volume': 'counter_volume', - } - fields = {} - for var in vars(args).items(): - k, v = var[0], var[1] - if v is not None: - if k == 'resource_metadata': - try: - fields[k] = json.loads(v) - except ValueError: - msg = ('Invalid resource metadata, it should be a json' - ' string, like: \'{"foo":"bar"}\'') - raise exc.CommandError(msg) - else: - fields[arg_to_field_mapping.get(k, k)] = v - sample = cc.samples.create(**fields) - fields = ['counter_name', 'user_id', 'resource_id', - 'timestamp', 'message_id', 'source', 'counter_unit', - 'counter_volume', 'project_id', 'resource_metadata', - 'counter_type'] - data = dict([(f.replace('counter_', ''), getattr(sample[0], f, '')) - for f in fields]) - utils.print_dict(data, wrap=72) - - -@utils.arg('-q', '--query', metavar='', - help='key[op]data_type::value; list. data_type is optional, ' - 'but if supplied must be string, integer, float, or boolean.') -@utils.arg('-l', '--limit', metavar='', - help='Maximum number of meters to return. %s' % DEFAULT_API_LIMIT) -@utils.arg('--unique', dest='unique', - metavar='{True|False}', - type=lambda v: strutils.bool_from_string(v, True), - help='Retrieves unique list of meters.') -def do_meter_list(cc, args={}): - """List the user's meters.""" - meters = cc.meters.list(q=options.cli_to_array(args.query), - limit=args.limit, unique=args.unique) - field_labels = ['Name', 'Type', 'Unit', 'Resource ID', 'User ID', - 'Project ID'] - fields = ['name', 'type', 'unit', 'resource_id', 'user_id', - 'project_id'] - utils.print_list(meters, fields, field_labels, - sortby=0) - - -@utils.arg('samples_list', metavar='', action=NotEmptyAction, - help='Json array with samples to create.') -@utils.arg('--direct', metavar='', default=False, - help='Post samples to storage directly.') -def do_sample_create_list(cc, args={}): - """Create a sample list.""" - sample_list_array = json.loads(args.samples_list) - samples = cc.samples.create_list(sample_list_array, direct=args.direct) - field_labels = ['Resource ID', 'Name', 'Type', 'Volume', 'Unit', - 'Timestamp'] - fields = ['resource_id', 'counter_name', 'counter_type', - 'counter_volume', 'counter_unit', 'timestamp'] - utils.print_list(samples, fields, field_labels, sortby=None) - - -def _display_alarm_list(alarms, sortby=None): - # omit action initially to keep output width sane - # (can switch over to vertical formatting when available from CLIFF) - field_labels = ['Alarm ID', 'Name', 'State', 'Severity', 'Enabled', - 'Continuous', 'Alarm condition', 'Time constraints'] - fields = ['alarm_id', 'name', 'state', 'severity', 'enabled', - 'repeat_actions', 'rule', 'time_constraints'] - utils.print_list( - alarms, fields, field_labels, - formatters={'rule': alarm_rule_formatter, - 'time_constraints': time_constraints_formatter_brief}, - sortby=sortby) - - -def _display_rule(type, rule): - if type == 'threshold': - return ('%(statistic)s(%(meter_name)s) %(comparison_operator)s ' - '%(threshold)s during %(evaluation_periods)s x %(period)ss' % - { - 'meter_name': rule['meter_name'], - 'threshold': rule['threshold'], - 'statistic': rule['statistic'], - 'evaluation_periods': rule['evaluation_periods'], - 'period': rule['period'], - 'comparison_operator': OPERATORS_STRING.get( - rule['comparison_operator']) - }) - elif type == 'combination': - return ('combinated states (%(operator)s) of %(alarms)s' % { - 'operator': rule['operator'].upper(), - 'alarms': ", ".join(rule['alarm_ids'])}) - else: - # just dump all - return "\n".join(["%s: %s" % (f, v) - for f, v in six.iteritems(rule)]) - - -def alarm_rule_formatter(alarm): - return _display_rule(alarm.type, alarm.rule) - - -def _display_time_constraints_brief(time_constraints): - if time_constraints: - return ', '.join('%(name)s at %(start)s %(timezone)s for %(duration)ss' - % { - 'name': tc['name'], - 'start': tc['start'], - 'duration': tc['duration'], - 'timezone': tc.get('timezone', '') - } - for tc in time_constraints) - else: - return 'None' - - -def time_constraints_formatter_brief(alarm): - return _display_time_constraints_brief(getattr(alarm, - 'time_constraints', - None)) - - -def _infer_type(detail): - if 'type' in detail: - return detail['type'] - elif 'meter_name' in detail['rule']: - return 'threshold' - elif 'alarms' in detail['rule']: - return 'combination' - else: - return 'unknown' - - -def alarm_change_detail_formatter(change): - detail = json.loads(change.detail) - fields = [] - if change.type == 'state transition': - fields.append('state: %s' % detail['state']) - elif change.type == 'creation' or change.type == 'deletion': - for k in ['name', 'description', 'type', 'rule', 'severity']: - if k == 'rule': - fields.append('rule: %s' % _display_rule(detail['type'], - detail[k])) - else: - fields.append('%s: %s' % (k, detail[k])) - if 'time_constraints' in detail: - fields.append('time_constraints: %s' % - _display_time_constraints_brief( - detail['time_constraints'])) - elif change.type == 'rule change': - for k, v in six.iteritems(detail): - if k == 'rule': - fields.append('rule: %s' % _display_rule(_infer_type(detail), - v)) - else: - fields.append('%s: %s' % (k, v)) - return '\n'.join(fields) - - -@utils.arg('-q', '--query', metavar='', - help='key[op]data_type::value; list. data_type is optional, ' - 'but if supplied must be string, integer, float, or boolean.') -def do_alarm_list(cc, args={}): - """List the user's alarms.""" - warnings.warn("Alarm commands are deprecated, please use aodhclient") - alarms = cc.alarms.list(q=options.cli_to_array(args.query)) - _display_alarm_list(alarms, sortby=0) - - -def alarm_query_formater(alarm): - qs = [] - for q in alarm.rule['query']: - qs.append('%s %s %s' % ( - q['field'], OPERATORS_STRING.get(q['op']), q['value'])) - return r' AND\n'.join(qs) - - -def time_constraints_formatter_full(alarm): - time_constraints = [] - for tc in alarm.time_constraints: - lines = [] - for k in ['name', 'description', 'start', 'duration', 'timezone']: - if k in tc and tc[k]: - lines.append(r'%s: %s' % (k, tc[k])) - time_constraints.append('{' + r',\n '.join(lines) + '}') - return '[' + r',\n '.join(time_constraints) + ']' - - -def _display_alarm(alarm): - fields = ['name', 'description', 'type', - 'state', 'severity', 'enabled', 'alarm_id', 'user_id', - 'project_id', 'alarm_actions', 'ok_actions', - 'insufficient_data_actions', 'repeat_actions'] - data = dict([(f, getattr(alarm, f, '')) for f in fields]) - data.update(alarm.rule) - if alarm.type == 'threshold': - data['query'] = alarm_query_formater(alarm) - if alarm.time_constraints: - data['time_constraints'] = time_constraints_formatter_full(alarm) - utils.print_dict(data, wrap=72) - - -@utils.arg('-a', '--alarm_id', metavar='', - action=obsoleted_by('alarm_id'), help=argparse.SUPPRESS, - dest='alarm_id_deprecated') -@utils.arg('alarm_id', metavar='', nargs='?', - action=NotEmptyAction, help='ID of the alarm to show.') -def do_alarm_show(cc, args={}): - """Show an alarm.""" - warnings.warn("Alarm commands are deprecated, please use aodhclient") - alarm = cc.alarms.get(args.alarm_id) - # alarm.get actually catches the HTTPNotFound exception and turns the - # result into None if the alarm wasn't found. - if alarm is None: - raise exc.CommandError('Alarm not found: %s' % args.alarm_id) - else: - _display_alarm(alarm) - - -def common_alarm_arguments(create=False): - def _wrapper(func): - @utils.arg('--name', metavar='', required=create, - help='Name of the alarm (must be unique per tenant).') - @utils.arg('--project-id', metavar='', - dest='alarm_project_id', - help='Tenant to associate with alarm ' - '(configurable by admin users only).') - @utils.arg('--user-id', metavar='', - dest='alarm_user_id', - help='User to associate with alarm ' - '(configurable by admin users only).') - @utils.arg('--description', metavar='', - help='Free text description of the alarm.') - @utils.arg('--state', metavar='', - help='State of the alarm, one of: ' + str(ALARM_STATES)) - @utils.arg('--severity', metavar='', - help='Severity of the alarm, one of: ' - + str(ALARM_SEVERITY)) - @utils.arg('--enabled', - type=lambda v: strutils.bool_from_string(v, True), - metavar='{True|False}', - help='True if alarm evaluation/actioning is enabled.') - @utils.arg('--alarm-action', dest='alarm_actions', - metavar='', action='append', default=None, - help=('URL to invoke when state transitions to alarm. ' - 'May be used multiple times.')) - @utils.arg('--ok-action', dest='ok_actions', - metavar='', action='append', default=None, - help=('URL to invoke when state transitions to OK. ' - 'May be used multiple times.')) - @utils.arg('--insufficient-data-action', - dest='insufficient_data_actions', - metavar='', action='append', default=None, - help=('URL to invoke when state transitions to ' - 'insufficient data. May be used multiple times.')) - @utils.arg('--time-constraint', dest='time_constraints', - metavar='