Merge tag '1.0.1' into debian/unstable

python-ceilometerclient 1.0.1 release
This commit is contained in:
Thomas Goirand
2013-07-26 11:20:57 +08:00
49 changed files with 1397 additions and 856 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,7 @@
.coverage .coverage
.venv .venv
.testrepository
subunit.log
*,cover *,cover
cover cover
*.pyc *.pyc

4
.testr.conf Normal file
View File

@@ -0,0 +1,4 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./ ./tests $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@@ -1,10 +1,8 @@
include AUTHORS include AUTHORS
include HACKING
include LICENSE include LICENSE
include README.rst include README.md
include ChangeLog include ChangeLog
include tox.ini include tox.ini
include ceilometerclient/versioninfo
recursive-include doc * recursive-include doc *
recursive-include tests * recursive-include tests *
recursive-include tools * recursive-include tools *

View File

@@ -9,23 +9,13 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import inspect
import os
__all__ = ['__version__']
def _get_ceilometerclient_version(): import pbr.version
"""Read version from versioninfo file."""
mod_abspath = inspect.getabsfile(inspect.currentframe())
ceilometerclient_path = os.path.dirname(mod_abspath)
version_path = os.path.join(ceilometerclient_path, 'versioninfo')
if os.path.exists(version_path): version_info = pbr.version.VersionInfo('python-ceilometerclient')
version = open(version_path).read().strip() try:
else: __version__ = version_info.version_string()
version = "Unknown, couldn't find versioninfo file at %s"\ except AttributeError:
% version_path __version__ = None
return version
__version__ = _get_ceilometerclient_version()

View File

@@ -11,6 +11,83 @@
# under the License. # under the License.
from ceilometerclient.common import utils from ceilometerclient.common import utils
from keystoneclient.v2_0 import client as ksclient
def _get_ksclient(**kwargs):
"""Get an endpoint and auth token from Keystone.
:param kwargs: keyword args containing credentials:
* username: name of user
* password: user's password
* auth_url: endpoint to authenticate against
* insecure: allow insecure SSL (no cert verification)
* tenant_{name|id}: name or ID of tenant
"""
return ksclient.Client(username=kwargs.get('username'),
password=kwargs.get('password'),
tenant_id=kwargs.get('tenant_id'),
tenant_name=kwargs.get('tenant_name'),
auth_url=kwargs.get('auth_url'),
insecure=kwargs.get('insecure'))
def _get_endpoint(client, **kwargs):
"""Get an endpoint using the provided keystone client."""
return client.service_catalog.url_for(
service_type=kwargs.get('service_type') or 'metering',
endpoint_type=kwargs.get('endpoint_type') or 'publicURL')
def get_client(api_version, **kwargs):
"""Get an authtenticated client, based on the credentials
in the keyword args.
:param api_version: the API version to use ('1' or '2')
:param kwargs: keyword args containing credentials, either:
* os_auth_token: pre-existing token to re-use
* ceilometer_url: ceilometer API endpoint
or:
* os_username: name of user
* os_password: user's password
* os_auth_url: endpoint to authenticate against
* insecure: allow insecure SSL (no cert verification)
* os_tenant_{name|id}: name or ID of tenant
"""
if kwargs.get('os_auth_token') and kwargs.get('ceilometer_url'):
token = kwargs.get('os_auth_token')
endpoint = kwargs.get('ceilometer_url')
elif (kwargs.get('os_username') and
kwargs.get('os_password') and
kwargs.get('os_auth_url') and
(kwargs.get('os_tenant_id') or kwargs.get('os_tenant_name'))):
ks_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'),
'service_type': kwargs.get('os_service_type'),
'endpoint_type': kwargs.get('os_endpoint_type'),
'insecure': kwargs.get('insecure'),
}
_ksclient = _get_ksclient(**ks_kwargs)
token = kwargs.get('os_auth_token') or _ksclient.auth_token
endpoint = kwargs.get('ceilometer_url') or \
_get_endpoint(_ksclient, **ks_kwargs)
cli_kwargs = {
'token': token,
'insecure': kwargs.get('insecure'),
'timeout': kwargs.get('timeout'),
'ca_file': kwargs.get('ca_file'),
'cert_file': kwargs.get('cert_file'),
'key_file': kwargs.get('key_file'),
}
return Client(api_version, endpoint, **cli_kwargs)
def Client(version, *args, **kwargs): def Client(version, *args, **kwargs):

View File

@@ -29,9 +29,8 @@ except NameError:
def getid(obj): def getid(obj):
""" """Abstracts the common pattern of allowing both an object or an
Abstracts the common pattern of allowing both an object or an object's ID object's ID (UUID) as a parameter when dealing with relationships.
(UUID) as a parameter when dealing with relationships.
""" """
try: try:
return obj.id return obj.id
@@ -40,16 +39,21 @@ def getid(obj):
class Manager(object): class Manager(object):
""" """Managers interact with a particular type of API
Managers interact with a particular type of API (servers, flavors, images, (samples, meters, alarms, etc.) and provide CRUD operations for them.
etc.) and provide CRUD operations for them.
""" """
resource_class = None resource_class = None
def __init__(self, api): def __init__(self, api):
self.api = api self.api = api
def _list(self, url, response_key=None, obj_class=None, body=None): def _create(self, url, body):
resp, body = self.api.json_request('POST', url, body=body)
if body:
return self.resource_class(self, body)
def _list(self, url, response_key=None, obj_class=None, body=None,
expect_single=False):
resp, body = self.api.json_request('GET', url) resp, body = self.api.json_request('GET', url)
if obj_class is None: if obj_class is None:
@@ -62,21 +66,22 @@ class Manager(object):
return [] return []
else: else:
data = body data = body
if expect_single:
data = [data]
return [obj_class(self, res, loaded=True) for res in data if res] return [obj_class(self, res, loaded=True) for res in data if res]
def _delete(self, url):
self.api.raw_request('DELETE', url)
def _update(self, url, body, response_key=None): def _update(self, url, body, response_key=None):
resp, body = self.api.json_request('PUT', url, body=body) resp, body = self.api.json_request('PUT', url, body=body)
# PUT requests may not return a body # PUT requests may not return a body
if body: if body:
return self.resource_class(self, body[response_key]) return self.resource_class(self, body)
def _delete(self, url):
self.api.raw_request('DELETE', url)
class Resource(object): class Resource(object):
""" """A resource represents a particular instance of an object (tenant, user,
A resource represents a particular instance of an object (tenant, user,
etc). This is pretty much just a bag for attributes. etc). This is pretty much just a bag for attributes.
:param manager: Manager object :param manager: Manager object

View File

