Convert strutils to oslo.utils.encodeutils

Convert the encode/decode functions from oslo-incubator to use
oslo.utils encodeutils, as the incubator functions are now
deprecated.

Also syncs oslo-incubator to 62394a3 to purge usage of strutils
from the openstack/common modules.

Note includes oslo fix https://review.openstack.org/#/c/133290/
which we need or the python3 tests won't pass.

Change-Id: I630fe3f3ce14ae745a8417bfe6552acd31341c9c
Partial-Bug: #1380629
This commit is contained in:
Steven Hardy 2014-10-29 16:51:15 +00:00
parent aea6e7dcbc
commit 5259f00827
19 changed files with 331 additions and 443 deletions

View File

@ -24,10 +24,10 @@ import six
from six.moves.urllib import parse from six.moves.urllib import parse
from oslo.serialization import jsonutils from oslo.serialization import jsonutils
from oslo.utils import encodeutils
from heatclient import exc from heatclient import exc
from heatclient.openstack.common import importutils from heatclient.openstack.common import importutils
from heatclient.openstack.common import strutils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
USER_AGENT = 'python-heatclient' USER_AGENT = 'python-heatclient'
@ -84,15 +84,20 @@ class HTTPClient(object):
else: else:
self.verify_cert = kwargs.get('ca_file', get_system_ca_file()) self.verify_cert = kwargs.get('ca_file', get_system_ca_file())
# FIXME(shardy): We need this for compatibility with the oslo apiclient
# we should move to inheriting this class from the oslo HTTPClient
self.last_request_id = None
def safe_header(self, name, value): def safe_header(self, name, value):
if name in SENSITIVE_HEADERS: if name in SENSITIVE_HEADERS:
# because in python3 byte string handling is ... ug # because in python3 byte string handling is ... ug
v = value.encode('utf-8') v = value.encode('utf-8')
h = hashlib.sha1(v) h = hashlib.sha1(v)
d = h.hexdigest() d = h.hexdigest()
return strutils.safe_decode(name), "{SHA1}%s" % d return encodeutils.safe_decode(name), "{SHA1}%s" % d
else: else:
return strutils.safe_decode(name), strutils.safe_decode(value) return (encodeutils.safe_decode(name),
encodeutils.safe_decode(value))
def log_curl_request(self, method, url, kwargs): def log_curl_request(self, method, url, kwargs):
curl = ['curl -i -X %s' % method] curl = ['curl -i -X %s' % method]

View File

@ -1,17 +0,0 @@
#
# 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 six
six.add_move(six.MovedModule('mox', 'mox', 'mox3.mox'))

View File

@ -0,0 +1,40 @@
# 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.
"""oslo.i18n integration module.
See http://docs.openstack.org/developer/oslo.i18n/usage.html
"""
import oslo.i18n
# NOTE(dhellmann): This reference to o-s-l-o will be replaced by the
# application name when this module is synced into the separate
# repository. It is OK to have more than one translation function
# using the same domain, since there will still only be one message
# catalog.
_translators = oslo.i18n.TranslatorFactory(domain='heatclient')
# The primary translation function using the well-known name "_"
_ = _translators.primary
# Translators for log levels.
#
# The abbreviated names are meant to reflect the usual use of a short
# name like '_'. The "L" is for "log" and the other letter comes from
# the level.
_LI = _translators.log_info
_LW = _translators.log_warning
_LE = _translators.log_error
_LC = _translators.log_critical

View File

@ -213,8 +213,8 @@ class BaseAuthPlugin(object):
:type service_type: string :type service_type: string
:param endpoint_type: Type of endpoint. :param endpoint_type: Type of endpoint.
Possible values: public or publicURL, Possible values: public or publicURL,
internal or internalURL, internal or internalURL,
admin or adminURL admin or adminURL
:type endpoint_type: string :type endpoint_type: string
:returns: tuple of token and endpoint strings :returns: tuple of token and endpoint strings
:raises: EndpointException :raises: EndpointException

View File

