Retire Packaging Deb project repos
This commit is part of a series to retire the Packaging Deb project. Step 2 is to remove all content from the project repos, replacing it with a README notification where to find ongoing work, and how to recover the repo if needed at some future point (as in https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project). Change-Id: I15d73bb42d7cd76c073019db73510370eea14ec2
This commit is contained in:
16
.gitignore
vendored
16
.gitignore
vendored
@@ -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
|
@@ -1,4 +0,0 @@
|
||||
[gerrit]
|
||||
host=review.openstack.org
|
||||
port=29418
|
||||
project=openstack/python-ceilometerclient.git
|
@@ -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
|
@@ -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
|
175
LICENSE
175
LICENSE
@@ -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.
|
14
README
Normal file
14
README
Normal file
@@ -0,0 +1,14 @@
|
||||
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 ongoing work on maintaining OpenStack packages in the Debian
|
||||
distribution, please see the Debian OpenStack packaging team at
|
||||
https://wiki.debian.org/OpenStack/.
|
||||
|
||||
For any further questions, please email
|
||||
openstack-dev@lists.openstack.org or join #openstack-dev on
|
||||
Freenode.
|
28
README.rst
28
README.rst
@@ -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
|
@@ -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
|
@@ -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
|
||||
"""
|
@@ -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 "<Extension '%s'>" % 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)
|
@@ -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)
|
@@ -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)
|
@@ -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,
|
||||
})
|
@@ -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)
|
@@ -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/keystoneclient 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
|
@@ -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)
|
@@ -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', '')
|
@@ -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)
|
@@ -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
|
@@ -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='<CEILOMETER_URL>',
|
||||
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='<subcommand>')
|
||||
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='<subcommand>', nargs='?',
|
||||
help='Display help for <subcommand>')
|
||||
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()
|
@@ -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)
|
@@ -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 jenkins:jenkins $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 jenkins:stack $CEILOMETERCLIENT_DIR
|
||||
|
||||
# Run tests
|
||||
echo "Running ceilometerclient functional test suite"
|
||||
set +e
|
||||
# Preserve env for OS_ credentials
|
||||
sudo -E -H -u jenkins tox -efunctional
|
||||
EXIT_CODE=$?
|
||||
set -e
|
||||
|
||||
# Collect and parse result
|
||||
generate_testr_results
|
||||
exit $EXIT_CODE
|
@@ -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')
|
@@ -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)
|
@@ -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)
|
@@ -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)
|
@@ -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')
|
@@ -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)
|
@@ -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())
|
@@ -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)
|
@@ -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'])
|
@@ -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)
|
@@ -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)
|
@@ -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)
|
@@ -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))
|
@@ -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))
|
@@ -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))
|
@@ -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)
|
@@ -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())
|
File diff suppressed because it is too large
Load Diff
@@ -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',
|
||||
))
|
@@ -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)
|
@@ -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)
|
@@ -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
|
@@ -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 "<Alarm %s>" % 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 "<AlarmChange %s>" % 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)
|
@@ -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 "<Capabilities %s>" % self._info
|
||||
|
||||
|
||||
class CapabilitiesManager(base.Manager):
|
||||
resource_class = Capabilities
|
||||
|
||||
def get(self):
|
||||
path = "/v2/capabilities"
|
||||
return Capabilities(self, self.api.get(path).json())
|
@@ -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
|
@@ -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 "<EventType %s>" % 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)
|
@@ -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 "<Event %s>" % 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
|
@@ -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 "<Meter %s>" % 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))
|
@@ -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 <type>::<value>,
|
||||
# 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
|
@@ -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'
|
@@ -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 "<Resource %s>" % 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
|
@@ -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 "<OldSample %s>" % 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 "<Sample %s>" % 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
|
File diff suppressed because it is too large
Load Diff
@@ -1,58 +0,0 @@
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import six
|
||||
|
||||
from ceilometerclient.common import base
|
||||
from ceilometerclient.v2 import options
|
||||
|
||||
|
||||
class Statistics(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Statistics %s>" % self._info
|
||||
|
||||
|
||||
class StatisticsManager(base.Manager):
|
||||
resource_class = Statistics
|
||||
|
||||
@staticmethod
|
||||
def _build_aggregates(aggregates):
|
||||
url_aggregates = []
|
||||
for aggregate in aggregates:
|
||||
if 'param' in aggregate:
|
||||
url_aggregates.insert(
|
||||
0,
|
||||
"aggregate.param=%(param)s" % aggregate
|
||||
)
|
||||
url_aggregates.insert(
|
||||
0,
|
||||
"aggregate.func=%(func)s" % aggregate
|
||||
)
|
||||
else:
|
||||
url_aggregates.append(
|
||||
"aggregate.func=%(func)s" % aggregate
|
||||
)
|
||||
return url_aggregates
|
||||
|
||||
def list(self, meter_name, q=None, period=None, groupby=None,
|
||||
aggregates=None):
|
||||
groupby = groupby or []
|
||||
aggregates = aggregates or []
|
||||
p = ['period=%s' % period] if period else []
|
||||
if isinstance(groupby, six.string_types):
|
||||
groupby = [groupby]
|
||||
p.extend(['groupby=%s' % g for g in groupby] if groupby else [])
|
||||
p.extend(self._build_aggregates(aggregates))
|
||||
return self._list(options.build_url(
|
||||
'/v2/meters/' + meter_name + '/statistics',
|
||||
q, p))
|
@@ -1,28 +0,0 @@
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from ceilometerclient.common import base
|
||||
|
||||
|
||||
class TraitDescription(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Trait %s>" % self._info
|
||||
|
||||
|
||||
class TraitDescriptionManager(base.Manager):
|
||||
resource_class = TraitDescription
|
||||
|
||||
def list(self, event_type):
|
||||
path = '/v2/event_types/%s/traits' % event_type
|
||||
return self._list(path)
|
@@ -1,28 +0,0 @@
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from ceilometerclient.common import base
|
||||
|
||||
|
||||
class Trait(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Trait %s>" % self._info
|
||||
|
||||
|
||||
class TraitManager(base.Manager):
|
||||
resource_class = Trait
|
||||
|
||||
def list(self, event_type, trait_name):
|
||||
path = '/v2/event_types/%s/traits/%s' % (event_type, trait_name)
|
||||
return self._list(path)
|
@@ -1,54 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.abspath(os.path.join(BASE_DIR, "..", "..", ".."))
|
||||
|
||||
sys.path.insert(0, ROOT)
|
||||
sys.path.insert(0, BASE_DIR)
|
||||
|
||||
|
||||
def gen_ref(ver, title, names):
|
||||
refdir = os.path.join(BASE_DIR, "ref")
|
||||
pkg = "ceilometerclient"
|
||||
if ver:
|
||||
pkg = "%s.%s" % (pkg, ver)
|
||||
refdir = os.path.join(refdir, ver)
|
||||
if not os.path.exists(refdir):
|
||||
os.makedirs(refdir)
|
||||
idxpath = os.path.join(refdir, "index.rst")
|
||||
with open(idxpath, "w") as idx:
|
||||
idx.write(("%(title)s\n"
|
||||
"%(signs)s\n"
|
||||
"\n"
|
||||
".. toctree::\n"
|
||||
" :maxdepth: 1\n"
|
||||
"\n") % {"title": title, "signs": "=" * len(title)})
|
||||
for name in names:
|
||||
idx.write(" %s\n" % name)
|
||||
rstpath = os.path.join(refdir, "%s.rst" % name)
|
||||
with open(rstpath, "w") as rst:
|
||||
rst.write(("%(title)s\n"
|
||||
"%(signs)s\n"
|
||||
"\n"
|
||||
".. automodule:: %(pkg)s.%(name)s\n"
|
||||
" :members:\n"
|
||||
" :undoc-members:\n"
|
||||
" :show-inheritance:\n"
|
||||
" :noindex:\n")
|
||||
% {"title": name.capitalize(),
|
||||
"signs": "=" * len(name),
|
||||
"pkg": pkg, "name": name})
|
@@ -1,47 +0,0 @@
|
||||
The :mod:`ceilometerclient` Python API
|
||||
======================================
|
||||
|
||||
.. module:: ceilometerclient
|
||||
:synopsis: A client for the OpenStack Ceilometer API.
|
||||
|
||||
.. currentmodule:: ceilometerclient
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
First create a client instance with your credentials::
|
||||
|
||||
>>> import ceilometerclient.client
|
||||
>>> cclient = ceilometerclient.client.get_client(VERSION, os_username=USERNAME, os_password=PASSWORD, os_tenant_name=PROJECT_NAME, os_auth_url=AUTH_URL)
|
||||
|
||||
Here ``VERSION`` should be: ``2``.
|
||||
|
||||
Then call methods on its managers::
|
||||
|
||||
>>> cclient.meters.list()
|
||||
[<Meter ...>, ...]
|
||||
|
||||
>>> cclient.new_samples.list()
|
||||
[<Sample ...>, ...]
|
||||
|
||||
V2 client tips
|
||||
++++++++++++++
|
||||
|
||||
Use queries to narrow your search (more info at `Ceilometer V2 API reference`__)::
|
||||
|
||||
>>> query = [dict(field='resource_id', op='eq', value='5a301761-f78b-46e2-8900-8b4f6fe6675a'), dict(field='meter',op='eq',value='cpu_util')]
|
||||
>>> cclient.new_samples.list(q=query, limit=10)
|
||||
[<Sample ...>, ...]
|
||||
|
||||
__ https://docs.openstack.org/ceilometer/latest/webapi/v2.html#Query
|
||||
|
||||
Reference
|
||||
---------
|
||||
|
||||
For more information, see the reference:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
ref/index
|
||||
ref/v2/index
|
@@ -1,76 +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
|
||||
execfile(os.path.join("..", "ext", "gen_ref.py"))
|
||||
|
||||
project = 'python-ceilometerclient'
|
||||
|
||||
gen_ref("", "Client Reference", ["client", "exc"])
|
||||
gen_ref("v2", "Version 2 API Reference",
|
||||
["meters", "samples", "statistics", "resources", "query", "alarms",
|
||||
"events", "event_types", "traits", "trait_descriptions"])
|
||||
|
||||
# -- General configuration ----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'oslosphinx' ]
|
||||
|
||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||
# text edit cycles.
|
||||
# execute "export SPHINX_DEBUG=1" in your terminal to disable
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
copyright = u'OpenStack Foundation'
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
add_module_names = True
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# -- Options for HTML output --------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
#html_theme = 'nature'
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = '%sdoc' % project
|
||||
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass
|
||||
# [howto/manual]).
|
||||
latex_documents = [
|
||||
(
|
||||
'index',
|
||||
'%s.tex' % project,
|
||||
u'%s Documentation' % project,
|
||||
u'OpenStack Foundation',
|
||||
'manual'
|
||||
),
|
||||
]
|
@@ -1,45 +0,0 @@
|
||||
Python bindings to the OpenStack Ceilometer API
|
||||
==================================================
|
||||
|
||||
This is a client for OpenStack Ceilometer API. There's :doc:`a Python API
|
||||
<api>` (the :mod:`ceilometerclient` module), and a :doc:`command-line script
|
||||
<shell>` (installed as :program:`ceilometer`). Each implements the entire
|
||||
OpenStack Ceilometer API.
|
||||
|
||||
.. seealso::
|
||||
|
||||
You may want to read the `OpenStack Ceilometer Developer Guide`__ -- the overview, at
|
||||
least -- to get an idea of the concepts. By understanding the concepts
|
||||
this library should make more sense.
|
||||
|
||||
__ https://docs.openstack.org/ceilometer/latest/
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
shell
|
||||
api
|
||||
ref/index
|
||||
ref/v2/index
|
||||
|
||||
Contributing
|
||||
============
|
||||
|
||||
Code is hosted at `git.openstack.org`_. Submit bugs to the Ceilometer project on
|
||||
`Launchpad`_. Submit code to the openstack/python-ceilometerclient project using
|
||||
`Gerrit`_.
|
||||
|
||||
.. _git.openstack.org: https://git.openstack.org/cgit/openstack/python-ceilometerclient
|
||||
.. _Launchpad: https://launchpad.net/ceilometer
|
||||
.. _Gerrit: https://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
|
||||
Run tests with ``python setup.py test``.
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
@@ -1,61 +0,0 @@
|
||||
The :program:`ceilometer` shell utility
|
||||
=========================================
|
||||
|
||||
.. program:: ceilometer
|
||||
.. highlight:: bash
|
||||
|
||||
The :program:`ceilometer` shell utility interacts with OpenStack Ceilometer API
|
||||
from the command line. It supports the entirety of the OpenStack Ceilometer API.
|
||||
|
||||
You'll need to provide :program:`ceilometer` with your OpenStack credentials.
|
||||
You can do this with the :option:`--os-username`, :option:`--os-password`,
|
||||
:option:`--os-tenant-id` and :option:`--os-auth-url` options, but it's easier to
|
||||
just set them as environment variables:
|
||||
|
||||
.. envvar:: OS_USERNAME
|
||||
|
||||
Your OpenStack username.
|
||||
|
||||
.. envvar:: OS_PASSWORD
|
||||
|
||||
Your password.
|
||||
|
||||
.. envvar:: OS_TENANT_NAME
|
||||
|
||||
Project to work on.
|
||||
|
||||
.. envvar:: OS_AUTH_URL
|
||||
|
||||
The OpenStack auth server URL (keystone).
|
||||
|
||||
For example, in Bash you would use::
|
||||
|
||||
export OS_USERNAME=user
|
||||
export OS_PASSWORD=pass
|
||||
export OS_TENANT_NAME=myproject
|
||||
export OS_AUTH_URL=http://auth.example.com:5000/v2.0
|
||||
|
||||
The command line tool will attempt to reauthenticate using your provided credentials
|
||||
for every request. You can override this behavior by manually supplying an auth
|
||||
token using :option:`--os-ceilometer-url` and :option:`--os-auth-token`. You can alternatively
|
||||
set these environment variables::
|
||||
|
||||
export OS_CEILOMETER_URL=http://ceilometer.example.org:8777
|
||||
export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155
|
||||
|
||||
From there, all shell commands take the form::
|
||||
|
||||
ceilometer <command> [arguments...]
|
||||
|
||||
Run :program:`ceilometer help` to get a full list of all possible commands,
|
||||
and run :program:`ceilometer help <command>` to get detailed help for that
|
||||
command.
|
||||
|
||||
V2 client tips
|
||||
++++++++++++++
|
||||
|
||||
Use queries to narrow your search (more info at `Ceilometer V2 API reference`__)::
|
||||
|
||||
ceilometer sample-list --meter cpu_util --query 'resource_id=5a301761-f78b-46e2-8900-8b4f6fe6675a' --limit 10
|
||||
|
||||
__ https://docs.openstack.org/ceilometer/latest/webapi/v2.html#Query
|
@@ -1,3 +0,0 @@
|
||||
---
|
||||
deprecations:
|
||||
- Alarm commands are deprecated in favor of aodhclient.
|
@@ -1,6 +0,0 @@
|
||||
---
|
||||
deprecations:
|
||||
- |
|
||||
As the Ceilometer API has been deprecated, this client is also now marked
|
||||
as deprecated and will print a warning when used as a command-line
|
||||
interface tool.
|
@@ -1,7 +0,0 @@
|
||||
---
|
||||
prelude: >
|
||||
Panko replaces the API and storage of events previously in Ceilometer
|
||||
features:
|
||||
- |
|
||||
Similar to aodh redirect support, specify `panko_endpoint` as a redirect
|
||||
to Panko API.
|
@@ -1,278 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Ceilometer Client Release Notes documentation build configuration file,
|
||||
# created by sphinx-quickstart on Mon Nov 23 20:38:38 2015.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'oslosphinx',
|
||||
'reno.sphinxext',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
# source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Ceilometer Client Release Notes'
|
||||
copyright = u'2015-present, Ceilometer developers'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
import pbr.version
|
||||
ceilometer_version = pbr.version.VersionInfo('python-ceilometerclient')
|
||||
# The short X.Y version.
|
||||
version = ceilometer_version.canonical_version_string()
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = ceilometer_version.version_string_with_vcs()
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
# language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
# today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
# today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = []
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
# default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
# add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
# add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
# show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
# modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
# keep_warnings = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
# html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
# html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
# html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
# html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
# html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
# html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
# html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
# html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
# html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
# html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
# html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
# html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
# html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
# html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
# html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
# html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
# html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
# html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'CeilometerClientReleaseNotestdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
# 'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'PythonCeilometerClient.tex',
|
||||
u'Ceilometer Client Release Notes Documentation',
|
||||
u'Ceilometer developers', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
# latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
# latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
# latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
# latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
# latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
# latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'pythonceilometerclient',
|
||||
u'Ceilometer Client Release Notes Documentation',
|
||||
[u'Ceilometer developers'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
# man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'PythonCeilometerClient',
|
||||
u'Ceilometer Client Release Notes Documentation',
|
||||
u'Ceilometer developers', 'PythonCeilometerClient',
|
||||
'One line description of project.', 'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
# texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
# texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
# texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
# texinfo_no_detailmenu = False
|
||||
|
||||
# -- Options for Internationalization output ------------------------------
|
||||
locale_dirs = ['locale/']
|
@@ -1,20 +0,0 @@
|
||||
Welcome to Ceilometer Client Release Notes documentation!
|
||||
=========================================================
|
||||
|
||||
Contents
|
||||
========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
unreleased
|
||||
ocata
|
||||
newton
|
||||
mitaka
|
||||
liberty
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`search`
|
@@ -1,6 +0,0 @@
|
||||
==============================
|
||||
Liberty Series Release Notes
|
||||
==============================
|
||||
|
||||
.. release-notes::
|
||||
:branch: origin/stable/liberty
|
@@ -1,6 +0,0 @@
|
||||
===================================
|
||||
Mitaka Series Release Notes
|
||||
===================================
|
||||
|
||||
.. release-notes::
|
||||
:branch: origin/stable/mitaka
|
@@ -1,6 +0,0 @@
|
||||
===================================
|
||||
Newton Series Release Notes
|
||||
===================================
|
||||
|
||||
.. release-notes::
|
||||
:branch: origin/stable/newton
|
@@ -1,6 +0,0 @@
|
||||
===================================
|
||||
Ocata Series Release Notes
|
||||
===================================
|
||||
|
||||
.. release-notes::
|
||||
:branch: origin/stable/ocata
|
@@ -1,5 +0,0 @@
|
||||
============================
|
||||
Current Series Release Notes
|
||||
============================
|
||||
|
||||
.. release-notes::
|
@@ -1,13 +0,0 @@
|
||||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
pbr>=1.6 # Apache-2.0
|
||||
iso8601>=0.1.11 # MIT
|
||||
keystoneauth1>=2.1.0 # Apache-2.0
|
||||
oslo.i18n>=2.1.0 # Apache-2.0
|
||||
oslo.serialization>=1.10.0 # Apache-2.0
|
||||
oslo.utils>=3.17.0 # Apache-2.0
|
||||
PrettyTable<0.8,>=0.7 # BSD
|
||||
requests!=2.9.0,>=2.8.1 # Apache-2.0
|
||||
six>=1.9.0 # MIT
|
||||
stevedore>=1.10.0 # Apache-2.0
|
42
setup.cfg
42
setup.cfg
@@ -1,42 +0,0 @@
|
||||
[metadata]
|
||||
name = python-ceilometerclient
|
||||
summary = OpenStack Telemetry API Client Library
|
||||
description-file =
|
||||
README.rst
|
||||
author = OpenStack
|
||||
author-email = openstack-dev@lists.openstack.org
|
||||
home-page = https://docs.openstack.org/python-ceilometerclient/latest/
|
||||
classifier =
|
||||
Environment :: OpenStack
|
||||
Intended Audience :: Information Technology
|
||||
Intended Audience :: System Administrators
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: POSIX :: Linux
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.5
|
||||
|
||||
[files]
|
||||
packages =
|
||||
ceilometerclient
|
||||
|
||||
[global]
|
||||
setup-hooks =
|
||||
pbr.hooks.setup_hook
|
||||
|
||||
[entry_points]
|
||||
console_scripts =
|
||||
ceilometer = ceilometerclient.shell:main
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/source
|
||||
build-dir = doc/build
|
||||
all_files = 1
|
||||
|
||||
[upload_sphinx]
|
||||
upload-dir = doc/build/html
|
||||
|
||||
[wheel]
|
||||
universal = 1
|
29
setup.py
29
setup.py
@@ -1,29 +0,0 @@
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
||||
import setuptools
|
||||
|
||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
||||
# setuptools if some other modules registered functions in `atexit`.
|
||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
||||
try:
|
||||
import multiprocessing # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr>=1.8'],
|
||||
pbr=True)
|
@@ -1,14 +0,0 @@
|
||||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
# Hacking already pins down pep8, pyflakes and flake8
|
||||
coverage>=3.6 # Apache-2.0
|
||||
fixtures<2.0,>=1.3.1 # Apache-2.0/BSD
|
||||
mock>=1.2 # BSD
|
||||
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
|
||||
reno>=1.6.2 # Apache2
|
||||
python-subunit>=0.0.18 # Apache-2.0/BSD
|
||||
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD
|
||||
tempest>=11.0.0 # Apache-2.0
|
||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||
testtools>=1.4.0 # MIT
|
@@ -1,27 +0,0 @@
|
||||
# bash completion for openstack ceilometer
|
||||
|
||||
_ceilometer_opts="" # lazy init
|
||||
_ceilometer_flags="" # lazy init
|
||||
_ceilometer_opts_exp="" # lazy init
|
||||
_ceilometer()
|
||||
{
|
||||
local cur prev kbc
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
|
||||
if [ "x$_ceilometer_opts" == "x" ] ; then
|
||||
kbc="`ceilometer bash-completion | sed -e "s/ -h / /"`"
|
||||
_ceilometer_opts="`echo "$kbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/[ ][ ]*/ /g"`"
|
||||
_ceilometer_flags="`echo " $kbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/[ ][ ]*/ /g"`"
|
||||
_ceilometer_opts_exp="`echo $_ceilometer_opts | sed -e "s/[ ]/|/g"`"
|
||||
fi
|
||||
|
||||
if [[ " ${COMP_WORDS[@]} " =~ " "($_ceilometer_opts_exp)" " && "$prev" != "help" ]] ; then
|
||||
COMPREPLY=($(compgen -W "${_ceilometer_flags}" -- ${cur}))
|
||||
else
|
||||
COMPREPLY=($(compgen -W "${_ceilometer_opts}" -- ${cur}))
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
complete -F _ceilometer ceilometer
|
45
tox.ini
45
tox.ini
@@ -1,45 +0,0 @@
|
||||
[tox]
|
||||
envlist = py35,py27,pypy,pep8
|
||||
minversion = 1.6
|
||||
skipsdist = True
|
||||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
install_command = pip install -U {opts} {packages}
|
||||
setenv = VIRTUAL_ENV={envdir}
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
# NOTE(tonyb): This project has chosen to *NOT* consume upper-constraints.txt
|
||||
commands =
|
||||
python setup.py testr --slowest --testr-args='{posargs}'
|
||||
|
||||
[testenv:pep8]
|
||||
deps = hacking<0.13,>=0.12
|
||||
commands = flake8
|
||||
|
||||
[testenv:cover]
|
||||
commands =
|
||||
python setup.py testr --coverage --testr-args='{posargs}'
|
||||
coverage report
|
||||
|
||||
[testenv:venv]
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:functional]
|
||||
setenv = OS_TEST_PATH=./ceilometerclient/tests/functional
|
||||
passenv = OS_*
|
||||
|
||||
[testenv:docs]
|
||||
commands=
|
||||
python setup.py build_sphinx
|
||||
|
||||
[testenv:releasenotes]
|
||||
commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
|
||||
|
||||
[flake8]
|
||||
show-source = True
|
||||
exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,tools
|
||||
|
||||
[hacking]
|
||||
import_exceptions =
|
||||
ceilometerclient.i18n
|
Reference in New Issue
Block a user