diff --git a/shade/_legacy_clients.py b/shade/_legacy_clients.py new file mode 100644 index 000000000..3790d593b --- /dev/null +++ b/shade/_legacy_clients.py @@ -0,0 +1,197 @@ +# 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 os_client_config import constructors + +from shade import _utils + + +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' + 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_or_return_legacy_client('neutron', 'network') + + @property + def nova_client(self): + return self._create_legacy_client( + 'nova', 'compute', version='2.0', deprecated=False) + + @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', deprecated=False) + + @property + def keystone_client(self): + return self._create_legacy_client( + 'keystone', 'identity', deprecated=False) + + # 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' + + @property + def ironic_client(self): + return self._create_legacy_client( + 'ironic', 'baremetal', deprecated=False, + 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) diff --git a/shade/openstackcloud.py b/shade/openstackcloud.py index 251c385eb..cb50a42b7 100644 --- a/shade/openstackcloud.py +++ b/shade/openstackcloud.py @@ -13,7 +13,6 @@ import collections import functools import hashlib -import importlib import ipaddress import json import jsonpatch @@ -40,6 +39,7 @@ from shade import _adapter from shade._heat import event_utils from shade._heat import template_utils from shade import _log +from shade import _legacy_clients from shade import _normalize from shade import meta from shade import task_manager @@ -92,7 +92,9 @@ def _no_pending_stacks(stacks): return True -class OpenStackCloud(_normalize.Normalizer): +class OpenStackCloud( + _normalize.Normalizer, + _legacy_clients.LegacyClientFactoryMixin): """Represent a connection to an OpenStack Cloud. OpenStackCloud is the entry point for all cloud operations, regardless @@ -296,18 +298,7 @@ class OpenStackCloud(_normalize.Normalizer): self._keystone_session = None - self._cinder_client = None - self._glance_client = None - self._glance_endpoint = None - self._heat_client = None - self._keystone_client = None - self._neutron_client = None - self._nova_client = None - self._trove_client = None - self._designate_client = None - self._magnum_client = None - self._swift_client = None - + self._legacy_clients = {} self._raw_clients = {} self._local_ipv6 = _utils.localhost_supports_ipv6() @@ -543,134 +534,6 @@ class OpenStackCloud(_normalize.Normalizer): new_resource = _utils._dictify_resource(resource) return pprint.pformat(new_resource) - # LEGACY CLIENT IMPORTS - def _deprecated_import_check(self, module_name): - 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): - if self._trove_client is None: - _deprecated_import_check('troveclient') - self._trove_client = self._get_client('database') - return self._trove_client - - @property - def magnum_client(self): - if self._magnum_client is None: - _deprecated_import_check('magnumclient') - self._magnum_client = self._get_client('container-infra') - return self._magnum_client - - @property - def neutron_client(self): - if self._neutron_client is None: - _deprecated_import_check('neutronclient') - self._neutron_client = self._get_client('network') - return self._neutron_client - - @property - def nova_client(self): - if self._nova_client is None: - self._nova_client = self._get_client('compute', version='2.0') - return self._nova_client - - @property - def glance_client(self): - if self._glance_client is None: - _deprecated_import_check('glanceclient') - self._glance_client = self._get_client('image') - return self._glance_client - - @property - def heat_client(self): - if self._heat_client is None: - _deprecated_import_check('heatclient') - self._heat_client = self._get_client('orchestration') - return self._heat_client - - @property - def swift_client(self): - if self._swift_client is None: - _deprecated_import_check('swiftclient') - self._swift_client = self._get_client('object-store') - return self._swift_client - - 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): - # NOTE(mordred): Not using deprecated_client_check because the - # error message needs to be different - 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 " - "swift client"): - 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) - - @property - def cinder_client(self): - if self._cinder_client is None: - _deprecated_import_check('cinderclient') - self._cinder_client = self._get_client('volume') - return self._cinder_client - - @property - def designate_client(self): - # Note: Explicit constructor is needed until occ 1.27.0 - import designateclient.client # flake8: noqa - if self._designate_client is None: - self._designate_client = self._get_client( - 'dns', designateclient.client.Client) - return self._designate_client - - @property - def keystone_client(self): - if self._keystone_client is None: - self._keystone_client = self._get_client('identity') - return self._keystone_client - @property def keystone_session(self): if self._keystone_session is None: diff --git a/shade/operatorcloud.py b/shade/operatorcloud.py index dd365d79e..d994081c5 100644 --- a/shade/operatorcloud.py +++ b/shade/operatorcloud.py @@ -13,7 +13,6 @@ import datetime import jsonpatch -from ironicclient import client as ironic_client from ironicclient import exceptions as ironic_exceptions from novaclient import exceptions as nova_exceptions @@ -32,30 +31,6 @@ class OperatorCloud(openstackcloud.OpenStackCloud): See the :class:`OpenStackCloud` class for a description of most options. """ - def __init__(self, *args, **kwargs): - super(OperatorCloud, self).__init__(*args, **kwargs) - self._ironic_client = None - - # 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. - ironic_api_microversion = '1.6' - - @property - def ironic_client(self): - if self._ironic_client is None: - self._ironic_client = self._get_client( - 'baremetal', ironic_client.Client, - os_ironic_api_version=self.ironic_api_microversion) - return self._ironic_client - def list_nics(self): with _utils.shade_exceptions("Error fetching machine port list"): return self.manager.submit_task(_tasks.MachinePortList())