@@ -58,7 +58,8 @@ class HTTPClient(object):
parts = urlparse.urlparse(endpoint) parts = urlparse.urlparse(endpoint)
_args = (parts.hostname, parts.port, parts.path) _args = (parts.hostname, parts.port, parts.path)
_kwargs = {'timeout': float(kwargs.get('timeout', 600))} _kwargs = {'timeout': (float(kwargs.get('timeout'))
if kwargs.get('timeout') else 600)}
if parts.scheme == 'https': if parts.scheme == 'https':
_class = VerifiedHTTPSConnection _class = VerifiedHTTPSConnection
@@ -77,7 +78,7 @@ class HTTPClient(object):
def get_connection(self): def get_connection(self):
_class = self.connection_params[0] _class = self.connection_params[0]
try: try:
return _class(*self.connection_params[1], return _class(*self.connection_params[1][0:2],
**self.connection_params[2]) **self.connection_params[2])
except httplib.InvalidURL: except httplib.InvalidURL:
raise exc.InvalidEndpoint() raise exc.InvalidEndpoint()
@@ -124,7 +125,7 @@ class HTTPClient(object):
return '%s/%s' % (base_url.rstrip('/'), url.lstrip('/')) return '%s/%s' % (base_url.rstrip('/'), url.lstrip('/'))
def _http_request(self, url, method, **kwargs): def _http_request(self, url, method, **kwargs):
""" Send an http request with the specified characteristics. """Send an http request with the specified characteristics.
Wrapper around httplib.HTTP(S)Connection.request to handle tasks such Wrapper around httplib.HTTP(S)Connection.request to handle tasks such
as setting headers and error handling. as setting headers and error handling.
@@ -180,8 +181,12 @@ class HTTPClient(object):
kwargs['body'] = json.dumps(kwargs['body']) kwargs['body'] = json.dumps(kwargs['body'])
resp, body_iter = self._http_request(url, method, **kwargs) resp, body_iter = self._http_request(url, method, **kwargs)
content_type = resp.getheader('content-type', None)
if 'application/json' in resp.getheader('content-type', None): if resp.status == 204 or resp.status == 205 or content_type is None:
return resp, list()
if 'application/json' in content_type:
body = ''.join([chunk for chunk in body_iter]) body = ''.join([chunk for chunk in body_iter])
try: try:
body = json.loads(body) body = json.loads(body)
@@ -220,8 +225,7 @@ class VerifiedHTTPSConnection(httplib.HTTPSConnection):
self.insecure = insecure self.insecure = insecure
def connect(self): def connect(self):
""" """Connect to a host on a given (SSL) port.
Connect to a host on a given (SSL) port.
If ca_file is pointing somewhere, use it to check Server Certificate. If ca_file is pointing somewhere, use it to check Server Certificate.
Redefined/copied and extended from httplib.py:1105 (Python 2.6.x). Redefined/copied and extended from httplib.py:1105 (Python 2.6.x).
@@ -249,7 +253,7 @@ class VerifiedHTTPSConnection(httplib.HTTPSConnection):
@staticmethod @staticmethod
def get_system_ca_file(): def get_system_ca_file():
""""Return path to system default CA file""" """Return path to system default CA file."""
# Standard CA file locations for Debian/Ubuntu, RedHat/Fedora, # Standard CA file locations for Debian/Ubuntu, RedHat/Fedora,
# Suse, FreeBSD/OpenBSD # Suse, FreeBSD/OpenBSD
ca_path = ['/etc/ssl/certs/ca-certificates.crt', ca_path = ['/etc/ssl/certs/ca-certificates.crt',

View File

@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import errno
import hashlib
import os import os
import sys import sys
import textwrap
import uuid import uuid
import prettytable import prettytable
@@ -40,7 +39,8 @@ def pretty_choice_list(l):
def print_list(objs, fields, field_labels, formatters={}, sortby=0): def print_list(objs, fields, field_labels, formatters={}, sortby=0):
pt = prettytable.PrettyTable([f for f in field_labels], caching=False) pt = prettytable.PrettyTable([f for f in field_labels],
caching=False, print_empty=False)
pt.align = 'l' pt.align = 'l'
for o in objs: for o in objs:
@@ -49,22 +49,33 @@ def print_list(objs, fields, field_labels, formatters={}, sortby=0):
if field in formatters: if field in formatters:
row.append(formatters[field](o)) row.append(formatters[field](o))
else: else:
data = getattr(o, field, None) or '' data = getattr(o, field, '')
row.append(data) row.append(data)
pt.add_row(row) pt.add_row(row)
print pt.get_string(sortby=field_labels[sortby]) print pt.get_string(sortby=field_labels[sortby])
def print_dict(d, formatters={}): def print_dict(d, dict_property="Property", wrap=0):
pt = prettytable.PrettyTable(['Property', 'Value'], caching=False) pt = prettytable.PrettyTable([dict_property, 'Value'],
caching=False, print_empty=False)
pt.align = 'l' pt.align = 'l'
for k, v in d.iteritems():
for field in d.keys(): # convert dict to str to check length
if field in formatters: if isinstance(v, dict):
pt.add_row([field, formatters[field](d[field])]) v = str(v)
if wrap > 0:
v = textwrap.fill(str(v), wrap)
# if value has a newline, add in multiple rows
# e.g. fault with stacktrace
if v and isinstance(v, basestring) and r'\n' in v:
lines = v.strip().split(r'\n')
col1 = k
for line in lines:
pt.add_row([col1, line])
col1 = ''
else: else:
pt.add_row([field, d[field]]) pt.add_row([k, v])
print pt.get_string(sortby='Property') print pt.get_string()
def find_resource(manager, name_or_id): def find_resource(manager, name_or_id):

View File

@@ -23,7 +23,7 @@ class BaseException(Exception):
class CommandError(BaseException): class CommandError(BaseException):
"""Invalid usage of CLI""" """Invalid usage of CLI."""
class InvalidEndpoint(BaseException): class InvalidEndpoint(BaseException):
@@ -35,11 +35,11 @@ class CommunicationError(BaseException):
class ClientException(Exception): class ClientException(Exception):
"""DEPRECATED""" """DEPRECATED."""
class HTTPException(ClientException): class HTTPException(ClientException):
"""Base exception for all HTTP-derived exceptions""" """Base exception for all HTTP-derived exceptions."""
code = 'N/A' code = 'N/A'
def __init__(self, details=None): def __init__(self, details=None):
@@ -60,7 +60,7 @@ class HTTPMultipleChoices(HTTPException):
class BadRequest(HTTPException): class BadRequest(HTTPException):
"""DEPRECATED""" """DEPRECATED."""
code = 400 code = 400
@@ -69,7 +69,7 @@ class HTTPBadRequest(BadRequest):
class Unauthorized(HTTPException): class Unauthorized(HTTPException):
"""DEPRECATED""" """DEPRECATED."""
code = 401 code = 401
@@ -78,7 +78,7 @@ class HTTPUnauthorized(Unauthorized):
class Forbidden(HTTPException): class Forbidden(HTTPException):
"""DEPRECATED""" """DEPRECATED."""
code = 403 code = 403
@@ -87,7 +87,7 @@ class HTTPForbidden(Forbidden):
class NotFound(HTTPException): class NotFound(HTTPException):
"""DEPRECATED""" """DEPRECATED."""
code = 404 code = 404
@@ -100,7 +100,7 @@ class HTTPMethodNotAllowed(HTTPException):
class Conflict(HTTPException): class Conflict(HTTPException):
"""DEPRECATED""" """DEPRECATED."""
code = 409 code = 409
@@ -109,7 +109,7 @@ class HTTPConflict(Conflict):
class OverLimit(HTTPException): class OverLimit(HTTPException):
"""DEPRECATED""" """DEPRECATED."""
code = 413 code = 413
@@ -130,7 +130,7 @@ class HTTPBadGateway(HTTPException):
class ServiceUnavailable(HTTPException): class ServiceUnavailable(HTTPException):
"""DEPRECATED""" """DEPRECATED."""
code = 503 code = 503
@@ -154,10 +154,10 @@ def from_response(response):
class NoTokenLookupException(Exception): class NoTokenLookupException(Exception):
"""DEPRECATED""" """DEPRECATED."""
pass pass
class EndpointNotFound(Exception): class EndpointNotFound(Exception):
"""DEPRECATED""" """DEPRECATED."""
pass pass

View File

@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4 # vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC. # Copyright 2011 OpenStack Foundation.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@@ -1,335 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# Copyright 2012-2013 Hewlett-Packard Development Company, L.P.
# 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.
"""
Utilities with minimum-depends for use in setup.py
"""
import email
import os
import re
import subprocess
import sys
from setuptools.command import sdist
def parse_mailmap(mailmap='.mailmap'):
mapping = {}
if os.path.exists(mailmap):
with open(mailmap, 'r') as fp:
for l in fp:
try:
canonical_email, alias = re.match(
r'[^#]*?(<.+>).*(<.+>).*', l).groups()
except AttributeError:
continue
mapping[alias] = canonical_email
return mapping
def canonicalize_emails(changelog, mapping):
"""Takes in a string and an email alias mapping and replaces all
instances of the aliases in the string with their real email.
"""
for alias, email_address in mapping.iteritems():
changelog = changelog.replace(alias, email_address)
return changelog
# Get requirements from the first file that exists
def get_reqs_from_files(requirements_files):
for requirements_file in requirements_files:
if os.path.exists(requirements_file):
with open(requirements_file, 'r') as fil:
return fil.read().split('\n')
return []
def parse_requirements(requirements_files=['requirements.txt',
'tools/pip-requires']):
requirements = []
for line in get_reqs_from_files(requirements_files):
# For the requirements list, we need to inject only the portion
# after egg= so that distutils knows the package it's looking for
# such as:
# -e git://github.com/openstack/nova/master#egg=nova
if re.match(r'\s*-e\s+', line):
requirements.append(re.sub(r'\s*-e\s+.*#egg=(.*)$', r'\1',
line))
# such as:
# http://github.com/openstack/nova/zipball/master#egg=nova
elif re.match(r'\s*https?:', line):
requirements.append(re.sub(r'\s*https?:.*#egg=(.*)$', r'\1',
line))
# -f lines are for index locations, and don't get used here
elif re.match(r'\s*-f\s+', line):
pass
# argparse is part of the standard library starting with 2.7
# adding it to the requirements list screws distro installs
elif line == 'argparse' and sys.version_info >= (2, 7):
pass
else:
requirements.append(line)
return requirements
def parse_dependency_links(requirements_files=['requirements.txt',
'tools/pip-requires']):
dependency_links = []
# dependency_links inject alternate locations to find packages listed
# in requirements
for line in get_reqs_from_files(requirements_files):
# skip comments and blank lines
if re.match(r'(\s*#)|(\s*$)', line):
continue
# lines with -e or -f need the whole line, minus the flag
if re.match(r'\s*-[ef]\s+', line):
dependency_links.append(re.sub(r'\s*-[ef]\s+', '', line))
# lines that are only urls can go in unmolested
elif re.match(r'\s*https?:', line):
dependency_links.append(line)
return dependency_links
def _run_shell_command(cmd, throw_on_error=False):
if os.name == 'nt':
output = subprocess.Popen(["cmd.exe", "/C", cmd],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
else:
output = subprocess.Popen(["/bin/sh", "-c", cmd],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
if output.returncode and throw_on_error:
raise Exception("%s returned %d" % cmd, output.returncode)
out = output.communicate()
if len(out) == 0:
return None
if len(out[0].strip()) == 0:
return None
return out[0].strip()
def write_git_changelog():
"""Write a changelog based on the git changelog."""
new_changelog = 'ChangeLog'
if not os.getenv('SKIP_WRITE_GIT_CHANGELOG'):
if os.path.isdir('.git'):
git_log_cmd = 'git log --stat'
changelog = _run_shell_command(git_log_cmd)
mailmap = parse_mailmap()
with open(new_changelog, "w") as changelog_file:
changelog_file.write(canonicalize_emails(changelog, mailmap))
else:
open(new_changelog, 'w').close()
def generate_authors():
"""Create AUTHORS file using git commits."""
jenkins_email = 'jenkins@review.(openstack|stackforge).org'
old_authors = 'AUTHORS.in'
new_authors = 'AUTHORS'
if not os.getenv('SKIP_GENERATE_AUTHORS'):
if os.path.isdir('.git'):
# don't include jenkins email address in AUTHORS file
git_log_cmd = ("git log --format='%aN <%aE>' | sort -u | "
"egrep -v '" + jenkins_email + "'")
changelog = _run_shell_command(git_log_cmd)
mailmap = parse_mailmap()
with open(new_authors, 'w') as new_authors_fh:
new_authors_fh.write(canonicalize_emails(changelog, mailmap))
if os.path.exists(old_authors):
with open(old_authors, "r") as old_authors_fh:
new_authors_fh.write('\n' + old_authors_fh.read())
else:
open(new_authors, 'w').close()
_rst_template = """%(heading)s
%(underline)s
.. automodule:: %(module)s
:members:
:undoc-members:
:show-inheritance:
"""
def get_cmdclass():
"""Return dict of commands to run from setup.py."""
cmdclass = dict()
def _find_modules(arg, dirname, files):
for filename in files:
if filename.endswith('.py') and filename != '__init__.py':
arg["%s.%s" % (dirname.replace('/', '.'),
filename[:-3])] = True
class LocalSDist(sdist.sdist):
"""Builds the ChangeLog and Authors files from VC first."""
def run(self):
write_git_changelog()
generate_authors()
# sdist.sdist is an old style class, can't use super()
sdist.sdist.run(self)
cmdclass['sdist'] = LocalSDist
# If Sphinx is installed on the box running setup.py,
# enable setup.py to build the documentation, otherwise,
# just ignore it
try:
from sphinx.setup_command import BuildDoc
class LocalBuildDoc(BuildDoc):
builders = ['html', 'man']
def generate_autoindex(self):
print "**Autodocumenting from %s" % os.path.abspath(os.curdir)
modules = {}
option_dict = self.distribution.get_option_dict('build_sphinx')
source_dir = os.path.join(option_dict['source_dir'][1], 'api')
if not os.path.exists(source_dir):
os.makedirs(source_dir)
for pkg in self.distribution.packages:
if '.' not in pkg:
os.path.walk(pkg, _find_modules, modules)
module_list = modules.keys()
module_list.sort()
autoindex_filename = os.path.join(source_dir, 'autoindex.rst')
with open(autoindex_filename, 'w') as autoindex:
autoindex.write(""".. toctree::
:maxdepth: 1
""")
for module in module_list:
output_filename = os.path.join(source_dir,
"%s.rst" % module)
heading = "The :mod:`%s` Module" % module
underline = "=" * len(heading)
values = dict(module=module, heading=heading,
underline=underline)
print "Generating %s" % output_filename
with open(output_filename, 'w') as output_file:
output_file.write(_rst_template % values)
autoindex.write(" %s.rst\n" % module)
def run(self):
if not os.getenv('SPHINX_DEBUG'):
self.generate_autoindex()
for builder in self.builders:
self.builder = builder
self.finalize_options()
self.project = self.distribution.get_name()
self.version = self.distribution.get_version()
self.release = self.distribution.get_version()
BuildDoc.run(self)
class LocalBuildLatex(LocalBuildDoc):
builders = ['latex']
cmdclass['build_sphinx'] = LocalBuildDoc
cmdclass['build_sphinx_latex'] = LocalBuildLatex
except ImportError:
pass
return cmdclass
def _get_revno():
"""Return the number of commits since the most recent tag.
We use git-describe to find this out, but if there are no
tags then we fall back to counting commits since the beginning
of time.
"""
describe = _run_shell_command("git describe --always")
if "-" in describe:
return describe.rsplit("-", 2)[-2]
# no tags found
revlist = _run_shell_command("git rev-list --abbrev-commit HEAD")
return len(revlist.splitlines())
def get_version_from_git(pre_version):
"""Return a version which is equal to the tag that's on the current
revision if there is one, or tag plus number of additional revisions
if the current revision has no tag."""
if os.path.isdir('.git'):
if pre_version:
try:
return _run_shell_command(
"git describe --exact-match",
throw_on_error=True).replace('-', '.')
except Exception:
sha = _run_shell_command("git log -n1 --pretty=format:%h")
return "%s.a%s.g%s" % (pre_version, _get_revno(), sha)
else:
return _run_shell_command(
"git describe --always").replace('-', '.')
return None
def get_version_from_pkg_info(package_name):
"""Get the version from PKG-INFO file if we can."""
try:
pkg_info_file = open('PKG-INFO', 'r')
except (IOError, OSError):
return None
try:
pkg_info = email.message_from_file(pkg_info_file)
except email.MessageError:
return None
# Check to make sure we're in our own dir
if pkg_info.get('Name', None) != package_name:
return None
return pkg_info.get('Version', None)
def get_version(package_name, pre_version=None):
"""Get the version of the project. First, try getting it from PKG-INFO, if
it exists. If it does, that means we're in a distribution tarball or that
install has happened. Otherwise, if there is no PKG-INFO file, pull the
version from git.
We do not support setup.py version sanity in git archive tarballs, nor do
we support packagers directly sucking our git repo into theirs. We expect
that a source tarball be made from our git repo - or that if someone wants
to make a source tarball from a fork of our repo with additional tags in it
that they understand and desire the results of doing that.
"""
version = os.environ.get("OSLO_PACKAGE_VERSION", None)
if version:
return version
version = get_version_from_pkg_info(package_name)
if version:
return version
version = get_version_from_git(pre_version)
if version:
return version
raise Exception("Versioning for this project requires either an sdist"
" tarball, or access to an upstream git repository.")

View File

@@ -1,94 +0,0 @@
# Copyright 2012 OpenStack LLC
# Copyright 2012-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.
"""
Utilities for consuming the version from pkg_resources.
"""
import pkg_resources
class VersionInfo(object):
def __init__(self, package):
"""Object that understands versioning for a package
:param package: name of the python package, such as glance, or
python-glanceclient
"""
self.package = package
self.release = None
self.version = None
self._cached_version = None
def __str__(self):
"""Make the VersionInfo object behave like a string."""
return self.version_string()
def __repr__(self):
"""Include the name."""
return "VersionInfo(%s:%s)" % (self.package, self.version_string())
def _get_version_from_pkg_resources(self):
"""Get the version of the package from the pkg_resources record
associated with the package."""
try:
requirement = pkg_resources.Requirement.parse(self.package)
provider = pkg_resources.get_provider(requirement)
return provider.version
except pkg_resources.DistributionNotFound:
# The most likely cause for this is running tests in a tree with
# produced from a tarball where the package itself has not been
# installed into anything. Check for a PKG-INFO file.
from ceilometerclient.openstack.common import setup
return setup.get_version_from_pkg_info(self.package)
def release_string(self):
"""Return the full version of the package including suffixes indicating
VCS status.
"""
if self.release is None:
self.release = self._get_version_from_pkg_resources()
return self.release
def version_string(self):
"""Return the short version minus any alpha/beta tags."""
if self.version is None:
parts = []
for part in self.release_string().split('.'):
if part[0].isdigit():
parts.append(part)
else:
break
self.version = ".".join(parts)
return self.version
# Compatibility functions
canonical_version_string = version_string
version_string_with_vcs = release_string
def cached_version_string(self, prefix=""):
"""Generate an object which will expand in a string context to
the results of version_string(). We do this so that don't
call into pkg_resources every time we start up a program when
passing version information into the CONF constructor, but
rather only do the calculation when and if a version is requested
"""
if not self._cached_version:
self._cached_version = "%s%s" % (prefix,
self.version_string())
return self._cached_version

View File

@@ -19,13 +19,10 @@ import httplib2
import logging import logging
import sys import sys
from keystoneclient.v2_0 import client as ksclient import ceilometerclient
from ceilometerclient import client as ceiloclient
from ceilometerclient import exc
from ceilometerclient import client as ceilometerclient
from ceilometerclient.common import utils from ceilometerclient.common import utils
from ceilometerclient import exc
logger = logging.getLogger(__name__)
class CeilometerShell(object): class CeilometerShell(object):
@@ -46,6 +43,10 @@ class CeilometerShell(object):
help=argparse.SUPPRESS, help=argparse.SUPPRESS,
) )
parser.add_argument('--version',
action='version',
version=ceilometerclient.__version__)
parser.add_argument('-d', '--debug', parser.add_argument('-d', '--debug',
default=bool(utils.env('CEILOMETERCLIENT_DEBUG')), default=bool(utils.env('CEILOMETERCLIENT_DEBUG')),
action='store_true', action='store_true',
@@ -143,9 +144,9 @@ class CeilometerShell(object):
parser.add_argument('--ceilometer-api-version', parser.add_argument('--ceilometer-api-version',
default=utils.env( default=utils.env(
'CEILOMETER_API_VERSION', default='1'), 'CEILOMETER_API_VERSION', default='2'),
help='Defaults to env[CEILOMETER_API_VERSION] ' help='Defaults to env[CEILOMETER_API_VERSION] '
'or 1') 'or 2')
parser.add_argument('--ceilometer_api_version', parser.add_argument('--ceilometer_api_version',
help=argparse.SUPPRESS) help=argparse.SUPPRESS)
@@ -197,28 +198,6 @@ class CeilometerShell(object):
subparser.add_argument(*args, **kwargs) subparser.add_argument(*args, **kwargs)
subparser.set_defaults(func=callback) subparser.set_defaults(func=callback)
def _get_ksclient(self, **kwargs):
"""Get an endpoint and auth token from Keystone.
:param username: name of user
:param password: user's password
:param tenant_id: unique identifier of tenant
:param tenant_name: name of tenant
:param auth_url: endpoint to authenticate against
"""
return ksclient.Client(username=kwargs.get('username'),
password=kwargs.get('password'),
tenant_id=kwargs.get('tenant_id'),
tenant_name=kwargs.get('tenant_name'),
auth_url=kwargs.get('auth_url'),
insecure=kwargs.get('insecure'))
def _get_endpoint(self, client, **kwargs):
"""Get an endpoint using the provided keystone client."""
return client.service_catalog.url_for(
service_type=kwargs.get('service_type') or 'metering',
endpoint_type=kwargs.get('endpoint_type') or 'publicURL')
def _setup_debugging(self, debug): def _setup_debugging(self, debug):
if debug: if debug:
logging.basicConfig( logging.basicConfig(
@@ -252,10 +231,7 @@ class CeilometerShell(object):
self.do_help(args) self.do_help(args)
return 0 return 0
if args.os_auth_token and args.ceilometer_url: if not (args.os_auth_token and args.ceilometer_url):
token = args.os_auth_token
endpoint = args.ceilometer_url
else:
if not args.os_username: if not args.os_username:
raise exc.CommandError("You must provide a username via " raise exc.CommandError("You must provide a username via "
"either --os-username or via " "either --os-username or via "
@@ -275,32 +251,8 @@ class CeilometerShell(object):
raise exc.CommandError("You must provide an auth url via " raise exc.CommandError("You must provide an auth url via "
"either --os-auth-url or via " "either --os-auth-url or via "
"env[OS_AUTH_URL]") "env[OS_AUTH_URL]")
kwargs = {
'username': args.os_username,
'password': args.os_password,
'tenant_id': args.os_tenant_id,
'tenant_name': args.os_tenant_name,
'auth_url': args.os_auth_url,
'service_type': args.os_service_type,
'endpoint_type': args.os_endpoint_type,
'insecure': args.insecure
}
_ksclient = self._get_ksclient(**kwargs)
token = args.os_auth_token or _ksclient.auth_token
endpoint = args.ceilometer_url or \ client = ceiloclient.get_client(api_version, **(args.__dict__))
self._get_endpoint(_ksclient, **kwargs)
kwargs = {
'token': token,
'insecure': args.insecure,
'timeout': args.timeout,
'ca_file': args.ca_file,
'cert_file': args.cert_file,
'key_file': args.key_file,
}
client = ceilometerclient.Client(api_version, endpoint, **kwargs)
try: try:
args.func(client, args) args.func(client, args)
@@ -310,9 +262,7 @@ class CeilometerShell(object):
@utils.arg('command', metavar='<subcommand>', nargs='?', @utils.arg('command', metavar='<subcommand>', nargs='?',
help='Display help for <subcommand>') help='Display help for <subcommand>')
def do_help(self, args): def do_help(self, args):
""" """Display help about this program or one of its subcommands."""
Display help about this program or one of its subcommands.
"""
if getattr(args, 'command', None): if getattr(args, 'command', None):
if args.command in self.subcommands: if args.command in self.subcommands:
self.subcommands[args.command].print_help() self.subcommands[args.command].print_help()
@@ -334,7 +284,7 @@ def main():
try: try:
CeilometerShell().main(sys.argv[1:]) CeilometerShell().main(sys.argv[1:])
except Exception, e: except Exception as e:
print >> sys.stderr, e print >> sys.stderr, e
sys.exit(1) sys.exit(1)

View File

@@ -13,4 +13,4 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from ceilometerclient.v1.client import Client from ceilometerclient.v1.client import Client # noqa

View File

@@ -28,7 +28,7 @@ class Client(http.HTTPClient):
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
""" Initialize a new client for the Ceilometer v1 API. """ """Initialize a new client for the Ceilometer v1 API."""
super(Client, self).__init__(*args, **kwargs) super(Client, self).__init__(*args, **kwargs)
self.meters = meters.MeterManager(self) self.meters = meters.MeterManager(self)
self.samples = meters.SampleManager(self) self.samples = meters.SampleManager(self)

View File

@@ -36,7 +36,7 @@ import ceilometerclient.exc as exc
help='ISO date in UTC which limits events by ' help='ISO date in UTC which limits events by '
'timestamp <= this value') 'timestamp <= this value')
def do_sample_list(cc, args): def do_sample_list(cc, args):
'''List the samples for this meters''' '''List the samples for this meters.'''
fields = {'counter_name': args.counter_name, fields = {'counter_name': args.counter_name,
'resource_id': args.resource_id, 'resource_id': args.resource_id,
'user_id': args.user_id, 'user_id': args.user_id,
@@ -84,7 +84,7 @@ def do_meter_list(cc, args={}):
@utils.arg('-s', '--source', metavar='<SOURCE>', @utils.arg('-s', '--source', metavar='<SOURCE>',
help='ID of the resource to show projects for.') help='ID of the resource to show projects for.')
def do_user_list(cc, args={}): def do_user_list(cc, args={}):
'''List the users''' '''List the users.'''
kwargs = {'source': args.source} kwargs = {'source': args.source}
users = cc.users.list(**kwargs) users = cc.users.list(**kwargs)
field_labels = ['User ID'] field_labels = ['User ID']
@@ -108,7 +108,7 @@ def do_user_list(cc, args={}):
help='ISO date in UTC which limits resouces by ' help='ISO date in UTC which limits resouces by '
'last update time <= this value') 'last update time <= this value')
def do_resource_list(cc, args={}): def do_resource_list(cc, args={}):
'''List the resources''' '''List the resources.'''
kwargs = {'source': args.source, kwargs = {'source': args.source,
'user_id': args.user_id, 'user_id': args.user_id,
'project_id': args.project_id, 'project_id': args.project_id,
@@ -126,7 +126,7 @@ def do_resource_list(cc, args={}):
@utils.arg('-s', '--source', metavar='<SOURCE>', @utils.arg('-s', '--source', metavar='<SOURCE>',
help='ID of the resource to show projects for.') help='ID of the resource to show projects for.')
def do_project_list(cc, args={}): def do_project_list(cc, args={}):
'''List the projects''' '''List the projects.'''
kwargs = {'source': args.source} kwargs = {'source': args.source}
projects = cc.projects.list(**kwargs) projects = cc.projects.list(**kwargs)

View File

@@ -13,4 +13,4 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from ceilometerclient.v2.client import Client from ceilometerclient.v2.client import Client # noqa

View File

@@ -0,0 +1,72 @@
# -*- encoding: utf-8 -*-
#
# Copyright © 2013 Red Hat, Inc
#
# Author: Eoghan Glynn <eglynn@redhat.com>
#
# 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
UPDATABLE_ATTRIBUTES = [
'description',
'period',
'evaluation_periods',
'state',
'enabled',
'counter_name',
'statistic',
'comparison_operator',
'threshold',
'alarm_actions',
'ok_actions',
'insufficient_data_actions',
]
CREATION_ATTRIBUTES = UPDATABLE_ATTRIBUTES + ['name', 'project_id', 'user_id']
class Alarm(base.Resource):
def __repr__(self):
return "<Alarm %s>" % self._info
class AlarmManager(base.Manager):
resource_class = Alarm
@staticmethod
def _path(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
def create(self, **kwargs):
new = dict((key, value) for (key, value) in kwargs.items()
if key in CREATION_ATTRIBUTES)
return self._create(self._path(), new)
def update(self, alarm_id, **kwargs):
updated = dict((key, value) for (key, value) in kwargs.items()
if key in UPDATABLE_ATTRIBUTES)
return self._update(self._path(alarm_id), updated)
def delete(self, alarm_id):
return self._delete(self._path(alarm_id))

View File

@@ -14,6 +14,7 @@
# under the License. # under the License.
from ceilometerclient.common import http from ceilometerclient.common import http
from ceilometerclient.v2 import alarms
from ceilometerclient.v2 import meters from ceilometerclient.v2 import meters
from ceilometerclient.v2 import resources from ceilometerclient.v2 import resources
from ceilometerclient.v2 import samples from ceilometerclient.v2 import samples
@@ -31,9 +32,10 @@ class Client(http.HTTPClient):
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
""" Initialize a new client for the Ceilometer v1 API. """ """Initialize a new client for the Ceilometer v1 API."""
super(Client, self).__init__(*args, **kwargs) super(Client, self).__init__(*args, **kwargs)
self.meters = meters.MeterManager(self) self.meters = meters.MeterManager(self)
self.samples = samples.SampleManager(self) self.samples = samples.SampleManager(self)
self.statistics = statistics.StatisticsManager(self) self.statistics = statistics.StatisticsManager(self)
self.resources = resources.ResourceManager(self) self.resources = resources.ResourceManager(self)
self.alarms = alarms.AlarmManager(self)

View File

@@ -15,14 +15,15 @@ import re
import urllib import urllib
def build_url(path, q): def build_url(path, q, params=None):
''' '''This converts from a list of dicts and a list of params to
This converts from a list of dict's to what the rest api needs what the rest api needs, so from:
so from: "[{field=this,op=le,value=34},{field=that,op=eq,value=foo}],
"[{field=this,op=le,value=34},{field=that,op=eq,value=foo}]" ['foo=bar','sna=fu']"
to: to:
"?q.field=this&q.op=le&q.value=34& "?q.field=this&q.op=le&q.value=34&
q.field=that&q.op=eq&q.value=foo" q.field=that&q.op=eq&q.value=foo&
foo=bar&sna=fu"
''' '''
if q: if q:
query_params = {'q.field': [], query_params = {'q.field': [],
@@ -35,12 +36,15 @@ def build_url(path, q):
path += "?" + urllib.urlencode(query_params, doseq=True) path += "?" + urllib.urlencode(query_params, doseq=True)
if params:
for p in params:
path += '&%s' % p
return path return path
def cli_to_array(cli_query): def cli_to_array(cli_query):
''' '''This converts from the cli list of queries to what is required
This converts from the cli list of queries to what is required
by the python api. by the python api.
so from: so from:
"this<=34;that=foo" "this<=34;that=foo"

View File

@@ -29,3 +29,10 @@ class ResourceManager(base.Manager):
def list(self, q=None): def list(self, q=None):
path = '/v2/resources' path = '/v2/resources'
return self._list(options.build_url(path, q)) return self._list(options.build_url(path, q))
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

@@ -17,18 +17,26 @@
# under the License. # under the License.
from ceilometerclient.common import utils from ceilometerclient.common import utils
from ceilometerclient import exc
from ceilometerclient.v2 import options from ceilometerclient.v2 import options
import ceilometerclient.exc as exc
ALARM_STATES = ['ok', 'alarm', 'insufficient_data']
ALARM_OPERATORS = ['lt', 'le', 'eq', 'ne', 'ge', 'gt']
STATISTICS = ['max', 'min', 'avg', 'sum', 'count']
@utils.arg('-q', '--query', metavar='<QUERY>', @utils.arg('-q', '--query', metavar='<QUERY>',
help='key[op]value; list.') help='key[op]value; list.')
@utils.arg('-m', '--meter', metavar='<NAME>', @utils.arg('-m', '--meter', metavar='<NAME>',
help='Name of meter to show samples for.') help='Name of meter to show samples for.')
@utils.arg('-p', '--period', metavar='<PERIOD>',
help='Period in seconds over which to group samples.')
def do_statistics(cc, args): def do_statistics(cc, args):
'''List the statistics for this meters''' '''List the statistics for this meter.'''
fields = {'meter_name': args.meter, fields = {'meter_name': args.meter,
'q': options.cli_to_array(args.query)} 'q': options.cli_to_array(args.query),
'period': args.period}
if args.meter is None: if args.meter is None:
raise exc.CommandError('Meter name not provided (-m <meter name>)') raise exc.CommandError('Meter name not provided (-m <meter name>)')
try: try:
@@ -50,7 +58,7 @@ def do_statistics(cc, args):
@utils.arg('-m', '--meter', metavar='<NAME>', @utils.arg('-m', '--meter', metavar='<NAME>',
help='Name of meter to show samples for.') help='Name of meter to show samples for.')
def do_sample_list(cc, args): def do_sample_list(cc, args):
'''List the samples for this meters''' '''List the samples for this meters.'''
fields = {'meter_name': args.meter, fields = {'meter_name': args.meter,
'q': options.cli_to_array(args.query)} 'q': options.cli_to_array(args.query)}
if args.meter is None: if args.meter is None:
@@ -71,7 +79,7 @@ def do_sample_list(cc, args):
@utils.arg('-q', '--query', metavar='<QUERY>', @utils.arg('-q', '--query', metavar='<QUERY>',
help='key[op]value; list.') help='key[op]value; list.')
def do_meter_list(cc, args={}): def do_meter_list(cc, args={}):
'''List the user's meter''' '''List the user's meters.'''
meters = cc.meters.list(q=options.cli_to_array(args.query)) meters = cc.meters.list(q=options.cli_to_array(args.query))
field_labels = ['Name', 'Type', 'Unit', 'Resource ID', 'User ID', field_labels = ['Name', 'Type', 'Unit', 'Resource ID', 'User ID',
'Project ID'] 'Project ID']
@@ -81,13 +89,167 @@ def do_meter_list(cc, args={}):
sortby=0) sortby=0)
@utils.arg('-q', '--query', metavar='<QUERY>',
help='key[op]value; list.')
def do_alarm_list(cc, args={}):
'''List the user's alarms.'''
alarms = cc.alarms.list(q=options.cli_to_array(args.query))
# omit action initially to keep output width sane
# (can switch over to vertical formatting when available from CLIFF)
field_labels = ['Name', 'Description', 'Metric', 'Period', 'Count',
'Threshold', 'Comparison', 'State', 'Enabled', 'Alarm ID',
'User ID', 'Project ID']
fields = ['name', 'description', 'counter_name', 'period',
'evaluation_periods', 'threshold', 'comparison_operator',
'state', 'enabled', 'alarm_id', 'user_id', 'project_id']
utils.print_list(alarms, fields, field_labels,
sortby=0)
def _display_alarm(alarm):
fields = ['name', 'description', 'counter_name', 'period',
'evaluation_periods', 'threshold', 'comparison_operator',
'state', 'enabled', 'alarm_id', 'user_id', 'project_id',
'alarm_actions', 'ok_actions', 'insufficient_data_actions']
data = dict([(f, getattr(alarm, f, '')) for f in fields])
utils.print_dict(data, wrap=72)
@utils.arg('-a', '--alarm_id', metavar='<ALARM_ID>',
help='ID of the alarm to show.')
def do_alarm_show(cc, args={}):
'''Show an alarm.'''
if args.alarm_id is None:
raise exc.CommandError('Alarm ID not provided (-a <alarm id>)')
try:
alarm = cc.alarms.get(args.alarm_id)
except exc.HTTPNotFound:
raise exc.CommandError('Alarm not found: %s' % args.alarm_id)
else:
_display_alarm(alarm)
@utils.arg('--name', metavar='<NAME>',
help='Name of the alarm (must be unique per tenant)')
@utils.arg('--project-id', metavar='<PROJECT_ID>',
help='Tenant to associate with alarm '
'(only settable by admin users)')
@utils.arg('--user-id', metavar='<USER_ID>',
help='User to associate with alarm '
'(only settable by admin users)')
@utils.arg('--description', metavar='<DESCRIPTION>',
help='Free text description of the alarm')
@utils.arg('--period', type=int, metavar='<PERIOD>',
help='Length of each period (seconds) to evaluate over')
@utils.arg('--evaluation-periods', type=int, metavar='<COUNT>',
help='Number of periods to evaluate over')
@utils.arg('--state', metavar='<STATE>',
help='State of the alarm, one of: ' + str(ALARM_STATES))
@utils.arg('--enabled', type=utils.string_to_bool, metavar='{True|False}',
help='True if alarm evaluation/actioning is enabled')
@utils.arg('--counter-name', metavar='<METRIC>',
help='Metric to evaluate against')
@utils.arg('--statistic', metavar='<STATISTIC>',
help='Statistic to evaluate, one of: ' + str(STATISTICS))
@utils.arg('--comparison-operator', metavar='<OPERATOR>',
help='Operator to compare with, one of: ' + str(ALARM_OPERATORS))
@utils.arg('--threshold', type=float, metavar='<THRESHOLD>',
help='Threshold to evaluate against')
@utils.arg('--alarm-action', dest='alarm_actions',
metavar='<Webhook URL>', action='append', default=None,
help=('URL to invoke when state transitions to alarm. '
'May be used multiple times.'))
@utils.arg('--ok-action', dest='ok_actions',
metavar='<Webhook URL>', action='append', default=None,
help=('URL to invoke when state transitions to OK. '
'May be used multiple times.'))
@utils.arg('--insufficient-data-action', dest='insufficient_data_actions',
metavar='<Webhook URL>', action='append', default=None,
help=('URL to invoke when state transitions to unkown. '
'May be used multiple times.'))
def do_alarm_create(cc, args={}):
'''Create a new alarm.'''
fields = dict(filter(lambda x: not (x[1] is None), vars(args).items()))
alarm = cc.alarms.create(**fields)
_display_alarm(alarm)
@utils.arg('-a', '--alarm_id', metavar='<ALARM_ID>',
help='ID of the alarm to update.')
@utils.arg('--description', metavar='<DESCRIPTION>',
help='Free text description of the alarm')
@utils.arg('--period', type=int, metavar='<PERIOD>',
help='Length of each period (seconds) to evaluate over')
@utils.arg('--evaluation-periods', type=int, metavar='<COUNT>',
help='Number of periods to evaluate over')
@utils.arg('--state', metavar='<STATE>',
help='State of the alarm, one of: ' + str(ALARM_STATES))
@utils.arg('--enabled', type=utils.string_to_bool, metavar='{True|False}',
help='True if alarm evaluation/actioning is enabled')
@utils.arg('--counter-name', metavar='<METRIC>',
help='Metric to evaluate against')
@utils.arg('--statistic', metavar='<STATISTIC>',
help='Statistic to evaluate, one of: ' + str(STATISTICS))
@utils.arg('--comparison-operator', metavar='<OPERATOR>',
help='Operator to compare with, one of: ' + str(ALARM_OPERATORS))
@utils.arg('--threshold', type=float, metavar='<THRESHOLD>',
help='Threshold to evaluate against')
@utils.arg('--alarm-action', dest='alarm_actions',
metavar='<Webhook URL>', action='append', default=None,
help=('URL to invoke when state transitions to alarm. '
'May be used multiple times.'))
@utils.arg('--ok-action', dest='ok_actions',
metavar='<Webhook URL>', action='append', default=None,
help=('URL to invoke when state transitions to OK. '
'May be used multiple times.'))
@utils.arg('--insufficient-data-action', dest='insufficient_data_actions',
metavar='<Webhook URL>', action='append', default=None,
help=('URL to invoke when state transitions to unkown. '
'May be used multiple times.'))
def do_alarm_update(cc, args={}):
'''Update an existing alarm.'''
fields = dict(filter(lambda x: not (x[1] is None), vars(args).items()))
fields.pop('alarm_id')
alarm = cc.alarms.update(args.alarm_id, **fields)
_display_alarm(alarm)
@utils.arg('-a', '--alarm_id', metavar='<ALARM_ID>',
help='ID of the alarm to show.')
def do_alarm_delete(cc, args={}):
'''Delete an alarm.'''
if args.alarm_id is None:
raise exc.CommandError('Alarm ID not provided (-a <alarm id>)')
try:
cc.alarms.delete(args.alarm_id)
except exc.HTTPNotFound:
raise exc.CommandError('Alarm not found: %s' % args.alarm_id)
@utils.arg('-q', '--query', metavar='<QUERY>', @utils.arg('-q', '--query', metavar='<QUERY>',
help='key[op]value; list.') help='key[op]value; list.')
def do_resource_list(cc, args={}): def do_resource_list(cc, args={}):
'''List the resources''' '''List the resources.'''
resources = cc.resources.list(q=options.cli_to_array(args.query)) resources = cc.resources.list(q=options.cli_to_array(args.query))
field_labels = ['Resource ID', 'Source', 'User ID', 'Project ID'] field_labels = ['Resource ID', 'Source', 'User ID', 'Project ID']
fields = ['resource_id', 'source', 'user_id', 'project_id'] fields = ['resource_id', 'source', 'user_id', 'project_id']
utils.print_list(resources, fields, field_labels, utils.print_list(resources, fields, field_labels,
sortby=1) sortby=1)
@utils.arg('-r', '--resource_id', metavar='<RESOURCE_ID>',
help='ID of the resource to show.')
def do_resource_show(cc, args={}):
'''Show the resource.'''
if args.resource_id is None:
raise exc.CommandError('Resource id not provided (-r <resource id>)')
try:
resource = cc.resources.get(args.resource_id)
except exc.HTTPNotFound:
raise exc.CommandError('Resource not found: %s' % args.resource_id)
else:
fields = ['resource_id', 'source', 'user_id',
'project_id', 'metadata']
data = dict([(f, getattr(resource, f, '')) for f in fields])
utils.print_dict(data, wrap=72)

View File

@@ -23,7 +23,8 @@ class Statistics(base.Resource):
class StatisticsManager(base.Manager): class StatisticsManager(base.Manager):
resource_class = Statistics resource_class = Statistics
def list(self, meter_name, q=None): def list(self, meter_name, q=None, period=None):
p = ['period=%s' % period] if period else None
return self._list(options.build_url( return self._list(options.build_url(
'/v2/meters/' + meter_name + '/statistics', '/v2/meters/' + meter_name + '/statistics',
q)) q, p))

View File

@@ -1,7 +1,8 @@
[DEFAULT] [DEFAULT]
# The list of modules to copy from openstack-common # The list of modules to copy from openstack-common
modules=setup,importutils,version module=importutils
module=install_venv_common
# The base module to hold the copy of openstack.common # The base module to hold the copy of openstack.common
base=ceilometerclient base=ceilometerclient

6
requirements.txt Normal file
View File

@@ -0,0 +1,6 @@
# This file is managed by openstack-depends
argparse
httplib2
iso8601>=0.1.4
prettytable>=0.6,<0.8
python-keystoneclient>=0.2,<0.3

View File

@@ -1,49 +1,164 @@
#!/bin/bash #!/bin/bash
set -eu
function usage { function usage {
echo "Usage: $0 [OPTION]..." echo "Usage: $0 [OPTION]..."
echo "Run python-ceilometerclient's test suite(s)" echo "Run python-ceilometerclient test suite"
echo "" echo ""
echo " -V, --virtual-env Always use virtualenv. Install automatically if not present"
echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment"
echo " -s, --no-site-packages Isolate the virtualenv from the global Python environment"
echo " -x, --stop Stop running tests after the first error or failure."
echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added."
echo " -p, --pep8 Just run pep8" echo " -p, --pep8 Just run pep8"
echo " -P, --no-pep8 Don't run pep8"
echo " -c, --coverage Generate coverage report"
echo " -h, --help Print this usage message" echo " -h, --help Print this usage message"
echo " --hide-elapsed Don't print the elapsed time for each test along with slow test list"
echo "" echo ""
echo "This script is deprecated and currently retained for compatibility." echo "Note: with no options specified, the script will try to run the tests in a virtual environment,"
echo 'You can run the full test suite for multiple environments by running "tox".' echo " If no virtualenv is found, the script will ask if you would like to create one. If you "
echo 'You can run tests for only python 2.7 by running "tox -e py27", or run only' echo " prefer to run tests NOT in a virtual environment, simply pass the -N option."
echo 'the pep8 tests with "tox -e pep8".'
exit exit
} }
command -v tox > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo 'This script requires "tox" to run.'
echo 'You can install it with "pip install tox".'
exit 1;
fi
just_pep8=0
function process_option { function process_option {
case "$1" in case "$1" in
-h|--help) usage;; -h|--help) usage;;
-p|--pep8) let just_pep8=1;; -V|--virtual-env) always_venv=1; never_venv=0;;
-N|--no-virtual-env) always_venv=0; never_venv=1;;
-s|--no-site-packages) no_site_packages=1;;
-f|--force) force=1;;
-p|--pep8) just_pep8=1;;
-P|--no-pep8) no_pep8=1;;
-c|--coverage) coverage=1;;
-*) testropts="$testropts $1";;
*) testrargs="$testrargs $1"
esac esac
} }
venv=.venv
with_venv=tools/with_venv.sh
always_venv=0
never_venv=0
force=0
no_site_packages=0
installvenvopts=
testrargs=
testropts=
wrapper=""
just_pep8=0
no_pep8=0
coverage=0
LANG=en_US.UTF-8
LANGUAGE=en_US:en
LC_ALL=C
for arg in "$@"; do for arg in "$@"; do
process_option $arg process_option $arg
done done
if [ $no_site_packages -eq 1 ]; then
installvenvopts="--no-site-packages"
fi
function init_testr {
if [ ! -d .testrepository ]; then
${wrapper} testr init
fi
}
function run_tests {
# Cleanup *pyc
${wrapper} find . -type f -name "*.pyc" -delete
if [ $coverage -eq 1 ]; then
# Do not test test_coverage_ext when gathering coverage.
if [ "x$testrargs" = "x" ]; then
testrargs="^(?!.*test_coverage_ext).*$"
fi
export PYTHON="${wrapper} coverage run --source novaclient --parallel-mode"
fi
# Just run the test suites in current environment
set +e
TESTRTESTS="$TESTRTESTS $testrargs"
echo "Running \`${wrapper} $TESTRTESTS\`"
${wrapper} $TESTRTESTS
RESULT=$?
set -e
copy_subunit_log
return $RESULT
}
function copy_subunit_log {
LOGNAME=`cat .testrepository/next-stream`
LOGNAME=$(($LOGNAME - 1))
LOGNAME=".testrepository/${LOGNAME}"
cp $LOGNAME subunit.log
}
function run_pep8 {
echo "Running flake8 ..."
${wrapper} flake8
}
TESTRTESTS="testr run --parallel $testropts"
if [ $never_venv -eq 0 ]
then
# Remove the virtual environment if --force used
if [ $force -eq 1 ]; then
echo "Cleaning virtualenv..."
rm -rf ${venv}
fi
if [ -e ${venv} ]; then
wrapper="${with_venv}"
else
if [ $always_venv -eq 1 ]; then
# Automatically install the virtualenv
python tools/install_venv.py $installvenvopts
wrapper="${with_venv}"
else
echo -e "No virtual environment found...create one? (Y/n) \c"
read use_ve
if [ "x$use_ve" = "xY" -o "x$use_ve" = "x" -o "x$use_ve" = "xy" ]; then
# Install the virtualenv and run the test suite in it
python tools/install_venv.py $installvenvopts
wrapper=${with_venv}
fi
fi
fi
fi
# Delete old coverage data from previous runs
if [ $coverage -eq 1 ]; then
${wrapper} coverage erase
fi
if [ $just_pep8 -eq 1 ]; then if [ $just_pep8 -eq 1 ]; then
tox -e pep8 run_pep8
exit exit
fi fi
tox -e py27 $toxargs 2>&1 | tee run_tests.err.log || exit init_testr
if [ ${PIPESTATUS[0]} -ne 0 ]; then run_tests
exit ${PIPESTATUS[0]}
# NOTE(sirp): we only want to run pep8 when we're running the full-test suite,
# not when we're running tests individually. To handle this, we need to
# distinguish between options (noseopts), which begin with a '-', and
# arguments (testrargs).
if [ -z "$testrargs" ]; then
if [ $no_pep8 -eq 0 ]; then
run_pep8
fi
fi fi
if [ -z "$toxargs" ]; then if [ $coverage -eq 1 ]; then
tox -e pep8 echo "Generating coverage report in covhtml/"
${wrapper} coverage combine
${wrapper} coverage html --include='novaclient/*' --omit='novaclient/openstack/common/*' -d covhtml -i
fi fi

