Skip to content

Commit 6143c84

Browse files
avoid --abort-on-container-exit to allow containers to react to SIGTERM
1 parent cdce9c8 commit 6143c84

File tree

4 files changed

+166
-100
lines changed

4 files changed

+166
-100
lines changed

docker-compose.yml

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,19 @@ services:
3131
environment:
3232
- CRON=$CRON
3333
- ROLE=server
34-
- SERVER_PARAMS=$SERVER_PARAMS
3534
- SSLKEYLOGFILE=/logs/keys.log
3635
- QLOGDIR=/logs/qlog/
3736
- TESTCASE=$TESTCASE_SERVER
38-
depends_on:
39-
- sim
4037
cap_add:
4138
- NET_ADMIN
4239
ulimits:
4340
memlock: 67108864
4441
networks:
4542
rightnet:
4643
ipv4_address: 193.167.100.100
44+
extra_hosts:
45+
- "sim:193.167.100.2"
46+
- "client:193.167.0.100"
4747

4848
client:
4949
image: $CLIENT
@@ -57,13 +57,10 @@ services:
5757
environment:
5858
- CRON=$CRON
5959
- ROLE=client
60-
- CLIENT_PARAMS=$CLIENT_PARAMS
6160
- SSLKEYLOGFILE=/logs/keys.log
6261
- QLOGDIR=/logs/qlog/
6362
- TESTCASE=$TESTCASE_CLIENT
6463
- REQUESTS=$REQUESTS
65-
depends_on:
66-
- sim
6764
cap_add:
6865
- NET_ADMIN
6966
ulimits:
@@ -72,6 +69,7 @@ services:
7269
leftnet:
7370
ipv4_address: 193.167.0.100
7471
extra_hosts:
72+
- "sim:193.167.0.2"
7573
- "server:193.167.100.100"
7674

7775
iperf_server:
@@ -81,8 +79,6 @@ services:
8179
tty: true
8280
environment:
8381
- ROLE=server
84-
depends_on:
85-
- sim
8682
cap_add:
8783
- NET_ADMIN
8884
networks:
@@ -98,8 +94,6 @@ services:
9894
tty: true
9995
environment:
10096
- ROLE=client
101-
depends_on:
102-
- sim
10397
cap_add:
10498
- NET_ADMIN
10599
networks:

docker.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import io
2+
import logging
3+
import shutil
4+
import subprocess
5+
import threading
6+
7+
8+
class DockerRunner:
9+
_containers = None
10+
_cond = None
11+
_timeout = 0 # in seconds
12+
_expired = False
13+
14+
def __init__(self, timeout: int):
15+
self._containers = []
16+
self._cond = threading.Condition()
17+
self._timeout = timeout
18+
19+
def add_container(self, name: str, env: dict):
20+
self._containers.append({"name": name, "env": env})
21+
22+
def _run_container(self, cmd: str, env: dict, name: str):
23+
self._execute(cmd, env, name)
24+
with self._cond:
25+
logging.debug("%s container returned.", name)
26+
self._cond.notify()
27+
28+
def _execute(self, cmd: str, env: dict = {}, name: str = ""):
29+
p = subprocess.Popen(
30+
cmd.split(" "),
31+
bufsize=1,
32+
env=env,
33+
stdout=subprocess.PIPE,
34+
stderr=subprocess.STDOUT,
35+
universal_newlines=True,
36+
)
37+
for line in p.stdout:
38+
ll = ""
39+
if name:
40+
ll = name + ": "
41+
ll = ll + line.rstrip()
42+
logging.debug(ll)
43+
44+
def _run_timer(self):
45+
logging.debug("Timer expired. Stopping all containers.")
46+
self._expired = True
47+
with self._cond:
48+
self._cond.notify()
49+
50+
def run(self) -> (str, bool): # returns if the timer expired
51+
# also log to a string, so we can parse the output later
52+
output_string = io.StringIO()
53+
output_stream = logging.StreamHandler(output_string)
54+
output_stream.setLevel(logging.DEBUG)
55+
logging.getLogger().addHandler(output_stream)
56+
57+
threads = []
58+
# Start all containers (in separate threads)
59+
docker_compose = shutil.which("docker-compose")
60+
for e in self._containers:
61+
t = threading.Thread(
62+
target=self._run_container,
63+
kwargs={
64+
"cmd": docker_compose + " up " + e["name"],
65+
"env": e["env"],
66+
"name": e["name"],
67+
},
68+
)
69+
t.start()
70+
threads.append(t)
71+
# set a timer
72+
timer = threading.Timer(self._timeout, self._run_timer)
73+
timer.start()
74+
75+
# Wait for the first container to exit.
76+
# Then stop all other docker containers.
77+
with self._cond:
78+
self._cond.wait()
79+
names = [x["name"] for x in self._containers]
80+
self._execute(
81+
shutil.which("docker-compose") + " stop -t 5 " + " ".join(names)
82+
)
83+
# wait for all threads to finish
84+
for t in threads:
85+
t.join()
86+
timer.cancel()
87+
88+
output = output_string.getvalue()
89+
output_string.close()
90+
logging.getLogger().removeHandler(output_stream)
91+
return output, self._expired

