diff --git a/jupyterhub/__init__.py b/jupyterhub/__init__.py index 16d186b8..4f1853b7 100644 --- a/jupyterhub/__init__.py +++ b/jupyterhub/__init__.py @@ -1 +1 @@ -from .version import version_info, __version__ +from ._version import version_info, __version__ diff --git a/jupyterhub/_version.py b/jupyterhub/_version.py new file mode 100644 index 00000000..f322236f --- /dev/null +++ b/jupyterhub/_version.py @@ -0,0 +1,40 @@ +"""JupyterHub version info""" + +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + +version_info = ( + 0, + 8, + 0, + 'dev', +) + +__version__ = '.'.join(map(str, version_info)) + + +def _check_version(hub_version, singleuser_version, log): + """Compare Hub and single-user server versions""" + if not hub_version: + log.warning("Hub has no version header, which means it is likely < 0.8. Expected %s", __version__) + return + + if not singleuser_version: + log.warning("Single-user server has no version header, which means it is likely < 0.8. Expected %s", __version__) + return + + # compare minor X.Y versions + if hub_version != singleuser_version: + from distutils.version import LooseVersion as V + hub_major_minor = V(hub_version).version[:2] + singleuser_major_minor = V(__version__).version[:2] + if singleuser_major_minor == hub_major_minor: + # patch-level mismatch or lower, log difference at debug-level + # because this should be fine + log_method = log.debug + 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, __version__, + ) diff --git a/jupyterhub/apihandlers/hub.py b/jupyterhub/apihandlers/hub.py index 77b7b758..59708848 100644 --- a/jupyterhub/apihandlers/hub.py +++ b/jupyterhub/apihandlers/hub.py @@ -11,7 +11,7 @@ from tornado.ioloop import IOLoop from ..utils import admin_only from .base import APIHandler -from ..version import __version__ +from .._version import __version__ class ShutdownAPIHandler(APIHandler): diff --git a/jupyterhub/singleuser.py b/jupyterhub/singleuser.py index 0aaea47e..d52c0a12 100755 --- a/jupyterhub/singleuser.py +++ b/jupyterhub/singleuser.py @@ -4,12 +4,16 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. +from distutils.version import LooseVersion as V import os +import re from textwrap import dedent from urllib.parse import urlparse from jinja2 import ChoiceLoader, FunctionLoader +from tornado.httpclient import AsyncHTTPClient +from tornado import gen from tornado import ioloop from tornado.web import HTTPError @@ -37,7 +41,7 @@ from notebook.auth.login import LoginHandler from notebook.auth.logout import LogoutHandler from notebook.base.handlers import IPythonHandler -from jupyterhub import __version__ +from ._version import __version__, _check_version from .log import log_request from .services.auth import HubOAuth, HubOAuthenticated, HubOAuthCallbackHandler from .utils import url_path_join @@ -337,7 +341,27 @@ class SingleUserNotebookApp(NotebookApp): path = list(_exclude_home(path)) return path + @gen.coroutine + def check_hub_version(self): + """Test a connection to my Hub + + - exit if I can't connect at all + - check version and warn on sufficient mismatch + """ + client = AsyncHTTPClient() + try: + resp = yield client.fetch(self.hub_api_url) + except Exception: + self.log.exception("Failed to connect to my Hub at %s. Is it running?", self.hub_api_url) + self.exit(1) + + hub_version = resp.headers.get('X-JupyterHub-Version') + _check_version(hub_version, __version__, self.log) + def start(self): + self.log.info("Starting jupyterhub-singleuser server version %s", __version__) + # start by hitting Hub to check version + ioloop.IOLoop.current().run_sync(self.check_hub_version) super(SingleUserNotebookApp, self).start() def init_hub_auth(self): diff --git a/jupyterhub/user.py b/jupyterhub/user.py index d00262e8..f23eaf33 100644 --- a/jupyterhub/user.py +++ b/jupyterhub/user.py @@ -12,6 +12,7 @@ from tornado.log import app_log from .utils import url_path_join, default_server_name from . import orm +from ._version import _check_version, __version__ from .objects import Server from traitlets import HasTraits, Any, Dict, observe, default from .spawner import LocalProcessSpawner @@ -328,7 +329,7 @@ class User(HasTraits): db.commit() self.waiting_for_response = True try: - yield server.wait_up(http=True, timeout=spawner.http_timeout) + resp = yield server.wait_up(http=True, timeout=spawner.http_timeout) except Exception as e: if isinstance(e, TimeoutError): self.log.warning( @@ -353,6 +354,9 @@ class User(HasTraits): ), exc_info=True) # raise original TimeoutError raise e + else: + server_version = resp.headers.get('X-JupyterHub-Version') + _check_version(__version__, server_version, self.log) finally: self.waiting_for_response = False self.spawn_pending = False diff --git a/jupyterhub/version.py b/jupyterhub/version.py deleted file mode 100644 index 06a6b133..00000000 --- a/jupyterhub/version.py +++ /dev/null @@ -1,13 +0,0 @@ -"""JupyterHub version info""" - -# Copyright (c) Jupyter Development Team. -# Distributed under the terms of the Modified BSD License. - -version_info = ( - 0, - 8, - 0, - 'dev', -) - -__version__ = '.'.join(map(str, version_info))