monasca-agent/monasca_agent/common/keystone.py
Andreas Jaeger ea9f2ebc15 Update api-ref location
The api documentation is now published on docs.openstack.org instead
of developer.openstack.org. Update all links that are changed to the
new location.

Note that redirects will be set up as well but let's point now to the
new location.

For details, see:
http://lists.openstack.org/pipermail/openstack-discuss/2019-July/007828.html

Change-Id: I744e2f029df54410394341e60a46b56658e4175c
2019-07-30 19:51:03 +02:00

340 lines
13 KiB
Python

# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP
# Copyright 2017 Fujitsu LIMITED
# 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 logging
from keystoneauth1 import identity
from keystoneauth1 import session
from keystoneclient import discover
import six
from monasca_agent.common import singleton
from monasca_agent import version as ma_version
LOG = logging.getLogger(__name__)
_DEFAULT_SERVICE_TYPE = 'monitoring'
_DEFAULT_ENDPOINT_TYPE = 'public'
def _sanitize_args(data):
"""Removes keys for which value is None.
:param data: dictionary with data
:type data: dict
:return: cleaned data
:rtype: dict
"""
return {k: v for k, v in data.items() if v is not None}
def get_session(**kwargs):
"""Creates new keystone session.
Method uses :py:class:`keystoneauth1.identity.Password`
abstracting from underlying Keystone version
This method is capable of creating a session regardless of
Keystone version (either v2 or v3). However if:
- using **Keystone v2** following arguments [domain_id, domain_name,
project_domain_id and project_domain_name] should not be set. Keystone V2
does not support authentication with domain scope.
- using **Keystone v2** following arguments are prohibited:
[user_domain_id, user_domain_name]
- using **Keystone v3** be careful with the scope of authentication.
For more details about scopes refer to identity_tokens_ and v3_identity_
.. _v3_api: https://docs.openstack.org/api-ref/identity/v3/index.html?expanded=token-authe
ntication-with-scoped-authorization-detail
.. _identity_tokens: https://docs.openstack.org/admin-guide/identity-tokens.html
In overall:
- for **Keystone V2** following arguments are allowed:
[auth_url, user_id, username, password, trust_id, tenant_name,
tenant_id, project_name, project_id].
* for **Keystone V3** following argumenta are allowed:
[auth_url, user_id, username, password, user_domain_id, user_domain_name,
trust_id, project_id, project_name, project_domain_id,
project_domain_name, domain_id, domain_name, tenant_id, tenant_name]
However, note that project_id and project_name will override tenant_id
and tenant_name, as in::
>>> project_id = project_id or tenant_id
>>> project_name = project_name or tenant_name
Arguments tenant_id and tenant_name are kept for sake of
backward compatibility between two versions of Keystone.
Note:
Keystone version is resolved on the runtime
by keystoneauth1 library
:param string auth_url: URL of keystone service.
:param string username: Username for authentication.
:param string password: Password for authentication.
:param string user_id: User ID for authentication.
:param string user_domain_id: User's domain ID for authentication
(replaced by default_domain_if if set)
:param string user_domain_name: User's domain name for authentication
(replaced by default_domain_name if set)
:param string project_id: Project ID for authentication
:param string project_name: Project Name for authentication
:param string project_domain_id: Project Domain ID for authentication
:param string project_domain_name: Project Domain Name for authentication
:param string tenant_id: Tenant ID for authentication
(replaced by project_id if set)
:param string tenant_name: Tenant Name for authentication
(replaced by project_name if set)
:param string domain_id: Domain ID for authentication.
:param string domain_name: Domain name for authentication
:param string trust_id: Trust ID for authentication.
:param string default_domain_id: Default domain ID for authentication.
:param string default_domain_name: Default domain name for authentication
:param float keystone_timeout: A timeout to pass to requests. This should be a
numerical value indicating some amount (or fraction)
of seconds or 0 for no timeout. (optional, defaults
to 0)
:param verify: The verification arguments to pass to requests. These are of
the same form as requests expects, so True or False to
verify (or not) against system certificates or a path to a
bundle or CA certs to check against or None for requests to
attempt to locate and use certificates. (optional, defaults
to True)
:param bool reauthenticate: Should reauthenticate if token expires
(optional, defaults to True)
:return: session instance
:rtype: keystoneauth1.session.Session
"""
LOG.debug('Initializing keystone session using generic password')
auth = identity.Password(
auth_url=kwargs.get('auth_url', None),
username=kwargs.get('username', None),
password=kwargs.get('password', None),
user_id=kwargs.get('user_id', None),
user_domain_id=kwargs.get('user_domain_id', None),
user_domain_name=kwargs.get('user_domain_name', None),
project_id=kwargs.get('project_id', None),
project_name=kwargs.get('project_name', None),
project_domain_id=kwargs.get('project_domain_id', None),
project_domain_name=kwargs.get('project_domain_name', None),
tenant_id=kwargs.get('tenant_id', None),
tenant_name=kwargs.get('tenant_name', None),
domain_id=kwargs.get('domain_id', None),
domain_name=kwargs.get('domain_name', None),
trust_id=kwargs.get('trust_id', None),
default_domain_id=kwargs.get('default_domain_id', None),
default_domain_name=kwargs.get('default_domain_name', None),
reauthenticate=kwargs.get('reauthenticate', True)
)
sess = session.Session(auth=auth,
app_name='monasca-agent',
app_version=ma_version.version_string,
user_agent='monasca-agent',
timeout=kwargs.get('keystone_timeout', None),
verify=kwargs.get('verify', True))
return sess
def get_client(**kwargs):
"""Creates new keystone client.
Initializes new keystone client.
Method does not assume what version of keystone is used.
That responsibility is delegated to
:py:class:`keystoneauth1.discover.Discover`.
Version of the keystone will be the newest one available.
There are two ways to call this method:
using existing session object (:py:class:`keystoneauth1.session.Session`::
>>> s = session.Session(**args)
>>> c = get_client(session=s)
initializing new keystone client from credentials::
>>> c = get_client({'username':'mini-mon', 'password':'test', ...})
:param kwargs: list of arguments passed to method
:type kwargs: dict
:return: keystone client instance
:rtype: Union[keystoneclient.v3.client.Client,
keystoneclient.v2_0.client.Client]
"""
if 'session' not in kwargs:
LOG.debug('Initializing fresh keystone client')
sess = get_session(**kwargs)
else:
LOG.debug('Initializing keystone client from existing session')
sess = kwargs.get('session')
disc = discover.Discover(session=sess)
LOG.debug('Available keystone versions are %s' % disc.version_data())
ks = disc.create_client(**kwargs)
ks.auth_ref = sess.auth.get_auth_ref(session=sess)
LOG.info('Using keystone version %s', ks.version)
return ks
def get_args(config):
"""Utility to extract keystone args from agent's config.
Method retrieves all keystone related settings, from
agent's configuration, that are actually set.
:param config: agent's config
:type config: dict
:returns: cleaned args
:rtype: dict
"""
raw_args = {
'auth_url': config.get('keystone_url', None),
'username': config.get('username', None),
'password': config.get('password', None),
'user_id': config.get('user_id', None),
'user_domain_id': config.get('user_domain_id', None),
'user_domain_name': config.get('user_domain_name', None),
'project_id': config.get('project_id', None),
'project_name': config.get('project_name', None),
'project_domain_name': config.get('project_domain_name', None),
'project_domain_id': config.get('project_domain_id', None),
'domain_id': config.get('domain_id', None),
'domain_name': config.get('domain_name', None),
'tenant_id': config.get('tenant_id', None),
'tenant_name': config.get('tenant_name', None),
'trust_id': config.get('trust_id', None),
'default_domain_id': config.get('default_domain_id', None),
'default_domain_name': config.get('default_domain_name', None),
'url': config.get('url', None), # hardcoded monasca-api url
'service_type': config.get('service_type', _DEFAULT_SERVICE_TYPE),
'endpoint_type': config.get('endpoint_type', _DEFAULT_ENDPOINT_TYPE),
'region_name': config.get('region_name', None),
'keystone_timeout': config.get('keystone_timeout', None),
'verify': False if config.get('insecure') else config.get('ca_file', None),
'reauthenticate': config.get('reauthenticate', True)
}
clean_args = _sanitize_args(raw_args)
LOG.debug('Removed %d keys that did not present values in configuration',
len(raw_args) - len(clean_args))
return clean_args
@six.add_metaclass(singleton.Singleton)
class Keystone(object):
def __init__(self, config):
self._config = get_args(config)
self._keystone_client = None
def _init_client(self):
"""Get a new keystone client object.
For more details see:
- :py:func:`monasca_agent.common.keystone.get_session(**args)`
- :py:func:`monasca_agent.common.keystone.get_client(**args)`
Note:
This method initializes client only once on
behalf of its own
:return: keystone client instance
:rtype: Union[keystoneclient.v3.client.Client,
keystoneclient.v2_0.client.Client]
"""
if self._keystone_client:
LOG.debug('Keystone client is already initialized')
return self._keystone_client
ks = get_client(**self._config)
self._keystone_client = ks
return ks
def get_monasca_url(self):
"""Retrieves monasca endpoint url.
monasca endpoint url can be retrieved from two locations:
* agent configuration (value must be present under api.url key)
* keystone catalog (requires settings api.service_type,
api.endpoint_type and api.region_name)
First method tries low-cost approach: checking if url is available
in configuration file. If not, it moves to querying the keystone
catalog
:return: monasca endpoint url
:rtype: basestring
"""
if self._config.get('url', None):
endpoint = self._config.get('url')
LOG.debug('Using monasca-api url %s from configuration' % endpoint)
else:
# NOTE(trebskit) no need to sanitize these values here
# as we're using already local (clean) copy
args = {
'service_type': self._config.get('service_type'),
'interface': self._config.get('endpoint_type'),
'region_name': self._config.get('region_name', None) # that one has no default
}
catalog = self._init_client().auth_ref.service_catalog
endpoint = catalog.url_for(**args)
LOG.debug('Using monasca-api url %s from catalog[%s]'
% (endpoint, args))
return endpoint
def get_token(self):
"""Validate token is project scoped and return it if it is
project_id and auth_token were fetched when keystone client was created
"""
return self._init_client().auth_token
def refresh_token(self):
"""Gets a new keystone client object and token
This method should be called if the token has expired
"""
self._keystone_client = None
return self.get_token()
def get_session(self):
"""Returns session of this client.
:return: session instance
:rtype: keystoneauth1.session.Session
"""
return self._init_client().session