persist user_options

remember user_options from the previous run

this allows user options set via spawn form to be re-used when restarting e.g. a named server via the api
This commit is contained in:
Min RK
2019-02-28 14:27:47 +01:00
parent 25aa892f86
commit ddc852d658
4 changed files with 87 additions and 1 deletions

View File

@@ -0,0 +1,24 @@
"""persist user_options
Revision ID: 4dc2d5a8c53c
Revises: 896818069c98
Create Date: 2019-02-28 14:14:27.423927
"""
# revision identifiers, used by Alembic.
revision = '4dc2d5a8c53c'
down_revision = '896818069c98'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
from jupyterhub.orm import JSONDict
def upgrade():
op.add_column('spawners', sa.Column('user_options', JSONDict()))
def downgrade():
op.drop_column('spawners', sa.Column('user_options'))

View File

@@ -216,6 +216,7 @@ class Spawner(Base):
started = Column(DateTime)
last_activity = Column(DateTime, nullable=True)
user_options = Column(JSONDict)
# properties on the spawner wrapper
# some APIs get these low-level objects

View File

@@ -517,6 +517,58 @@ async def test_spawn(app):
assert app.users.count_active_users()['pending'] == 0
async def test_user_options(app, username):
db = app.db
name = username
user = add_user(db, app=app, name=name)
options = {'s': ['value'], 'i': 5}
before_servers = sorted(db.query(orm.Server), key=lambda s: s.url)
r = await api_request(
app, 'users', name, 'server', method='post', data=json.dumps(options)
)
assert r.status_code == 201
assert 'pid' in user.orm_spawners[''].state
app_user = app.users[name]
assert app_user.spawner is not None
spawner = app_user.spawner
assert spawner.user_options == options
assert spawner.orm_spawner.user_options == options
# stop the server
r = await api_request(app, 'users', name, 'server', method='delete')
# orm_spawner still exists and has a reference to the user_options
assert spawner.orm_spawner.user_options == options
# spawn again, no options specified
# should re-use options from last spawn
r = await api_request(app, 'users', name, 'server', method='post')
assert r.status_code == 201
assert 'pid' in user.orm_spawners[''].state
app_user = app.users[name]
assert app_user.spawner is not None
spawner = app_user.spawner
assert spawner.user_options == options
# stop the server
r = await api_request(app, 'users', name, 'server', method='delete')
# spawn again, new options specified
# should override options from last spawn
new_options = {'key': 'value'}
r = await api_request(
app, 'users', name, 'server', method='post', data=json.dumps(new_options)
)
assert r.status_code == 201
assert 'pid' in user.orm_spawners[''].state
app_user = app.users[name]
assert app_user.spawner is not None
spawner = app_user.spawner
assert spawner.user_options == new_options
# saved in db
assert spawner.orm_spawner.user_options == new_options
async def test_spawn_handler(app):
"""Test that the requesting Handler is passed to Spawner.handler"""
db = app.db

View File

@@ -479,8 +479,17 @@ class User:
# pass requesting handler to the spawner
# e.g. for processing GET params
spawner.handler = handler
# Passing user_options to the spawner
spawner.user_options = options or {}
if options is None:
# options unspecified, load from db which should have the previous value
options = spawner.orm_spawner.user_options or {}
else:
# options specified, save for use as future defaults
spawner.orm_spawner.user_options = options
db.commit()
spawner.user_options = options
# we are starting a new server, make sure it doesn't restore state
spawner.clear_state()