Merge master into rbac

This commit is contained in:
Min RK
2020-12-02 11:28:53 +01:00
49 changed files with 679 additions and 328 deletions

View File

@@ -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