Files
orca/cloud/dev/scripts/relay-live-cell-image-overlay.test.mjs
Jinwoo Hong bf3f95245c feat(relay): declare Asia cell c30 at the c27 shape (#22375)
* feat(relay): declare Asia cell c30 at the c27 shape

Adds production-gce-c30 in asia-east2-a at the reviewed Asia shape (6,000
request units, 3,000/60 connection limits, 16-connection pool, disabled) and
the rehome trust the other Asia cells carry.

Every Asia enumeration now knows C30. The topology, admission, and director
tools treat it as its own reviewed wave so its plan and registration never
touch the live launch cells. C30 promotion requires C27 general and fresh
staging evidence. The topology and director validators now pin the committed
production pool of 16 instead of the stale 10, which had made the topology
workflow reject the committed launch cells.

* fix(relay): plan C30 at live images and prove it with its own canary

The shared URL map pulls every cell into the C30 topology plan, so the workflow
now plans each non-target cell at the image its live template serves, and the
validator names any change to a cell outside the wave. C30 promotion runs the
same five-minute production canary and automatic rollback C27 used, with the
load report proving the canary control was placed on C30, instead of relying
on staging evidence. C30 leaves the shadow gate's fleet pool list until it
serves, rollback rejects mixed partial sets, and a budget test pins the
mixed-Asia-pool refusal.

* fix(relay): pin C30 to the production director's live image digest

C30 promotion requires the director and C30 to report one digest, so C30
takes the director's sha256:4158d8a2 (read 2026-09-22). C27-C29 keep their
committed lines; every Asia check compares only the cells named in a run.

* fix(relay): read the committed cell map from a plan, not console

terraform console evaluates every output against state, and the Relay
deployments output indexes each cell's MIG, so it fails with Invalid index
while C30 is declared but not created. Read the map from a no-refresh,
unlocked plan over the same targets instead, and refuse empty overlay input.

* fix(relay): keep console readers working and C30 migration-only until promotion

relay_gce_cell_deployments indexed each cell's MIG, backend, and template,
so once C30 is declared but not applied every production terraform console
reader printed a warning to stdout and broke its jq parse. Wrap those six
lookups in try(..., null).

Same-cap listed C30 as general, so a rollback dispatch on a migration-only
C30 would restore it with activate and skip its canary. List it with the
migration-only cells until the promotion follow-up moves it.
2026-09-22 23:41:09 -04:00

120 lines
5.3 KiB
JavaScript

import assert from 'node:assert/strict'
import { spawnSync } from 'node:child_process'
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { test } from 'node:test'
import { fileURLToPath } from 'node:url'
import { overlayRelayLiveCellImages } from './relay-live-cell-image-overlay.mjs'
const repository = 'us-central1-docker.pkg.dev/onorca-cloud/orca-cloud/relay'
const committed = `${repository}@sha256:${'a'.repeat(64)}`
const served = `${repository}@sha256:${'b'.repeat(64)}`
const template = readFileSync(
new URL('../../infra/terraform/relay-gce-startup.sh.tftpl', import.meta.url),
'utf8'
)
// Renders only the two lines the overlay reads, from the real template, so a template edit fails here.
function startupScript(image) {
return template
.replaceAll('${trimprefix(regex("@sha256:[a-f0-9]{64}$", relay_image), "@")}', image.split('@')[1])
.replaceAll('${relay_image}', image)
.replaceAll('${cloud_sql_proxy_image}', `gcr.io/cloud-sql-connectors/cloud-sql-proxy@sha256:${'c'.repeat(64)}`)
}
const cell = (image) => ({ hostname: 'x', region: 'asia-east2', zone: 'asia-east2-a', image, connection_hard_cap: 3000 })
const committedCells = {
'production-gce-c1': cell(committed),
'production-gce-c27': cell(committed),
'production-gce-c30': cell(committed)
}
const live = [
{ index: 'production-gce-c1', metadata_startup_script: startupScript(committed) },
{ index: 'production-gce-c27', metadata_startup_script: startupScript(served) }
]
test('plans every non-target cell at its served image while the target has no template yet', () => {
const overlay = overlayRelayLiveCellImages({
committedCells, liveTemplates: live, targetCellIds: ['production-gce-c30']
})
assert.deepEqual(overlay, {
relay_gce_cells: {
'production-gce-c1': cell(committed),
'production-gce-c27': cell(served),
'production-gce-c30': cell(committed)
}
})
})
test('leaves a target cell at its committed image even once it has a live template', () => {
const overlay = overlayRelayLiveCellImages({
committedCells,
liveTemplates: [...live, { index: 'production-gce-c30', metadata_startup_script: startupScript(served) }],
targetCellIds: ['production-gce-c30']
})
assert.equal(overlay.relay_gce_cells['production-gce-c30'].image, committed)
})
test('refuses a non-target cell that has no live template', () => {
assert.throws(() => overlayRelayLiveCellImages({
committedCells, liveTemplates: live.slice(1), targetCellIds: ['production-gce-c30']
}), /production-gce-c1 is not a target and has no live template/)
})
test('refuses a live template without one pinned Relay image', () => {
const digestMismatch = startupScript(served)
.replace(`%s\\n' 'sha256:${'b'.repeat(64)}'`, `%s\\n' 'sha256:${'d'.repeat(64)}'`)
for (const script of [
digestMismatch,
startupScript(served).replace(`docker pull '${served}'`, ''),
`${startupScript(served)}\ndocker pull '${committed}'`,
startupScript(`${repository}:latest`)
]) {
assert.throws(() => overlayRelayLiveCellImages({
committedCells,
liveTemplates: [live[0], { index: 'production-gce-c27', metadata_startup_script: script }],
targetCellIds: ['production-gce-c30']
}), /production-gce-c27 live template has no single pinned Relay image/)
}
})
test('refuses duplicate live templates and an undeclared target', () => {
assert.throws(() => overlayRelayLiveCellImages({
committedCells, liveTemplates: [...live, live[1]], targetCellIds: ['production-gce-c30']
}), /more than one live template/)
assert.throws(() => overlayRelayLiveCellImages({
committedCells, liveTemplates: live, targetCellIds: ['production-gce-c31']
}), /production-gce-c31 is not a committed Relay cell/)
})
test('builds the overlay from plan variables when the target cell has no template in state yet', () => {
const dir = mkdtempSync(join(tmpdir(), 'relay-live-cell-image-overlay-'))
try {
const cells = join(dir, 'cells.json')
const templates = join(dir, 'templates.json')
const output = join(dir, 'overlay.tfvars.json')
// Plan variables carry the map as written: optional attributes the tfvars omit stay absent.
const { connection_hard_cap: _omitted, ...c30 } = cell(committed)
writeFileSync(cells, JSON.stringify({ ...committedCells, 'production-gce-c30': c30 }))
writeFileSync(templates, JSON.stringify(live))
assert.ok(!live.some(({ index }) => index === 'production-gce-c30'))
const run = (cellsPath) => spawnSync(process.execPath, [
fileURLToPath(new URL('./relay-live-cell-image-overlay.mjs', import.meta.url)),
'--cells-json', cellsPath, '--live-templates-json', templates,
'--cell-ids', 'production-gce-c30', '--output', output
], { encoding: 'utf8' })
const result = run(cells)
assert.equal(result.status, 0, result.stderr)
assert.deepEqual(JSON.parse(result.stdout), { cells: 3, drifted: ['production-gce-c27'] })
assert.deepEqual(JSON.parse(readFileSync(output, 'utf8')).relay_gce_cells['production-gce-c30'], c30)
const empty = join(dir, 'empty.json')
writeFileSync(empty, '')
const failed = run(empty)
assert.notEqual(failed.status, 0)
assert.match(failed.stderr, /committed Relay cells is empty/)
} finally {
rmSync(dir, { recursive: true, force: true })
}
})