#!/usr/bin/env python # coding: utf-8 # # Checking for running servers in JupyterHub # # As asked [on the Jupyter forum](https://discourse.jupyter.org/t/jupyterhub-rest-api-detect-if-user-already-has-server-running/7805): # # > Using the Rest api is there a way to check on whether or not a user has a non named notebook server running # In[1]: import requests # an admin service token added via config # or a token owned by the user api_token = "fb605a38c7ed4056b2280ee9bdb25258" s = requests.Session() s.headers["Authorization"] = f"token {api_token}" username = "test-1" hub_host = "http://localhost:8765" hub_api = f"{hub_host}/hub/api" user_url = f"{hub_api}/users/{username}" server_url = f"{hub_api}/users/{username}/server" # The [user model](https://jupyterhub.readthedocs.io/en/latest/_static/rest-api/index.html#/definitions/User) has a `servers` dict of 'active' servers. # 'active' means it is not stopped, i.e. it can be 'ready' or pending a transition from stopped to running or running to stopped. # # The first state is: If a server name is not present in the dict, it is definitely not running. # # Here we get the user model for a user who has no running servers. # The `servers` dict is empty. # In[2]: r = s.get(user_url) r.json() # ## Start a server # # If we start the server and check again immediately, # the server will be in a "pending spawn" state, # which is in transition from stopped to running. # # Note the following state: # # - the `servers` dict has a [server record](https://jupyterhub.readthedocs.io/en/latest/_static/rest-api/index.html#/definitions/Server) with key `''`. The empty string is the default server name, # i.e. the server at `/user/{name}`. # - The server has a field `'pending': 'spawn'` indicating that it is pending a spawn action # - The server has `'ready': False` indicating that it is not ready # In[3]: r = s.post(server_url) r.raise_for_status() r = s.get(user_url) r.json() # ## Wait for the server to finish starting # # We can wait for the server to be ready by following the progress event stream. # When this finishes, the server will be ready. # In[4]: import json from pprint import pprint progress_url = hub_host + r.json()["servers"][""]["progress_url"] for line in s.get(progress_url, stream=True).iter_lines(): line = line.decode("utf8") if line.startswith("data:"): rest = line.split(":", 1)[1] event = json.loads(rest) pprint(event) # Now that the server is running and ready, we can check the user model again. # # # In[5]: r = s.get(user_url) r.json() # ## Stopping the server # # We can do the same process to stop the server # In[6]: s.delete(server_url) r = s.get(user_url) r.json() # To summarize: # # - A [user](https://jupyterhub.readthedocs.io/en/latest/_static/rest-api/index.html#/definitions/User)'s servers dict contains all "active" servers, keyed by server name # - `""` is the key for the default server (no name) # - Each [server dict](https://jupyterhub.readthedocs.io/en/latest/_static/rest-api/index.html#/definitions/Server) has # - a `ready` boolean field that is True if the server is running and ready to handle requests, False otherwise # - a `pending` field that is None if no transitions are pending, or a string describing which transition is pending (i.e. 'spawn' or 'stop'). # # What you do with `pending` will depend on why you are asking. # Most API requests on a server will raise 400 if you try to act on a server that's pending some transition, so you may need to wait if you are asking because you want to do something like start a server if it's not already running. # # If a server is ready *or* `pending == 'spawn'`, it's safe to redirect users to the server's URL # because JupyterHub will send them to the spawn-pending page and ultimately redirect when it's ready. # # If you have a reason to wait for a server to finish a pending spawn via the API, the progress event stream API works. # # A server pending 'stop' can't be visited and can't be restarted until the stop event is complete. # # To retrieve the current status of the user's default server or any named server: # In[7]: username = "test-1" servername = "" # empty for default, or pick any name user_model = s.get(f"{hub_api}/users/{username}").json() server = user_model["servers"].get(servername, {}) if server: pending = server["pending"] ready = server["ready"] # server.url is a *path*, does not include https://host unless user-subdomains are used url = server["url"] else: # no server field, not running, nothing pending pending = None ready = False url = None if ready: print(f"{username}/{servername} running and ready at {url}") elif pending: print(f"{username}/{servername} pending {pending}") else: print(f"{username}/{servername} not running")