From 447edd081aacff095c3332bde63e5fd630dff96d Mon Sep 17 00:00:00 2001 From: Min RK Date: Sat, 26 Aug 2017 22:38:50 -0400 Subject: [PATCH] Provide more detailed error message in case of version mismatch this is the most likely cause of redirect loops when using docker, so record the spawner version and check it when a redirect is detected. In the event of a redirect and mismatch, fail with a message explaining the version mismatch and how to fix it. --- jupyterhub/_version.py | 7 +++++-- jupyterhub/handlers/base.py | 25 +++++++++++++++++++++---- jupyterhub/spawner.py | 1 + jupyterhub/user.py | 3 +++ 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/jupyterhub/_version.py b/jupyterhub/_version.py index 422cb6a1..a5f506e6 100644 --- a/jupyterhub/_version.py +++ b/jupyterhub/_version.py @@ -35,8 +35,11 @@ def _check_version(hub_version, singleuser_version, log): else: # log warning-level for more significant mismatch, such as 0.8 vs 0.9, etc. log_method = log.warning - log_method("jupyterhub version %s != jupyterhub-singleuser version %s", - hub_version, singleuser_version, + extra = " This could cause failure to authenticate and result in redirect loops!" + log_method( + "jupyterhub version %s != jupyterhub-singleuser version %s." + extra, + hub_version, + singleuser_version, ) else: log.debug("jupyterhub and jupyterhub-singleuser both on version %s" % hub_version) diff --git a/jupyterhub/handlers/base.py b/jupyterhub/handlers/base.py index f11ccb16..f46befc4 100644 --- a/jupyterhub/handlers/base.py +++ b/jupyterhub/handlers/base.py @@ -20,7 +20,7 @@ from .. import __version__ from .. import orm from ..objects import Server from ..spawner import LocalProcessSpawner -from ..utils import default_server_name, url_path_join, exponential_backoff +from ..utils import default_server_name, url_path_join # pattern for the authentication token header auth_header_pat = re.compile(r'^(?:token|bearer)\s+([^\s]+)$', flags=re.IGNORECASE) @@ -675,7 +675,10 @@ class UserSpawnHandler(BaseHandler): return # spawn has supposedly finished, check on the status - status = yield spawner.poll() + if spawner.ready: + status = yield spawner.poll() + else: + status = 0 if status is not None: if spawner.options_form: self.redirect(url_concat(url_path_join(self.hub.base_url, 'spawn'), @@ -693,9 +696,23 @@ class UserSpawnHandler(BaseHandler): self.log.warning("Invalid redirects argument %r", self.get_argument('redirects')) redirects = 0 - if redirects >= self.settings.get('user_redirect_limit', 5): + # check redirect limit to prevent browser-enforced limits. + # In case of version mismatch, raise on only two redirects. + if redirects >= self.settings.get( + 'user_redirect_limit', 4 + ) or (redirects >= 2 and spawner._jupyterhub_version != __version__): # We stop if we've been redirected too many times. - raise web.HTTPError(500, "Redirect loop detected.") + msg = "Redirect loop detected." + if spawner._jupyterhub_version != __version__: + msg += ( + " Notebook has jupyterhub version {singleuser}, but the Hub expects {hub}." + " Try installing jupyterhub=={hub} in the user environment" + " if you continue to have problems." + ).format( + singleuser=spawner._jupyterhub_version or 'unknown (likely < 0.8)', + hub=__version__, + ) + raise web.HTTPError(500, msg) # set login cookie anew self.set_login_cookie(current_user) diff --git a/jupyterhub/spawner.py b/jupyterhub/spawner.py index df717f53..4d70dd9b 100644 --- a/jupyterhub/spawner.py +++ b/jupyterhub/spawner.py @@ -53,6 +53,7 @@ class Spawner(LoggingConfigurable): _stop_pending = False _proxy_pending = False _waiting_for_response = False + _jupyterhub_version = None @property def _log_name(self): diff --git a/jupyterhub/user.py b/jupyterhub/user.py index 701cbbc1..0e465aa2 100644 --- a/jupyterhub/user.py +++ b/jupyterhub/user.py @@ -472,6 +472,9 @@ class User(HasTraits): else: server_version = resp.headers.get('X-JupyterHub-Version') _check_version(__version__, server_version, self.log) + # record the Spawner version for better error messages + # if it doesn't work + spawner._jupyterhub_version = server_version finally: spawner._waiting_for_response = False spawner._start_pending = False