213 lines
8.8 KiB
Python
213 lines
8.8 KiB
Python
# Copyright 2010-2015 OpenStack Foundation
|
|
# All Rights Reserved.
|
|
#
|
|
# 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.
|
|
|
|
"""Connection Manager for Swift connections that responsible for providing
|
|
connection with valid credentials and updated token"""
|
|
|
|
import logging
|
|
|
|
from oslo_utils import encodeutils
|
|
|
|
from glance_store import exceptions
|
|
from glance_store.i18n import _, _LI
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class SwiftConnectionManager(object):
|
|
"""Connection Manager class responsible for initializing and managing
|
|
swiftclient connections in store. The instance of that class can provide
|
|
swift connections with a valid(and refreshed) user token if the token is
|
|
going to expire soon.
|
|
"""
|
|
|
|
AUTH_HEADER_NAME = 'X-Auth-Token'
|
|
|
|
def __init__(self, store, store_location, context=None,
|
|
allow_reauth=False):
|
|
"""Initialize manager with parameters required to establish connection.
|
|
|
|
Initialize store and prepare it for interacting with swift. Also
|
|
initialize keystone client that need to be used for authentication if
|
|
allow_reauth is True.
|
|
The method invariant is the following: if method was executed
|
|
successfully and self.allow_reauth is True users can safely request
|
|
valid(no expiration) swift connections any time. Otherwise, connection
|
|
manager initialize a connection once and always returns that connection
|
|
to users.
|
|
|
|
:param store: store that provides connections
|
|
:param store_location: image location in store
|
|
:param context: user context to access data in Swift
|
|
:param allow_reauth: defines if re-authentication need to be executed
|
|
when a user request the connection
|
|
"""
|
|
self._client = None
|
|
self.store = store
|
|
self.location = store_location
|
|
self.context = context
|
|
self.allow_reauth = allow_reauth
|
|
self.storage_url = self._get_storage_url()
|
|
self.connection = self._init_connection()
|
|
|
|
def get_connection(self):
|
|
"""Get swift client connection.
|
|
|
|
Returns swift client connection. If allow_reauth is True and
|
|
connection token is going to expire soon then the method returns
|
|
updated connection.
|
|
The method invariant is the following: if self.allow_reauth is False
|
|
then the method returns the same connection for every call. So the
|
|
connection may expire. If self.allow_reauth is True the returned
|
|
swift connection is always valid and cannot expire at least for
|
|
swift_store_expire_soon_interval.
|
|
"""
|
|
if self.allow_reauth:
|
|
# we are refreshing token only and if only connection manager
|
|
# re-authentication is allowed. Token refreshing is setup by
|
|
# connection manager users. Also we disable re-authentication
|
|
# if there is not way to execute it (cannot initialize trusts for
|
|
# multi-tenant or auth_version is not 3)
|
|
auth_ref = self.client.session.auth.auth_ref
|
|
# if connection token is going to expire soon (keystone checks
|
|
# is token is going to expire or expired already)
|
|
if self.store.backend_group:
|
|
interval = getattr(
|
|
self.store.conf, self.store.backend_group
|
|
).swift_store_expire_soon_interval
|
|
else:
|
|
store_conf = self.store.conf.glance_store
|
|
interval = store_conf.swift_store_expire_soon_interval
|
|
|
|
if auth_ref.will_expire_soon(interval):
|
|
LOG.info(_LI("Requesting new token for swift connection."))
|
|
# request new token with session and client provided by store
|
|
auth_token = self.client.session.get_auth_headers().get(
|
|
self.AUTH_HEADER_NAME)
|
|
LOG.info(_LI("Token has been successfully requested. "
|
|
"Refreshing swift connection."))
|
|
# initialize new switclient connection with fresh token
|
|
self.connection = self.store.get_store_connection(
|
|
auth_token, self.storage_url)
|
|
return self.connection
|
|
|
|
@property
|
|
def client(self):
|
|
"""Return keystone client to request a new token.
|
|
|
|
Initialize a client lazily from the method provided by glance_store.
|
|
The method invariant is the following: if client cannot be
|
|
initialized raise exception otherwise return initialized client that
|
|
can be used for re-authentication any time.
|
|
"""
|
|
if self._client is None:
|
|
self._client = self._init_client()
|
|
return self._client
|
|
|
|
def _init_connection(self):
|
|
"""Initialize and return valid Swift connection."""
|
|
auth_token = self.client.session.get_auth_headers().get(
|
|
self.AUTH_HEADER_NAME)
|
|
return self.store.get_store_connection(
|
|
auth_token, self.storage_url)
|
|
|
|
def _init_client(self):
|
|
"""Initialize Keystone client."""
|
|
return self.store.init_client(location=self.location,
|
|
context=self.context)
|
|
|
|
def _get_storage_url(self):
|
|
"""Request swift storage url."""
|
|
raise NotImplementedError()
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
pass
|
|
|
|
|
|
class SingleTenantConnectionManager(SwiftConnectionManager):
|
|
def _get_storage_url(self):
|
|
"""Get swift endpoint from keystone
|
|
|
|
Return endpoint for swift from service catalog. The method works only
|
|
Keystone v3. If you are using different version (1 or 2)
|
|
it returns None.
|
|
:return: swift endpoint
|
|
"""
|
|
if self.store.auth_version == '3':
|
|
try:
|
|
return self.client.session.get_endpoint(
|
|
service_type=self.store.service_type,
|
|
interface=self.store.endpoint_type,
|
|
region_name=self.store.region
|
|
)
|
|
except Exception as e:
|
|
# do the same that swift driver does
|
|
# when catching ClientException
|
|
msg = _("Cannot find swift service endpoint : "
|
|
"%s") % encodeutils.exception_to_unicode(e)
|
|
raise exceptions.BackendException(msg)
|
|
|
|
def _init_connection(self):
|
|
if self.store.auth_version == '3':
|
|
return super(SingleTenantConnectionManager,
|
|
self)._init_connection()
|
|
else:
|
|
# no re-authentication for v1 and v2
|
|
self.allow_reauth = False
|
|
# use good old connection initialization
|
|
return self.store.get_connection(self.location, self.context)
|
|
|
|
|
|
class MultiTenantConnectionManager(SwiftConnectionManager):
|
|
|
|
def __init__(self, store, store_location, context=None,
|
|
allow_reauth=False):
|
|
# no context - no party
|
|
if context is None:
|
|
reason = _("Multi-tenant Swift storage requires a user context.")
|
|
raise exceptions.BadStoreConfiguration(store_name="swift",
|
|
reason=reason)
|
|
super(MultiTenantConnectionManager, self).__init__(
|
|
store, store_location, context, allow_reauth)
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
if self._client and self.client.trust_id:
|
|
# client has been initialized - need to cleanup resources
|
|
LOG.info(_LI("Revoking trust %s"), self.client.trust_id)
|
|
self.client.trusts.delete(self.client.trust_id)
|
|
|
|
def _get_storage_url(self):
|
|
return self.location.swift_url
|
|
|
|
def _init_connection(self):
|
|
if self.allow_reauth:
|
|
try:
|
|
return super(MultiTenantConnectionManager,
|
|
self)._init_connection()
|
|
except Exception as e:
|
|
LOG.debug("Cannot initialize swift connection for multi-tenant"
|
|
" store with trustee token: %s. Using user token for"
|
|
" connection initialization.", e)
|
|
# for multi-tenant store we have a token, so we can use it
|
|
# for connection initialization but we cannot fetch new token
|
|
# with client
|
|
self.allow_reauth = False
|
|
|
|
return self.store.get_store_connection(
|
|
self.context.auth_token, self.storage_url)
|