ff5b6bc0c9
The end-goal here is to be able to do version discovery rather than relying on config and in-code version defaults for what gets attached to the connection as the service proxy for a service. However, since we're currently constructing Adapter objects at instantation time we'd have to authenticate AND do version discovery on every service when we create a Connection to be able to do that. That would be bad. Add a Descriptor class that creates the Proxy object on-demand. That is, when someone does "conn.compute", a Proxy will be created and returned. To support doing that without a ton of duplicate copy-pasta for each service, add a metaclass to Connection which reads os-service-types and does the import lookup / BaseProxy fallback as part of Connection class creation. One of the upsides to this is that we can add docstrings to the service descriptor objects - meaning that the docs for Connection actually list the proxy objects. While we're in here, fix a NOTE in a connection doc string and add a reference to BaseProxy in the docs so that it'll show up in the docs too. Change-Id: I3bef5de60b848146fc8563d853774769d0875c65
200 lines
6.2 KiB
Python
200 lines
6.2 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.
|
|
|
|
"""
|
|
The :class:`~openstack.service_filter.ServiceFilter` is the base class
|
|
for service identifiers and user service preferences. Each
|
|
:class:`~openstack.resource.Resource` has a service identifier to
|
|
associate the resource with a service. An example of a service identifier
|
|
would be ``openstack.compute.compute_service.ComputeService``.
|
|
The service preference and the service identifier are joined to create a
|
|
filter to match a service.
|
|
|
|
Examples
|
|
--------
|
|
|
|
The :class:`~openstack.service_filter.ServiceFilter` class can be built
|
|
with a service type, interface, region, name, and version.
|
|
|
|
Create a service filter
|
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Create a compute service and service preference. Join the services
|
|
and match::
|
|
|
|
from openstack import service_filter
|
|
from openstack.compute import compute_service
|
|
default = compute_service.ComputeService()
|
|
preference = service_filter.ServiceFilter('compute', version='v2')
|
|
result = preference.join(default)
|
|
matches = (result.match_service_type('compute') and
|
|
result.match_service_name('Hal9000') and
|
|
result.match_region('DiscoveryOne') and
|
|
result.match_interface('public'))
|
|
print(str(result))
|
|
print("matches=" + str(matches))
|
|
|
|
The resulting output from the code::
|
|
|
|
service_type=compute,interface=public,version=v2
|
|
matches=True
|
|
"""
|
|
|
|
|
|
class ValidVersion(object):
|
|
|
|
def __init__(self, module, path=None):
|
|
"""" Valid service version.
|
|
|
|
:param string module: Module associated with version.
|
|
:param string path: URL path version.
|
|
"""
|
|
self.module = module
|
|
self.path = path or module
|
|
|
|
|
|
class ServiceFilter(dict):
|
|
UNVERSIONED = ''
|
|
PUBLIC = 'public'
|
|
INTERNAL = 'internal'
|
|
ADMIN = 'admin'
|
|
valid_versions = []
|
|
|
|
def __init__(self, service_type, interface=PUBLIC, region=None,
|
|
service_name=None, version=None, api_version=None,
|
|
requires_project_id=False):
|
|
"""Create a service identifier.
|
|
|
|
:param string service_type: The desired type of service.
|
|
:param string interface: The exposure of the endpoint. Should be
|
|
`public` (default), `internal` or `admin`.
|
|
:param string region: The desired region (optional).
|
|
:param string service_name: Name of the service
|
|
:param string version: Version of service to use.
|
|
:param string api_version: Microversion of service supported.
|
|
:param bool requires_project_id: True if this service's endpoint
|
|
expects project id to be included.
|
|
"""
|
|
self['service_type'] = service_type.lower()
|
|
self['interface'] = interface
|
|
self['region_name'] = region
|
|
self['service_name'] = service_name
|
|
self['version'] = version
|
|
self['api_version'] = api_version
|
|
self['requires_project_id'] = requires_project_id
|
|
|
|
@classmethod
|
|
def _get_proxy_class_names(cls):
|
|
names = []
|
|
module_name = ".".join(cls.__module__.split('.')[:-1])
|
|
for version in cls.valid_versions:
|
|
names.append("{module}.{version}._proxy.Proxy".format(
|
|
module=module_name,
|
|
version=version.module))
|
|
return names
|
|
|
|
@property
|
|
def service_type(self):
|
|
return self['service_type']
|
|
|
|
@property
|
|
def interface(self):
|
|
return self['interface']
|
|
|
|
@interface.setter
|
|
def interface(self, value):
|
|
self['interface'] = value
|
|
|
|
@property
|
|
def region(self):
|
|
return self['region_name']
|
|
|
|
@region.setter
|
|
def region(self, value):
|
|
self['region_name'] = value
|
|
|
|
@property
|
|
def service_name(self):
|
|
return self['service_name']
|
|
|
|
@service_name.setter
|
|
def service_name(self, value):
|
|
self['service_name'] = value
|
|
|
|
@property
|
|
def version(self):
|
|
return self['version']
|
|
|
|
@version.setter
|
|
def version(self, value):
|
|
self['version'] = value
|
|
|
|
@property
|
|
def api_version(self):
|
|
return self['api_version']
|
|
|
|
@api_version.setter
|
|
def api_version(self, value):
|
|
self['api_version'] = value
|
|
|
|
@property
|
|
def requires_project_id(self):
|
|
return self['requires_project_id']
|
|
|
|
@requires_project_id.setter
|
|
def requires_project_id(self, value):
|
|
self['requires_project_id'] = value
|
|
|
|
@property
|
|
def path(self):
|
|
return self['path']
|
|
|
|
@path.setter
|
|
def path(self, value):
|
|
self['path'] = value
|
|
|
|
def get_path(self, version=None):
|
|
if not self.version:
|
|
self.version = version
|
|
return self.get('path', self._get_valid_version().path)
|
|
|
|
def get_filter(self):
|
|
filter = dict(self)
|
|
del filter['version']
|
|
return filter
|
|
|
|
def _get_valid_version(self):
|
|
if self.valid_versions:
|
|
if self.version:
|
|
for valid in self.valid_versions:
|
|
# NOTE(thowe): should support fuzzy match e.g: v2.1==v2
|
|
if self.version.startswith(valid.module):
|
|
return valid
|
|
return self.valid_versions[0]
|
|
return ValidVersion('')
|
|
|
|
def get_module(self):
|
|
"""Get the full module name associated with the service."""
|
|
module = self.__class__.__module__.split('.')
|
|
module = ".".join(module[:-1])
|
|
module = module + "." + self._get_valid_version().module
|
|
return module
|
|
|
|
def get_service_module(self):
|
|
"""Get the module version of the service name.
|
|
|
|
This would often be the same as the service type except in cases like
|
|
object store where the service type is `object-store` and the module
|
|
is `object_store`.
|
|
"""
|
|
return self.__class__.__module__.split('.')[-2]
|