mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
fix(ci): align recovery gate with durable admission
This commit is contained in:
@@ -42,8 +42,7 @@ function validReport() {
|
||||
'postgres@sha256:1961f96e6029a02c3812d7cb329a3b03a3ac2bb067058dec17b0f5596aca9296',
|
||||
migrationImageId: `docker://${admin}`,
|
||||
initialRecoveryImageId: `docker://${admin}`,
|
||||
stageRecoveryImageId: `docker://${admin}`,
|
||||
rejectionRecoveryImageId: `docker://${admin}`,
|
||||
upgradeRecoveryImageId: `docker://${admin}`,
|
||||
postgresImageId: `containerd://sha256:${'e'.repeat(64)}`,
|
||||
},
|
||||
ordering: {
|
||||
@@ -51,22 +50,13 @@ function validReport() {
|
||||
migrationCompletedAt: '2026-08-14T08:00:01.000Z',
|
||||
initialRecoveryJobUid: '00000000-0000-4000-8000-000000000002',
|
||||
initialRecoveryCompletedAt: '2026-08-14T08:00:02.000Z',
|
||||
upgradeStageJobUid: '00000000-0000-4000-8000-000000000003',
|
||||
upgradeStageFailedAt: '2026-08-14T08:00:03.000Z',
|
||||
transitionJobUid: '00000000-0000-4000-8000-000000000004',
|
||||
transitionCompletedAt: '2026-08-14T08:00:04.000Z',
|
||||
rejectionRecoveryJobUid: '00000000-0000-4000-8000-000000000005',
|
||||
rejectionRecoveryCompletedAt: '2026-08-14T08:00:05.000Z',
|
||||
runtimeCreatedAt: '2026-08-14T08:00:06.000Z',
|
||||
runtimeBoundRecoveryJobUid: '00000000-0000-4000-8000-000000000005',
|
||||
upgradeRecoveryJobUid: '00000000-0000-4000-8000-000000000003',
|
||||
upgradeRecoveryCompletedAt: '2026-08-14T08:00:03.000Z',
|
||||
runtimeCreatedAt: '2026-08-14T08:00:04.000Z',
|
||||
runtimeBoundRecoveryJobUid: '00000000-0000-4000-8000-000000000003',
|
||||
},
|
||||
failedUpgrade: {
|
||||
stageFailure: {
|
||||
jobUid: '00000000-0000-4000-8000-000000000003',
|
||||
reason: 'ClusterPluginPackageRecoveryRequiredError',
|
||||
durableState: 'staged',
|
||||
},
|
||||
transitionReceiptDigest: 'f'.repeat(64),
|
||||
recoveryJobUid: '00000000-0000-4000-8000-000000000003',
|
||||
rejectionReason: 'activation_fact_conflict',
|
||||
candidateRevisionCount: 0,
|
||||
activePointerUnchanged: true,
|
||||
@@ -83,7 +73,6 @@ function validReport() {
|
||||
initialMutationCount: 4,
|
||||
upgradeMutationCount: 3,
|
||||
headInstallationId: 'install-plugin-recovery-e2e-upgrade',
|
||||
transitionReceiptCount: 1,
|
||||
initialRevisionCount: 1,
|
||||
upgradeRevisionCount: 0,
|
||||
recoverableCount: 0,
|
||||
@@ -91,11 +80,11 @@ function validReport() {
|
||||
oci: {
|
||||
https: true,
|
||||
authentication: 'exact-registry-basic',
|
||||
authenticatedRequestCount: 18,
|
||||
requestCount: 18,
|
||||
authenticatedRequestCount: 12,
|
||||
requestCount: 12,
|
||||
uniquePaths: 12,
|
||||
initialRequestCount: 6,
|
||||
upgradeRequestCount: 12,
|
||||
upgradeRequestCount: 6,
|
||||
redirects: 0,
|
||||
},
|
||||
kubernetes: {
|
||||
@@ -118,9 +107,9 @@ function validReport() {
|
||||
},
|
||||
runtime: {
|
||||
replicas: 2,
|
||||
creationTimestamp: '2026-08-14T08:00:06.000Z',
|
||||
recoveryJobUid: '00000000-0000-4000-8000-000000000005',
|
||||
recoveryCompletedAt: '2026-08-14T08:00:05.000Z',
|
||||
creationTimestamp: '2026-08-14T08:00:04.000Z',
|
||||
recoveryJobUid: '00000000-0000-4000-8000-000000000003',
|
||||
recoveryCompletedAt: '2026-08-14T08:00:03.000Z',
|
||||
nodes: ['worker-a', 'worker-b'],
|
||||
imageIds: [`docker://${control}`],
|
||||
},
|
||||
@@ -142,7 +131,8 @@ test('offline audit accepts one exact low-sensitive recovery report', () => {
|
||||
test('offline audit rejects broken upgrade, ordering and image relationships', () => {
|
||||
const report = validReport();
|
||||
report.database.upgradeRevisionCount = 1;
|
||||
report.ordering.transitionCompletedAt = '2026-08-14T07:59:59.000Z';
|
||||
report.ordering.upgradeRecoveryCompletedAt =
|
||||
'2026-08-14T07:59:59.000Z';
|
||||
report.runtime.imageIds = [`docker://sha256:${'9'.repeat(64)}`];
|
||||
report.gates.activePointerJsonUnchanged = false;
|
||||
const codes = validatePluginPackageRecoveryE2ELiveReport(report).findings.map(
|
||||
|
||||
@@ -3,6 +3,9 @@ const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { test } = require('node:test');
|
||||
const yaml = require('js-yaml');
|
||||
const {
|
||||
createFixture,
|
||||
} = require('../../scripts/ql3-plugin-package-recovery-e2e-fixture.cjs');
|
||||
|
||||
const root = path.resolve(__dirname, '../..');
|
||||
const livePath = path.join(
|
||||
@@ -53,6 +56,27 @@ test('fixture uses a real HTTPS and content-addressed OCI Distribution surface',
|
||||
assert.match(live, /requestCount: packageRequests\.length/);
|
||||
});
|
||||
|
||||
test('fixture locks are bound to durable version-three approval dispatches', () => {
|
||||
const value = createFixture({
|
||||
registry: 'registry.fixture.test',
|
||||
architecture: 'amd64',
|
||||
createdAtMs: 1_000,
|
||||
});
|
||||
for (const selected of [value.initial, value.upgrade]) {
|
||||
const proposal = selected.authority.proposalCommand.proposal;
|
||||
const dispatch = selected.authority.dispatch;
|
||||
assert.equal(selected.lock.approval.requestVersion, 3);
|
||||
assert.equal(selected.lock.approval.dispatchId, dispatch.id);
|
||||
assert.equal(selected.lock.actionDigest, proposal.actionDigest);
|
||||
assert.equal(selected.lock.planDigest, proposal.previewDigest);
|
||||
assert.equal(dispatch.action.actionRef, proposal.actionRef);
|
||||
assert.equal(
|
||||
selected.authority.consumptionCommand.dispatchId,
|
||||
dispatch.id,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('gate runs migration, healthy activation and a durable rejected upgrade', () => {
|
||||
assert.match(live, /operations\/base\/migrate-job\.yaml/);
|
||||
assert.match(
|
||||
@@ -69,6 +93,14 @@ test('gate runs migration, healthy activation and a durable rejected upgrade', (
|
||||
);
|
||||
assert.match(live, /plugin-package-recovery\/base\/recover-job\.yaml/);
|
||||
assert.match(fixture, /PostgresPluginPackageInstallRepository/);
|
||||
assert.match(fixture, /PostgresPluginPackageInstallProposalRepository/);
|
||||
assert.match(fixture, /PostgresApprovalRequestRepository/);
|
||||
assert.match(fixture, /PostgresApprovedActionExecutionRepository/);
|
||||
assert.match(fixture, /PostgresProjectPolicyRepository/);
|
||||
assert.match(fixture, /\.createProposal\(selected\.authority\.proposalCommand\)/);
|
||||
assert.match(fixture, /\.consume\(selected\.authority\.consumptionCommand\)/);
|
||||
assert.match(fixture, /repository\.admit\(/);
|
||||
assert.doesNotMatch(fixture, /pluginPackageInstallCreate/);
|
||||
assert.match(
|
||||
fixture,
|
||||
/PostgresPluginPackagePublisherTrustAuthorityRepository/,
|
||||
@@ -79,12 +111,9 @@ test('gate runs migration, healthy activation and a durable rejected upgrade', (
|
||||
assert.match(fixture, /publisherTrustStatus/);
|
||||
assert.match(live, /QL3_E2E_POSTGRES_PACKAGE_MANAGER_USER/);
|
||||
assert.match(live, /key: 'package-manager-password'/);
|
||||
assert.match(
|
||||
fixture,
|
||||
/PostgresPluginPackageSecretBindingTransitionRepository/,
|
||||
);
|
||||
assert.match(fixture, /createPluginPackageInstall/);
|
||||
assert.match(fixture, /pluginPackageInstallCreate/);
|
||||
assert.match(live, /QL3_E2E_POSTGRES_RUNTIME_USER/);
|
||||
assert.match(live, /key: 'runtime-password'/);
|
||||
assert.doesNotMatch(fixture, /commit-transition/);
|
||||
assert.match(live, /initialSeed\.state, 'queued'/);
|
||||
assert.match(live, /upgradeSeed\.state, 'queued'/);
|
||||
assert.match(live, /value\.migrationCount, 65/);
|
||||
@@ -99,27 +128,18 @@ test('gate runs migration, healthy activation and a durable rejected upgrade', (
|
||||
});
|
||||
|
||||
test('deployment controller rejects the upgrade before creating runtime', () => {
|
||||
const failedStageWait = live.indexOf(
|
||||
"waitForJob(UPGRADE_STAGE_JOB, 'failed')",
|
||||
);
|
||||
const transitionWait = live.indexOf('waitForJob(TRANSITION_JOB)');
|
||||
const rejectionWait = live.indexOf('waitForJob(UPGRADE_REJECTION_JOB)');
|
||||
const rejectionWait = live.indexOf('waitForJob(UPGRADE_RECOVERY_JOB)');
|
||||
const pointerProof = live.indexOf(
|
||||
'assert.deepEqual(pointerAfterRejection, pointerBeforeUpgrade)',
|
||||
);
|
||||
const runtimeApply = live.indexOf(
|
||||
'const runtime = applyRuntimeAfterRecovery(',
|
||||
);
|
||||
assert.ok(failedStageWait > 0);
|
||||
assert.ok(transitionWait > failedStageWait);
|
||||
assert.ok(rejectionWait > transitionWait);
|
||||
assert.ok(rejectionWait > 0);
|
||||
assert.ok(pointerProof > rejectionWait);
|
||||
assert.ok(runtimeApply > pointerProof);
|
||||
assert.match(live, /activePointerUnchanged: true/);
|
||||
assert.match(
|
||||
live,
|
||||
/stageFailure\.name,[\s\S]*'ClusterPluginPackageRecoveryRequiredError'/,
|
||||
);
|
||||
assert.doesNotMatch(live, /missingTransitionFailedClosed/);
|
||||
assert.match(live, /qinglong\.io\/plugin-recovery-job-uid/);
|
||||
assert.match(live, /qinglong\.io\/plugin-recovery-completed-at/);
|
||||
assert.match(live, /rollout[\s\S]*status/);
|
||||
|
||||
Reference in New Issue
Block a user