sync to latest oslo-incubator code
This patch also syncs with latest oslo-incubator, to solve bug 1354470 and safe log auth token when --debug is enabled, see https://github.com/openstack/oslo-incubator/blob/master/openstack/common/apiclient/client.py#L128 Note, this patch also removes importutils.py since it is no longer used. Change-Id: I4f2d18ef05c3be27cf2ee3474761408e3f5ccbcd Closes-Bug: #1354470
This commit is contained in:
@@ -40,6 +40,6 @@ try:
|
|||||||
_LC = _translators.log_critical
|
_LC = _translators.log_critical
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# NOTE(dims): Support for cases where a project wants to use
|
# NOTE(dims): Support for cases where a project wants to use
|
||||||
# code from ceilometerclient-incubator, but is not ready to be internationalized
|
# code from oslo-incubator, but is not ready to be internationalized
|
||||||
# (like tempest)
|
# (like tempest)
|
||||||
_ = _LI = _LW = _LE = _LC = lambda x: x
|
_ = _LI = _LW = _LE = _LC = lambda x: x
|
||||||
|
|||||||
@@ -17,6 +17,19 @@
|
|||||||
# E0202: An attribute inherited from %s hide this method
|
# E0202: An attribute inherited from %s hide this method
|
||||||
# pylint: disable=E0202
|
# pylint: disable=E0202
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
#
|
||||||
|
# THIS MODULE IS DEPRECATED
|
||||||
|
#
|
||||||
|
# Please refer to
|
||||||
|
# https://etherpad.openstack.org/p/kilo-ceilometerclient-library-proposals for
|
||||||
|
# the discussion leading to this deprecation.
|
||||||
|
#
|
||||||
|
# We recommend checking out the python-openstacksdk project
|
||||||
|
# (https://launchpad.net/python-openstacksdk) instead.
|
||||||
|
#
|
||||||
|
########################################################################
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
|
|||||||
@@ -20,6 +20,20 @@
|
|||||||
Base utilities to build API operation managers and objects on top of.
|
Base utilities to build API operation managers and objects on top of.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
#
|
||||||
|
# THIS MODULE IS DEPRECATED
|
||||||
|
#
|
||||||
|
# Please refer to
|
||||||
|
# https://etherpad.openstack.org/p/kilo-ceilometerclient-library-proposals for
|
||||||
|
# the discussion leading to this deprecation.
|
||||||
|
#
|
||||||
|
# We recommend checking out the python-openstacksdk project
|
||||||
|
# (https://launchpad.net/python-openstacksdk) instead.
|
||||||
|
#
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
|
||||||
# E1102: %s is not callable
|
# E1102: %s is not callable
|
||||||
# pylint: disable=E1102
|
# pylint: disable=E1102
|
||||||
|
|
||||||
@@ -495,6 +509,8 @@ class Resource(object):
|
|||||||
new = self.manager.get(self.id)
|
new = self.manager.get(self.id)
|
||||||
if new:
|
if new:
|
||||||
self._add_details(new._info)
|
self._add_details(new._info)
|
||||||
|
self._add_details(
|
||||||
|
{'x_request_id': self.manager.client.last_request_id})
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if not isinstance(other, Resource):
|
if not isinstance(other, Resource):
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ OpenStack Client interface. Handles the REST calls and responses.
|
|||||||
# E0202: An attribute inherited from %s hide this method
|
# E0202: An attribute inherited from %s hide this method
|
||||||
# pylint: disable=E0202
|
# pylint: disable=E0202
|
||||||
|
|
||||||
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@@ -33,14 +34,15 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from oslo.utils import encodeutils
|
||||||
from oslo.utils import importutils
|
from oslo.utils import importutils
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from ceilometerclient.openstack.common._i18n import _
|
from ceilometerclient.openstack.common._i18n import _
|
||||||
from ceilometerclient.openstack.common.apiclient import exceptions
|
from ceilometerclient.openstack.common.apiclient import exceptions
|
||||||
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
SENSITIVE_HEADERS = ('X-Auth-Token', 'X-Subject-Token',)
|
||||||
|
|
||||||
|
|
||||||
class HTTPClient(object):
|
class HTTPClient(object):
|
||||||
@@ -98,19 +100,32 @@ class HTTPClient(object):
|
|||||||
self.http = http or requests.Session()
|
self.http = http or requests.Session()
|
||||||
|
|
||||||
self.cached_token = None
|
self.cached_token = None
|
||||||
|
self.last_request_id = None
|
||||||
|
|
||||||
|
def _safe_header(self, name, value):
|
||||||
|
if name in SENSITIVE_HEADERS:
|
||||||
|
# because in python3 byte string handling is ... ug
|
||||||
|
v = value.encode('utf-8')
|
||||||
|
h = hashlib.sha1(v)
|
||||||
|
d = h.hexdigest()
|
||||||
|
return encodeutils.safe_decode(name), "{SHA1}%s" % d
|
||||||
|
else:
|
||||||
|
return (encodeutils.safe_decode(name),
|
||||||
|
encodeutils.safe_decode(value))
|
||||||
|
|
||||||
def _http_log_req(self, method, url, kwargs):
|
def _http_log_req(self, method, url, kwargs):
|
||||||
if not self.debug:
|
if not self.debug:
|
||||||
return
|
return
|
||||||
|
|
||||||
string_parts = [
|
string_parts = [
|
||||||
"curl -i",
|
"curl -g -i",
|
||||||
"-X '%s'" % method,
|
"-X '%s'" % method,
|
||||||
"'%s'" % url,
|
"'%s'" % url,
|
||||||
]
|
]
|
||||||
|
|
||||||
for element in kwargs['headers']:
|
for element in kwargs['headers']:
|
||||||
header = "-H '%s: %s'" % (element, kwargs['headers'][element])
|
header = ("-H '%s: %s'" %
|
||||||
|
self._safe_header(element, kwargs['headers'][element]))
|
||||||
string_parts.append(header)
|
string_parts.append(header)
|
||||||
|
|
||||||
_logger.debug("REQ: %s" % " ".join(string_parts))
|
_logger.debug("REQ: %s" % " ".join(string_parts))
|
||||||
@@ -177,6 +192,8 @@ class HTTPClient(object):
|
|||||||
start_time, time.time()))
|
start_time, time.time()))
|
||||||
self._http_log_resp(resp)
|
self._http_log_resp(resp)
|
||||||
|
|
||||||
|
self.last_request_id = resp.headers.get('x-openstack-request-id')
|
||||||
|
|
||||||
if resp.status_code >= 400:
|
if resp.status_code >= 400:
|
||||||
_logger.debug(
|
_logger.debug(
|
||||||
"Request returned failure status: %s",
|
"Request returned failure status: %s",
|
||||||
@@ -327,6 +344,10 @@ class BaseClient(object):
|
|||||||
return self.http_client.client_request(
|
return self.http_client.client_request(
|
||||||
self, method, url, **kwargs)
|
self, method, url, **kwargs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_request_id(self):
|
||||||
|
return self.http_client.last_request_id
|
||||||
|
|
||||||
def head(self, url, **kwargs):
|
def head(self, url, **kwargs):
|
||||||
return self.client_request("HEAD", url, **kwargs)
|
return self.client_request("HEAD", url, **kwargs)
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,19 @@
|
|||||||
Exception definitions.
|
Exception definitions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
#
|
||||||
|
# THIS MODULE IS DEPRECATED
|
||||||
|
#
|
||||||
|
# Please refer to
|
||||||
|
# https://etherpad.openstack.org/p/kilo-ceilometerclient-library-proposals for
|
||||||
|
# the discussion leading to this deprecation.
|
||||||
|
#
|
||||||
|
# We recommend checking out the python-openstacksdk project
|
||||||
|
# (https://launchpad.net/python-openstacksdk) instead.
|
||||||
|
#
|
||||||
|
########################################################################
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@@ -34,14 +47,6 @@ class ClientException(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MissingArgs(ClientException):
|
|
||||||
"""Supplied arguments are not sufficient for calling a function."""
|
|
||||||
def __init__(self, missing):
|
|
||||||
self.missing = missing
|
|
||||||
msg = _("Missing arguments: %s") % ", ".join(missing)
|
|
||||||
super(MissingArgs, self).__init__(msg)
|
|
||||||
|
|
||||||
|
|
||||||
class ValidationError(ClientException):
|
class ValidationError(ClientException):
|
||||||
"""Error in validation on API client side."""
|
"""Error in validation on API client side."""
|
||||||
pass
|
pass
|
||||||
@@ -62,11 +67,16 @@ class AuthorizationFailure(ClientException):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ConnectionRefused(ClientException):
|
class ConnectionError(ClientException):
|
||||||
"""Cannot connect to API service."""
|
"""Cannot connect to API service."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionRefused(ConnectionError):
|
||||||
|
"""Connection refused while trying to connect to API service."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AuthPluginOptionsMissing(AuthorizationFailure):
|
class AuthPluginOptionsMissing(AuthorizationFailure):
|
||||||
"""Auth plugin misses some options."""
|
"""Auth plugin misses some options."""
|
||||||
def __init__(self, opt_names):
|
def __init__(self, opt_names):
|
||||||
@@ -447,10 +457,13 @@ def from_response(response, method, url):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if isinstance(body, dict) and isinstance(body.get("error"), dict):
|
if isinstance(body, dict):
|
||||||
error = body["error"]
|
error = body.get(list(body)[0])
|
||||||
kwargs["message"] = error.get("message")
|
if isinstance(error, dict):
|
||||||
kwargs["details"] = error.get("details")
|
kwargs["message"] = (error.get("message") or
|
||||||
|
error.get("faultstring"))
|
||||||
|
kwargs["details"] = (error.get("details") or
|
||||||
|
six.text_type(body))
|
||||||
elif content_type.startswith("text/"):
|
elif content_type.startswith("text/"):
|
||||||
kwargs["details"] = response.text
|
kwargs["details"] = response.text
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,19 @@ wrong the tests might raise AssertionError. I've indicated in comments the
|
|||||||
places where actual behavior differs from the spec.
|
places where actual behavior differs from the spec.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
#
|
||||||
|
# THIS MODULE IS DEPRECATED
|
||||||
|
#
|
||||||
|
# Please refer to
|
||||||
|
# https://etherpad.openstack.org/p/kilo-ceilometerclient-library-proposals for
|
||||||
|
# the discussion leading to this deprecation.
|
||||||
|
#
|
||||||
|
# We recommend checking out the python-openstacksdk project
|
||||||
|
# (https://launchpad.net/python-openstacksdk) instead.
|
||||||
|
#
|
||||||
|
########################################################################
|
||||||
|
|
||||||
# W0102: Dangerous default value %s as argument
|
# W0102: Dangerous default value %s as argument
|
||||||
# pylint: disable=W0102
|
# pylint: disable=W0102
|
||||||
|
|
||||||
@@ -168,6 +181,8 @@ class FakeHTTPClient(client.HTTPClient):
|
|||||||
else:
|
else:
|
||||||
status, body = resp
|
status, body = resp
|
||||||
headers = {}
|
headers = {}
|
||||||
|
self.last_request_id = headers.get('x-openstack-request-id',
|
||||||
|
'req-test')
|
||||||
return TestResponse({
|
return TestResponse({
|
||||||
"status_code": status,
|
"status_code": status,
|
||||||
"text": body,
|
"text": body,
|
||||||
|
|||||||
100
ceilometerclient/openstack/common/apiclient/utils.py
Normal file
100
ceilometerclient/openstack/common/apiclient/utils.py
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
#
|
||||||
|
# THIS MODULE IS DEPRECATED
|
||||||
|
#
|
||||||
|
# Please refer to
|
||||||
|
# https://etherpad.openstack.org/p/kilo-ceilometerclient-library-proposals for
|
||||||
|
# the discussion leading to this deprecation.
|
||||||
|
#
|
||||||
|
# We recommend checking out the python-openstacksdk project
|
||||||
|
# (https://launchpad.net/python-openstacksdk) instead.
|
||||||
|
#
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
from oslo.utils import encodeutils
|
||||||
|
import six
|
||||||
|
|
||||||
|
from ceilometerclient.openstack.common._i18n import _
|
||||||
|
from ceilometerclient.openstack.common.apiclient import exceptions
|
||||||
|
from ceilometerclient.openstack.common import uuidutils
|
||||||
|
|
||||||
|
|
||||||
|
def find_resource(manager, name_or_id, **find_args):
|
||||||
|
"""Look for resource in a given manager.
|
||||||
|
|
||||||
|
Used as a helper for the _find_* methods.
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def _find_hypervisor(cs, hypervisor):
|
||||||
|
#Get a hypervisor by name or ID.
|
||||||
|
return cliutils.find_resource(cs.hypervisors, hypervisor)
|
||||||
|
"""
|
||||||
|
# first try to get entity as integer id
|
||||||
|
try:
|
||||||
|
return manager.get(int(name_or_id))
|
||||||
|
except (TypeError, ValueError, exceptions.NotFound):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# now try to get entity as uuid
|
||||||
|
try:
|
||||||
|
if six.PY2:
|
||||||
|
tmp_id = encodeutils.safe_encode(name_or_id)
|
||||||
|
else:
|
||||||
|
tmp_id = encodeutils.safe_decode(name_or_id)
|
||||||
|
|
||||||
|
if uuidutils.is_uuid_like(tmp_id):
|
||||||
|
return manager.get(tmp_id)
|
||||||
|
except (TypeError, ValueError, exceptions.NotFound):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# for str id which is not uuid
|
||||||
|
if getattr(manager, 'is_alphanum_id_allowed', False):
|
||||||
|
try:
|
||||||
|
return manager.get(name_or_id)
|
||||||
|
except exceptions.NotFound:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
return manager.find(human_id=name_or_id, **find_args)
|
||||||
|
except exceptions.NotFound:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# finally try to find entity by name
|
||||||
|
try:
|
||||||
|
resource = getattr(manager, 'resource_class', None)
|
||||||
|
name_attr = resource.NAME_ATTR if resource else 'name'
|
||||||
|
kwargs = {name_attr: name_or_id}
|
||||||
|
kwargs.update(find_args)
|
||||||
|
return manager.find(**kwargs)
|
||||||
|
except exceptions.NotFound:
|
||||||
|
msg = _("No %(name)s with a name or "
|
||||||
|
"ID of '%(name_or_id)s' exists.") % \
|
||||||
|
{
|
||||||
|
"name": manager.resource_class.__name__.lower(),
|
||||||
|
"name_or_id": name_or_id
|
||||||
|
}
|
||||||
|
raise exceptions.CommandError(msg)
|
||||||
|
except exceptions.NoUniqueMatch:
|
||||||
|
msg = _("Multiple %(name)s matches found for "
|
||||||
|
"'%(name_or_id)s', use an ID to be more specific.") % \
|
||||||
|
{
|
||||||
|
"name": manager.resource_class.__name__.lower(),
|
||||||
|
"name_or_id": name_or_id
|
||||||
|
}
|
||||||
|
raise exceptions.CommandError(msg)
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
# Copyright 2011 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
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('.')
|
|
||||||
__import__(mod_str)
|
|
||||||
try:
|
|
||||||
return getattr(sys.modules[mod_str], class_str)
|
|
||||||
except AttributeError:
|
|
||||||
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):
|
|
||||||
"""Tries to import object from default namespace.
|
|
||||||
|
|
||||||
Imports 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]
|
|
||||||
|
|
||||||
|
|
||||||
def import_versioned_module(version, submodule=None):
|
|
||||||
module = 'ceilometerclient.v%s' % version
|
|
||||||
if submodule:
|
|
||||||
module = '.'.join((module, submodule))
|
|
||||||
return import_module(module)
|
|
||||||
|
|
||||||
|
|
||||||
def try_import(import_str, default=None):
|
|
||||||
"""Try to import a module and if it fails return default."""
|
|
||||||
try:
|
|
||||||
return import_module(import_str)
|
|
||||||
except ImportError:
|
|
||||||
return default
|
|
||||||
Reference in New Issue
Block a user