fix(ci): wait for live service readiness

This commit is contained in:
whyour
2026-08-22 17:32:47 +08:00
parent 1250555881
commit 35efa1f33e
4 changed files with 99 additions and 7 deletions
@@ -1098,6 +1098,36 @@ function recoveryResources(fixture, jobName) {
]; ];
} }
function waitForPostgresService(timeoutMs = 60_000) {
const deadline = Date.now() + timeoutMs;
let lastFact = 'probe not started';
while (Date.now() < deadline) {
const probe = kubectl(
[
'-n',
NAMESPACE,
'exec',
`pod/${POSTGRES_NAME}`,
'--',
'pg_isready',
'--host',
POSTGRES_NAME,
'--port',
'5432',
'--username',
'postgres',
'--dbname',
'qinglong',
],
{ capture: true, quiet: true, allowFailure: true },
);
if (probe.status === 0) return;
lastFact = (probe.stderr || probe.stdout || `status ${probe.status}`).trim();
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1_000);
}
fail(`PostgreSQL Service was not ready after ${timeoutMs}ms: ${lastFact}`);
}
function waitForJob( function waitForJob(
name, name,
expectedStatus = 'complete', expectedStatus = 'complete',
@@ -1795,6 +1825,7 @@ async function main(argv = process.argv.slice(2)) {
], ],
{ capture: true, quiet: true }, { capture: true, quiet: true },
); );
waitForPostgresService();
kubectl( kubectl(
[ [
'-n', '-n',
@@ -1074,6 +1074,30 @@ request.on('timeout',()=>request.destroy(Object.assign(new Error('timeout'),{cod
return evidence; return evidence;
} }
async function retryProviderEvidence(read, pause = undefined) {
assert.equal(typeof read, 'function');
assert.ok(pause === undefined || typeof pause === 'function');
let lastError;
for (let attempt = 1; attempt <= 3; attempt += 1) {
try {
return await read();
} catch (error) {
lastError = error;
if (
!/\b(?:ECONNREFUSED|ECONNRESET|ETIMEDOUT|TIMEOUT)\b/.test(
error instanceof Error ? error.message : String(error),
) ||
attempt === 3
) {
throw error;
}
await (pause ??
(() => new Promise((resolve) => setTimeout(resolve, 500))))();
}
}
throw lastError;
}
async function waitForExecutorProviderEgress({ async function waitForExecutorProviderEgress({
fixture, fixture,
adminImage, adminImage,
@@ -1606,14 +1630,16 @@ async function main() {
let observedProviderRequests = 0; let observedProviderRequests = 0;
let evidenceSequence = 0; let evidenceSequence = 0;
const observeProvider = async (pod, sourceNodeName) => { const observeProvider = async (pod, sourceNodeName) => {
evidenceSequence += 1;
applyExecutorNetworkPolicy(fixture, pod.status.podIP); applyExecutorNetworkPolicy(fixture, pod.status.podIP);
const evidence = await providerEvidence({ const evidence = await retryProviderEvidence(async () => {
fixture, evidenceSequence += 1;
adminImage, return providerEvidence({
name: `ql3-provider-evidence-${evidenceSequence}`, fixture,
nodeName: sourceNodeName, adminImage,
providerPodIp: pod.status.podIP, name: `ql3-provider-evidence-${evidenceSequence}`,
nodeName: sourceNodeName,
providerPodIp: pod.status.podIP,
});
}); });
const count = evidence.requestCount; const count = evidence.requestCount;
const observationKey = providerObservationKey(pod); const observationKey = providerObservationKey(pod);
@@ -2064,5 +2090,6 @@ module.exports = {
executorJob, executorJob,
providerObservationKey, providerObservationKey,
providerServerSource, providerServerSource,
retryProviderEvidence,
terminalJobSnapshot, terminalJobSnapshot,
}; };
@@ -72,6 +72,16 @@ test('report migration evidence follows the complete PostgreSQL stream', () => {
); );
}); });
test('waits for PostgreSQL Service connectivity before migration', () => {
const readyProbe = live.indexOf('waitForPostgresService();');
const migration = live.indexOf(
"apply(migrationJob(), 'create reviewed migration Job')",
);
assert.ok(readyProbe > 0);
assert.ok(migration > readyProbe);
assert.match(live, /'pg_isready',[\s\S]*'--host',[\s\S]*POSTGRES_NAME/);
});
test('fixture locks are bound to durable version-three approval dispatches', () => { test('fixture locks are bound to durable version-three approval dispatches', () => {
const value = createFixture({ const value = createFixture({
registry: 'registry.fixture.test', registry: 'registry.fixture.test',
@@ -14,6 +14,7 @@ const {
executorJob, executorJob,
providerObservationKey, providerObservationKey,
providerServerSource, providerServerSource,
retryProviderEvidence,
terminalJobSnapshot, terminalJobSnapshot,
} = require('../../scripts/ql3-provider-credential-test-kubernetes-live-contract.cjs'); } = require('../../scripts/ql3-provider-credential-test-kubernetes-live-contract.cjs');
const { const {
@@ -363,6 +364,29 @@ test('starts a fresh provider request baseline after a container restart', () =>
assert.equal(providerObservationKey(pod), 'provider-uid:1'); assert.equal(providerObservationKey(pod), 'provider-uid:1');
}); });
test('retries only bounded transient provider evidence failures', async () => {
let attempts = 0;
const evidence = await retryProviderEvidence(
async () => {
attempts += 1;
if (attempts < 3) throw new Error('{"code":"ECONNREFUSED"}');
return { requestCount: 1 };
},
async () => {},
);
assert.deepEqual(evidence, { requestCount: 1 });
assert.equal(attempts, 3);
await assert.rejects(
retryProviderEvidence(
async () => {
throw new Error('invalid evidence schema');
},
async () => {},
),
/invalid evidence schema/,
);
});
test('provider fixture logs only generation and authorization decision', () => { test('provider fixture logs only generation and authorization decision', () => {
const source = providerServerSource(); const source = providerServerSource();
assert.match(source, /event:'provider_request',generation,allowed/); assert.match(source, /event:'provider_request',generation,allowed/);