Add basic functionality
Signed-off-by: Angus Salkeld <asalkeld@redhat.com>
This commit is contained in:
31
ceilometerclient/__init__.py
Normal file
31
ceilometerclient/__init__.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# 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 inspect
|
||||
import os
|
||||
|
||||
|
||||
def _get_ceilometerclient_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 = open(version_path).read().strip()
|
||||
else:
|
||||
version = "Unknown, couldn't find versioninfo file at %s"\
|
||||
% version_path
|
||||
|
||||
return version
|
||||
|
||||
|
||||
__version__ = _get_ceilometerclient_version()
|
19
ceilometerclient/client.py
Normal file
19
ceilometerclient/client.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# 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 utils
|
||||
|
||||
|
||||
def Client(version, *args, **kwargs):
|
||||
module = utils.import_versioned_module(version, 'client')
|
||||
client_class = getattr(module, 'Client')
|
||||
return client_class(*args, **kwargs)
|
0
ceilometerclient/common/__init__.py
Normal file
0
ceilometerclient/common/__init__.py
Normal file
131
ceilometerclient/common/base.py
Normal file
131
ceilometerclient/common/base.py
Normal file
@@ -0,0 +1,131 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Base utilities to build API operation managers and objects on top of.
|
||||
"""
|
||||
|
||||
import copy
|
||||
|
||||
|
||||
# Python 2.4 compat
|
||||
try:
|
||||
all
|
||||
except NameError:
|
||||
def all(iterable):
|
||||
return True not in (not x for x in iterable)
|
||||
|
||||
|
||||
def getid(obj):
|
||||
"""
|
||||
Abstracts the common pattern of allowing both an object or an object's ID
|
||||
(UUID) as a parameter when dealing with relationships.
|
||||
"""
|
||||
try:
|
||||
return obj.id
|
||||
except AttributeError:
|
||||
return obj
|
||||
|
||||
|
||||
class Manager(object):
|
||||
"""
|
||||
Managers interact with a particular type of API (servers, flavors, images,
|
||||
etc.) and provide CRUD operations for them.
|
||||
"""
|
||||
resource_class = None
|
||||
|
||||
def __init__(self, api):
|
||||
self.api = api
|
||||
|
||||
def _list(self, url, response_key, obj_class=None, body=None):
|
||||
resp, body = self.api.json_request('GET', url)
|
||||
|
||||
if obj_class is None:
|
||||
obj_class = self.resource_class
|
||||
|
||||
data = body[response_key]
|
||||
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):
|
||||
resp, body = self.api.json_request('PUT', url, body=body)
|
||||
# PUT requests may not return a body
|
||||
if body:
|
||||
return self.resource_class(self, body[response_key])
|
||||
|
||||
|
||||
class Resource(object):
|
||||
"""
|
||||
A resource represents a particular instance of an object (tenant, user,
|
||||
etc). This is pretty much just a bag for attributes.
|
||||
|
||||
:param manager: Manager object
|
||||
:param info: dictionary representing resource attributes
|
||||
:param loaded: prevent lazy-loading if set to True
|
||||
"""
|
||||
def __init__(self, manager, info, loaded=False):
|
||||
self.manager = manager
|
||||
self._info = info
|
||||
self._add_details(info)
|
||||
self._loaded = loaded
|
||||
|
||||
def _add_details(self, info):
|
||||
for (k, v) in info.iteritems():
|
||||
setattr(self, k, v)
|
||||
|
||||
def __getattr__(self, k):
|
||||
if k not in self.__dict__:
|
||||
#NOTE(bcwaldon): disallow lazy-loading if already loaded once
|
||||
if not self.is_loaded():
|
||||
self.get()
|
||||
return self.__getattr__(k)
|
||||
|
||||
raise AttributeError(k)
|
||||
else:
|
||||
return self.__dict__[k]
|
||||
|
||||
def __repr__(self):
|
||||
reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and
|
||||
k != 'manager')
|
||||
info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
|
||||
return "<%s %s>" % (self.__class__.__name__, info)
|
||||
|
||||
def get(self):
|
||||
# set_loaded() first ... so if we have to bail, we know we tried.
|
||||
self.set_loaded(True)
|
||||
if not hasattr(self.manager, 'get'):
|
||||
return
|
||||
|
||||
new = self.manager.get(self.id)
|
||||
if new:
|
||||
self._add_details(new._info)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, self.__class__):
|
||||
return False
|
||||
if hasattr(self, 'id') and hasattr(other, 'id'):
|
||||
return self.id == other.id
|
||||
return self._info == other._info
|
||||
|
||||
def is_loaded(self):
|
||||
return self._loaded
|
||||
|
||||
def set_loaded(self, val):
|
||||
self._loaded = val
|
||||
|
||||
def to_dict(self):
|
||||
return copy.deepcopy(self._info)
|
276
ceilometerclient/common/http.py
Normal file
276
ceilometerclient/common/http.py
Normal file
@@ -0,0 +1,276 @@
|
||||
# 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 copy
|
||||
import httplib
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
import StringIO
|
||||
import urlparse
|
||||
|
||||
try:
|
||||
import ssl
|
||||
except ImportError:
|
||||
#TODO(bcwaldon): Handle this failure more gracefully
|
||||
pass
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
# Python 2.5 compat fix
|
||||
if not hasattr(urlparse, 'parse_qsl'):
|
||||
import cgi
|
||||
urlparse.parse_qsl = cgi.parse_qsl
|
||||
|
||||
|
||||
from ceilometerclient import exc
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
USER_AGENT = 'python-ceilometerclient'
|
||||
CHUNKSIZE = 1024 * 64 # 64kB
|
||||
|
||||
|
||||
class HTTPClient(object):
|
||||
|
||||
def __init__(self, endpoint, **kwargs):
|
||||
self.endpoint = endpoint
|
||||
self.auth_token = kwargs.get('token')
|
||||
self.connection_params = self.get_connection_params(endpoint, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_connection_params(endpoint, **kwargs):
|
||||
parts = urlparse.urlparse(endpoint)
|
||||
|
||||
_args = (parts.hostname, parts.port, parts.path)
|
||||
_kwargs = {'timeout': float(kwargs.get('timeout', 600))}
|
||||
|
||||
if parts.scheme == 'https':
|
||||
_class = VerifiedHTTPSConnection
|
||||
_kwargs['ca_file'] = kwargs.get('ca_file', None)
|
||||
_kwargs['cert_file'] = kwargs.get('cert_file', None)
|
||||
_kwargs['key_file'] = kwargs.get('key_file', None)
|
||||
_kwargs['insecure'] = kwargs.get('insecure', False)
|
||||
elif parts.scheme == 'http':
|
||||
_class = httplib.HTTPConnection
|
||||
else:
|
||||
msg = 'Unsupported scheme: %s' % parts.scheme
|
||||
raise exc.InvalidEndpoint(msg)
|
||||
|
||||
return (_class, _args, _kwargs)
|
||||
|
||||
def get_connection(self):
|
||||
_class = self.connection_params[0]
|
||||
try:
|
||||
return _class(*self.connection_params[1],
|
||||
**self.connection_params[2])
|
||||
except httplib.InvalidURL:
|
||||
raise exc.InvalidEndpoint()
|
||||
|
||||
def log_curl_request(self, method, url, kwargs):
|
||||
curl = ['curl -i -X %s' % method]
|
||||
|
||||
for (key, value) in kwargs['headers'].items():
|
||||
header = '-H \'%s: %s\'' % (key, value)
|
||||
curl.append(header)
|
||||
|
||||
conn_params_fmt = [
|
||||
('key_file', '--key %s'),
|
||||
('cert_file', '--cert %s'),
|
||||
('ca_file', '--cacert %s'),
|
||||
]
|
||||
for (key, fmt) in conn_params_fmt:
|
||||
value = self.connection_params[2].get(key)
|
||||
if value:
|
||||
curl.append(fmt % value)
|
||||
|
||||
if self.connection_params[2].get('insecure'):
|
||||
curl.append('-k')
|
||||
|
||||
if 'body' in kwargs:
|
||||
curl.append('-d \'%s\'' % kwargs['body'])
|
||||
|
||||
curl.append('%s%s' % (self.endpoint, url))
|
||||
LOG.debug(' '.join(curl))
|
||||
|
||||
@staticmethod
|
||||
def log_http_response(resp, body=None):
|
||||
status = (resp.version / 10.0, resp.status, resp.reason)
|
||||
dump = ['\nHTTP/%.1f %s %s' % status]
|
||||
dump.extend(['%s: %s' % (k, v) for k, v in resp.getheaders()])
|
||||
dump.append('')
|
||||
if body:
|
||||
dump.extend([body, ''])
|
||||
LOG.debug('\n'.join(dump))
|
||||
|
||||
def _http_request(self, url, method, **kwargs):
|
||||
""" Send an http request with the specified characteristics.
|
||||
|
||||
Wrapper around httplib.HTTP(S)Connection.request to handle tasks such
|
||||
as setting headers and error handling.
|
||||
"""
|
||||
# Copy the kwargs so we can reuse the original in case of redirects
|
||||
kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
|
||||
kwargs['headers'].setdefault('User-Agent', USER_AGENT)
|
||||
if self.auth_token:
|
||||
kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
|
||||
|
||||
self.log_curl_request(method, url, kwargs)
|
||||
conn = self.get_connection()
|
||||
|
||||
try:
|
||||
conn_params = self.connection_params[1][2]
|
||||
conn_url = os.path.normpath('%s/%s' % (conn_params, url))
|
||||
conn.request(method, conn_url, **kwargs)
|
||||
resp = conn.getresponse()
|
||||
except socket.gaierror as e:
|
||||
message = "Error finding address for %(url)s: %(e)s" % locals()
|
||||
raise exc.InvalidEndpoint(message=message)
|
||||
except (socket.error, socket.timeout) as e:
|
||||
endpoint = self.endpoint
|
||||
message = "Error communicating with %(endpoint)s %(e)s" % locals()
|
||||
raise exc.CommunicationError(message=message)
|
||||
|
||||
body_iter = ResponseBodyIterator(resp)
|
||||
|
||||
# Read body into string if it isn't obviously image data
|
||||
if resp.getheader('content-type', None) != 'application/octet-stream':
|
||||
body_str = ''.join([chunk for chunk in body_iter])
|
||||
self.log_http_response(resp, body_str)
|
||||
body_iter = StringIO.StringIO(body_str)
|
||||
else:
|
||||
self.log_http_response(resp)
|
||||
|
||||
if 400 <= resp.status < 600:
|
||||
LOG.warn("Request returned failure status.")
|
||||
raise exc.from_response(resp)
|
||||
elif resp.status in (301, 302, 305):
|
||||
# Redirected. Reissue the request to the new location.
|
||||
return self._http_request(resp['location'], method, **kwargs)
|
||||
elif resp.status == 300:
|
||||
raise exc.from_response(resp)
|
||||
|
||||
return resp, body_iter
|
||||
|
||||
def json_request(self, method, url, **kwargs):
|
||||
kwargs.setdefault('headers', {})
|
||||
kwargs['headers'].setdefault('Content-Type', 'application/json')
|
||||
kwargs['headers'].setdefault('Accept', 'application/json')
|
||||
|
||||
if 'body' in kwargs:
|
||||
kwargs['body'] = json.dumps(kwargs['body'])
|
||||
|
||||
resp, body_iter = self._http_request(url, method, **kwargs)
|
||||
|
||||
if 'application/json' in resp.getheader('content-type', None):
|
||||
body = ''.join([chunk for chunk in body_iter])
|
||||
try:
|
||||
body = json.loads(body)
|
||||
except ValueError:
|
||||
LOG.error('Could not decode response body as JSON')
|
||||
else:
|
||||
body = None
|
||||
|
||||
return resp, body
|
||||
|
||||
def raw_request(self, method, url, **kwargs):
|
||||
kwargs.setdefault('headers', {})
|
||||
kwargs['headers'].setdefault('Content-Type',
|
||||
'application/octet-stream')
|
||||
return self._http_request(url, method, **kwargs)
|
||||
|
||||
|
||||
class VerifiedHTTPSConnection(httplib.HTTPSConnection):
|
||||
"""httplib-compatibile connection using client-side SSL authentication
|
||||
|
||||
:see http://code.activestate.com/recipes/
|
||||
577548-https-httplib-client-connection-with-certificate-v/
|
||||
"""
|
||||
|
||||
def __init__(self, host, port, key_file=None, cert_file=None,
|
||||
ca_file=None, timeout=None, insecure=False):
|
||||
httplib.HTTPSConnection.__init__(self, host, port, key_file=key_file,
|
||||
cert_file=cert_file)
|
||||
self.key_file = key_file
|
||||
self.cert_file = cert_file
|
||||
if ca_file is not None:
|
||||
self.ca_file = ca_file
|
||||
else:
|
||||
self.ca_file = self.get_system_ca_file()
|
||||
self.timeout = timeout
|
||||
self.insecure = insecure
|
||||
|
||||
def connect(self):
|
||||
"""
|
||||
Connect to a host on a given (SSL) port.
|
||||
If ca_file is pointing somewhere, use it to check Server Certificate.
|
||||
|
||||
Redefined/copied and extended from httplib.py:1105 (Python 2.6.x).
|
||||
This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to
|
||||
ssl.wrap_socket(), which forces SSL to check server certificate against
|
||||
our client certificate.
|
||||
"""
|
||||
sock = socket.create_connection((self.host, self.port), self.timeout)
|
||||
|
||||
if self._tunnel_host:
|
||||
self.sock = sock
|
||||
self._tunnel()
|
||||
|
||||
if self.insecure is True:
|
||||
kwargs = {'cert_reqs': ssl.CERT_NONE}
|
||||
else:
|
||||
kwargs = {'cert_reqs': ssl.CERT_REQUIRED, 'ca_certs': self.ca_file}
|
||||
|
||||
if self.cert_file:
|
||||
kwargs['certfile'] = self.cert_file
|
||||
if self.key_file:
|
||||
kwargs['keyfile'] = self.key_file
|
||||
|
||||
self.sock = ssl.wrap_socket(sock, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_system_ca_file():
|
||||
""""Return path to system default CA file"""
|
||||
# Standard CA file locations for Debian/Ubuntu, RedHat/Fedora,
|
||||
# Suse, FreeBSD/OpenBSD
|
||||
ca_path = ['/etc/ssl/certs/ca-certificates.crt',
|
||||
'/etc/pki/tls/certs/ca-bundle.crt',
|
||||
'/etc/ssl/ca-bundle.pem',
|
||||
'/etc/ssl/cert.pem']
|
||||
for ca in ca_path:
|
||||
if os.path.exists(ca):
|
||||
return ca
|
||||
return None
|
||||
|
||||
|
||||
class ResponseBodyIterator(object):
|
||||
"""A class that acts as an iterator over an HTTP response."""
|
||||
|
||||
def __init__(self, resp):
|
||||
self.resp = resp
|
||||
|
||||
def __iter__(self):
|
||||
while True:
|
||||
yield self.next()
|
||||
|
||||
def next(self):
|
||||
chunk = self.resp.read(CHUNKSIZE)
|
||||
if chunk:
|
||||
return chunk
|
||||
else:
|
||||
raise StopIteration()
|
122
ceilometerclient/common/utils.py
Normal file
122
ceilometerclient/common/utils.py
Normal file
@@ -0,0 +1,122 @@
|
||||
# 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 errno
|
||||
import hashlib
|
||||
import os
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
import prettytable
|
||||
|
||||
from ceilometerclient import exc
|
||||
from ceilometerclient.openstack.common import importutils
|
||||
|
||||
|
||||
# Decorator for cli-args
|
||||
def arg(*args, **kwargs):
|
||||
def _decorator(func):
|
||||
# Because of the sematics of decorator composition if we just append
|
||||
# to the options list positional options will appear to be backwards.
|
||||
func.__dict__.setdefault('arguments', []).insert(0, (args, kwargs))
|
||||
return func
|
||||
return _decorator
|
||||
|
||||
|
||||
def pretty_choice_list(l):
|
||||
return ', '.join("'%s'" % i for i in l)
|
||||
|
||||
|
||||
def print_list(objs, fields, field_labels, formatters={}, sortby=0):
|
||||
pt = prettytable.PrettyTable([f for f in field_labels], caching=False)
|
||||
pt.align = 'l'
|
||||
|
||||
for o in objs:
|
||||
row = []
|
||||
for field in fields:
|
||||
if field in formatters:
|
||||
row.append(formatters[field](o))
|
||||
else:
|
||||
data = getattr(o, field, None) or ''
|
||||
row.append(data)
|
||||
pt.add_row(row)
|
||||
print pt.get_string(sortby=field_labels[sortby])
|
||||
|
||||
|
||||
def print_dict(d, formatters={}):
|
||||
pt = prettytable.PrettyTable(['Property', 'Value'], caching=False)
|
||||
pt.align = 'l'
|
||||
|
||||
for field in d.keys():
|
||||
if field in formatters:
|
||||
pt.add_row([field, formatters[field](d[field])])
|
||||
else:
|
||||
pt.add_row([field, d[field]])
|
||||
print pt.get_string(sortby='Property')
|
||||
|
||||
|
||||
def find_resource(manager, name_or_id):
|
||||
"""Helper for the _find_* methods."""
|
||||
# first try to get entity as integer id
|
||||
try:
|
||||
if isinstance(name_or_id, int) or name_or_id.isdigit():
|
||||
return manager.get(int(name_or_id))
|
||||
except exc.NotFound:
|
||||
pass
|
||||
|
||||
# now try to get entity as uuid
|
||||
try:
|
||||
uuid.UUID(str(name_or_id))
|
||||
return manager.get(name_or_id)
|
||||
except (ValueError, exc.NotFound):
|
||||
pass
|
||||
|
||||
# finally try to find entity by name
|
||||
try:
|
||||
return manager.find(name=name_or_id)
|
||||
except exc.NotFound:
|
||||
msg = "No %s with a name or ID of '%s' exists." % \
|
||||
(manager.resource_class.__name__.lower(), name_or_id)
|
||||
raise exc.CommandError(msg)
|
||||
|
||||
|
||||
def string_to_bool(arg):
|
||||
return arg.strip().lower() in ('t', 'true', 'yes', '1')
|
||||
|
||||
|
||||
def env(*vars, **kwargs):
|
||||
"""Search for the first defined of possibly many env vars
|
||||
|
||||
Returns the first environment variable defined in vars, or
|
||||
returns the default defined in kwargs.
|
||||
"""
|
||||
for v in vars:
|
||||
value = os.environ.get(v, None)
|
||||
if value:
|
||||
return value
|
||||
return kwargs.get('default', '')
|
||||
|
||||
|
||||
def import_versioned_module(version, submodule=None):
|
||||
module = 'ceilometerclient.v%s' % version
|
||||
if submodule:
|
||||
module = '.'.join((module, submodule))
|
||||
return importutils.import_module(module)
|
||||
|
||||
|
||||
def exit(msg=''):
|
||||
if msg:
|
||||
print >> sys.stderr, msg
|
||||
sys.exit(1)
|
163
ceilometerclient/exc.py
Normal file
163
ceilometerclient/exc.py
Normal file
@@ -0,0 +1,163 @@
|
||||
# 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 sys
|
||||
|
||||
|
||||
class BaseException(Exception):
|
||||
"""An error occurred."""
|
||||
def __init__(self, message=None):
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return self.message or self.__class__.__doc__
|
||||
|
||||
|
||||
class CommandError(BaseException):
|
||||
"""Invalid usage of CLI"""
|
||||
|
||||
|
||||
class InvalidEndpoint(BaseException):
|
||||
"""The provided endpoint is invalid."""
|
||||
|
||||
|
||||
class CommunicationError(BaseException):
|
||||
"""Unable to communicate with server."""
|
||||
|
||||
|
||||
class ClientException(Exception):
|
||||
"""DEPRECATED"""
|
||||
|
||||
|
||||
class HTTPException(ClientException):
|
||||
"""Base exception for all HTTP-derived exceptions"""
|
||||
code = 'N/A'
|
||||
|
||||
def __init__(self, details=None):
|
||||
self.details = details
|
||||
|
||||
def __str__(self):
|
||||
return "%s (HTTP %s)" % (self.__class__.__name__, self.code)
|
||||
|
||||
|
||||
class HTTPMultipleChoices(HTTPException):
|
||||
code = 300
|
||||
|
||||
def __str__(self):
|
||||
self.details = ("Requested version of OpenStack Images API is not"
|
||||
"available.")
|
||||
return "%s (HTTP %s) %s" % (self.__class__.__name__, self.code,
|
||||
self.details)
|
||||
|
||||
|
||||
class BadRequest(HTTPException):
|
||||
"""DEPRECATED"""
|
||||
code = 400
|
||||
|
||||
|
||||
class HTTPBadRequest(BadRequest):
|
||||
pass
|
||||
|
||||
|
||||
class Unauthorized(HTTPException):
|
||||
"""DEPRECATED"""
|
||||
code = 401
|
||||
|
||||
|
||||
class HTTPUnauthorized(Unauthorized):
|
||||
pass
|
||||
|
||||
|
||||
class Forbidden(HTTPException):
|
||||
"""DEPRECATED"""
|
||||
code = 403
|
||||
|
||||
|
||||
class HTTPForbidden(Forbidden):
|
||||
pass
|
||||
|
||||
|
||||
class NotFound(HTTPException):
|
||||
"""DEPRECATED"""
|
||||
code = 404
|
||||
|
||||
|
||||
class HTTPNotFound(NotFound):
|
||||
pass
|
||||
|
||||
|
||||
class HTTPMethodNotAllowed(HTTPException):
|
||||
code = 405
|
||||
|
||||
|
||||
class Conflict(HTTPException):
|
||||
"""DEPRECATED"""
|
||||
code = 409
|
||||
|
||||
|
||||
class HTTPConflict(Conflict):
|
||||
pass
|
||||
|
||||
|
||||
class OverLimit(HTTPException):
|
||||
"""DEPRECATED"""
|
||||
code = 413
|
||||
|
||||
|
||||
class HTTPOverLimit(OverLimit):
|
||||
pass
|
||||
|
||||
|
||||
class HTTPInternalServerError(HTTPException):
|
||||
code = 500
|
||||
|
||||
|
||||
class HTTPNotImplemented(HTTPException):
|
||||
code = 501
|
||||
|
||||
|
||||
class HTTPBadGateway(HTTPException):
|
||||
code = 502
|
||||
|
||||
|
||||
class ServiceUnavailable(HTTPException):
|
||||
"""DEPRECATED"""
|
||||
code = 503
|
||||
|
||||
|
||||
class HTTPServiceUnavailable(ServiceUnavailable):
|
||||
pass
|
||||
|
||||
|
||||
#NOTE(bcwaldon): Build a mapping of HTTP codes to corresponding exception
|
||||
# classes
|
||||
_code_map = {}
|
||||
for obj_name in dir(sys.modules[__name__]):
|
||||
if obj_name.startswith('HTTP'):
|
||||
obj = getattr(sys.modules[__name__], obj_name)
|
||||
_code_map[obj.code] = obj
|
||||
|
||||
|
||||
def from_response(response):
|
||||
"""Return an instance of an HTTPException based on httplib response."""
|
||||
cls = _code_map.get(response.status, HTTPException)
|
||||
return cls()
|
||||
|
||||
|
||||
class NoTokenLookupException(Exception):
|
||||
"""DEPRECATED"""
|
||||
pass
|
||||
|
||||
|
||||
class EndpointNotFound(Exception):
|
||||
"""DEPRECATED"""
|
||||
pass
|
0
ceilometerclient/openstack/__init__.py
Normal file
0
ceilometerclient/openstack/__init__.py
Normal file
0
ceilometerclient/openstack/common/__init__.py
Normal file
0
ceilometerclient/openstack/common/__init__.py
Normal file
59
ceilometerclient/openstack/common/importutils.py
Normal file
59
ceilometerclient/openstack/common/importutils.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 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 related utilities and helper functions.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
|
||||
def import_class(import_str):
|
||||
"""Returns a class from a string including module and class"""
|
||||
mod_str, _sep, class_str = import_str.rpartition('.')
|
||||
try:
|
||||
__import__(mod_str)
|
||||
return getattr(sys.modules[mod_str], class_str)
|
||||
except (ValueError, AttributeError), exc:
|
||||
raise ImportError('Class %s cannot be found (%s)' %
|
||||
(class_str,
|
||||
traceback.format_exception(*sys.exc_info())))
|
||||
|
||||
|
||||
def import_object(import_str, *args, **kwargs):
|
||||
"""Import a class and return an instance of it."""
|
||||
return import_class(import_str)(*args, **kwargs)
|
||||
|
||||
|
||||
def import_object_ns(name_space, import_str, *args, **kwargs):
|
||||
"""
|
||||
Import a class and return an instance of it, first by trying
|
||||
to find the class in a default namespace, then failing back to
|
||||
a full path if not found in the default namespace.
|
||||
"""
|
||||
import_value = "%s.%s" % (name_space, import_str)
|
||||
try:
|
||||
return import_class(import_value)(*args, **kwargs)
|
||||
except ImportError:
|
||||
return import_class(import_str)(*args, **kwargs)
|
||||
|
||||
|
||||
def import_module(import_str):
|
||||
"""Import a module."""
|
||||
__import__(import_str)
|
||||
return sys.modules[import_str]
|
366
ceilometerclient/openstack/common/setup.py
Normal file
366
ceilometerclient/openstack/common/setup.py
Normal file
@@ -0,0 +1,366 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 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.
|
||||
|
||||
"""
|
||||
Utilities with minimum-depends for use in setup.py
|
||||
"""
|
||||
|
||||
import datetime
|
||||
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:
|
||||
l = l.strip()
|
||||
if not l.startswith('#') and ' ' in l:
|
||||
canonical_email, alias = [x for x in l.split(' ')
|
||||
if x.startswith('<')]
|
||||
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 in mapping.iteritems():
|
||||
changelog = changelog.replace(alias, email)
|
||||
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 write_requirements():
|
||||
venv = os.environ.get('VIRTUAL_ENV', None)
|
||||
if venv is not None:
|
||||
with open("requirements.txt", "w") as req_file:
|
||||
output = subprocess.Popen(["pip", "-E", venv, "freeze", "-l"],
|
||||
stdout=subprocess.PIPE)
|
||||
requirements = output.communicate()[0].strip()
|
||||
req_file.write(requirements)
|
||||
|
||||
|
||||
def _run_shell_command(cmd):
|
||||
if os.name == 'nt':
|
||||
output = subprocess.Popen(["cmd.exe", "/C", cmd],
|
||||
stdout=subprocess.PIPE)
|
||||
else:
|
||||
output = subprocess.Popen(["/bin/sh", "-c", cmd],
|
||||
stdout=subprocess.PIPE)
|
||||
out = output.communicate()
|
||||
if len(out) == 0:
|
||||
return None
|
||||
if len(out[0].strip()) == 0:
|
||||
return None
|
||||
return out[0].strip()
|
||||
|
||||
|
||||
def _get_git_next_version_suffix(branch_name):
|
||||
datestamp = datetime.datetime.now().strftime('%Y%m%d')
|
||||
if branch_name == 'milestone-proposed':
|
||||
revno_prefix = "r"
|
||||
else:
|
||||
revno_prefix = ""
|
||||
_run_shell_command("git fetch origin +refs/meta/*:refs/remotes/meta/*")
|
||||
milestone_cmd = "git show meta/openstack/release:%s" % branch_name
|
||||
milestonever = _run_shell_command(milestone_cmd)
|
||||
if milestonever:
|
||||
first_half = "%s~%s" % (milestonever, datestamp)
|
||||
else:
|
||||
first_half = datestamp
|
||||
|
||||
post_version = _get_git_post_version()
|
||||
# post version should look like:
|
||||
# 0.1.1.4.gcc9e28a
|
||||
# where the bit after the last . is the short sha, and the bit between
|
||||
# the last and second to last is the revno count
|
||||
(revno, sha) = post_version.split(".")[-2:]
|
||||
second_half = "%s%s.%s" % (revno_prefix, revno, sha)
|
||||
return ".".join((first_half, second_half))
|
||||
|
||||
|
||||
def _get_git_current_tag():
|
||||
return _run_shell_command("git tag --contains HEAD")
|
||||
|
||||
|
||||
def _get_git_tag_info():
|
||||
return _run_shell_command("git describe --tags")
|
||||
|
||||
|
||||
def _get_git_post_version():
|
||||
current_tag = _get_git_current_tag()
|
||||
if current_tag is not None:
|
||||
return current_tag
|
||||
else:
|
||||
tag_info = _get_git_tag_info()
|
||||
if tag_info is None:
|
||||
base_version = "0.0"
|
||||
cmd = "git --no-pager log --oneline"
|
||||
out = _run_shell_command(cmd)
|
||||
revno = len(out.split("\n"))
|
||||
sha = _run_shell_command("git describe --always")
|
||||
else:
|
||||
tag_infos = tag_info.split("-")
|
||||
base_version = "-".join(tag_infos[:-2])
|
||||
(revno, sha) = tag_infos[-2:]
|
||||
return "%s.%s.%s" % (base_version, revno, sha)
|
||||
|
||||
|
||||
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 read_versioninfo(project):
|
||||
"""Read the versioninfo file. If it doesn't exist, we're in a github
|
||||
zipball, and there's really no way to know what version we really
|
||||
are, but that should be ok, because the utility of that should be
|
||||
just about nil if this code path is in use in the first place."""
|
||||
versioninfo_path = os.path.join(project, 'versioninfo')
|
||||
if os.path.exists(versioninfo_path):
|
||||
with open(versioninfo_path, 'r') as vinfo:
|
||||
version = vinfo.read().strip()
|
||||
else:
|
||||
version = "0.0.0"
|
||||
return version
|
||||
|
||||
|
||||
def write_versioninfo(project, version):
|
||||
"""Write a simple file containing the version of the package."""
|
||||
with open(os.path.join(project, 'versioninfo'), 'w') as fil:
|
||||
fil.write("%s\n" % version)
|
||||
|
||||
|
||||
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):
|
||||
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 ['html', 'man']:
|
||||
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)
|
||||
cmdclass['build_sphinx'] = LocalBuildDoc
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
return cmdclass
|
||||
|
||||
|
||||
def get_git_branchname():
|
||||
for branch in _run_shell_command("git branch --color=never").split("\n"):
|
||||
if branch.startswith('*'):
|
||||
_branch_name = branch.split()[1].strip()
|
||||
if _branch_name == "(no":
|
||||
_branch_name = "no-branch"
|
||||
return _branch_name
|
||||
|
||||
|
||||
def get_pre_version(projectname, base_version):
|
||||
"""Return a version which is leading up to a version that will
|
||||
be released in the future."""
|
||||
if os.path.isdir('.git'):
|
||||
current_tag = _get_git_current_tag()
|
||||
if current_tag is not None:
|
||||
version = current_tag
|
||||
else:
|
||||
branch_name = os.getenv('BRANCHNAME',
|
||||
os.getenv('GERRIT_REFNAME',
|
||||
get_git_branchname()))
|
||||
version_suffix = _get_git_next_version_suffix(branch_name)
|
||||
version = "%s~%s" % (base_version, version_suffix)
|
||||
write_versioninfo(projectname, version)
|
||||
return version
|
||||
else:
|
||||
version = read_versioninfo(projectname)
|
||||
return version
|
||||
|
||||
|
||||
def get_post_version(projectname):
|
||||
"""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'):
|
||||
version = _get_git_post_version()
|
||||
write_versioninfo(projectname, version)
|
||||
return version
|
||||
return read_versioninfo(projectname)
|
148
ceilometerclient/openstack/common/version.py
Normal file
148
ceilometerclient/openstack/common/version.py
Normal file
@@ -0,0 +1,148 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack LLC
|
||||
#
|
||||
# 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 auto-generated versioninfo files.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import pkg_resources
|
||||
|
||||
import setup
|
||||
|
||||
|
||||
class _deferred_version_string(object):
|
||||
"""Internal helper class which provides delayed version calculation."""
|
||||
def __init__(self, version_info, prefix):
|
||||
self.version_info = version_info
|
||||
self.prefix = prefix
|
||||
|
||||
def __str__(self):
|
||||
return "%s%s" % (self.prefix, self.version_info.version_string())
|
||||
|
||||
def __repr__(self):
|
||||
return "%s%s" % (self.prefix, self.version_info.version_string())
|
||||
|
||||
|
||||
class VersionInfo(object):
|
||||
|
||||
def __init__(self, package, python_package=None, pre_version=None):
|
||||
"""Object that understands versioning for a package
|
||||
:param package: name of the top level python namespace. For glance,
|
||||
this would be "glance" for python-glanceclient, it
|
||||
would be "glanceclient"
|
||||
:param python_package: optional name of the project name. For
|
||||
glance this can be left unset. For
|
||||
python-glanceclient, this would be
|
||||
"python-glanceclient"
|
||||
:param pre_version: optional version that the project is working to
|
||||
"""
|
||||
self.package = package
|
||||
if python_package is None:
|
||||
self.python_package = package
|
||||
else:
|
||||
self.python_package = python_package
|
||||
self.pre_version = pre_version
|
||||
self.version = None
|
||||
|
||||
def _generate_version(self):
|
||||
"""Defer to the openstack.common.setup routines for making a
|
||||
version from git."""
|
||||
if self.pre_version is None:
|
||||
return setup.get_post_version(self.python_package)
|
||||
else:
|
||||
return setup.get_pre_version(self.python_package, self.pre_version)
|
||||
|
||||
def _newer_version(self, pending_version):
|
||||
"""Check to see if we're working with a stale version or not.
|
||||
We expect a version string that either looks like:
|
||||
2012.2~f3~20120708.10.4426392
|
||||
which is an unreleased version of a pre-version, or:
|
||||
0.1.1.4.gcc9e28a
|
||||
which is an unreleased version of a post-version, or:
|
||||
0.1.1
|
||||
Which is a release and which should match tag.
|
||||
For now, if we have a date-embedded version, check to see if it's
|
||||
old, and if so re-generate. Otherwise, just deal with it.
|
||||
"""
|
||||
try:
|
||||
version_date = int(self.version.split("~")[-1].split('.')[0])
|
||||
if version_date < int(datetime.date.today().strftime('%Y%m%d')):
|
||||
return self._generate_version()
|
||||
else:
|
||||
return pending_version
|
||||
except Exception:
|
||||
return pending_version
|
||||
|
||||
def version_string_with_vcs(self, always=False):
|
||||
"""Return the full version of the package including suffixes indicating
|
||||
VCS status.
|
||||
|
||||
For instance, if we are working towards the 2012.2 release,
|
||||
canonical_version_string should return 2012.2 if this is a final
|
||||
release, or else something like 2012.2~f1~20120705.20 if it's not.
|
||||
|
||||
:param always: if true, skip all version caching
|
||||
"""
|
||||
if always:
|
||||
self.version = self._generate_version()
|
||||
|
||||
if self.version is None:
|
||||
|
||||
requirement = pkg_resources.Requirement.parse(self.python_package)
|
||||
versioninfo = "%s/versioninfo" % self.package
|
||||
try:
|
||||
raw_version = pkg_resources.resource_string(requirement,
|
||||
versioninfo)
|
||||
self.version = self._newer_version(raw_version.strip())
|
||||
except (IOError, pkg_resources.DistributionNotFound):
|
||||
self.version = self._generate_version()
|
||||
|
||||
return self.version
|
||||
|
||||
def canonical_version_string(self, always=False):
|
||||
"""Return the simple version of the package excluding any suffixes.
|
||||
|
||||
For instance, if we are working towards the 2012.2 release,
|
||||
canonical_version_string should return 2012.2 in all cases.
|
||||
|
||||
:param always: if true, skip all version caching
|
||||
"""
|
||||
return self.version_string_with_vcs(always).split('~')[0]
|
||||
|
||||
def version_string(self, always=False):
|
||||
"""Return the base version of the package.
|
||||
|
||||
For instance, if we are working towards the 2012.2 release,
|
||||
version_string should return 2012.2 if this is a final release, or
|
||||
2012.2-dev if it is not.
|
||||
|
||||
:param always: if true, skip all version caching
|
||||
"""
|
||||
version_parts = self.version_string_with_vcs(always).split('~')
|
||||
if len(version_parts) == 1:
|
||||
return version_parts[0]
|
||||
else:
|
||||
return '%s-dev' % (version_parts[0],)
|
||||
|
||||
def deferred_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
|
||||
"""
|
||||
return _deferred_version_string(self, prefix)
|
335
ceilometerclient/shell.py
Normal file
335
ceilometerclient/shell.py
Normal file
@@ -0,0 +1,335 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Command-line interface to the OpenStack Metrices API.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import httplib2
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from keystoneclient.v2_0 import client as ksclient
|
||||
|
||||
from ceilometerclient import exc
|
||||
from ceilometerclient import client as ceilometerclient
|
||||
from ceilometerclient.common import utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CeilometerShell(object):
|
||||
|
||||
def get_base_parser(self):
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='ceilometer',
|
||||
description=__doc__.strip(),
|
||||
epilog='See "ceilometer help COMMAND" '
|
||||
'for help on a specific command.',
|
||||
add_help=False,
|
||||
formatter_class=HelpFormatter,
|
||||
)
|
||||
|
||||
# Global arguments
|
||||
parser.add_argument('-h', '--help',
|
||||
action='store_true',
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
|
||||
parser.add_argument('-d', '--debug',
|
||||
default=bool(utils.env('CEILOMETERCLIENT_DEBUG')),
|
||||
action='store_true',
|
||||
help='Defaults to env[CEILOMETERCLIENT_DEBUG]')
|
||||
|
||||
parser.add_argument('-v', '--verbose',
|
||||
default=False, action="store_true",
|
||||
help="Print more verbose output")
|
||||
|
||||
parser.add_argument('-k', '--insecure',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help="Explicitly allow glanceclient to perform \"insecure\" "
|
||||
"SSL (https) requests. The server's certificate will "
|
||||
"not be verified against any certificate authorities. "
|
||||
"This option should be used with caution.")
|
||||
|
||||
parser.add_argument('--cert-file',
|
||||
help='Path of certificate file to use in SSL connection. This '
|
||||
'file can optionally be prepended with the private key.')
|
||||
|
||||
parser.add_argument('--key-file',
|
||||
help='Path of client key to use in SSL connection. This option is '
|
||||
'not necessary if your key is prepended to your cert file.')
|
||||
|
||||
parser.add_argument('--ca-file',
|
||||
help='Path of CA SSL certificate(s) used to verify the remote '
|
||||
'server\'s certificate. Without this option glance looks '
|
||||
'for the default system CA certificates.')
|
||||
|
||||
parser.add_argument('--timeout',
|
||||
default=600,
|
||||
help='Number of seconds to wait for a response')
|
||||
|
||||
parser.add_argument('--os-username',
|
||||
default=utils.env('OS_USERNAME'),
|
||||
help='Defaults to env[OS_USERNAME]')
|
||||
|
||||
parser.add_argument('--os_username',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-password',
|
||||
default=utils.env('OS_PASSWORD'),
|
||||
help='Defaults to env[OS_PASSWORD]')
|
||||
|
||||
parser.add_argument('--os_password',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-tenant-id',
|
||||
default=utils.env('OS_TENANT_ID'),
|
||||
help='Defaults to env[OS_TENANT_ID]')
|
||||
|
||||
parser.add_argument('--os_tenant_id',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-tenant-name',
|
||||
default=utils.env('OS_TENANT_NAME'),
|
||||
help='Defaults to env[OS_TENANT_NAME]')
|
||||
|
||||
parser.add_argument('--os_tenant_name',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-auth-url',
|
||||
default=utils.env('OS_AUTH_URL'),
|
||||
help='Defaults to env[OS_AUTH_URL]')
|
||||
|
||||
parser.add_argument('--os_auth_url',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-region-name',
|
||||
default=utils.env('OS_REGION_NAME'),
|
||||
help='Defaults to env[OS_REGION_NAME]')
|
||||
|
||||
parser.add_argument('--os_region_name',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-auth-token',
|
||||
default=utils.env('OS_AUTH_TOKEN'),
|
||||
help='Defaults to env[OS_AUTH_TOKEN]')
|
||||
|
||||
parser.add_argument('--os_auth_token',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--ceilometer-url',
|
||||
default=utils.env('CEILOMETER_URL'),
|
||||
help='Defaults to env[CEILOMETER_URL]')
|
||||
|
||||
parser.add_argument('--ceilometer_url',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--ceilometer-api-version',
|
||||
default=utils.env('CEILOMETER_API_VERSION', default='1'),
|
||||
help='Defaults to env[CEILOMETER_API_VERSION] or 1')
|
||||
|
||||
parser.add_argument('--ceilometer_api_version',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-service-type',
|
||||
default=utils.env('OS_SERVICE_TYPE'),
|
||||
help='Defaults to env[OS_SERVICE_TYPE]')
|
||||
|
||||
parser.add_argument('--os_service_type',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-endpoint-type',
|
||||
default=utils.env('OS_ENDPOINT_TYPE'),
|
||||
help='Defaults to env[OS_ENDPOINT_TYPE]')
|
||||
|
||||
parser.add_argument('--os_endpoint_type',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
return parser
|
||||
|
||||
def get_subcommand_parser(self, version):
|
||||
parser = self.get_base_parser()
|
||||
|
||||
self.subcommands = {}
|
||||
subparsers = parser.add_subparsers(metavar='<subcommand>')
|
||||
submodule = utils.import_versioned_module(version, 'shell')
|
||||
self._find_actions(subparsers, submodule)
|
||||
self._find_actions(subparsers, self)
|
||||
|
||||
return parser
|
||||
|
||||
def _find_actions(self, subparsers, actions_module):
|
||||
for attr in (a for a in dir(actions_module) if a.startswith('do_')):
|
||||
# I prefer to be hypen-separated instead of underscores.
|
||||
command = attr[3:].replace('_', '-')
|
||||
callback = getattr(actions_module, attr)
|
||||
desc = callback.__doc__ or ''
|
||||
help = desc.strip().split('\n')[0]
|
||||
arguments = getattr(callback, 'arguments', [])
|
||||
|
||||
subparser = subparsers.add_parser(command,
|
||||
help=help,
|
||||
description=desc,
|
||||
add_help=False,
|
||||
formatter_class=HelpFormatter
|
||||
)
|
||||
subparser.add_argument('-h', '--help',
|
||||
action='help',
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
self.subcommands[command] = subparser
|
||||
for (args, kwargs) in arguments:
|
||||
subparser.add_argument(*args, **kwargs)
|
||||
subparser.set_defaults(func=callback)
|
||||
|
||||
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 'orchestration',
|
||||
endpoint_type=kwargs.get('endpoint_type') or 'publicURL')
|
||||
|
||||
def _setup_debugging(self, debug):
|
||||
if debug:
|
||||
logging.basicConfig(
|
||||
format="%(levelname)s (%(module)s:%(lineno)d) %(message)s",
|
||||
level=logging.DEBUG)
|
||||
|
||||
httplib2.debuglevel = 1
|
||||
else:
|
||||
logging.basicConfig(
|
||||
format="%(levelname)s %(message)s",
|
||||
level=logging.INFO)
|
||||
|
||||
def main(self, argv):
|
||||
# Parse args once to find version
|
||||
parser = self.get_base_parser()
|
||||
(options, args) = parser.parse_known_args(argv)
|
||||
self._setup_debugging(options.debug)
|
||||
|
||||
# build available subcommands based on version
|
||||
api_version = options.ceilometer_api_version
|
||||
subcommand_parser = self.get_subcommand_parser(api_version)
|
||||
self.parser = subcommand_parser
|
||||
|
||||
# Handle top-level --help/-h before attempting to parse
|
||||
# a command off the command line
|
||||
if options.help or not argv:
|
||||
self.do_help(options)
|
||||
return 0
|
||||
|
||||
# Parse args again and call whatever callback was selected
|
||||
args = subcommand_parser.parse_args(argv)
|
||||
|
||||
# Short-circuit and deal with help command right away.
|
||||
if args.func == self.do_help:
|
||||
self.do_help(args)
|
||||
return 0
|
||||
|
||||
if not args.os_username:
|
||||
raise exc.CommandError("You must provide a username via"
|
||||
" either --os-username or env[OS_USERNAME]")
|
||||
|
||||
if not args.os_password:
|
||||
raise exc.CommandError("You must provide a password via"
|
||||
" either --os-password or env[OS_PASSWORD]")
|
||||
|
||||
if not (args.os_tenant_id or args.os_tenant_name):
|
||||
raise exc.CommandError("You must provide a tenant_id via"
|
||||
" either --os-tenant-id or via env[OS_TENANT_ID]")
|
||||
|
||||
if not args.os_auth_url:
|
||||
raise exc.CommandError("You must provide an auth url via"
|
||||
" either --os-auth-url or via 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 \
|
||||
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:
|
||||
args.func(client, args)
|
||||
except exc.Unauthorized:
|
||||
raise exc.CommandError("Invalid OpenStack Identity credentials.")
|
||||
|
||||
@utils.arg('command', metavar='<subcommand>', nargs='?',
|
||||
help='Display help for <subcommand>')
|
||||
def do_help(self, args):
|
||||
"""
|
||||
Display help about this program or one of its subcommands.
|
||||
"""
|
||||
if getattr(args, 'command', None):
|
||||
if args.command in self.subcommands:
|
||||
self.subcommands[args.command].print_help()
|
||||
else:
|
||||
raise exc.CommandError("'%s' is not a valid subcommand" %
|
||||
args.command)
|
||||
else:
|
||||
self.parser.print_help()
|
||||
|
||||
|
||||
class HelpFormatter(argparse.HelpFormatter):
|
||||
def start_section(self, heading):
|
||||
# Title-case the headings
|
||||
heading = '%s%s' % (heading[0].upper(), heading[1:])
|
||||
super(HelpFormatter, self).start_section(heading)
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
CeilometerShell().main(sys.argv[1:])
|
||||
|
||||
except Exception, e:
|
||||
print >> sys.stderr, e
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
16
ceilometerclient/v1/__init__.py
Normal file
16
ceilometerclient/v1/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# 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.
|
||||
|
||||
from ceilometerclient.v1.client import Client
|
37
ceilometerclient/v1/client.py
Normal file
37
ceilometerclient/v1/client.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# 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.
|
||||
|
||||
from ceilometerclient.common import http
|
||||
from ceilometerclient.v1 import meters
|
||||
|
||||
|
||||
class Client(http.HTTPClient):
|
||||
"""Client for the Ceilometer v1 API.
|
||||
|
||||
:param string endpoint: A user-supplied endpoint URL for the ceilometer
|
||||
service.
|
||||
:param string token: Token for authentication.
|
||||
:param integer timeout: Allows customization of the timeout for client
|
||||
http requests. (optional)
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
""" Initialize a new client for the Ceilometer v1 API. """
|
||||
super(Client, self).__init__(*args, **kwargs)
|
||||
self.meters = meters.MeterManager(self)
|
||||
self.events = meters.EventManager(self)
|
||||
self.users = meters.UserManager(self)
|
||||
self.resources = meters.ResourceManager(self)
|
||||
self.projects = meters.ProjectManager(self)
|
143
ceilometerclient/v1/meters.py
Normal file
143
ceilometerclient/v1/meters.py
Normal file
@@ -0,0 +1,143 @@
|
||||
# Copyright 2012 OpenMeter 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.
|
||||
|
||||
from ceilometerclient.common import base
|
||||
|
||||
class User(base.Resource):
|
||||
def __init__(self, manager, info, loaded=False):
|
||||
print 'user %s' % str(info)
|
||||
_d = {unicode('user_id'): info}
|
||||
super(User, self).__init__(manager, _d, loaded)
|
||||
|
||||
def __repr__(self):
|
||||
return "<User %s>" % self._info
|
||||
|
||||
def data(self, **kwargs):
|
||||
return self.manager.data(self, **kwargs)
|
||||
|
||||
|
||||
class UserManager(base.Manager):
|
||||
resource_class = User
|
||||
|
||||
def list(self, **kwargs):
|
||||
return self._list('/users', 'users')
|
||||
|
||||
|
||||
class Project(base.Resource):
|
||||
def __init__(self, manager, info, loaded=False):
|
||||
_d = {unicode('project_id'): info}
|
||||
super(Project, self).__init__(manager, _d, loaded)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Project %s>" % self._info
|
||||
|
||||
def data(self, **kwargs):
|
||||
return self.manager.data(self, **kwargs)
|
||||
|
||||
|
||||
class ProjectManager(base.Manager):
|
||||
resource_class = Project
|
||||
|
||||
def list(self, **kwargs):
|
||||
s = kwargs.get('source')
|
||||
if s:
|
||||
path = '/sources/%s/projects' % (kwargs['source'])
|
||||
else:
|
||||
path = '/projects'
|
||||
|
||||
return self._list(path, 'projects')
|
||||
|
||||
|
||||
class Resource(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Resource %s>" % self._info
|
||||
|
||||
def data(self, **kwargs):
|
||||
return self.manager.data(self, **kwargs)
|
||||
|
||||
|
||||
class ResourceManager(base.Manager):
|
||||
resource_class = Resource
|
||||
|
||||
def list(self, **kwargs):
|
||||
u = kwargs.get('user_id')
|
||||
s = kwargs.get('source')
|
||||
if u:
|
||||
path = '/users/%s/resources' % (u)
|
||||
elif s:
|
||||
path = '/sources/%s/resources' % (s)
|
||||
else:
|
||||
path = '/resources'
|
||||
return self._list(path, 'resources')
|
||||
|
||||
|
||||
class Event(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Event %s>" % self._info
|
||||
|
||||
def data(self, **kwargs):
|
||||
return self.manager.data(self, **kwargs)
|
||||
|
||||
|
||||
class EventManager(base.Manager):
|
||||
resource_class = Event
|
||||
|
||||
def list(self, **kwargs):
|
||||
c = kwargs['counter_name']
|
||||
r = kwargs.get('resource_id')
|
||||
u = kwargs.get('user_id')
|
||||
p = kwargs.get('project_id')
|
||||
s = kwargs.get('source')
|
||||
if r:
|
||||
path = '/resources/%s/events/%s' % (r, c)
|
||||
elif u:
|
||||
path = '/users/%s/events/%s' % (u, c)
|
||||
elif p:
|
||||
path = '/projects/%s/events/%s' % (p, c)
|
||||
elif s:
|
||||
path = '/sources/%s/events/%s' % (s, c)
|
||||
else:
|
||||
path = '/events'
|
||||
|
||||
self._list(path, 'events')
|
||||
|
||||
|
||||
class Meter(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Meter %s>" % self._info
|
||||
|
||||
def data(self, **kwargs):
|
||||
return self.manager.data(self, **kwargs)
|
||||
|
||||
|
||||
class MeterManager(base.Manager):
|
||||
resource_class = Meter
|
||||
|
||||
def list(self, **kwargs):
|
||||
r = kwargs.get('resource_id')
|
||||
u = kwargs.get('user_id')
|
||||
p = kwargs.get('project_id')
|
||||
s = kwargs.get('source')
|
||||
if u:
|
||||
path = '/users/%s/meters' % u
|
||||
elif r:
|
||||
path = '/resources/%s/meters' % r
|
||||
elif p:
|
||||
path = '/projects/%s/meters' % p
|
||||
elif s:
|
||||
path = '/sources/%s/meters' % s
|
||||
else:
|
||||
path = '/meters'
|
||||
return self._list(path, 'meters')
|
109
ceilometerclient/v1/shell.py
Normal file
109
ceilometerclient/v1/shell.py
Normal file
@@ -0,0 +1,109 @@
|
||||
# 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 json
|
||||
|
||||
from ceilometerclient.common import utils
|
||||
import ceilometerclient.exc as exc
|
||||
|
||||
|
||||
@utils.arg('-s', '--source', metavar='<SOURCE>',
|
||||
help='ID of the resource to show events for.')
|
||||
@utils.arg('-r', '--resource_id', metavar='<RESOURCE_ID>',
|
||||
help='ID of the resource to show events for.')
|
||||
@utils.arg('-u', '--user_id', metavar='<USER_ID>',
|
||||
help='ID of the user to show events for.')
|
||||
@utils.arg('-p', '--project_id', metavar='<PROJECT_ID>',
|
||||
help='ID of the project to show events for.')
|
||||
@utils.arg('-c', '--counter_name', metavar='<NAME>',
|
||||
help='Name of meter to show events for.')
|
||||
def do_event_list(cc, args):
|
||||
'''List the events for this meters'''
|
||||
fields = {'counter_name': args.counter_name,
|
||||
'resource_id': args.resource_id,
|
||||
'user_id': args.user_id,
|
||||
'project_id': args.project_id,
|
||||
'source': args.source}
|
||||
try:
|
||||
events = cc.events.list(**fields)
|
||||
except exc.HTTPNotFound:
|
||||
raise exc.CommandError('Events not found: %s' % args.counter_name)
|
||||
else:
|
||||
json_format = lambda js: json.dumps(js, indent=2)
|
||||
formatters = {
|
||||
'metadata': json_format,
|
||||
}
|
||||
for e in events:
|
||||
utils.print_dict(e.to_dict(), formatters=formatters)
|
||||
|
||||
|
||||
@utils.arg('-s', '--source', metavar='<SOURCE>',
|
||||
help='ID of the resource to show events for.')
|
||||
@utils.arg('-r', '--resource_id', metavar='<RESOURCE_ID>',
|
||||
help='ID of the resource to show events for.')
|
||||
@utils.arg('-u', '--user_id', metavar='<USER_ID>',
|
||||
help='ID of the user to show events for.')
|
||||
@utils.arg('-p', '--project_id', metavar='<PROJECT_ID>',
|
||||
help='ID of the project to show events for.')
|
||||
def do_metric_list(cc, args={}):
|
||||
'''List the user's metrices'''
|
||||
fields = {'resource_id': args.resource_id,
|
||||
'user_id': args.user_id,
|
||||
'project_id': args.project_id,
|
||||
'source': args.source}
|
||||
metrics = cc.meters.list(**fields)
|
||||
field_labels = ['Name', 'Resource ID', 'User ID']
|
||||
fields = ['counter_name', 'resource_id', 'user_id']
|
||||
utils.print_list(metrics, fields, field_labels,
|
||||
sortby=0)
|
||||
|
||||
|
||||
def do_user_list(cc, args={}):
|
||||
'''List the users'''
|
||||
kwargs = {}
|
||||
users = cc.users.list(**kwargs)
|
||||
field_labels = ['User ID']
|
||||
fields = ['user_id']
|
||||
utils.print_list(users, fields, field_labels,
|
||||
sortby=0)
|
||||
|
||||
|
||||
@utils.arg('-s', '--source', metavar='<SOURCE>',
|
||||
help='ID of the resource to show events for.')
|
||||
@utils.arg('-u', '--user_id', metavar='<USER_ID>',
|
||||
help='ID of the user to show events for.')
|
||||
def do_resource_list(cc, args={}):
|
||||
'''List the users'''
|
||||
kwargs = {'source': args.source,
|
||||
'user_id': args.user_id}
|
||||
resources = cc.resources.list(**kwargs)
|
||||
|
||||
field_labels = ['Resource ID', 'Source', 'User ID', 'Project ID']
|
||||
fields = ['resource_id', 'source', 'user_id', 'project_id']
|
||||
utils.print_list(resources, fields, field_labels,
|
||||
sortby=1)
|
||||
|
||||
|
||||
@utils.arg('-s', '--source', metavar='<SOURCE>',
|
||||
help='ID of the resource to show events for.')
|
||||
def do_project_list(cc, args={}):
|
||||
'''List the projects'''
|
||||
kwargs = {'source': args.source}
|
||||
projects = cc.projects.list(**kwargs)
|
||||
|
||||
field_labels = ['Project ID']
|
||||
fields = ['project_id']
|
||||
utils.print_list(projects, fields, field_labels,
|
||||
sortby=0)
|
19
ceilometerclient/version.py
Normal file
19
ceilometerclient/version.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# 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.openstack.common import version as common_version
|
||||
|
||||
version_info = common_version.VersionInfo('ceilometerclient',
|
||||
python_package='python-ceilometerclient')
|
2
setup.py
2
setup.py
@@ -43,7 +43,7 @@ setuptools.setup(
|
||||
'Programming Language :: Python',
|
||||
],
|
||||
entry_points={
|
||||
'console_scripts': ['ceilometerclient = ceilometerclient.shell:main']
|
||||
'console_scripts': ['ceilometer = ceilometerclient.shell:main']
|
||||
},
|
||||
dependency_links=setup.parse_dependency_links(),
|
||||
tests_require=setup.parse_requirements(['tools/test-requires']),
|
||||
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
80
tests/fakes.py
Normal file
80
tests/fakes.py
Normal file
@@ -0,0 +1,80 @@
|
||||
|
||||
import httplib
|
||||
import json
|
||||
import mox
|
||||
|
||||
from keystoneclient.v2_0 import client as ksclient
|
||||
from ceilometerclient.v1 import client as v1client
|
||||
|
||||
|
||||
def script_keystone_client():
|
||||
ksclient.Client(auth_url='http://no.where',
|
||||
insecure=False,
|
||||
password='password',
|
||||
tenant_id='',
|
||||
tenant_name='tenant_name',
|
||||
username='username').AndReturn(
|
||||
FakeKeystone('abcd1234'))
|
||||
|
||||
|
||||
def script_heat_list():
|
||||
resp_dict = {"stacks": [{
|
||||
"id": "1",
|
||||
"stack_name": "teststack",
|
||||
"stack_status": 'CREATE_COMPLETE',
|
||||
"creation_time": "2012-10-25T01:58:47Z"
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"stack_name": "teststack2",
|
||||
"stack_status": 'IN_PROGRESS',
|
||||
"creation_time": "2012-10-25T01:58:47Z"
|
||||
}]
|
||||
}
|
||||
resp = FakeHTTPResponse(200,
|
||||
'success, yo',
|
||||
{'content-type': 'application/json'},
|
||||
json.dumps(resp_dict))
|
||||
v1client.Client.json_request('GET',
|
||||
'/stacks?limit=20').AndReturn((resp, resp_dict))
|
||||
|
||||
|
||||
def fake_headers():
|
||||
return {'X-Auth-Token': 'abcd1234',
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-ceilometerclient'}
|
||||
|
||||
|
||||
class FakeServiceCatalog():
|
||||
def url_for(self, endpoint_type, service_type):
|
||||
return 'http://192.168.1.5:8004/v1/f14b41234'
|
||||
|
||||
|
||||
class FakeKeystone():
|
||||
service_catalog = FakeServiceCatalog()
|
||||
|
||||
def __init__(self, auth_token):
|
||||
self.auth_token = auth_token
|
||||
|
||||
|
||||
class FakeHTTPResponse():
|
||||
|
||||
version = 1.1
|
||||
|
||||
def __init__(self, status, reason, headers, body):
|
||||
self.headers = headers
|
||||
self.body = body
|
||||
self.status = status
|
||||
self.reason = reason
|
||||
|
||||
def getheader(self, name, default=None):
|
||||
return self.headers.get(name, default)
|
||||
|
||||
def getheaders(self):
|
||||
return self.headers.items()
|
||||
|
||||
def read(self, amt=None):
|
||||
b = self.body
|
||||
self.body = None
|
||||
return b
|
390
tests/test_shell.py
Normal file
390
tests/test_shell.py
Normal file
@@ -0,0 +1,390 @@
|
||||
import cStringIO
|
||||
import os
|
||||
import httplib2
|
||||
import sys
|
||||
|
||||
import mox
|
||||
import unittest
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
from keystoneclient.v2_0 import client as ksclient
|
||||
|
||||
from ceilometerclient import exc
|
||||
from ceilometerclient.v1 import client as v1client
|
||||
import ceilometerclient.shell
|
||||
import fakes
|
||||
|
||||
TEST_VAR_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||
'var'))
|
||||
|
||||
|
||||
class ShellValidationTest(unittest.TestCase):
|
||||
|
||||
def test_missing_auth(self):
|
||||
_old_env, os.environ = os.environ, {
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_TENANT_NAME': 'tenant_name',
|
||||
'OS_AUTH_URL': 'http://no.where',
|
||||
}
|
||||
self.shell_error('list', 'You must provide a username')
|
||||
|
||||
os.environ = _old_env
|
||||
|
||||
_old_env, os.environ = os.environ, {
|
||||
'OS_USERNAME': 'username',
|
||||
'OS_TENANT_NAME': 'tenant_name',
|
||||
'OS_AUTH_URL': 'http://no.where',
|
||||
}
|
||||
self.shell_error('list', 'You must provide a password')
|
||||
|
||||
os.environ = _old_env
|
||||
|
||||
_old_env, os.environ = os.environ, {
|
||||
'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_AUTH_URL': 'http://no.where',
|
||||
}
|
||||
self.shell_error('list', 'You must provide a tenant_id')
|
||||
|
||||
os.environ = _old_env
|
||||
|
||||
_old_env, os.environ = os.environ, {
|
||||
'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_TENANT_NAME': 'tenant_name',
|
||||
}
|
||||
self.shell_error('list', 'You must provide an auth url')
|
||||
|
||||
os.environ = _old_env
|
||||
|
||||
def test_failed_auth(self):
|
||||
m = mox.Mox()
|
||||
m.StubOutWithMock(ksclient, 'Client')
|
||||
m.StubOutWithMock(v1client.Client, 'json_request')
|
||||
fakes.script_keystone_client()
|
||||
v1client.Client.json_request('GET',
|
||||
'/stacks?limit=20').AndRaise(exc.Unauthorized)
|
||||
|
||||
m.ReplayAll()
|
||||
_old_env, os.environ = os.environ, {
|
||||
'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_TENANT_NAME': 'tenant_name',
|
||||
'OS_AUTH_URL': 'http://no.where',
|
||||
}
|
||||
self.shell_error('list', 'Invalid OpenStack Identity credentials.')
|
||||
|
||||
m.VerifyAll()
|
||||
|
||||
os.environ = _old_env
|
||||
m.UnsetStubs()
|
||||
|
||||
def test_create_validation(self):
|
||||
m = mox.Mox()
|
||||
m.StubOutWithMock(ksclient, 'Client')
|
||||
m.StubOutWithMock(v1client.Client, 'json_request')
|
||||
fakes.script_keystone_client()
|
||||
|
||||
m.ReplayAll()
|
||||
_old_env, os.environ = os.environ, {
|
||||
'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_TENANT_NAME': 'tenant_name',
|
||||
'OS_AUTH_URL': 'http://no.where',
|
||||
}
|
||||
self.shell_error('create teststack '
|
||||
'--parameters="InstanceType=m1.large;DBUsername=wp;'
|
||||
'DBPassword=verybadpassword;KeyName=heat_key;'
|
||||
'LinuxDistribution=F17"',
|
||||
'Need to specify exactly one of')
|
||||
|
||||
m.VerifyAll()
|
||||
|
||||
os.environ = _old_env
|
||||
m.UnsetStubs()
|
||||
|
||||
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(unittest.TestCase):
|
||||
|
||||
# Patch os.environ to avoid required auth info.
|
||||
def setUp(self):
|
||||
self.m = mox.Mox()
|
||||
self.m.StubOutWithMock(ksclient, 'Client')
|
||||
self.m.StubOutWithMock(v1client.Client, 'json_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):
|
||||
orig = sys.stdout
|
||||
try:
|
||||
sys.stdout = cStringIO.StringIO()
|
||||
_shell = ceilometerclient.shell.CeilometerShell()
|
||||
_shell.main(argstr.split())
|
||||
except SystemExit:
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
self.assertEqual(exc_value.code, 0)
|
||||
finally:
|
||||
out = sys.stdout.getvalue()
|
||||
sys.stdout.close()
|
||||
sys.stdout = orig
|
||||
|
||||
return out
|
||||
|
||||
def test_help_unknown_command(self):
|
||||
self.assertRaises(exc.CommandError, self.shell, 'help foofoo')
|
||||
|
||||
def test_debug(self):
|
||||
httplib2.debuglevel = 0
|
||||
self.shell('--debug help')
|
||||
self.assertEqual(httplib2.debuglevel, 1)
|
||||
|
||||
def test_help(self):
|
||||
required = [
|
||||
'^usage: heat',
|
||||
'(?m)^See "heat help COMMAND" for help on a specific command',
|
||||
]
|
||||
for argstr in ['--help', 'help']:
|
||||
help_text = self.shell(argstr)
|
||||
for r in required:
|
||||
self.assertRegexpMatches(help_text, r)
|
||||
|
||||
def test_help_on_subcommand(self):
|
||||
required = [
|
||||
'^usage: heat list',
|
||||
"(?m)^List the user's stacks",
|
||||
]
|
||||
argstrings = [
|
||||
'help list',
|
||||
]
|
||||
for argstr in argstrings:
|
||||
help_text = self.shell(argstr)
|
||||
for r in required:
|
||||
self.assertRegexpMatches(help_text, r)
|
||||
|
||||
def test_list(self):
|
||||
fakes.script_keystone_client()
|
||||
fakes.script_heat_list()
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
list_text = self.shell('list')
|
||||
|
||||
required = [
|
||||
'ID',
|
||||
'Status',
|
||||
'Created',
|
||||
'teststack/1',
|
||||
'CREATE_COMPLETE',
|
||||
'IN_PROGRESS',
|
||||
]
|
||||
for r in required:
|
||||
self.assertRegexpMatches(list_text, r)
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_describe(self):
|
||||
fakes.script_keystone_client()
|
||||
resp_dict = {"stack": {
|
||||
"id": "1",
|
||||
"stack_name": "teststack",
|
||||
"stack_status": 'CREATE_COMPLETE',
|
||||
"creation_time": "2012-10-25T01:58:47Z"
|
||||
}
|
||||
}
|
||||
resp = fakes.FakeHTTPResponse(200,
|
||||
'OK',
|
||||
{'content-type': 'application/json'},
|
||||
json.dumps(resp_dict))
|
||||
v1client.Client.json_request('GET',
|
||||
'/stacks/teststack/1').AndReturn((resp, resp_dict))
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
list_text = self.shell('describe teststack/1')
|
||||
|
||||
required = [
|
||||
'id',
|
||||
'stack_name',
|
||||
'stack_status',
|
||||
'creation_time',
|
||||
'teststack',
|
||||
'CREATE_COMPLETE',
|
||||
'2012-10-25T01:58:47Z'
|
||||
]
|
||||
for r in required:
|
||||
self.assertRegexpMatches(list_text, r)
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_create(self):
|
||||
fakes.script_keystone_client()
|
||||
resp = fakes.FakeHTTPResponse(201,
|
||||
'Created',
|
||||
{'location': 'http://no.where/v1/tenant_id/stacks/teststack2/2'},
|
||||
None)
|
||||
v1client.Client.json_request('POST', '/stacks',
|
||||
body=mox.IgnoreArg()).AndReturn((resp, None))
|
||||
fakes.script_heat_list()
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
template_file = os.path.join(TEST_VAR_DIR, 'minimal.template')
|
||||
create_text = self.shell('create teststack '
|
||||
'--template-file=%s '
|
||||
'--parameters="InstanceType=m1.large;DBUsername=wp;'
|
||||
'DBPassword=verybadpassword;KeyName=heat_key;'
|
||||
'LinuxDistribution=F17"' % template_file)
|
||||
|
||||
required = [
|
||||
'Name/ID',
|
||||
'teststack/1'
|
||||
]
|
||||
for r in required:
|
||||
self.assertRegexpMatches(create_text, r)
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_create_url(self):
|
||||
|
||||
fakes.script_keystone_client()
|
||||
resp = fakes.FakeHTTPResponse(201,
|
||||
'Created',
|
||||
{'location': 'http://no.where/v1/tenant_id/stacks/teststack2/2'},
|
||||
None)
|
||||
v1client.Client.json_request('POST', '/stacks',
|
||||
body=mox.IgnoreArg()).AndReturn((resp, None))
|
||||
fakes.script_heat_list()
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
create_text = self.shell('create teststack '
|
||||
'--template-url=http://no.where/minimal.template '
|
||||
'--parameters="InstanceType=m1.large;DBUsername=wp;'
|
||||
'DBPassword=verybadpassword;KeyName=heat_key;'
|
||||
'LinuxDistribution=F17"')
|
||||
|
||||
required = [
|
||||
'Name/ID',
|
||||
'teststack2/2'
|
||||
]
|
||||
for r in required:
|
||||
self.assertRegexpMatches(create_text, r)
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_create_object(self):
|
||||
|
||||
fakes.script_keystone_client()
|
||||
template_file = os.path.join(TEST_VAR_DIR, 'minimal.template')
|
||||
template_data = open(template_file).read()
|
||||
v1client.Client.raw_request('GET',
|
||||
'http://no.where/container/minimal.template',
|
||||
).AndReturn(template_data)
|
||||
|
||||
resp = fakes.FakeHTTPResponse(201,
|
||||
'Created',
|
||||
{'location': 'http://no.where/v1/tenant_id/stacks/teststack2/2'},
|
||||
None)
|
||||
v1client.Client.json_request('POST', '/stacks',
|
||||
body=mox.IgnoreArg()).AndReturn((resp, None))
|
||||
|
||||
fakes.script_heat_list()
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
create_text = self.shell('create teststack2 '
|
||||
'--template-object=http://no.where/container/minimal.template '
|
||||
'--parameters="InstanceType=m1.large;DBUsername=wp;'
|
||||
'DBPassword=verybadpassword;KeyName=heat_key;'
|
||||
'LinuxDistribution=F17"')
|
||||
|
||||
required = [
|
||||
'Name/ID',
|
||||
'teststack2/2'
|
||||
]
|
||||
for r in required:
|
||||
self.assertRegexpMatches(create_text, r)
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_update(self):
|
||||
fakes.script_keystone_client()
|
||||
resp = fakes.FakeHTTPResponse(202,
|
||||
'Accepted',
|
||||
{},
|
||||
'The request is accepted for processing.')
|
||||
v1client.Client.json_request('PUT', '/stacks/teststack2/2',
|
||||
body=mox.IgnoreArg()).AndReturn((resp, None))
|
||||
fakes.script_heat_list()
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
template_file = os.path.join(TEST_VAR_DIR, 'minimal.template')
|
||||
create_text = self.shell('update teststack2/2 '
|
||||
'--template-file=%s '
|
||||
'--parameters="InstanceType=m1.large;DBUsername=wp;'
|
||||
'DBPassword=verybadpassword;KeyName=heat_key;'
|
||||
'LinuxDistribution=F17"' % template_file)
|
||||
|
||||
required = [
|
||||
'Name/ID',
|
||||
'teststack/1'
|
||||
]
|
||||
for r in required:
|
||||
self.assertRegexpMatches(create_text, r)
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_delete(self):
|
||||
fakes.script_keystone_client()
|
||||
resp = fakes.FakeHTTPResponse(204,
|
||||
'No Content',
|
||||
{},
|
||||
None)
|
||||
v1client.Client.raw_request('DELETE', '/stacks/teststack2/2',
|
||||
).AndReturn((resp, None))
|
||||
fakes.script_heat_list()
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
create_text = self.shell('delete teststack2/2')
|
||||
|
||||
required = [
|
||||
'Name/ID',
|
||||
'teststack/1'
|
||||
]
|
||||
for r in required:
|
||||
self.assertRegexpMatches(create_text, r)
|
||||
|
||||
self.m.VerifyAll()
|
0
tests/v1/__init__.py
Normal file
0
tests/v1/__init__.py
Normal file
31
tests/v1/test_shell.py
Normal file
31
tests/v1/test_shell.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# 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 unittest
|
||||
import ceilometerclient.v1.shell as shell
|
||||
|
||||
|
||||
class shellTest(unittest.TestCase):
|
||||
|
||||
def test_format_parameters(self):
|
||||
p = shell.format_parameters('InstanceType=m1.large;DBUsername=wp;'
|
||||
'DBPassword=verybadpassword;KeyName=heat_key;'
|
||||
'LinuxDistribution=F17')
|
||||
self.assertEqual({'InstanceType': 'm1.large',
|
||||
'DBUsername': 'wp',
|
||||
'DBPassword': 'verybadpassword',
|
||||
'KeyName': 'heat_key',
|
||||
'LinuxDistribution': 'F17'
|
||||
}, p)
|
||||
self.assertEqual({}, shell.format_parameters(None))
|
Reference in New Issue
Block a user