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:
Tony Breeds
2017-09-12 15:56:30 -06:00
parent a5f1795869
commit f35b9656f0
88 changed files with 14 additions and 11797 deletions

16
.gitignore vendored
View File

@@ -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

View File

@@ -1,4 +0,0 @@
[gerrit]
host=review.openstack.org
port=29418
project=openstack/python-ceilometerclient.git

View File

@@ -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

View File

@@ -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
View File

@@ -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
View 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.

View File

@@ -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

View File

@@ -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

View File

@@ -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
"""

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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,
})

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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', '')

View File

@@ -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)

View File

@@ -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

View File

@@ -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()

View File

@@ -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)

View File

@@ -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

View File

@@ -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')

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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')

View File

@@ -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)

View File

@@ -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())

View File

@@ -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)

View File

@@ -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'])

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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))

View File

@@ -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))

View File

@@ -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))

View File

@@ -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)

View File

@@ -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

View File

@@ -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',
))

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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())

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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))

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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))

View File

@@ -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)

View File

@@ -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)

View File

@@ -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})

View File

@@ -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

View File

@@ -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'
),
]

View File

@@ -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`

View File

@@ -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

View File

@@ -1,3 +0,0 @@
---
deprecations:
- Alarm commands are deprecated in favor of aodhclient.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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/']

View File

@@ -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`

View File

@@ -1,6 +0,0 @@
==============================
Liberty Series Release Notes
==============================
.. release-notes::
:branch: origin/stable/liberty

View File

@@ -1,6 +0,0 @@
===================================
Mitaka Series Release Notes
===================================
.. release-notes::
:branch: origin/stable/mitaka

View File

@@ -1,6 +0,0 @@
===================================
Newton Series Release Notes
===================================
.. release-notes::
:branch: origin/stable/newton

View File

@@ -1,6 +0,0 @@
===================================
Ocata Series Release Notes
===================================
.. release-notes::
:branch: origin/stable/ocata

View File

@@ -1,5 +0,0 @@
============================
Current Series Release Notes
============================
.. release-notes::

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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
View File

@@ -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