Files
jupyterhub/jupyterhub/tests/test_proxy.py
2017-01-27 04:14:51 -08:00

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