
troveclient requires username and password as parameters to the Client object, but if a Session is passed (like we do) that's not needed. A patch has been submitted to troveclient, but until that has been released, simply send None to both parameters. Change-Id: Ie130a4e83cceb7cab69bfbeb559493d195ef35e1
474 lines
19 KiB
Python
474 lines
19 KiB
Python
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
|
|
#
|
|
# 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 adapter
|
|
from keystoneauth1 import plugin
|
|
from keystoneauth1 import session
|
|
import requestsexceptions
|
|
|
|
from os_client_config import _log
|
|
from os_client_config import constructors
|
|
from os_client_config import exceptions
|
|
|
|
|
|
def _get_client(service_key):
|
|
class_mapping = constructors.get_constructor_mapping()
|
|
if service_key not in class_mapping:
|
|
raise exceptions.OpenStackConfigException(
|
|
"Service {service_key} is unkown. Please pass in a client"
|
|
" constructor or submit a patch to os-client-config".format(
|
|
service_key=service_key))
|
|
mod_name, ctr_name = class_mapping[service_key].rsplit('.', 1)
|
|
lib_name = mod_name.split('.')[0]
|
|
try:
|
|
mod = importlib.import_module(mod_name)
|
|
except ImportError:
|
|
raise exceptions.OpenStackConfigException(
|
|
"Client for '{service_key}' was requested, but"
|
|
" {mod_name} was unable to be imported. Either import"
|
|
" the module yourself and pass the constructor in as an argument,"
|
|
" or perhaps you do not have python-{lib_name} installed.".format(
|
|
service_key=service_key,
|
|
mod_name=mod_name,
|
|
lib_name=lib_name))
|
|
try:
|
|
ctr = getattr(mod, ctr_name)
|
|
except AttributeError:
|
|
raise exceptions.OpenStackConfigException(
|
|
"Client for '{service_key}' was requested, but although"
|
|
" {mod_name} imported fine, the constructor at {fullname}"
|
|
" as not found. Please check your installation, we have no"
|
|
" clue what is wrong with your computer.".format(
|
|
service_key=service_key,
|
|
mod_name=mod_name,
|
|
fullname=class_mapping[service_key]))
|
|
return ctr
|
|
|
|
|
|
def _make_key(key, service_type):
|
|
if not service_type:
|
|
return key
|
|
else:
|
|
service_type = service_type.lower().replace('-', '_')
|
|
return "_".join([service_type, key])
|
|
|
|
|
|
class CloudConfig(object):
|
|
def __init__(self, name, region, config,
|
|
force_ipv4=False, auth_plugin=None,
|
|
openstack_config=None):
|
|
self.name = name
|
|
self.region = region
|
|
self.config = config
|
|
self.log = _log.setup_logging(__name__)
|
|
self._force_ipv4 = force_ipv4
|
|
self._auth = auth_plugin
|
|
self._openstack_config = openstack_config
|
|
self._keystone_session = None
|
|
|
|
def __getattr__(self, key):
|
|
"""Return arbitrary attributes."""
|
|
|
|
if key.startswith('os_'):
|
|
key = key[3:]
|
|
|
|
if key in [attr.replace('-', '_') for attr in self.config]:
|
|
return self.config[key]
|
|
else:
|
|
return None
|
|
|
|
def __iter__(self):
|
|
return self.config.__iter__()
|
|
|
|
def __eq__(self, other):
|
|
return (self.name == other.name and self.region == other.region
|
|
and self.config == other.config)
|
|
|
|
def __ne__(self, other):
|
|
return not self == other
|
|
|
|
def get_requests_verify_args(self):
|
|
"""Return the verify and cert values for the requests library."""
|
|
if self.config['verify'] and self.config['cacert']:
|
|
verify = self.config['cacert']
|
|
else:
|
|
verify = self.config['verify']
|
|
if self.config['cacert']:
|
|
warnings.warn(
|
|
"You are specifying a cacert for the cloud {0} but "
|
|
"also to ignore the host verification. The host SSL cert "
|
|
"will not be verified.".format(self.name))
|
|
|
|
cert = self.config.get('cert', None)
|
|
if cert:
|
|
if self.config['key']:
|
|
cert = (cert, self.config['key'])
|
|
return (verify, cert)
|
|
|
|
def get_services(self):
|
|
"""Return a list of service types we know something about."""
|
|
services = []
|
|
for key, val in self.config.items():
|
|
if (key.endswith('api_version')
|
|
or key.endswith('service_type')
|
|
or key.endswith('service_name')):
|
|
services.append("_".join(key.split('_')[:-2]))
|
|
return list(set(services))
|
|
|
|
def get_auth_args(self):
|
|
return self.config['auth']
|
|
|
|
def get_interface(self, service_type=None):
|
|
key = _make_key('interface', service_type)
|
|
interface = self.config.get('interface')
|
|
return self.config.get(key, interface)
|
|
|
|
def get_region_name(self, service_type=None):
|
|
if not service_type:
|
|
return self.region
|
|
key = _make_key('region_name', service_type)
|
|
return self.config.get(key, self.region)
|
|
|
|
def get_api_version(self, service_type):
|
|
key = _make_key('api_version', service_type)
|
|
return self.config.get(key, None)
|
|
|
|
def get_service_type(self, service_type):
|
|
key = _make_key('service_type', service_type)
|
|
# Cinder did an evil thing where they defined a second service
|
|
# type in the catalog. Of course, that's insane, so let's hide this
|
|
# atrocity from the as-yet-unsullied eyes of our users.
|
|
# Of course, if the user requests a volumev2, that structure should
|
|
# still work.
|
|
if (service_type == 'volume' and
|
|
self.get_api_version(service_type).startswith('2')):
|
|
service_type = 'volumev2'
|
|
return self.config.get(key, service_type)
|
|
|
|
def get_service_name(self, service_type):
|
|
key = _make_key('service_name', service_type)
|
|
return self.config.get(key, None)
|
|
|
|
def get_endpoint(self, service_type):
|
|
key = _make_key('endpoint_override', service_type)
|
|
old_key = _make_key('endpoint', service_type)
|
|
return self.config.get(key, self.config.get(old_key, None))
|
|
|
|
@property
|
|
def prefer_ipv6(self):
|
|
return not self._force_ipv4
|
|
|
|
@property
|
|
def force_ipv4(self):
|
|
return self._force_ipv4
|
|
|
|
def get_auth(self):
|
|
"""Return a keystoneauth plugin from the auth credentials."""
|
|
return self._auth
|
|
|
|
def get_session(self):
|
|
"""Return a keystoneauth session based on the auth credentials."""
|
|
if self._keystone_session is None:
|
|
if not self._auth:
|
|
raise exceptions.OpenStackConfigException(
|
|
"Problem with auth parameters")
|
|
(verify, cert) = self.get_requests_verify_args()
|
|
# Turn off urllib3 warnings about insecure certs if we have
|
|
# explicitly configured requests to tell it we do not want
|
|
# cert verification
|
|
if not verify:
|
|
self.log.debug(
|
|
"Turning off SSL warnings for {cloud}:{region}"
|
|
" since verify=False".format(
|
|
cloud=self.name, region=self.region))
|
|
requestsexceptions.squelch_warnings(insecure_requests=not verify)
|
|
self._keystone_session = session.Session(
|
|
auth=self._auth,
|
|
verify=verify,
|
|
cert=cert,
|
|
timeout=self.config['api_timeout'])
|
|
return self._keystone_session
|
|
|
|
def get_session_client(self, service_key):
|
|
"""Return a prepped requests adapter for a given service.
|
|
|
|
This is useful for making direct requests calls against a
|
|
'mounted' endpoint. That is, if you do:
|
|
|
|
client = get_session_client('compute')
|
|
|
|
then you can do:
|
|
|
|
client.get('/flavors')
|
|
|
|
and it will work like you think.
|
|
"""
|
|
|
|
return adapter.Adapter(
|
|
session=self.get_session(),
|
|
service_type=self.get_service_type(service_key),
|
|
service_name=self.get_service_name(service_key),
|
|
interface=self.get_interface(service_key),
|
|
region_name=self.region)
|
|
|
|
def get_session_endpoint(self, service_key):
|
|
"""Return the endpoint from config or the catalog.
|
|
|
|
If a configuration lists an explicit endpoint for a service,
|
|
return that. Otherwise, fetch the service catalog from the
|
|
keystone session and return the appropriate endpoint.
|
|
|
|
:param service_key: Generic key for service, such as 'compute' or
|
|
'network'
|
|
|
|
:returns: Endpoint for the service, or None if not found
|
|
"""
|
|
|
|
override_endpoint = self.get_endpoint(service_key)
|
|
if override_endpoint:
|
|
return override_endpoint
|
|
# keystone is a special case in keystone, because what?
|
|
session = self.get_session()
|
|
if service_key == 'identity':
|
|
endpoint = session.get_endpoint(interface=plugin.AUTH_INTERFACE)
|
|
else:
|
|
endpoint = session.get_endpoint(
|
|
service_type=self.get_service_type(service_key),
|
|
service_name=self.get_service_name(service_key),
|
|
interface=self.get_interface(service_key),
|
|
region_name=self.region)
|
|
return endpoint
|
|
|
|
def get_legacy_client(
|
|
self, service_key, client_class=None, interface_key=None,
|
|
pass_version_arg=True, version=None, **kwargs):
|
|
"""Return a legacy OpenStack client object for the given config.
|
|
|
|
Most of the OpenStack python-*client libraries have the same
|
|
interface for their client constructors, but there are several
|
|
parameters one wants to pass given a :class:`CloudConfig` object.
|
|
|
|
In the future, OpenStack API consumption should be done through
|
|
the OpenStack SDK, but that's not ready yet. This is for getting
|
|
Client objects from python-*client only.
|
|
|
|
:param service_key: Generic key for service, such as 'compute' or
|
|
'network'
|
|
:param client_class: Class of the client to be instantiated. This
|
|
should be the unversioned version if there
|
|
is one, such as novaclient.client.Client, or
|
|
the versioned one, such as
|
|
neutronclient.v2_0.client.Client if there isn't
|
|
:param interface_key: (optional) Some clients, such as glanceclient
|
|
only accept the parameter 'interface' instead
|
|
of 'endpoint_type' - this is a get-out-of-jail
|
|
parameter for those until they can be aligned.
|
|
os-client-config understands this to be the
|
|
case if service_key is image, so this is really
|
|
only for use with other unknown broken clients.
|
|
:param pass_version_arg: (optional) If a versioned Client constructor
|
|
was passed to client_class, set this to
|
|
False, which will tell get_client to not
|
|
pass a version parameter. os-client-config
|
|
already understand that this is the
|
|
case for network, so it can be omitted in
|
|
that case.
|
|
:param version: (optional) Version string to override the configured
|
|
version string.
|
|
:param kwargs: (optional) keyword args are passed through to the
|
|
Client constructor, so this is in case anything
|
|
additional needs to be passed in.
|
|
"""
|
|
if not client_class:
|
|
client_class = _get_client(service_key)
|
|
|
|
# Because of course swift is different
|
|
if service_key == 'object-store':
|
|
return self._get_swift_client(client_class=client_class, **kwargs)
|
|
interface = self.get_interface(service_key)
|
|
# trigger exception on lack of service
|
|
endpoint = self.get_session_endpoint(service_key)
|
|
endpoint_override = self.get_endpoint(service_key)
|
|
|
|
if not interface_key:
|
|
if service_key in ('image', 'key-manager'):
|
|
interface_key = 'interface'
|
|
else:
|
|
interface_key = 'endpoint_type'
|
|
|
|
constructor_kwargs = dict(
|
|
session=self.get_session(),
|
|
service_name=self.get_service_name(service_key),
|
|
service_type=self.get_service_type(service_key),
|
|
endpoint_override=endpoint_override,
|
|
region_name=self.region)
|
|
|
|
if service_key == 'image':
|
|
# os-client-config does not depend on glanceclient, but if
|
|
# the user passed in glanceclient.client.Client, which they
|
|
# would need to do if they were requesting 'image' - then
|
|
# they necessarily have glanceclient installed
|
|
from glanceclient.common import utils as glance_utils
|
|
endpoint, detected_version = glance_utils.strip_version(endpoint)
|
|
# If the user has passed in a version, that's explicit, use it
|
|
if not version:
|
|
version = detected_version
|
|
# If the user has passed in or configured an override, use it.
|
|
# Otherwise, ALWAYS pass in an endpoint_override becuase
|
|
# we've already done version stripping, so we don't want version
|
|
# reconstruction to happen twice
|
|
if not endpoint_override:
|
|
constructor_kwargs['endpoint_override'] = endpoint
|
|
constructor_kwargs.update(kwargs)
|
|
constructor_kwargs[interface_key] = interface
|
|
if pass_version_arg:
|
|
if not version:
|
|
version = self.get_api_version(service_key)
|
|
# Temporary workaround while we wait for python-openstackclient
|
|
# to be able to handle 2.0 which is what neutronclient expects
|
|
if service_key == 'network' and version == '2':
|
|
version = '2.0'
|
|
if service_key == 'identity':
|
|
# Workaround for bug#1513839
|
|
if 'endpoint' not in constructor_kwargs:
|
|
endpoint = self.get_session_endpoint('identity')
|
|
constructor_kwargs['endpoint'] = endpoint
|
|
if service_key == 'network':
|
|
constructor_kwargs['api_version'] = version
|
|
else:
|
|
constructor_kwargs['version'] = version
|
|
if service_key == 'database':
|
|
# TODO(mordred) Remove when https://review.openstack.org/314032
|
|
# has landed and released. We're passing in a Session, but the
|
|
# trove Client object has username and password as required
|
|
# args
|
|
constructor_kwargs['username'] = None
|
|
constructor_kwargs['password'] = None
|
|
|
|
return client_class(**constructor_kwargs)
|
|
|
|
def _get_swift_client(self, client_class, **kwargs):
|
|
auth_args = self.get_auth_args()
|
|
auth_version = self.get_api_version('identity')
|
|
session = self.get_session()
|
|
token = session.get_token()
|
|
endpoint = self.get_session_endpoint(service_key='object-store')
|
|
if not endpoint:
|
|
return None
|
|
# If we have a username/password, we want to pass them to
|
|
# swift - because otherwise it will not re-up tokens appropriately
|
|
# However, if we only have non-password auth, then get a token
|
|
# and pass it in
|
|
swift_kwargs = dict(
|
|
auth_version=auth_version,
|
|
preauthurl=endpoint,
|
|
preauthtoken=token,
|
|
os_options=dict(
|
|
region_name=self.get_region_name(),
|
|
auth_token=token,
|
|
object_storage_url=endpoint,
|
|
service_type=self.get_service_type('object-store'),
|
|
endpoint_type=self.get_interface('object-store'),
|
|
|
|
))
|
|
if self.config['api_timeout'] is not None:
|
|
swift_kwargs['timeout'] = float(self.config['api_timeout'])
|
|
|
|
# create with password
|
|
swift_kwargs['user'] = auth_args.get('username')
|
|
swift_kwargs['key'] = auth_args.get('password')
|
|
swift_kwargs['authurl'] = auth_args.get('auth_url')
|
|
os_options = {}
|
|
if auth_version == '2.0':
|
|
os_options['tenant_name'] = auth_args.get('project_name')
|
|
os_options['tenant_id'] = auth_args.get('project_id')
|
|
else:
|
|
os_options['project_name'] = auth_args.get('project_name')
|
|
os_options['project_id'] = auth_args.get('project_id')
|
|
|
|
for key in (
|
|
'user_id',
|
|
'project_domain_id',
|
|
'project_domain_name',
|
|
'user_domain_id',
|
|
'user_domain_name'):
|
|
os_options[key] = auth_args.get(key)
|
|
swift_kwargs['os_options'].update(os_options)
|
|
|
|
return client_class(**swift_kwargs)
|
|
|
|
def get_cache_expiration_time(self):
|
|
if self._openstack_config:
|
|
return self._openstack_config.get_cache_expiration_time()
|
|
|
|
def get_cache_path(self):
|
|
if self._openstack_config:
|
|
return self._openstack_config.get_cache_path()
|
|
|
|
def get_cache_class(self):
|
|
if self._openstack_config:
|
|
return self._openstack_config.get_cache_class()
|
|
|
|
def get_cache_arguments(self):
|
|
if self._openstack_config:
|
|
return self._openstack_config.get_cache_arguments()
|
|
|
|
def get_cache_expiration(self):
|
|
if self._openstack_config:
|
|
return self._openstack_config.get_cache_expiration()
|
|
|
|
def get_cache_resource_expiration(self, resource, default=None):
|
|
"""Get expiration time for a resource
|
|
|
|
:param resource: Name of the resource type
|
|
:param default: Default value to return if not found (optional,
|
|
defaults to None)
|
|
|
|
:returns: Expiration time for the resource type as float or default
|
|
"""
|
|
if self._openstack_config:
|
|
expiration = self._openstack_config.get_cache_expiration()
|
|
if resource not in expiration:
|
|
return default
|
|
return float(expiration[resource])
|
|
|
|
def get_external_networks(self):
|
|
"""Get list of network names for external networks."""
|
|
return [
|
|
net['name'] for net in self.config['networks']
|
|
if net['routes_externally']]
|
|
|
|
def get_internal_networks(self):
|
|
"""Get list of network names for internal networks."""
|
|
return [
|
|
net['name'] for net in self.config['networks']
|
|
if not net['routes_externally']]
|
|
|
|
def get_default_network(self):
|
|
"""Get network used for default interactions."""
|
|
for net in self.config['networks']:
|
|
if net['default_interface']:
|
|
return net['name']
|
|
return None
|
|
|
|
def get_nat_destination(self):
|
|
"""Get network used for NAT destination."""
|
|
for net in self.config['networks']:
|
|
if net['nat_destination']:
|
|
return net['name']
|
|
return None
|