Add forgotten common folder and other files

This commit is contained in:
efedorova 2013-10-16 19:07:44 +04:00
parent e68f0c28e8
commit 4110e728e1
10 changed files with 1517 additions and 0 deletions

View File

21
metadataclient/client.py Normal file
View File

@ -0,0 +1,21 @@
# Copyright (c) 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from metadataclient.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)

View File

View File

@ -0,0 +1,167 @@
# 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=None, obj_class=None,
body=None, headers={}):
resp, body = self.api.json_request('GET', url, headers=headers)
if obj_class is None:
obj_class = self.resource_class
if response_key:
if not response_key in body:
body[response_key] = []
data = body[response_key]
else:
data = body
return [obj_class(self, res, loaded=True) for res in data if res]
def _delete(self, url, headers={}):
self.api.raw_request('DELETE', url, headers=headers)
def _update(self, url, body, response_key=None, headers={}):
resp, body = self.api.json_request('PUT', url, body=body,
headers=headers)
# PUT requests may not return a body
if body:
if response_key:
return self.resource_class(self, body[response_key])
return self.resource_class(self, body)
def _create(self, url, body=None, response_key=None,
return_raw=False, headers={}):
if body:
resp, body = self.api.json_request('POST', url,
body=body, headers=headers)
else:
resp, body = self.api.json_request('POST', url, headers=headers)
if return_raw:
if response_key:
return body[response_key]
return body
if response_key:
return self.resource_class(self, body[response_key])
return self.resource_class(self, body)
def _get(self, url, response_key=None, return_raw=False, headers={}):
resp, body = self.api.json_request('GET', url, headers=headers)
if return_raw:
if response_key:
return body[response_key]
return body
if response_key:
return self.resource_class(self, body[response_key])
return self.resource_class(self, body)
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)

View File

@ -0,0 +1,178 @@
# 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 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 or self.__class__.__name__
def __str__(self):
return "%s (HTTP %s)" % (self.details, 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, body=None):
"""Return an instance of an HTTPException based on httplib response."""
cls = _code_map.get(response.status, HTTPException)
if body:
details = body.replace('\n\n', '\n')
return cls(details=details)
return cls()
class NoTokenLookupException(Exception):
"""DEPRECATED!"""
pass
class EndpointNotFound(Exception):
"""DEPRECATED!"""
pass
class SSLConfigurationError(BaseException):
pass
class SSLCertificateError(BaseException):
pass

View File

