diff options
author | Michael Basnight <mbasnight@gmail.com> | 2013-06-17 23:34:27 -0700 |
---|---|---|
committer | Michael Basnight <mbasnight@gmail.com> | 2013-06-21 20:15:23 +0000 |
commit | 9916c8f2733b683d859770d05dacd2c9c82912d7 (patch) | |
tree | 084a0d53580cbbd34ed8f28de9302d6c78f7050d /troveclient/client.py | |
parent | bc90b3e088d3d4b83b5b3de0f9f83d9b6956947d (diff) | |
download | python-troveclient-9916c8f2733b683d859770d05dacd2c9c82912d7.tar.gz |
Rename from reddwarf to trove.0.1.3
Implements Blueprint reddwarf-trove-rename
Change-Id: Ib2d694c7466887ca297bea4250eca17cdc06b7bf
Diffstat (limited to 'troveclient/client.py')
-rw-r--r-- | troveclient/client.py | 370 |
1 files changed, 370 insertions, 0 deletions
diff --git a/troveclient/client.py b/troveclient/client.py new file mode 100644 index 0000000..1409448 --- /dev/null +++ b/troveclient/client.py @@ -0,0 +1,370 @@ +# Copyright (c) 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 httplib2 +import logging +import os +import time +import urlparse +import sys + +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 troveclient import auth +from troveclient import exceptions + + +_logger = logging.getLogger(__name__) +RDC_PP = os.environ.get("RDC_PP", "False") == "True" + + +expected_errors = (400, 401, 403, 404, 408, 409, 413, 422, 500, 501) + + +def log_to_streamhandler(stream=None): + stream = stream or sys.stderr + ch = logging.StreamHandler(stream) + _logger.setLevel(logging.DEBUG) + _logger.addHandler(ch) + + +if 'REDDWARFCLIENT_DEBUG' in os.environ and os.environ['REDDWARFCLIENT_DEBUG']: + log_to_streamhandler() + + +class TroveHTTPClient(httplib2.Http): + + USER_AGENT = 'python-troveclient' + + def __init__(self, user, password, tenant, auth_url, service_name, + service_url=None, + auth_strategy=None, insecure=False, + timeout=None, proxy_tenant_id=None, + proxy_token=None, region_name=None, + endpoint_type='publicURL', service_type=None, + timings=False): + + super(TroveHTTPClient, self).__init__(timeout=timeout) + + self.username = user + self.password = password + self.tenant = tenant + if auth_url: + self.auth_url = auth_url.rstrip('/') + else: + self.auth_url = None + self.region_name = region_name + self.endpoint_type = endpoint_type + self.service_url = service_url + self.service_type = service_type + self.service_name = service_name + self.timings = timings + + self.times = [] # [("item", starttime, endtime), ...] + + self.auth_token = None + self.proxy_token = proxy_token + self.proxy_tenant_id = proxy_tenant_id + + # httplib2 overrides + self.force_exception_to_status_code = True + self.disable_ssl_certificate_validation = insecure + + auth_cls = auth.get_authenticator_cls(auth_strategy) + + self.authenticator = auth_cls(self, auth_strategy, + self.auth_url, self.username, + self.password, self.tenant, + region=region_name, + service_type=service_type, + service_name=service_name, + service_url=service_url) + + def get_timings(self): + return self.times + + def http_log(self, args, kwargs, resp, body): + if not RDC_PP: + self.simple_log(args, kwargs, resp, body) + else: + self.pretty_log(args, kwargs, resp, body) + + def simple_log(self, args, kwargs, resp, body): + if not _logger.isEnabledFor(logging.DEBUG): + return + + string_parts = ['curl -i'] + for element in args: + if element in ('GET', 'POST'): + string_parts.append(' -X %s' % element) + else: + string_parts.append(' %s' % element) + + for element in kwargs['headers']: + header = ' -H "%s: %s"' % (element, kwargs['headers'][element]) + string_parts.append(header) + + _logger.debug("REQ: %s\n" % "".join(string_parts)) + if 'body' in kwargs: + _logger.debug("REQ BODY: %s\n" % (kwargs['body'])) + _logger.debug("RESP:%s %s\n", resp, body) + + def pretty_log(self, args, kwargs, resp, body): + from troveclient import common + if not _logger.isEnabledFor(logging.DEBUG): + return + + string_parts = ['curl -i'] + for element in args: + if element in ('GET', 'POST'): + string_parts.append(' -X %s' % element) + else: + string_parts.append(' %s' % element) + + for element in kwargs['headers']: + header = ' -H "%s: %s"' % (element, kwargs['headers'][element]) + string_parts.append(header) + + curl_cmd = "".join(string_parts) + _logger.debug("REQUEST:") + if 'body' in kwargs: + _logger.debug("%s -d '%s'" % (curl_cmd, kwargs['body'])) + try: + req_body = json.dumps(json.loads(kwargs['body']), + sort_keys=True, indent=4) + except: + req_body = kwargs['body'] + _logger.debug("BODY: %s\n" % (req_body)) + else: + _logger.debug(curl_cmd) + + try: + resp_body = json.dumps(json.loads(body), sort_keys=True, indent=4) + except: + resp_body = body + _logger.debug("RESPONSE HEADERS: %s" % resp) + _logger.debug("RESPONSE BODY : %s" % resp_body) + + def request(self, *args, **kwargs): + kwargs.setdefault('headers', kwargs.get('headers', {})) + kwargs['headers']['User-Agent'] = self.USER_AGENT + self.morph_request(kwargs) + + resp, body = super(TroveHTTPClient, self).request(*args, **kwargs) + + # Save this in case anyone wants it. + self.last_response = (resp, body) + self.http_log(args, kwargs, resp, body) + + if body: + try: + body = self.morph_response_body(body) + except exceptions.ResponseFormatError: + # Acceptable only if the response status is an error code. + # Otherwise its the API or client misbehaving. + self.raise_error_from_status(resp, None) + raise # Not accepted! + else: + body = None + + if resp.status in expected_errors: + raise exceptions.from_response(resp, body) + + return resp, body + + def raise_error_from_status(self, resp, body): + if resp.status in expected_errors: + raise exceptions.from_response(resp, body) + + def morph_request(self, kwargs): + kwargs['headers']['Accept'] = 'application/json' + kwargs['headers']['Content-Type'] = 'application/json' + if 'body' in kwargs: + kwargs['body'] = json.dumps(kwargs['body']) + + def morph_response_body(self, body_string): + try: + return json.loads(body_string) + except ValueError: + raise exceptions.ResponseFormatError() + + def _time_request(self, url, method, **kwargs): + start_time = time.time() + resp, body = self.request(url, method, **kwargs) + self.times.append(("%s %s" % (method, url), + start_time, time.time())) + return resp, body + + def _cs_request(self, url, method, **kwargs): + def request(): + kwargs.setdefault('headers', {})['X-Auth-Token'] = self.auth_token + if self.tenant: + kwargs['headers']['X-Auth-Project-Id'] = self.tenant + + resp, body = self._time_request(self.service_url + url, method, + **kwargs) + return resp, body + + if not self.auth_token or not self.service_url: + self.authenticate() + + # Perform the request once. If we get a 401 back then it + # might be because the auth token expired, so try to + # re-authenticate and try again. If it still fails, bail. + try: + return request() + except exceptions.Unauthorized, ex: + self.authenticate() + return request() + + def get(self, url, **kwargs): + return self._cs_request(url, 'GET', **kwargs) + + def post(self, url, **kwargs): + return self._cs_request(url, 'POST', **kwargs) + + def put(self, url, **kwargs): + return self._cs_request(url, 'PUT', **kwargs) + + def delete(self, url, **kwargs): + return self._cs_request(url, 'DELETE', **kwargs) + + def authenticate(self): + """Auths the client and gets a token. May optionally set a service url. + + The client will get auth errors until the authentication step + occurs. Additionally, if a service_url was not explicitly given in + the clients __init__ method, one will be obtained from the auth + service. + + """ + catalog = self.authenticator.authenticate() + if self.service_url: + possible_service_url = None + else: + if self.endpoint_type == "publicURL": + possible_service_url = catalog.get_public_url() + elif self.endpoint_type == "adminURL": + possible_service_url = catalog.get_management_url() + self.authenticate_with_token(catalog.get_token(), possible_service_url) + + def authenticate_with_token(self, token, service_url=None): + self.auth_token = token + if not self.service_url: + if not service_url: + raise exceptions.ServiceUrlNotGiven() + else: + self.service_url = service_url + + +class Dbaas(object): + """ + Top-level object to access the Rackspace Database as a Service API. + + Create an instance with your creds:: + + >>> red = Dbaas(USERNAME, API_KEY, TENANT, AUTH_URL, SERVICE_NAME, + SERVICE_URL) + + Then call methods on its managers:: + + >>> red.instances.list() + ... + >>> red.flavors.list() + ... + + &c. + """ + + def __init__(self, username, api_key, tenant=None, auth_url=None, + service_type='database', service_name='trove', + service_url=None, insecure=False, auth_strategy='keystone', + region_name=None, client_cls=TroveHTTPClient): + from troveclient.versions import Versions + from troveclient.databases import Databases + from troveclient.flavors import Flavors + from troveclient.instances import Instances + from troveclient.limits import Limits + from troveclient.users import Users + from troveclient.root import Root + from troveclient.hosts import Hosts + from troveclient.quota import Quotas + from troveclient.backups import Backups + from troveclient.security_groups import SecurityGroups + from troveclient.security_groups import SecurityGroupRules + from troveclient.storage import StorageInfo + from troveclient.management import Management + from troveclient.accounts import Accounts + from troveclient.diagnostics import DiagnosticsInterrogator + from troveclient.diagnostics import HwInfoInterrogator + + self.client = client_cls(username, api_key, tenant, auth_url, + service_type=service_type, + service_name=service_name, + service_url=service_url, + insecure=insecure, + auth_strategy=auth_strategy, + region_name=region_name) + self.versions = Versions(self) + self.databases = Databases(self) + self.flavors = Flavors(self) + self.instances = Instances(self) + self.limits = Limits(self) + self.users = Users(self) + self.root = Root(self) + self.hosts = Hosts(self) + self.quota = Quotas(self) + self.backups = Backups(self) + self.security_groups = SecurityGroups(self) + self.security_group_rules = SecurityGroupRules(self) + self.storage = StorageInfo(self) + self.management = Management(self) + self.accounts = Accounts(self) + self.diagnostics = DiagnosticsInterrogator(self) + self.hwinfo = HwInfoInterrogator(self) + + class Mgmt(object): + def __init__(self, dbaas): + self.instances = dbaas.management + self.hosts = dbaas.hosts + self.accounts = dbaas.accounts + self.storage = dbaas.storage + + self.mgmt = Mgmt(self) + + def set_management_url(self, url): + self.client.management_url = url + + def get_timings(self): + return self.client.get_timings() + + def authenticate(self): + """ + Authenticate against the server. + + This is called to perform an authentication to retrieve a token. + + Returns on success; raises :exc:`exceptions.Unauthorized` if the + credentials are wrong. + """ + self.client.authenticate() |