View File

@@ -1,10 +1,33 @@
[nosetests] [metadata]
cover-package = ceilometerclient name = python-ceilometerclient
cover-html = true summary = OpenStack Metering API Client Library
cover-erase = true description-file =
cover-inclusive = true README.md
verbosity=2 author = OpenStack
detailed-errors=1 author-email = openstack-dev@lists.openstack.org
home-page = http://www.openstack.org/
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
Intended Audience :: System Administrators
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 2.6
[files]
packages =
ceilometerclient
[global]
setup-hooks =
pbr.hooks.setup_hook
[entry_points]
console_scripts =
ceilometer = ceilometerclient.shell:main
[build_sphinx] [build_sphinx]
source-dir = doc/source source-dir = doc/source

View File

@@ -1,3 +1,6 @@
#!/usr/bin/env python
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
@@ -6,48 +9,13 @@
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import os
import setuptools import setuptools
from ceilometerclient.openstack.common import setup
project = 'python-ceilometerclient'
def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()
setuptools.setup( setuptools.setup(
name=project, setup_requires=['d2to1>=0.2.10,<0.3', 'pbr>=0.5,<0.6'],
version=setup.get_version(project), d2to1=True)
author='Ceilometer Developers',
author_email='openstack-dev@lists.openstack.org',
description="Client library for ceilometer",
long_description=read('README.md'),
license='Apache',
url='https://github.com/openstack/python-ceilometerclient',
packages=setuptools.find_packages(exclude=['tests', 'tests.*']),
include_package_data=True,
install_requires=setup.parse_requirements(),
test_suite="nose.collector",
cmdclass=setup.get_cmdclass(),
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Console',
'Intended Audience :: Developers',
'Intended Audience :: Information Technology',
'License :: OSI Approved :: Apache Software License',
'Operating System :: OS Independent',
'Programming Language :: Python',
],
entry_points={
'console_scripts': ['ceilometer = ceilometerclient.shell:main']
},
dependency_links=setup.parse_dependency_links(),
tests_require=setup.parse_requirements(['tools/test-requires']),
setup_requires=['setuptools-git>=0.4'],
)

