mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-13 04:53:01 +00:00
add JupyterHub.allow_multiple_servers
This commit is contained in:
@@ -35,6 +35,9 @@ if not os.path.exists(ssl_dir):
|
|||||||
os.makedirs(ssl_dir)
|
os.makedirs(ssl_dir)
|
||||||
|
|
||||||
|
|
||||||
|
# Allows multiple single-server per user
|
||||||
|
c.JupyterHub.allow_multiple_servers = False
|
||||||
|
|
||||||
# https on :443
|
# https on :443
|
||||||
c.JupyterHub.port = 443
|
c.JupyterHub.port = 443
|
||||||
c.JupyterHub.ssl_key = pjoin(ssl_dir, 'ssl.key')
|
c.JupyterHub.ssl_key = pjoin(ssl_dir, 'ssl.key')
|
||||||
|
@@ -160,12 +160,52 @@ class UserAPIHandler(APIHandler):
|
|||||||
self.write(json.dumps(self.user_model(user)))
|
self.write(json.dumps(self.user_model(user)))
|
||||||
|
|
||||||
|
|
||||||
class UserCreateServerAPIHandler(APIHandler):
|
class UserServerAPIHandler(APIHandler):
|
||||||
|
"""Create and delete single-user servers
|
||||||
|
|
||||||
|
This handler should be used when c.JupyterHub.allow_multiple_servers = False
|
||||||
|
"""
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
@admin_or_self
|
@admin_or_self
|
||||||
def post(self, name):
|
def post(self, name):
|
||||||
|
user = self.find_user(name)
|
||||||
|
if user.running:
|
||||||
|
# include notify, so that a server that died is noticed immediately
|
||||||
|
state = yield user.spawner.poll_and_notify()
|
||||||
|
if state is None:
|
||||||
|
raise web.HTTPError(400, "%s's server is already running" % name)
|
||||||
|
|
||||||
|
options = self.get_json_body()
|
||||||
|
yield self.spawn_single_user(user, options=options)
|
||||||
|
status = 202 if user.spawn_pending else 201
|
||||||
|
self.set_status(status)
|
||||||
|
|
||||||
|
@gen.coroutine
|
||||||
|
@admin_or_self
|
||||||
|
def delete(self, name):
|
||||||
|
user = self.find_user(name)
|
||||||
|
if user.stop_pending:
|
||||||
|
self.set_status(202)
|
||||||
|
return
|
||||||
|
if not user.running:
|
||||||
|
raise web.HTTPError(400, "%s's server is not running" % name)
|
||||||
|
# include notify, so that a server that died is noticed immediately
|
||||||
|
status = yield user.spawner.poll_and_notify()
|
||||||
|
if status is not None:
|
||||||
|
raise web.HTTPError(400, "%s's server is not running" % name)
|
||||||
|
yield self.stop_single_user(user)
|
||||||
|
status = 202 if user.stop_pending else 204
|
||||||
|
self.set_status(status)
|
||||||
|
|
||||||
|
|
||||||
|
class UserCreateMultiServerAPIHandler(APIHandler):
|
||||||
|
"""Create multi single-user server
|
||||||
|
|
||||||
|
This handler should be used when c.JupyterHub.allow_multiple_servers = True
|
||||||
|
"""
|
||||||
|
@gen.coroutine
|
||||||
|
@admin_or_self
|
||||||
|
def post(self, name):
|
||||||
user = self.find_user(name)
|
user = self.find_user(name)
|
||||||
if user.running:
|
if user.running:
|
||||||
# include notify, so that a server that died is noticed immediately
|
# include notify, so that a server that died is noticed immediately
|
||||||
@@ -179,8 +219,13 @@ class UserCreateServerAPIHandler(APIHandler):
|
|||||||
self.set_status(status)
|
self.set_status(status)
|
||||||
|
|
||||||
|
|
||||||
class UserDeleteServerAPIHandler(APIHandler):
|
class UserDeleteMultiServerAPIHandler(APIHandler):
|
||||||
|
"""Delete multi single-user server
|
||||||
|
|
||||||
|
Expect a server_name inside the url /user/:user/servers/:server_name
|
||||||
|
|
||||||
|
This handler should be used when c.JupyterHub.allow_multiple_servers = True
|
||||||
|
"""
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
@admin_or_self
|
@admin_or_self
|
||||||
def delete(self, name, server_name):
|
def delete(self, name, server_name):
|
||||||
@@ -227,7 +272,8 @@ class UserAdminAccessAPIHandler(APIHandler):
|
|||||||
default_handlers = [
|
default_handlers = [
|
||||||
(r"/api/users", UserListAPIHandler),
|
(r"/api/users", UserListAPIHandler),
|
||||||
(r"/api/users/([^/]+)", UserAPIHandler),
|
(r"/api/users/([^/]+)", UserAPIHandler),
|
||||||
(r"/api/users/([^/]+)/servers", UserCreateServerAPIHandler),
|
(r"/api/users/([^/]+)/server", UserServerAPIHandler),
|
||||||
(r"/api/users/([^/]+)/servers/([^/]+)", UserDeleteServerAPIHandler),
|
(r"/api/users/([^/]+)/servers", UserCreateMultiServerAPIHandler),
|
||||||
|
(r"/api/users/([^/]+)/servers/([^/]+)", UserDeleteMultiServerAPIHandler),
|
||||||
(r"/api/users/([^/]+)/admin-access", UserAdminAccessAPIHandler),
|
(r"/api/users/([^/]+)/admin-access", UserAdminAccessAPIHandler),
|
||||||
]
|
]
|
||||||
|
@@ -502,6 +502,10 @@ class JupyterHub(Application):
|
|||||||
def _authenticator_default(self):
|
def _authenticator_default(self):
|
||||||
return self.authenticator_class(parent=self, db=self.db)
|
return self.authenticator_class(parent=self, db=self.db)
|
||||||
|
|
||||||
|
allow_multiple_servers = Bool(False,
|
||||||
|
help="Allow multiple single-server per user"
|
||||||
|
).tag(config=True)
|
||||||
|
|
||||||
# class for spawning single-user servers
|
# class for spawning single-user servers
|
||||||
spawner_class = Type(LocalProcessSpawner, Spawner,
|
spawner_class = Type(LocalProcessSpawner, Spawner,
|
||||||
help="""The class to use for spawning single-user servers.
|
help="""The class to use for spawning single-user servers.
|
||||||
@@ -1322,6 +1326,7 @@ class JupyterHub(Application):
|
|||||||
subdomain_host=self.subdomain_host,
|
subdomain_host=self.subdomain_host,
|
||||||
domain=self.domain,
|
domain=self.domain,
|
||||||
statsd=self.statsd,
|
statsd=self.statsd,
|
||||||
|
allow_multiple_servers=self.allow_multiple_servers,
|
||||||
)
|
)
|
||||||
# allow configured settings to have priority
|
# allow configured settings to have priority
|
||||||
settings.update(self.tornado_settings)
|
settings.update(self.tornado_settings)
|
||||||
|
@@ -65,7 +65,7 @@ class Server(Base):
|
|||||||
__tablename__ = 'servers'
|
__tablename__ = 'servers'
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
|
|
||||||
name = Column(Unicode(32)) # must be unique between user's servers
|
name = Column(Unicode(32), default='') # must be unique between user's servers
|
||||||
proto = Column(Unicode(15), default='http')
|
proto = Column(Unicode(15), default='http')
|
||||||
ip = Column(Unicode(255), default='') # could also be a DNS name
|
ip = Column(Unicode(255), default='') # could also be a DNS name
|
||||||
port = Column(Integer, default=random_port)
|
port = Column(Integer, default=random_port)
|
||||||
|
@@ -121,6 +121,8 @@ class User(HasTraits):
|
|||||||
|
|
||||||
hub = self.db.query(orm.Hub).first()
|
hub = self.db.query(orm.Hub).first()
|
||||||
|
|
||||||
|
self.allow_multiple_servers = self.settings.get('allow_multiple_servers', False)
|
||||||
|
|
||||||
self.cookie_name = '%s-%s' % (hub.server.cookie_name, quote(self.name, safe=''))
|
self.cookie_name = '%s-%s' % (hub.server.cookie_name, quote(self.name, safe=''))
|
||||||
self.base_url = url_path_join(
|
self.base_url = url_path_join(
|
||||||
self.settings.get('base_url', '/'), 'user', self.escaped_name)
|
self.settings.get('base_url', '/'), 'user', self.escaped_name)
|
||||||
@@ -203,18 +205,27 @@ class User(HasTraits):
|
|||||||
def spawn(self, options=None):
|
def spawn(self, options=None):
|
||||||
"""Start the user's spawner
|
"""Start the user's spawner
|
||||||
|
|
||||||
Because there could be more then one server per user
|
depending from the value of JupyterHub.allow_multiple_servers
|
||||||
each server has to have a unique name between the servers of a given user
|
|
||||||
|
|
||||||
base_url is built using user's base url and adding /server/{name}
|
if False:
|
||||||
where name is the server uuid urlsafed
|
JupyterHub expects only one single-server per user
|
||||||
|
url of the server will be /user/:name
|
||||||
|
|
||||||
|
if True:
|
||||||
|
JupyterHub expects more than one single-server per user
|
||||||
|
url of the server will be /user/:name/:server_name
|
||||||
"""
|
"""
|
||||||
db = self.db
|
db = self.db
|
||||||
|
|
||||||
if options is not None and 'server_name' in options:
|
if self.allow_multiple_servers:
|
||||||
server_name = options['server_name']
|
if options is not None and 'server_name' in options:
|
||||||
|
server_name = options['server_name']
|
||||||
|
else:
|
||||||
|
server_name = default_server_name(self)
|
||||||
else:
|
else:
|
||||||
server_name = default_server_name(self)
|
server_name = ''
|
||||||
|
|
||||||
|
|
||||||
server = orm.Server(
|
server = orm.Server(
|
||||||
name = server_name,
|
name = server_name,
|
||||||
cookie_name=self.cookie_name,
|
cookie_name=self.cookie_name,
|
||||||
|
Reference in New Issue
Block a user