mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-16 06:22:59 +00:00
Add more docs for spawner base class
This commit is contained in:
@@ -351,29 +351,31 @@ class Spawner(LoggingConfigurable):
|
|||||||
self.load_state(self.user.state)
|
self.load_state(self.user.state)
|
||||||
|
|
||||||
def load_state(self, state):
|
def load_state(self, state):
|
||||||
"""load state from the database
|
"""
|
||||||
|
Restore state of spawner from database.
|
||||||
|
|
||||||
This is the extensible part of state.
|
Called for each user's spawner after the hub process restarts.
|
||||||
|
|
||||||
Override in a subclass if there is state to load.
|
`state` is a dict that'll contain the value returned by `get_state` of
|
||||||
Should call `super`.
|
the spawner, or {} if the spawner hasn't persisted any state yet.
|
||||||
|
|
||||||
See Also
|
Override in subclasses to restore any extra state that is needed to track
|
||||||
--------
|
the single-user server for that user. Subclasses should call super().
|
||||||
|
|
||||||
get_state, clear_state
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_state(self):
|
def get_state(self):
|
||||||
"""store the state necessary for load_state
|
"""
|
||||||
|
Save state of spawner into database.
|
||||||
|
|
||||||
A black box of extra state for custom spawners.
|
A black box of extra state for custom spawners. The returned value of this is
|
||||||
Subclasses should call `super`.
|
passed to `load_state`.
|
||||||
|
|
||||||
|
Subclasses should call `super().get_state()`, augment the state returned from
|
||||||
|
there, and return that state.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
|
|
||||||
state: dict
|
state: dict
|
||||||
a JSONable dict of state
|
a JSONable dict of state
|
||||||
"""
|
"""
|
||||||
@@ -381,20 +383,25 @@ class Spawner(LoggingConfigurable):
|
|||||||
return state
|
return state
|
||||||
|
|
||||||
def clear_state(self):
|
def clear_state(self):
|
||||||
"""clear any state that should be cleared when the process stops
|
"""
|
||||||
|
Clear any state that should be cleared when the single-user server stops.
|
||||||
|
|
||||||
State that should be preserved across server instances should not be cleared.
|
State that should be preserved across single-user server instances should not be cleared.
|
||||||
|
|
||||||
Subclasses should call super, to ensure that state is properly cleared.
|
Subclasses should call super, to ensure that state is properly cleared.
|
||||||
"""
|
"""
|
||||||
self.api_token = ''
|
self.api_token = ''
|
||||||
|
|
||||||
def get_env(self):
|
def get_env(self):
|
||||||
"""Return the environment dict to use for the Spawner.
|
"""
|
||||||
|
Return the environment dict to use for the Spawner.
|
||||||
|
|
||||||
This applies things like `env_keep`, anything defined in `Spawner.environment`,
|
This applies things like `env_keep`, anything defined in `Spawner.environment`,
|
||||||
and adds the API token to the env.
|
and adds the API token to the env.
|
||||||
|
|
||||||
|
When overriding in subclasses, subclasses must call `super().get_env()`, extend the
|
||||||
|
returned dict and return it.
|
||||||
|
|
||||||
Use this to access the env in Spawner.start to allow extension in subclasses.
|
Use this to access the env in Spawner.start to allow extension in subclasses.
|
||||||
"""
|
"""
|
||||||
env = {}
|
env = {}
|
||||||
@@ -434,7 +441,8 @@ class Spawner(LoggingConfigurable):
|
|||||||
return env
|
return env
|
||||||
|
|
||||||
def template_namespace(self):
|
def template_namespace(self):
|
||||||
"""Return the template namespace for format-string formatting.
|
"""
|
||||||
|
Return the template namespace for format-string formatting.
|
||||||
|
|
||||||
Currently used on default_url and notebook_dir.
|
Currently used on default_url and notebook_dir.
|
||||||
|
|
||||||
@@ -457,7 +465,8 @@ class Spawner(LoggingConfigurable):
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
def format_string(self, s):
|
def format_string(self, s):
|
||||||
"""Render a Python format string
|
"""
|
||||||
|
Render a Python format string.
|
||||||
|
|
||||||
Uses :meth:`Spawner.template_namespace` to populate format namespace.
|
Uses :meth:`Spawner.template_namespace` to populate format namespace.
|
||||||
|
|
||||||
@@ -472,7 +481,11 @@ class Spawner(LoggingConfigurable):
|
|||||||
return s.format(**self.template_namespace())
|
return s.format(**self.template_namespace())
|
||||||
|
|
||||||
def get_args(self):
|
def get_args(self):
|
||||||
"""Return the arguments to be passed after self.cmd"""
|
"""
|
||||||
|
Return the arguments to be passed after self.cmd.
|
||||||
|
|
||||||
|
Doesn't expect shell expansion to happen.
|
||||||
|
"""
|
||||||
args = [
|
args = [
|
||||||
'--user="%s"' % self.user.name,
|
'--user="%s"' % self.user.name,
|
||||||
'--cookie-name="%s"' % self.user.server.cookie_name,
|
'--cookie-name="%s"' % self.user.server.cookie_name,
|
||||||
@@ -506,11 +519,11 @@ class Spawner(LoggingConfigurable):
|
|||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Start the single-user server
|
"""
|
||||||
|
Start the single-user server.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
(ip, port): the ip, port where the Hub can connect to the server.
|
||||||
(ip, port): the ip, port where the Hub can connect to the server.
|
|
||||||
|
|
||||||
.. versionchanged:: 0.7
|
.. versionchanged:: 0.7
|
||||||
Return ip, port instead of setting on self.user.server directly.
|
Return ip, port instead of setting on self.user.server directly.
|
||||||
@@ -519,17 +532,24 @@ class Spawner(LoggingConfigurable):
|
|||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def stop(self, now=False):
|
def stop(self, now=False):
|
||||||
"""Stop the single-user process"""
|
"""
|
||||||
|
Stop the single-user server.
|
||||||
|
|
||||||
|
If `now` is set to `False`, do not wait for the server to stop. Otherwise, wait for
|
||||||
|
the server to stop before returning.
|
||||||
|
|
||||||
|
Must be a Torando coroutine.
|
||||||
|
"""
|
||||||
raise NotImplementedError("Override in subclass. Must be a Tornado gen.coroutine.")
|
raise NotImplementedError("Override in subclass. Must be a Tornado gen.coroutine.")
|
||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def poll(self):
|
def poll(self):
|
||||||
"""Check if the single-user process is running
|
"""
|
||||||
|
Check if the single-user process is running
|
||||||
|
|
||||||
returns:
|
returns:
|
||||||
|
None, if single-user process is running.
|
||||||
None, if single-user process is running.
|
Exit status (0 if unknown), if it is not running.
|
||||||
Exit status (0 if unknown), if it is not running.
|
|
||||||
|
|
||||||
State transitions, behavior, and return response:
|
State transitions, behavior, and return response:
|
||||||
|
|
||||||
@@ -551,9 +571,8 @@ class Spawner(LoggingConfigurable):
|
|||||||
raise NotImplementedError("Override in subclass. Must be a Tornado gen.coroutine.")
|
raise NotImplementedError("Override in subclass. Must be a Tornado gen.coroutine.")
|
||||||
|
|
||||||
def add_poll_callback(self, callback, *args, **kwargs):
|
def add_poll_callback(self, callback, *args, **kwargs):
|
||||||
"""add a callback to fire when the subprocess stops
|
"""
|
||||||
|
Add a callback to fire when the single-user server stops.
|
||||||
as noticed by periodic poll_and_notify()
|
|
||||||
"""
|
"""
|
||||||
if args or kwargs:
|
if args or kwargs:
|
||||||
cb = callback
|
cb = callback
|
||||||
@@ -561,17 +580,18 @@ class Spawner(LoggingConfigurable):
|
|||||||
self._callbacks.append(callback)
|
self._callbacks.append(callback)
|
||||||
|
|
||||||
def stop_polling(self):
|
def stop_polling(self):
|
||||||
"""stop the periodic poll"""
|
"""
|
||||||
|
Stop polling for single-user server's running state.
|
||||||
|
"""
|
||||||
if self._poll_callback:
|
if self._poll_callback:
|
||||||
self._poll_callback.stop()
|
self._poll_callback.stop()
|
||||||
self._poll_callback = None
|
self._poll_callback = None
|
||||||
|
|
||||||
def start_polling(self):
|
def start_polling(self):
|
||||||
"""Start polling periodically
|
"""
|
||||||
|
Start polling periodically for single-user server's running state.
|
||||||
callbacks registered via `add_poll_callback` will fire
|
|
||||||
if/when the process stops.
|
|
||||||
|
|
||||||
|
Callbacks registered via `add_poll_callback` will fire if/when the server stops.
|
||||||
Explicit termination via the stop method will not trigger the callbacks.
|
Explicit termination via the stop method will not trigger the callbacks.
|
||||||
"""
|
"""
|
||||||
if self.poll_interval <= 0:
|
if self.poll_interval <= 0:
|
||||||
@@ -590,8 +610,8 @@ class Spawner(LoggingConfigurable):
|
|||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def poll_and_notify(self):
|
def poll_and_notify(self):
|
||||||
"""Used as a callback to periodically poll the process,
|
"""
|
||||||
and notify any watchers
|
Used as a callback to periodically poll the process and notify any watchers
|
||||||
"""
|
"""
|
||||||
status = yield self.poll()
|
status = yield self.poll()
|
||||||
if status is None:
|
if status is None:
|
||||||
@@ -610,7 +630,9 @@ class Spawner(LoggingConfigurable):
|
|||||||
death_interval = Float(0.1)
|
death_interval = Float(0.1)
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def wait_for_death(self, timeout=10):
|
def wait_for_death(self, timeout=10):
|
||||||
"""wait for the process to die, up to timeout seconds"""
|
"""
|
||||||
|
Wait for the single-user server to die, up to timeout seconds
|
||||||
|
"""
|
||||||
for i in range(int(timeout / self.death_interval)):
|
for i in range(int(timeout / self.death_interval)):
|
||||||
status = yield self.poll()
|
status = yield self.poll()
|
||||||
if status is not None:
|
if status is not None:
|
||||||
@@ -618,8 +640,13 @@ class Spawner(LoggingConfigurable):
|
|||||||
else:
|
else:
|
||||||
yield gen.sleep(self.death_interval)
|
yield gen.sleep(self.death_interval)
|
||||||
|
|
||||||
|
|
||||||
def _try_setcwd(path):
|
def _try_setcwd(path):
|
||||||
"""Try to set CWD, walking up and ultimately falling back to a temp dir"""
|
"""
|
||||||
|
Try to set CWD to path, walking up until a valid directory is found.
|
||||||
|
|
||||||
|
If no valid directory is found, a temp directory is created and cwd is set to that.
|
||||||
|
"""
|
||||||
while path != '/':
|
while path != '/':
|
||||||
try:
|
try:
|
||||||
os.chdir(path)
|
os.chdir(path)
|
||||||
@@ -635,7 +662,12 @@ def _try_setcwd(path):
|
|||||||
|
|
||||||
|
|
||||||
def set_user_setuid(username):
|
def set_user_setuid(username):
|
||||||
"""return a preexec_fn for setting the user (via setuid) of a spawned process"""
|
"""
|
||||||
|
Return a preexec_fn for spawning a single-user server as a particular user.
|
||||||
|
|
||||||
|
Returned preexec_fn will set uid/gid, and attempt to chdir to the target user's
|
||||||
|
homedirectory.
|
||||||
|
"""
|
||||||
user = pwd.getpwnam(username)
|
user = pwd.getpwnam(username)
|
||||||
uid = user.pw_uid
|
uid = user.pw_uid
|
||||||
gid = user.pw_gid
|
gid = user.pw_gid
|
||||||
@@ -643,7 +675,11 @@ def set_user_setuid(username):
|
|||||||
gids = [ g.gr_gid for g in grp.getgrall() if username in g.gr_mem ]
|
gids = [ g.gr_gid for g in grp.getgrall() if username in g.gr_mem ]
|
||||||
|
|
||||||
def preexec():
|
def preexec():
|
||||||
# set the user and group
|
"""
|
||||||
|
Set uid/gid of current process. Executed after fork but before exec by python.
|
||||||
|
|
||||||
|
Also try to chdir to the user's home directory.
|
||||||
|
"""
|
||||||
os.setgid(gid)
|
os.setgid(gid)
|
||||||
try:
|
try:
|
||||||
os.setgroups(gids)
|
os.setgroups(gids)
|
||||||
|
Reference in New Issue
Block a user