mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-10 11:33:01 +00:00
Merge master into rbac
This commit is contained in:
@@ -1,22 +1,20 @@
|
||||
"""Tests for the REST API."""
|
||||
import asyncio
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import uuid
|
||||
from concurrent.futures import Future
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from unittest import mock
|
||||
from urllib.parse import quote
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from async_generator import async_generator
|
||||
from async_generator import yield_
|
||||
from pytest import mark
|
||||
from tornado import gen
|
||||
|
||||
import jupyterhub
|
||||
from .. import orm
|
||||
from ..objects import Server
|
||||
from ..utils import url_path_join as ujoin
|
||||
from ..utils import utcnow
|
||||
from .mocking import mock_role
|
||||
@@ -28,7 +26,6 @@ from .utils import async_requests
|
||||
from .utils import auth_header
|
||||
from .utils import find_user
|
||||
|
||||
|
||||
# --------------------
|
||||
# Authentication tests
|
||||
# --------------------
|
||||
@@ -183,6 +180,71 @@ async def test_get_users(app):
|
||||
assert r.status_code == 403
|
||||
|
||||
|
||||
@mark.user
|
||||
@mark.parametrize(
|
||||
"state",
|
||||
("inactive", "active", "ready", "invalid"),
|
||||
)
|
||||
async def test_get_users_state_filter(app, state):
|
||||
db = app.db
|
||||
|
||||
# has_one_active: one active, one inactive, zero ready
|
||||
has_one_active = add_user(db, app=app, name='has_one_active')
|
||||
# has_two_active: two active, ready servers
|
||||
has_two_active = add_user(db, app=app, name='has_two_active')
|
||||
# has_two_inactive: two spawners, neither active
|
||||
has_two_inactive = add_user(db, app=app, name='has_two_inactive')
|
||||
# has_zero: no Spawners registered at all
|
||||
has_zero = add_user(db, app=app, name='has_zero')
|
||||
|
||||
test_usernames = set(
|
||||
("has_one_active", "has_two_active", "has_two_inactive", "has_zero")
|
||||
)
|
||||
|
||||
user_states = {
|
||||
"inactive": ["has_two_inactive", "has_zero"],
|
||||
"ready": ["has_two_active"],
|
||||
"active": ["has_one_active", "has_two_active"],
|
||||
"invalid": [],
|
||||
}
|
||||
expected = user_states[state]
|
||||
|
||||
def add_spawner(user, name='', active=True, ready=True):
|
||||
"""Add a spawner in a requested state
|
||||
|
||||
If active, should turn up in an active query
|
||||
If active and ready, should turn up in a ready query
|
||||
If not active, should turn up in an inactive query
|
||||
"""
|
||||
spawner = user.spawners[name]
|
||||
db.commit()
|
||||
if active:
|
||||
orm_server = orm.Server()
|
||||
db.add(orm_server)
|
||||
db.commit()
|
||||
spawner.server = Server(orm_server=orm_server)
|
||||
db.commit()
|
||||
if not ready:
|
||||
spawner._spawn_pending = True
|
||||
return spawner
|
||||
|
||||
for name in ("", "secondary"):
|
||||
add_spawner(has_two_active, name, active=True)
|
||||
add_spawner(has_two_inactive, name, active=False)
|
||||
|
||||
add_spawner(has_one_active, active=True, ready=False)
|
||||
add_spawner(has_one_active, "inactive", active=False)
|
||||
|
||||
r = await api_request(app, 'users?state={}'.format(state))
|
||||
if state == "invalid":
|
||||
assert r.status_code == 400
|
||||
return
|
||||
assert r.status_code == 200
|
||||
|
||||
usernames = sorted(u["name"] for u in r.json() if u["name"] in test_usernames)
|
||||
assert usernames == expected
|
||||
|
||||
|
||||
@mark.user
|
||||
async def test_get_self(app):
|
||||
db = app.db
|
||||
@@ -206,14 +268,22 @@ async def test_get_self(app):
|
||||
)
|
||||
db.add(oauth_token)
|
||||
db.commit()
|
||||
r = await api_request(app, 'user', headers={'Authorization': 'token ' + token},)
|
||||
r = await api_request(
|
||||
app,
|
||||
'user',
|
||||
headers={'Authorization': 'token ' + token},
|
||||
)
|
||||
r.raise_for_status()
|
||||
model = r.json()
|
||||
assert model['name'] == u.name
|
||||
|
||||
# invalid auth gets 403
|
||||
with mock_role(app, 'user'):
|
||||
r = await api_request(app, 'user', headers={'Authorization': 'token notvalid'},)
|
||||
r = await api_request(
|
||||
app,
|
||||
'user',
|
||||
headers={'Authorization': 'token notvalid'},
|
||||
)
|
||||
assert r.status_code == 403
|
||||
|
||||
|
||||
@@ -234,7 +304,11 @@ async def test_get_user(app):
|
||||
name = 'user'
|
||||
_ = await api_request(app, 'users', name, headers=auth_header(app.db, name))
|
||||
with mock_role(app, role=name, name=name):
|
||||
r = await api_request(app, 'users', name,)
|
||||
r = await api_request(
|
||||
app,
|
||||
'users',
|
||||
name,
|
||||
)
|
||||
assert r.status_code == 200
|
||||
|
||||
user = normalize_user(r.json())
|
||||
@@ -616,7 +690,7 @@ async def test_slow_spawn(app, no_patience, slow_spawn):
|
||||
|
||||
async def wait_spawn():
|
||||
while not app_user.running:
|
||||
await gen.sleep(0.1)
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
await wait_spawn()
|
||||
assert not app_user.spawner._spawn_pending
|
||||
@@ -625,7 +699,7 @@ async def test_slow_spawn(app, no_patience, slow_spawn):
|
||||
|
||||
async def wait_stop():
|
||||
while app_user.spawner._stop_pending:
|
||||
await gen.sleep(0.1)
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
r = await api_request(app, 'users', name, 'server', method='delete')
|
||||
r.raise_for_status()
|
||||
@@ -659,7 +733,7 @@ async def test_never_spawn(app, no_patience, never_spawn):
|
||||
assert app.users.count_active_users()['pending'] == 1
|
||||
|
||||
while app_user.spawner.pending:
|
||||
await gen.sleep(0.1)
|
||||
await asyncio.sleep(0.1)
|
||||
print(app_user.spawner.pending)
|
||||
|
||||
assert not app_user.spawner._spawn_pending
|
||||
@@ -685,7 +759,7 @@ async def test_slow_bad_spawn(app, no_patience, slow_bad_spawn):
|
||||
r = await api_request(app, 'users', name, 'server', method='post')
|
||||
r.raise_for_status()
|
||||
while user.spawner.pending:
|
||||
await gen.sleep(0.1)
|
||||
await asyncio.sleep(0.1)
|
||||
# spawn failed
|
||||
assert not user.running
|
||||
assert app.users.count_active_users()['pending'] == 0
|
||||
@@ -821,32 +895,12 @@ async def test_progress_bad_slow(request, app, no_patience, slow_bad_spawn):
|
||||
}
|
||||
|
||||
|
||||
@async_generator
|
||||
async def progress_forever():
|
||||
"""progress function that yields messages forever"""
|
||||
for i in range(1, 10):
|
||||
await yield_({'progress': i, 'message': 'Stage %s' % i})
|
||||
yield {'progress': i, 'message': 'Stage %s' % i}
|
||||
# wait a long time before the next event
|
||||
await gen.sleep(10)
|
||||
|
||||
|
||||
if sys.version_info >= (3, 6):
|
||||
# additional progress_forever defined as native
|
||||
# async generator
|
||||
# to test for issues with async_generator wrappers
|
||||
exec(
|
||||
"""
|
||||
async def progress_forever_native():
|
||||
for i in range(1, 10):
|
||||
yield {
|
||||
'progress': i,
|
||||
'message': 'Stage %s' % i,
|
||||
}
|
||||
# wait a long time before the next event
|
||||
await gen.sleep(10)
|
||||
""",
|
||||
globals(),
|
||||
)
|
||||
await asyncio.sleep(10)
|
||||
|
||||
|
||||
async def test_spawn_progress_cutoff(request, app, no_patience, slow_spawn):
|
||||
@@ -857,11 +911,7 @@ async def test_spawn_progress_cutoff(request, app, no_patience, slow_spawn):
|
||||
db = app.db
|
||||
name = 'geddy'
|
||||
app_user = add_user(db, app=app, name=name)
|
||||
if sys.version_info >= (3, 6):
|
||||
# Python >= 3.6, try native async generator
|
||||
app_user.spawner.progress = globals()['progress_forever_native']
|
||||
else:
|
||||
app_user.spawner.progress = progress_forever
|
||||
app_user.spawner.progress = progress_forever
|
||||
app_user.spawner.delay = 1
|
||||
|
||||
r = await api_request(app, 'users', name, 'server', method='post')
|
||||
@@ -888,8 +938,8 @@ async def test_spawn_limit(app, no_patience, slow_spawn, request):
|
||||
# start two pending spawns
|
||||
names = ['ykka', 'hjarka']
|
||||
users = [add_user(db, app=app, name=name) for name in names]
|
||||
users[0].spawner._start_future = Future()
|
||||
users[1].spawner._start_future = Future()
|
||||
users[0].spawner._start_future = asyncio.Future()
|
||||
users[1].spawner._start_future = asyncio.Future()
|
||||
for name in names:
|
||||
await api_request(app, 'users', name, 'server', method='post')
|
||||
assert app.users.count_active_users()['pending'] == 2
|
||||
@@ -897,7 +947,7 @@ async def test_spawn_limit(app, no_patience, slow_spawn, request):
|
||||
# ykka and hjarka's spawns are both pending. Essun should fail with 429
|
||||
name = 'essun'
|
||||
user = add_user(db, app=app, name=name)
|
||||
user.spawner._start_future = Future()
|
||||
user.spawner._start_future = asyncio.Future()
|
||||
r = await api_request(app, 'users', name, 'server', method='post')
|
||||
assert r.status_code == 429
|
||||
|
||||
@@ -905,7 +955,7 @@ async def test_spawn_limit(app, no_patience, slow_spawn, request):
|
||||
users[0].spawner._start_future.set_result(None)
|
||||
# wait for ykka to finish
|
||||
while not users[0].running:
|
||||
await gen.sleep(0.1)
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
assert app.users.count_active_users()['pending'] == 1
|
||||
r = await api_request(app, 'users', name, 'server', method='post')
|
||||
@@ -916,7 +966,7 @@ async def test_spawn_limit(app, no_patience, slow_spawn, request):
|
||||
for user in users[1:]:
|
||||
user.spawner._start_future.set_result(None)
|
||||
while not all(u.running for u in users):
|
||||
await gen.sleep(0.1)
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
# everybody's running, pending count should be back to 0
|
||||
assert app.users.count_active_users()['pending'] == 0
|
||||
@@ -925,7 +975,7 @@ async def test_spawn_limit(app, no_patience, slow_spawn, request):
|
||||
r = await api_request(app, 'users', u.name, 'server', method='delete')
|
||||
r.raise_for_status()
|
||||
while any(u.spawner.active for u in users):
|
||||
await gen.sleep(0.1)
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
|
||||
@mark.slow
|
||||
@@ -1003,7 +1053,7 @@ async def test_start_stop_race(app, no_patience, slow_spawn):
|
||||
r = await api_request(app, 'users', user.name, 'server', method='delete')
|
||||
assert r.status_code == 400
|
||||
while not spawner.ready:
|
||||
await gen.sleep(0.1)
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
spawner.delay = 3
|
||||
# stop the spawner
|
||||
@@ -1011,7 +1061,7 @@ async def test_start_stop_race(app, no_patience, slow_spawn):
|
||||
assert r.status_code == 202
|
||||
assert spawner.pending == 'stop'
|
||||
# make sure we get past deleting from the proxy
|
||||
await gen.sleep(1)
|
||||
await asyncio.sleep(1)
|
||||
# additional stops while stopping shouldn't trigger a new stop
|
||||
with mock.patch.object(spawner, 'stop') as m:
|
||||
r = await api_request(app, 'users', user.name, 'server', method='delete')
|
||||
@@ -1023,7 +1073,7 @@ async def test_start_stop_race(app, no_patience, slow_spawn):
|
||||
assert r.status_code == 400
|
||||
|
||||
while spawner.active:
|
||||
await gen.sleep(0.1)
|
||||
await asyncio.sleep(0.1)
|
||||
# start after stop is okay
|
||||
r = await api_request(app, 'users', user.name, 'server', method='post')
|
||||
assert r.status_code == 202
|
||||
|
Reference in New Issue
Block a user