Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1237)

Unified Diff: appengine/swarming/swarming_bot/bot_code/remote_client.py

Issue 2024313003: Send authorization headers when calling Swarming backend. (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-py@master
Patch Set: rebase Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: appengine/swarming/swarming_bot/bot_code/remote_client.py
diff --git a/appengine/swarming/swarming_bot/bot_code/remote_client.py b/appengine/swarming/swarming_bot/bot_code/remote_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..d39e76aaff15e74fb9cb42cfafb43989f493e4d7
--- /dev/null
+++ b/appengine/swarming/swarming_bot/bot_code/remote_client.py
@@ -0,0 +1,122 @@
+# Copyright 2016 The LUCI Authors. All rights reserved.
+# Use of this source code is governed under the Apache License, Version 2.0
+# that can be found in the LICENSE file.
+
+import logging
+import threading
+import time
+import traceback
+
+from utils import net
+
+
+# RemoteClient will attempt to refresh the authentication headers once they are
+# this close to the expiration.
+AUTH_HEADERS_EXPIRATION_SEC = 6*60
+
+
+# How long to wait for a response from the server. Must not be greater than
+# AUTH_HEADERS_EXPIRATION_SEC, since otherwise there's a chance auth headers
+# will expire while we wait for connection.
+NET_CONNECTION_TIMEOUT_SEC = 5*60
+
+
+class InitializationError(Exception):
+ """Raised by RemoteClient.initialize on fatal errors."""
+ def __init__(self, last_error):
+ super(InitializationError, self).__init__('Failed to grab auth headers')
+ self.last_error = last_error
+
+
+class RemoteClient(object):
+ """RemoteClient knows how to make authenticated calls to the backend.
+
+ It also holds in-memory cache of authentication headers and periodically
+ refreshes them (by calling supplied callback, that usually is implemented in
+ terms of bot_config.get_authentication_headers() function).
+
+ If the callback is None, skips authentication (this is used during initial
+ stages of the bot bootstrap).
+ """
+
+ def __init__(self, server, auth_headers_callback):
+ self._server = server
+ self._auth_headers_callback = auth_headers_callback
+ self._lock = threading.Lock()
+ self._headers = None
+ self._exp_ts = None
+ self._disabled = not auth_headers_callback
+
+ def initialize(self, quit_bit):
+ """Grabs initial auth headers, retrying on errors a bunch of times.
+
+ Raises InitializationError if all attempts fail. Aborts attempts and returns
+ if quit_bit is signaled.
+ """
+ attempts = 30
+ while not quit_bit.is_set():
+ try:
+ self._get_headers_or_throw()
+ return
+ except Exception as e:
+ last_error = '%s\n%s' % (e, traceback.format_exc()[-2048:])
+ logging.exception('Failed to grab initial auth headers')
+ attempts -= 1
+ if not attempts:
+ raise InitializationError(last_error)
+ time.sleep(2)
+
+ @property
+ def uses_auth(self):
+ """Returns True if get_authentication_headers() returns some headers.
+
+ If bot_config.get_authentication_headers() is not implement it will return
+ False.
+ """
+ return bool(self.get_authentication_headers())
+
+ def get_authentication_headers(self):
+ """Returns a dict with the headers, refreshing them if necessary.
+
+ Will always return a dict (perhaps empty if no auth headers are provided by
+ the callback or it has failed).
+ """
+ try:
+ return self._get_headers_or_throw()
+ except Exception:
M-A Ruel 2016/06/03 20:00:49 I wish the kind of exceptions trapped was scoped.
Vadim Sh. 2016/06/03 23:37:55 bot_config.py callback can through whatever it wan
+ logging.exception('Failed to refresh auth headers, using cached ones')
+ return self._headers or {}
+
+ def _get_headers_or_throw(self):
+ if self._disabled:
+ return {}
+ with self._lock:
+ if (self._exp_ts is None or
+ self._exp_ts - time.time() < AUTH_HEADERS_EXPIRATION_SEC):
+ self._headers, self._exp_ts = self._auth_headers_callback()
+ if self._exp_ts is None:
+ logging.info('Not using auth headers')
+ self._disabled = True
+ self._headers = {}
+ else:
+ logging.info(
+ 'Refreshed auth headers, they expire in %d sec',
+ self._exp_ts - time.time())
+ return self._headers or {}
+
+ def url_read_json(self, url_path, data=None):
+ """Does POST (if data is not None) or GET request to a JSON endpoint."""
+ return net.url_read_json(
+ self._server + url_path,
+ data=data,
+ headers=self.get_authentication_headers(),
+ timeout=NET_CONNECTION_TIMEOUT_SEC,
+ follow_redirects=False)
+
+ def url_retrieve(self, filepath, url_path):
+ """Fetches the file from the given URL path on the server."""
+ return net.url_retrieve(
+ filepath,
+ self._server + url_path,
+ headers=self.get_authentication_headers(),
+ timeout=NET_CONNECTION_TIMEOUT_SEC)

Powered by Google App Engine
This is Rietveld 408576698