Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 121 additions & 19 deletions .github/workflows/server-parity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -318,13 +318,14 @@ jobs:
timeout-minutes: 60
strategy:
fail-fast: false
# Each database runs in three parts on separate runners: the native
# Each database runs in four parts on separate runners: the native
# database suites and TLS checks, the shared corpus with its profiles and
# restart cases, and the separate cohorts. Every part bootstraps the
# same database and native build; no part depends on another.
# restart cases, the published-SDK direction profiles, and the separate
# cohorts. Every part bootstraps the same database and native build; no
# part depends on another.
matrix:
label: [mysql-8.0, existing-php-mysql-matrix, mariadb-10.11]
part: [native-tests, corpus, cohorts]
part: [native-tests, corpus, cohorts, sdk-directions]
include:
- label: mysql-8.0
image: mirror.gcr.io/library/mysql@sha256:62fb722c78b24245ddff1796a0fcee4a49cc5b87e0aaaf20c92d1da9e0a2497b
Expand Down Expand Up @@ -607,8 +608,6 @@ jobs:
bash scripts/conformance/server-parity/legacy-marker-profile.sh mysql http://parity-php:8080 http://parity-native-a:8080
bash scripts/conformance/server-parity/range-repair-profile.sh mysql http://parity-native-a:8080
bash scripts/conformance/server-parity/range-repair-profile.sh mysql http://parity-native-a:8080 pending-native
# Every SDK direction profile, in three concurrent lanes.
bash scripts/conformance/server-parity/sdk-direction-lanes.sh mysql http://parity-native-a:8080
docker run --rm --user=1000:1000 --network "$RESOURCE_SCOPE" --entrypoint sh \
-v "$PWD:/source:ro" -v "$PWD/parity-evidence:/evidence" -w /source \
-e DW_PARITY_TOKEN=parity-test-token "$image" -ec '
Expand Down Expand Up @@ -678,6 +677,39 @@ jobs:
# check has run, so one difference cannot hide the rest.
test "$native_record_status" -eq 0
test "$compare_status" -eq 0
- name: Compare every published-SDK direction profile in three concurrent lanes
if: ${{ matrix.part == 'sdk-directions' }}
run: |
set -euo pipefail
# Bounded container log tails make a failure in this step diagnosable from its own log.
container_log_tails() {
bash scripts/conformance/server-parity/container-log-tails.sh
}
trap container_log_tails ERR
image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.1.json)
docker run --rm --user="$(id -u):$(id -g)" --entrypoint composer \
-e COMPOSER_HOME=/tmp/composer -v "$PWD:/source" -w /source "$image" \
install --working-dir=scripts/conformance/server-parity --no-interaction --no-progress
# Each profile brings its own published PHP and embedded databases and
# shares this part's native node, as the profiles shared the corpus's.
docker run -d --name "$RESOURCE_SCOPE-native-a" --network "$RESOURCE_SCOPE" --network-alias=parity-native-a \
--user="$(id -u):$(id -g)" --cpus=1 --memory=256m --memory-swap=256m \
-e DW_RUST_EXPERIMENTAL=1 -e DW_AUTH_TOKEN=parity-test-token -e DW_BIND_ADDRESS=0.0.0.0:8080 \
-e DB_CONNECTION=mysql -e DB_HOST=parity-mysql -e DB_DATABASE=native_ref \
-e DB_USERNAME=root -e DB_PASSWORD=parity-disposable-password \
-v "$PWD/rust-target:/target:ro" --tmpfs /state --entrypoint /target/debug/durable-workflow-server \
mirror.gcr.io/library/rust@sha256:ba81bc3eaa4422af576c0262515d96b0111a628a6ccc2c86557cf55c9a4bbee0
ready=0
for attempt in $(seq 1 30); do
if docker run --rm --user=1000:1000 --network "$RESOURCE_SCOPE" --entrypoint curl "$image" \
-fsS http://parity-native-a:8080/api/ready > parity-evidence/sdk-native-readiness.json; then
ready=1
break
fi
sleep 1
done
test "$ready" = 1
bash scripts/conformance/server-parity/sdk-direction-lanes.sh mysql http://parity-native-a:8080
- name: Start the shared native node the cohorts use
id: prepare_cohorts
if: ${{ matrix.part == 'cohorts' }}
Expand Down Expand Up @@ -862,13 +894,14 @@ jobs:
timeout-minutes: 60
strategy:
fail-fast: false
# Each database runs in three parts on separate runners: the native
# Each database runs in four parts on separate runners: the native
# database suites and TLS checks, the shared corpus with its profiles and
# restart cases, and the separate cohorts. Every part bootstraps the
# same database and native build; no part depends on another.
# restart cases, the published-SDK direction profiles, and the separate
# cohorts. Every part bootstraps the same database and native build; no
# part depends on another.
matrix:
label: [existing-php-matrix, postgres-17]
part: [native-tests, corpus, cohorts]
part: [native-tests, corpus, cohorts, sdk-directions]
include:
- label: existing-php-matrix
image: mirror.gcr.io/library/postgres@sha256:f1c3376c26f2609ab9f29f71f824103fe2fcd8ee0346485cb6122a4f93df6f94
Expand Down Expand Up @@ -1118,8 +1151,6 @@ jobs:
bash scripts/conformance/server-parity/legacy-marker-profile.sh pgsql http://parity-php:8080 http://parity-native-a:8080
bash scripts/conformance/server-parity/range-repair-profile.sh pgsql http://parity-native-a:8080
bash scripts/conformance/server-parity/range-repair-profile.sh pgsql http://parity-native-a:8080 pending-native
# Every SDK direction profile, in three concurrent lanes.
bash scripts/conformance/server-parity/sdk-direction-lanes.sh pgsql http://parity-native-a:8080
docker run --rm --user=1000:1000 --network "$RESOURCE_SCOPE" --entrypoint sh \
-v "$PWD:/source:ro" -v "$PWD/parity-evidence:/evidence" -w /source \
-e DW_PARITY_TOKEN=parity-test-token "$image" -ec '
Expand Down Expand Up @@ -1189,6 +1220,39 @@ jobs:
# check has run, so one difference cannot hide the rest.
test "$native_record_status" -eq 0
test "$compare_status" -eq 0
- name: Compare every published-SDK direction profile in three concurrent lanes
if: ${{ matrix.part == 'sdk-directions' }}
run: |
set -euo pipefail
# Bounded container log tails make a failure in this step diagnosable from its own log.
container_log_tails() {
bash scripts/conformance/server-parity/container-log-tails.sh
}
trap container_log_tails ERR
image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.1.json)
docker run --rm --user="$(id -u):$(id -g)" --entrypoint composer \
-e COMPOSER_HOME=/tmp/composer -v "$PWD:/source" -w /source "$image" \
install --working-dir=scripts/conformance/server-parity --no-interaction --no-progress
# Each profile brings its own published PHP and embedded databases and
# shares this part's native node, as the profiles shared the corpus's.
docker run -d --name "$RESOURCE_SCOPE-native-a" --network "$RESOURCE_SCOPE" --network-alias=parity-native-a \
--user="$(id -u):$(id -g)" --cpus=1 --memory=256m --memory-swap=256m \
-e DW_RUST_EXPERIMENTAL=1 -e DW_AUTH_TOKEN=parity-test-token -e DW_BIND_ADDRESS=0.0.0.0:8080 \
-e DB_CONNECTION=pgsql -e DB_HOST=parity-pg -e DB_DATABASE=native_ref \
-e DB_USERNAME=parity -e DB_PASSWORD=parity-disposable-password \
-v "$PWD/rust-target:/target:ro" --tmpfs /state --entrypoint /target/debug/durable-workflow-server \
mirror.gcr.io/library/rust@sha256:ba81bc3eaa4422af576c0262515d96b0111a628a6ccc2c86557cf55c9a4bbee0
ready=0
for attempt in $(seq 1 30); do
if docker run --rm --user=1000:1000 --network "$RESOURCE_SCOPE" --entrypoint curl "$image" \
-fsS http://parity-native-a:8080/api/ready > parity-evidence/sdk-native-readiness.json; then
ready=1
break
fi
sleep 1
done
test "$ready" = 1
bash scripts/conformance/server-parity/sdk-direction-lanes.sh pgsql http://parity-native-a:8080
- name: Start the shared native node the cohorts use
id: prepare_cohorts
if: ${{ matrix.part == 'cohorts' }}
Expand Down Expand Up @@ -1417,12 +1481,12 @@ jobs:
timeout-minutes: 60
strategy:
fail-fast: false
# Three parts on separate runners: the native suites and codec, the
# shared corpus with its profiles and restart cases, and the separate
# cohorts. Every part builds the same native server; no part depends on
# another.
# Four parts on separate runners: the native suites and codec, the
# shared corpus with its profiles and restart cases, the published-SDK
# direction profiles, and the separate cohorts. Every part builds the
# same native server; no part depends on another.
matrix:
part: [native-tests, corpus, cohorts]
part: [native-tests, corpus, cohorts, sdk-directions]
env:
DW_PARITY_PART: ${{ matrix.part }}
steps:
Expand Down Expand Up @@ -1635,8 +1699,6 @@ jobs:
bash scripts/conformance/server-parity/legacy-marker-profile.sh sqlite http://parity-server:8080 http://parity-rust:8080
bash scripts/conformance/server-parity/range-repair-profile.sh sqlite http://parity-rust:8080
bash scripts/conformance/server-parity/range-repair-profile.sh sqlite http://parity-rust:8080 pending-native
# Every SDK direction profile, in three concurrent lanes.
bash scripts/conformance/server-parity/sdk-direction-lanes.sh sqlite http://parity-rust:8080
docker run --rm --user=1000:1000 --entrypoint php --network "$RESOURCE_SCOPE" \
-v "$PWD:/source:ro" -v "$PWD/parity-evidence:/evidence" -w /source \
-e DW_PARITY_TOKEN=parity-test-token "$image" scripts/conformance/server-parity/restart.php \
Expand Down Expand Up @@ -1705,6 +1767,46 @@ jobs:
# check has run, so one difference cannot hide the rest.
test "$native_record_status" -eq 0
test "$compare_status" -eq 0
- name: Compare every published-SDK direction profile in three concurrent lanes
if: ${{ matrix.part == 'sdk-directions' }}
env:
RESOURCE_SCOPE: dw-parity-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}
run: |
set -euo pipefail
# Bounded container log tails make a failure in this step diagnosable from its own log.
container_log_tails() {
bash scripts/conformance/server-parity/container-log-tails.sh
}
trap container_log_tails ERR
image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.1.json)
docker run --rm --user="$(id -u):$(id -g)" --entrypoint composer \
-e COMPOSER_HOME=/tmp/composer -v "$PWD:/source" -w /source "$image" \
install --working-dir=scripts/conformance/server-parity --no-interaction --no-progress
mkdir -p parity-state parity-evidence
# The profiles' published PHP containers run as 1000:1000; grant that
# user alone, not everyone.
setfacl -m u:1000:rwx,d:u:1000:rwx parity-state parity-evidence
docker network create "$RESOURCE_SCOPE"
# Each profile brings its own published PHP and embedded databases and
# shares this part's native node, as the profiles shared the corpus's.
docker run -d --name "$RESOURCE_SCOPE-rust" --network "$RESOURCE_SCOPE" --network-alias=parity-rust \
--user="$(id -u):$(id -g)" --cpus=1 --memory=256m --memory-swap=256m \
-e DW_RUST_EXPERIMENTAL=1 -e DW_AUTH_TOKEN=parity-test-token \
-e DB_DATABASE=/state/rust.sqlite -e DW_BIND_ADDRESS=0.0.0.0:8080 \
-v "$PWD/rust-target:/target:ro" -v "$PWD/parity-state:/state" \
--entrypoint /target/debug/durable-workflow-server \
mirror.gcr.io/library/rust@sha256:ba81bc3eaa4422af576c0262515d96b0111a628a6ccc2c86557cf55c9a4bbee0
ready=0
for attempt in $(seq 1 30); do
if docker run --rm --user=1000:1000 --network "$RESOURCE_SCOPE" --entrypoint curl "$image" \
-fsS http://parity-rust:8080/api/ready > parity-evidence/sdk-native-readiness.json; then
ready=1
break
fi
sleep 1
done
test "$ready" = 1
bash scripts/conformance/server-parity/sdk-direction-lanes.sh sqlite http://parity-rust:8080
- name: Start the shared native node the cohorts use
id: prepare_cohorts
if: ${{ matrix.part == 'cohorts' }}
Expand Down
9 changes: 6 additions & 3 deletions scripts/conformance/server-parity/sdk-direction-lanes.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ lanes=(
"timer:sdk-timers activity:sdk-directions query:sdk-queries"
)
run_lane() {
local lane=$1 entry variant tag status
local lane=$1 entry variant tag status started
for entry in $lane; do
variant=${entry%%:*} tag=${entry#*:} status=0
variant=${entry%%:*} tag=${entry#*:} status=0 started=$SECONDS
bash scripts/conformance/server-parity/sdk-direction-profile.sh "$backend" "$native_url" "$variant" \
> "$evidence/$tag.log" 2>&1 || status=$?
printf '%s\n' "$status" > "$evidence/$tag.status"
# Each profile's duration, so lane balance can be measured from the log.
printf '%s\n' "$((SECONDS - started))" > "$evidence/$tag.seconds"
# The profile's status is already kept; a failed stop leaves the server to
# the final cleanup.
docker stop --time 30 "$RESOURCE_SCOPE-$tag-server" >/dev/null 2>&1 \
Expand All @@ -48,7 +50,8 @@ for lane in "${lanes[@]}"; do
for entry in $lane; do
tag=${entry#*:}
status=$(cat "$evidence/$tag.status" 2>/dev/null || echo missing)
echo "::group::$tag (exit $status)"
seconds=$(cat "$evidence/$tag.seconds" 2>/dev/null || echo unknown)
echo "::group::$tag (exit $status, $seconds s)"
cat "$evidence/$tag.log"
echo "::endgroup::"
if [[ "$status" != 0 ]]; then
Expand Down
13 changes: 9 additions & 4 deletions tests/Unit/ServerParityPartsTest.mjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// Each Shared Server fixtures database job runs in three parts on separate
// Each Shared Server fixtures database job runs in four parts on separate
// runners. Every step either prepares the part (and so runs in every part) or
// belongs to exactly one part, so no check runs twice or not at all.
import test from 'node:test';
import assert from 'node:assert/strict';
import {readFileSync} from 'node:fs';

const workflow = readFileSync('.github/workflows/server-parity.yml', 'utf8');
const PARTS = ['native-tests', 'corpus', 'cohorts'];
const PARTS = ['native-tests', 'corpus', 'cohorts', 'sdk-directions'];
const EVERY_PART = [/^actions\/checkout@/, /^Fetch /, /^Create TLS fixture certificates$/, /^Bootstrap independent /,
/^Collect /, /^Summarize /, /^Retain /];

Expand Down Expand Up @@ -39,9 +39,9 @@ function partsOf(step) {

for (const [name, key] of [['mysql-execution', 'Compare three runtimes'], ['postgres-storage', 'Compare three runtimes'],
['execution-and-codec', 'Execute and compare Rust']]) {
test(`${name} runs every check in exactly one of three parts`, () => {
test(`${name} runs every check in exactly one of four parts`, () => {
const block = job(name);
assert.match(block, /^ {8}part: \[native-tests, corpus, cohorts\]$/m);
assert.match(block, /^ {8}part: \[native-tests, corpus, cohorts, sdk-directions\]$/m);
assert.match(block, /^ {6}DW_PARITY_PART: \$\{\{ matrix\.part \}\}$/m);
assert.match(block, /^ {4}timeout-minutes: 60$/m);
const all = steps(block);
Expand All @@ -55,6 +55,11 @@ for (const [name, key] of [['mysql-execution', 'Compare three runtimes'], ['post
}
const inPart = part => all.filter(step => partsOf(step).length === 1 && partsOf(step)[0] === part).map(step => step.name);
assert.ok(inPart('corpus').some(step => step.startsWith(key)), `${name}: the corpus comparison is in the corpus part`);
// The published-SDK direction lanes run once, in their own part, so the
// corpus part no longer waits for them.
assert.deepEqual(inPart('sdk-directions'), ['Compare every published-SDK direction profile in three concurrent lanes'], name);
const lanes = all.filter(step => step.text.includes('sdk-direction-lanes.sh')).map(step => step.name);
assert.deepEqual(lanes, ['Compare every published-SDK direction profile in three concurrent lanes'], `${name}: lanes run in one step`);
const cohorts = inPart('cohorts');
for (const cohort of ['Start the shared native node the cohorts use', 'Compare separate configured role-token cohort',
'Back up, take over and resume a separate published PHP upgrade source',
Expand Down
6 changes: 3 additions & 3 deletions tests/Unit/ServerParitySdkDirectionLanesTest.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ test('all nine profiles pass and each PHP server is stopped after its profile',
assert.deepEqual(new Set(Object.values(statuses)), new Set(['0']));
assertEveryServerRetired(events);
for (const [variant, tag] of Object.entries(PROFILES)) {
assert.match(child.stdout, new RegExp(`::group::${tag} \\(exit 0\\)\\nprofile output for ${variant}\\n::endgroup::`));
assert.match(child.stdout, new RegExp(`::group::${tag} \\(exit 0, \\d+ s\\)\\nprofile output for ${variant}\\n::endgroup::`));
}
assert.doesNotMatch(child.stderr, /::error::|::warning::/);
});
Expand All @@ -104,8 +104,8 @@ test('a failed profile still runs its lane and fails the wrapper only after ever
assert.ok(started(events, 'activity') > events.indexOf('end timer'), 'the lane continues after a failure');
assert.match(child.stderr, /^::error::SDK direction profile sdk-timers exited 7$/m);
assert.equal(child.stderr.match(/::error::/g).length, 1);
assert.match(child.stdout, /::group::sdk-timers \(exit 7\)\nprofile output for timer\n::endgroup::/);
assert.match(child.stdout, /::group::sdk-queries \(exit 0\)/, 'logs after the failure are still printed');
assert.match(child.stdout, /::group::sdk-timers \(exit 7, \d+ s\)\nprofile output for timer\n::endgroup::/);
assert.match(child.stdout, /::group::sdk-queries \(exit 0, \d+ s\)/, 'logs after the failure are still printed');
});

test('a failed stop warns without changing any profile status', () => {
Expand Down
Loading