14
test-requirements.txt Normal file
View File

@@ -0,0 +1,14 @@
# Install bounded pep8/pyflakes first, then let flake8 install
pep8==1.4.5
pyflakes==0.7.2
flake8==2.0
hacking>=0.5.3,<0.6
coverage>=3.6
discover
fixtures>=0.3.12
mox>=0.5.3
python-subunit
sphinx>=1.1.2
testrepository>=0.0.13
testtools>=0.9.29

View File

@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import unittest
from tests import utils from tests import utils
from ceilometerclient.common import http from ceilometerclient.common import http
@@ -23,7 +21,7 @@ from ceilometerclient.common import http
fixtures = {} fixtures = {}
class HttpClientTest(unittest.TestCase): class HttpClientTest(utils.BaseTestCase):
def test_url_generation_trailing_slash_in_base(self): def test_url_generation_trailing_slash_in_base(self):
client = http.HTTPClient('http://localhost/') client = http.HTTPClient('http://localhost/')

View File

@@ -1,69 +1,43 @@
import cStringIO import cStringIO
import os
import httplib2 import httplib2
import re
import sys import sys
import mox import fixtures
import unittest from testtools import matchers
import unittest2
try:
import json
except ImportError:
import simplejson as json
from keystoneclient.v2_0 import client as ksclient from keystoneclient.v2_0 import client as ksclient
from ceilometerclient import exc from ceilometerclient import exc
from ceilometerclient import shell as ceilometer_shell
from ceilometerclient.v1 import client as v1client from ceilometerclient.v1 import client as v1client
import ceilometerclient.shell from tests import utils
FAKE_ENV = {'OS_USERNAME': 'username',
'OS_PASSWORD': 'password',
'OS_TENANT_NAME': 'tenant_name',
'OS_AUTH_URL': 'http://no.where'}
class ShellValidationTest(unittest.TestCase): class ShellTest(utils.BaseTestCase):
re_options = re.DOTALL | re.MULTILINE
def shell_error(self, argstr, error_match):
orig = sys.stderr
try:
sys.stderr = cStringIO.StringIO()
_shell = ceilometerclient.shell.CeilometerShell()
_shell.main(argstr.split())
except exc.CommandError as e:
self.assertRegexpMatches(e.__str__(), error_match)
else:
self.fail('Expected error matching: %s' % error_match)
finally:
err = sys.stderr.getvalue()
sys.stderr.close()
sys.stderr = orig
return err
class ShellTest(unittest2.TestCase):
# Patch os.environ to avoid required auth info. # Patch os.environ to avoid required auth info.
def make_env(self, exclude=None):
env = dict((k, v) for k, v in FAKE_ENV.items() if k != exclude)
self.useFixture(fixtures.MonkeyPatch('os.environ', env))
def setUp(self): def setUp(self):
self.m = mox.Mox() super(ShellTest, self).setUp()
self.m.StubOutWithMock(ksclient, 'Client') self.m.StubOutWithMock(ksclient, 'Client')
self.m.StubOutWithMock(v1client.Client, 'json_request') self.m.StubOutWithMock(v1client.Client, 'json_request')
self.m.StubOutWithMock(v1client.Client, 'raw_request') self.m.StubOutWithMock(v1client.Client, 'raw_request')
global _old_env
fake_env = {
'OS_USERNAME': 'username',
'OS_PASSWORD': 'password',
'OS_TENANT_NAME': 'tenant_name',
'OS_AUTH_URL': 'http://no.where',
}
_old_env, os.environ = os.environ, fake_env.copy()
def tearDown(self):
self.m.UnsetStubs()
global _old_env
os.environ = _old_env
def shell(self, argstr): def shell(self, argstr):
orig = sys.stdout orig = sys.stdout
try: try:
sys.stdout = cStringIO.StringIO() sys.stdout = cStringIO.StringIO()
_shell = ceilometerclient.shell.CeilometerShell() _shell = ceilometer_shell.CeilometerShell()
_shell.main(argstr.split()) _shell.main(argstr.split())
except SystemExit: except SystemExit:
exc_type, exc_value, exc_traceback = sys.exc_info() exc_type, exc_value, exc_traceback = sys.exc_info()
@@ -85,18 +59,21 @@ class ShellTest(unittest2.TestCase):
def test_help(self): def test_help(self):
required = [ required = [
'^usage: ceilometer', '.*?^usage: ceilometer',
'(?m)^See "ceilometer help COMMAND" for help on a specific command', '.*?^See "ceilometer help COMMAND" '
'for help on a specific command',
] ]
for argstr in ['--help', 'help']: for argstr in ['--help', 'help']:
help_text = self.shell(argstr) help_text = self.shell(argstr)
for r in required: for r in required:
self.assertRegexpMatches(help_text, r) self.assertThat(help_text,
matchers.MatchesRegex(r,
self.re_options))
def test_help_on_subcommand(self): def test_help_on_subcommand(self):
required = [ required = [
'^usage: ceilometer meter-list', '.*?^usage: ceilometer meter-list',
"(?m)^List the user's meter", ".*?^List the user's meter",
] ]
argstrings = [ argstrings = [
'help meter-list', 'help meter-list',
@@ -104,19 +81,9 @@ class ShellTest(unittest2.TestCase):
for argstr in argstrings: for argstr in argstrings:
help_text = self.shell(argstr) help_text = self.shell(argstr)
for r in required: for r in required:
self.assertRegexpMatches(help_text, r) self.assertThat(help_text,
matchers.MatchesRegex(r, self.re_options))
def test_auth_param(self): def test_auth_param(self):
class TokenContext(object): self.make_env(exclude='OS_USERNAME')
def __enter__(self):
fake_env = {
'OS_AUTH_TOKEN': 'token',
'CEILOMETER_URL': 'http://no.where'
}
self.old_env, os.environ = os.environ, fake_env.copy()
def __exit__(self, exc_type, exc_value, traceback):
os.environ = self.old_env
with TokenContext():
self.test_help() self.test_help()

47
tests/test_utils.py Normal file
View File

@@ -0,0 +1,47 @@
# Copyright 2013 OpenStack LLC.
# 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 cStringIO
import sys
from ceilometerclient.common import utils
from tests import utils as test_utils
class UtilsTest(test_utils.BaseTestCase):
def test_prettytable(self):
class Struct:
def __init__(self, **entries):
self.__dict__.update(entries)
# test that the prettytable output is wellformatted (left-aligned)
saved_stdout = sys.stdout
try:
sys.stdout = output_dict = cStringIO.StringIO()
utils.print_dict({'K': 'k', 'Key': 'Value'})
finally:
sys.stdout = saved_stdout
self.assertEqual(output_dict.getvalue(), '''\
+----------+-------+
| Property | Value |
+----------+-------+
| K | k |
| Key | Value |
+----------+-------+
''')

View File

@@ -14,11 +14,23 @@
# under the License. # under the License.
import copy import copy
import fixtures
import mox
import StringIO import StringIO
import testtools
from ceilometerclient.common import http from ceilometerclient.common import http
class BaseTestCase(testtools.TestCase):
def setUp(self):
super(BaseTestCase, self).setUp()
self.m = mox.Mox()
self.addCleanup(self.m.UnsetStubs)
self.useFixture(fixtures.FakeLogger())
class FakeAPI(object): class FakeAPI(object):
def __init__(self, fixtures): def __init__(self, fixtures):
self.fixtures = fixtures self.fixtures = fixtures
@@ -41,8 +53,7 @@ class FakeAPI(object):
class FakeResponse(object): class FakeResponse(object):
def __init__(self, headers, body=None, version=None): def __init__(self, headers, body=None, version=None):
""" """:param headers: dict representing HTTP response headers
:param headers: dict representing HTTP response headers
:param body: file-like object :param body: file-like object
""" """
self.headers = headers self.headers = headers
@@ -56,4 +67,3 @@ class FakeResponse(object):
def read(self, amt): def read(self, amt):
return self.body.read(amt) return self.body.read(amt)

View File

@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import unittest
import ceilometerclient.v1.meters import ceilometerclient.v1.meters
from tests import utils from tests import utils
@@ -107,9 +105,10 @@ fixtures = {
} }
class MeterManagerTest(unittest.TestCase): class MeterManagerTest(utils.BaseTestCase):
def setUp(self): def setUp(self):
super(MeterManagerTest, self).setUp()
self.api = utils.FakeAPI(fixtures) self.api = utils.FakeAPI(fixtures)
self.mgr = ceilometerclient.v1.meters.MeterManager(self.api) self.mgr = ceilometerclient.v1.meters.MeterManager(self.api)

View File

@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import unittest
import ceilometerclient.v1.meters import ceilometerclient.v1.meters
from tests import utils from tests import utils
@@ -32,17 +30,16 @@ fixtures = {
'/v1/sources/source_b/projects': { '/v1/sources/source_b/projects': {
'GET': ( 'GET': (
{}, {},
{'projects': [ {'projects': ['b']},
'b',
]},
), ),
}, },
} }
class ProjectManagerTest(unittest.TestCase): class ProjectManagerTest(utils.BaseTestCase):
def setUp(self): def setUp(self):
super(ProjectManagerTest, self).setUp()
self.api = utils.FakeAPI(fixtures) self.api = utils.FakeAPI(fixtures)
self.mgr = ceilometerclient.v1.meters.ProjectManager(self.api) self.mgr = ceilometerclient.v1.meters.ProjectManager(self.api)

