Skip to content

Commit d3ee41d

Browse files
mccoypscbedd
andauthored
[Test Proxy] Add fixture to automatically start/stop Docker container (Azure#21538)
Co-authored-by: scbedd <[email protected]>
1 parent e2316d8 commit d3ee41d

File tree

2 files changed

+164
-0
lines changed

2 files changed

+164
-0
lines changed

tools/azure-sdk-tools/devtools_testutils/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
)
1616
from .keyvault_preparer import KeyVaultPreparer
1717
from .powershell_preparer import PowerShellPreparer
18+
from .proxy_docker_startup import start_test_proxy, stop_test_proxy, test_proxy
1819
from .proxy_testcase import recorded_by_proxy
1920
from .sanitizers import (
2021
add_body_key_sanitizer,
@@ -57,6 +58,9 @@
5758
"CachedResourceGroupPreparer",
5859
"PowerShellPreparer",
5960
"recorded_by_proxy",
61+
"test_proxy",
62+
"start_test_proxy",
63+
"stop_test_proxy",
6064
"ResponseCallback",
6165
"RetryCounter",
6266
"FakeTokenCredential",
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# -------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for
4+
# license information.
5+
# --------------------------------------------------------------------------
6+
import json
7+
import os
8+
import logging
9+
import requests
10+
import shlex
11+
import sys
12+
import time
13+
from typing import TYPE_CHECKING
14+
15+
import pytest
16+
import subprocess
17+
18+
from .config import PROXY_URL
19+
20+
if TYPE_CHECKING:
21+
from typing import Optional
22+
23+
24+
_LOGGER = logging.getLogger()
25+
26+
CONTAINER_NAME = "ambitious_azsdk_test_proxy"
27+
LINUX_IMAGE_SOURCE_PREFIX = "azsdkengsys.azurecr.io/engsys/testproxy-lin"
28+
WINDOWS_IMAGE_SOURCE_PREFIX = "azsdkengsys.azurecr.io/engsys/testproxy-win"
29+
CONTAINER_STARTUP_TIMEOUT = 6000
30+
PROXY_MANUALLY_STARTED = os.getenv('PROXY_MANUAL_START', False)
31+
32+
REPO_ROOT = os.path.abspath(os.path.join(os.path.abspath(__file__), "..", "..", "..", ".."))
33+
34+
35+
def get_image_tag():
36+
# type: () -> str
37+
"""Gets the test proxy Docker image tag from the docker-start-proxy.ps1 script in /eng/common"""
38+
pwsh_script_location = os.path.abspath(
39+
os.path.join(REPO_ROOT, os.path.relpath("eng/common/testproxy/docker-start-proxy.ps1"))
40+
)
41+
42+
image_tag = None
43+
with open(pwsh_script_location, "r") as f:
44+
for line in f:
45+
if line.startswith("$SELECTED_IMAGE_TAG"):
46+
image_tag_with_quotes = line.split()[-1]
47+
image_tag = image_tag_with_quotes.strip('"')
48+
49+
return image_tag
50+
51+
52+
def get_container_info():
53+
# type: () -> Optional[dict]
54+
"""Returns a dictionary containing the test proxy container's information, or None if the container isn't present"""
55+
proc = subprocess.Popen(
56+
shlex.split("docker container ls -a --format '{{json .}}' --filter name=" + CONTAINER_NAME),
57+
stdout=subprocess.PIPE,
58+
stderr=subprocess.PIPE,
59+
)
60+
61+
output, stderr = proc.communicate()
62+
try:
63+
# This will succeed if we found a container with CONTAINER_NAME
64+
return json.loads(output)
65+
# We'll get a JSONDecodeError on Py3 (ValueError on Py2) if output is empty (i.e. there's no proxy container)
66+
except ValueError:
67+
# Didn't find a container with CONTAINER_NAME
68+
return None
69+
70+
71+
def create_container():
72+
# type: () -> None
73+
"""Creates the test proxy Docker container"""
74+
# Most of the time, running this script on a Windows machine will work just fine, as Docker defaults to Linux
75+
# containers. However, in CI, Windows images default to _Windows_ containers. We cannot swap them. We can tell
76+
# if we're in a CI build by checking for the environment variable TF_BUILD.
77+
if sys.platform.startswith("win") and os.environ.get("TF_BUILD"):
78+
image_prefix = WINDOWS_IMAGE_SOURCE_PREFIX
79+
path_prefix = "C:"
80+
linux_container_args = ""
81+
else:
82+
image_prefix = LINUX_IMAGE_SOURCE_PREFIX
83+
path_prefix = ""
84+
linux_container_args = "--add-host=host.docker.internal:host-gateway"
85+
86+
image_tag = get_image_tag()
87+
proc = subprocess.Popen(
88+
shlex.split(
89+
"docker container create -v '{}:{}/etc/testproxy' {} -p 5001:5001 -p 5000:5000 --name {} {}:{}".format(
90+
REPO_ROOT, path_prefix, linux_container_args, CONTAINER_NAME, image_prefix, image_tag
91+
)
92+
)
93+
)
94+
proc.communicate()
95+
96+
97+
def start_test_proxy():
98+
# type: () -> None
99+
"""Starts the test proxy and returns when the proxy server is ready to receive requests"""
100+
101+
if not PROXY_MANUALLY_STARTED:
102+
_LOGGER.info("Starting the test proxy container...")
103+
104+
container_info = get_container_info()
105+
if container_info:
106+
_LOGGER.debug("Found an existing instance of the test proxy container.")
107+
108+
if container_info["State"] == "running":
109+
_LOGGER.debug("Proxy container is already running. Exiting...")
110+
return
111+
112+
else:
113+
_LOGGER.debug("No instance of the test proxy container found. Attempting creation...")
114+
create_container()
115+
116+
_LOGGER.debug("Attempting to start the test proxy container...")
117+
118+
proc = subprocess.Popen(shlex.split("docker container start " + CONTAINER_NAME))
119+
proc.communicate()
120+
121+
# Wait for the proxy server to become available
122+
start = time.time()
123+
now = time.time()
124+
status_code = 0
125+
while now - start < CONTAINER_STARTUP_TIMEOUT and status_code != 200:
126+
try:
127+
response = requests.get(PROXY_URL.rstrip("/") + "/Info/Available", timeout=60)
128+
status_code = response.status_code
129+
# We get an SSLError if the container is started but the endpoint isn't available yet
130+
except requests.exceptions.SSLError:
131+
pass
132+
now = time.time()
133+
134+
135+
def stop_test_proxy():
136+
# type: () -> None
137+
"""Stops any running instance of the test proxy"""
138+
139+
if not PROXY_MANUALLY_STARTED:
140+
_LOGGER.info("Stopping the test proxy container...")
141+
142+
container_info = get_container_info()
143+
if container_info:
144+
if container_info["State"] == "running":
145+
_LOGGER.debug("Found a running instance of the test proxy container; shutting it down...")
146+
147+
proc = subprocess.Popen(shlex.split("docker container stop " + CONTAINER_NAME))
148+
proc.communicate()
149+
else:
150+
_LOGGER.debug("No running instance of the test proxy container found. Exiting...")
151+
152+
153+
@pytest.fixture(scope="session")
154+
def test_proxy():
155+
"""Pytest fixture to be used before running any tests that are recorded with the test proxy"""
156+
start_test_proxy()
157+
# Everything before this yield will be run before fixtures that invoke this one are run
158+
# Everything after it will be run after invoking fixtures are done executing
159+
yield
160+
stop_test_proxy()

0 commit comments

Comments
 (0)