forked from jupyterhub/zero-to-jupyterhub-k8s
-
Notifications
You must be signed in to change notification settings - Fork 0
/
test_spawn.py
244 lines (194 loc) · 9.56 KB
/
test_spawn.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
import os
import subprocess
import time
import pytest
import requests
import yaml
# Makes heavy use of JupyterHub's API:
# http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyterhub/jupyterhub/master/docs/rest-api.yml
# load app version of chart
here = os.path.dirname(os.path.abspath(__file__))
chart_yaml = os.path.join(here, os.pardir, "jupyterhub", "Chart.yaml")
with open(chart_yaml) as f:
chart = yaml.safe_load(f)
jupyterhub_version = chart["appVersion"]
def test_api(api_request):
"""
Tests the hub api's root endpoint (/). The hub's version should be returned.
A typical jupyterhub logging response to this test:
[I 2019-09-25 12:03:12.051 JupyterHub log:174] 200 GET /hub/api ([email protected]) 9.57ms
"""
print("asking for the hub's version")
r = api_request.get("")
assert r.status_code == 200
assert r.json().get("version", "version-missing") == jupyterhub_version
def test_api_info(api_request):
"""
Tests the hub api's /info endpoint. Information about the hub should be
returned.
A typical jupyterhub logging response to this test:
[I 2019-09-25 12:03:12.086 JupyterHub log:174] 200 GET /hub/api/info ([email protected]) 10.21ms
"""
print("asking for the hub information")
r = api_request.get("/info")
assert r.status_code == 200
result = r.json()
assert result["spawner"]["class"] == "kubespawner.spawner.KubeSpawner"
def test_hub_api_create_user_and_get_information_about_user(api_request, jupyter_user):
"""
Tests the hub api's /users/:user endpoint, both POST and GET.
A jupyter user is created and commited to the hub database through the
jupyter_user pytest fixture declared in conftest.py. Due to this, this first
test to use the jupyter_user fixture is actually testing both the pytest
fixture that creates the user as well as the ability to get information from
the hub about the user. The jupyter_user fixture will automatically clean up
the user from the hub's database when this function exits.
A typical jupyterhub logging response to this test:
[I 2019-09-25 12:03:12.126 JupyterHub log:174] 201 POST /hub/api/users/testuser-7c70eb90-035b-4d9f-92a5-482e441e307d ([email protected]) 20.74ms
[I 2019-09-25 12:03:12.153 JupyterHub log:174] 200 GET /hub/api/users/testuser-7c70eb90-035b-4d9f-92a5-482e441e307d ([email protected]) 11.91ms
[D 2019-09-25 12:03:12.180 JupyterHub user:240] Creating <class 'kubespawner.spawner.KubeSpawner'> for testuser-7c70eb90-035b-4d9f-92a5-482e441e307d:
[I 2019-09-25 12:03:12.204 JupyterHub reflector:199] watching for pods with label selector='component=singleuser-server' in namespace jh-dev
[D 2019-09-25 12:03:12.205 JupyterHub reflector:202] Connecting pods watcher
[I 2019-09-25 12:03:12.229 JupyterHub reflector:199] watching for events with field selector='involvedObject.kind=Pod' in namespace jh-dev
[D 2019-09-25 12:03:12.229 JupyterHub reflector:202] Connecting events watcher
[I 2019-09-25 12:03:12.269 JupyterHub log:174] 204 DELETE /hub/api/users/testuser-7c70eb90-035b-4d9f-92a5-482e441e307d ([email protected]) 98.85ms
"""
print("create a user, and get information about the user")
r = api_request.get("/users/" + jupyter_user)
assert r.status_code == 200
assert r.json()["name"] == jupyter_user
def test_hub_api_list_users(api_request, jupyter_user):
"""
Tests the hub api's /users endpoint. Information about users should be
returned.
A typical jupyterhub logging response to this test:
[I 2019-09-25 12:03:12.303 JupyterHub log:174] 201 POST /hub/api/users/testuser-0d2b0fc9-5ac4-4d8c-8d25-c4545665f81f ([email protected]) 15.53ms
[I 2019-09-25 12:03:12.331 JupyterHub log:174] 200 GET /hub/api/users ([email protected]) 10.83ms
[D 2019-09-25 12:03:12.358 JupyterHub user:240] Creating <class 'kubespawner.spawner.KubeSpawner'> for testuser-0d2b0fc9-5ac4-4d8c-8d25-c4545665f81f:
[I 2019-09-25 12:03:12.365 JupyterHub log:174] 204 DELETE /hub/api/users/testuser-0d2b0fc9-5ac4-4d8c-8d25-c4545665f81f ([email protected]) 18.44ms
"""
print("create a test user, get information about all users, and find the test user")
r = api_request.get("/users")
assert r.status_code == 200
assert any(u["name"] == jupyter_user for u in r.json())
def test_hub_can_talk_to_proxy(api_request, request_data):
"""
Tests the hub api's /proxy endpoint.
A typical jupyterhub logging response to this test:
[I 2019-09-25 12:03:12.395 JupyterHub log:174] 200 GET /hub/api/proxy ([email protected]) 13.48ms
"""
endtime = time.time() + request_data["test_timeout"]
while time.time() < endtime:
try:
r = api_request.get("/proxy")
if r.status_code == 200:
break
print(r.json())
except requests.RequestException as e:
print(e)
time.sleep(1)
assert r.status_code == 200, "Failed to get /proxy"
def test_hub_api_request_user_spawn(api_request, jupyter_user, request_data, pebble_acme_ca_cert):
"""
Tests the hub api's /users/:user/server POST endpoint. A user pod should be
created with environment variables defined in singleuser.extraEnv etc.
"""
print("asking kubespawner to spawn a server for a test user")
r = api_request.post("/users/" + jupyter_user + "/server")
assert r.status_code in (201, 202)
try:
# check successfull spawn
server_model = _wait_for_user_to_spawn(
api_request, jupyter_user, request_data["test_timeout"]
)
assert server_model
r = requests.get(
request_data["hub_url"].partition("/hub/api")[0]
+ server_model["url"]
+ "api",
verify=pebble_acme_ca_cert,
)
assert r.status_code == 200
assert "version" in r.json()
# check user pod's extra environment variable
pod_name = server_model["state"]["pod_name"]
c = subprocess.run([
"kubectl", "exec", pod_name, "--",
"sh", "-c", "if [ -z $TEST_ENV_FIELDREF_TO_NAMESPACE ]; then exit 1; fi",
])
assert c.returncode == 0, f"singleuser.extraEnv didn't lead to a mounted environment variable!"
finally:
_delete_server(api_request, jupyter_user, request_data["test_timeout"])
@pytest.mark.netpol
def test_singleuser_netpol(api_request, jupyter_user, request_data):
"""
Tests a spawned user pods ability to communicate with allowed and blocked
internet locations.
"""
print("asking kubespawner to spawn a server for a test user to test network policies")
r = api_request.post("/users/" + jupyter_user + "/server")
assert r.status_code in (201, 202)
try:
# check successfull spawn
server_model = _wait_for_user_to_spawn(
api_request, jupyter_user, request_data["test_timeout"]
)
assert server_model
pod_name = server_model["state"]["pod_name"]
c = subprocess.run([
"kubectl", "exec", pod_name, "--",
"nslookup", "hub",
])
assert c.returncode == 0, "DNS issue: failed to resolve 'hub' from a singleuser-server"
c = subprocess.run([
"kubectl", "exec", pod_name, "--",
"nslookup", "jupyter.org",
])
assert c.returncode == 0, "DNS issue: failed to resolve 'jupyter.org' from a singleuser-server"
# Must match CIDR in singleuser.networkPolicy.egress.
allowed_url = "http://jupyter.org"
blocked_url = "http://mybinder.org"
c = subprocess.run([
"kubectl", "exec", pod_name, "--",
"wget", "--quiet", "--tries=3", "--timeout=3", allowed_url,
])
assert c.returncode == 0, f"Network issue: access to '{blocked_url}' was supposed to be allowed"
c = subprocess.run([
"kubectl", "exec", pod_name, "--",
"wget", "--quiet", "--server-response", "-O-", "--tries=3", "--timeout=3", blocked_url,
])
assert c.returncode > 0, f"Network issue: access to '{blocked_url}' was supposed to be denied"
finally:
_delete_server(api_request, jupyter_user, request_data["test_timeout"])
def _wait_for_user_to_spawn(api_request, jupyter_user, timeout):
endtime = time.time() + timeout
while time.time() < endtime:
# NOTE: If this request fails with a 503 response from the proxy, the
# hub pod has probably crashed by the tests interaction with it.
r = api_request.get("/users/" + jupyter_user)
r.raise_for_status()
user_model = r.json()
# Note that JupyterHub has a concept of named servers, so the default
# server is named "", a blank string.
if "" in user_model["servers"]:
server_model = user_model["servers"][""]
if server_model["ready"]:
return server_model
else:
print("Awaiting server info to be part of user_model...")
time.sleep(1)
return False
def _delete_server(api_request, jupyter_user, timeout):
# NOTE: If this request fails with a 503 response from the proxy, the hub
# pod has probably crashed by the previous tests' interaction with it.
r = api_request.delete("/users/" + jupyter_user + "/server")
assert r.status_code in (202, 204)
endtime = time.time() + timeout
while time.time() < endtime:
r = api_request.get("/users/" + jupyter_user)
r.raise_for_status()
user_model = r.json()
if "" not in user_model["servers"]:
return True
time.sleep(1)
return False