1# Copyright 2010-2015 OpenStack Foundation 2# All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); you may 5# not use this file except in compliance with the License. You may obtain 6# a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13# License for the specific language governing permissions and limitations 14# under the License. 15 16"""Connection Manager for Swift connections that responsible for providing 17connection with valid credentials and updated token""" 18 19import logging 20 21from oslo_utils import encodeutils 22 23from glance_store import exceptions 24from glance_store.i18n import _, _LI 25 26LOG = logging.getLogger(__name__) 27 28 29class SwiftConnectionManager(object): 30 """Connection Manager class responsible for initializing and managing 31 swiftclient connections in store. The instance of that class can provide 32 swift connections with a valid(and refreshed) user token if the token is 33 going to expire soon. 34 """ 35 36 AUTH_HEADER_NAME = 'X-Auth-Token' 37 38 def __init__(self, store, store_location, context=None, 39 allow_reauth=False): 40 """Initialize manager with parameters required to establish connection. 41 42 Initialize store and prepare it for interacting with swift. Also 43 initialize keystone client that need to be used for authentication if 44 allow_reauth is True. 45 The method invariant is the following: if method was executed 46 successfully and self.allow_reauth is True users can safely request 47 valid(no expiration) swift connections any time. Otherwise, connection 48 manager initialize a connection once and always returns that connection 49 to users. 50 51 :param store: store that provides connections 52 :param store_location: image location in store 53 :param context: user context to access data in Swift 54 :param allow_reauth: defines if re-authentication need to be executed 55 when a user request the connection 56 """ 57 self._client = None 58 self.store = store 59 self.location = store_location 60 self.context = context 61 self.allow_reauth = allow_reauth 62 self.storage_url = self._get_storage_url() 63 self.connection = self._init_connection() 64 65 def get_connection(self): 66 """Get swift client connection. 67 68 Returns swift client connection. If allow_reauth is True and 69 connection token is going to expire soon then the method returns 70 updated connection. 71 The method invariant is the following: if self.allow_reauth is False 72 then the method returns the same connection for every call. So the 73 connection may expire. If self.allow_reauth is True the returned 74 swift connection is always valid and cannot expire at least for 75 swift_store_expire_soon_interval. 76 """ 77 if self.allow_reauth: 78 # we are refreshing token only and if only connection manager 79 # re-authentication is allowed. Token refreshing is setup by 80 # connection manager users. Also we disable re-authentication 81 # if there is not way to execute it (cannot initialize trusts for 82 # multi-tenant or auth_version is not 3) 83 auth_ref = self.client.session.auth.auth_ref 84 # if connection token is going to expire soon (keystone checks 85 # is token is going to expire or expired already) 86 if self.store.backend_group: 87 interval = getattr( 88 self.store.conf, self.store.backend_group 89 ).swift_store_expire_soon_interval 90 else: 91 store_conf = self.store.conf.glance_store 92 interval = store_conf.swift_store_expire_soon_interval 93 94 if auth_ref.will_expire_soon(interval): 95 LOG.info(_LI("Requesting new token for swift connection.")) 96 # request new token with session and client provided by store 97 auth_token = self.client.session.get_auth_headers().get( 98 self.AUTH_HEADER_NAME) 99 LOG.info(_LI("Token has been successfully requested. " 100 "Refreshing swift connection.")) 101 # initialize new switclient connection with fresh token 102 self.connection = self.store.get_store_connection( 103 auth_token, self.storage_url) 104 return self.connection 105 106 @property 107 def client(self): 108 """Return keystone client to request a new token. 109 110 Initialize a client lazily from the method provided by glance_store. 111 The method invariant is the following: if client cannot be 112 initialized raise exception otherwise return initialized client that 113 can be used for re-authentication any time. 114 """ 115 if self._client is None: 116 self._client = self._init_client() 117 return self._client 118 119 def _init_connection(self): 120 """Initialize and return valid Swift connection.""" 121 auth_token = self.client.session.get_auth_headers().get( 122 self.AUTH_HEADER_NAME) 123 return self.store.get_store_connection( 124 auth_token, self.storage_url) 125 126 def _init_client(self): 127 """Initialize Keystone client.""" 128 return self.store.init_client(location=self.location, 129 context=self.context) 130 131 def _get_storage_url(self): 132 """Request swift storage url.""" 133 raise NotImplementedError() 134 135 def __enter__(self): 136 return self 137 138 def __exit__(self, exc_type, exc_val, exc_tb): 139 pass 140 141 142class SingleTenantConnectionManager(SwiftConnectionManager): 143 def _get_storage_url(self): 144 """Get swift endpoint from keystone 145 146 Return endpoint for swift from service catalog if not overridden in 147 store configuration. The method works only Keystone v3. 148 If you are using different version (1 or 2) 149 it returns None. 150 :return: swift endpoint 151 """ 152 153 if self.store.conf_endpoint: 154 return self.store.conf_endpoint 155 156 if self.store.auth_version == '3': 157 try: 158 return self.client.session.get_endpoint( 159 service_type=self.store.service_type, 160 interface=self.store.endpoint_type, 161 region_name=self.store.region 162 ) 163 except Exception as e: 164 # do the same that swift driver does 165 # when catching ClientException 166 msg = _("Cannot find swift service endpoint : " 167 "%s") % encodeutils.exception_to_unicode(e) 168 raise exceptions.BackendException(msg) 169 170 def _init_connection(self): 171 if self.store.auth_version == '3': 172 return super(SingleTenantConnectionManager, 173 self)._init_connection() 174 else: 175 # no re-authentication for v1 and v2 176 self.allow_reauth = False 177 # use good old connection initialization 178 return self.store.get_connection(self.location, self.context) 179 180 181class MultiTenantConnectionManager(SwiftConnectionManager): 182 183 def __init__(self, store, store_location, context=None, 184 allow_reauth=False): 185 # no context - no party 186 if context is None: 187 reason = _("Multi-tenant Swift storage requires a user context.") 188 raise exceptions.BadStoreConfiguration(store_name="swift", 189 reason=reason) 190 super(MultiTenantConnectionManager, self).__init__( 191 store, store_location, context, allow_reauth) 192 193 def __exit__(self, exc_type, exc_val, exc_tb): 194 if self._client and self.client.trust_id: 195 # client has been initialized - need to cleanup resources 196 LOG.info(_LI("Revoking trust %s"), self.client.trust_id) 197 self.client.trusts.delete(self.client.trust_id) 198 199 def _get_storage_url(self): 200 return self.location.swift_url 201 202 def _init_connection(self): 203 if self.allow_reauth: 204 try: 205 return super(MultiTenantConnectionManager, 206 self)._init_connection() 207 except Exception as e: 208 LOG.debug("Cannot initialize swift connection for multi-tenant" 209 " store with trustee token: %s. Using user token for" 210 " connection initialization.", e) 211 # for multi-tenant store we have a token, so we can use it 212 # for connection initialization but we cannot fetch new token 213 # with client 214 self.allow_reauth = False 215 216 return self.store.get_store_connection( 217 self.context.auth_token, self.storage_url) 218