mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-07 18:14:10 +00:00
174 lines
5.1 KiB
Python
174 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Example of starting/stopping a server via the JupyterHub API
|
|
|
|
1. get user status
|
|
2. start server
|
|
3. wait for server to be ready via progress api
|
|
4. make a request to the server itself
|
|
5. stop server via API
|
|
6. wait for server to finish stopping
|
|
"""
|
|
import json
|
|
import logging
|
|
import pathlib
|
|
import time
|
|
|
|
import requests
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def get_token():
|
|
"""boilerplate: get token from share file.
|
|
|
|
Make sure to start jupyterhub in this directory first
|
|
"""
|
|
here = pathlib.Path(__file__).parent
|
|
token_file = here.joinpath("service-token")
|
|
log.info(f"Loading token from {token_file}")
|
|
with token_file.open("r") as f:
|
|
token = f.read().strip()
|
|
return token
|
|
|
|
|
|
def make_session(token):
|
|
"""Create a requests.Session with our service token in the Authorization header"""
|
|
session = requests.Session()
|
|
session.headers = {"Authorization": f"token {token}"}
|
|
return session
|
|
|
|
|
|
def event_stream(session, url):
|
|
"""Generator yielding events from a JSON event stream
|
|
|
|
For use with the server progress API
|
|
"""
|
|
r = session.get(url, stream=True)
|
|
r.raise_for_status()
|
|
for line in r.iter_lines():
|
|
line = line.decode('utf8', 'replace')
|
|
# event lines all start with `data:`
|
|
# all other lines should be ignored (they will be empty)
|
|
if line.startswith('data:'):
|
|
yield json.loads(line.split(':', 1)[1])
|
|
|
|
|
|
def start_server(session, hub_url, user, server_name=""):
|
|
"""Start a server for a jupyterhub user
|
|
|
|
Returns the full URL for accessing the server
|
|
"""
|
|
user_url = f"{hub_url}/hub/api/users/{user}"
|
|
log_name = f"{user}/{server_name}".rstrip("/")
|
|
|
|
# step 1: get user status
|
|
r = session.get(user_url)
|
|
r.raise_for_status()
|
|
user_model = r.json()
|
|
|
|
# if server is not 'active', request launch
|
|
if server_name not in user_model.get('servers', {}):
|
|
log.info(f"Starting server {log_name}")
|
|
r = session.post(f"{user_url}/servers/{server_name}")
|
|
r.raise_for_status()
|
|
if r.status_code == 201:
|
|
log.info(f"Server {log_name} is launched and ready")
|
|
elif r.status_code == 202:
|
|
log.info(f"Server {log_name} is launching...")
|
|
else:
|
|
log.warning(f"Unexpected status: {r.status_code}")
|
|
r = session.get(user_url)
|
|
r.raise_for_status()
|
|
user_model = r.json()
|
|
|
|
# report server status
|
|
server = user_model['servers'][server_name]
|
|
if server['pending']:
|
|
status = f"pending {server['pending']}"
|
|
elif server['ready']:
|
|
status = "ready"
|
|
else:
|
|
# shouldn't be possible!
|
|
raise ValueError(f"Unexpected server state: {server}")
|
|
|
|
log.info(f"Server {log_name} is {status}")
|
|
|
|
# wait for server to be ready using progress API
|
|
progress_url = user_model['servers'][server_name]['progress_url']
|
|
for event in event_stream(session, f"{hub_url}{progress_url}"):
|
|
log.info(f"Progress {event['progress']}%: {event['message']}")
|
|
if event.get("ready"):
|
|
server_url = event['url']
|
|
break
|
|
else:
|
|
# server never ready
|
|
raise ValueError(f"{log_name} never started!")
|
|
|
|
# at this point, we know the server is ready and waiting to receive requests
|
|
# return the full URL where the server can be accessed
|
|
return f"{hub_url}{server_url}"
|
|
|
|
|
|
def stop_server(session, hub_url, user, server_name=""):
|
|
"""Stop a server via the JupyterHub API
|
|
|
|
Returns when the server has finished stopping
|
|
"""
|
|
# step 1: get user status
|
|
user_url = f"{hub_url}/hub/api/users/{user}"
|
|
server_url = f"{user_url}/servers/{server_name}"
|
|
log_name = f"{user}/{server_name}".rstrip("/")
|
|
|
|
log.info(f"Stopping server {log_name}")
|
|
r = session.delete(server_url)
|
|
if r.status_code == 404:
|
|
log.info(f"Server {log_name} already stopped")
|
|
|
|
r.raise_for_status()
|
|
if r.status_code == 204:
|
|
log.info(f"Server {log_name} stopped")
|
|
return
|
|
|
|
# else: 202, stop requested, but not complete
|
|
# wait for stop to finish
|
|
log.info(f"Server {log_name} stopping...")
|
|
|
|
# wait for server to be done stopping
|
|
while True:
|
|
r = session.get(user_url)
|
|
r.raise_for_status()
|
|
user_model = r.json()
|
|
if server_name not in user_model.get("servers", {}):
|
|
log.info(f"Server {log_name} stopped")
|
|
return
|
|
server = user_model["servers"][server_name]
|
|
if not server['pending']:
|
|
raise ValueError(f"Waiting for {log_name}, but no longer pending.")
|
|
log.info(f"Server {log_name} pending: {server['pending']}")
|
|
# wait to poll again
|
|
time.sleep(1)
|
|
|
|
|
|
def main():
|
|
"""Start and stop one server
|
|
|
|
Uses test-user and hub from jupyterhub_config.py in this directory
|
|
"""
|
|
user = "test-user"
|
|
hub_url = "http://127.0.0.1:8000"
|
|
|
|
session = make_session(get_token())
|
|
server_url = start_server(session, hub_url, user)
|
|
r = session.get(f"{server_url}/api/status")
|
|
r.raise_for_status()
|
|
log.info(f"Server status: {r.text}")
|
|
|
|
stop_server(session, hub_url, user)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
logging.basicConfig(level=logging.INFO)
|
|
main()
|