@ -26,11 +26,12 @@ Base utilities to build API operation managers and objects on top of.
import abc import abc
import copy import copy
from oslo.utils import strutils
import six import six
from six.moves.urllib import parse from six.moves.urllib import parse
from heatclient.openstack.common._i18n import _
from heatclient.openstack.common.apiclient import exceptions from heatclient.openstack.common.apiclient import exceptions
from heatclient.openstack.common import strutils
def getid(obj): def getid(obj):
@ -74,8 +75,8 @@ class HookableMixin(object):
:param cls: class that registers hooks :param cls: class that registers hooks
:param hook_type: hook type, e.g., '__pre_parse_args__' :param hook_type: hook type, e.g., '__pre_parse_args__'
:param **args: args to be passed to every hook function :param args: args to be passed to every hook function
:param **kwargs: kwargs to be passed to every hook function :param kwargs: kwargs to be passed to every hook function
""" """
hook_funcs = cls._hooks_map.get(hook_type) or [] hook_funcs = cls._hooks_map.get(hook_type) or []
for hook_func in hook_funcs: for hook_func in hook_funcs:
@ -98,12 +99,13 @@ class BaseManager(HookableMixin):
super(BaseManager, self).__init__() super(BaseManager, self).__init__()
self.client = client self.client = client
def _list(self, url, response_key, obj_class=None, json=None): def _list(self, url, response_key=None, obj_class=None, json=None):
"""List the collection. """List the collection.
:param url: a partial URL, e.g., '/servers' :param url: a partial URL, e.g., '/servers'
:param response_key: the key to be looked up in response dictionary, :param response_key: the key to be looked up in response dictionary,
e.g., 'servers' e.g., 'servers'. If response_key is None - all response body
will be used.
:param obj_class: class for constructing the returned objects :param obj_class: class for constructing the returned objects
(self.resource_class will be used by default) (self.resource_class will be used by default)
:param json: data that will be encoded as JSON and passed in POST :param json: data that will be encoded as JSON and passed in POST
@ -117,7 +119,7 @@ class BaseManager(HookableMixin):
if obj_class is None: if obj_class is None:
obj_class = self.resource_class obj_class = self.resource_class
data = body[response_key] data = body[response_key] if response_key is not None else body
# NOTE(ja): keystone returns values as list as {'values': [ ... ]} # NOTE(ja): keystone returns values as list as {'values': [ ... ]}
# unlike other services which just return the list... # unlike other services which just return the list...
try: try:
@ -127,15 +129,17 @@ class BaseManager(HookableMixin):
return [obj_class(self, res, loaded=True) for res in data if res] return [obj_class(self, res, loaded=True) for res in data if res]
def _get(self, url, response_key): def _get(self, url, response_key=None):
"""Get an object from collection. """Get an object from collection.
:param url: a partial URL, e.g., '/servers' :param url: a partial URL, e.g., '/servers'
:param response_key: the key to be looked up in response dictionary, :param response_key: the key to be looked up in response dictionary,
e.g., 'server' e.g., 'server'. If response_key is None - all response body
will be used.
""" """
body = self.client.get(url).json() body = self.client.get(url).json()
return self.resource_class(self, body[response_key], loaded=True) data = body[response_key] if response_key is not None else body
return self.resource_class(self, data, loaded=True)
def _head(self, url): def _head(self, url):
"""Retrieve request headers for an object. """Retrieve request headers for an object.
@ -145,21 +149,23 @@ class BaseManager(HookableMixin):
resp = self.client.head(url) resp = self.client.head(url)
return resp.status_code == 204 return resp.status_code == 204
def _post(self, url, json, response_key, return_raw=False): def _post(self, url, json, response_key=None, return_raw=False):
"""Create an object. """Create an object.
:param url: a partial URL, e.g., '/servers' :param url: a partial URL, e.g., '/servers'
:param json: data that will be encoded as JSON and passed in POST :param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default) request (GET will be sent by default)
:param response_key: the key to be looked up in response dictionary, :param response_key: the key to be looked up in response dictionary,
e.g., 'servers' e.g., 'server'. If response_key is None - all response body
will be used.
:param return_raw: flag to force returning raw JSON instead of :param return_raw: flag to force returning raw JSON instead of
Python object of self.resource_class Python object of self.resource_class
""" """
body = self.client.post(url, json=json).json() body = self.client.post(url, json=json).json()
data = body[response_key] if response_key is not None else body
if return_raw: if return_raw:
return body[response_key] return data
return self.resource_class(self, body[response_key]) return self.resource_class(self, data)
def _put(self, url, json=None, response_key=None): def _put(self, url, json=None, response_key=None):
"""Update an object with PUT method. """Update an object with PUT method.
@ -168,7 +174,8 @@ class BaseManager(HookableMixin):
:param json: data that will be encoded as JSON and passed in POST :param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default) request (GET will be sent by default)
:param response_key: the key to be looked up in response dictionary, :param response_key: the key to be looked up in response dictionary,
e.g., 'servers' e.g., 'servers'. If response_key is None - all response body
will be used.
""" """
resp = self.client.put(url, json=json) resp = self.client.put(url, json=json)
# PUT requests may not return a body # PUT requests may not return a body
@ -186,7 +193,8 @@ class BaseManager(HookableMixin):
:param json: data that will be encoded as JSON and passed in POST :param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default) request (GET will be sent by default)
:param response_key: the key to be looked up in response dictionary, :param response_key: the key to be looked up in response dictionary,
e.g., 'servers' e.g., 'servers'. If response_key is None - all response body
will be used.
""" """
body = self.client.patch(url, json=json).json() body = self.client.patch(url, json=json).json()
if response_key is not None: if response_key is not None:
@ -219,7 +227,10 @@ class ManagerWithFind(BaseManager):
matches = self.findall(**kwargs) matches = self.findall(**kwargs)
num_matches = len(matches) num_matches = len(matches)
if num_matches == 0: if num_matches == 0:
msg = "No %s matching %s." % (self.resource_class.__name__, kwargs) msg = _("No %(name)s matching %(args)s.") % {
'name': self.resource_class.__name__,
'args': kwargs
}
raise exceptions.NotFound(msg) raise exceptions.NotFound(msg)
elif num_matches > 1: elif num_matches > 1:
raise exceptions.NoUniqueMatch() raise exceptions.NoUniqueMatch()
@ -373,7 +384,10 @@ class CrudManager(BaseManager):
num = len(rl) num = len(rl)
if num == 0: if num == 0:
msg = "No %s matching %s." % (self.resource_class.__name__, kwargs) msg = _("No %(name)s matching %(args)s.") % {
'name': self.resource_class.__name__,
'args': kwargs
}
raise exceptions.NotFound(404, msg) raise exceptions.NotFound(404, msg)
elif num > 1: elif num > 1:
raise exceptions.NoUniqueMatch raise exceptions.NoUniqueMatch
@ -441,8 +455,10 @@ class Resource(object):
def human_id(self): def human_id(self):
"""Human-readable ID which can be used for bash completion. """Human-readable ID which can be used for bash completion.
""" """
if self.NAME_ATTR in self.__dict__ and self.HUMAN_ID: if self.HUMAN_ID:
return strutils.to_slug(getattr(self, self.NAME_ATTR)) name = getattr(self, self.NAME_ATTR, None)
if name is not None:
return strutils.to_slug(name)
return None return None
def _add_details(self, info): def _add_details(self, info):
@ -456,7 +472,7 @@ class Resource(object):
def __getattr__(self, k): def __getattr__(self, k):
if k not in self.__dict__: if k not in self.__dict__:
#NOTE(bcwaldon): disallow lazy-loading if already loaded once # NOTE(bcwaldon): disallow lazy-loading if already loaded once
if not self.is_loaded(): if not self.is_loaded():
self.get() self.get()
return self.__getattr__(k) return self.__getattr__(k)
@ -479,6 +495,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):

View File

@ -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,19 +34,22 @@ try:
except ImportError: except ImportError:
import json import json
from oslo.utils import encodeutils
from oslo.utils import importutils
import requests import requests
from heatclient.openstack.common._i18n import _
from heatclient.openstack.common.apiclient import exceptions from heatclient.openstack.common.apiclient import exceptions
from heatclient.openstack.common import importutils
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
SENSITIVE_HEADERS = ('X-Auth-Token', 'X-Subject-Token',)
class HTTPClient(object): class HTTPClient(object):
"""This client handles sending HTTP requests to OpenStack servers. """This client handles sending HTTP requests to OpenStack servers.
Features: Features:
- share authentication information between several clients to different - share authentication information between several clients to different
services (e.g., for compute and image clients); services (e.g., for compute and image clients);
- reissue authentication request for expired tokens; - reissue authentication request for expired tokens;
@ -96,6 +100,18 @@ 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:
@ -108,7 +124,8 @@ class HTTPClient(object):
] ]
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))
@ -151,10 +168,10 @@ class HTTPClient(object):
:param method: method of HTTP request :param method: method of HTTP request
:param url: URL of HTTP request :param url: URL of HTTP request
:param kwargs: any other parameter that can be passed to :param kwargs: any other parameter that can be passed to
' requests.Session.request (such as `headers`) or `json` requests.Session.request (such as `headers`) or `json`
that will be encoded as JSON and used as `data` argument that will be encoded as JSON and used as `data` argument
""" """
kwargs.setdefault("headers", kwargs.get("headers", {})) kwargs.setdefault("headers", {})
kwargs["headers"]["User-Agent"] = self.user_agent kwargs["headers"]["User-Agent"] = self.user_agent
if self.original_ip: if self.original_ip:
kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % ( kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % (
@ -175,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",
@ -206,7 +225,7 @@ class HTTPClient(object):
:param method: method of HTTP request :param method: method of HTTP request
:param url: URL of HTTP request :param url: URL of HTTP request
:param kwargs: any other parameter that can be passed to :param kwargs: any other parameter that can be passed to
' `HTTPClient.request` `HTTPClient.request`
""" """
filter_args = { filter_args = {
@ -228,7 +247,7 @@ class HTTPClient(object):
**filter_args) **filter_args)
if not (token and endpoint): if not (token and endpoint):
raise exceptions.AuthorizationFailure( raise exceptions.AuthorizationFailure(
"Cannot find endpoint or token for request") _("Cannot find endpoint or token for request"))
old_token_endpoint = (token, endpoint) old_token_endpoint = (token, endpoint)
kwargs.setdefault("headers", {})["X-Auth-Token"] = token kwargs.setdefault("headers", {})["X-Auth-Token"] = token
@ -245,6 +264,10 @@ class HTTPClient(object):
raise raise
self.cached_token = None self.cached_token = None
client.cached_endpoint = None client.cached_endpoint = None
if self.auth_plugin.opts.get('token'):
self.auth_plugin.opts['token'] = None
if self.auth_plugin.opts.get('endpoint'):
self.auth_plugin.opts['endpoint'] = None
self.authenticate() self.authenticate()
try: try:
token, endpoint = self.auth_plugin.token_and_endpoint( token, endpoint = self.auth_plugin.token_and_endpoint(
@ -321,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)
@ -351,8 +378,11 @@ class BaseClient(object):
try: try:
client_path = version_map[str(version)] client_path = version_map[str(version)]
except (KeyError, ValueError): except (KeyError, ValueError):
msg = "Invalid %s client version '%s'. must be one of: %s" % ( msg = _("Invalid %(api_name)s client version '%(version)s'. "
(api_name, version, ', '.join(version_map.keys()))) "Must be one of: %(version_map)s") % {
'api_name': api_name,
'version': version,
'version_map': ', '.join(version_map.keys())}
raise exceptions.UnsupportedVersion(msg) raise exceptions.UnsupportedVersion(msg)
return importutils.import_class(client_path) return importutils.import_class(client_path)

View File

@ -25,6 +25,8 @@ import sys
import six import six
from heatclient.openstack.common._i18n import _
class ClientException(Exception): class ClientException(Exception):
"""The base exception class for all exceptions this library raises. """The base exception class for all exceptions this library raises.
@ -32,14 +34,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 argument(s): %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
@ -69,16 +63,16 @@ class AuthPluginOptionsMissing(AuthorizationFailure):
"""Auth plugin misses some options.""" """Auth plugin misses some options."""
def __init__(self, opt_names): def __init__(self, opt_names):
super(AuthPluginOptionsMissing, self).__init__( super(AuthPluginOptionsMissing, self).__init__(
"Authentication failed. Missing options: %s" % _("Authentication failed. Missing options: %s") %
", ".join(opt_names)) ", ".join(opt_names))
self.opt_names = opt_names self.opt_names = opt_names
class AuthSystemNotFound(AuthorizationFailure): class AuthSystemNotFound(AuthorizationFailure):
"""User has specified a AuthSystem that is not installed.""" """User has specified an AuthSystem that is not installed."""
def __init__(self, auth_system): def __init__(self, auth_system):
super(AuthSystemNotFound, self).__init__( super(AuthSystemNotFound, self).__init__(
"AuthSystemNotFound: %s" % repr(auth_system)) _("AuthSystemNotFound: %s") % repr(auth_system))
self.auth_system = auth_system self.auth_system = auth_system
@ -101,7 +95,7 @@ class AmbiguousEndpoints(EndpointException):
"""Found more than one matching endpoint in Service Catalog.""" """Found more than one matching endpoint in Service Catalog."""
def __init__(self, endpoints=None): def __init__(self, endpoints=None):
super(AmbiguousEndpoints, self).__init__( super(AmbiguousEndpoints, self).__init__(
"AmbiguousEndpoints: %s" % repr(endpoints)) _("AmbiguousEndpoints: %s") % repr(endpoints))
self.endpoints = endpoints self.endpoints = endpoints
@ -109,7 +103,7 @@ class HttpError(ClientException):
"""The base exception class for all HTTP exceptions. """The base exception class for all HTTP exceptions.
""" """
http_status = 0 http_status = 0
message = "HTTP Error" message = _("HTTP Error")
def __init__(self, message=None, details=None, def __init__(self, message=None, details=None,
response=None, request_id=None, response=None, request_id=None,
@ -129,7 +123,7 @@ class HttpError(ClientException):
class HTTPRedirection(HttpError): class HTTPRedirection(HttpError):
"""HTTP Redirection.""" """HTTP Redirection."""
message = "HTTP Redirection" message = _("HTTP Redirection")
class HTTPClientError(HttpError): class HTTPClientError(HttpError):
@ -137,7 +131,7 @@ class HTTPClientError(HttpError):
Exception for cases in which the client seems to have erred. Exception for cases in which the client seems to have erred.
""" """
message = "HTTP Client Error" message = _("HTTP Client Error")
class HttpServerError(HttpError): class HttpServerError(HttpError):
@ -146,7 +140,7 @@ class HttpServerError(HttpError):
Exception for cases in which the server is aware that it has Exception for cases in which the server is aware that it has
erred or is incapable of performing the request. erred or is incapable of performing the request.
""" """
message = "HTTP Server Error" message = _("HTTP Server Error")
class MultipleChoices(HTTPRedirection): class MultipleChoices(HTTPRedirection):
@ -156,7 +150,7 @@ class MultipleChoices(HTTPRedirection):
""" """
http_status = 300 http_status = 300
message = "Multiple Choices" message = _("Multiple Choices")
class BadRequest(HTTPClientError): class BadRequest(HTTPClientError):
@ -165,7 +159,7 @@ class BadRequest(HTTPClientError):
The request cannot be fulfilled due to bad syntax. The request cannot be fulfilled due to bad syntax.
""" """
http_status = 400 http_status = 400
message = "Bad Request" message = _("Bad Request")
class Unauthorized(HTTPClientError): class Unauthorized(HTTPClientError):
@ -175,7 +169,7 @@ class Unauthorized(HTTPClientError):
is required and has failed or has not yet been provided. is required and has failed or has not yet been provided.
""" """
http_status = 401 http_status = 401
message = "Unauthorized" message = _("Unauthorized")
class PaymentRequired(HTTPClientError): class PaymentRequired(HTTPClientError):
@ -184,7 +178,7 @@ class PaymentRequired(HTTPClientError):
Reserved for future use. Reserved for future use.
""" """
http_status = 402 http_status = 402
message = "Payment Required" message = _("Payment Required")
class Forbidden(HTTPClientError): class Forbidden(HTTPClientError):
@ -194,7 +188,7 @@ class Forbidden(HTTPClientError):
to it. to it.
""" """
http_status = 403 http_status = 403
message = "Forbidden" message = _("Forbidden")
class NotFound(HTTPClientError): class NotFound(HTTPClientError):
@ -204,7 +198,7 @@ class NotFound(HTTPClientError):
in the future. in the future.
""" """
http_status = 404 http_status = 404
message = "Not Found" message = _("Not Found")
class MethodNotAllowed(HTTPClientError): class MethodNotAllowed(HTTPClientError):
@ -214,7 +208,7 @@ class MethodNotAllowed(HTTPClientError):
by that resource. by that resource.
""" """
http_status = 405 http_status = 405
message = "Method Not Allowed" message = _("Method Not Allowed")
class NotAcceptable(HTTPClientError): class NotAcceptable(HTTPClientError):
@ -224,7 +218,7 @@ class NotAcceptable(HTTPClientError):
acceptable according to the Accept headers sent in the request. acceptable according to the Accept headers sent in the request.
""" """
http_status = 406 http_status = 406
message = "Not Acceptable" message = _("Not Acceptable")
class ProxyAuthenticationRequired(HTTPClientError): class ProxyAuthenticationRequired(HTTPClientError):
@ -233,7 +227,7 @@ class ProxyAuthenticationRequired(HTTPClientError):
The client must first authenticate itself with the proxy. The client must first authenticate itself with the proxy.
""" """
http_status = 407 http_status = 407
message = "Proxy Authentication Required" message = _("Proxy Authentication Required")
class RequestTimeout(HTTPClientError): class RequestTimeout(HTTPClientError):
@ -242,7 +236,7 @@ class RequestTimeout(HTTPClientError):
The server timed out waiting for the request. The server timed out waiting for the request.
""" """
http_status = 408 http_status = 408
message = "Request Timeout" message = _("Request Timeout")
class Conflict(HTTPClientError): class Conflict(HTTPClientError):
@ -252,7 +246,7 @@ class Conflict(HTTPClientError):
in the request, such as an edit conflict. in the request, such as an edit conflict.
""" """
http_status = 409 http_status = 409
message = "Conflict" message = _("Conflict")
class Gone(HTTPClientError): class Gone(HTTPClientError):
@ -262,7 +256,7 @@ class Gone(HTTPClientError):
not be available again. not be available again.
""" """
http_status = 410 http_status = 410
message = "Gone" message = _("Gone")
class LengthRequired(HTTPClientError): class LengthRequired(HTTPClientError):
@ -272,7 +266,7 @@ class LengthRequired(HTTPClientError):
required by the requested resource. required by the requested resource.
""" """
http_status = 411 http_status = 411
message = "Length Required" message = _("Length Required")
class PreconditionFailed(HTTPClientError): class PreconditionFailed(HTTPClientError):
@ -282,7 +276,7 @@ class PreconditionFailed(HTTPClientError):
put on the request. put on the request.
""" """
http_status = 412 http_status = 412
message = "Precondition Failed" message = _("Precondition Failed")
class RequestEntityTooLarge(HTTPClientError): class RequestEntityTooLarge(HTTPClientError):
@ -291,7 +285,7 @@ class RequestEntityTooLarge(HTTPClientError):
The request is larger than the server is willing or able to process. The request is larger than the server is willing or able to process.
""" """
http_status = 413 http_status = 413
message = "Request Entity Too Large" message = _("Request Entity Too Large")
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
try: try:
@ -308,7 +302,7 @@ class RequestUriTooLong(HTTPClientError):
The URI provided was too long for the server to process. The URI provided was too long for the server to process.
""" """
http_status = 414 http_status = 414
message = "Request-URI Too Long" message = _("Request-URI Too Long")
class UnsupportedMediaType(HTTPClientError): class UnsupportedMediaType(HTTPClientError):
@ -318,7 +312,7 @@ class UnsupportedMediaType(HTTPClientError):
not support. not support.
""" """
http_status = 415 http_status = 415
message = "Unsupported Media Type" message = _("Unsupported Media Type")
class RequestedRangeNotSatisfiable(HTTPClientError): class RequestedRangeNotSatisfiable(HTTPClientError):
@ -328,7 +322,7 @@ class RequestedRangeNotSatisfiable(HTTPClientError):
supply that portion. supply that portion.
""" """
http_status = 416 http_status = 416
message = "Requested Range Not Satisfiable" message = _("Requested Range Not Satisfiable")
class ExpectationFailed(HTTPClientError): class ExpectationFailed(HTTPClientError):
@ -337,7 +331,7 @@ class ExpectationFailed(HTTPClientError):
The server cannot meet the requirements of the Expect request-header field. The server cannot meet the requirements of the Expect request-header field.
""" """
http_status = 417 http_status = 417
message = "Expectation Failed" message = _("Expectation Failed")
class UnprocessableEntity(HTTPClientError): class UnprocessableEntity(HTTPClientError):
@ -347,7 +341,7 @@ class UnprocessableEntity(HTTPClientError):
errors. errors.
""" """
http_status = 422 http_status = 422
message = "Unprocessable Entity" message = _("Unprocessable Entity")
class InternalServerError(HttpServerError): class InternalServerError(HttpServerError):
@ -356,7 +350,7 @@ class InternalServerError(HttpServerError):
A generic error message, given when no more specific message is suitable. A generic error message, given when no more specific message is suitable.
""" """
http_status = 500 http_status = 500
message = "Internal Server Error" message = _("Internal Server Error")
# NotImplemented is a python keyword. # NotImplemented is a python keyword.
@ -367,7 +361,7 @@ class HttpNotImplemented(HttpServerError):
the ability to fulfill the request. the ability to fulfill the request.
""" """
http_status = 501 http_status = 501
message = "Not Implemented" message = _("Not Implemented")
class BadGateway(HttpServerError): class BadGateway(HttpServerError):
@ -377,7 +371,7 @@ class BadGateway(HttpServerError):
response from the upstream server. response from the upstream server.
""" """
http_status = 502 http_status = 502
message = "Bad Gateway" message = _("Bad Gateway")
class ServiceUnavailable(HttpServerError): class ServiceUnavailable(HttpServerError):
@ -386,7 +380,7 @@ class ServiceUnavailable(HttpServerError):
The server is currently unavailable. The server is currently unavailable.
""" """
http_status = 503 http_status = 503
message = "Service Unavailable" message = _("Service Unavailable")
class GatewayTimeout(HttpServerError): class GatewayTimeout(HttpServerError):
@ -396,7 +390,7 @@ class GatewayTimeout(HttpServerError):
response from the upstream server. response from the upstream server.
""" """
http_status = 504 http_status = 504
message = "Gateway Timeout" message = _("Gateway Timeout")
class HttpVersionNotSupported(HttpServerError): class HttpVersionNotSupported(HttpServerError):
@ -405,7 +399,7 @@ class HttpVersionNotSupported(HttpServerError):
The server does not support the HTTP protocol version used in the request. The server does not support the HTTP protocol version used in the request.
""" """
http_status = 505 http_status = 505
message = "HTTP Version Not Supported" message = _("HTTP Version Not Supported")
# _code_map contains all the classes that have http_status attribute. # _code_map contains all the classes that have http_status attribute.
@ -423,12 +417,17 @@ def from_response(response, method, url):
:param method: HTTP method used for request :param method: HTTP method used for request
:param url: URL used for request :param url: URL used for request
""" """
req_id = response.headers.get("x-openstack-request-id")
# NOTE(hdd) true for older versions of nova and cinder
if not req_id:
req_id = response.headers.get("x-compute-request-id")
kwargs = { kwargs = {
"http_status": response.status_code, "http_status": response.status_code,
"response": response, "response": response,
"method": method, "method": method,
"url": url, "url": url,
"request_id": response.headers.get("x-compute-request-id"), "request_id": req_id,
} }
if "retry-after" in response.headers: if "retry-after" in response.headers:
kwargs["retry_after"] = response.headers["retry-after"] kwargs["retry_after"] = response.headers["retry-after"]
@ -440,8 +439,8 @@ def from_response(response, method, url):
except ValueError: except ValueError:
pass pass
else: else:
if isinstance(body, dict): if isinstance(body, dict) and isinstance(body.get("error"), dict):
error = list(body.values())[0] error = body["error"]
kwargs["message"] = error.get("message") kwargs["message"] = error.get("message")
kwargs["details"] = error.get("details") kwargs["details"] = error.get("details")
elif content_type.startswith("text/"): elif content_type.startswith("text/"):

View File

@ -33,7 +33,9 @@ from six.moves.urllib import parse
from heatclient.openstack.common.apiclient import client from heatclient.openstack.common.apiclient import client
def assert_has_keys(dct, required=[], optional=[]): def assert_has_keys(dct, required=None, optional=None):
required = required or []
optional = optional or []
for k in required: for k in required:
try: try:
assert k in dct assert k in dct
@ -79,7 +81,7 @@ class FakeHTTPClient(client.HTTPClient):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.callstack = [] self.callstack = []
self.fixtures = kwargs.pop("fixtures", None) or {} self.fixtures = kwargs.pop("fixtures", None) or {}
if not args and not "auth_plugin" in kwargs: if not args and "auth_plugin" not in kwargs:
args = (None, ) args = (None, )
super(FakeHTTPClient, self).__init__(*args, **kwargs) super(FakeHTTPClient, self).__init__(*args, **kwargs)
@ -166,6 +168,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,

View File

@ -0,0 +1,87 @@
#
# 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 oslo.utils import encodeutils
import six
from heatclient.openstack.common._i18n import _
from heatclient.openstack.common.apiclient import exceptions
from heatclient.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)

View File

@ -24,14 +24,21 @@ import os
import sys import sys
import textwrap import textwrap
from oslo.utils import encodeutils
from oslo.utils import strutils
import prettytable import prettytable
import six import six
from six import moves from six import moves
from heatclient.openstack.common.apiclient import exceptions from heatclient.openstack.common._i18n import _
from heatclient.openstack.common.gettextutils import _
from heatclient.openstack.common import strutils
from heatclient.openstack.common import uuidutils class MissingArgs(Exception):
"""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)
def validate_args(fn, *args, **kwargs): def validate_args(fn, *args, **kwargs):
@ -56,7 +63,7 @@ def validate_args(fn, *args, **kwargs):
required_args = argspec.args[:len(argspec.args) - num_defaults] required_args = argspec.args[:len(argspec.args) - num_defaults]
def isbound(method): def isbound(method):
return getattr(method, 'im_self', None) is not None return getattr(method, '__self__', None) is not None
if isbound(fn): if isbound(fn):
required_args.pop(0) required_args.pop(0)
@ -64,7 +71,7 @@ def validate_args(fn, *args, **kwargs):
missing = [arg for arg in required_args if arg not in kwargs] missing = [arg for arg in required_args if arg not in kwargs]
missing = missing[len(args):] missing = missing[len(args):]
if missing: if missing:
raise exceptions.MissingArgs(missing) raise MissingArgs(missing)
def arg(*args, **kwargs): def arg(*args, **kwargs):
@ -132,7 +139,7 @@ def isunauthenticated(func):
def print_list(objs, fields, formatters=None, sortby_index=0, def print_list(objs, fields, formatters=None, sortby_index=0,
mixed_case_fields=None): mixed_case_fields=None, field_labels=None):
"""Print a list or objects as a table, one row per object. """Print a list or objects as a table, one row per object.
:param objs: iterable of :class:`Resource` :param objs: iterable of :class:`Resource`
@ -141,14 +148,22 @@ def print_list(objs, fields, formatters=None, sortby_index=0,
:param sortby_index: index of the field for sorting table rows :param sortby_index: index of the field for sorting table rows
:param mixed_case_fields: fields corresponding to object attributes that :param mixed_case_fields: fields corresponding to object attributes that
have mixed case names (e.g., 'serverId') have mixed case names (e.g., 'serverId')
:param field_labels: Labels to use in the heading of the table, default to
fields.
""" """
formatters = formatters or {} formatters = formatters or {}
mixed_case_fields = mixed_case_fields or [] mixed_case_fields = mixed_case_fields or []
field_labels = field_labels or fields
if len(field_labels) != len(fields):
raise ValueError(_("Field labels list %(labels)s has different number "
"of elements than fields list %(fields)s"),
{'labels': field_labels, 'fields': fields})
if sortby_index is None: if sortby_index is None:
kwargs = {} kwargs = {}
else: else:
kwargs = {'sortby': fields[sortby_index]} kwargs = {'sortby': field_labels[sortby_index]}
pt = prettytable.PrettyTable(fields, caching=False) pt = prettytable.PrettyTable(field_labels)
pt.align = 'l' pt.align = 'l'
for o in objs: for o in objs:
@ -165,7 +180,10 @@ def print_list(objs, fields, formatters=None, sortby_index=0,
row.append(data) row.append(data)
pt.add_row(row) pt.add_row(row)
print(strutils.safe_encode(pt.get_string(**kwargs))) if six.PY3:
print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode())
else:
print(encodeutils.safe_encode(pt.get_string(**kwargs)))
def print_dict(dct, dict_property="Property", wrap=0): def print_dict(dct, dict_property="Property", wrap=0):
@ -175,7 +193,7 @@ def print_dict(dct, dict_property="Property", wrap=0):
:param dict_property: name of the first column :param dict_property: name of the first column
:param wrap: wrapping for the second column :param wrap: wrapping for the second column
""" """
pt = prettytable.PrettyTable([dict_property, 'Value'], caching=False) pt = prettytable.PrettyTable([dict_property, 'Value'])
pt.align = 'l' pt.align = 'l'
for k, v in six.iteritems(dct): for k, v in six.iteritems(dct):
# convert dict to str to check length # convert dict to str to check length
@ -193,7 +211,11 @@ def print_dict(dct, dict_property="Property", wrap=0):
col1 = '' col1 = ''
else: else:
pt.add_row([k, v]) pt.add_row([k, v])
print(strutils.safe_encode(pt.get_string()))
if six.PY3:
print(encodeutils.safe_encode(pt.get_string()).decode())
else:
print(encodeutils.safe_encode(pt.get_string()))
def get_password(max_password_prompts=3): def get_password(max_password_prompts=3):
@ -217,76 +239,16 @@ def get_password(max_password_prompts=3):
return pw return pw
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:
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:
tmp_id = strutils.safe_encode(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)
def service_type(stype): def service_type(stype):
"""Adds 'service_type' attribute to decorated function. """Adds 'service_type' attribute to decorated function.
Usage: Usage:
@service_type('volume')
def mymethod(f): .. code-block:: python
...
@service_type('volume')
def mymethod(f):
...
""" """
def inner(f): def inner(f):
f.service_type = stype f.service_type = stype

View File

@ -1,245 +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.
"""
System-level utilities and helper functions.
"""
import math
import re
import sys
import unicodedata
import six
from heatclient.openstack.common.gettextutils import _
UNIT_PREFIX_EXPONENT = {
'k': 1,
'K': 1,
'Ki': 1,
'M': 2,
'Mi': 2,
'G': 3,
'Gi': 3,
'T': 4,
'Ti': 4,
}
UNIT_SYSTEM_INFO = {
'IEC': (1024, re.compile(r'(^[-+]?\d*\.?\d+)([KMGT]i?)?(b|bit|B)$')),
'SI': (1000, re.compile(r'(^[-+]?\d*\.?\d+)([kMGT])?(b|bit|B)$')),
}
TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes')
FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no')
SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]")
SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+")
def int_from_bool_as_string(subject):
"""Interpret a string as a boolean and return either 1 or 0.
Any string value in:
('True', 'true', 'On', 'on', '1')
is interpreted as a boolean True.
Useful for JSON-decoded stuff and config file parsing
"""
return bool_from_string(subject) and 1 or 0
def bool_from_string(subject, strict=False, default=False):
"""Interpret a string as a boolean.
A case-insensitive match is performed such that strings matching 't',
'true', 'on', 'y', 'yes', or '1' are considered True and, when
`strict=False`, anything else returns the value specified by 'default'.
Useful for JSON-decoded stuff and config file parsing.
If `strict=True`, unrecognized values, including None, will raise a
ValueError which is useful when parsing values passed in from an API call.
Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'.
"""
if not isinstance(subject, six.string_types):
subject = str(subject)
lowered = subject.strip().lower()
if lowered in TRUE_STRINGS:
return True
elif lowered in FALSE_STRINGS:
return False
elif strict:
acceptable = ', '.join(
"'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS))
msg = _("Unrecognized value '%(val)s', acceptable values are:"
" %(acceptable)s") % {'val': subject,
'acceptable': acceptable}
raise ValueError(msg)
else:
return default
def safe_decode(text, incoming=None, errors='strict'):
"""Decodes incoming text/bytes string using `incoming` if they're not
already unicode.
:param incoming: Text's current encoding
:param errors: Errors handling policy. See here for valid
values http://docs.python.org/2/library/codecs.html
:returns: text or a unicode `incoming` encoded
representation of it.
:raises TypeError: If text is not an instance of str
"""
if not isinstance(text, (six.string_types, six.binary_type)):
raise TypeError("%s can't be decoded" % type(text))
if isinstance(text, six.text_type):
return text
if not incoming:
incoming = (sys.stdin.encoding or
sys.getdefaultencoding())
try:
return text.decode(incoming, errors)
except UnicodeDecodeError:
# Note(flaper87) If we get here, it means that
# sys.stdin.encoding / sys.getdefaultencoding
# didn't return a suitable encoding to decode
# text. This happens mostly when global LANG
# var is not set correctly and there's no
# default encoding. In this case, most likely
# python will use ASCII or ANSI encoders as
# default encodings but they won't be capable
# of decoding non-ASCII characters.
#
# Also, UTF-8 is being used since it's an ASCII
# extension.
return text.decode('utf-8', errors)
def safe_encode(text, incoming=None,
encoding='utf-8', errors='strict'):
"""Encodes incoming text/bytes string using `encoding`.
If incoming is not specified, text is expected to be encoded with
current python's default encoding. (`sys.getdefaultencoding`)
:param incoming: Text's current encoding
:param encoding: Expected encoding for text (Default UTF-8)
:param errors: Errors handling policy. See here for valid
values http://docs.python.org/2/library/codecs.html
:returns: text or a bytestring `encoding` encoded
representation of it.
:raises TypeError: If text is not an instance of str
"""
if not isinstance(text, (six.string_types, six.binary_type)):
raise TypeError("%s can't be encoded" % type(text))
if not incoming:
incoming = (sys.stdin.encoding or
sys.getdefaultencoding())
if isinstance(text, six.text_type):
if six.PY3:
return text.encode(encoding, errors).decode(incoming)
else:
return text.encode(encoding, errors)
elif text and encoding != incoming:
# Decode text before encoding it with `encoding`
text = safe_decode(text, incoming, errors)
if six.PY3:
return text.encode(encoding, errors).decode(incoming)
else:
return text.encode(encoding, errors)
return text
def string_to_bytes(text, unit_system='IEC', return_int=False):
"""Converts a string into an float representation of bytes.
The units supported for IEC ::
Kb(it), Kib(it), Mb(it), Mib(it), Gb(it), Gib(it), Tb(it), Tib(it)
KB, KiB, MB, MiB, GB, GiB, TB, TiB
The units supported for SI ::
kb(it), Mb(it), Gb(it), Tb(it)
kB, MB, GB, TB
Note that the SI unit system does not support capital letter 'K'
:param text: String input for bytes size conversion.
:param unit_system: Unit system for byte size conversion.
:param return_int: If True, returns integer representation of text
in bytes. (default: decimal)
:returns: Numerical representation of text in bytes.
:raises ValueError: If text has an invalid value.
"""
try:
base, reg_ex = UNIT_SYSTEM_INFO[unit_system]
except KeyError:
msg = _('Invalid unit system: "%s"') % unit_system
raise ValueError(msg)
match = reg_ex.match(text)
if match:
magnitude = float(match.group(1))
unit_prefix = match.group(2)
if match.group(3) in ['b', 'bit']:
magnitude /= 8
else:
msg = _('Invalid string format: %s') % text
raise ValueError(msg)
if not unit_prefix:
res = magnitude
else:
res = magnitude * pow(base, UNIT_PREFIX_EXPONENT[unit_prefix])
if return_int:
return int(math.ceil(res))
return res
def to_slug(value, incoming=None, errors="strict"):
"""Normalize string.
Convert to lowercase, remove non-word characters, and convert spaces
to hyphens.
Inspired by Django's `slugify` filter.
:param value: Text to slugify
:param incoming: Text's current encoding
:param errors: Errors handling policy. See here for valid
values http://docs.python.org/2/library/codecs.html
:returns: slugified unicode representation of `value`
:raises TypeError: If text is not an instance of str
"""
value = safe_decode(value, incoming, errors)
# NOTE(aababilov): no need to use safe_(encode|decode) here:
# encodings are always "ascii", error handling is always "ignore"
# and types are always known (first: unicode; second: str)
value = unicodedata.normalize("NFKD", value).encode(
"ascii", "ignore").decode("ascii")
value = SLUGIFY_STRIP_RE.sub("", value).strip().lower()
return SLUGIFY_HYPHENATE_RE.sub("-", value)

View File

@ -23,6 +23,8 @@ import sys
import six import six
import six.moves.urllib.parse as urlparse import six.moves.urllib.parse as urlparse
from oslo.utils import encodeutils
from keystoneclient.auth.identity import v2 as v2_auth from keystoneclient.auth.identity import v2 as v2_auth
from keystoneclient.auth.identity import v3 as v3_auth from keystoneclient.auth.identity import v3 as v3_auth
from keystoneclient import discover from keystoneclient import discover
@ -35,7 +37,6 @@ from heatclient.common import utils
from heatclient import exc from heatclient import exc
from heatclient.openstack.common.gettextutils import _ from heatclient.openstack.common.gettextutils import _
from heatclient.openstack.common import importutils from heatclient.openstack.common import importutils
from heatclient.openstack.common import strutils
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
osprofiler_profiler = importutils.try_import("osprofiler.profiler") osprofiler_profiler = importutils.try_import("osprofiler.profiler")
@ -662,7 +663,7 @@ def main(args=None):
if '--debug' in args or '-d' in args: if '--debug' in args or '-d' in args:
raise raise
else: else:
print(strutils.safe_encode(six.text_type(e)), file=sys.stderr) print(encodeutils.safe_encode(six.text_type(e)), file=sys.stderr)
sys.exit(1) sys.exit(1)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -27,11 +27,11 @@ import testtools
import uuid import uuid
from oslo.serialization import jsonutils from oslo.serialization import jsonutils
from oslo.utils import encodeutils
from keystoneclient.fixture import v2 as ks_v2_fixture from keystoneclient.fixture import v2 as ks_v2_fixture
from keystoneclient.fixture import v3 as ks_v3_fixture from keystoneclient.fixture import v3 as ks_v3_fixture
from heatclient.openstack.common import strutils
from mox3 import mox from mox3 import mox
from heatclient.common import http from heatclient.common import http
@ -1806,7 +1806,7 @@ class ShellTestEvents(ShellBase):
http.HTTPClient.json_request( http.HTTPClient.json_request(
'GET', '/stacks/%s/resources/%s/events' % ( 'GET', '/stacks/%s/resources/%s/events' % (
parse.quote(stack_id, ''), parse.quote(stack_id, ''),
parse.quote(strutils.safe_encode( parse.quote(encodeutils.safe_encode(
resource_name), ''))).AndReturn((resp, resp_dict)) resource_name), ''))).AndReturn((resp, resp_dict))
self.m.ReplayAll() self.m.ReplayAll()
@ -1864,7 +1864,7 @@ class ShellTestEvents(ShellBase):
'GET', '/stacks/%s/resources/%s/events/%s' % 'GET', '/stacks/%s/resources/%s/events/%s' %
( (
parse.quote(stack_id, ''), parse.quote(stack_id, ''),
parse.quote(strutils.safe_encode( parse.quote(encodeutils.safe_encode(
resource_name), ''), resource_name), ''),
parse.quote(self.event_id_one, '') parse.quote(self.event_id_one, '')
)).AndReturn((resp, resp_dict)) )).AndReturn((resp, resp_dict))
@ -2053,7 +2053,7 @@ class ShellTestResources(ShellBase):
'GET', '/stacks/%s/resources/%s' % 'GET', '/stacks/%s/resources/%s' %
( (
parse.quote(stack_id, ''), parse.quote(stack_id, ''),
parse.quote(strutils.safe_encode( parse.quote(encodeutils.safe_encode(
resource_name), '') resource_name), '')
)).AndReturn((resp, resp_dict)) )).AndReturn((resp, resp_dict))
@ -2099,7 +2099,7 @@ class ShellTestResources(ShellBase):
'POST', '/stacks/%s/resources/%s/signal' % 'POST', '/stacks/%s/resources/%s/signal' %
( (
parse.quote(stack_id, ''), parse.quote(stack_id, ''),
parse.quote(strutils.safe_encode( parse.quote(encodeutils.safe_encode(
resource_name), '') resource_name), '')
), ),
data={'message': 'Content'}).AndReturn((resp, '')) data={'message': 'Content'}).AndReturn((resp, ''))
@ -2125,7 +2125,7 @@ class ShellTestResources(ShellBase):
'POST', '/stacks/%s/resources/%s/signal' % 'POST', '/stacks/%s/resources/%s/signal' %
( (
parse.quote(stack_id, ''), parse.quote(stack_id, ''),
parse.quote(strutils.safe_encode( parse.quote(encodeutils.safe_encode(
resource_name), '') resource_name), '')
), data=None).AndReturn((resp, '')) ), data=None).AndReturn((resp, ''))
@ -2192,7 +2192,7 @@ class ShellTestResources(ShellBase):
'POST', '/stacks/%s/resources/%s/signal' % 'POST', '/stacks/%s/resources/%s/signal' %
( (
parse.quote(stack_id, ''), parse.quote(stack_id, ''),
parse.quote(strutils.safe_encode( parse.quote(encodeutils.safe_encode(
resource_name), '') resource_name), '')
), ),
data={'message': 'Content'}).AndReturn((resp, '')) data={'message': 'Content'}).AndReturn((resp, ''))

View File

@ -16,8 +16,9 @@
import six import six
from six.moves.urllib import parse from six.moves.urllib import parse
from oslo.utils import encodeutils
from heatclient.openstack.common.apiclient import base from heatclient.openstack.common.apiclient import base
from heatclient.openstack.common import strutils
from heatclient.v1 import stacks from heatclient.v1 import stacks
DEFAULT_PAGE_SIZE = 20 DEFAULT_PAGE_SIZE = 20
@ -61,7 +62,7 @@ class EventManager(stacks.StackChildManager):
stack_id = self._resolve_stack_id(stack_id) stack_id = self._resolve_stack_id(stack_id)
url = '/stacks/%s/resources/%s/events' % ( url = '/stacks/%s/resources/%s/events' % (
parse.quote(stack_id, ''), parse.quote(stack_id, ''),
parse.quote(strutils.safe_encode(resource_name), '')) parse.quote(encodeutils.safe_encode(resource_name), ''))
if params: if params:
url += '?%s' % parse.urlencode(params, True) url += '?%s' % parse.urlencode(params, True)
@ -77,7 +78,7 @@ class EventManager(stacks.StackChildManager):
stack_id = self._resolve_stack_id(stack_id) stack_id = self._resolve_stack_id(stack_id)
url_str = '/stacks/%s/resources/%s/events/%s' % ( url_str = '/stacks/%s/resources/%s/events/%s' % (
parse.quote(stack_id, ''), parse.quote(stack_id, ''),
parse.quote(strutils.safe_encode(resource_name), ''), parse.quote(encodeutils.safe_encode(resource_name), ''),
parse.quote(event_id, '')) parse.quote(event_id, ''))
resp, body = self.client.json_request('GET', url_str) resp, body = self.client.json_request('GET', url_str)
return Event(self, body['event']) return Event(self, body['event'])

View File

@ -13,8 +13,9 @@
from six.moves.urllib import parse from six.moves.urllib import parse
from oslo.utils import encodeutils
from heatclient.openstack.common.apiclient import base from heatclient.openstack.common.apiclient import base
from heatclient.openstack.common import strutils
class ResourceType(base.Resource): class ResourceType(base.Resource):
@ -43,12 +44,12 @@ class ResourceTypeManager(base.BaseManager):
:param resource_type: name of the resource type to get the details for :param resource_type: name of the resource type to get the details for
""" """
url_str = '/resource_types/%s' % ( url_str = '/resource_types/%s' % (
parse.quote(strutils.safe_encode(resource_type), '')) parse.quote(encodeutils.safe_encode(resource_type), ''))
resp, body = self.client.json_request('GET', url_str) resp, body = self.client.json_request('GET', url_str)
return body return body
def generate_template(self, resource_type): def generate_template(self, resource_type):
url_str = '/resource_types/%s/template' % ( url_str = '/resource_types/%s/template' % (
parse.quote(strutils.safe_encode(resource_type), '')) parse.quote(encodeutils.safe_encode(resource_type), ''))
resp, body = self.client.json_request('GET', url_str) resp, body = self.client.json_request('GET', url_str)
return body return body

View File

@ -15,8 +15,9 @@
from six.moves.urllib import parse from six.moves.urllib import parse
from oslo.utils import encodeutils
from heatclient.openstack.common.apiclient import base from heatclient.openstack.common.apiclient import base
from heatclient.openstack.common import strutils
from heatclient.v1 import stacks from heatclient.v1 import stacks
DEFAULT_PAGE_SIZE = 20 DEFAULT_PAGE_SIZE = 20
@ -57,7 +58,7 @@ class ResourceManager(stacks.StackChildManager):
stack_id = self._resolve_stack_id(stack_id) stack_id = self._resolve_stack_id(stack_id)
url_str = '/stacks/%s/resources/%s' % ( url_str = '/stacks/%s/resources/%s' % (
parse.quote(stack_id, ''), parse.quote(stack_id, ''),
parse.quote(strutils.safe_encode(resource_name), '')) parse.quote(encodeutils.safe_encode(resource_name), ''))
resp, body = self.client.json_request('GET', url_str) resp, body = self.client.json_request('GET', url_str)
return Resource(self, body['resource']) return Resource(self, body['resource'])
@ -70,7 +71,7 @@ class ResourceManager(stacks.StackChildManager):
stack_id = self._resolve_stack_id(stack_id) stack_id = self._resolve_stack_id(stack_id)
url_str = '/stacks/%s/resources/%s/metadata' % ( url_str = '/stacks/%s/resources/%s/metadata' % (
parse.quote(stack_id, ''), parse.quote(stack_id, ''),
parse.quote(strutils.safe_encode(resource_name), '')) parse.quote(encodeutils.safe_encode(resource_name), ''))
resp, body = self.client.json_request('GET', url_str) resp, body = self.client.json_request('GET', url_str)
return body['metadata'] return body['metadata']
@ -83,7 +84,7 @@ class ResourceManager(stacks.StackChildManager):
stack_id = self._resolve_stack_id(stack_id) stack_id = self._resolve_stack_id(stack_id)
url_str = '/stacks/%s/resources/%s/signal' % ( url_str = '/stacks/%s/resources/%s/signal' % (
parse.quote(stack_id, ''), parse.quote(stack_id, ''),
parse.quote(strutils.safe_encode(resource_name), '')) parse.quote(encodeutils.safe_encode(resource_name), ''))
resp, body = self.client.json_request('POST', url_str, data=data) resp, body = self.client.json_request('POST', url_str, data=data)
return body return body
@ -92,6 +93,6 @@ class ResourceManager(stacks.StackChildManager):
instead. instead.
""" """
url_str = '/resource_types/%s/template' % ( url_str = '/resource_types/%s/template' % (
parse.quote(strutils.safe_encode(resource_name), '')) parse.quote(encodeutils.safe_encode(resource_name), ''))
resp, body = self.client.json_request('GET', url_str) resp, body = self.client.json_request('GET', url_str)
return body return body

View File

@ -19,10 +19,10 @@ from six.moves.urllib import request
import yaml import yaml
from oslo.serialization import jsonutils from oslo.serialization import jsonutils
from oslo.utils import strutils
from heatclient.common import template_utils from heatclient.common import template_utils
from heatclient.common import utils from heatclient.common import utils
from heatclient.openstack.common import strutils
import heatclient.exc as exc import heatclient.exc as exc

View File

@ -1,7 +1,7 @@
[DEFAULT] [DEFAULT]
# The list of modules to copy from openstack-common # The list of modules to copy from openstack-common
modules=importutils,gettextutils,strutils,apiclient.base,apiclient.exceptions modules=apiclient
module=cliutils module=cliutils
# The base module to hold the copy of openstack.common # The base module to hold the copy of openstack.common

View File

@ -7,6 +7,7 @@ argparse
iso8601>=0.1.9 iso8601>=0.1.9
PrettyTable>=0.7,<0.8 PrettyTable>=0.7,<0.8
oslo.serialization>=1.0.0 # Apache-2.0 oslo.serialization>=1.0.0 # Apache-2.0
oslo.utils>=1.0.0 # Apache-2.0
python-keystoneclient>=0.11.1 python-keystoneclient>=0.11.1
PyYAML>=3.1.0 PyYAML>=3.1.0
requests>=2.2.0,!=2.4.0 requests>=2.2.0,!=2.4.0