44
55from __future__ import annotations
66
7+ import time
78import uuid
89from http import HTTPStatus
910from pathlib import Path
2324 from docker .models .networks import Network
2425
2526
27+ # We do not cover this function because hitting particular branches depends on
28+ # timing.
29+ def wait_for_flask_app_to_start (base_url : str ) -> None : # pragma: no cover
30+ """
31+ Wait for a server to start.
32+
33+ Args:
34+ base_url: The base URL of the Flask app to wait for.
35+ """
36+ max_attempts = 10
37+ sleep_seconds = 0.5
38+ url = f"{ base_url } /{ uuid .uuid4 ().hex } "
39+ for _ in range (max_attempts ):
40+ try :
41+ response = requests .get (url , timeout = 30 )
42+ except requests .exceptions .ConnectionError :
43+ time .sleep (sleep_seconds )
44+ else :
45+ if response .status_code in (
46+ HTTPStatus .NOT_FOUND ,
47+ HTTPStatus .UNAUTHORIZED ,
48+ HTTPStatus .FORBIDDEN ,
49+ ):
50+ return
51+ error_message = (
52+ f"Could not connect to { url } after "
53+ f"{ max_attempts * sleep_seconds } seconds."
54+ )
55+ raise RuntimeError (error_message )
56+
57+
2658@pytest .fixture (name = "custom_bridge_network" )
2759def fixture_custom_bridge_network () -> Iterator [Network ]:
2860 """
2961 Yield a custom bridge network which containers can connect to.
62+
63+ This also cleans up all containers connected to the network and the network
64+ after the test.
3065 """
3166 client = docker .from_env ()
3267 try :
@@ -44,6 +79,11 @@ def fixture_custom_bridge_network() -> Iterator[Network]:
4479 try :
4580 yield network
4681 finally :
82+ network .reload ()
83+ for container in network .containers :
84+ network .disconnect (container = container )
85+ container .stop ()
86+ container .remove ()
4787 network .remove ()
4888
4989
@@ -103,7 +143,9 @@ def test_build_and_run(
103143
104144 database = VuforiaDatabase ()
105145 target_manager_container_name = "vws-mock-target-manager-" + random
106- target_manager_base_url = f"http://{ target_manager_container_name } :5000"
146+ target_manager_internal_base_url = (
147+ f"http://{ target_manager_container_name } :5000"
148+ )
107149
108150 target_manager_container = client .containers .run (
109151 image = target_manager_image ,
@@ -118,15 +160,19 @@ def test_build_and_run(
118160 name = "vws-mock-vws-" + random ,
119161 publish_all_ports = True ,
120162 network = custom_bridge_network .name ,
121- environment = {"TARGET_MANAGER_BASE_URL" : target_manager_base_url },
163+ environment = {
164+ "TARGET_MANAGER_BASE_URL" : target_manager_internal_base_url ,
165+ },
122166 )
123167 vwq_container = client .containers .run (
124168 image = vwq_image ,
125169 detach = True ,
126170 name = "vws-mock-vwq-" + random ,
127171 publish_all_ports = True ,
128172 network = custom_bridge_network .name ,
129- environment = {"TARGET_MANAGER_BASE_URL" : target_manager_base_url },
173+ environment = {
174+ "TARGET_MANAGER_BASE_URL" : target_manager_internal_base_url ,
175+ },
130176 )
131177
132178 for container in (target_manager_container , vws_container , vwq_container ):
@@ -135,8 +181,8 @@ def test_build_and_run(
135181 target_manager_port_attrs = target_manager_container .attrs [
136182 "NetworkSettings"
137183 ]["Ports" ]
138- target_manager_host_ip = target_manager_port_attrs ["5000/tcp" ][0 ]["HostIp" ]
139- target_manager_host_port = target_manager_port_attrs ["5000/tcp" ][0 ][
184+ task_manager_host_ip = target_manager_port_attrs ["5000/tcp" ][0 ]["HostIp" ]
185+ task_manager_host_port = target_manager_port_attrs ["5000/tcp" ][0 ][
140186 "HostPort"
141187 ]
142188
@@ -148,11 +194,21 @@ def test_build_and_run(
148194 vwq_host_ip = vwq_port_attrs ["5000/tcp" ][0 ]["HostIp" ]
149195 vwq_host_port = vwq_port_attrs ["5000/tcp" ][0 ]["HostPort" ]
150196
151- target_manager_host_url = (
152- f"http://{ target_manager_host_ip } :{ target_manager_host_port } "
197+ base_vws_url = f"http://{ vws_host_ip } :{ vws_host_port } "
198+ base_vwq_url = f"http://{ vwq_host_ip } :{ vwq_host_port } "
199+ base_task_manager_url = (
200+ f"http://{ task_manager_host_ip } :{ task_manager_host_port } "
153201 )
202+
203+ for base_url in (
204+ base_vws_url ,
205+ base_vwq_url ,
206+ base_task_manager_url ,
207+ ):
208+ wait_for_flask_app_to_start (base_url = base_url )
209+
154210 response = requests .post (
155- url = f"{ target_manager_host_url } /databases" ,
211+ url = f"{ base_task_manager_url } /databases" ,
156212 json = database .to_dict (),
157213 timeout = 30 ,
158214 )
@@ -162,7 +218,7 @@ def test_build_and_run(
162218 vws_client = VWS (
163219 server_access_key = database .server_access_key ,
164220 server_secret_key = database .server_secret_key ,
165- base_vws_url = f"http:// { vws_host_ip } : { vws_host_port } " ,
221+ base_vws_url = base_vws_url ,
166222 )
167223
168224 target_id = vws_client .add_target (
@@ -178,13 +234,9 @@ def test_build_and_run(
178234 cloud_reco_client = CloudRecoService (
179235 client_access_key = database .client_access_key ,
180236 client_secret_key = database .client_secret_key ,
181- base_vwq_url = f"http:// { vwq_host_ip } : { vwq_host_port } " ,
237+ base_vwq_url = base_vwq_url ,
182238 )
183239
184240 matching_targets = cloud_reco_client .query (image = high_quality_image )
185241
186- for container in (target_manager_container , vws_container , vwq_container ):
187- container .stop ()
188- container .remove ()
189-
190242 assert matching_targets [0 ].target_id == target_id
0 commit comments