Refactor Swarm E2E tests: optimize timeouts, enhance stack deployment/wait logic, consolidate replicas, and update HAProxy config refresh cycle
This commit is contained in:
parent
d9c7ff89cc
commit
5a9f14e980
3 changed files with 26 additions and 22 deletions
|
|
@ -63,6 +63,7 @@ services:
|
||||||
replicas: 1
|
replicas: 1
|
||||||
environment:
|
environment:
|
||||||
EASYHAPROXY_DISCOVER: swarm
|
EASYHAPROXY_DISCOVER: swarm
|
||||||
|
EASYHAPROXY_REFRESH_CONF: "2"
|
||||||
EASYHAPROXY_SSL_MODE: "loose"
|
EASYHAPROXY_SSL_MODE: "loose"
|
||||||
EASYHAPROXY_CERTBOT_EMAIL: changeme@example.org
|
EASYHAPROXY_CERTBOT_EMAIL: changeme@example.org
|
||||||
HAPROXY_CUSTOMERRORS: "true"
|
HAPROXY_CUSTOMERRORS: "true"
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ services:
|
||||||
- node.role == manager
|
- node.role == manager
|
||||||
environment:
|
environment:
|
||||||
EASYHAPROXY_DISCOVER: swarm
|
EASYHAPROXY_DISCOVER: swarm
|
||||||
|
EASYHAPROXY_REFRESH_CONF: "2"
|
||||||
EASYHAPROXY_SSL_MODE: "loose"
|
EASYHAPROXY_SSL_MODE: "loose"
|
||||||
HAPROXY_CUSTOMERRORS: "true"
|
HAPROXY_CUSTOMERRORS: "true"
|
||||||
HAPROXY_USERNAME: admin
|
HAPROXY_USERNAME: admin
|
||||||
|
|
@ -127,7 +128,7 @@ services:
|
||||||
environment:
|
environment:
|
||||||
TITLE: "Public Website"
|
TITLE: "Public Website"
|
||||||
deploy:
|
deploy:
|
||||||
replicas: 4
|
replicas: 1
|
||||||
labels:
|
labels:
|
||||||
easyhaproxy.http.host: "website.example.com"
|
easyhaproxy.http.host: "website.example.com"
|
||||||
easyhaproxy.http.port: "80"
|
easyhaproxy.http.port: "80"
|
||||||
|
|
@ -146,7 +147,7 @@ services:
|
||||||
environment:
|
environment:
|
||||||
TITLE: "Protected API"
|
TITLE: "Protected API"
|
||||||
deploy:
|
deploy:
|
||||||
replicas: 6
|
replicas: 1
|
||||||
labels:
|
labels:
|
||||||
easyhaproxy.http.host: "api.example.com"
|
easyhaproxy.http.host: "api.example.com"
|
||||||
easyhaproxy.http.port: "80"
|
easyhaproxy.http.port: "80"
|
||||||
|
|
@ -173,7 +174,7 @@ services:
|
||||||
environment:
|
environment:
|
||||||
TITLE: "Admin Panel"
|
TITLE: "Admin Panel"
|
||||||
deploy:
|
deploy:
|
||||||
replicas: 2
|
replicas: 1
|
||||||
labels:
|
labels:
|
||||||
easyhaproxy.http.host: "admin.example.com"
|
easyhaproxy.http.host: "admin.example.com"
|
||||||
easyhaproxy.http.port: "80"
|
easyhaproxy.http.port: "80"
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ def wait_for_http(
|
||||||
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
|
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
time.sleep(3)
|
time.sleep(1)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -271,8 +271,8 @@ class SwarmFixture:
|
||||||
self.stack_name = stack_name
|
self.stack_name = stack_name
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
|
|
||||||
def up(self):
|
def deploy(self):
|
||||||
"""Deploy the stack and wait for all services to be running."""
|
"""Issue `docker stack deploy` for each file without waiting for replicas."""
|
||||||
for stack_file in self.stack_files:
|
for stack_file in self.stack_files:
|
||||||
name = Path(stack_file).name
|
name = Path(stack_file).name
|
||||||
print(f"\n → Deploying stack '{self.stack_name}' from {name}...")
|
print(f"\n → Deploying stack '{self.stack_name}' from {name}...")
|
||||||
|
|
@ -290,16 +290,23 @@ class SwarmFixture:
|
||||||
result.returncode, result.args, result.stdout, result.stderr
|
result.returncode, result.args, result.stdout, result.stderr
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def wait_ready(self):
|
||||||
|
"""Wait until all services in this stack have reached their target replica count."""
|
||||||
if not wait_for_swarm_services(self.stack_name, self.timeout):
|
if not wait_for_swarm_services(self.stack_name, self.timeout):
|
||||||
raise TimeoutError(
|
raise TimeoutError(
|
||||||
f"Stack '{self.stack_name}' services did not become ready within {self.timeout}s"
|
f"Stack '{self.stack_name}' services did not become ready within {self.timeout}s"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def up(self):
|
||||||
|
"""Deploy the stack and wait for all services to be running."""
|
||||||
|
self.deploy()
|
||||||
|
self.wait_ready()
|
||||||
|
|
||||||
def down(self):
|
def down(self):
|
||||||
"""Force-kill all stack containers, then remove the stack definition."""
|
"""Force-kill all stack containers, then remove the stack definition."""
|
||||||
print(f"\n → Removing stack '{self.stack_name}'...")
|
print(f"\n → Removing stack '{self.stack_name}'...")
|
||||||
|
|
||||||
# Force-kill running containers immediately (no graceful shutdown period)
|
# Force-kill all running containers immediately (SIGKILL — no grace period)
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["docker", "ps", "-q", "--filter", f"name={self.stack_name}_"],
|
["docker", "ps", "-q", "--filter", f"name={self.stack_name}_"],
|
||||||
capture_output=True, text=True
|
capture_output=True, text=True
|
||||||
|
|
@ -314,17 +321,7 @@ class SwarmFixture:
|
||||||
capture_output=True, text=True
|
capture_output=True, text=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Poll until no containers from this stack remain, so ports are free
|
# Containers are already dead from docker kill above; ports are freed immediately.
|
||||||
start = time.time()
|
|
||||||
while time.time() - start < 60:
|
|
||||||
result = subprocess.run(
|
|
||||||
["docker", "ps", "-q", "--filter", f"name={self.stack_name}_"],
|
|
||||||
capture_output=True, text=True
|
|
||||||
)
|
|
||||||
if not result.stdout.strip():
|
|
||||||
break
|
|
||||||
time.sleep(2)
|
|
||||||
|
|
||||||
print(f" ✓ Stack '{self.stack_name}' removed")
|
print(f" ✓ Stack '{self.stack_name}' removed")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -368,16 +365,21 @@ def swarm_basic_services(generate_ssl_certificates) -> Generator[None, None, Non
|
||||||
easyhaproxy = SwarmFixture(str(SWARM_DIR / "easyhaproxy.yml"), "easyhaproxy", timeout=120)
|
easyhaproxy = SwarmFixture(str(SWARM_DIR / "easyhaproxy.yml"), "easyhaproxy", timeout=120)
|
||||||
services = SwarmFixture(str(SWARM_DIR / "services.yml"), "services", timeout=60)
|
services = SwarmFixture(str(SWARM_DIR / "services.yml"), "services", timeout=60)
|
||||||
|
|
||||||
easyhaproxy.up()
|
# Deploy both stacks immediately so they start pulling/starting in parallel,
|
||||||
services.up()
|
# then wait for each to reach its target replica count.
|
||||||
|
easyhaproxy.deploy()
|
||||||
|
services.deploy()
|
||||||
|
easyhaproxy.wait_ready()
|
||||||
|
services.wait_ready()
|
||||||
|
|
||||||
# Wait for EasyHAProxy to auto-attach services, regenerate config, and
|
# Wait for EasyHAProxy to auto-attach services, regenerate config, and
|
||||||
# for HAProxy to start serving traffic (up to 2 refresh cycles = ~20s).
|
# for HAProxy to start serving traffic (up to 2 refresh cycles = ~20s).
|
||||||
|
# Do NOT include 503 — that means HAProxy is up but the backend isn't ready yet.
|
||||||
print("\n → Waiting for HAProxy to discover and configure swarm services...")
|
print("\n → Waiting for HAProxy to discover and configure swarm services...")
|
||||||
ready = wait_for_http(
|
ready = wait_for_http(
|
||||||
"https://127.0.0.1/",
|
"https://127.0.0.1/",
|
||||||
headers={"Host": "host1.local"},
|
headers={"Host": "host1.local"},
|
||||||
expected_status=[200, 301, 302, 503],
|
expected_status=[200, 301, 302],
|
||||||
timeout=120,
|
timeout=120,
|
||||||
)
|
)
|
||||||
if not ready:
|
if not ready:
|
||||||
|
|
@ -415,7 +417,7 @@ def swarm_plugins_combined(generate_ssl_certificates) -> Generator[None, None, N
|
||||||
ready = wait_for_http(
|
ready = wait_for_http(
|
||||||
"http://127.0.0.1/",
|
"http://127.0.0.1/",
|
||||||
headers={"Host": "website.example.com"},
|
headers={"Host": "website.example.com"},
|
||||||
expected_status=[200, 404, 403, 401, 503],
|
expected_status=[200],
|
||||||
timeout=120,
|
timeout=120,
|
||||||
)
|
)
|
||||||
if not ready:
|
if not ready:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue