6ccd16cc32
Six is in use to help us to keep support for python 2.7. Since the ussuri cycle we decide to remove the python 2.7 support so we can go ahead and also remove six usage from the python code. Review process and help ----------------------- Removing six introduce a lot of changes and an huge amount of modified files To simplify reviews we decided to split changes into several patches to avoid painful reviews and avoid mistakes. To review this patch you can use the six documentation [1] to obtain help and understand choices. Additional informations ----------------------- Changes related to 'six.b(data)' [2] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ six.b [2] encode the given datas in latin-1 in python3 so I did the same things in this patch. Latin-1 is equal to iso-8859-1 [3]. This encoding is the default encoding [4] of certain descriptive HTTP headers. I suggest to keep latin-1 for the moment and to move to another encoding in a follow-up patch if needed to move to most powerful encoding (utf8). HTML4 support utf8 charset and utf8 is the default charset for HTML5 [5]. Note that this commit message is autogenerated and not necesserly contains changes related to 'six.b' [1] https://six.readthedocs.io/ [2] https://six.readthedocs.io/#six.b [3] https://docs.python.org/3/library/codecs.html#standard-encodings [4] https://www.w3schools.com/charsets/ref_html_8859.asp [5] https://www.w3schools.com/html/html_charset.asp Patch 5 of a serie of 28 patches Change-Id: Idb037ded55698790fc1658896f1e2dcdce89f3f9
191 lines
6.4 KiB
Python
191 lines
6.4 KiB
Python
#
|
|
# 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 abc
|
|
import weakref
|
|
|
|
from keystoneauth1 import exceptions
|
|
from keystoneauth1.identity import generic
|
|
from keystoneauth1 import plugin
|
|
from oslo_config import cfg
|
|
from oslo_utils import excutils
|
|
|
|
import requests
|
|
|
|
from heat.common import config
|
|
from heat.common import exception as heat_exception
|
|
|
|
cfg.CONF.import_opt('client_retry_limit', 'heat.common.config')
|
|
|
|
|
|
class ClientPlugin(object, metaclass=abc.ABCMeta):
|
|
|
|
# Module which contains all exceptions classes which the client
|
|
# may emit
|
|
exceptions_module = None
|
|
|
|
# supported service types, service like cinder support multiple service
|
|
# types, so its used in list format
|
|
service_types = []
|
|
|
|
# To make the backward compatibility with existing resource plugins
|
|
default_version = None
|
|
|
|
supported_versions = []
|
|
|
|
def __init__(self, context):
|
|
self._context = weakref.ref(context)
|
|
self._clients = weakref.ref(context.clients)
|
|
self._client_instances = {}
|
|
self._endpoint_existence = {}
|
|
|
|
@property
|
|
def context(self):
|
|
ctxt = self._context()
|
|
assert ctxt is not None, "Need a reference to the context"
|
|
return ctxt
|
|
|
|
@property
|
|
def clients(self):
|
|
return self._clients()
|
|
|
|
_get_client_option = staticmethod(config.get_client_option)
|
|
|
|
def client(self, version=None):
|
|
if not version:
|
|
version = self.default_version
|
|
|
|
if version in self._client_instances:
|
|
return self._client_instances[version]
|
|
|
|
# Back-ward compatibility
|
|
if version is None:
|
|
self._client_instances[version] = self._create()
|
|
else:
|
|
if version not in self.supported_versions:
|
|
raise heat_exception.InvalidServiceVersion(
|
|
version=version,
|
|
service=self._get_service_name())
|
|
|
|
self._client_instances[version] = self._create(version=version)
|
|
|
|
return self._client_instances[version]
|
|
|
|
@abc.abstractmethod
|
|
def _create(self, version=None):
|
|
"""Return a newly created client."""
|
|
pass
|
|
|
|
def _get_region_name(self):
|
|
reg = self.context.region_name or cfg.CONF.region_name_for_services
|
|
# If Shared Services configured, override region for image/volumes
|
|
shared_services_region_name = cfg.CONF.region_name_for_shared_services
|
|
shared_services_types = cfg.CONF.shared_services_types
|
|
if shared_services_region_name:
|
|
if set(self.service_types) & set(shared_services_types):
|
|
reg = shared_services_region_name
|
|
return reg
|
|
|
|
def url_for(self, **kwargs):
|
|
keystone_session = self.context.keystone_session
|
|
|
|
def get_endpoint():
|
|
return keystone_session.get_endpoint(**kwargs)
|
|
|
|
# NOTE(jamielennox): use the session defined by the keystoneclient
|
|
# options as traditionally the token was always retrieved from
|
|
# keystoneclient.
|
|
try:
|
|
kwargs.setdefault('interface', kwargs.pop('endpoint_type'))
|
|
except KeyError:
|
|
pass
|
|
|
|
kwargs.setdefault('region_name', self._get_region_name())
|
|
|
|
url = None
|
|
try:
|
|
url = get_endpoint()
|
|
except exceptions.EmptyCatalog:
|
|
endpoint = keystone_session.get_endpoint(
|
|
None, interface=plugin.AUTH_INTERFACE)
|
|
token = keystone_session.get_token(None)
|
|
token_obj = generic.Token(endpoint, token)
|
|
auth_ref = token_obj.get_access(keystone_session)
|
|
if auth_ref.has_service_catalog():
|
|
self.context.reload_auth_plugin()
|
|
url = get_endpoint()
|
|
|
|
# NOTE(jamielennox): raising exception maintains compatibility with
|
|
# older keystoneclient service catalog searching.
|
|
if url is None:
|
|
raise exceptions.EndpointNotFound()
|
|
|
|
return url
|
|
|
|
def is_client_exception(self, ex):
|
|
"""Returns True if the current exception comes from the client."""
|
|
if self.exceptions_module:
|
|
if isinstance(self.exceptions_module, list):
|
|
for m in self.exceptions_module:
|
|
if type(ex) in m.__dict__.values():
|
|
return True
|
|
else:
|
|
return type(ex) in self.exceptions_module.__dict__.values()
|
|
return False
|
|
|
|
def is_not_found(self, ex):
|
|
"""Returns True if the exception is a not-found."""
|
|
return False
|
|
|
|
def is_over_limit(self, ex):
|
|
"""Returns True if the exception is an over-limit."""
|
|
return False
|
|
|
|
def is_conflict(self, ex):
|
|
"""Returns True if the exception is a conflict."""
|
|
return False
|
|
|
|
@excutils.exception_filter
|
|
def ignore_not_found(self, ex):
|
|
"""Raises the exception unless it is a not-found."""
|
|
return self.is_not_found(ex)
|
|
|
|
@excutils.exception_filter
|
|
def ignore_conflict_and_not_found(self, ex):
|
|
"""Raises the exception unless it is a conflict or not-found."""
|
|
return self.is_conflict(ex) or self.is_not_found(ex)
|
|
|
|
def does_endpoint_exist(self,
|
|
service_type,
|
|
service_name):
|
|
endpoint_key = (service_type, service_name)
|
|
if endpoint_key not in self._endpoint_existence:
|
|
endpoint_type = self._get_client_option(service_name,
|
|
'endpoint_type')
|
|
try:
|
|
self.url_for(service_type=service_type,
|
|
endpoint_type=endpoint_type)
|
|
self._endpoint_existence[endpoint_key] = True
|
|
except exceptions.EndpointNotFound:
|
|
self._endpoint_existence[endpoint_key] = False
|
|
|
|
return self._endpoint_existence[endpoint_key]
|
|
|
|
|
|
def retry_if_connection_err(exception):
|
|
return isinstance(exception, requests.ConnectionError)
|
|
|
|
|
|
def retry_if_result_is_false(result):
|
|
return result is False
|