interop.py

Lines changed: 67 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from termcolor import colored
1717

1818
import testcases
19+
from docker import DockerRunner
1920
from result import TestResult
2021
from testcases import Perspective
2122

@@ -122,46 +123,48 @@ def _check_impl_is_compliant(self, name: str) -> bool:
122123

123124
# check that the client is capable of returning UNSUPPORTED
124125
logging.debug("Checking compliance of %s client", name)
125-
cmd = (
126-
"CERTS=./certs" + " "
127-
"TESTCASE_CLIENT=" + random_string(6) + " "
128-
"SERVER_LOGS=/dev/null "
129-
"CLIENT_LOGS=" + client_log_dir.name + " "
130-
"WWW=" + www_dir.name + " "
131-
"DOWNLOADS=" + downloads_dir.name + " "
132-
'SCENARIO="simple-p2p --delay=15ms --bandwidth=10Mbps --queue=25" '
133-
"CLIENT=" + self._implementations[name]["image"] + " "
134-
"docker-compose up --timeout 0 --abort-on-container-exit -V sim client"
135-
)
136-
output = subprocess.run(
137-
cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
138-
)
139-
if not self._is_unsupported(output.stdout.splitlines()):
126+
env_sim = {"SCENARIO": "simple-p2p --delay=15ms --bandwidth=10Mbps --queue=25"}
127+
env_client = {
128+
"CERTS": "./certs",
129+
"TESTCASE_CLIENT": random_string(6),
130+
"DOWNLOADS": downloads_dir.name,
131+
"CLIENT_LOGS": client_log_dir.name,
132+
"CLIENT": self._implementations[name]["image"],
133+
}
134+
env = {}
135+
env.update(env_sim)
136+
env.update(env_client)
137+
r = DockerRunner(5)
138+
r.add_container("client", env)
139+
r.add_container("sim", env)
140+
output, expired = r.run()
141+
if expired or not self._is_unsupported(output.splitlines()):
140142
logging.error("%s client not compliant.", name)
141-
logging.debug("%s", output.stdout.decode("utf-8"))
143+
logging.debug("%s", output)
142144
self.compliant[name] = False
143145
return False
144146
logging.debug("%s client compliant.", name)
145147

146148
# check that the server is capable of returning UNSUPPORTED
147149
logging.debug("Checking compliance of %s server", name)
148150
server_log_dir = tempfile.TemporaryDirectory(dir="/tmp", prefix="logs_server_")
149-
cmd = (
150-
"CERTS=./certs" + " "
151-
"TESTCASE_SERVER=" + random_string(6) + " "
152-
"SERVER_LOGS=" + server_log_dir.name + " "
153-
"CLIENT_LOGS=/dev/null "
154-
"WWW=" + www_dir.name + " "
155-
"DOWNLOADS=" + downloads_dir.name + " "
156-
"SERVER=" + self._implementations[name]["image"] + " "
157-
"docker-compose up -V server"
158-
)
159-
output = subprocess.run(
160-
cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
161-
)
162-
if not self._is_unsupported(output.stdout.splitlines()):
151+
env_server = {
152+
"CERTS": "./certs",
153+
"TESTCASE_SERVER": random_string(6),
154+
"SERVER_LOGS": server_log_dir.name,
155+
"WWW": www_dir.name,
156+
"SERVER": self._implementations[name]["image"],
157+
}
158+
env = {}
159+
env.update(env_sim)
160+
env.update(env_server)
161+
r = DockerRunner(5)
162+
r.add_container("server", env)
163+
r.add_container("sim", env)
164+
output, expired = r.run()
165+
if expired or not self._is_unsupported(output.splitlines()):
163166
logging.error("%s server not compliant.", name)
164-
logging.debug("%s", output.stdout.decode("utf-8"))
167+
logging.debug("%s", output)
165168
self.compliant[name] = False
166169
return False
167170
logging.debug("%s server compliant.", name)
@@ -330,73 +333,51 @@ def _run_test(
330333
server_keylog_file=server_log_dir.name + "/keys.log",
331334
)
332335
print(
333-
"Server: "
334-
+ server
335-
+ ". Client: "
336-
+ client
337-
+ ". Running test case: "
338-
+ str(testcase)
336+
"Server: {}. Client: {}. Running test: {}".format(
337+
server, client, str(testcase)
338+
)
339339
)
340340