View File

@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import unittest
import ceilometerclient.v1.meters import ceilometerclient.v1.meters
from tests import utils from tests import utils
@@ -106,9 +104,10 @@ fixtures = {
} }
class ResourceManagerTest(unittest.TestCase): class ResourceManagerTest(utils.BaseTestCase):
def setUp(self): def setUp(self):
super(ResourceManagerTest, self).setUp()
self.api = utils.FakeAPI(fixtures) self.api = utils.FakeAPI(fixtures)
self.mgr = ceilometerclient.v1.meters.ResourceManager(self.api) self.mgr = ceilometerclient.v1.meters.ResourceManager(self.api)
@@ -150,9 +149,11 @@ class ResourceManagerTest(unittest.TestCase):
self.assertEqual(resources[0].resource_id, 'a') self.assertEqual(resources[0].resource_id, 'a')
def test_list_by_timestamp(self): def test_list_by_timestamp(self):
resources = list(self.mgr.list(start_timestamp='now', end_timestamp='now')) resources = list(self.mgr.list(start_timestamp='now',
end_timestamp='now'))
expect = [ expect = [
('GET', '/v1/resources?start_timestamp=now&end_timestamp=now', {}, None), ('GET', '/v1/resources?start_timestamp=now&end_timestamp=now',
{}, None),
] ]
self.assertEqual(self.api.calls, expect) self.assertEqual(self.api.calls, expect)
self.assertEqual(len(resources), 1) self.assertEqual(len(resources), 1)