@ -0,0 +1,498 @@
# 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 errno
import hashlib
import httplib
import logging
import posixpath
import socket
import StringIO
import struct
import urlparse
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
import OpenSSL
from metadataclient.common import exceptions as exc
from metadataclient.common import utils
from metadataclient.openstack.common import strutils
try:
from eventlet import patcher
# Handle case where we are running in a monkey patched environment
if patcher.is_monkey_patched('socket'):
from eventlet.green.httplib import HTTPSConnection
from eventlet.green.OpenSSL.SSL import GreenConnection as Connection
from eventlet.greenio import GreenSocket
# TODO(mclaren): A getsockopt workaround: see 'getsockopt' doc string
GreenSocket.getsockopt = utils.getsockopt
else:
raise ImportError
except ImportError:
from httplib import HTTPSConnection
from OpenSSL.SSL import Connection as Connection
LOG = logging.getLogger(__name__)
USER_AGENT = 'python-metadatalient'
CHUNKSIZE = 1024 * 64 # 64kB
class HTTPClient(object):
def __init__(self, endpoint, **kwargs):
self.endpoint = endpoint
endpoint_parts = self.parse_endpoint(self.endpoint)
self.endpoint_scheme = endpoint_parts.scheme
self.endpoint_hostname = endpoint_parts.hostname
self.endpoint_port = endpoint_parts.port
self.endpoint_path = endpoint_parts.path
self.connection_class = self.get_connection_class(self.endpoint_scheme)
self.connection_kwargs = self.get_connection_kwargs(
self.endpoint_scheme, **kwargs)
self.identity_headers = kwargs.get('identity_headers')
self.auth_token = kwargs.get('token')
if self.identity_headers:
if self.identity_headers.get('X-Auth-Token'):
self.auth_token = self.identity_headers.get('X-Auth-Token')
del self.identity_headers['X-Auth-Token']
@staticmethod
def parse_endpoint(endpoint):
return urlparse.urlparse(endpoint)
@staticmethod
def get_connection_class(scheme):
if scheme == 'https':
return VerifiedHTTPSConnection
else:
return httplib.HTTPConnection
@staticmethod
def get_connection_kwargs(scheme, **kwargs):
_kwargs = {'timeout': float(kwargs.get('timeout', 600))}
if scheme == 'https':
_kwargs['cacert'] = kwargs.get('cacert', None)
_kwargs['cert_file'] = kwargs.get('cert_file', None)
_kwargs['key_file'] = kwargs.get('key_file', None)
_kwargs['insecure'] = kwargs.get('insecure', False)
_kwargs['ssl_compression'] = kwargs.get('ssl_compression', True)
return _kwargs
def get_connection(self):
_class = self.connection_class
try:
return _class(self.endpoint_hostname, self.endpoint_port,
**self.connection_kwargs)
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'),
('cacert', '--cacert %s'),
]
for (key, fmt) in conn_params_fmt:
value = self.connection_kwargs.get(key)
if value:
curl.append(fmt % value)
if self.connection_kwargs.get('insecure'):
curl.append('-k')
if kwargs.get('body') is not None:
curl.append('-d \'%s\'' % kwargs['body'])
curl.append('%s%s' % (self.endpoint, url))
LOG.debug(strutils.safe_encode(' '.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(strutils.safe_encode('\n'.join(dump)))
@staticmethod
def encode_headers(headers):
"""Encodes headers.
Note: This should be used right before
sending anything out.
:param headers: Headers to encode
:returns: Dictionary with encoded headers'
names and values
"""
to_str = strutils.safe_encode
return dict([(to_str(h), to_str(v)) for h, v in headers.iteritems()])
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)
if self.identity_headers:
for k, v in self.identity_headers.iteritems():
kwargs['headers'].setdefault(k, v)
self.log_curl_request(method, url, kwargs)
conn = self.get_connection()
# Note(flaper87): Before letting headers / url fly,
# they should be encoded otherwise httplib will
# complain. If we decide to rely on python-request
# this wont be necessary anymore.
kwargs['headers'] = self.encode_headers(kwargs['headers'])
try:
if self.endpoint_path:
url = '%s/%s' % (self.endpoint_path, url)
conn_url = posixpath.normpath(url)
# Note(flaper87): Ditto, headers / url
# encoding to make httplib happy.
conn_url = strutils.safe_encode(conn_url)
if kwargs['headers'].get('Transfer-Encoding') == 'chunked':
conn.putrequest(method, conn_url)
for header, value in kwargs['headers'].items():
conn.putheader(header, value)
conn.endheaders()
chunk = kwargs['body'].read(CHUNKSIZE)
# Chunk it, baby...
while chunk:
conn.send('%x\r\n%s\r\n' % (len(chunk), chunk))
chunk = kwargs['body'].read(CHUNKSIZE)
conn.send('0\r\n\r\n')
else:
conn.request(method, conn_url, **kwargs)
resp = conn.getresponse()
except socket.gaierror as e:
message = "Error finding address for %s: %s" % (
self.endpoint_hostname, e)
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.error("Request returned failure status.")
raise exc.from_response(resp, body_str)
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')
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')
if 'body' in kwargs:
if (hasattr(kwargs['body'], 'read')
and method.lower() in ('post', 'put')):
# We use 'Transfer-Encoding: chunked' because
# body size may not always be known in advance.
kwargs['headers']['Transfer-Encoding'] = 'chunked'
return self._http_request(url, method, **kwargs)
def archive_request(self, method, url, **kwargs):
kwargs.setdefault('headers', {})
kwargs['headers'].setdefault('Content-Type',
'application/x-tar')
return self._http_request(url, method, **kwargs)
class OpenSSLConnectionDelegator(object):
"""
An OpenSSL.SSL.Connection delegator.
Supplies an additional 'makefile' method which httplib requires
and is not present in OpenSSL.SSL.Connection.
Note: Since it is not possible to inherit from OpenSSL.SSL.Connection
a delegator must be used.
"""
def __init__(self, *args, **kwargs):
self.connection = Connection(*args, **kwargs)
def __getattr__(self, name):
return getattr(self.connection, name)
def makefile(self, *args, **kwargs):
# Making sure socket is closed when this file is closed
# since we now avoid closing socket on connection close
# see new close method under VerifiedHTTPSConnection
kwargs['close'] = True
return socket._fileobject(self.connection, *args, **kwargs)
class VerifiedHTTPSConnection(HTTPSConnection):
"""
Extended HTTPSConnection which uses the OpenSSL library
for enhanced SSL support.
Note: Much of this functionality can eventually be replaced
with native Python 3.3 code.
"""
def __init__(self, host, port=None, key_file=None, cert_file=None,
cacert=None, timeout=None, insecure=False,
ssl_compression=True):
HTTPSConnection.__init__(self, host, port,
key_file=key_file,
cert_file=cert_file)
self.key_file = key_file
self.cert_file = cert_file
self.timeout = timeout
self.insecure = insecure
self.ssl_compression = ssl_compression
self.cacert = cacert
self.setcontext()
@staticmethod
def host_matches_cert(host, x509):
"""
Verify that the the x509 certificate we have received
from 'host' correctly identifies the server we are
connecting to, ie that the certificate's Common Name
or a Subject Alternative Name matches 'host'.
"""
# First see if we can match the CN
if x509.get_subject().commonName == host:
return True
# Also try Subject Alternative Names for a match
san_list = None
for i in xrange(x509.get_extension_count()):
ext = x509.get_extension(i)
if ext.get_short_name() == 'subjectAltName':
san_list = str(ext)
for san in ''.join(san_list.split()).split(','):
if san == "DNS:%s" % host:
return True
# Server certificate does not match host
msg = ('Host "%s" does not match x509 certificate contents: '
'CommonName "%s"' % (host, x509.get_subject().commonName))
if san_list is not None:
msg = msg + ', subjectAltName "%s"' % san_list
raise exc.SSLCertificateError(msg)
def verify_callback(self, connection, x509, errnum,
depth, preverify_ok):
# NOTE(leaman): preverify_ok may be a non-boolean type
preverify_ok = bool(preverify_ok)
if x509.has_expired():
msg = "SSL Certificate expired on '%s'" % x509.get_notAfter()
raise exc.SSLCertificateError(msg)
if depth == 0 and preverify_ok:
# We verify that the host matches against the last
# certificate in the chain
return self.host_matches_cert(self.host, x509)
else:
# Pass through OpenSSL's default result
return preverify_ok
def setcontext(self):
"""
Set up the OpenSSL context.
"""
self.context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
if self.ssl_compression is False:
self.context.set_options(0x20000) # SSL_OP_NO_COMPRESSION
if self.insecure is not True:
self.context.set_verify(OpenSSL.SSL.VERIFY_PEER,
self.verify_callback)
else:
self.context.set_verify(OpenSSL.SSL.VERIFY_NONE,
lambda *args: True)
if self.cert_file:
try:
self.context.use_certificate_file(self.cert_file)
except Exception as e:
msg = 'Unable to load cert from "%s" %s' % (self.cert_file, e)
raise exc.SSLConfigurationError(msg)
if self.key_file is None:
# We support having key and cert in same file
try:
self.context.use_privatekey_file(self.cert_file)
except Exception as e:
msg = ('No key file specified and unable to load key '
'from "%s" %s' % (self.cert_file, e))
raise exc.SSLConfigurationError(msg)
if self.key_file:
try:
self.context.use_privatekey_file(self.key_file)
except Exception as e:
msg = 'Unable to load key from "%s" %s' % (self.key_file, e)
raise exc.SSLConfigurationError(msg)
if self.cacert:
try:
self.context.load_verify_locations(self.cacert)
except Exception as e:
msg = 'Unable to load CA from "%s"' % (self.cacert, e)
raise exc.SSLConfigurationError(msg)
else:
self.context.set_default_verify_paths()
def connect(self):
"""
Connect to an SSL port using the OpenSSL library and apply
per-connection parameters.
"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if self.timeout is not None:
# '0' microseconds
sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO,
struct.pack('fL', self.timeout, 0))
self.sock = OpenSSLConnectionDelegator(self.context, sock)
self.sock.connect((self.host, self.port))
def close(self):
if self.sock:
# Removing reference to socket but don't close it yet.
# Response close will close both socket and associated
# file. Closing socket too soon will cause response
# reads to fail with socket IO error 'Bad file descriptor'.
self.sock = None
# Calling close on HTTPConnection to continue doing that cleanup.
HTTPSConnection.close(self)
class ResponseBodyIterator(object):
"""
A class that acts as an iterator over an HTTP response.
This class will also check response body integrity when iterating over
the instance and if a checksum was supplied using `set_checksum` method,
else by default the class will not do any integrity check.
"""
def __init__(self, resp):
self._resp = resp
self._checksum = None
self._size = int(resp.getheader('content-length', 0))
self._end_reached = False
def set_checksum(self, checksum):
"""
Set checksum to check against when iterating over this instance.
:raise: AttributeError if iterator is already consumed.
"""
if self._end_reached:
raise AttributeError("Can't set checksum for an already consumed"
" iterator")
self._checksum = checksum
def __len__(self):
return int(self._size)
def __iter__(self):
md5sum = hashlib.md5()
while True:
try:
chunk = self.next()
except StopIteration:
self._end_reached = True
# NOTE(mouad): Check image integrity when the end of response
# body is reached.
md5sum = md5sum.hexdigest()
if self._checksum is not None and md5sum != self._checksum:
raise IOError(errno.EPIPE,
'Corrupted image. Checksum was %s '
'expected %s' % (md5sum, self._checksum))
raise
else:
yield chunk
md5sum.update(chunk)
def next(self):
chunk = self._resp.read(CHUNKSIZE)
if chunk:
return chunk
else:
raise StopIteration()

View File

@ -0,0 +1,119 @@
# 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 sys
import uuid
import os
from muranoclient.common import exceptions
import prettytable
from muranoclient.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 exceptions.NotFound:
pass
# now try to get entity as uuid
try:
uuid.UUID(str(name_or_id))
return manager.get(name_or_id)
except (ValueError, exceptions.NotFound):
pass
# finally try to find entity by name
try:
return manager.find(name=name_or_id)
except exceptions.NotFound:
msg = "No %s with a name or ID of '%s' exists." % \
(manager.resource_class.__name__.lower(), name_or_id)
raise exceptions.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 = 'muranoclient.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)

178
metadataclient/exc.py Normal file
View File

@ -0,0 +1,178 @@
# 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 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 or self.__class__.__name__
def __str__(self):
return "%s (HTTP %s)" % (self.details, 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, body=None):
"""Return an instance of an HTTPException based on httplib response."""
cls = _code_map.get(response.status, HTTPException)
if body:
details = body.replace('\n\n', '\n')
return cls(details=details)
return cls()
class NoTokenLookupException(Exception):
"""DEPRECATED!"""
pass
class EndpointNotFound(Exception):
"""DEPRECATED!"""
pass
class SSLConfigurationError(BaseException):
pass
class SSLCertificateError(BaseException):
pass

322
metadataclient/shell.py Normal file
View File

@ -0,0 +1,322 @@
# Copyright (c) 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Command-line interface to the Murano Metadata Repository API.
"""
import argparse
import logging
import sys
import httplib2
from keystoneclient.v2_0 import client as ksclient
import metadataclient
from metadataclient.common import exceptions
from metadataclient.common import utils
class MuranoRepositoryShell(object):
def get_base_parser(self):
parser = argparse.ArgumentParser(
prog='muranorepository',
description=__doc__.strip(),
epilog='See "metadata 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('METADATACLIENT_DEBUG')),
action='store_true',
help='Defaults to env[METADATACLIENT_DEBUG]')
parser.add_argument('-v', '--verbose',
default=False, action="store_true",
help="Print more verbose output")
parser.add_argument('--get-schema',
default=False, action="store_true",
dest='get_schema',
help='Force retrieving the schema used to generate'
' portions of the help text rather than using'
' a cached copy. Ignored with api version 1')
parser.add_argument('-k', '--insecure',
default=False,
action='store_true',
help='Explicitly allow metadataclient 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('--os-cacert',
metavar='<ca-certificate-file>',
dest='os_cacert',
default=utils.env('OS_CACERT'),
help='Path of CA TLS certificate(s) used to '
'verify the remote server\'s certificate. '
'Without this option metadata looks for the '
'default system CA certificates.')
parser.add_argument('--ca-file',
dest='os_cacert',
help='DEPRECATED! Use --os-cacert.')
parser.add_argument('--timeout',
default=600,
help='Number of seconds to wait for a response')
parser.add_argument('--os-tenant-id',
default=utils.env('OS_TENANT_ID'),
help='Defaults to env[OS_TENANT_ID]')
parser.add_argument('--os-tenant-name',
default=utils.env('OS_TENANT_NAME'),
help='Defaults to env[OS_TENANT_NAME]')
parser.add_argument('--os-auth-url',
default=utils.env('OS_AUTH_URL'),
help='Defaults to env[OS_AUTH_URL]')
parser.add_argument('--os-region-name',
default=utils.env('OS_REGION_NAME'),
help='Defaults to env[OS_REGION_NAME]')
parser.add_argument('--os-auth-token',
default=utils.env('OS_AUTH_TOKEN'),
help='Defaults to env[OS_AUTH_TOKEN]')
parser.add_argument('--os-service-type',
default=utils.env('OS_SERVICE_TYPE'),
help='Defaults to env[OS_SERVICE_TYPE]')
parser.add_argument('--os-endpoint-type',
default=utils.env('OS_ENDPOINT_TYPE'),
help='Defaults to env[OS_ENDPOINT_TYPE]')
parser.add_argument('--murano-metadata-url',
default=utils.env('MURANO_METADATA_URL'),
help='Defaults to env[MURANO_METADATA_URL]')
parser.add_argument('--murano_metadata-api-version',
default=utils.env(
'MURANO_METADATA_API_VERSION', default='1'),
help='Defaults to env[MURANO_METADATA_API_VERSION] '
'or 1')
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 'metering',
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.murano_metadata_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 args.os_auth_token and args.murano_metadata_url:
token = args.os_auth_token
endpoint = args.murano_metadata_url
else:
if not args.os_username:
raise exceptions.CommandError("You must provide a username "
"via either --os-username "
"or via env[OS_USERNAME]")
if not args.os_password:
raise exceptions.CommandError("You must provide a password "
"via either --os-password "
"or via env[OS_PASSWORD]")
if not (args.os_tenant_id or args.os_tenant_name):
raise exceptions.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 exceptions.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
url = args.murano_metadata_url
endpoint = 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 = metadataclient.Client(api_version, endpoint, **kwargs)
try:
args.func(client, args)
except exceptions.Unauthorized:
msg = "Invalid OpenStack Identity credentials."
raise exceptions.CommandError(msg)
@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:
msg = "'%s' is not a valid subcommand"
raise exceptions.CommandError(msg % 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:
MuranoRepositoryShell().main(sys.argv[1:])
except Exception, e:
print >> sys.stderr, e
sys.exit(1)
if __name__ == "__main__":
main()

34
test.py Normal file
View File

@ -0,0 +1,34 @@
from metadataclient.v1.client import Client
def metadataclient():
endpoint = 'http://localhost:5000'
insecure = False
token_id = '12casc2'
return Client(endpoint=endpoint, token=token_id, insecure=insecure)
def main():
# metadataclient().metadata_client.get_ui_data()
# metadataclient().metadata_client.get_conductor_data()
admin = metadataclient().metadata_admin
# print admin.list_ui()
# print admin.list_ui('Murano')
# print admin.list_agent()
# workflows = admin.list_workflows()
# for key, value in workflows.iteritems():
# for i in value:
# print i
# heat = metadataclient().metadata_admin.list_heat()
# for key, value in heat.iteritems():
# for i in value:
# print i
# admin.get_file('ui', 'WebServer.yaml')
# print admin.get_file('ui', 'Murano/Demo.yaml')
# admin.create_directory('ui', 'test')
# admin.delete_dir('ui', 'Murano/README.rst')
if __name__ == "__main__":
main()