223 lines
9.6 KiB
Python
223 lines
9.6 KiB
Python
# Copyright 2017-2020 Wind River Inc
|
|
|
|
# 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.
|
|
|
|
"""
|
|
OpenStack Driver
|
|
"""
|
|
import collections
|
|
import random
|
|
|
|
from oslo_concurrency import lockutils
|
|
from oslo_log import log
|
|
from oslo_utils import timeutils
|
|
|
|
from dccommon import consts
|
|
from dccommon.drivers.openstack.fm import FmClient
|
|
from dccommon.drivers.openstack.keystone_v3 import KeystoneClient
|
|
from dccommon.drivers.openstack.sysinv_v1 import SysinvClient
|
|
from dccommon import exceptions
|
|
|
|
|
|
# Gap, in seconds, to determine whether the given token is about to expire
|
|
# These values are used to randomize the token early renewal duration and
|
|
# to distribute the new keystone creation to different audit cycles
|
|
STALE_TOKEN_DURATION_MIN = 40
|
|
STALE_TOKEN_DURATION_MAX = 120
|
|
STALE_TOKEN_DURATION_STEP = 20
|
|
|
|
KEYSTONE_CLIENT_NAME = 'keystone'
|
|
SYSINV_CLIENT_NAME = 'sysinv'
|
|
FM_CLIENT_NAME = 'fm'
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
LOCK_NAME = 'dc-openstackdriver-platform'
|
|
|
|
SUPPORTED_REGION_CLIENTS = [
|
|
SYSINV_CLIENT_NAME,
|
|
FM_CLIENT_NAME
|
|
]
|
|
|
|
# region client type and class mappings
|
|
region_client_class_map = {
|
|
SYSINV_CLIENT_NAME: SysinvClient,
|
|
FM_CLIENT_NAME: FmClient,
|
|
}
|
|
|
|
|
|
class OpenStackDriver(object):
|
|
|
|
os_clients_dict = collections.defaultdict(dict)
|
|
_identity_tokens = {}
|
|
|
|
def __init__(self, region_name=consts.CLOUD_0, thread_name='dcorch',
|
|
auth_url=None, region_clients=SUPPORTED_REGION_CLIENTS):
|
|
# Check if objects are cached and try to use those
|
|
self.region_name = region_name
|
|
self.keystone_client = None
|
|
self.sysinv_client = None
|
|
self.fm_client = None
|
|
|
|
if region_clients:
|
|
# check if the requested clients are in the supported client list
|
|
result = all(c in SUPPORTED_REGION_CLIENTS for c in region_clients)
|
|
if not result:
|
|
message = ("Requested clients are not supported: %s" %
|
|
' '.join(region_clients))
|
|
LOG.error(message)
|
|
raise exceptions.InvalidInputError
|
|
|
|
self.get_cached_keystone_client(region_name)
|
|
if self.keystone_client is None:
|
|
LOG.info("get new keystone client for subcloud %s", region_name)
|
|
try:
|
|
self.keystone_client = KeystoneClient(region_name, auth_url)
|
|
OpenStackDriver.update_region_clients(region_name,
|
|
KEYSTONE_CLIENT_NAME,
|
|
self.keystone_client)
|
|
except Exception as exception:
|
|
LOG.error('keystone_client region %s error: %s' %
|
|
(region_name, exception.message))
|
|
raise exception
|
|
|
|
if region_clients:
|
|
self.get_cached_region_clients_for_thread(region_name,
|
|
thread_name,
|
|
region_clients)
|
|
for client_name in region_clients:
|
|
client_obj_name = client_name + '_client'
|
|
if getattr(self, client_obj_name) is None:
|
|
# Create new client object and cache it
|
|
try:
|
|
client_object = region_client_class_map[client_name](
|
|
region_name, self.keystone_client.session)
|
|
setattr(self, client_obj_name, client_object)
|
|
OpenStackDriver.update_region_clients(region_name,
|
|
client_name,
|
|
client_object,
|
|
thread_name)
|
|
except Exception as exception:
|
|
LOG.error('Region %s client %s thread %s error: %s' %
|
|
(region_name, client_name, thread_name,
|
|
exception.message))
|
|
raise exception
|
|
|
|
@lockutils.synchronized(LOCK_NAME)
|
|
def get_cached_keystone_client(self, region_name):
|
|
if ((region_name in OpenStackDriver.os_clients_dict) and
|
|
(KEYSTONE_CLIENT_NAME in
|
|
OpenStackDriver.os_clients_dict[region_name]) and
|
|
self._is_token_valid(region_name)):
|
|
self.keystone_client = (OpenStackDriver.os_clients_dict
|
|
[region_name][KEYSTONE_CLIENT_NAME])
|
|
|
|
@lockutils.synchronized(LOCK_NAME)
|
|
def get_cached_region_clients_for_thread(self, region_name, thread_name,
|
|
clients):
|
|
if ((region_name in OpenStackDriver.os_clients_dict) and
|
|
(thread_name in OpenStackDriver.os_clients_dict[
|
|
region_name])):
|
|
for client in clients:
|
|
if client in (OpenStackDriver.os_clients_dict[region_name]
|
|
[thread_name]):
|
|
LOG.debug('Using cached OS %s client objects %s %s' %
|
|
(client, region_name, thread_name))
|
|
client_obj = (OpenStackDriver.os_clients_dict[region_name]
|
|
[thread_name][client])
|
|
setattr(self, client + '_client', client_obj)
|
|
else:
|
|
OpenStackDriver.os_clients_dict[region_name][thread_name] = {}
|
|
|
|
@classmethod
|
|
@lockutils.synchronized(LOCK_NAME)
|
|
def update_region_clients(cls, region_name, client_name, client_object,
|
|
thread_name=None):
|
|
if thread_name is not None:
|
|
cls.os_clients_dict[region_name][thread_name][client_name] = \
|
|
client_object
|
|
else:
|
|
cls.os_clients_dict[region_name][client_name] = client_object
|
|
|
|
@classmethod
|
|
@lockutils.synchronized(LOCK_NAME)
|
|
def delete_region_clients(cls, region_name, clear_token=False):
|
|
LOG.warn("delete_region_clients=%s, clear_token=%s" %
|
|
(region_name, clear_token))
|
|
if region_name in cls.os_clients_dict:
|
|
del cls.os_clients_dict[region_name]
|
|
if clear_token:
|
|
cls._identity_tokens[region_name] = None
|
|
|
|
@classmethod
|
|
@lockutils.synchronized(LOCK_NAME)
|
|
def delete_region_clients_for_thread(cls, region_name, thread_name):
|
|
LOG.debug("delete_region_clients=%s, thread_name=%s" %
|
|
(region_name, thread_name))
|
|
if (region_name in cls.os_clients_dict and
|
|
thread_name in cls.os_clients_dict[region_name]):
|
|
del cls.os_clients_dict[region_name][thread_name]
|
|
|
|
def _is_token_valid(self, region_name):
|
|
try:
|
|
keystone = \
|
|
OpenStackDriver.os_clients_dict[region_name]['keystone'].\
|
|
keystone_client
|
|
if (not OpenStackDriver._identity_tokens
|
|
or region_name not in OpenStackDriver._identity_tokens
|
|
or not OpenStackDriver._identity_tokens[region_name]):
|
|
OpenStackDriver._identity_tokens[region_name] = \
|
|
keystone.tokens.validate(keystone.session.get_token(),
|
|
include_catalog=False)
|
|
LOG.info("Token for subcloud %s expires_at=%s" %
|
|
(region_name,
|
|
OpenStackDriver._identity_tokens[region_name]
|
|
['expires_at']))
|
|
else:
|
|
token = keystone.tokens.validate(
|
|
OpenStackDriver._identity_tokens[region_name],
|
|
include_catalog=False)
|
|
if token != OpenStackDriver._identity_tokens[region_name]:
|
|
LOG.debug("%s: updating token %s to %s" %
|
|
(region_name,
|
|
OpenStackDriver._identity_tokens[region_name],
|
|
token))
|
|
OpenStackDriver._identity_tokens[region_name] = token
|
|
|
|
except Exception as exception:
|
|
LOG.info('_is_token_valid handle: %s', exception.message)
|
|
# Reset the cached dictionary
|
|
OpenStackDriver.os_clients_dict[region_name] = \
|
|
collections.defaultdict(dict)
|
|
OpenStackDriver._identity_tokens[region_name] = None
|
|
return False
|
|
|
|
expiry_time = timeutils.normalize_time(timeutils.parse_isotime(
|
|
self._identity_tokens[region_name]['expires_at']))
|
|
duration = random.randrange(STALE_TOKEN_DURATION_MIN,
|
|
STALE_TOKEN_DURATION_MAX,
|
|
STALE_TOKEN_DURATION_STEP)
|
|
if timeutils.is_soon(expiry_time, duration):
|
|
LOG.info("The cached keystone token for subcloud %s "
|
|
"will expire soon %s" %
|
|
(region_name,
|
|
OpenStackDriver._identity_tokens[region_name]
|
|
['expires_at']))
|
|
# Reset the cached dictionary
|
|
OpenStackDriver.os_clients_dict[region_name] = \
|
|
collections.defaultdict(dict)
|
|
OpenStackDriver._identity_tokens[region_name] = None
|
|
return False
|
|
else:
|
|
return True
|