View File

@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import unittest
import ceilometerclient.v1.meters import ceilometerclient.v1.meters
from tests import utils from tests import utils
@@ -120,9 +118,10 @@ fixtures = {
} }
class SampleManagerTest(unittest.TestCase): class SampleManagerTest(utils.BaseTestCase):
def setUp(self): def setUp(self):
super(SampleManagerTest, self).setUp()
self.api = utils.FakeAPI(fixtures) self.api = utils.FakeAPI(fixtures)
self.mgr = ceilometerclient.v1.meters.SampleManager(self.api) self.mgr = ceilometerclient.v1.meters.SampleManager(self.api)

View File

@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import unittest
import ceilometerclient.v1.meters import ceilometerclient.v1.meters
from tests import utils from tests import utils
@@ -32,17 +30,16 @@ fixtures = {
'/v1/sources/source_b/users': { '/v1/sources/source_b/users': {
'GET': ( 'GET': (
{}, {},
{'users': [ {'users': ['b']},
'b',
]},
), ),
}, },
} }
class UserManagerTest(unittest.TestCase): class UserManagerTest(utils.BaseTestCase):
def setUp(self): def setUp(self):
super(UserManagerTest, self).setUp()
self.api = utils.FakeAPI(fixtures) self.api = utils.FakeAPI(fixtures)
self.mgr = ceilometerclient.v1.meters.UserManager(self.api) self.mgr = ceilometerclient.v1.meters.UserManager(self.api)

