Skip to content

Commit 383e8a3

Browse files
authored
Merge pull request #58 from cmd-ntrf/remote_port
Select singleserver port number on remote host
2 parents 393d10c + 2e024e8 commit 383e8a3

File tree

7 files changed

+67
-12
lines changed

7 files changed

+67
-12
lines changed

batchspawner/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
from .batchspawner import *
2+
from . import api

batchspawner/api.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import json
2+
from tornado import web
3+
from jupyterhub.apihandlers import APIHandler, default_handlers
4+
5+
class BatchSpawnerAPIHandler(APIHandler):
6+
@web.authenticated
7+
def post(self):
8+
"""POST set user's spawner port number"""
9+
user = self.get_current_user()
10+
data = self.get_json_body()
11+
port = int(data.get('port', 0))
12+
user.spawner.current_port = port
13+
self.finish(json.dumps({"message": "BatchSpawner port configured"}))
14+
self.set_status(201)
15+
16+
default_handlers.append((r"/api/batchspawner", BatchSpawnerAPIHandler))

batchspawner/batchspawner.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from tornado.iostream import StreamClosedError
3030

3131
from jupyterhub.spawner import Spawner
32+
from jupyterhub.traitlets import Command
3233
from traitlets import (
3334
Integer, Unicode, Float, Dict, default
3435
)
@@ -73,6 +74,9 @@ class BatchSpawnerBase(Spawner):
7374
state_gethost
7475
"""
7576

77+
# override default since will need to set the listening port using the api
78+
cmd = Command(['batchspawner-singleuser'], allow_none=True).tag(config=True)
79+
7680
# override default since batch systems typically need longer
7781
start_timeout = Integer(300).tag(config=True)
7882

@@ -164,6 +168,9 @@ def _req_keepvars_default(self):
164168
# Will get the address of the server as reported by job manager
165169
current_ip = Unicode()
166170

171+
# Will get the port of the server as reported by singleserver
172+
current_port = Integer()
173+
167174
# Prepare substitution variables for templates using req_xyz traits
168175
def get_req_subvars(self):
169176
reqlist = [ t for t in self.trait_names() if t.startswith('req_') ]
@@ -342,14 +349,9 @@ def poll(self):
342349
@gen.coroutine
343350
def start(self):
344351
"""Start the process"""
345-
if self.user and self.user.server and self.user.server.port:
346-
self.port = self.user.server.port
347-
self.db.commit()
348-
elif (jupyterhub.version_info < (0,7) and not self.user.server.port) or (
349-
jupyterhub.version_info >= (0,7) and not self.port
350-
):
351-
self.port = random_port()
352-
self.db.commit()
352+
if jupyterhub.version_info >= (0,8) and self.server:
353+
self.server.port = self.port
354+
353355
job = yield self.submit_batch_script()
354356

355357
# We are called with a timeout, and if the timeout expires this function will
@@ -374,16 +376,19 @@ def start(self):
374376
yield gen.sleep(self.startup_poll_interval)
375377

376378
self.current_ip = self.state_gethost()
379+
while self.current_port == 0:
380+
yield gen.sleep(self.startup_poll_interval)
381+
377382
if jupyterhub.version_info < (0,7):
378383
# store on user for pre-jupyterhub-0.7:
379-
self.user.server.port = self.port
384+
self.user.server.port = self.current_port
380385
self.user.server.ip = self.current_ip
381386
self.db.commit()
382387
self.log.info("Notebook server job {0} started at {1}:{2}".format(
383-
self.job_id, self.current_ip, self.port)
388+
self.job_id, self.current_ip, self.current_port)
384389
)
385390

386-
return self.current_ip, self.port
391+
return self.current_ip, self.current_port
387392

388393
@gen.coroutine
389394
def stop(self, now=False):

batchspawner/singleuser.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from jupyterhub.singleuser import SingleUserNotebookApp
2+
from jupyterhub.utils import random_port, url_path_join
3+
from traitlets import default
4+
5+
class BatchSingleUserNotebookApp(SingleUserNotebookApp):
6+
@default('port')
7+
def _port(self):
8+
return random_port()
9+
10+
def start(self):
11+
# Send Notebook app's port number to remote Spawner
12+
self.hub_auth._api_request(method='POST',
13+
url=url_path_join(self.hub_api_url, 'batchspawner'),
14+
json={'port' : self.port})
15+
super().start()
16+
17+
def main(argv=None):
18+
return BatchSingleUserNotebookApp.launch_instance(argv)
19+
20+
if __name__ == "__main__":
21+
main()

batchspawner/tests/test_spawners.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010
from tornado import gen
1111

1212
try:
13-
from jupyterhub.objects import Hub
13+
from jupyterhub.objects import Hub, Server
1414
from jupyterhub.user import User
1515
except:
1616
pass
1717

1818
testhost = "userhost123"
1919
testjob = "12345"
20+
testport = 54321
2021

2122
class BatchDummy(BatchSpawnerRegexStates):
2223
exec_prefix = ''
@@ -59,8 +60,11 @@ def new_spawner(db, spawner_class=BatchDummy, **kwargs):
5960
else:
6061
hub = Hub()
6162
user = User(user, {})
63+
server = Server()
64+
kwargs.setdefault('server', server)
6265
kwargs.setdefault('hub', hub)
6366
kwargs.setdefault('user', user)
67+
kwargs.setdefault('current_port', testport)
6468
kwargs.setdefault('INTERRUPT_TIMEOUT', 1)
6569
kwargs.setdefault('TERM_TIMEOUT', 1)
6670
kwargs.setdefault('KILL_TIMEOUT', 1)

scripts/batchspawner-singleuser

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env python3
2+
3+
from batchspawner.singleuser import main
4+
5+
if __name__ == '__main__':
6+
main()

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import sys
1515

1616
from setuptools import setup
17+
from glob import glob
1718

1819
pjoin = os.path.join
1920
here = os.path.abspath(os.path.dirname(__file__))
@@ -28,6 +29,7 @@
2829

2930
setup_args = dict(
3031
name = 'batchspawner',
32+
scripts = glob(pjoin('scripts', '*')),
3133
packages = ['batchspawner'],
3234
version = version_ns['__version__'],
3335
description = """Batchspawner: A spawner for Jupyterhub to spawn notebooks using batch resource managers.""",

0 commit comments

Comments
 (0)