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
	 Angus Salkeld
					Angus Salkeld