diff --git a/.github/workflows/kubernetes-validation.yml b/.github/workflows/kubernetes-validation.yml index d9ae13c0b..d136d7778 100644 --- a/.github/workflows/kubernetes-validation.yml +++ b/.github/workflows/kubernetes-validation.yml @@ -43,10 +43,13 @@ jobs: install_dir="${RUNNER_TEMP}/bin" mkdir -p "${install_dir}" - curl -fsSL -o "${install_dir}/kind" https://kind.sigs.k8s.io/dl/v0.23.0/kind-linux-amd64 + # Pinned by the projects' published SHA-256 sums (server#677). + curl --proto =https --tlsv1.2 -fsSL -o "${install_dir}/kind" https://kind.sigs.k8s.io/dl/v0.23.0/kind-linux-amd64 + echo "1d86e3069ffbe3da9f1a918618aecbc778e00c75f838882d0dfa2d363bc4a68c ${install_dir}/kind" | sha256sum --check --strict chmod +x "${install_dir}/kind" - curl -fsSL -o "${install_dir}/kubectl" https://dl.k8s.io/release/v1.29.9/bin/linux/amd64/kubectl + curl --proto =https --tlsv1.2 -fsSL -o "${install_dir}/kubectl" https://dl.k8s.io/release/v1.29.9/bin/linux/amd64/kubectl + echo "7b0de2466458cc3c12cf8742dc800c77d4fa72e831aa522df65e510d33b329e2 ${install_dir}/kubectl" | sha256sum --check --strict chmod +x "${install_dir}/kubectl" echo "${install_dir}" >> "${GITHUB_PATH}" diff --git a/.github/workflows/server-perf-soak.yml b/.github/workflows/server-perf-soak.yml index bf0e8548f..da97bc7c3 100644 --- a/.github/workflows/server-perf-soak.yml +++ b/.github/workflows/server-perf-soak.yml @@ -31,7 +31,7 @@ on: description: "Experimental HTTP runtime built from the exact published image" required: false type: choice - options: [apache, nginx-fpm, apache-event-fpm, frankenphp, swoole, openswoole] + options: [apache, nginx-fpm, apache-event-fpm] default: apache fixed_envelope: description: "Apply Server #137 fixed resource limits to every compared service" diff --git a/docs/perf-runner.md b/docs/perf-runner.md index 34d5840fb..941145431 100644 --- a/docs/perf-runner.md +++ b/docs/perf-runner.md @@ -138,11 +138,10 @@ Artifacts land in `build/perf/` by default. The script removes the Compose proje For the Server #137 HTTP-runtime comparison, dispatch `Server Perf Soak` on `main` with an exact `published_server_image` digest, `fixed_envelope=true`, -and `http_variant=apache`, `swoole`, or `openswoole`. Each dispatch uses the -same published application, queue worker, scheduler, SDK fixture, workload, -host plan, and 1-CPU/1-GiB HTTP cap. The Swoole variants build local Octane -images from the selected published digest with 12 request workers and a -500-request recycle limit; the official image remains the Apache baseline. +and `http_variant=apache`, `nginx-fpm`, or `apache-event-fpm`. Each dispatch +uses the same published application, queue worker, scheduler, SDK fixture, +workload, host plan, and 1-CPU/1-GiB HTTP cap; the official image remains the +Apache baseline. The Octane variants were removed in server#677. The fixed overlay pins MySQL, Redis, Composer and PHP image digests, caps the other services, and sets identical worker/query poll admission limits across variants. `server-image.json` records the actual HTTP diff --git a/scripts/conformance/server-parity/native-image-corpus.sh b/scripts/conformance/server-parity/native-image-corpus.sh index 539eb36ef..c49ad7e02 100755 --- a/scripts/conformance/server-parity/native-image-corpus.sh +++ b/scripts/conformance/server-parity/native-image-corpus.sh @@ -91,7 +91,7 @@ docker run --label "dev.durable-workflow.qualification.scope=$RESOURCE_SCOPE" -d "$image" -c 'while :; do php artisan workflow:v2:repair-pass --json; php artisan schedule:evaluate --limit=100 --json; sleep 1; done' >/dev/null mkdir -p parity-consumers -curl --fail --location --retry 3 --silent --output parity-consumers/dw.phar https://github.com/durable-workflow/cli/releases/download/2.2.0/dw.phar +curl --proto =https --proto-redir =https --fail --location --retry 3 --silent --output parity-consumers/dw.phar https://github.com/durable-workflow/cli/releases/download/2.2.0/dw.phar printf '%s %s\n' "$(jq -r '.visibility.cli.phar_sha256' tests/Fixtures/ServerParity/visibility-current-runs.json)" parity-consumers/dw.phar \ | sha256sum -c > parity-evidence/cli-artifact.txt export DW_PARITY_CLI_PHAR=/source/parity-consumers/dw.phar diff --git a/scripts/perf/Dockerfile.octane-frankenphp-experiment b/scripts/perf/Dockerfile.octane-frankenphp-experiment deleted file mode 100644 index 22ff52967..000000000 --- a/scripts/perf/Dockerfile.octane-frankenphp-experiment +++ /dev/null @@ -1,47 +0,0 @@ -ARG SERVER_IMAGE=durableworkflow/server@sha256:998b9dffe1f8effb159af9660ac190ccbc9d0c954900e68e9946baa6bb4c6c42 - -FROM composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee AS phpredis-source - -ARG PHPREDIS_VERSION=6.3.0 -ARG PHPREDIS_COMMIT=df4fab2de7fc327c54c94a13af2b9542e4fbd720 - -RUN git clone --depth 1 --branch "${PHPREDIS_VERSION}" https://github.com/phpredis/phpredis.git /phpredis \ - && test "$(git -C /phpredis rev-parse HEAD)" = "${PHPREDIS_COMMIT}" - -FROM ${SERVER_IMAGE} AS published - -FROM dunglas/frankenphp:php8.3@sha256:5918e1fce1e297eca32253a7dc95d5ab09ac156a127dc0eef354043d39ba97d3 - -RUN install-php-extensions pdo_mysql pdo_pgsql pcntl zip bcmath - -COPY --from=phpredis-source /phpredis /usr/src/php/ext/redis - -RUN apt-get update \ - && apt-get install -y --no-install-recommends $PHPIZE_DEPS \ - && docker-php-ext-install redis \ - && apt-get purge -y --auto-remove $PHPIZE_DEPS \ - && rm -rf /var/lib/apt/lists/* - -COPY --from=published /usr/bin/composer /usr/bin/composer -COPY --from=published /usr/local/bin/server-* /usr/local/bin/ -COPY --from=published /workflow/ /workflow/ -COPY --from=published /app/ /app/ - -WORKDIR /app - -# Composer trusts only the pinned Composer image's checked CA store here -# (GHSA-6q48-6c7w-9gw2); this base image's own store may hold a withdrawn root. -COPY --from=phpredis-source /etc/ssl/certs/ca-certificates.crt /etc/composer-trust/ca.pem -RUN install -d -m 0555 /etc/composer-trust/no-ca-directory -ENV COMPOSER_CAFILE=/etc/composer-trust/ca.pem COMPOSER_CAPATH=/etc/composer-trust/no-ca-directory -RUN composer require 'laravel/octane:2.20.0' 'laminas/laminas-diactoros:3.8.0' \ - 'symfony/psr-http-message-bridge:7.4.8' \ - --update-no-dev --no-interaction --no-progress --no-scripts \ - && php artisan package:discover --no-ansi \ - && php artisan octane:install --server=frankenphp --no-interaction - -ENV DW_WORKER_LONG_POLL_MAX_CONCURRENT=2 \ - DW_QUERY_TASK_POLL_MAX_CONCURRENT=1 - -ENTRYPOINT ["server-entrypoint"] -CMD ["php", "artisan", "octane:start", "--server=frankenphp", "--host=0.0.0.0", "--port=8080", "--workers=4"] diff --git a/scripts/perf/Dockerfile.octane-openswoole-experiment b/scripts/perf/Dockerfile.octane-openswoole-experiment deleted file mode 100644 index de56e02d2..000000000 --- a/scripts/perf/Dockerfile.octane-openswoole-experiment +++ /dev/null @@ -1,24 +0,0 @@ -ARG SERVER_IMAGE=durableworkflow/server@sha256:5d435d14fdbe3acc991ae591a155864f132cefbfbf1b53b724a8e7b006a91612 -FROM composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee AS checked-ca - -FROM ${SERVER_IMAGE} - -RUN apt-get update \ - && apt-get install -y --no-install-recommends $PHPIZE_DEPS libbrotli-dev libssl-dev pkg-config \ - && pecl install openswoole-26.2.0 \ - && docker-php-ext-enable openswoole \ - && rm -rf /var/lib/apt/lists/* - -# Composer trusts only the pinned Composer image's checked CA store here -# (GHSA-6q48-6c7w-9gw2); this base image's own store may hold a withdrawn root. -COPY --from=checked-ca /etc/ssl/certs/ca-certificates.crt /etc/composer-trust/ca.pem -RUN install -d -m 0555 /etc/composer-trust/no-ca-directory -ENV COMPOSER_CAFILE=/etc/composer-trust/ca.pem COMPOSER_CAPATH=/etc/composer-trust/no-ca-directory -RUN composer require 'laravel/octane:2.20.0' 'laminas/laminas-diactoros:3.8.0' \ - 'symfony/psr-http-message-bridge:7.4.8' \ - --update-no-dev --no-interaction --no-progress --no-scripts \ - && php artisan package:discover --no-ansi \ - && php artisan octane:install --server=swoole --no-interaction - -ENTRYPOINT ["server-entrypoint"] -CMD ["php", "artisan", "octane:start", "--server=swoole", "--host=0.0.0.0", "--port=8080", "--workers=4"] diff --git a/scripts/perf/Dockerfile.octane-swoole-experiment b/scripts/perf/Dockerfile.octane-swoole-experiment deleted file mode 100644 index 77e861720..000000000 --- a/scripts/perf/Dockerfile.octane-swoole-experiment +++ /dev/null @@ -1,24 +0,0 @@ -ARG SERVER_IMAGE=durableworkflow/server@sha256:4ee8a95df43fbd7022b4d1b583046063b9d3e6e2d1fd4408e58a6b8f486cff56 -FROM composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee AS checked-ca - -FROM ${SERVER_IMAGE} - -RUN apt-get update \ - && apt-get install -y --no-install-recommends $PHPIZE_DEPS libbrotli-dev libssl-dev pkg-config \ - && pecl install swoole-6.1.10 \ - && docker-php-ext-enable swoole \ - && rm -rf /var/lib/apt/lists/* - -# Composer trusts only the pinned Composer image's checked CA store here -# (GHSA-6q48-6c7w-9gw2); this base image's own store may hold a withdrawn root. -COPY --from=checked-ca /etc/ssl/certs/ca-certificates.crt /etc/composer-trust/ca.pem -RUN install -d -m 0555 /etc/composer-trust/no-ca-directory -ENV COMPOSER_CAFILE=/etc/composer-trust/ca.pem COMPOSER_CAPATH=/etc/composer-trust/no-ca-directory -RUN composer require 'laravel/octane:2.20.0' 'laminas/laminas-diactoros:3.8.0' \ - 'symfony/psr-http-message-bridge:7.4.8' \ - --update-no-dev --no-interaction --no-progress --no-scripts \ - && php artisan package:discover --no-ansi \ - && php artisan octane:install --server=swoole --no-interaction - -ENTRYPOINT ["server-entrypoint"] -CMD ["php", "artisan", "octane:start", "--server=swoole", "--host=0.0.0.0", "--port=8080", "--workers=4"] diff --git a/scripts/perf/octane-concurrent-isolation.compose.yml b/scripts/perf/octane-concurrent-isolation.compose.yml deleted file mode 100644 index 7222dfde6..000000000 --- a/scripts/perf/octane-concurrent-isolation.compose.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Disposable Server #137 multi-worker isolation fixture. -services: - server: - environment: - DW_ADMIN_TOKEN: probe-admin-token - DW_OPERATOR_TOKEN: probe-operator-token - DW_WORKER_TOKEN: probe-worker-token - DW_AUTH_BACKWARD_COMPATIBLE: 'false' - command: - - php - - artisan - - octane:start - - --server=swoole - - --host=0.0.0.0 - - --port=8080 - - --workers=4 - - --max-requests=50 diff --git a/scripts/perf/octane-concurrent-request-isolation.py b/scripts/perf/octane-concurrent-request-isolation.py deleted file mode 100644 index 0ff7dc8f2..000000000 --- a/scripts/perf/octane-concurrent-request-isolation.py +++ /dev/null @@ -1,118 +0,0 @@ -"""Bounded concurrent auth and namespace isolation probe for disposable #137 stacks.""" - -import concurrent.futures -import json -import os -import random -import secrets -import time -import urllib.error -import urllib.request - - -BASE = os.environ.get("DW_PROBE_URL", "").rstrip("/") -ADMIN = os.environ.get("DW_PROBE_TOKEN", "") -WORKER = os.environ.get("DW_PROBE_WORKER_TOKEN", "") -if not BASE or not ADMIN or not WORKER: - raise SystemExit("DW_PROBE_URL, DW_PROBE_TOKEN, and DW_PROBE_WORKER_TOKEN are required") - - -def request(token, method, path, namespace=None, body=None): - headers = { - "Accept": "application/json", - "Content-Type": "application/json", - "Authorization": f"Bearer {token}", - "X-Durable-Workflow-Control-Plane-Version": "2", - } - if namespace is not None: - headers["X-Namespace"] = namespace - payload = json.dumps(body).encode() if body is not None else None - req = urllib.request.Request(BASE + path, data=payload, headers=headers, method=method) - try: - with urllib.request.urlopen(req, timeout=15) as response: - status, raw = response.status, response.read() - except urllib.error.HTTPError as error: - status, raw = error.code, error.read() - try: - decoded = json.loads(raw) - except json.JSONDecodeError as error: - raise RuntimeError(f"{method} {path}: invalid JSON at HTTP {status}") from error - if not isinstance(decoded, dict): - raise RuntimeError(f"{method} {path}: response is not a JSON object") - return status, decoded - - -suffix = secrets.token_hex(5) -alpha, beta = "probe-alpha-" + suffix, "probe-beta-" + suffix -alpha_id, beta_id = "probe-alpha-workflow-" + suffix, "probe-beta-workflow-" + suffix -for namespace in (alpha, beta): - status, _ = request(ADMIN, "POST", "/api/namespaces", body={"name": namespace}) - if status != 201: - raise RuntimeError(f"create {namespace}: HTTP {status}") -for namespace, workflow_id in ((alpha, alpha_id), (beta, beta_id)): - status, response = request( - ADMIN, - "POST", - "/api/workflows", - namespace, - { - "workflow_id": workflow_id, - "workflow_type": "probe.concurrent-isolation", - "task_queue": "probe-isolation", - "input": [], - }, - ) - if status != 201 or response.get("namespace") != namespace: - raise RuntimeError(f"start {namespace}: HTTP {status}, wrong namespace") - - -def check(case): - label, token, namespace, workflow_id, expected_status, expected_namespace = case - started = time.monotonic() - status, response = request(token, "GET", "/api/workflows/" + workflow_id, namespace) - if status != expected_status: - raise RuntimeError(f"{label}: expected HTTP {expected_status}, received {status}") - if expected_namespace is not None and response.get("namespace") != expected_namespace: - raise RuntimeError(f"{label}: response escaped requested namespace") - return time.monotonic() - started - - -cases = [ - ("alpha own", ADMIN, alpha, alpha_id, 200, alpha), - ("beta own", ADMIN, beta, beta_id, 200, beta), - ("alpha cross", ADMIN, beta, alpha_id, 404, None), - ("beta cross", ADMIN, alpha, beta_id, 404, None), - ("alpha invalid", "invalid-probe-token", alpha, alpha_id, 401, None), - ("beta invalid", "invalid-probe-token", beta, beta_id, 401, None), - ("alpha worker", WORKER, alpha, alpha_id, 403, None), - ("beta worker", WORKER, beta, beta_id, 403, None), -] * 2 - -durations = [] -with concurrent.futures.ThreadPoolExecutor(max_workers=16) as pool: - for round_number in range(40): - shuffled = list(cases) - random.shuffle(shuffled) - try: - durations.extend(pool.map(check, shuffled)) - except Exception as error: - raise RuntimeError(f"concurrent round {round_number + 1}: {error}") from error - -for label, namespace, workflow_id in (("alpha final", alpha, alpha_id), ("beta final", beta, beta_id)): - status, response = request(ADMIN, "GET", "/api/workflows/" + workflow_id, namespace) - if status != 200 or response.get("namespace") != namespace: - raise RuntimeError(f"{label}: isolation lost after concurrent rounds") - -durations.sort() -print(json.dumps({ - "probe": "concurrent-request-isolation", - "namespaces": 2, - "created_workflows": 2, - "server_workers": 4, - "max_requests_per_worker": 50, - "rounds": 40, - "max_concurrent_clients": 16, - "checked_requests": len(durations), - "p95_response_seconds": round(durations[int(len(durations) * 0.95)], 3), - "result": "pass", -}, sort_keys=True)) diff --git a/scripts/perf/octane-frankenphp-experiment.compose.yml b/scripts/perf/octane-frankenphp-experiment.compose.yml deleted file mode 100644 index 6866d4608..000000000 --- a/scripts/perf/octane-frankenphp-experiment.compose.yml +++ /dev/null @@ -1,13 +0,0 @@ -services: - server: - image: ${COMPOSE_PROJECT_NAME:-dw}-octane-frankenphp:local - build: - context: ../.. - dockerfile: scripts/perf/Dockerfile.octane-frankenphp-experiment - args: - SERVER_IMAGE: ${DW_PROFILE_SERVER_IMAGE:?Set an exact published Server image digest} - healthcheck: - test: ["CMD", "curl", "--fail", "--silent", "http://127.0.0.1:8080/api/ready"] - interval: 10s - timeout: 5s - retries: 6 diff --git a/scripts/perf/octane-openswoole-experiment.compose.yml b/scripts/perf/octane-openswoole-experiment.compose.yml deleted file mode 100644 index 3f3b0e227..000000000 --- a/scripts/perf/octane-openswoole-experiment.compose.yml +++ /dev/null @@ -1,13 +0,0 @@ -services: - server: - image: ${COMPOSE_PROJECT_NAME:-dw}-octane-openswoole:local - build: - context: ../.. - dockerfile: scripts/perf/Dockerfile.octane-openswoole-experiment - args: - SERVER_IMAGE: ${DW_PROFILE_SERVER_IMAGE:?Set an exact published Server image digest} - healthcheck: - test: ["CMD", "curl", "--fail", "--silent", "http://127.0.0.1:8080/api/ready"] - interval: 10s - timeout: 5s - retries: 6 diff --git a/scripts/perf/octane-request-isolation.php b/scripts/perf/octane-request-isolation.php deleted file mode 100644 index 40bb93d90..000000000 --- a/scripts/perf/octane-request-isolation.php +++ /dev/null @@ -1,112 +0,0 @@ -} */ -function request(string $baseUrl, string $token, string $method, string $path, ?string $namespace = null, ?array $body = null): array -{ - $headers = [ - 'Accept: application/json', - 'Content-Type: application/json', - 'Authorization: Bearer '.$token, - 'X-Durable-Workflow-Control-Plane-Version: 2', - ]; - - if ($namespace !== null) { - $headers[] = 'X-Namespace: '.$namespace; - } - - $options = [ - 'method' => $method, - 'header' => implode("\r\n", $headers), - 'ignore_errors' => true, - 'timeout' => 10, - ]; - - if ($body !== null) { - $options['content'] = json_encode($body, JSON_THROW_ON_ERROR); - } - - $context = stream_context_create(['http' => $options]); - $response = @file_get_contents($baseUrl.$path, false, $context); - $responseHeaders = $http_response_header ?? []; - - if ($response === false || ! preg_match('/^HTTP\/\S+\s+(\d{3})/', $responseHeaders[0] ?? '', $match)) { - throw new RuntimeException('HTTP request failed: '.$method.' '.$path); - } - - $decoded = json_decode($response, true, 512, JSON_THROW_ON_ERROR); - - if (! is_array($decoded)) { - throw new RuntimeException('Expected a JSON object: '.$method.' '.$path); - } - - return ['status' => (int) $match[1], 'body' => $decoded]; -} - -function expectStatus(array $response, int $status, string $case): void -{ - if ($response['status'] !== $status) { - throw new RuntimeException($case.': expected HTTP '.$status.', received '.$response['status']); - } -} - -function expectNamespace(array $response, string $namespace, string $case): void -{ - expectStatus($response, 200, $case); - - if (($response['body']['namespace'] ?? null) !== $namespace) { - throw new RuntimeException($case.': response escaped the requested namespace'); - } -} - -$suffix = bin2hex(random_bytes(5)); -$alpha = 'probe-alpha-'.$suffix; -$beta = 'probe-beta-'.$suffix; -$alphaId = 'probe-alpha-workflow-'.$suffix; -$betaId = 'probe-beta-workflow-'.$suffix; - -foreach ([$alpha, $beta] as $namespace) { - expectStatus(request($baseUrl, $token, 'POST', '/api/namespaces', body: ['name' => $namespace]), 201, 'create '.$namespace); -} - -foreach ([[$alpha, $alphaId], [$beta, $betaId]] as [$namespace, $workflowId]) { - $started = request($baseUrl, $token, 'POST', '/api/workflows', $namespace, [ - 'workflow_id' => $workflowId, - 'workflow_type' => 'probe.request-isolation', - 'task_queue' => 'probe-isolation', - 'input' => [], - ]); - expectStatus($started, 201, 'start '.$namespace); - - if (($started['body']['namespace'] ?? null) !== $namespace) { - throw new RuntimeException('Workflow start escaped the requested namespace'); - } -} - -for ($round = 0; $round < 30; $round++) { - expectNamespace(request($baseUrl, $token, 'GET', '/api/workflows/'.$alphaId, $alpha), $alpha, 'alpha workflow'); - expectNamespace(request($baseUrl, $token, 'GET', '/api/workflows/'.$betaId, $beta), $beta, 'beta workflow'); - expectStatus(request($baseUrl, $token, 'GET', '/api/workflows/'.$alphaId, $beta), 404, 'beta cannot see alpha workflow'); - expectStatus(request($baseUrl, 'invalid-probe-token', 'GET', '/api/workflows/'.$alphaId, $alpha), 401, 'invalid token after success'); - expectStatus(request($baseUrl, $workerToken, 'GET', '/api/workflows/'.$alphaId, $alpha), 403, 'worker role after admin success'); - expectStatus(request($baseUrl, $token, 'GET', '/api/workflows/'.$betaId, $alpha), 404, 'alpha cannot see beta workflow'); - expectNamespace(request($baseUrl, $token, 'GET', '/api/workflows/'.$alphaId, $alpha), $alpha, 'valid token after rejection'); -} - -echo json_encode([ - 'probe' => 'persistent-request-isolation', - 'namespaces' => 2, - 'created_workflows' => 2, - 'rounds' => 30, - 'checked_requests' => 210, - 'result' => 'pass', -], JSON_THROW_ON_ERROR).PHP_EOL; diff --git a/scripts/perf/octane-single-worker-experiment.compose.yml b/scripts/perf/octane-single-worker-experiment.compose.yml deleted file mode 100644 index 1af2865fa..000000000 --- a/scripts/perf/octane-single-worker-experiment.compose.yml +++ /dev/null @@ -1,18 +0,0 @@ -# Force consecutive HTTP requests through one persistent PHP worker and recycle -# it frequently while checking request-scoped namespace and authentication state. -services: - server: - environment: - DW_ADMIN_TOKEN: probe-admin-token - DW_OPERATOR_TOKEN: probe-operator-token - DW_WORKER_TOKEN: probe-worker-token - DW_AUTH_BACKWARD_COMPATIBLE: 'false' - command: - - php - - artisan - - octane:start - - --server=${DW_PROFILE_OCTANE_SERVER:-frankenphp} - - --host=0.0.0.0 - - --port=8080 - - --workers=1 - - --max-requests=20 diff --git a/scripts/perf/octane-swoole-experiment.compose.yml b/scripts/perf/octane-swoole-experiment.compose.yml deleted file mode 100644 index e7f0822fd..000000000 --- a/scripts/perf/octane-swoole-experiment.compose.yml +++ /dev/null @@ -1,13 +0,0 @@ -services: - server: - image: ${COMPOSE_PROJECT_NAME:-dw}-octane-swoole:local - build: - context: ../.. - dockerfile: scripts/perf/Dockerfile.octane-swoole-experiment - args: - SERVER_IMAGE: ${DW_PROFILE_SERVER_IMAGE:?Set an exact published Server image digest} - healthcheck: - test: ["CMD", "curl", "--fail", "--silent", "http://127.0.0.1:8080/api/ready"] - interval: 10s - timeout: 5s - retries: 6 diff --git a/scripts/perf/poll-wait-octane-experiment.compose.yml b/scripts/perf/poll-wait-octane-experiment.compose.yml deleted file mode 100644 index 3d89ac70d..000000000 --- a/scripts/perf/poll-wait-octane-experiment.compose.yml +++ /dev/null @@ -1,14 +0,0 @@ -# Append only for disposable Octane long-poll experiments. -services: - server: - command: - - php - - artisan - - octane:start - - --server=${DW_PROFILE_OCTANE_SERVER:-frankenphp} - - --host=0.0.0.0 - - --port=8080 - - --workers=${DW_PROFILE_OCTANE_WORKERS:-12} - environment: - DW_WORKER_LONG_POLL_MAX_CONCURRENT: ${DW_PROFILE_POLL_WAIT_WORKER_LIMIT:?Set an experimental worker-poll limit} - DW_QUERY_TASK_POLL_MAX_CONCURRENT: ${DW_PROFILE_POLL_WAIT_QUERY_LIMIT:?Set an experimental query-poll limit} diff --git a/scripts/perf/run-server-soak.sh b/scripts/perf/run-server-soak.sh index 9a99b4080..11b2e8693 100755 --- a/scripts/perf/run-server-soak.sh +++ b/scripts/perf/run-server-soak.sh @@ -36,12 +36,12 @@ READINESS_DIAGNOSTICS="${DW_PERF_READINESS_DIAGNOSTICS:-0}" MYSQL_LOCK_DIAGNOSTICS="${DW_PERF_MYSQL_LOCK_DIAGNOSTICS:-0}" PUBLISHED_SERVER_IMAGE="${DW_PERF_PUBLISHED_SERVER_IMAGE:-}" HTTP_VARIANT="${DW_PERF_HTTP_VARIANT:-apache}" -PREBUILT_HTTP_IMAGE_ID="${DW_PERF_PREBUILT_HTTP_IMAGE_ID:-}" FIXED_ENVELOPE="${DW_PERF_FIXED_ENVELOPE:-0}" IMAGE_DISTRIBUTION_METRICS="${DW_PERF_IMAGE_DISTRIBUTION_METRICS:-0}" -if [[ "$HTTP_VARIANT" != apache && "$HTTP_VARIANT" != nginx-fpm && "$HTTP_VARIANT" != apache-event-fpm \ - && "$HTTP_VARIANT" != frankenphp && "$HTTP_VARIANT" != swoole && "$HTTP_VARIANT" != openswoole ]]; then - echo "DW_PERF_HTTP_VARIANT must be apache, nginx-fpm, apache-event-fpm, frankenphp, swoole, or openswoole." >&2 +# The Octane variants were removed with their Dockerfiles, whose PECL and +# extension downloads bypassed the checked CA stores (server#677). +if [[ "$HTTP_VARIANT" != apache && "$HTTP_VARIANT" != nginx-fpm && "$HTTP_VARIANT" != apache-event-fpm ]]; then + echo "DW_PERF_HTTP_VARIANT must be apache, nginx-fpm, or apache-event-fpm." >&2 exit 2 fi if [[ "$FIXED_ENVELOPE" != 0 && "$FIXED_ENVELOPE" != 1 ]]; then @@ -64,11 +64,6 @@ if [[ "$FIXED_ENVELOPE" == 1 && -z "$PUBLISHED_SERVER_IMAGE" ]]; then echo "The fixed comparison envelope requires an exact published Server image." >&2 exit 2 fi -if [[ -n "$PREBUILT_HTTP_IMAGE_ID" \ - && ( "$HTTP_VARIANT" != frankenphp || ! "$PREBUILT_HTTP_IMAGE_ID" =~ ^sha256:[0-9a-f]{64}$ ) ]]; then - echo "A prebuilt HTTP image ID requires FrankenPHP and an exact sha256 image ID." >&2 - exit 2 -fi if [[ "$READINESS_DIAGNOSTICS" != 0 && "$READINESS_DIAGNOSTICS" != 1 ]]; then echo "DW_PERF_READINESS_DIAGNOSTICS must be 0 or 1." >&2 exit 2 @@ -273,15 +268,7 @@ fi if [[ "$FIXED_ENVELOPE" == 1 ]]; then compose_files+=("$ROOT_DIR/scripts/perf/soak-fixed-envelope.compose.yml") fi -if [[ "$HTTP_VARIANT" == swoole || "$HTTP_VARIANT" == openswoole ]]; then - export DW_PROFILE_SERVER_IMAGE="$PUBLISHED_SERVER_IMAGE" - compose_files+=("$ROOT_DIR/scripts/perf/octane-${HTTP_VARIANT}-experiment.compose.yml") - compose_files+=("$ROOT_DIR/scripts/perf/soak-octane-experiment.compose.yml") -elif [[ "$HTTP_VARIANT" == frankenphp ]]; then - export DW_PROFILE_SERVER_IMAGE="$PUBLISHED_SERVER_IMAGE" - compose_files+=("$ROOT_DIR/scripts/perf/octane-frankenphp-experiment.compose.yml") - compose_files+=("$ROOT_DIR/scripts/perf/soak-frankenphp-experiment.compose.yml") -elif [[ "$HTTP_VARIANT" == nginx-fpm || "$HTTP_VARIANT" == apache-event-fpm ]]; then +if [[ "$HTTP_VARIANT" == nginx-fpm || "$HTTP_VARIANT" == apache-event-fpm ]]; then export DW_PROFILE_SERVER_IMAGE="$PUBLISHED_SERVER_IMAGE" compose_files+=("$ROOT_DIR/scripts/perf/fpm-experiment.compose.yml") if [[ "$HTTP_VARIANT" == apache-event-fpm ]]; then @@ -461,16 +448,7 @@ if [[ "$IMAGE_DISTRIBUTION_METRICS" == 1 ]]; then "${compose[@]}" pull mysql redis stack_started_ns="$(date +%s%N)" fi -if [[ -n "$PREBUILT_HTTP_IMAGE_ID" ]]; then - loaded_http_image_id="$(docker image inspect "${PROJECT}-octane-frankenphp:local" --format '{{.Id}}')" || loaded_http_image_id="" - if [[ "$loaded_http_image_id" != "$PREBUILT_HTTP_IMAGE_ID" ]]; then - write_environment_setup_failure 1 "prebuilt_http_image_identity" "transferred FrankenPHP image ID differs from the prebuilt image" - exit 1 - fi - "${compose[@]}" up -d --no-build --wait || setup_status=$? -else - "${compose[@]}" up -d --build --wait || setup_status=$? -fi +"${compose[@]}" up -d --build --wait || setup_status=$? if [[ "$IMAGE_DISTRIBUTION_METRICS" == 1 ]]; then stack_completed_ns="$(date +%s%N)" jq --argjson stack_setup_elapsed_ms "$(((stack_completed_ns - stack_started_ns) / 1000000))" \ @@ -519,23 +497,14 @@ if [[ -n "$PUBLISHED_SERVER_IMAGE" ]]; then if [[ "$service" == server && "$HTTP_VARIANT" != apache ]]; then if [[ "$HTTP_VARIANT" == nginx-fpm ]]; then expected_server_image="$(docker image inspect 'nginx@sha256:a8b39bd9cf0f83869a2162827a0caf6137ddf759d50a171451b335cecc87d236' --format '{{.Id}}')" - elif [[ "$HTTP_VARIANT" == apache-event-fpm ]]; then - expected_server_image="$(docker image inspect "${PROJECT}-apache-event-fpm:local" --format '{{.Id}}')" else - expected_server_image="$(docker image inspect "${PROJECT}-octane-${HTTP_VARIANT}:local" --format '{{.Id}}')" + expected_server_image="$(docker image inspect "${PROJECT}-apache-event-fpm:local" --format '{{.Id}}')" fi if [[ "$actual_image_id" != "$expected_server_image" ]]; then echo "Experimental HTTP server does not match the selected derived/proxy image." >&2 write_environment_setup_failure 1 "experimental_image_identity" "experimental HTTP image differs from selected derived/proxy image" exit 1 fi - if [[ "$HTTP_VARIANT" == swoole || "$HTTP_VARIANT" == openswoole ]]; then - expected_revision="$(docker inspect "${PROJECT}-${service}-1" --format '{{ index .Config.Labels "org.opencontainers.image.revision" }}')" - if [[ "$expected_revision" != "$server_source_sha" ]]; then - write_environment_setup_failure 1 "experimental_image_revision" "experimental HTTP image lacks selected published source revision" - exit 1 - fi - fi continue fi if [[ "$actual_image_id" != "$published_image_id" ]]; then @@ -555,11 +524,6 @@ if [[ "$HTTP_VARIANT" == nginx-fpm || "$HTTP_VARIANT" == apache-event-fpm ]]; th jq -n --arg base_image "$PUBLISHED_SERVER_IMAGE" --arg image_id "$actual_fpm_image" \ --arg php_version "$(docker exec "${PROJECT}-fpm-1" php -r 'echo PHP_VERSION;')" \ '{base_image:$base_image,image_id:$image_id,php_version:$php_version}' > "$ARTIFACT_DIR/fpm-image.json" -elif [[ "$HTTP_VARIANT" == frankenphp ]]; then - jq -n --arg base_image "$PUBLISHED_SERVER_IMAGE" \ - --arg image_id "$(docker inspect "${PROJECT}-server-1" --format '{{.Image}}')" \ - --arg php_version "$(docker exec "${PROJECT}-server-1" php -r 'echo PHP_VERSION;')" \ - '{base_image:$base_image,image_id:$image_id,php_version:$php_version}' > "$ARTIFACT_DIR/http-derived-image.json" fi http_image_id="$(docker inspect "${PROJECT}-server-1" --format '{{.Image}}')" jq --arg variant "$HTTP_VARIANT" --arg fixed_envelope "$FIXED_ENVELOPE" \ diff --git a/scripts/perf/run-vultr-soak.sh b/scripts/perf/run-vultr-soak.sh index 3d458336c..635a46ca4 100755 --- a/scripts/perf/run-vultr-soak.sh +++ b/scripts/perf/run-vultr-soak.sh @@ -50,9 +50,10 @@ if [[ -n "$PUBLISHED_SERVER_IMAGE" \ echo "DW_PERF_PUBLISHED_SERVER_IMAGE must be an exact durableworkflow/server sha256 digest." >&2 exit 2 fi -if [[ "$HTTP_VARIANT" != apache && "$HTTP_VARIANT" != nginx-fpm && "$HTTP_VARIANT" != apache-event-fpm \ - && "$HTTP_VARIANT" != frankenphp && "$HTTP_VARIANT" != swoole && "$HTTP_VARIANT" != openswoole ]]; then - echo "DW_PERF_HTTP_VARIANT must be apache, nginx-fpm, apache-event-fpm, frankenphp, swoole, or openswoole." >&2 +# The Octane variants were removed with their Dockerfiles, whose PECL and +# extension downloads bypassed the checked CA stores (server#677). +if [[ "$HTTP_VARIANT" != apache && "$HTTP_VARIANT" != nginx-fpm && "$HTTP_VARIANT" != apache-event-fpm ]]; then + echo "DW_PERF_HTTP_VARIANT must be apache, nginx-fpm, or apache-event-fpm." >&2 exit 2 fi if [[ "$FIXED_ENVELOPE" != 0 && "$FIXED_ENVELOPE" != 1 ]]; then @@ -147,35 +148,11 @@ cleanup() { status=1 fi - if [[ -n "${PREBUILT_HTTP_IMAGE_TAG:-}" ]]; then - docker image rm "$PREBUILT_HTTP_IMAGE_TAG" >/dev/null 2>&1 || true - fi rm -rf "$WORK_DIR" exit "$status" } trap cleanup EXIT INT TERM -PREBUILT_HTTP_IMAGE_ID="" -PREBUILT_HTTP_IMAGE_TAG="" -PREBUILT_HTTP_ARCHIVE_SHA="" -if [[ "$HTTP_VARIANT" == frankenphp ]]; then - if [[ -z "${DW_PERF_COMPOSE_PROJECT:-}" ]]; then - echo "FrankenPHP prebuild requires DW_PERF_COMPOSE_PROJECT." >&2 - exit 2 - fi - PREBUILT_HTTP_IMAGE_TAG="${DW_PERF_COMPOSE_PROJECT}-octane-frankenphp:local" - log "Building FrankenPHP image before creating a disposable host" - docker build \ - --file scripts/perf/Dockerfile.octane-frankenphp-experiment \ - --build-arg "SERVER_IMAGE=$PUBLISHED_SERVER_IMAGE" \ - --tag "$PREBUILT_HTTP_IMAGE_TAG" . - PREBUILT_HTTP_IMAGE_ID="$(docker image inspect "$PREBUILT_HTTP_IMAGE_TAG" --format '{{.Id}}')" - docker save --output "$WORK_DIR/http-image.tar" "$PREBUILT_HTTP_IMAGE_TAG" - PREBUILT_HTTP_ARCHIVE_SHA="$(sha256sum "$WORK_DIR/http-image.tar" | awk '{print $1}')" - log "Prepared FrankenPHP image $PREBUILT_HTTP_IMAGE_ID archive sha256:$PREBUILT_HTTP_ARCHIVE_SHA for transfer" -fi -export DW_PERF_PREBUILT_HTTP_IMAGE_ID="$PREBUILT_HTTP_IMAGE_ID" - controller_ip="$(curl --fail --silent --show-error https://api.ipify.org)" if [[ ! "$controller_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then echo "Unable to determine the GitHub runner IPv4 address." >&2 @@ -322,41 +299,6 @@ test "$(git rev-parse HEAD)" = "$sha" test -z "$(git status --porcelain --untracked-files=no)" REMOTE -if [[ -n "$PREBUILT_HTTP_IMAGE_ID" ]]; then - log "Transferring prebuilt FrankenPHP image archive" - scp "${ssh_options[@]}" "$WORK_DIR/http-image.tar" \ - "$SSH_USER@$INSTANCE_IP:http-image.tar" >/dev/null - remote_archive_sha="$(ssh "${ssh_options[@]}" "$SSH_USER@$INSTANCE_IP" \ - 'sha256sum "$HOME/http-image.tar"' | awk 'END {print $1}')" - if [[ "$remote_archive_sha" != "$PREBUILT_HTTP_ARCHIVE_SHA" ]]; then - printf 'Transferred FrankenPHP archive checksum mismatch: expected %s, got %s.\n' \ - "$PREBUILT_HTTP_ARCHIVE_SHA" "$remote_archive_sha" >&2 - exit 1 - fi - remote_http_image_id="$(ssh "${ssh_options[@]}" "$SSH_USER@$INSTANCE_IP" \ - bash -s -- "$PREBUILT_HTTP_IMAGE_TAG" <<'REMOTE' -set -euo pipefail -tag="$1" -trap 'rm -f "$HOME/http-image.tar"' EXIT -docker load --input "$HOME/http-image.tar" >/dev/null -docker image inspect "$tag" --format '{{.Id}}' -REMOTE -)" - if [[ ! "$remote_http_image_id" =~ ^sha256:[0-9a-f]{64}$ ]]; then - printf 'Unable to inspect the transferred FrankenPHP image tag: %q\n' \ - "$remote_http_image_id" >&2 - exit 1 - fi - log "Verified transferred archive sha256:$remote_archive_sha; loaded image ID $remote_http_image_id (builder ID $PREBUILT_HTTP_IMAGE_ID)" - jq -n --arg archive_sha256 "$remote_archive_sha" \ - --arg builder_image_id "$PREBUILT_HTTP_IMAGE_ID" \ - --arg loaded_image_id "$remote_http_image_id" \ - --arg image_tag "$PREBUILT_HTTP_IMAGE_TAG" \ - '{archive_sha256:$archive_sha256,builder_image_id:$builder_image_id,loaded_image_id:$loaded_image_id,image_tag:$image_tag}' \ - > "$(dirname "$PROVISION_LOG")/http-prebuilt-transfer.json" - export DW_PERF_PREBUILT_HTTP_IMAGE_ID="$remote_http_image_id" -fi - remote_env="$WORK_DIR/soak.env" : > "$remote_env" for name in \ @@ -365,7 +307,6 @@ for name in \ DW_PERF_READINESS_DIAGNOSTICS \ DW_PERF_PUBLISHED_SERVER_IMAGE \ DW_PERF_HTTP_VARIANT \ - DW_PERF_PREBUILT_HTTP_IMAGE_ID \ DW_PERF_FIXED_ENVELOPE \ DW_PERF_IMAGE_DISTRIBUTION_METRICS \ DW_PERF_NAMESPACES \ diff --git a/scripts/perf/soak-frankenphp-experiment.compose.yml b/scripts/perf/soak-frankenphp-experiment.compose.yml deleted file mode 100644 index 8c38348f1..000000000 --- a/scripts/perf/soak-frankenphp-experiment.compose.yml +++ /dev/null @@ -1,13 +0,0 @@ -services: - server: - build: - context: . - command: - - php - - artisan - - octane:start - - --server=frankenphp - - --host=0.0.0.0 - - --port=8080 - - --workers=12 - - --max-requests=500 diff --git a/scripts/perf/soak-octane-experiment.compose.yml b/scripts/perf/soak-octane-experiment.compose.yml deleted file mode 100644 index ca7816aad..000000000 --- a/scripts/perf/soak-octane-experiment.compose.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Append after the chosen Swoole/OpenSwoole image overlay for Server #137. -services: - server: - # This soak uses the repository-root Compose file first. Override the - # experiment overlay's relative context, which was written for a profile - # Compose file under scripts/perf/ as the first file. - build: - context: . - command: - - php - - artisan - - octane:start - - --server=swoole - - --host=0.0.0.0 - - --port=8080 - - --workers=12 - - --max-requests=500 diff --git a/scripts/perf/standard-workflow-profile.md b/scripts/perf/standard-workflow-profile.md index f8000b77a..76414ec7a 100644 --- a/scripts/perf/standard-workflow-profile.md +++ b/scripts/perf/standard-workflow-profile.md @@ -173,72 +173,7 @@ Remove this project and its synthetic durable state after recording evidence: docker compose down -v --remove-orphans ``` -For the experimental Octane/FrankenPHP candidate, append -`scripts/perf/octane-frankenphp-experiment.compose.yml` to the base profile and -fixed-envelope files. The derived image reuses the published Server application -and installs pinned Octane dependencies on PHP 8.3.33 ZTS; its PHP extensions -are built separately from the published Apache image. The candidate starts four -Octane workers by default. For eight concurrent idle polls, additionally append -`scripts/perf/poll-wait-octane-experiment.compose.yml` and set -`DW_PROFILE_POLL_WAIT_WORKER_LIMIT=6` and -`DW_PROFILE_POLL_WAIT_QUERY_LIMIT=2`; that overlay starts 12 workers, admitting -six workflow/activity polls and two query polls while leaving four workers for -ordinary requests. Keep the sum of the two caps below the available worker -count and record both values with each run. A queued request is not a successful -wait. A canary and idle-poll pass are only -setup evidence: persistent-worker state isolation, backend interruption, -repeatable throughput, and bounded memory still require qualification before -any runtime recommendation. - -To check request-scoped authentication and namespace state in one persistent -Octane worker, append `scripts/perf/octane-single-worker-experiment.compose.yml` -after the Octane overlay and start `server worker` in a fresh project. This -override configures synthetic role tokens, one HTTP worker, and recycling after -20 requests. It defaults to FrankenPHP. For the Swoole or OpenSwoole overlay, -set `DW_PROFILE_OCTANE_SERVER=swoole` when running `docker compose`; both use -Octane's Swoole server driver. Keep the published Server, MySQL and Redis image -digests and the fixed resource envelope the same across candidate fixtures. -Run the black-box probe from a separate PHP container on the project network: - -```sh -docker run --rm --user 1000:1000 --network "${COMPOSE_PROJECT_NAME}_default" \ - -e DW_PROBE_URL=http://server:8080 \ - -e DW_PROBE_TOKEN=probe-admin-token \ - -e DW_PROBE_WORKER_TOKEN=probe-worker-token \ - --mount "type=bind,source=$PWD/scripts/perf/octane-request-isolation.php,target=/probe.php,readonly" \ - php:8.3-cli php /probe.php -``` - -The probe creates disposable namespaces and pending workflows, then alternates -210 control-plane requests across namespace and credential boundaries. It must -report `result: pass`; any unexpected HTTP status or wrong-namespace response -exits nonzero. Remove the entire Compose project and its volumes afterward. - -For a bounded concurrent isolation check, use a new project with the same -published image digests and fixed envelope, replace the single-worker overlay -with `scripts/perf/octane-concurrent-isolation.compose.yml`, and retain the -selected Swoole or OpenSwoole overlay before it. The concurrent overlay sets -four HTTP workers, 50 requests per worker, and synthetic role tokens. For -example, with a unique `COMPOSE_PROJECT_NAME` and the three `DW_PROFILE_*_IMAGE` -digests set as above: - -```sh -export COMPOSE_FILE=scripts/perf/standard-workflow-profile.compose.yml:scripts/perf/standard-workflow-fixed-envelope.compose.yml:scripts/perf/octane-swoole-experiment.compose.yml:scripts/perf/octane-concurrent-isolation.compose.yml -docker compose up -d --build --wait server worker -docker run --rm --user 1000:1000 --network "${COMPOSE_PROJECT_NAME}_default" \ - -e DW_PROBE_URL=http://server:8080 \ - -e DW_PROBE_TOKEN=probe-admin-token \ - -e DW_PROBE_WORKER_TOKEN=probe-worker-token \ - --mount "type=bind,source=$PWD/scripts/perf/octane-concurrent-request-isolation.py,target=/probe.py,readonly" \ - python:3.12-alpine python /probe.py -docker compose down -v --remove-orphans -``` - -Repeat with `octane-openswoole-experiment.compose.yml` in a separate project. -The probe sends 16 simultaneous requests for 40 rounds across two namespaces, -mixing authorized, cross-namespace, invalid-token, and worker-role reads. It -checks all 640 statuses and returned namespaces, then verifies authorized reads -again. Inspect container health, restarts, and OOM state before cleanup. This -only tests a small synthetic request-isolation fixture; long polls, database -interruption, memory slope, sustained throughput, and full conformance still -need separate evidence. +The Octane (FrankenPHP, Swoole, OpenSwoole) experiments and their overlays and +probes were removed in server#677: their PECL and extension downloads bypassed +the checked CA stores, and the PHP Server they measured is retired. Their +earlier evidence stays in the issues that recorded it. diff --git a/tests/Unit/Support/composer-sinks.json b/tests/Unit/Support/composer-sinks.json index 365ec0184..643f29a66 100644 --- a/tests/Unit/Support/composer-sinks.json +++ b/tests/Unit/Support/composer-sinks.json @@ -1338,84 +1338,6 @@ } ] }, - { - "path": "scripts/perf/Dockerfile.octane-frankenphp-experiment", - "line": "FROM composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee AS phpredis-source", - "count": 1, - "confinement": "Pinned Composer 2.10.3 image; PinnedComposerImageTest checks its CA store.", - "guards": [ - { - "type": "pinned-reference", - "line": null, - "distance": null - } - ] - }, - { - "path": "scripts/perf/Dockerfile.octane-frankenphp-experiment", - "line": "RUN composer require 'laravel/octane:2.20.0' 'laminas/laminas-diactoros:3.8.0' \\", - "count": 1, - "confinement": "COMPOSER_CAFILE is the pinned image's checked store and COMPOSER_CAPATH an empty directory, set before the first package request.", - "guards": [ - { - "type": "checked-ca", - "line": "ENV COMPOSER_CAFILE=/etc/composer-trust/ca.pem COMPOSER_CAPATH=/etc/composer-trust/no-ca-directory", - "distance": 1 - } - ] - }, - { - "path": "scripts/perf/Dockerfile.octane-openswoole-experiment", - "line": "FROM composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee AS checked-ca", - "count": 1, - "confinement": "Pinned Composer 2.10.3 image; PinnedComposerImageTest checks its CA store.", - "guards": [ - { - "type": "pinned-reference", - "line": null, - "distance": null - } - ] - }, - { - "path": "scripts/perf/Dockerfile.octane-openswoole-experiment", - "line": "RUN composer require 'laravel/octane:2.20.0' 'laminas/laminas-diactoros:3.8.0' \\", - "count": 1, - "confinement": "COMPOSER_CAFILE is the pinned image's checked store and COMPOSER_CAPATH an empty directory, set before the first package request.", - "guards": [ - { - "type": "checked-ca", - "line": "ENV COMPOSER_CAFILE=/etc/composer-trust/ca.pem COMPOSER_CAPATH=/etc/composer-trust/no-ca-directory", - "distance": 1 - } - ] - }, - { - "path": "scripts/perf/Dockerfile.octane-swoole-experiment", - "line": "FROM composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee AS checked-ca", - "count": 1, - "confinement": "Pinned Composer 2.10.3 image; PinnedComposerImageTest checks its CA store.", - "guards": [ - { - "type": "pinned-reference", - "line": null, - "distance": null - } - ] - }, - { - "path": "scripts/perf/Dockerfile.octane-swoole-experiment", - "line": "RUN composer require 'laravel/octane:2.20.0' 'laminas/laminas-diactoros:3.8.0' \\", - "count": 1, - "confinement": "COMPOSER_CAFILE is the pinned image's checked store and COMPOSER_CAPATH an empty directory, set before the first package request.", - "guards": [ - { - "type": "checked-ca", - "line": "ENV COMPOSER_CAFILE=/etc/composer-trust/ca.pem COMPOSER_CAPATH=/etc/composer-trust/no-ca-directory", - "distance": 1 - } - ] - }, { "path": "scripts/perf/Dockerfile.sdk", "line": "FROM composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee AS dependencies", diff --git a/tests/Unit/Support/server_soak_test.py b/tests/Unit/Support/server_soak_test.py index 41e993722..dc11f557b 100644 --- a/tests/Unit/Support/server_soak_test.py +++ b/tests/Unit/Support/server_soak_test.py @@ -110,9 +110,6 @@ class RuntimeEvidenceConfigurationTest(unittest.TestCase): def test_installed_workflow_version_uses_php_service(self): for variant, service in ( ("apache", "server"), - ("swoole", "server"), - ("openswoole", "server"), - ("frankenphp", "server"), ("nginx-fpm", "fpm"), ("apache-event-fpm", "fpm"), ): @@ -146,12 +143,12 @@ def test_fpm_summary_counts_proxy_and_php_memory_together(self): self.assertEqual(80, resources["http"]["cpu_mean_percent"]) def test_soak_subprocesses_use_the_same_comparison_overlays(self) -> None: - with patch.dict(os.environ, {"DW_PERF_COMPOSE_FILES": "/tmp/base.yml:/tmp/fixed.yml:/tmp/swoole.yml"}): + with patch.dict(os.environ, {"DW_PERF_COMPOSE_FILES": "/tmp/base.yml:/tmp/fixed.yml:/tmp/overlay.yml"}): self.assertEqual( [ "docker", "compose", "-p", "fixture", "-f", "/tmp/base.yml", "-f", "/tmp/fixed.yml", - "-f", "/tmp/swoole.yml", "ps", "-q", "server", + "-f", "/tmp/overlay.yml", "ps", "-q", "server", ], server_soak.compose_command("fixture", "ps", "-q", "server"), ) diff --git a/tests/Unit/Support/vultr_soak_cleanup_test.py b/tests/Unit/Support/vultr_soak_cleanup_test.py index 5c3b82818..e2abded79 100644 --- a/tests/Unit/Support/vultr_soak_cleanup_test.py +++ b/tests/Unit/Support/vultr_soak_cleanup_test.py @@ -10,33 +10,24 @@ class DisposableRunnerCleanupTest(unittest.TestCase): - def test_frankenphp_build_failure_spends_no_provider_host(self): - with tempfile.TemporaryDirectory() as temporary: - root = Path(temporary) - docker = root / "docker" - docker.write_text("#!/bin/sh\nexit 17\n") - docker.chmod(0o755) - result = subprocess.run( - ["bash", str(SCRIPT)], - cwd=ROOT, - env={ - **os.environ, - "PATH": str(root) + os.pathsep + os.environ["PATH"], - "VULTR_API_KEY": "fixture-only", - "GITHUB_SHA": "a" * 40, - "GITHUB_RUN_ID": "1", - "GITHUB_RUN_ATTEMPT": "1", - "DW_PERF_HTTP_VARIANT": "frankenphp", - "DW_PERF_FIXED_ENVELOPE": "1", - "DW_PERF_COMPOSE_PROJECT": "fixture-frankenphp", - "DW_PERF_ARTIFACT_DIR": str(root / "artifacts"), - "DW_PERF_PUBLISHED_SERVER_IMAGE": "durableworkflow/server@sha256:" + "b" * 64, - }, - text=True, capture_output=True, timeout=5, - ) - self.assertEqual(17, result.returncode, result.stdout + result.stderr) - self.assertIn("Building FrankenPHP image before creating", result.stdout) - self.assertNotIn("Creating disposable Vultr instance", result.stdout) + def test_rejects_a_removed_octane_variant_before_provider_request(self): + result = subprocess.run( + ["bash", str(SCRIPT)], + env={ + **os.environ, + "VULTR_API_KEY": "fixture-only", + "GITHUB_SHA": "a" * 40, + "GITHUB_RUN_ID": "1", + "GITHUB_RUN_ATTEMPT": "1", + "DW_PERF_HTTP_VARIANT": "frankenphp", + "DW_PERF_FIXED_ENVELOPE": "1", + "DW_PERF_PUBLISHED_SERVER_IMAGE": "durableworkflow/server@sha256:" + "b" * 64, + }, + text=True, capture_output=True, timeout=5, + ) + self.assertEqual(2, result.returncode, result.stdout + result.stderr) + self.assertIn("must be apache, nginx-fpm, or apache-event-fpm", result.stderr) + self.assertNotIn("Creating disposable Vultr instance", result.stdout) def test_rejects_unpinned_server_image_before_provider_request(self): result = subprocess.run( @@ -64,7 +55,7 @@ def test_experimental_runtime_requires_fixed_published_tuple_before_provider_req "GITHUB_SHA": "a" * 40, "GITHUB_RUN_ID": "1", "GITHUB_RUN_ATTEMPT": "1", - "DW_PERF_HTTP_VARIANT": "swoole", + "DW_PERF_HTTP_VARIANT": "nginx-fpm", "DW_PERF_FIXED_ENVELOPE": "0", "DW_PERF_PUBLISHED_SERVER_IMAGE": "durableworkflow/server@sha256:" + "b" * 64, },