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.
This commit is contained in:
Min RK
2017-08-26 22:38:50 -04:00
parent e1531ec277
commit 447edd081a
4 changed files with 30 additions and 6 deletions

View File

@@ -35,8 +35,11 @@ def _check_version(hub_version, singleuser_version, log):
else: else:
# log warning-level for more significant mismatch, such as 0.8 vs 0.9, etc. # log warning-level for more significant mismatch, such as 0.8 vs 0.9, etc.
log_method = log.warning log_method = log.warning
log_method("jupyterhub version %s != jupyterhub-singleuser version %s", extra = " This could cause failure to authenticate and result in redirect loops!"
hub_version, singleuser_version, log_method(
"jupyterhub version %s != jupyterhub-singleuser version %s." + extra,
hub_version,
singleuser_version,
) )
else: else:
log.debug("jupyterhub and jupyterhub-singleuser both on version %s" % hub_version) log.debug("jupyterhub and jupyterhub-singleuser both on version %s" % hub_version)

View File

@@ -20,7 +20,7 @@ from .. import __version__
from .. import orm from .. import orm
from ..objects import Server from ..objects import Server
from ..spawner import LocalProcessSpawner 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 # pattern for the authentication token header
auth_header_pat = re.compile(r'^(?:token|bearer)\s+([^\s]+)$', flags=re.IGNORECASE) auth_header_pat = re.compile(r'^(?:token|bearer)\s+([^\s]+)$', flags=re.IGNORECASE)
@@ -675,7 +675,10 @@ class UserSpawnHandler(BaseHandler):
return return
# spawn has supposedly finished, check on the status # 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 status is not None:
if spawner.options_form: if spawner.options_form:
self.redirect(url_concat(url_path_join(self.hub.base_url, 'spawn'), 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')) self.log.warning("Invalid redirects argument %r", self.get_argument('redirects'))
redirects = 0 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. # 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 # set login cookie anew
self.set_login_cookie(current_user) self.set_login_cookie(current_user)

View File

@@ -53,6 +53,7 @@ class Spawner(LoggingConfigurable):
_stop_pending = False _stop_pending = False
_proxy_pending = False _proxy_pending = False
_waiting_for_response = False _waiting_for_response = False
_jupyterhub_version = None
@property @property
def _log_name(self): def _log_name(self):

View File

@@ -472,6 +472,9 @@ class User(HasTraits):
else: else:
server_version = resp.headers.get('X-JupyterHub-Version') server_version = resp.headers.get('X-JupyterHub-Version')
_check_version(__version__, server_version, self.log) _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: finally:
spawner._waiting_for_response = False spawner._waiting_for_response = False
spawner._start_pending = False spawner._start_pending = False