diff --git a/mistralclient/openstack/common/apiclient/client.py b/mistralclient/openstack/common/apiclient/client.py deleted file mode 100644 index 41d6ae83..00000000 --- a/mistralclient/openstack/common/apiclient/client.py +++ /dev/null @@ -1,364 +0,0 @@ -# Copyright 2010 Jacob Kaplan-Moss -# Copyright 2011 OpenStack Foundation -# Copyright 2011 Piston Cloud Computing, Inc. -# Copyright 2013 Alessio Ababilov -# Copyright 2013 Grid Dynamics -# Copyright 2013 OpenStack Foundation -# 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. - -""" -OpenStack Client interface. Handles the REST calls and responses. -""" - -# E0202: An attribute inherited from %s hide this method -# pylint: disable=E0202 - -import logging -import time - -try: - import simplejson as json -except ImportError: - import json - -import requests - -from mistralclient.openstack.common.apiclient import exceptions -from mistralclient.openstack.common.gettextutils import _ -from oslo_utils import importutils - - -_logger = logging.getLogger(__name__) - - -class HTTPClient(object): - """This client handles sending HTTP requests to OpenStack servers. - - Features: - - - share authentication information between several clients to different - services (e.g., for compute and image clients); - - reissue authentication request for expired tokens; - - encode/decode JSON bodies; - - raise exceptions on HTTP errors; - - pluggable authentication; - - store authentication information in a keyring; - - store time spent for requests; - - register clients for particular services, so one can use - `http_client.identity` or `http_client.compute`; - - log requests and responses in a format that is easy to copy-and-paste - into terminal and send the same request with curl. - """ - - user_agent = "mistralclient.openstack.common.apiclient" - - def __init__(self, - auth_plugin, - region_name=None, - endpoint_type="publicURL", - original_ip=None, - verify=True, - cert=None, - timeout=None, - timings=False, - keyring_saver=None, - debug=False, - user_agent=None, - http=None): - self.auth_plugin = auth_plugin - - self.endpoint_type = endpoint_type - self.region_name = region_name - - self.original_ip = original_ip - self.timeout = timeout - self.verify = verify - self.cert = cert - - self.keyring_saver = keyring_saver - self.debug = debug - self.user_agent = user_agent or self.user_agent - - self.times = [] # [("item", starttime, endtime), ...] - self.timings = timings - - # requests within the same session can reuse TCP connections from pool - self.http = http or requests.Session() - - self.cached_token = None - - def _http_log_req(self, method, url, kwargs): - if not self.debug: - return - - string_parts = [ - "curl -i", - "-X '%s'" % method, - "'%s'" % url, - ] - - for element in kwargs['headers']: - header = "-H '%s: %s'" % (element, kwargs['headers'][element]) - string_parts.append(header) - - _logger.debug("REQ: %s" % " ".join(string_parts)) - if 'data' in kwargs: - _logger.debug("REQ BODY: %s\n" % (kwargs['data'])) - - def _http_log_resp(self, resp): - if not self.debug: - return - _logger.debug( - "RESP: [%s] %s\n", - resp.status_code, - resp.headers) - if resp._content_consumed: - _logger.debug( - "RESP BODY: %s\n", - resp.text) - - def serialize(self, kwargs): - if kwargs.get('json') is not None: - kwargs['headers']['Content-Type'] = 'application/json' - kwargs['data'] = json.dumps(kwargs['json']) - try: - del kwargs['json'] - except KeyError: - pass - - def get_timings(self): - return self.times - - def reset_timings(self): - self.times = [] - - def request(self, method, url, **kwargs): - """Send an http request with the specified characteristics. - - Wrapper around `requests.Session.request` to handle tasks such as - setting headers, JSON encoding/decoding, and error handling. - - :param method: method of HTTP request - :param url: URL of HTTP request - :param kwargs: any other parameter that can be passed to - requests.Session.request (such as `headers`) or `json` - that will be encoded as JSON and used as `data` argument - """ - kwargs.setdefault("headers", kwargs.get("headers", {})) - kwargs["headers"]["User-Agent"] = self.user_agent - if self.original_ip: - kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % ( - self.original_ip, self.user_agent) - if self.timeout is not None: - kwargs.setdefault("timeout", self.timeout) - kwargs.setdefault("verify", self.verify) - if self.cert is not None: - kwargs.setdefault("cert", self.cert) - self.serialize(kwargs) - - self._http_log_req(method, url, kwargs) - if self.timings: - start_time = time.time() - resp = self.http.request(method, url, **kwargs) - if self.timings: - self.times.append(("%s %s" % (method, url), - start_time, time.time())) - self._http_log_resp(resp) - - if resp.status_code >= 400: - _logger.debug( - "Request returned failure status: %s", - resp.status_code) - raise exceptions.from_response(resp, method, url) - - return resp - - @staticmethod - def concat_url(endpoint, url): - """Concatenate endpoint and final URL. - - E.g., "http://keystone/v2.0/" and "/tokens" are concatenated to - "http://keystone/v2.0/tokens". - - :param endpoint: the base URL - :param url: the final URL - """ - return "%s/%s" % (endpoint.rstrip("/"), url.strip("/")) - - def client_request(self, client, method, url, **kwargs): - """Send an http request using `client`'s endpoint and specified `url`. - - If request was rejected as unauthorized (possibly because the token is - expired), issue one authorization attempt and send the request once - again. - - :param client: instance of BaseClient descendant - :param method: method of HTTP request - :param url: URL of HTTP request - :param kwargs: any other parameter that can be passed to - `HTTPClient.request` - """ - - filter_args = { - "endpoint_type": client.endpoint_type or self.endpoint_type, - "service_type": client.service_type, - } - token, endpoint = (self.cached_token, client.cached_endpoint) - just_authenticated = False - if not (token and endpoint): - try: - token, endpoint = self.auth_plugin.token_and_endpoint( - **filter_args) - except exceptions.EndpointException: - pass - if not (token and endpoint): - self.authenticate() - just_authenticated = True - token, endpoint = self.auth_plugin.token_and_endpoint( - **filter_args) - if not (token and endpoint): - raise exceptions.AuthorizationFailure( - _("Cannot find endpoint or token for request")) - - old_token_endpoint = (token, endpoint) - kwargs.setdefault("headers", {})["X-Auth-Token"] = token - self.cached_token = token - client.cached_endpoint = endpoint - # Perform the request once. If we get Unauthorized, then it - # might be because the auth token expired, so try to - # re-authenticate and try again. If it still fails, bail. - try: - return self.request( - method, self.concat_url(endpoint, url), **kwargs) - except exceptions.Unauthorized as unauth_ex: - if just_authenticated: - raise - self.cached_token = None - client.cached_endpoint = None - self.authenticate() - try: - token, endpoint = self.auth_plugin.token_and_endpoint( - **filter_args) - except exceptions.EndpointException: - raise unauth_ex - if (not (token and endpoint) or - old_token_endpoint == (token, endpoint)): - raise unauth_ex - self.cached_token = token - client.cached_endpoint = endpoint - kwargs["headers"]["X-Auth-Token"] = token - return self.request( - method, self.concat_url(endpoint, url), **kwargs) - - def add_client(self, base_client_instance): - """Add a new instance of :class:`BaseClient` descendant. - - `self` will store a reference to `base_client_instance`. - - Example: - - >>> def test_clients(): - ... from keystoneclient.auth import keystone - ... from openstack.common.apiclient import client - ... auth = keystone.KeystoneAuthPlugin( - ... username="user", password="pass", tenant_name="tenant", - ... auth_url="http://auth:5000/v2.0") - ... openstack_client = client.HTTPClient(auth) - ... # create nova client - ... from novaclient.v1_1 import client - ... client.Client(openstack_client) - ... # create keystone client - ... from keystoneclient.v2_0 import client - ... client.Client(openstack_client) - ... # use them - ... openstack_client.identity.tenants.list() - ... openstack_client.compute.servers.list() - """ - service_type = base_client_instance.service_type - if service_type and not hasattr(self, service_type): - setattr(self, service_type, base_client_instance) - - def authenticate(self): - self.auth_plugin.authenticate(self) - # Store the authentication results in the keyring for later requests - if self.keyring_saver: - self.keyring_saver.save(self) - - -class BaseClient(object): - """Top-level object to access the OpenStack API. - - This client uses :class:`HTTPClient` to send requests. :class:`HTTPClient` - will handle a bunch of issues such as authentication. - """ - - service_type = None - endpoint_type = None # "publicURL" will be used - cached_endpoint = None - - def __init__(self, http_client, extensions=None): - self.http_client = http_client - http_client.add_client(self) - - # Add in any extensions... - if extensions: - for extension in extensions: - if extension.manager_class: - setattr(self, extension.name, - extension.manager_class(self)) - - def client_request(self, method, url, **kwargs): - return self.http_client.client_request( - self, method, url, **kwargs) - - def head(self, url, **kwargs): - return self.client_request("HEAD", url, **kwargs) - - def get(self, url, **kwargs): - return self.client_request("GET", url, **kwargs) - - def post(self, url, **kwargs): - return self.client_request("POST", url, **kwargs) - - def put(self, url, **kwargs): - return self.client_request("PUT", url, **kwargs) - - def delete(self, url, **kwargs): - return self.client_request("DELETE", url, **kwargs) - - def patch(self, url, **kwargs): - return self.client_request("PATCH", url, **kwargs) - - @staticmethod - def get_class(api_name, version, version_map): - """Returns the client class for the requested API version - - :param api_name: the name of the API, e.g. 'compute', 'image', etc - :param version: the requested API version - :param version_map: a dict of client classes keyed by version - :rtype: a client class for the requested API version - """ - try: - client_path = version_map[str(version)] - except (KeyError, ValueError): - msg = _("Invalid %(api_name)s client version '%(version)s'. " - "Must be one of: %(version_map)s") % { - 'api_name': api_name, - 'version': version, - 'version_map': ', '.join(version_map.keys()) - } - raise exceptions.UnsupportedVersion(msg) - - return importutils.import_class(client_path) diff --git a/mistralclient/openstack/common/apiclient/fake_client.py b/mistralclient/openstack/common/apiclient/fake_client.py deleted file mode 100644 index e24c63dc..00000000 --- a/mistralclient/openstack/common/apiclient/fake_client.py +++ /dev/null @@ -1,175 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# 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. - -""" -A fake server that "responds" to API methods with pre-canned responses. - -All of these responses come from the spec, so if for some reason the spec's -wrong the tests might raise AssertionError. I've indicated in comments the -places where actual behavior differs from the spec. -""" - -# W0102: Dangerous default value %s as argument -# pylint: disable=W0102 - -import json - -import requests -import six -from six.moves.urllib import parse - -from mistralclient.openstack.common.apiclient import client - - -def assert_has_keys(dct, required=None, optional=None): - required = required or [] - optional = optional or [] - for k in required: - try: - assert k in dct - except AssertionError: - extra_keys = set(dct.keys()).difference(set(required + optional)) - raise AssertionError("found unexpected keys: %s" % - list(extra_keys)) - - -class TestResponse(requests.Response): - """Wrap requests.Response and provide a convenient initialization. - """ - - def __init__(self, data): - super(TestResponse, self).__init__() - self._content_consumed = True - if isinstance(data, dict): - self.status_code = data.get('status_code', 200) - # Fake the text attribute to streamline Response creation - text = data.get('text', "") - if isinstance(text, (dict, list)): - self._content = json.dumps(text) - default_headers = { - "Content-Type": "application/json", - } - else: - self._content = text - default_headers = {} - if six.PY3 and isinstance(self._content, six.string_types): - self._content = self._content.encode('utf-8', 'strict') - self.headers = data.get('headers') or default_headers - else: - self.status_code = data - - def __eq__(self, other): - return (self.status_code == other.status_code and - self.headers == other.headers and - self._content == other._content) - - -class FakeHTTPClient(client.HTTPClient): - - def __init__(self, *args, **kwargs): - self.callstack = [] - self.fixtures = kwargs.pop("fixtures", None) or {} - if not args and not "auth_plugin" in kwargs: - args = (None, ) - super(FakeHTTPClient, self).__init__(*args, **kwargs) - - def assert_called(self, method, url, body=None, pos=-1): - """Assert than an API method was just called. - """ - expected = (method, url) - called = self.callstack[pos][0:2] - assert self.callstack, \ - "Expected %s %s but no calls were made." % expected - - assert expected == called, 'Expected %s %s; got %s %s' % \ - (expected + called) - - if body is not None: - if self.callstack[pos][3] != body: - raise AssertionError('%r != %r' % - (self.callstack[pos][3], body)) - - def assert_called_anytime(self, method, url, body=None): - """Assert than an API method was called anytime in the test. - """ - expected = (method, url) - - assert self.callstack, \ - "Expected %s %s but no calls were made." % expected - - found = False - entry = None - for entry in self.callstack: - if expected == entry[0:2]: - found = True - break - - assert found, 'Expected %s %s; got %s' % \ - (method, url, self.callstack) - if body is not None: - assert entry[3] == body, "%s != %s" % (entry[3], body) - - self.callstack = [] - - def clear_callstack(self): - self.callstack = [] - - def authenticate(self): - pass - - def client_request(self, client, method, url, **kwargs): - # Check that certain things are called correctly - if method in ["GET", "DELETE"]: - assert "json" not in kwargs - - # Note the call - self.callstack.append( - (method, - url, - kwargs.get("headers") or {}, - kwargs.get("json") or kwargs.get("data"))) - try: - fixture = self.fixtures[url][method] - except KeyError: - pass - else: - return TestResponse({"headers": fixture[0], - "text": fixture[1]}) - - # Call the method - args = parse.parse_qsl(parse.urlparse(url)[4]) - kwargs.update(args) - munged_url = url.rsplit('?', 1)[0] - munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_') - munged_url = munged_url.replace('-', '_') - - callback = "%s_%s" % (method.lower(), munged_url) - - if not hasattr(self, callback): - raise AssertionError('Called unknown API method: %s %s, ' - 'expected fakes method name: %s' % - (method, url, callback)) - - resp = getattr(self, callback)(**kwargs) - if len(resp) == 3: - status, headers, body = resp - else: - status, body = resp - headers = {} - return TestResponse({ - "status_code": status, - "text": body, - "headers": headers, - })