mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
332 lines
12 KiB
JavaScript
332 lines
12 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
|
|
const {
|
|
PostgresTaskDefinitionRepository,
|
|
PostgresTriggerRepository,
|
|
} = require('../packages/ql3-cluster-postgres/dist/entrypoints/admin.js');
|
|
const {
|
|
PostgresClusterLegacyEnvMigrationPlanRepository,
|
|
} = require('../packages/ql3-cluster-postgres/dist/reconciliation/clusterLegacyEnvMigrationPlanRepository.js');
|
|
const {
|
|
PostgresClusterLegacyEnvMigrationApplicationRepository,
|
|
} = require('../packages/ql3-cluster-postgres/dist/reconciliation/clusterLegacyEnvMigrationApplicationRepository.js');
|
|
const {
|
|
createClusterLegacyEnvMigrationTaskMutationSetDigester,
|
|
createClusterLegacyEnvMigrationTriggerMutationSetDigester,
|
|
} = require('../packages/ql3-runtime-core/dist/migration/clusterLegacyEnvMigrationApplication.js');
|
|
const {
|
|
createSecretRef,
|
|
} = require('../packages/ql3-runtime-core/dist/secret/secretReference.js');
|
|
|
|
const FIXTURE = Object.freeze({
|
|
projectId: 'ha-legacy-env-application',
|
|
taskId: 'ha legacy env task',
|
|
triggerId: 'ha legacy env trigger',
|
|
planId: 'ha-legacy-env-plan',
|
|
planMutationId: '819f7900-0000-4000-8000-000000000001',
|
|
applicationId: 'ha-legacy-env-application-receipt',
|
|
applicationMutationId: '819f7900-0000-4000-8000-000000000002',
|
|
taskCreateMutationId: '819f7900-0000-4000-8000-000000000003',
|
|
triggerCreateMutationId: '819f7900-0000-4000-8000-000000000004',
|
|
taskAdvanceMutationId: '819f7900-0000-4000-8000-000000000005',
|
|
taskApplicationMutationId: '819f7900-0000-4000-8000-000000000006',
|
|
triggerApplicationMutationId: '819f7900-0000-4000-8000-000000000007',
|
|
privateBundleName: 'ha-legacy-env-bundle-private',
|
|
});
|
|
|
|
async function clusterLegacyEnvMigrationApplicationFacts(pool, fixture) {
|
|
const result = await pool.query(
|
|
`SELECT
|
|
receipt.receipt_digest AS "receiptDigest",
|
|
receipt.task_count AS "taskCount",
|
|
receipt.trigger_count AS "triggerCount",
|
|
task_item.revision AS "taskRevision",
|
|
task_item.content_digest AS "taskContentDigest",
|
|
task_item.execution_content_digest AS "executionContentDigest",
|
|
trigger_item.revision AS "triggerRevision",
|
|
trigger_item.content_digest AS "triggerContentDigest",
|
|
trigger_item.task_revision AS "triggerTaskRevision",
|
|
trigger_item.task_content_digest AS "triggerTaskContentDigest",
|
|
schedule.trigger_revision AS "scheduleRevision",
|
|
schedule.state_version AS "scheduleStateVersion",
|
|
schedule.claim_version AS "scheduleClaimVersion",
|
|
(SELECT count(*)::integer
|
|
FROM "ql3"."cluster_legacy_env_migration_plans" AS plan
|
|
WHERE plan.plan_id = receipt.plan_id) AS "planRows",
|
|
(SELECT count(*)::integer
|
|
FROM "ql3"."cluster_legacy_env_migration_application_receipts" AS stored
|
|
WHERE stored.application_id = receipt.application_id) AS "receiptRows",
|
|
(SELECT count(*)::integer
|
|
FROM "ql3"."cluster_legacy_env_migration_application_tasks" AS item
|
|
WHERE item.application_id = receipt.application_id) AS "taskItemRows",
|
|
(SELECT count(*)::integer
|
|
FROM "ql3"."cluster_legacy_env_migration_application_triggers" AS item
|
|
WHERE item.application_id = receipt.application_id) AS "triggerItemRows"
|
|
FROM "ql3"."cluster_legacy_env_migration_application_receipts" AS receipt
|
|
JOIN "ql3"."cluster_legacy_env_migration_application_tasks" AS task_item
|
|
ON task_item.application_id = receipt.application_id
|
|
AND task_item.ordinal = 0
|
|
JOIN "ql3"."cluster_legacy_env_migration_application_triggers" AS trigger_item
|
|
ON trigger_item.application_id = receipt.application_id
|
|
AND trigger_item.ordinal = 0
|
|
JOIN "ql3"."trigger_schedules" AS schedule
|
|
ON schedule.project_id = trigger_item.project_id
|
|
AND schedule.trigger_id = trigger_item.trigger_id
|
|
WHERE receipt.application_id = $1`,
|
|
[fixture.applicationId],
|
|
);
|
|
assert.equal(result.rowCount, 1);
|
|
return Object.freeze({ ...result.rows[0] });
|
|
}
|
|
|
|
function assertExpectedFacts(facts) {
|
|
assert.equal(facts.planRows, 1);
|
|
assert.equal(facts.receiptRows, 1);
|
|
assert.equal(facts.taskItemRows, 1);
|
|
assert.equal(facts.triggerItemRows, 1);
|
|
assert.equal(facts.taskCount, 1);
|
|
assert.equal(facts.triggerCount, 1);
|
|
assert.equal(facts.taskRevision, 3);
|
|
assert.equal(facts.triggerRevision, 2);
|
|
assert.equal(facts.triggerTaskRevision, 3);
|
|
assert.equal(facts.scheduleRevision, 2);
|
|
assert.equal(facts.scheduleStateVersion, 1);
|
|
assert.equal(facts.scheduleClaimVersion, 1);
|
|
for (const digest of [
|
|
facts.receiptDigest,
|
|
facts.taskContentDigest,
|
|
facts.executionContentDigest,
|
|
facts.triggerContentDigest,
|
|
facts.triggerTaskContentDigest,
|
|
]) {
|
|
assert.match(digest, /^[0-9a-f]{64}$/);
|
|
}
|
|
}
|
|
|
|
async function persistClusterLegacyEnvMigrationApplicationBeforePromotion(
|
|
options,
|
|
) {
|
|
const { migrationPool, automationPool } = options;
|
|
const observed = await migrationPool.query(
|
|
`SELECT floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint
|
|
AS "observedAtMs"`,
|
|
);
|
|
const occurredAtMs = Number(observed.rows[0].observedAtMs) - 1_000;
|
|
await migrationPool.query(
|
|
`INSERT INTO "ql3"."projects" (
|
|
id, name, slug, status, version, created_at_ms, updated_at_ms
|
|
) VALUES ($1, 'HA Legacy Env application', $1, 'active', 1, $2, $2)`,
|
|
[FIXTURE.projectId, occurredAtMs],
|
|
);
|
|
|
|
const tasks = new PostgresTaskDefinitionRepository(migrationPool);
|
|
const triggers = new PostgresTriggerRepository(migrationPool);
|
|
const initialTask = (
|
|
await tasks.appendTaskDefinitionRevision({
|
|
projectId: FIXTURE.projectId,
|
|
taskId: FIXTURE.taskId,
|
|
expectedRevision: null,
|
|
mutationId: FIXTURE.taskCreateMutationId,
|
|
name: 'HA Legacy Env command',
|
|
description: 'promotion replay fixture',
|
|
kind: 'command',
|
|
spec: {
|
|
schema: 'qinglong/command@v1',
|
|
config: {
|
|
command: { kind: 'argv', file: '/bin/echo', args: ['ha'] },
|
|
timeoutMs: 30_000,
|
|
},
|
|
},
|
|
labels: { 'qinglong.io/source': 'ha-contract' },
|
|
enabled: true,
|
|
occurredAtMs,
|
|
})
|
|
).definition;
|
|
const initialTrigger = (
|
|
await triggers.appendTriggerRevision({
|
|
projectId: FIXTURE.projectId,
|
|
triggerId: FIXTURE.triggerId,
|
|
expectedRevision: null,
|
|
mutationId: FIXTURE.triggerCreateMutationId,
|
|
taskId: FIXTURE.taskId,
|
|
taskRevision: initialTask.revision,
|
|
taskContentDigest: initialTask.contentDigest,
|
|
spec: {
|
|
schema: 'qinglong/cron@v1',
|
|
config: {
|
|
expression: '* * * * *',
|
|
timezone: 'UTC',
|
|
misfirePolicy: 'skip',
|
|
},
|
|
},
|
|
enabled: false,
|
|
occurredAtMs: occurredAtMs + 1,
|
|
})
|
|
).trigger;
|
|
const currentTask = (
|
|
await tasks.appendTaskDefinitionRevision({
|
|
projectId: FIXTURE.projectId,
|
|
taskId: FIXTURE.taskId,
|
|
expectedRevision: initialTask.revision,
|
|
mutationId: FIXTURE.taskAdvanceMutationId,
|
|
name: initialTask.name,
|
|
description: initialTask.description,
|
|
kind: initialTask.kind,
|
|
spec: initialTask.spec,
|
|
labels: initialTask.labels,
|
|
enabled: initialTask.enabled,
|
|
occurredAtMs: occurredAtMs + 2,
|
|
})
|
|
).definition;
|
|
|
|
const taskMutations = Object.freeze([
|
|
Object.freeze({
|
|
ordinal: 0,
|
|
taskId: FIXTURE.taskId,
|
|
previousRevision: currentTask.revision,
|
|
previousContentDigest: currentTask.contentDigest,
|
|
mutationId: FIXTURE.taskApplicationMutationId,
|
|
}),
|
|
]);
|
|
const triggerMutations = Object.freeze([
|
|
Object.freeze({
|
|
ordinal: 0,
|
|
triggerId: FIXTURE.triggerId,
|
|
taskId: FIXTURE.taskId,
|
|
previousRevision: initialTrigger.revision,
|
|
previousContentDigest: initialTrigger.contentDigest,
|
|
previousTaskRevision: initialTask.revision,
|
|
previousTaskContentDigest: initialTask.contentDigest,
|
|
mutationId: FIXTURE.triggerApplicationMutationId,
|
|
}),
|
|
]);
|
|
const taskDigester = createClusterLegacyEnvMigrationTaskMutationSetDigester();
|
|
taskDigester.update(taskMutations[0]);
|
|
const taskSet = taskDigester.finish();
|
|
const triggerDigester =
|
|
createClusterLegacyEnvMigrationTriggerMutationSetDigester();
|
|
triggerDigester.update(triggerMutations[0]);
|
|
const triggerSet = triggerDigester.finish();
|
|
const secretRef = createSecretRef({
|
|
projectId: FIXTURE.projectId,
|
|
name: FIXTURE.privateBundleName,
|
|
version: 1,
|
|
});
|
|
const plan = (
|
|
await new PostgresClusterLegacyEnvMigrationPlanRepository(
|
|
automationPool,
|
|
).publish({
|
|
planId: FIXTURE.planId,
|
|
mutationId: FIXTURE.planMutationId,
|
|
projectId: FIXTURE.projectId,
|
|
source: {
|
|
reconciliationBundleDigest: '1'.repeat(64),
|
|
decisionDigest: '2'.repeat(64),
|
|
candidateSetDigest: '3'.repeat(64),
|
|
sourceRowCount: 1,
|
|
activeRowCount: 1,
|
|
disabledRowCount: 0,
|
|
effectiveBindingCount: 1,
|
|
},
|
|
target: {
|
|
secretRef,
|
|
taskRevisionSetDigest: taskSet.revisionSetDigest,
|
|
triggerRevisionSetDigest: triggerSet.revisionSetDigest,
|
|
taskCount: taskSet.count,
|
|
triggerCount: triggerSet.count,
|
|
totalEffectiveBytes: 128,
|
|
},
|
|
})
|
|
).plan;
|
|
const intent = Object.freeze({
|
|
applicationId: FIXTURE.applicationId,
|
|
mutationId: FIXTURE.applicationMutationId,
|
|
projectId: FIXTURE.projectId,
|
|
planId: FIXTURE.planId,
|
|
planDigest: plan.planDigest,
|
|
taskMutationSetDigest: taskSet.mutationSetDigest,
|
|
triggerMutationSetDigest: triggerSet.mutationSetDigest,
|
|
});
|
|
let taskStreamOpenCount = 0;
|
|
let triggerStreamOpenCount = 0;
|
|
const applications =
|
|
new PostgresClusterLegacyEnvMigrationApplicationRepository(automationPool);
|
|
const applied = await applications.apply(intent, {
|
|
taskMutations() {
|
|
taskStreamOpenCount += 1;
|
|
return taskMutations;
|
|
},
|
|
triggerMutations() {
|
|
triggerStreamOpenCount += 1;
|
|
return triggerMutations;
|
|
},
|
|
});
|
|
assert.equal(applied.status, 'applied');
|
|
assert.equal(taskStreamOpenCount, 1);
|
|
assert.equal(triggerStreamOpenCount, 1);
|
|
const primaryBeforePromotion =
|
|
await clusterLegacyEnvMigrationApplicationFacts(migrationPool, FIXTURE);
|
|
assertExpectedFacts(primaryBeforePromotion);
|
|
return {
|
|
fixture: Object.freeze({
|
|
...FIXTURE,
|
|
intent,
|
|
receipt: applied.receipt,
|
|
}),
|
|
report: {
|
|
primaryBeforePromotion,
|
|
standbyBeforePromotion: null,
|
|
promotedAfterReplay: null,
|
|
replicatedBeforePromotion: false,
|
|
exactReplayAfterPromotion: false,
|
|
replayStatus: null,
|
|
mutationStreamsOpenedAfterPromotion: null,
|
|
durableRowsAddedByReplay: null,
|
|
contentFree: true,
|
|
},
|
|
};
|
|
}
|
|
|
|
async function verifyPromotedClusterLegacyEnvMigrationApplication(options) {
|
|
const { automationPool, promotedPool, evidence } = options;
|
|
let mutationStreamsOpened = 0;
|
|
const applications =
|
|
new PostgresClusterLegacyEnvMigrationApplicationRepository(automationPool);
|
|
const beforeReplay = await clusterLegacyEnvMigrationApplicationFacts(
|
|
promotedPool,
|
|
evidence.fixture,
|
|
);
|
|
assert.deepEqual(beforeReplay, evidence.report.primaryBeforePromotion);
|
|
const replay = await applications.apply(evidence.fixture.intent, {
|
|
taskMutations() {
|
|
mutationStreamsOpened += 1;
|
|
throw new Error('exact replay must not reopen Task mutations');
|
|
},
|
|
triggerMutations() {
|
|
mutationStreamsOpened += 1;
|
|
throw new Error('exact replay must not reopen Trigger mutations');
|
|
},
|
|
});
|
|
assert.equal(replay.status, 'existing');
|
|
assert.deepEqual(replay.receipt, evidence.fixture.receipt);
|
|
const promotedAfterReplay = await clusterLegacyEnvMigrationApplicationFacts(
|
|
promotedPool,
|
|
evidence.fixture,
|
|
);
|
|
assertExpectedFacts(promotedAfterReplay);
|
|
assert.deepEqual(promotedAfterReplay, beforeReplay);
|
|
evidence.report.promotedAfterReplay = promotedAfterReplay;
|
|
evidence.report.exactReplayAfterPromotion = true;
|
|
evidence.report.replayStatus = replay.status;
|
|
evidence.report.mutationStreamsOpenedAfterPromotion = mutationStreamsOpened;
|
|
evidence.report.durableRowsAddedByReplay =
|
|
promotedAfterReplay.receiptRows - beforeReplay.receiptRows;
|
|
return evidence.report;
|
|
}
|
|
|
|
module.exports = {
|
|
clusterLegacyEnvMigrationApplicationFacts,
|
|
persistClusterLegacyEnvMigrationApplicationBeforePromotion,
|
|
verifyPromotedClusterLegacyEnvMigrationApplication,
|
|
};
|