ci: split failover switch into per-platform Linux and Windows variables

Replace the single DSH_CI_FAILOVER variable with two independent
switches so an outage on one platform no longer retargets the other:

- DSH_CI_FAILOVER_LINUX: the three required Linux workers (node-24,
  node-24-coverage, node-24-consumers) and the all-checks-passed verdict,
  which resolves its pool to vm-backup and keeps its concurrency and
  cache-restore branches.
- DSH_CI_FAILOVER_WINDOWS: the non-blocking windows-native job, which
  resolves to the dsh-win-ci pool.

all-checks-passed rides the Linux switch because it aggregates the
required Linux workers and runs on the vm-backup pool. The Dependabot
exclusion is preserved on both switches. The failover runbook (EN/ZH)
and its translation pairing, plus the docs that referenced the old
variable, are updated in the same change.
This commit is contained in:
Chinesezjc
2026-08-13 16:54:57 +08:00
parent d8f5b0507d
commit 65f679b33a
9 changed files with 88 additions and 57 deletions
+23 -2
View File
@@ -34,14 +34,20 @@ describe('CI workflow', () => {
|| !isRecord(workflow.jobs['windows-native'])
|| !isRecord(workflow.jobs['wine-apt-cache'])
|| !isRecord(workflow.jobs['serial-windows'])
|| !isRecord(workflow.jobs['node-24'])
|| !isRecord(workflow.jobs['node-24-coverage'])
|| !isRecord(workflow.jobs['node-24-consumers'])
|| !isRecord(workflow.jobs['all-checks-passed'])) {
throw new TypeError('CI workflow must define windows, windows-native, wine-apt-cache, serial-windows, and all-checks-passed jobs')
throw new TypeError('CI workflow must define windows, windows-native, wine-apt-cache, serial-windows, node-24, node-24-coverage, node-24-consumers, and all-checks-passed jobs')
}
const windows = workflow.jobs.windows
const windowsNative = workflow.jobs['windows-native']
const wineAptCache = workflow.jobs['wine-apt-cache']
const serialWindows = workflow.jobs['serial-windows']
const node24 = workflow.jobs['node-24']
const node24Coverage = workflow.jobs['node-24-coverage']
const node24Consumers = workflow.jobs['node-24-consumers']
const aggregate = workflow.jobs['all-checks-passed']
if (!Array.isArray(windows.steps) || !Array.isArray(aggregate.needs)) {
throw new TypeError('Windows job must define steps and the aggregate must define needs')
@@ -57,8 +63,10 @@ describe('CI workflow', () => {
expect(commandSteps.some(step => step.run.includes('wine-windows-gates.sh'))).toBe(true)
// windows-native: non-blocking native job with failover, runs windows-complete.
// Its pool is resolved by the Windows-specific switch.
expect(typeof windowsNative['runs-on']).toBe('string')
expect(windowsNative['runs-on']).toContain('DSH_CI_FAILOVER')
expect(windowsNative['runs-on']).toContain('DSH_CI_FAILOVER_WINDOWS')
expect(windowsNative['runs-on']).not.toContain('DSH_CI_FAILOVER_LINUX')
expect(windowsNative['runs-on']).toContain('self-hosted')
expect(windowsNative['runs-on']).toContain('dsh-win-ci')
expect(windowsNative['runs-on']).toContain('dsh-windows-2025-16core')
@@ -82,6 +90,19 @@ describe('CI workflow', () => {
expect(aggregate.needs).toContain('windows')
expect(aggregate.needs).not.toContain('windows-native')
expect(aggregate.needs).not.toContain('serial-windows')
// Linux failover is a separate switch: the three required Linux workers
// and the verdict job resolve their pool through DSH_CI_FAILOVER_LINUX,
// never the Windows switch.
for (const [jobName, job] of [['node-24', node24], ['node-24-coverage', node24Coverage], ['node-24-consumers', node24Consumers]] as const) {
expect(typeof job['runs-on']).toBe('string')
expect(job['runs-on'], `${jobName} runs-on must use the Linux failover switch`).toContain('DSH_CI_FAILOVER_LINUX')
expect(job['runs-on'], `${jobName} runs-on must not use the Windows failover switch`).not.toContain('DSH_CI_FAILOVER_WINDOWS')
expect(job['runs-on']).toContain('vm-backup')
}
expect(aggregate['runs-on']).toContain('DSH_CI_FAILOVER_LINUX')
expect(aggregate['runs-on']).not.toContain('DSH_CI_FAILOVER_WINDOWS')
expect(aggregate['runs-on']).toContain('vm-backup')
})
it('exempts push from cancellation, so one master merge does not cancel the running drill', () => {