# 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 from keystoneauth1 import plugin from os_client_config import constructors from shade import _utils from shade import exc class LegacyClientFactoryMixin(object): """Mixin Class containing factory functions for legacy client objects. Methods in this class exist for backwards compatibility so will not go away any time - but they are all things whose use is discouraged. They're in a mixin to unclutter the main class file. """ def _create_legacy_client( self, client, service, deprecated=True, module_name=None, **kwargs): if client not in self._legacy_clients: if deprecated: self._deprecated_import_check(client) if module_name: constructors.get_constructor_mapping()[service] = module_name self._legacy_clients[client] = self._get_client(service, **kwargs) return self._legacy_clients[client] def _deprecated_import_check(self, client): module_name = '{client}client'.format(client=client) warnings.warn( 'Using shade to get a {module_name} object is deprecated. If you' ' need a {module_name} object, please use make_legacy_client in' ' os-client-config instead'.format(module_name=module_name)) try: importlib.import_module(module_name) except ImportError: self.log.error( '{module_name} is no longer a dependency of shade. You need to' ' install python-{module_name} directly.'.format( module_name=module_name)) raise @property def trove_client(self): return self._create_legacy_client('trove', 'database') @property def magnum_client(self): return self._create_legacy_client('magnum', 'container-infra') @property def neutron_client(self): return self._create_legacy_client('neutron', 'network') @property def nova_client(self): return self._create_legacy_client('nova', 'compute', version='2.0') @property def glance_client(self): return self._create_legacy_client('glance', 'image') @property def heat_client(self): return self._create_legacy_client('heat', 'orchestration') @property def swift_client(self): return self._create_legacy_client('swift', 'object-store') @property def cinder_client(self): return self._create_legacy_client('cinder', 'volume') @property def designate_client(self): return self._create_legacy_client('designate', 'dns') @property def keystone_client(self): # Trigger discovery from ksa self._identity_client # Skip broken discovery in ksc. We're good thanks. from keystoneclient.v2_0 import client as v2_client from keystoneclient.v3 import client as v3_client if self.cloud_config.config['identity_api_version'] == '3': client_class = v3_client else: client_class = v2_client return self._create_legacy_client( 'keystone', 'identity', client_class=client_class.Client, deprecated=True, endpoint=self._identity_client.get_endpoint(), endpoint_override=self._identity_client.get_endpoint()) # Set the ironic API microversion to a known-good # supported/tested with the contents of shade. # # NOTE(TheJulia): Defaulted to version 1.6 as the ironic # state machine changes which will increment the version # and break an automatic transition of an enrolled node # to an available state. Locking the version is intended # to utilize the original transition until shade supports # calling for node inspection to allow the transition to # take place automatically. # NOTE(mordred): shade will handle microversions more # directly in the REST layer. This microversion property # will never change. When we implement REST, we should # start at 1.6 since that's what we've been requesting # via ironic_client @property def ironic_api_microversion(self): # NOTE(mordred) Abuse _legacy_clients to only show # this warning once if 'ironic-microversion' not in self._legacy_clients: warnings.warn( 'shade is transitioning to direct REST calls which' ' will handle microversions with no action needed' ' on the part of the user. The ironic_api_microversion' ' property is only used by the legacy ironic_client' ' constructor and will never change. If you are using' ' it for any reason, either switch to just using' ' shade ironic-related API calls, or use os-client-config' ' make_legacy_client directly and pass os_ironic_api_version' ' to it as an argument. It is highly recommended to' ' stop using this property.') self._legacy_clients['ironic-microversion'] = True return self._get_legacy_ironic_microversion() def _get_legacy_ironic_microversion(self): return '1.6' def _join_ksa_version(self, version): return ".".join([str(x) for x in version]) @property def ironic_client(self): # Trigger discovery from ksa. This will make ironicclient and # keystoneauth1.adapter.Adapter code paths both go through discovery. # ironicclient does its own magic with discovery, so we won't # pass an endpoint_override here like we do for keystoneclient. # Just so it's not wasted though, make sure we can handle the # min microversion we need. needed = self._get_legacy_ironic_microversion() # TODO(mordred) Bug in ksa - don't do microversion matching for # auth_type = admin_token. Remove this if when the fix lands. if (hasattr(plugin.BaseAuthPlugin, 'get_endpoint_data') or self.cloud_config.config['auth_type'] not in ( 'admin_token', 'none')): # TODO(mordred) once we're on REST properly, we need a better # method for matching requested and available microversion endpoint_data = self._baremetal_client.get_endpoint_data() if not endpoint_data.min_microversion: raise exc.OpenStackCloudException( "shade needs an ironic that supports microversions") if endpoint_data.min_microversion[1] > int(needed[-1]): raise exc.OpenStackCloudException( "shade needs an ironic that supports microversion {needed}" " but the ironic found has a minimum microversion" " of {found}".format( needed=needed, found=self._join_ksa_version( endpoint_data.min_microversion))) if endpoint_data.max_microversion[1] < int(needed[-1]): raise exc.OpenStackCloudException( "shade needs an ironic that supports microversion {needed}" " but the ironic found has a maximum microversion" " of {found}".format( needed=needed, found=self._join_ksa_version( endpoint_data.max_microversion))) return self._create_legacy_client( 'ironic', 'baremetal', module_name='ironicclient.client.Client', os_ironic_api_version=self._get_legacy_ironic_microversion()) def _get_swift_kwargs(self): auth_version = self.cloud_config.get_api_version('identity') auth_args = self.cloud_config.config.get('auth', {}) os_options = {'auth_version': auth_version} if auth_version == '2.0': os_options['os_tenant_name'] = auth_args.get('project_name') os_options['os_tenant_id'] = auth_args.get('project_id') else: os_options['os_project_name'] = auth_args.get('project_name') os_options['os_project_id'] = auth_args.get('project_id') for key in ( 'username', 'password', 'auth_url', 'user_id', 'project_domain_id', 'project_domain_name', 'user_domain_id', 'user_domain_name'): os_options['os_{key}'.format(key=key)] = auth_args.get(key) return os_options @property def swift_service(self): suppress_warning = 'swift-service' not in self._legacy_clients return self.make_swift_service_object(suppress_warning) def make_swift_service(self, suppress_warning=False): # NOTE(mordred): Not using helper functions because the # error message needs to be different if not suppress_warning: warnings.warn( 'Using shade to get a SwiftService object is deprecated. shade' ' will automatically do the things SwiftServices does as part' ' of the normal object resource calls. If you are having' ' trouble using those such that you still need to use' ' SwiftService, please file a bug with shade.' ' If you understand the issues and want to make this warning' ' go away, use cloud.make_swift_service(True) instead of' ' cloud.swift_service') # Abuse self._legacy_clients so that we only give the warning # once. We don't cache SwiftService objects. self._legacy_clients['swift-service'] = True try: import swiftclient.service except ImportError: self.log.error( 'swiftclient is no longer a dependency of shade. You need to' ' install python-swiftclient directly.') with _utils.shade_exceptions("Error constructing SwiftService"): endpoint = self.get_session_endpoint( service_key='object-store') options = dict(os_auth_token=self.auth_token, os_storage_url=endpoint, os_region_name=self.region_name) options.update(self._get_swift_kwargs()) return swiftclient.service.SwiftService(options=options)