Skip to content

Commit 721e62e

Browse files
inlinedschnecle
authored andcommitted
Cloud Run Functions data model (#8767)
* Checkpoint * Checkpoint * Run Functions experiment + model changes * Simplify; format * Checkpoint * Checkpoint * Run Functions experiment + model changes * Simplify; format * Checkpoint * Refactor scaling and tool * Fix tests. Somehow unrelated code is failing * Fix tests * Resotore runv2 from reflog * Format fix * PR feedback
1 parent 55dc41a commit 721e62e

18 files changed

+1236
-76
lines changed

src/deploy/functions/args.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export interface Context {
5151
unreachableRegions?: {
5252
gcfV1: string[];
5353
gcfV2: string[];
54+
run: string[];
5455
};
5556

5657
// Tracks metrics about codebase deployments to send to GA4

src/deploy/functions/backend.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,8 @@ export interface ServiceConfiguration {
300300
serviceAccount?: string | null;
301301
}
302302

303-
export type FunctionsPlatform = "gcfv1" | "gcfv2";
304-
export const AllFunctionsPlatforms: FunctionsPlatform[] = ["gcfv1", "gcfv2"];
303+
export const AllFunctionsPlatforms = ["gcfv1", "gcfv2", "run"] as const;
304+
export type FunctionsPlatform = (typeof AllFunctionsPlatforms)[number];
305305

306306
export type Triggered =
307307
| HttpsTriggered
@@ -534,6 +534,7 @@ async function loadExistingBackend(ctx: Context): Promise<void> {
534534
ctx.unreachableRegions = {
535535
gcfV1: [],
536536
gcfV2: [],
537+
run: [],
537538
};
538539
const gcfV1Results = await gcf.listAllFunctions(ctx.projectId);
539540
for (const apiFunction of gcfV1Results.functions) {
@@ -623,6 +624,15 @@ export async function checkAvailability(context: Context, want: Backend): Promis
623624
"\nCloud Functions in these regions won't be deleted.",
624625
);
625626
}
627+
628+
if (context.unreachableRegions?.run.length) {
629+
utils.logLabeledWarning(
630+
"functions",
631+
"The following Cloud Run regions are currently unreachable:\n" +
632+
context.unreachableRegions.run.join("\n") +
633+
"\nCloud Run services in these regions won't be deleted.",
634+
);
635+
}
626636
}
627637

628638
/** A helper utility for flattening all endpoints in a backend since typing is a bit wonky. */

src/deploy/functions/build.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,8 @@ export interface SecretEnvVar {
208208
export type MemoryOption = 128 | 256 | 512 | 1024 | 2048 | 4096 | 8192 | 16384 | 32768;
209209
const allMemoryOptions: MemoryOption[] = [128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768];
210210

211-
export type FunctionsPlatform = backend.FunctionsPlatform;
211+
// Run is an automatic migration from gcfv2 and is not used on the wire.
212+
export type FunctionsPlatform = Exclude<backend.FunctionsPlatform, "run">;
212213
export const AllFunctionsPlatforms: FunctionsPlatform[] = ["gcfv1", "gcfv2"];
213214
export type VpcEgressSetting = backend.VpcEgressSettings;
214215
export const AllVpcEgressSettings: VpcEgressSetting[] = ["PRIVATE_RANGES_ONLY", "ALL_TRAFFIC"];

src/deploy/functions/ensure.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export async function defaultServiceAccount(e: backend.Endpoint): Promise<string
2828
const metadata = await metadataCall;
2929
if (e.platform === "gcfv1") {
3030
return `${metadata.projectId}@appspot.gserviceaccount.com`;
31-
} else if (e.platform === "gcfv2") {
31+
} else if (e.platform === "gcfv2" || e.platform === "run") {
3232
return await getDefaultServiceAccount(metadata.projectNumber);
3333
}
3434
assertExhaustive(e.platform);

src/deploy/functions/functionsDeployHelper.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as backend from "./backend";
22
import { DEFAULT_CODEBASE, ValidatedConfig } from "../../functions/projectConfig";
3+
import { assertExhaustive } from "../../functional";
34

45
export interface EndpointFilter {
56
// If codebase is undefined, match all functions in all codebase that matches the idChunks.
@@ -130,8 +131,12 @@ export function getEndpointFilters(options: { only?: string }): EndpointFilter[]
130131
export function getHumanFriendlyPlatformName(platform: backend.Endpoint["platform"]): string {
131132
if (platform === "gcfv1") {
132133
return "1st Gen";
134+
} else if (platform === "gcfv2") {
135+
return "2nd Gen";
136+
} else if (platform === "run") {
137+
return "Cloud Run";
133138
}
134-
return "2nd Gen";
139+
assertExhaustive(platform);
135140
}
136141

137142
/**

src/deploy/functions/prepare.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,11 @@ export async function prepare(
159159
let resource: string;
160160
if (endpoint.platform === "gcfv1") {
161161
resource = `projects/${endpoint.project}/locations/${endpoint.region}/functions/${endpoint.id}`;
162-
} else if (endpoint.platform === "gcfv2") {
162+
} else if (endpoint.platform === "gcfv2" || endpoint.platform === "run") {
163163
// N.B. If GCF starts allowing v1's allowable characters in IDs they're
164164
// going to need to have a transform to create a service ID (which has a
165165
// more restrictive character set). We'll need to reimplement that here.
166+
// BUG BUG BUG. This has happened and we need to fix it.
166167
resource = `projects/${endpoint.project}/locations/${endpoint.region}/services/${endpoint.id}`;
167168
} else {
168169
assertExhaustive(endpoint.platform);

src/deploy/functions/pricing.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,12 @@ export function canCalculateMinInstanceCost(endpoint: backend.Endpoint): boolean
160160
const SECONDS_PER_MONTH = 30 * 24 * 60 * 60;
161161

162162
/** The cost of a series of endpoints at 100% idle in a 30d month. */
163+
// BUG BUG BUG!
164+
// This method incorrectly gives a disjoint free tier for GCF v1 and GCF v2 which
165+
// was broken and never fixed when GCF decided to vendor Run usage as the GCF SKU.
166+
// It should be a single free tier that applies to both. This will soon be wrong
167+
// in a _different_ way when GCF v2 un-vendors the SKU and instead v2 and Run should
168+
// share a free tier.
163169
export function monthlyMinInstanceCost(endpoints: backend.Endpoint[]): number {
164170
// Assertion: canCalculateMinInstanceCost
165171
type Usage = {
@@ -169,6 +175,7 @@ export function monthlyMinInstanceCost(endpoints: backend.Endpoint[]): number {
169175
const usage: Record<backend.FunctionsPlatform, Record<tier, Usage>> = {
170176
gcfv1: { 1: { ram: 0, cpu: 0 }, 2: { ram: 0, cpu: 0 } },
171177
gcfv2: { 1: { ram: 0, cpu: 0 }, 2: { ram: 0, cpu: 0 } },
178+
run: { 1: { ram: 0, cpu: 0 }, 2: { ram: 0, cpu: 0 } },
172179
};
173180

174181
for (const endpoint of endpoints) {
@@ -188,10 +195,10 @@ export function monthlyMinInstanceCost(endpoints: backend.Endpoint[]): number {
188195
} else {
189196
// V2 is currently fixed at 1vCPU.
190197
const tier = V2_REGION_TO_TIER[endpoint.region];
191-
usage["gcfv2"][tier].ram =
192-
usage["gcfv2"][tier].ram + ramGb * SECONDS_PER_MONTH * endpoint.minInstances;
193-
usage["gcfv2"][tier].cpu =
194-
usage["gcfv2"][tier].cpu +
198+
usage[endpoint.platform][tier].ram =
199+
usage[endpoint.platform][tier].ram + ramGb * SECONDS_PER_MONTH * endpoint.minInstances;
200+
usage[endpoint.platform][tier].cpu =
201+
usage[endpoint.platform][tier].cpu +
195202
(endpoint.cpu as number) * SECONDS_PER_MONTH * endpoint.minInstances;
196203
}
197204
}
@@ -218,5 +225,15 @@ export function monthlyMinInstanceCost(endpoints: backend.Endpoint[]): number {
218225
v2CpuBill -= V2_FREE_TIER.vCpu * V2_RATES.vCpu[1];
219226
v2CpuBill = Math.max(v2CpuBill, 0);
220227

221-
return v1MemoryBill + v1CpuBill + v2MemoryBill + v2CpuBill;
228+
let runMemoryBill =
229+
usage["run"][1].ram * V2_RATES.memoryGb[1] + usage["run"][2].ram * V2_RATES.memoryGb[2];
230+
runMemoryBill -= V2_FREE_TIER.memoryGb * V2_RATES.memoryGb[1];
231+
runMemoryBill = Math.max(runMemoryBill, 0);
232+
233+
let runCpuBill =
234+
usage["run"][1].cpu * V2_RATES.idleVCpu[1] + usage["run"][2].cpu * V2_RATES.idleVCpu[2];
235+
runCpuBill -= V2_FREE_TIER.vCpu * V2_RATES.vCpu[1];
236+
runCpuBill = Math.max(runCpuBill, 0);
237+
238+
return v1MemoryBill + v1CpuBill + v2MemoryBill + v2CpuBill + runMemoryBill + runCpuBill;
222239
}

src/deploy/functions/release/fabricator.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,10 @@ export class Fabricator {
183183
await this.createV1Function(endpoint, scraperV1);
184184
} else if (endpoint.platform === "gcfv2") {
185185
await this.createV2Function(endpoint, scraperV2);
186+
} else if (endpoint.platform === "run") {
187+
throw new FirebaseError("Creating new Cloud Run functions is not supported yet.", {
188+
exit: 1,
189+
});
186190
} else {
187191
assertExhaustive(endpoint.platform);
188192
}
@@ -206,6 +210,8 @@ export class Fabricator {
206210
await this.updateV1Function(update.endpoint, scraperV1);
207211
} else if (update.endpoint.platform === "gcfv2") {
208212
await this.updateV2Function(update.endpoint, scraperV2);
213+
} else if (update.endpoint.platform === "run") {
214+
throw new FirebaseError("Updating Cloud Run functions is not supported yet.", { exit: 1 });
209215
} else {
210216
assertExhaustive(update.endpoint.platform);
211217
}
@@ -216,10 +222,13 @@ export class Fabricator {
216222
async deleteEndpoint(endpoint: backend.Endpoint): Promise<void> {
217223
await this.deleteTrigger(endpoint);
218224
if (endpoint.platform === "gcfv1") {
219-
await this.deleteV1Function(endpoint);
220-
} else {
221-
await this.deleteV2Function(endpoint);
225+
return this.deleteV1Function(endpoint);
226+
} else if (endpoint.platform === "gcfv2") {
227+
return this.deleteV2Function(endpoint);
228+
} else if (endpoint.platform === "run") {
229+
throw new FirebaseError("Deleting Cloud Run functions is not supported yet.", { exit: 1 });
222230
}
231+
assertExhaustive(endpoint.platform);
223232
}
224233

225234
async createV1Function(endpoint: backend.Endpoint, scraper: SourceTokenScraper): Promise<void> {
@@ -623,6 +632,11 @@ export class Fabricator {
623632
// Set/Delete trigger is responsible for wiring up a function with any trigger not owned
624633
// by the GCF API. This includes schedules, task queues, and blocking function triggers.
625634
async setTrigger(endpoint: backend.Endpoint): Promise<void> {
635+
if (endpoint.platform === "run") {
636+
throw new FirebaseError("Setting triggers for Cloud Run functions is not supported yet.", {
637+
exit: 1,
638+
});
639+
}
626640
if (backend.isScheduleTriggered(endpoint)) {
627641
if (endpoint.platform === "gcfv1") {
628642
await this.upsertScheduleV1(endpoint);
@@ -640,6 +654,11 @@ export class Fabricator {
640654
}
641655

642656
async deleteTrigger(endpoint: backend.Endpoint): Promise<void> {
657+
if (endpoint.platform === "run") {
658+
throw new FirebaseError("Deleting triggers for Cloud Run functions is not supported yet.", {
659+
exit: 1,
660+
});
661+
}
643662
if (backend.isScheduleTriggered(endpoint)) {
644663
if (endpoint.platform === "gcfv1") {
645664
await this.deleteScheduleV1(endpoint);

src/deploy/hosting/convertConfig.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,7 @@ describe("convertConfig", () => {
476476
unreachableRegions: {
477477
gcfV1: [],
478478
gcfV2: [],
479+
run: [],
479480
},
480481
};
481482
const deploy: HostingDeploy = {

src/experiments.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ export const ALL_EXPERIMENTS = experiments({
6565
default: true,
6666
public: true,
6767
},
68+
runfunctions: {
69+
shortDescription:
70+
"Functions created using the V2 API target Cloud Run Functions (not production ready)",
71+
public: false,
72+
},
6873

6974
// Emulator experiments
7075
emulatoruisnapshot: {

0 commit comments

Comments
 (0)