diff --git a/jupyterhub_config.py b/jupyterhub_config.py index fde5034..2d3d1b7 100644 --- a/jupyterhub_config.py +++ b/jupyterhub_config.py @@ -1,22 +1,20 @@ import os +c = get_config() + c.JupyterHub.spawner_class = 'marathonspawner.MarathonSpawner' c.JupyterHub.ip = '0.0.0.0' c.JupyterHub.hub_ip = '0.0.0.0' - -# Don't try to cleanup servers on exit - since in general for k8s, we want -# the hub to be able to restart without losing user containers +c.JupyterHub.cmd = 'start-singleuser.sh' c.JupyterHub.cleanup_servers = False +c.MarathonSpawner.app_prefix = 'jupyter' c.MarathonSpawner.app_image = 'jupyterhub/singleuser' c.MarathonSpawner.app_prefix = 'jupyter' c.MarathonSpawner.marathon_host = 'http://leader.mesos:8080' c.MarathonSpawner.ports = [8000] c.MarathonSpawner.mem_limit = '2G' c.MarathonSpawner.cpu_limit = 1 -c.MarathonSpawner.hub_ip_connect = os.environ['HUB_IP_CONNECT'] -c.MarathonSpawner.hub_port_connect = os.environ['HUB_PORT_CONNECT'] - c.JupyterHub.authenticator_class = 'dummyauthenticator.DummyAuthenticator' diff --git a/marathonspawner/_version.py b/marathonspawner/_version.py index e1424ed..abeeedb 100644 --- a/marathonspawner/_version.py +++ b/marathonspawner/_version.py @@ -1 +1 @@ -__version__ = '0.3.1' +__version__ = '0.4.0' diff --git a/marathonspawner/marathonspawner.py b/marathonspawner/marathonspawner.py index 63b65a6..738f026 100644 --- a/marathonspawner/marathonspawner.py +++ b/marathonspawner/marathonspawner.py @@ -2,11 +2,12 @@ import socket from concurrent.futures import ThreadPoolExecutor from urllib.parse import urlparse, urlunparse +import warnings from textwrap import dedent from tornado import gen from tornado.concurrent import run_on_executor -from traitlets import Any, Integer, List, Unicode, default +from traitlets import Any, Integer, List, Unicode, default, observe from marathon import MarathonClient from marathon.models.app import MarathonApp, MarathonHealthCheck @@ -18,10 +19,12 @@ from .volumenaming import default_format_volume_name +import jupyterhub +_jupyterhub_xy = '%i.%i' % (jupyterhub.version_info[:2]) class MarathonSpawner(Spawner): - app_image = Unicode("jupyterhub/singleuser", config=True) + app_image = Unicode("jupyterhub/singleuser:%s" % _jupyterhub_xy, config=True) app_prefix = Unicode( "jupyter", @@ -76,11 +79,29 @@ class MarathonSpawner(Spawner): help="Public IP address of the hub" ).tag(config=True) + @observe('hub_ip_connect') + def _ip_connect_changed(self, change): + if jupyterhub.version_info >= (0, 8): + warnings.warn( + "MarathonSpawner.hub_ip_connect is no longer needed with JupyterHub 0.8." + " Use JupyterHub.hub_connect_ip instead.", + DeprecationWarning, + ) + hub_port_connect = Integer( -1, help="Public PORT of the hub" ).tag(config=True) + @observe('hub_port_connect') + def _port_connect_changed(self, change): + if jupyterhub.version_info >= (0, 8): + warnings.warn( + "MarathonSpawner.hub_port_connect is no longer needed with JupyterHub 0.8." + " Use JupyterHub.hub_connect_port instead.", + DeprecationWarning, + ) + format_volume_name = Any( help="""Any callable that accepts a string template and a Spawner instance as parameters in that order and returns a string. @@ -91,6 +112,16 @@ class MarathonSpawner(Spawner): def _get_default_format_volume_name(self): return default_format_volume_name + # fix default port to 8888, used in the container + @default('port') + def _port_default(self): + return 8888 + + # default to listening on all-interfaces in the container + @default('ip') + def _ip_default(self): + return '0.0.0.0' + _executor = None @property def executor(self): @@ -122,7 +153,7 @@ def get_health_checks(self): protocol='TCP', port_index=0, grace_period_seconds=300, - interval_seconds=60, + interval_seconds=30, timeout_seconds=20, max_consecutive_failures=0 )) @@ -201,25 +232,17 @@ def _public_hub_api_url(self): uri.fragment )) - def get_env(self): - env = super(MarathonSpawner, self).get_env() - env.update(dict( - # Jupyter Hub config - JPY_USER=self.user.name, - JPY_COOKIE_NAME=self.user.server.cookie_name, - JPY_BASE_URL=self.user.server.base_url, - JPY_HUB_PREFIX=self.hub.server.base_url, - )) - - if self.notebook_dir: - env['NOTEBOOK_DIR'] = self.notebook_dir - - if self.hub_ip_connect or self.hub_port_connect > 0: - hub_api_url = self._public_hub_api_url() - else: - hub_api_url = self.hub.api_url - env['JPY_HUB_API_URL'] = hub_api_url - return env + def get_args(self): + args = super().get_args() + if self.hub_ip_connect: + # JupyterHub 0.7 specifies --hub-api-url + # on the command-line, which is hard to update + for idx, arg in enumerate(list(args)): + if arg.startswith('--hub-api-url='): + args.pop(idx) + break + args.append('--hub-api-url=%s' % self._public_hub_api_url()) + return args @gen.coroutine def start(self): @@ -239,8 +262,10 @@ def start(self): else: mem_request = 1024.0 + cmd = self.cmd + self.get_args() app_request = MarathonApp( id=self.container_name, + cmd=' '.join(cmd), env=self.get_env(), cpus=self.cpu_limit, mem=mem_request,