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