diff --git a/tests_e2e/swarm/easyhaproxy.yml b/tests_e2e/swarm/easyhaproxy.yml index 552bd2a..d9d66ab 100644 --- a/tests_e2e/swarm/easyhaproxy.yml +++ b/tests_e2e/swarm/easyhaproxy.yml @@ -63,6 +63,7 @@ services: replicas: 1 environment: EASYHAPROXY_DISCOVER: swarm + EASYHAPROXY_REFRESH_CONF: "2" EASYHAPROXY_SSL_MODE: "loose" EASYHAPROXY_CERTBOT_EMAIL: changeme@example.org HAPROXY_CUSTOMERRORS: "true" diff --git a/tests_e2e/swarm/plugins-combined.yml b/tests_e2e/swarm/plugins-combined.yml index 28c3f4e..f7d2dd0 100644 --- a/tests_e2e/swarm/plugins-combined.yml +++ b/tests_e2e/swarm/plugins-combined.yml @@ -109,6 +109,7 @@ services: - node.role == manager environment: EASYHAPROXY_DISCOVER: swarm + EASYHAPROXY_REFRESH_CONF: "2" EASYHAPROXY_SSL_MODE: "loose" HAPROXY_CUSTOMERRORS: "true" HAPROXY_USERNAME: admin @@ -127,7 +128,7 @@ services: environment: TITLE: "Public Website" deploy: - replicas: 4 + replicas: 1 labels: easyhaproxy.http.host: "website.example.com" easyhaproxy.http.port: "80" @@ -146,7 +147,7 @@ services: environment: TITLE: "Protected API" deploy: - replicas: 6 + replicas: 1 labels: easyhaproxy.http.host: "api.example.com" easyhaproxy.http.port: "80" @@ -173,7 +174,7 @@ services: environment: TITLE: "Admin Panel" deploy: - replicas: 2 + replicas: 1 labels: easyhaproxy.http.host: "admin.example.com" easyhaproxy.http.port: "80" diff --git a/tests_e2e/test_swarm.py b/tests_e2e/test_swarm.py index 4556be1..82bdeb9 100644 --- a/tests_e2e/test_swarm.py +++ b/tests_e2e/test_swarm.py @@ -192,7 +192,7 @@ def wait_for_http( except (requests.exceptions.ConnectionError, requests.exceptions.Timeout): pass - time.sleep(3) + time.sleep(1) return False @@ -271,8 +271,8 @@ class SwarmFixture: self.stack_name = stack_name self.timeout = timeout - def up(self): - """Deploy the stack and wait for all services to be running.""" + def deploy(self): + """Issue `docker stack deploy` for each file without waiting for replicas.""" for stack_file in self.stack_files: name = Path(stack_file).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 ) + 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): raise TimeoutError( 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): """Force-kill all stack containers, then remove the stack definition.""" 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( ["docker", "ps", "-q", "--filter", f"name={self.stack_name}_"], capture_output=True, text=True @@ -314,17 +321,7 @@ class SwarmFixture: capture_output=True, text=True ) - # Poll until no containers from this stack remain, so ports are free - 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) - + # Containers are already dead from docker kill above; ports are freed immediately. 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) services = SwarmFixture(str(SWARM_DIR / "services.yml"), "services", timeout=60) - easyhaproxy.up() - services.up() + # Deploy both stacks immediately so they start pulling/starting in parallel, + # 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 # 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...") ready = wait_for_http( "https://127.0.0.1/", headers={"Host": "host1.local"}, - expected_status=[200, 301, 302, 503], + expected_status=[200, 301, 302], timeout=120, ) if not ready: @@ -415,7 +417,7 @@ def swarm_plugins_combined(generate_ssl_certificates) -> Generator[None, None, N ready = wait_for_http( "http://127.0.0.1/", headers={"Host": "website.example.com"}, - expected_status=[200, 404, 403, 401, 503], + expected_status=[200], timeout=120, ) if not ready: