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:
parent
aea6e7dcbc
commit
5259f00827
@ -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]
|
||||||
|
@ -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'))
|
|
40
heatclient/openstack/common/_i18n.py
Normal file
40
heatclient/openstack/common/_i18n.py
Normal 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
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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)
|
||||||
|
@ -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/"):
|
||||||
|
@ -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,
|
||||||
|
87
heatclient/openstack/common/apiclient/utils.py
Normal file
87
heatclient/openstack/common/apiclient/utils.py
Normal 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)
|
@ -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
|
||||||
|
@ -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)
|
|
@ -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__":
|
||||||
|
@ -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, ''))
|
||||||
|
@ -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'])
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user