Skip to content

Commit 16d8da0

Browse files
committed
feat(webapp): add compute migration resolver
1 parent fda814c commit 16d8da0

2 files changed

Lines changed: 200 additions & 0 deletions

File tree

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { hashBucket } from "~/utils/computeBucket";
2+
3+
/** Subset of the global flags snapshot this resolver reads. */
4+
export type ComputeMigrationFlags = {
5+
computeMigrationEnabled?: boolean;
6+
computeMigrationFreePercentage?: number;
7+
computeMigrationPaidPercentage?: number;
8+
};
9+
10+
export type ComputeBackingMap = Record<string, string>;
11+
12+
/**
13+
* Parse COMPUTE_BACKING_MAP (container-region masterQueue -> compute-backing
14+
* masterQueue). Never throws: bad JSON or non-string values yield {} so a
15+
* misconfigured env disables migration rather than breaking triggers.
16+
*/
17+
export function parseComputeBackingMap(raw: string): ComputeBackingMap {
18+
try {
19+
const parsed = JSON.parse(raw);
20+
if (typeof parsed !== "object" || parsed === null) return {};
21+
const out: ComputeBackingMap = {};
22+
for (const [k, v] of Object.entries(parsed)) {
23+
if (typeof v === "string") out[k] = v;
24+
}
25+
return out;
26+
} catch {
27+
return {};
28+
}
29+
}
30+
31+
type MigrationDecisionInput = {
32+
planType: string | undefined;
33+
orgId: string;
34+
orgFeatureFlags: Record<string, unknown> | null | undefined;
35+
flags: ComputeMigrationFlags | undefined;
36+
};
37+
38+
/**
39+
* Whether this org should run on the compute backing. Shared by the trigger-time
40+
* transform and the deploy-time template decision so a migrated org always gets a
41+
* compute template. Precedence: per-org override (both directions) wins; otherwise
42+
* global enable + the plan's percentage bucket. Enterprise and unknown plans are
43+
* never enrolled by percentage (override only). The sole opt-out is the per-org
44+
* `computeMigrationEnabled: false`.
45+
*/
46+
export function isOrgMigrated({
47+
planType,
48+
orgId,
49+
orgFeatureFlags,
50+
flags,
51+
}: MigrationDecisionInput): boolean {
52+
const override = orgFeatureFlags?.["computeMigrationEnabled"];
53+
if (override === false) return false;
54+
if (override === true) return true;
55+
56+
if (!(flags?.computeMigrationEnabled ?? false)) return false;
57+
58+
const pct =
59+
planType === "free"
60+
? flags?.computeMigrationFreePercentage ?? 0
61+
: planType === "paid"
62+
? flags?.computeMigrationPaidPercentage ?? 0
63+
: 0; // enterprise / undefined
64+
65+
return hashBucket(orgId) < pct;
66+
}
67+
68+
type ResolveInput = MigrationDecisionInput & {
69+
baseWorkerQueue: string | undefined;
70+
envType: string;
71+
backingMap: ComputeBackingMap;
72+
};
73+
74+
/**
75+
* Rewrite the resolved worker queue to its compute backing when the org is
76+
* migrated and the region has a backing. Same-geo swap (us-east-1 -> us-east-1-next):
77+
* any explicit placement is a geography preference, honored by staying in-region.
78+
* Applied after region resolution, mirroring the scheduled-split.
79+
*/
80+
export function resolveComputeMigration({
81+
baseWorkerQueue,
82+
envType,
83+
backingMap,
84+
...decision
85+
}: ResolveInput): string | undefined {
86+
if (baseWorkerQueue === undefined) return baseWorkerQueue;
87+
if (envType === "DEVELOPMENT") return baseWorkerQueue;
88+
if (!isOrgMigrated(decision)) return baseWorkerQueue;
89+
return backingMap[baseWorkerQueue] ?? baseWorkerQueue;
90+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { describe, it, expect } from "vitest";
2+
import {
3+
parseComputeBackingMap,
4+
isOrgMigrated,
5+
resolveComputeMigration,
6+
} from "~/runEngine/concerns/computeMigration.server";
7+
8+
const BACKING = { "us-east-1": "us-east-1-next" };
9+
10+
describe("parseComputeBackingMap", () => {
11+
it("parses valid JSON", () => {
12+
expect(parseComputeBackingMap('{"us-east-1":"us-east-1-next"}')).toEqual(BACKING);
13+
});
14+
it("returns {} on invalid JSON without throwing", () => {
15+
expect(parseComputeBackingMap("not json")).toEqual({});
16+
});
17+
it("returns {} on non-string values", () => {
18+
expect(parseComputeBackingMap('{"us-east-1":5}')).toEqual({});
19+
});
20+
});
21+
22+
describe("isOrgMigrated", () => {
23+
const base = {
24+
planType: "free" as string | undefined,
25+
orgFeatureFlags: {} as Record<string, unknown>,
26+
flags: { computeMigrationEnabled: true, computeMigrationFreePercentage: 100 },
27+
};
28+
29+
it("migrates a free org at 100%", () => {
30+
expect(isOrgMigrated({ ...base, orgId: "org_x" })).toBe(true);
31+
});
32+
it("does not migrate when globally disabled", () => {
33+
expect(
34+
isOrgMigrated({ ...base, orgId: "org_x", flags: { computeMigrationEnabled: false, computeMigrationFreePercentage: 100 } })
35+
).toBe(false);
36+
});
37+
it("per-org override false excludes even at 100%", () => {
38+
expect(
39+
isOrgMigrated({ ...base, orgId: "org_x", orgFeatureFlags: { computeMigrationEnabled: false } })
40+
).toBe(false);
41+
});
42+
it("per-org override true enrolls even when globally off", () => {
43+
expect(
44+
isOrgMigrated({
45+
...base,
46+
orgId: "org_x",
47+
orgFeatureFlags: { computeMigrationEnabled: true },
48+
flags: { computeMigrationEnabled: false, computeMigrationFreePercentage: 0 },
49+
})
50+
).toBe(true);
51+
});
52+
it("paid uses the paid dial", () => {
53+
expect(
54+
isOrgMigrated({
55+
planType: "paid",
56+
orgId: "org_x",
57+
orgFeatureFlags: {},
58+
flags: { computeMigrationEnabled: true, computeMigrationPaidPercentage: 100 },
59+
})
60+
).toBe(true);
61+
});
62+
it("enterprise is never enrolled by percentage", () => {
63+
expect(
64+
isOrgMigrated({
65+
planType: "enterprise",
66+
orgId: "org_x",
67+
orgFeatureFlags: {},
68+
flags: { computeMigrationEnabled: true, computeMigrationFreePercentage: 100, computeMigrationPaidPercentage: 100 },
69+
})
70+
).toBe(false);
71+
});
72+
it("undefined planType is not enrolled", () => {
73+
expect(
74+
isOrgMigrated({ planType: undefined, orgId: "org_x", orgFeatureFlags: {}, flags: { computeMigrationEnabled: true } })
75+
).toBe(false);
76+
});
77+
});
78+
79+
describe("resolveComputeMigration", () => {
80+
const enrolled = {
81+
planType: "free",
82+
orgFeatureFlags: {},
83+
flags: { computeMigrationEnabled: true, computeMigrationFreePercentage: 100 },
84+
envType: "PRODUCTION",
85+
backingMap: BACKING,
86+
};
87+
88+
it("swaps to the compute backing for an enrolled free org", () => {
89+
expect(resolveComputeMigration({ ...enrolled, baseWorkerQueue: "us-east-1", orgId: "org_x" }))
90+
.toBe("us-east-1-next");
91+
});
92+
it("leaves a region with no backing untouched (EU)", () => {
93+
expect(resolveComputeMigration({ ...enrolled, baseWorkerQueue: "eu-central-1", orgId: "org_x" }))
94+
.toBe("eu-central-1");
95+
});
96+
it("does not migrate DEVELOPMENT", () => {
97+
expect(
98+
resolveComputeMigration({ ...enrolled, baseWorkerQueue: "us-east-1", orgId: "org_x", envType: "DEVELOPMENT" })
99+
).toBe("us-east-1");
100+
});
101+
it("leaves a non-enrolled org untouched", () => {
102+
expect(
103+
resolveComputeMigration({ ...enrolled, baseWorkerQueue: "us-east-1", orgId: "org_x", flags: { computeMigrationEnabled: false } })
104+
).toBe("us-east-1");
105+
});
106+
it("undefined baseWorkerQueue passes through", () => {
107+
expect(resolveComputeMigration({ ...enrolled, baseWorkerQueue: undefined, orgId: "org_x" }))
108+
.toBeUndefined();
109+
});
110+
});

0 commit comments

Comments
 (0)