1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as compute from "@distilled.cloud/gcp/compute_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import { MinimumLogLevel } from "effect/References";
7import * as Schedule from "effect/Schedule";
8import { GcpEnvironment } from "@/GCP/Environment";
9
10const { test } = Test.make({ providers: GCP.providers() });
11
12const logLevel = Effect.provideService(
13 MinimumLogLevel,
14 process.env.DEBUG ? "Debug" : "Info",
15);
16
17const region = "us-central1";
18
19const waitUntilGone = (autoscaler: string) =>
20 GcpEnvironment.current.pipe(
21 Effect.flatMap(({ project }) =>
22 compute.getRegionAutoscalers({ project, region, autoscaler }).pipe(
23 Effect.as("found" as const),
24 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
25 Effect.repeat({
26 schedule: Schedule.spaced("1 second"),
27 until: (status) => status === "gone",
28 times: 15,
29 }),
30 ),
31 ),
32 );
33
34const templateProps: GCP.Compute.InstanceTemplateProps = {
35 machineType: "e2-micro",
36 disks: [
37 {
38 boot: true,
39 autoDelete: true,
40 sourceImage: "projects/debian-cloud/global/images/family/debian-12",
41 diskSizeGb: 10,
42 },
43 ],
44 networkInterfaces: [{ network: "global/networks/default" }],
45};
46
47test.provider.skipIf(!!process.env.FAST)(
48 "create, update, and delete a region autoscaler",
49 (stack) =>
50 Effect.gen(function* () {
51 const { project } = yield* GcpEnvironment.current;
52 yield* stack.destroy();
53
54 const created = yield* stack.deploy(
55 Effect.gen(function* () {
56 const template = yield* GCP.Compute.InstanceTemplate(
57 "Tpl",
58 templateProps,
59 );
60 const mig = yield* GCP.Compute.RegionInstanceGroupManager("Mig", {
61 region,
62 instanceTemplate: template.templateName.as<string>(),
63 targetSize: 0,
64 });
65 const scaler = yield* GCP.Compute.RegionAutoscaler("Scale", {
66 region,
67 target: mig.selfLink.as<string>(),
68 description: "test scaler",
69 labels: { env: "test" },
70 autoscalingPolicy: {
71 minNumReplicas: 0,
72 maxNumReplicas: 1,
73 coolDownPeriodSec: 60,
74 mode: "OFF",
75 cpuUtilization: { utilizationTarget: 0.6 },
76 },
77 });
78 return { template, mig, scaler };
79 }),
80 );
81
82 expect(created.scaler.autoscalerName).toEqual(expect.any(String));
83 expect(created.scaler.region).toEqual(region);
84 expect(created.scaler.project).toEqual(project);
85 expect(created.scaler.description).toEqual("test scaler");
86 expect(created.scaler.labels).toMatchObject({ env: "test" });
87 expect(created.scaler.target).toContain(created.mig.managerName);
88 expect(created.scaler.autoscalingPolicy?.maxNumReplicas).toEqual(1);
89 expect(created.scaler.autoscalingPolicy?.minNumReplicas).toEqual(0);
90 expect(created.scaler.autoscalingPolicy?.mode).toEqual("OFF");
91
92 const fetched = yield* compute.getRegionAutoscalers({
93 project,
94 region,
95 autoscaler: created.scaler.autoscalerName,
96 });
97 expect(fetched.name).toEqual(created.scaler.autoscalerName);
98 expect(fetched.description).toContain("[alchemy ");
99 expect(fetched.description).toContain("test scaler");
100 expect(fetched.description).toContain("env=test");
101 expect(fetched.autoscalingPolicy?.maxNumReplicas).toEqual(1);
102 expect(fetched.autoscalingPolicy?.coolDownPeriodSec).toEqual(60);
103 expect(
104 fetched.autoscalingPolicy?.cpuUtilization?.utilizationTarget,
105 ).toEqual(0.6);
106 expect(fetched.target).toEqual(expect.any(String));
107 expect(fetched.target).toContain(created.mig.managerName);
108
109 const updated = yield* stack.deploy(
110 Effect.gen(function* () {
111 const template = yield* GCP.Compute.InstanceTemplate(
112 "Tpl",
113 templateProps,
114 );
115 const mig = yield* GCP.Compute.RegionInstanceGroupManager("Mig", {
116 region,
117 instanceTemplate: template.templateName.as<string>(),
118 targetSize: 0,
119 });
120 const scaler = yield* GCP.Compute.RegionAutoscaler("Scale", {
121 region,
122 target: mig.selfLink.as<string>(),
123 description: "updated scaler",
124 labels: { env: "prod", role: "scale" },
125 autoscalingPolicy: {
126 minNumReplicas: 0,
127 maxNumReplicas: 2,
128 coolDownPeriodSec: 90,
129 mode: "OFF",
130 cpuUtilization: { utilizationTarget: 0.5 },
131 },
132 });
133 return { template, mig, scaler };
134 }),
135 );
136
137 expect(updated.scaler.autoscalerName).toEqual(
138 created.scaler.autoscalerName,
139 );
140 expect(updated.scaler.autoscalerId).toEqual(created.scaler.autoscalerId);
141 expect(updated.scaler.description).toEqual("updated scaler");
142 expect(updated.scaler.labels).toMatchObject({
143 env: "prod",
144 role: "scale",
145 });
146 expect(updated.scaler.autoscalingPolicy?.maxNumReplicas).toEqual(2);
147 expect(updated.scaler.autoscalingPolicy?.coolDownPeriodSec).toEqual(90);
148
149 const refetched = yield* compute.getRegionAutoscalers({
150 project,
151 region,
152 autoscaler: updated.scaler.autoscalerName,
153 });
154 expect(refetched.id).toEqual(created.scaler.autoscalerId);
155 expect(refetched.description).toContain("updated scaler");
156 expect(refetched.description).toContain("env=prod");
157 expect(refetched.autoscalingPolicy?.maxNumReplicas).toEqual(2);
158 expect(
159 refetched.autoscalingPolicy?.cpuUtilization?.utilizationTarget,
160 ).toEqual(0.5);
161
162 yield* stack.destroy();
163
164 const gone = yield* waitUntilGone(created.scaler.autoscalerName);
165 expect(gone).toEqual("gone");
166 }).pipe(logLevel),
167 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 120_000 },
168);