diff --git a/docs/source/spawners.md b/docs/source/spawners.md index 5462dae3..7c435af6 100644 --- a/docs/source/spawners.md +++ b/docs/source/spawners.md @@ -1,17 +1,32 @@ # Writing a custom Spawner -Each single-user server is started by a [Spawner][]. +[Spawner][] starts each single-user notebook server. The Spawner represents an abstract interface to a process, and a custom Spawner needs to be able to take three actions: -1. start the process -2. poll whether the process is still running -3. stop the process +- start the process +- poll whether the process is still running +- stop the process -See a list of custom Spawners [on the wiki](https://github.com/jupyter/jupyterhub/wiki/Spawners). +## Examples +Custom Spawners for JupyterHub can be found on the [JupyterHub wiki](https://github.com/jupyter/jupyterhub/wiki/Spawners). Some examples include: +- [DockerSpawner](https://github.com/jupyter/dockerspawner) for spawning user servers in Docker containers + * dockerspawner.DockerSpawner for spawning identical Docker containers for + each users + * dockerspawner.SystemUserSpawner for spawning Docker containers with an + environment and home directory for each users +- [SudoSpawner](https://github.com/jupyter/sudospawner) enables JupyterHub to + run without being root, by spawning an intermediate process via `sudo` +- [BatchSpawner](https://github.com/mbmilligan/batchspawner) for spawning remote + servers using batch systems +- [RemoteSpawner](https://github.com/zonca/remotespawner) to spawn notebooks + and a remote server and tunnel the port via SSH +- [SwarmSpawner](https://github.com/compmodels/jupyterhub/blob/master/swarmspawner.py) + for spawning containers using Docker Swarm +## Spawner control methods -## Spawner.start +### Spawner.start `Spawner.start` should start the single-user server for a single user. Information about the user can be retrieved from `self.user`, @@ -20,8 +35,9 @@ an object encapsulating the user's name, authentication, and server info. When `Spawner.start` returns, it should have stored the IP and port of the single-user server in `self.user.server`. -**NOTE:** when writing coroutines, *never* `yield` in between a db change and a commit. -Most `Spawner.start`s should have something looking like: +**NOTE:** When writing coroutines, *never* `yield` in between a database change and a commit. + +Most `Spawner.start` functions will look similar to this example: ```python def start(self): @@ -36,33 +52,30 @@ not just requested. JupyterHub can handle `Spawner.start` being very slow (such as PBS-style batch queues, or instantiating whole AWS instances) via relaxing the `Spawner.start_timeout` config value. - -## Spawner.poll +### Spawner.poll `Spawner.poll` should check if the spawner is still running. It should return `None` if it is still running, and an integer exit status, otherwise. -For the local process case, this uses `os.kill(PID, 0)` -to check if the process is still around. +For the local process case, `Spawner.poll` uses `os.kill(PID, 0)` +to check if the local process is still running. -## Spawner.stop - -`Spawner.stop` should stop the process. It must be a tornado coroutine, -and should return when the process has finished exiting. +### Spawner.stop +`Spawner.stop` should stop the process. It must be a tornado coroutine, which should return when the process has finished exiting. ## Spawner state -JupyterHub should be able to stop and restart without having to teardown -single-user servers. This means that a Spawner may need to persist -some information that it can be restored. -A dictionary of JSON-able state can be used to store this information. +JupyterHub should be able to stop and restart without tearing down +single-user notebook servers. To do this task, a Spawner may need to persist +some information that can be restored later. +A JSON-able dictionary of state can be used to store persisted information. -Unlike start/stop/poll, the state methods must not be coroutines. +Unlike start, stop, and poll methods, the state methods must not be coroutines. -In the single-process case, this is only the process ID of the server: +For the single-process case, the Spawner state is only the process ID of the server: ```python def get_state(self): @@ -94,11 +107,11 @@ or docker-based deployments where users can select from a list of base images. This feature is enabled by setting `Spawner.options_form`, which is an HTML form snippet inserted unmodified into the spawn form. -If the `Spawner.options_form` is defined, when a user would start their server, they will be directed to a form page, like this: +If the `Spawner.options_form` is defined, when a user tries to start their server, they will be directed to a form page, like this: ![spawn-form](images/spawn-form.png) -If `Spawner.options_form` is undefined, the users server is spawned directly, and no spawn page is rendered. +If `Spawner.options_form` is undefined, the user's server is spawned directly, and no spawn page is rendered. See [this example](https://github.com/jupyter/jupyterhub/blob/master/examples/spawn-form/jupyterhub_config.py) for a form that allows custom CLI args for the local spawner. @@ -115,9 +128,9 @@ Options from this form will always be a dictionary of lists of strings, e.g.: } ``` -When formdata arrives, it is passed through `Spawner.options_from_form(formdata)`, +When `formdata` arrives, it is passed through `Spawner.options_from_form(formdata)`, which is a method to turn the form data into the correct structure. -This method must return a dictionary, and is meant to interpret the lists-of-strings into the correct types, e.g. for the above form it would look like: +This method must return a dictionary, and is meant to interpret the lists-of-strings into the correct types. For example, the `options_from_form` for the above form would look like: ```python def options_from_form(self, formdata): @@ -140,7 +153,7 @@ which would return: } ``` -When `Spawner.spawn` is called, this dict is accessible as `self.user_options`. +When `Spawner.spawn` is called, this dictionary is accessible as `self.user_options`.