163
tests/v2/test_alarms.py Normal file
View File

@@ -0,0 +1,163 @@
# -*- encoding: utf-8 -*-
#
# Copyright © 2013 Red Hat, Inc
#
# Author: Eoghan Glynn <eglynn@redhat.com>
#
# 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 unittest
import ceilometerclient.v2.alarms
from tests import utils
AN_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'counter_name': u'storage.objects',
u'period': 240.0,
u'alarm_id': u'alarm-id',
u'state': u'ok',
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'name': 'SwiftObjectAlarm'}
CREATE_ALARM = copy.deepcopy(AN_ALARM)
del CREATE_ALARM['timestamp']
del CREATE_ALARM['state_timestamp']
del CREATE_ALARM['matching_metadata']
del CREATE_ALARM['alarm_id']
DELTA_ALARM = {u'alarm_actions': ['url1', 'url2'],
u'comparison_operator': u'lt',
u'threshold': 42.1}
UPDATED_ALARM = copy.deepcopy(AN_ALARM)
UPDATED_ALARM.update(DELTA_ALARM)
fixtures = {
'/v2/alarms':
{
'GET': (
{},
[AN_ALARM],
),
'POST': (
{},
CREATE_ALARM,
),
},
'/v2/alarms/alarm-id':
{
'GET': (
{},
AN_ALARM,
),
'PUT': (
{},
UPDATED_ALARM,
),
},
'/v2/alarms?q.op=&q.op=&q.value=project-id&q.value=SwiftObjectAlarm'
'&q.field=project_id&q.field=name':
{
'GET': (
{},
[AN_ALARM],
),
},
'/v2/alarms/victim-id':
{
'DELETE': (
{},
None,
),
},
}
class AlarmManagerTest(unittest.TestCase):
def setUp(self):
self.api = utils.FakeAPI(fixtures)
self.mgr = ceilometerclient.v2.alarms.AlarmManager(self.api)
def test_list_all(self):
alarms = list(self.mgr.list())
expect = [
('GET', '/v2/alarms', {}, None),
]
self.assertEqual(self.api.calls, expect)
self.assertEqual(len(alarms), 1)
self.assertEqual(alarms[0].alarm_id, '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.op=&q.op=&q.value=project-id&q.value='
'SwiftObjectAlarm&q.field=project_id&q.field=name',
{}, None),
]
self.assertEqual(self.api.calls, expect)
self.assertEqual(len(alarms), 1)
self.assertEqual(alarms[0].alarm_id, 'alarm-id')
def test_get(self):
alarm = self.mgr.get(alarm_id='alarm-id')
expect = [
('GET', '/v2/alarms/alarm-id', {}, None),
]
self.assertEqual(self.api.calls, expect)
self.assertTrue(alarm)
self.assertEqual(alarm.alarm_id, 'alarm-id')
def test_create(self):
alarm = self.mgr.create(**CREATE_ALARM)
expect = [
('POST', '/v2/alarms', {}, CREATE_ALARM),
]
self.assertEqual(self.api.calls, expect)
self.assertTrue(alarm)
def test_update(self):
alarm = self.mgr.update(alarm_id='alarm-id', **DELTA_ALARM)
expect = [
('PUT', '/v2/alarms/alarm-id', {}, DELTA_ALARM),
]
self.assertEqual(self.api.calls, expect)
self.assertTrue(alarm)
self.assertEqual(alarm.alarm_id, 'alarm-id')
for (key, value) in DELTA_ALARM.iteritems():
self.assertEqual(getattr(alarm, key), value)
def test_delete(self):
deleted = self.mgr.delete(alarm_id='victim-id')
expect = [
('DELETE', '/v2/alarms/victim-id', {}, None),
]
self.assertEqual(self.api.calls, expect)
self.assertTrue(deleted is None)

View File

@@ -11,12 +11,11 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import unittest
from ceilometerclient.v2 import options from ceilometerclient.v2 import options
from tests import utils
class BuildUrlTest(unittest.TestCase): class BuildUrlTest(utils.BaseTestCase):
def test_one(self): def test_one(self):
url = options.build_url('/', [{'field': 'this', url = options.build_url('/', [{'field': 'this',
@@ -42,31 +41,35 @@ class BuildUrlTest(unittest.TestCase):
self.assertEqual(url, '/?q.op=&q.value=43&q.field=this') self.assertEqual(url, '/?q.op=&q.value=43&q.field=this')
class CliTest(unittest.TestCase): class CliTest(utils.BaseTestCase):
def test_one(self): def test_one(self):
ar = options.cli_to_array('this<=34') ar = options.cli_to_array('this<=34')
self.assertEqual(ar, [{'field': 'this','op': 'le','value': '34'}]) self.assertEqual(ar, [{'field': 'this', 'op': 'le', 'value': '34'}])
def test_two(self): def test_two(self):
ar = options.cli_to_array('this<=34;that!=foo') ar = options.cli_to_array('this<=34;that!=foo')
self.assertEqual(ar, [{'field': 'this','op': 'le','value': '34'}, self.assertEqual(ar, [{'field': 'this', 'op': 'le', 'value': '34'},
{'field': 'that','op': 'ne','value': 'foo'}]) {'field': 'that', 'op': 'ne', 'value': 'foo'}])
def test_negative(self): def test_negative(self):
ar = options.cli_to_array('this>=-783') ar = options.cli_to_array('this>=-783')
self.assertEqual(ar, [{'field': 'this','op': 'ge','value': '-783'}]) self.assertEqual(ar, [{'field': 'this', 'op': 'ge', 'value': '-783'}])
def test_float(self): def test_float(self):
ar = options.cli_to_array('this<=283.347') ar = options.cli_to_array('this<=283.347')
self.assertEqual(ar, [{'field': 'this','op': 'le','value': '283.347'}]) self.assertEqual(ar, [{'field': 'this',
'op': 'le', 'value': '283.347'}])
def test_invalid_seperator(self): def test_invalid_seperator(self):
self.assertRaises(ValueError, options.cli_to_array, 'this=2.4,fooo=doof') self.assertRaises(ValueError, options.cli_to_array,
'this=2.4,fooo=doof')
def test_invalid_operator(self): def test_invalid_operator(self):
self.assertRaises(ValueError, options.cli_to_array, 'this=2.4;fooo-doof') self.assertRaises(ValueError, options.cli_to_array,
'this=2.4;fooo-doof')
def test_with_dot(self): def test_with_dot(self):
ar = options.cli_to_array('metadata.this<=34') ar = options.cli_to_array('metadata.this<=34')
self.assertEqual(ar, [{'field': 'metadata.this','op': 'le','value': '34'}]) self.assertEqual(ar, [{'field': 'metadata.this',
'op': 'le', 'value': '34'}])

105
tests/v2/test_resources.py Normal file
View File

@@ -0,0 +1,105 @@
# Copyright 2012 OpenStack LLC.
# 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 ceilometerclient.v2.resources
from tests import utils
fixtures = {
'/v2/resources': {
'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.op=&q.value=a&q.field=resource_id':
{
'GET': (
{},
[
{
'resource_id': 'a',
'project_id': 'project_bla',
'user_id': 'freddy',
'metadata': {'zxc_id': 'bla'},
},
]
),
},
'/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.api = utils.FakeAPI(fixtures)
self.mgr = ceilometerclient.v2.resources.ResourceManager(self.api)
def test_list_all(self):
resources = list(self.mgr.list())
expect = [
('GET', '/v2/resources', {}, None),
]
self.assertEqual(self.api.calls, expect)
self.assertEqual(len(resources), 2)
self.assertEqual(resources[0].resource_id, 'a')
self.assertEqual(resources[1].resource_id, 'b')
def test_list_one(self):
resource = self.mgr.get(resource_id='a')
expect = [
('GET', '/v2/resources/a', {}, None),
]
self.assertEqual(self.api.calls, expect)
self.assertTrue(resource)
self.assertEqual(resource.resource_id, 'a')
def test_list_by_query(self):
resources = list(self.mgr.list(q=[{"field": "resource_id",
"value": "a"},
]))
expect = [
('GET', '/v2/resources?q.op=&q.value=a&q.field=resource_id',
{}, None),
]
self.assertEqual(self.api.calls, expect)
self.assertEqual(len(resources), 1)
self.assertEqual(resources[0].resource_id, 'a')

View File

@@ -13,14 +13,14 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import unittest
import ceilometerclient.v2.samples import ceilometerclient.v2.samples
from tests import utils from tests import utils
base_url = '/v2/meters/instance'
args = 'q.op=&q.op=&q.value=foo&q.value=bar&q.field=resource_id&q.field=source'
fixtures = { fixtures = {
'/v2/meters/instance': base_url:
{ {
'GET': ( 'GET': (
{}, {},
@@ -40,7 +40,7 @@ fixtures = {
] ]
), ),
}, },
'/v2/meters/instance?q.op=&q.op=&q.value=foo&q.value=bar&q.field=resource_id&q.field=source': '%s?%s' % (base_url, args):
{ {
'GET': ( 'GET': (
{}, {},
@@ -50,9 +50,10 @@ fixtures = {
} }
class SampleManagerTest(unittest.TestCase): class SampleManagerTest(utils.BaseTestCase):
def setUp(self): def setUp(self):
super(SampleManagerTest, self).setUp()
self.api = utils.FakeAPI(fixtures) self.api = utils.FakeAPI(fixtures)
self.mgr = ceilometerclient.v2.samples.SampleManager(self.api) self.mgr = ceilometerclient.v2.samples.SampleManager(self.api)
@@ -73,10 +74,6 @@ class SampleManagerTest(unittest.TestCase):
{"field": "source", {"field": "source",
"value": "bar"}, "value": "bar"},
])) ]))
expect = [ expect = [('GET', '%s?%s' % (base_url, args), {}, None)]
('GET',
'/v2/meters/instance?q.op=&q.op=&q.value=foo&q.value=bar&q.field=resource_id&q.field=source',
{}, None),
]
self.assertEqual(self.api.calls, expect) self.assertEqual(self.api.calls, expect)
self.assertEqual(len(samples), 0) self.assertEqual(len(samples), 0)

View File

@@ -13,18 +13,13 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import unittest
import ceilometerclient.v2.statistics import ceilometerclient.v2.statistics
from tests import utils from tests import utils
base_url = '/v2/meters/instance/statistics'
fixtures = { qry = 'q.op=&q.op=&q.value=foo&q.value=bar&q.field=resource_id&q.field=source'
'/v2/meters/instance/statistics': period = '&period=60'
{ samples = [{
'GET': (
{},
[{
u'count': 135, u'count': 135,
u'duration_start': u'2013-02-04T10:51:42', u'duration_start': u'2013-02-04T10:51:42',
u'min': 1.0, u'min': 1.0,
@@ -35,31 +30,35 @@ fixtures = {
u'avg': 1.0, u'avg': 1.0,
u'sum': 135.0, u'sum': 135.0,
}] }]
fixtures = {
base_url:
{
'GET': (
{},
samples
), ),
}, },
'/v2/meters/instance/statistics?q.op=&q.op=&q.value=foo&q.value=bar&q.field=resource_id&q.field=source': '%s?%s' % (base_url, qry):
{ {
'GET': ( 'GET': (
{}, {},
[{ 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,
}]
), ),
} },
'%s?%s%s' % (base_url, qry, period):
{
'GET': (
{},
samples
),
},
} }
class StatisticsManagerTest(unittest.TestCase): class StatisticsManagerTest(utils.BaseTestCase):
def setUp(self): def setUp(self):
super(StatisticsManagerTest, self).setUp()
self.api = utils.FakeAPI(fixtures) self.api = utils.FakeAPI(fixtures)
self.mgr = ceilometerclient.v2.statistics.StatisticsManager(self.api) self.mgr = ceilometerclient.v2.statistics.StatisticsManager(self.api)
@@ -82,8 +81,24 @@ class StatisticsManagerTest(unittest.TestCase):
])) ]))
expect = [ expect = [
('GET', ('GET',
'/v2/meters/instance/statistics?q.op=&q.op=&q.value=foo&q.value=bar&q.field=resource_id&q.field=source', '%s?%s' % (base_url, qry), {}, None),
{}, None), ]
self.assertEqual(self.api.calls, expect)
self.assertEqual(len(stats), 1)
self.assertEqual(stats[0].count, 135)
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), {}, None),
] ]
self.assertEqual(self.api.calls, expect) self.assertEqual(self.api.calls, expect)
self.assertEqual(len(stats), 1) self.assertEqual(len(stats), 1)

