Fix Token Timeout Defect
Defect for Shipyard-45 - Change session authentication to a user provided function for generating request headers. - On a 401 response from Drydock, the API client will refresh the authentication headers once and retry the call. Change-Id: I0f0db8885f1e5f15b5ab6dda4549b9e81fb4a184
This commit is contained in:
parent
3fdebedf95
commit
14f4cfddc5
|
@ -103,11 +103,8 @@ def drydock(ctx, debug, url, os_project_domain_name, os_user_domain_name,
|
||||||
# setup the drydock client using the passed parameters.
|
# setup the drydock client using the passed parameters.
|
||||||
url_parse_result = urlparse(url)
|
url_parse_result = urlparse(url)
|
||||||
|
|
||||||
if not os_token:
|
def auth_gen():
|
||||||
token = KeystoneClient.get_token(ks_sess=ks_sess)
|
return list(ks_sess.get_auth_headers().items())
|
||||||
logger.debug("Creating Drydock client with token %s." % token)
|
|
||||||
else:
|
|
||||||
token = os_token
|
|
||||||
|
|
||||||
if not url_parse_result.scheme:
|
if not url_parse_result.scheme:
|
||||||
ctx.fail('URL must specify a scheme and hostname, optionally a port')
|
ctx.fail('URL must specify a scheme and hostname, optionally a port')
|
||||||
|
@ -116,7 +113,7 @@ def drydock(ctx, debug, url, os_project_domain_name, os_user_domain_name,
|
||||||
scheme=url_parse_result.scheme,
|
scheme=url_parse_result.scheme,
|
||||||
host=url_parse_result.hostname,
|
host=url_parse_result.hostname,
|
||||||
port=url_parse_result.port,
|
port=url_parse_result.port,
|
||||||
token=token))
|
auth_gen=auth_gen))
|
||||||
|
|
||||||
|
|
||||||
drydock.add_command(task.task)
|
drydock.add_command(task.task)
|
||||||
|
|
|
@ -24,17 +24,24 @@ class DrydockSession(object):
|
||||||
|
|
||||||
:param string host: The Drydock server hostname or IP
|
:param string host: The Drydock server hostname or IP
|
||||||
:param int port: (optional) The service port appended if specified
|
:param int port: (optional) The service port appended if specified
|
||||||
:param string token: Auth token
|
:param function auth_gen: Callable that will generate a list of authentication
|
||||||
|
header names and values (2 part tuple)
|
||||||
:param string marker: (optional) external context marker
|
:param string marker: (optional) external context marker
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, host, port=None, scheme='http', token=None,
|
def __init__(self, host, port=None, scheme='http', auth_gen=None,
|
||||||
marker=None):
|
marker=None):
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
self.__session = requests.Session()
|
self.__session = requests.Session()
|
||||||
|
self.auth_gen = auth_gen
|
||||||
|
|
||||||
|
self.set_auth()
|
||||||
|
|
||||||
|
self.marker = marker
|
||||||
self.__session.headers.update({
|
self.__session.headers.update({
|
||||||
'X-Auth-Token': token,
|
|
||||||
'X-Context-Marker': marker
|
'X-Context-Marker': marker
|
||||||
})
|
})
|
||||||
|
|
||||||
self.host = host
|
self.host = host
|
||||||
self.scheme = scheme
|
self.scheme = scheme
|
||||||
|
|
||||||
|
@ -46,10 +53,14 @@ class DrydockSession(object):
|
||||||
# assume default port for scheme
|
# assume default port for scheme
|
||||||
self.base_url = "%s://%s/api/" % (self.scheme, self.host)
|
self.base_url = "%s://%s/api/" % (self.scheme, self.host)
|
||||||
|
|
||||||
self.token = token
|
def set_auth(self):
|
||||||
self.marker = marker
|
"""Set the session's auth header."""
|
||||||
|
if self.auth_gen:
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger.debug("Updating session authentication header.")
|
||||||
|
auth_header = self.auth_gen()
|
||||||
|
self.__session.headers.update(auth_header)
|
||||||
|
else:
|
||||||
|
self.logger.debug("Cannot set auth header, no generator defined.")
|
||||||
|
|
||||||
def get(self, endpoint, query=None):
|
def get(self, endpoint, query=None):
|
||||||
"""
|
"""
|
||||||
|
@ -59,8 +70,16 @@ class DrydockSession(object):
|
||||||
:param dict query: A dict of k, v pairs to add to the query string
|
:param dict query: A dict of k, v pairs to add to the query string
|
||||||
:return: A requests.Response object
|
:return: A requests.Response object
|
||||||
"""
|
"""
|
||||||
resp = self.__session.get(
|
auth_refresh = False
|
||||||
self.base_url + endpoint, params=query, timeout=10)
|
while True:
|
||||||
|
resp = self.__session.get(
|
||||||
|
self.base_url + endpoint, params=query, timeout=10)
|
||||||
|
|
||||||
|
if resp.status_code == 401 and not auth_refresh:
|
||||||
|
self.set_auth()
|
||||||
|
auth_refresh = True
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
@ -75,16 +94,23 @@ class DrydockSession(object):
|
||||||
:param data: Something json.dumps(s) can serialize. Result will be used as the request body
|
:param data: Something json.dumps(s) can serialize. Result will be used as the request body
|
||||||
:return: A requests.Response object
|
:return: A requests.Response object
|
||||||
"""
|
"""
|
||||||
|
auth_refresh = False
|
||||||
|
while True:
|
||||||
|
self.logger.debug("Sending POST with drydock_client session")
|
||||||
|
if body is not None:
|
||||||
|
self.logger.debug("Sending POST with explicit body: \n%s" % body)
|
||||||
|
resp = self.__session.post(
|
||||||
|
self.base_url + endpoint, params=query, data=body, timeout=10)
|
||||||
|
else:
|
||||||
|
self.logger.debug("Sending POST with JSON body: \n%s" % str(data))
|
||||||
|
resp = self.__session.post(
|
||||||
|
self.base_url + endpoint, params=query, json=data, timeout=10)
|
||||||
|
|
||||||
self.logger.debug("Sending POST with drydock_client session")
|
if resp.status_code == 401 and not auth_refresh:
|
||||||
if body is not None:
|
self.set_auth()
|
||||||
self.logger.debug("Sending POST with explicit body: \n%s" % body)
|
auth_refresh = True
|
||||||
resp = self.__session.post(
|
else:
|
||||||
self.base_url + endpoint, params=query, data=body, timeout=10)
|
break
|
||||||
else:
|
|
||||||
self.logger.debug("Sending POST with JSON body: \n%s" % str(data))
|
|
||||||
resp = self.__session.post(
|
|
||||||
self.base_url + endpoint, params=query, json=data, timeout=10)
|
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import pytest
|
import pytest
|
||||||
|
import mock
|
||||||
import responses
|
import responses
|
||||||
|
|
||||||
import drydock_provisioner.drydock_client.session as dc_session
|
import drydock_provisioner.drydock_client.session as dc_session
|
||||||
|
@ -40,38 +41,6 @@ def test_session_init_minimal_no_port():
|
||||||
assert dd_ses.base_url == "http://%s/api/" % (host)
|
assert dd_ses.base_url == "http://%s/api/" % (host)
|
||||||
|
|
||||||
|
|
||||||
def test_session_init_uuid_token():
|
|
||||||
host = 'foo.bar.baz'
|
|
||||||
token = '5f1e08b6-38ec-4a99-9d0f-00d29c4e325b'
|
|
||||||
|
|
||||||
dd_ses = dc_session.DrydockSession(host, token=token)
|
|
||||||
|
|
||||||
assert dd_ses.base_url == "http://%s/api/" % (host)
|
|
||||||
assert dd_ses.token == token
|
|
||||||
|
|
||||||
|
|
||||||
def test_session_init_fernet_token():
|
|
||||||
host = 'foo.bar.baz'
|
|
||||||
token = 'gAAAAABU7roWGiCuOvgFcckec-0ytpGnMZDBLG9hA7Hr9qfvdZDHjsak39YN98HXxoYLIqVm' \
|
|
||||||
'19Egku5YR3wyI7heVrOmPNEtmr-fIM1rtahudEdEAPM4HCiMrBmiA1Lw6SU8jc2rPLC7FK7n' \
|
|
||||||
'BCia_BGhG17NVHuQu0S7waA306jyKNhHwUnpsBQ'
|
|
||||||
|
|
||||||
dd_ses = dc_session.DrydockSession(host, token=token)
|
|
||||||
|
|
||||||
assert dd_ses.base_url == "http://%s/api/" % (host)
|
|
||||||
assert dd_ses.token == token
|
|
||||||
|
|
||||||
|
|
||||||
def test_session_init_marker():
|
|
||||||
host = 'foo.bar.baz'
|
|
||||||
marker = '5f1e08b6-38ec-4a99-9d0f-00d29c4e325b'
|
|
||||||
|
|
||||||
dd_ses = dc_session.DrydockSession(host, marker=marker)
|
|
||||||
|
|
||||||
assert dd_ses.base_url == "http://%s/api/" % (host)
|
|
||||||
assert dd_ses.marker == marker
|
|
||||||
|
|
||||||
|
|
||||||
@responses.activate
|
@responses.activate
|
||||||
def test_session_get():
|
def test_session_get():
|
||||||
responses.add(
|
responses.add(
|
||||||
|
@ -83,7 +52,10 @@ def test_session_get():
|
||||||
token = '5f1e08b6-38ec-4a99-9d0f-00d29c4e325b'
|
token = '5f1e08b6-38ec-4a99-9d0f-00d29c4e325b'
|
||||||
marker = '40c3eaf6-6a8a-11e7-a4bd-080027ef795a'
|
marker = '40c3eaf6-6a8a-11e7-a4bd-080027ef795a'
|
||||||
|
|
||||||
dd_ses = dc_session.DrydockSession(host, token=token, marker=marker)
|
def auth_gen():
|
||||||
|
return [('X-Auth-Token', token)]
|
||||||
|
|
||||||
|
dd_ses = dc_session.DrydockSession(host, auth_gen=auth_gen, marker=marker)
|
||||||
|
|
||||||
resp = dd_ses.get('v1.0/test')
|
resp = dd_ses.get('v1.0/test')
|
||||||
req = resp.request
|
req = resp.request
|
||||||
|
@ -92,6 +64,31 @@ def test_session_get():
|
||||||
assert req.headers.get('X-Context-Marker', None) == marker
|
assert req.headers.get('X-Context-Marker', None) == marker
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
@mock.patch.object(dc_session.KeystoneClient, 'get_token',
|
||||||
|
return_value='5f1e08b6-38ec-4a99-9d0f-00d29c4e325b')
|
||||||
|
def test_session_get_returns_401(*args):
|
||||||
|
responses.add(
|
||||||
|
responses.GET,
|
||||||
|
'http://foo.bar.baz/api/v1.0/test',
|
||||||
|
body='okay',
|
||||||
|
status=401)
|
||||||
|
host = 'foo.bar.baz'
|
||||||
|
token = '5f1e08b6-38ec-4a99-9d0f-00d29c4e325b'
|
||||||
|
marker = '40c3eaf6-6a8a-11e7-a4bd-080027ef795a'
|
||||||
|
|
||||||
|
def auth_gen():
|
||||||
|
return [('X-Auth-Token', dc_session.KeystoneClient.get_token())]
|
||||||
|
|
||||||
|
dd_ses = dc_session.DrydockSession(host, auth_gen=auth_gen, marker=marker)
|
||||||
|
|
||||||
|
resp = dd_ses.get('v1.0/test')
|
||||||
|
req = resp.request
|
||||||
|
|
||||||
|
assert req.headers.get('X-Auth-Token', None) == token
|
||||||
|
assert req.headers.get('X-Context-Marker', None) == marker
|
||||||
|
assert dc_session.KeystoneClient.get_token.call_count == 2
|
||||||
|
|
||||||
@responses.activate
|
@responses.activate
|
||||||
def test_client_task_get():
|
def test_client_task_get():
|
||||||
task = {
|
task = {
|
||||||
|
|
Loading…
Reference in New Issue