# Copyright 2018 Red Hat, Inc. # # 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 importlib import warnings import os_service_types from openstack import _log from openstack import service_description _logger = _log.setup_logging('openstack') _service_type_manager = os_service_types.ServiceTypes() _DOC_TEMPLATE = ( ":class:`{class_name}` for {service_type} aka {project}") _PROXY_TEMPLATE = """Proxy for {service_type} aka {project} This proxy object could be an instance of {class_doc_strings} depending on client configuration and which version of the service is found on remotely on the cloud. """ class ConnectionMeta(type): def __new__(meta, name, bases, dct): for service in _service_type_manager.services: service_type = service['service_type'] if service_type == 'ec2-api': # NOTE(mordred) It doesn't make any sense to use ec2-api # from openstacksdk. The credentials API calls are all calls # on identity endpoints. continue desc_class = _find_service_description_class(service_type) descriptor_args = {'service_type': service_type} if not desc_class.supported_versions: doc = _DOC_TEMPLATE.format( class_name="{service_type} Proxy".format( service_type=service_type), **service) elif len(desc_class.supported_versions) == 1: supported_version = list( desc_class.supported_versions.keys())[0] doc = _DOC_TEMPLATE.format( class_name="{service_type} Proxy <{name}>".format( service_type=service_type, name=supported_version), **service) else: class_doc_strings = "\n".join([ ":class:`{class_name}`".format( class_name=proxy_class.__name__) for proxy_class in desc_class.supported_versions.values()]) doc = _PROXY_TEMPLATE.format( class_doc_strings=class_doc_strings, **service) descriptor = desc_class(**descriptor_args) descriptor.__doc__ = doc st = service_type.replace('-', '_') dct[st] = descriptor # Register the descriptor class with every known alias. Don't # add doc strings though - although they are supported, we don't # want to give anybody any bad ideas. Making a second descriptor # does not introduce runtime cost as the descriptors all use # the same _proxies dict on the instance. for alias_name in _get_aliases(st): if alias_name[-1].isdigit(): continue alias_descriptor = desc_class(**descriptor_args) dct[alias_name.replace('-', '_')] = alias_descriptor return super(ConnectionMeta, meta).__new__(meta, name, bases, dct) def _get_aliases(service_type, aliases=None): # We make connection attributes for all official real type names # and aliases. Three services have names they were called by in # openstacksdk that are not covered by Service Types Authority aliases. # Include them here - but take heed, no additional values should ever # be added to this list. # that were only used in openstacksdk resource naming. LOCAL_ALIASES = { 'baremetal': 'bare_metal', 'block_storage': 'block_store', 'clustering': 'cluster', } all_types = set(_service_type_manager.get_aliases(service_type)) if aliases: all_types.update(aliases) if service_type in LOCAL_ALIASES: all_types.add(LOCAL_ALIASES[service_type]) return all_types def _find_service_description_class(service_type): package_name = 'openstack.{service_type}'.format( service_type=service_type).replace('-', '_') module_name = service_type.replace('-', '_') + '_service' class_name = ''.join( [part.capitalize() for part in module_name.split('_')]) try: import_name = '.'.join([package_name, module_name]) service_description_module = importlib.import_module(import_name) except ImportError as e: # ImportWarning is ignored by default. This warning is here # as an opt-in for people trying to figure out why something # didn't work. warnings.warn( "Could not import {service_type} service description: {e}".format( service_type=service_type, e=str(e)), ImportWarning) return service_description.ServiceDescription # There are no cases in which we should have a module but not the class # inside it. service_description_class = getattr(service_description_module, class_name) return service_description_class