74
tools/install_venv.py Normal file
View File

@@ -0,0 +1,74 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2010 OpenStack Foundation
# Copyright 2013 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.
import os
import sys
import install_venv_common as install_venv
def print_help(venv, root):
help = """
Ceilometerclient development environment setup is complete.
Ceilometerclient development uses virtualenv to track and manage Python
dependencies while in development and testing.
To activate the Ceilometerclient virtualenv for the extent of your current
shell session you can run:
$ source %s/bin/activate
Or, if you prefer, you can run commands in the virtualenv on a case by case
basis by running:
$ %s/tools/with_venv.sh <your command>
Also, make test will automatically use the virtualenv.
"""
print help % (venv, root)
def main(argv):
root = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
if os.environ.get('tools_path'):
root = os.environ['tools_path']
venv = os.path.join(root, '.venv')
if os.environ.get('venv'):
venv = os.environ['venv']
pip_requires = os.path.join(root, 'requirements.txt')
test_requires = os.path.join(root, 'test-requirements.txt')
py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1])
project = 'Ceilometerclient'
install = install_venv.InstallVenv(root, venv, pip_requires, test_requires,
py_version, project)
options = install.parse_args(argv)
install.check_python_version()
install.check_dependencies()
install.create_virtualenv(no_site_packages=options.no_site_packages)
install.install_dependencies()
install.post_process()
print_help(venv, root)
if __name__ == '__main__':
main(sys.argv)

View File

@@ -0,0 +1,221 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 OpenStack Foundation
# Copyright 2013 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.
"""Provides methods needed by installation script for OpenStack development
virtual environments.
Since this script is used to bootstrap a virtualenv from the system's Python
environment, it should be kept strictly compatible with Python 2.6.
Synced in from openstack-common
"""
from __future__ import print_function
import optparse
import os
import subprocess
import sys
class InstallVenv(object):
def __init__(self, root, venv, pip_requires, test_requires, py_version,
project):
self.root = root
self.venv = venv
self.pip_requires = pip_requires
self.test_requires = test_requires
self.py_version = py_version
self.project = project
def die(self, message, *args):
print(message % args, file=sys.stderr)
sys.exit(1)
def check_python_version(self):
if sys.version_info < (2, 6):
self.die("Need Python Version >= 2.6")
def run_command_with_code(self, cmd, redirect_output=True,
check_exit_code=True):
"""Runs a command in an out-of-process shell.
Returns the output of that command. Working directory is self.root.
"""
if redirect_output:
stdout = subprocess.PIPE
else:
stdout = None
proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout)
output = proc.communicate()[0]
if check_exit_code and proc.returncode != 0:
self.die('Command "%s" failed.\n%s', ' '.join(cmd), output)
return (output, proc.returncode)
def run_command(self, cmd, redirect_output=True, check_exit_code=True):
return self.run_command_with_code(cmd, redirect_output,
check_exit_code)[0]
def get_distro(self):
if (os.path.exists('/etc/fedora-release') or
os.path.exists('/etc/redhat-release')):
return Fedora(self.root, self.venv, self.pip_requires,
self.test_requires, self.py_version, self.project)
else:
return Distro(self.root, self.venv, self.pip_requires,
self.test_requires, self.py_version, self.project)
def check_dependencies(self):
self.get_distro().install_virtualenv()
def create_virtualenv(self, no_site_packages=True):
"""Creates the virtual environment and installs PIP.
Creates the virtual environment and installs PIP only into the
virtual environment.
"""
if not os.path.isdir(self.venv):
print('Creating venv...', end=' ')
if no_site_packages:
self.run_command(['virtualenv', '-q', '--no-site-packages',
self.venv])
else:
self.run_command(['virtualenv', '-q', self.venv])
print('done.')
print('Installing pip in venv...', end=' ')
if not self.run_command(['tools/with_venv.sh', 'easy_install',
'pip>1.0']).strip():
self.die("Failed to install pip.")
print('done.')
else:
print("venv already exists...")
pass
def pip_install(self, *args):
self.run_command(['tools/with_venv.sh',
'pip', 'install', '--upgrade'] + list(args),
redirect_output=False)
def install_dependencies(self):
print('Installing dependencies with pip (this can take a while)...')
# First things first, make sure our venv has the latest pip and
# distribute.
# NOTE: we keep pip at version 1.1 since the most recent version causes
# the .venv creation to fail. See:
# https://bugs.launchpad.net/nova/+bug/1047120
self.pip_install('pip==1.1')
self.pip_install('distribute')
# Install greenlet by hand - just listing it in the requires file does
# not
# get it installed in the right order
self.pip_install('greenlet')
self.pip_install('-r', self.pip_requires)
self.pip_install('-r', self.test_requires)
def post_process(self):
self.get_distro().post_process()
def parse_args(self, argv):
"""Parses command-line arguments."""
parser = optparse.OptionParser()
parser.add_option('-n', '--no-site-packages',
action='store_true',
help="Do not inherit packages from global Python "
"install")
return parser.parse_args(argv[1:])[0]
class Distro(InstallVenv):
def check_cmd(self, cmd):
return bool(self.run_command(['which', cmd],
check_exit_code=False).strip())
def install_virtualenv(self):
if self.check_cmd('virtualenv'):
return
if self.check_cmd('easy_install'):
print('Installing virtualenv via easy_install...', end=' ')
if self.run_command(['easy_install', 'virtualenv']):
print('Succeeded')
return
else:
print('Failed')
self.die('ERROR: virtualenv not found.\n\n%s development'
' requires virtualenv, please install it using your'
' favorite package management tool' % self.project)
def post_process(self):
"""Any distribution-specific post-processing gets done here.
In particular, this is useful for applying patches to code inside
the venv.
"""
pass
class Fedora(Distro):
"""This covers all Fedora-based distributions.
Includes: Fedora, RHEL, CentOS, Scientific Linux
"""
def check_pkg(self, pkg):
return self.run_command_with_code(['rpm', '-q', pkg],
check_exit_code=False)[1] == 0
def apply_patch(self, originalfile, patchfile):
self.run_command(['patch', '-N', originalfile, patchfile],
check_exit_code=False)
def install_virtualenv(self):
if self.check_cmd('virtualenv'):
return
if not self.check_pkg('python-virtualenv'):
self.die("Please install 'python-virtualenv'.")
super(Fedora, self).install_virtualenv()
def post_process(self):
"""Workaround for a bug in eventlet.
This currently affects RHEL6.1, but the fix can safely be
applied to all RHEL and Fedora distributions.
This can be removed when the fix is applied upstream.
Nova: https://bugs.launchpad.net/nova/+bug/884915
Upstream: https://bitbucket.org/which_linden/eventlet/issue/89
"""
# Install "patch" program if it's not there
if not self.check_pkg('patch'):
self.die("Please install 'patch'.")
# Apply the eventlet patch
self.apply_patch(os.path.join(self.venv, 'lib', self.py_version,
'site-packages',
'eventlet/green/subprocess.py'),
'contrib/redhat-eventlet.patch')

View File

@@ -1,5 +0,0 @@
argparse
prettytable>=0.6,<0.7
python-keystoneclient>=0.1.2
httplib2
iso8601>=0.1.4

View File

@@ -1,11 +0,0 @@
distribute>=0.6.24
mox
nose
nose-exclude
nosexcover
openstack.nose_plugin
nosehtmloutput
pep8==1.3.3
setuptools-git>=0.4
sphinx>=1.1.2
unittest2

View File

@@ -1,10 +1,4 @@
#!/bin/bash #!/bin/bash
TOOLS=`dirname $0`
command -v tox > /dev/null 2>&1 VENV=$TOOLS/../.venv
if [ $? -ne 0 ]; then source $VENV/bin/activate && $@
echo 'This script requires "tox" to run.'
echo 'You can install it with "pip install tox".'
exit 1;
fi
tox -evenv -- $@

51
tox.ini
View File

@@ -3,44 +3,27 @@ envlist = py26,py27,pep8
[testenv] [testenv]
setenv = VIRTUAL_ENV={envdir} setenv = VIRTUAL_ENV={envdir}
NOSE_WITH_OPENSTACK=1 LANG=en_US.UTF-8
NOSE_OPENSTACK_COLOR=1 LANGUAGE=en_US:en
NOSE_OPENSTACK_RED=0.05 LC_ALL=C
NOSE_OPENSTACK_YELLOW=0.025 deps = -r{toxinidir}/requirements.txt
NOSE_OPENSTACK_SHOW_ELAPSED=1 -r{toxinidir}/test-requirements.txt
deps = -r{toxinidir}/tools/pip-requires commands =
-r{toxinidir}/tools/test-requires python setup.py testr --testr-args='{posargs}'
commands = nosetests
[testenv:pep8]
deps = pep8==1.3.3
commands = pep8 --repeat --show-source ceilometerclient setup.py
[testenv:venv]
commands = {posargs}
[testenv:cover]
commands = nosetests --cover-erase --cover-package=ceilometerclient --with-xcoverage
[tox:jenkins] [tox:jenkins]
downloadcache = ~/cache/pip downloadcache = ~/cache/pip
[testenv:jenkins26] [testenv:pep8]
basepython = python2.6 commands = flake8
setenv = NOSE_WITH_XUNIT=1
deps = file://{toxinidir}/.cache.bundle
[testenv:jenkins27] [testenv:cover]
basepython = python2.7 commands = python setup.py testr --coverage --testr-args='{posargs}'
setenv = NOSE_WITH_XUNIT=1
deps = file://{toxinidir}/.cache.bundle
[testenv:jenkinscover] [testenv:venv]
deps = file://{toxinidir}/.cache.bundle
setenv = NOSE_WITH_XUNIT=1
commands = nosetests --cover-erase --cover-package=ceilometerclient --with-xcoverage
[testenv:jenkinsvenv]
deps = file://{toxinidir}/.cache.bundle
setenv = NOSE_WITH_XUNIT=1
commands = {posargs} commands = {posargs}
[flake8]
ignore = E121,E122,E123,E128,E711,E721,E712,H302
show-source = True
exclude = .venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,tools