mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-12 20:43:02 +00:00
177 lines
5.4 KiB
Python
177 lines
5.4 KiB
Python
"""Test a proxy being started before the Hub"""
|
|
|
|
import json
|
|
import os
|
|
from queue import Queue
|
|
from subprocess import Popen
|
|
from urllib.parse import urlparse, unquote
|
|
|
|
import pytest
|
|
|
|
from .. import orm
|
|
from .mocking import MockHub
|
|
from .test_api import api_request
|
|
from ..utils import wait_for_http_server, url_path_join as ujoin
|
|
|
|
|
|
def test_external_proxy(request, io_loop):
|
|
|
|
auth_token = 'secret!'
|
|
proxy_ip = '127.0.0.1'
|
|
proxy_port = 54321
|
|
|
|
app = MockHub.instance(
|
|
proxy_api_ip=proxy_ip,
|
|
proxy_api_port=proxy_port,
|
|
proxy_auth_token=auth_token,
|
|
)
|
|
|
|
def fin():
|
|
MockHub.clear_instance()
|
|
app.stop()
|
|
|
|
request.addfinalizer(fin)
|
|
|
|
# configures and starts proxy process
|
|
env = os.environ.copy()
|
|
env['CONFIGPROXY_AUTH_TOKEN'] = auth_token
|
|
cmd = app.proxy_cmd + [
|
|
'--ip', app.ip,
|
|
'--port', str(app.port),
|
|
'--api-ip', proxy_ip,
|
|
'--api-port', str(proxy_port),
|
|
'--default-target', 'http://%s:%i' % (app.hub_ip, app.hub_port),
|
|
]
|
|
if app.subdomain_host:
|
|
cmd.append('--host-routing')
|
|
proxy = Popen(cmd, env=env)
|
|
|
|
|
|
def _cleanup_proxy():
|
|
if proxy.poll() is None:
|
|
proxy.terminate()
|
|
request.addfinalizer(_cleanup_proxy)
|
|
|
|
def wait_for_proxy():
|
|
io_loop.run_sync(lambda: wait_for_http_server('http://%s:%i' % (proxy_ip, proxy_port)))
|
|
wait_for_proxy()
|
|
|
|
app.start([])
|
|
assert app.proxy_process is None
|
|
|
|
# test if api service has a root route '/'
|
|
routes = io_loop.run_sync(app.proxy.get_routes)
|
|
assert list(routes.keys()) == ['/']
|
|
|
|
# add user to the db and server
|
|
name = 'river'
|
|
r = api_request(app, 'users', name, method='post')
|
|
r.raise_for_status()
|
|
r = api_request(app, 'users', name, 'server', method='post')
|
|
r.raise_for_status()
|
|
|
|
routes = io_loop.run_sync(app.proxy.get_routes)
|
|
# sets the desired path result
|
|
user_path = unquote(ujoin(app.base_url, 'user/river'))
|
|
if app.subdomain_host:
|
|
domain = urlparse(app.subdomain_host).hostname
|
|
user_path = '/%s.%s' % (name, domain) + user_path
|
|
assert sorted(routes.keys()) == ['/', user_path]
|
|
|
|
# teardown the proxy and start a new one in the same place
|
|
proxy.terminate()
|
|
proxy = Popen(cmd, env=env)
|
|
wait_for_proxy()
|
|
|
|
routes = io_loop.run_sync(app.proxy.get_routes)
|
|
assert list(routes.keys()) == ['/']
|
|
|
|
# poke the server to update the proxy
|
|
r = api_request(app, 'proxy', method='post')
|
|
r.raise_for_status()
|
|
|
|
# check that the routes are correct
|
|
routes = io_loop.run_sync(app.proxy.get_routes)
|
|
assert sorted(routes.keys()) == ['/', user_path]
|
|
|
|
# teardown the proxy, and start a new one with different auth and port
|
|
proxy.terminate()
|
|
new_auth_token = 'different!'
|
|
env['CONFIGPROXY_AUTH_TOKEN'] = new_auth_token
|
|
proxy_port = 55432
|
|
cmd = app.proxy_cmd + [
|
|
'--ip', app.ip,
|
|
'--port', str(app.port),
|
|
'--api-ip', app.proxy_api_ip,
|
|
'--api-port', str(proxy_port),
|
|
'--default-target', 'http://%s:%i' % (app.hub_ip, app.hub_port),
|
|
]
|
|
if app.subdomain_host:
|
|
cmd.append('--host-routing')
|
|
proxy = Popen(cmd, env=env)
|
|
wait_for_proxy()
|
|
|
|
# tell the hub where the new proxy is
|
|
r = api_request(app, 'proxy', method='patch', data=json.dumps({
|
|
'port': proxy_port,
|
|
'protocol': 'http',
|
|
'ip': app.ip,
|
|
'auth_token': new_auth_token,
|
|
}))
|
|
r.raise_for_status()
|
|
assert app.proxy.api_server.port == proxy_port
|
|
|
|
# get updated auth token from main thread
|
|
def get_app_proxy_token():
|
|
q = Queue()
|
|
app.io_loop.add_callback(lambda: q.put(app.proxy.auth_token))
|
|
return q.get(timeout=2)
|
|
|
|
assert get_app_proxy_token() == new_auth_token
|
|
app.proxy.auth_token = new_auth_token
|
|
|
|
# check that the routes are correct
|
|
routes = io_loop.run_sync(app.proxy.get_routes)
|
|
assert sorted(routes.keys()) == ['/', user_path]
|
|
|
|
|
|
@pytest.mark.parametrize("username, endpoints", [
|
|
('zoe', ['users/zoe', 'users/zoe/server']),
|
|
('50fia', ['users/50fia', 'users/50fia/server']),
|
|
('秀樹', ['users/秀樹', 'users/秀樹/server']),
|
|
])
|
|
def test_check_routes(app, io_loop, username, endpoints):
|
|
proxy = app.proxy
|
|
|
|
for endpoint in endpoints:
|
|
r = api_request(app, endpoint, method='post')
|
|
r.raise_for_status()
|
|
|
|
test_user = orm.User.find(app.db, username)
|
|
assert test_user is not None
|
|
|
|
# check a valid route exists for user
|
|
test_user = app.users[username]
|
|
before = sorted(io_loop.run_sync(app.proxy.get_routes))
|
|
assert unquote(test_user.proxy_path) in before
|
|
|
|
# check if a route is removed when user deleted
|
|
io_loop.run_sync(lambda: app.proxy.check_routes(app.users, app._service_map))
|
|
io_loop.run_sync(lambda: proxy.delete_user(test_user))
|
|
during = sorted(io_loop.run_sync(app.proxy.get_routes))
|
|
assert unquote(test_user.proxy_path) not in during
|
|
|
|
# check if a route exists for user
|
|
io_loop.run_sync(lambda: app.proxy.check_routes(app.users, app._service_map))
|
|
after = sorted(io_loop.run_sync(app.proxy.get_routes))
|
|
assert unquote(test_user.proxy_path) in after
|
|
|
|
# check that before and after state are the same
|
|
assert before == after
|
|
|
|
|
|
@pytest.mark.parametrize("test_data", [None, 'notjson', json.dumps([])])
|
|
def test_proxy_patch_bad_request_data(app, test_data):
|
|
r = api_request(app, 'proxy', method='patch', data=test_data)
|
|
assert r.status_code == 400
|