341341
reqs = " ".join(["https://server:443/" + p for p in testcase.get_paths()])
342342
logging.debug("Requests: %s", reqs)
343-
params = (
344-
"CERTS=./certs" + " "
345-
"TESTCASE_SERVER=" + testcase.testname(Perspective.SERVER) + " "
346-
"TESTCASE_CLIENT=" + testcase.testname(Perspective.CLIENT) + " "
347-
"WWW=" + testcase.www_dir() + " "
348-
"DOWNLOADS=" + testcase.download_dir() + " "
349-
"SERVER_LOGS=" + server_log_dir.name + " "
350-
"CLIENT_LOGS=" + client_log_dir.name + " "
351-
'SCENARIO="{}" '
352-
"CLIENT=" + self._implementations[client]["image"] + " "
353-
"SERVER=" + self._implementations[server]["image"] + " "
354-
'REQUESTS="' + reqs + '" '
355-
).format(testcase.scenario())
356-
params += " ".join(testcase.additional_envs())
357-
containers = "sim client server " + " ".join(testcase.additional_containers())
358-
cmd = (
359-
params
360-
+ " docker-compose up --abort-on-container-exit --timeout 1 "
361-
+ containers
362-
)
363-
logging.debug("Command: %s", cmd)
343+
r = DockerRunner(timeout=testcase.timeout())
344+
env_server = {
345+
"CERTS": "./certs",
346+
"TESTCASE_SERVER": testcase.testname(Perspective.SERVER),
347+
"WWW": testcase.www_dir(),
348+
"SERVER_LOGS": server_log_dir.name,
349+
"SERVER": self._implementations[server]["image"],
350+
}
351+
env_client = {
352+
"CERTS": "./certs",
353+
"TESTCASE_CLIENT": testcase.testname(Perspective.CLIENT),
354+
"DOWNLOADS": testcase.download_dir(),
355+
"CLIENT_LOGS": client_log_dir.name,
356+
"CLIENT": self._implementations[client]["image"],
357+
"REQUESTS": reqs,
358+
}
359+
env_sim = {"SCENARIO": testcase.scenario()}
360+
env = {}
361+
env.update(env_server)
362+
env.update(env_client)
363+
env.update(env_sim)
364+
r.add_container(name="server", env=env)
365+
r.add_container(name="client", env=env)
366+
r.add_container(name="sim", env=env)
367+
for c in testcase.additional_containers():
368+
r.add_container(name=c, env=testcase.additional_envs())
369+
output, expired = r.run()
364370

365371
status = TestResult.FAILED
366-
output = ""
367-
expired = False
368-
try:
369-
r = subprocess.run(
370-
cmd,
371-
shell=True,
372-
stdout=subprocess.PIPE,
373-
stderr=subprocess.STDOUT,
374-
timeout=testcase.timeout(),
375-
)
376-
output = r.stdout
377-
except subprocess.TimeoutExpired as ex:
378-
output = ex.stdout
379-
expired = True
380-
381-
logging.debug("%s", output.decode("utf-8"))
382-
383-
if expired:
384-
logging.debug("Test failed: took longer than %ds.", testcase.timeout())
385-
r = subprocess.run(
386-
"docker-compose stop " + containers,
387-
shell=True,
388-
stdout=subprocess.PIPE,
389-
stderr=subprocess.STDOUT,
390-
timeout=60,
391-
)
392-
logging.debug("%s", r.stdout.decode("utf-8"))
393372

394373
# copy the pcaps from the simulator
395374
self._copy_logs("sim", sim_log_dir)
396375
self._copy_logs("client", client_log_dir)
397376
self._copy_logs("server", server_log_dir)
398377

399-
if not expired:
378+
if expired:
379+
logging.debug("Test failed: took longer than %ds.", testcase.timeout())
380+
else:
400381
lines = output.splitlines()
401382
if self._is_unsupported(lines):
402383
status = TestResult.UNSUPPORTED

testcases.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,11 @@ def timeout() -> int:
8585

8686
@staticmethod
8787
def additional_envs() -> List[str]:
88-
return [""]
88+
return []
8989

9090
@staticmethod
9191
def additional_containers() -> List[str]:
92-
return [""]
92+
return []
9393

9494
def www_dir(self):
9595
if not self._www_dir:
@@ -1080,8 +1080,8 @@ def timeout() -> int:
10801080
return 180
10811081

10821082
@staticmethod
1083-
def additional_envs() -> List[str]:
1084-
return ["IPERF_CONGESTION=cubic"]
1083+
def additional_envs() -> dict:
1084+
return {"IPERF_CONGESTION": "cubic"}
10851085

10861086
@staticmethod
10871087
def additional_containers() -> List[str]:

0 commit comments

Comments
 (0)