ea9f2ebc15
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
340 lines
13 KiB
Python
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
|