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 zone = "us-central1-a";
18
19const waitUntilGone = (autoscaler: string) =>
20 GcpEnvironment.current.pipe(
21 Effect.flatMap(({ project }) =>
22 compute.getAutoscalers({ project, zone, 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: 10,
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, replace, and delete an 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.InstanceGroupManager("Mig", {
61 zone,
62 instanceTemplate: template.templateName.as<string>(),
63 targetSize: 0,
64 });
65 const scaler = yield* GCP.Compute.Autoscaler("Scale", {
66 zone,
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.zone).toEqual(zone);
84 expect(created.scaler.description).toEqual("test scaler");
85 expect(created.scaler.labels).toMatchObject({ env: "test" });
86 expect(created.scaler.autoscalingPolicy?.maxNumReplicas).toEqual(1);
87 expect(created.scaler.autoscalingPolicy?.minNumReplicas).toEqual(0);
88 expect(created.scaler.autoscalingPolicy?.mode).toEqual("OFF");
89
90 const fetched = yield* compute.getAutoscalers({
91 project,
92 zone,
93 autoscaler: created.scaler.autoscalerName,
94 });
95 expect(fetched.name).toEqual(created.scaler.autoscalerName);
96 expect(fetched.description).toContain("[alchemy ");
97 expect(fetched.description).toContain("test scaler");
98 expect(fetched.description).toContain("env=test");
99 expect(fetched.autoscalingPolicy?.maxNumReplicas).toEqual(1);
100 expect(fetched.autoscalingPolicy?.coolDownPeriodSec).toEqual(60);
101 expect(
102 fetched.autoscalingPolicy?.cpuUtilization?.utilizationTarget,
103 ).toEqual(0.6);
104 expect(fetched.target).toEqual(expect.any(String));
105
106 const updated = yield* stack.deploy(
107 Effect.gen(function* () {
108 const template = yield* GCP.Compute.InstanceTemplate(
109 "Tpl",
110 templateProps,
111 );
112 const mig = yield* GCP.Compute.InstanceGroupManager("Mig", {
113 zone,
114 instanceTemplate: template.templateName.as<string>(),
115 targetSize: 0,
116 });
117 const scaler = yield* GCP.Compute.Autoscaler("Scale", {
118 autoscalerName: created.scaler.autoscalerName,
119 zone,
120 target: mig.selfLink.as<string>(),
121 description: "updated scaler",
122 labels: { env: "prod", role: "scale" },
123 autoscalingPolicy: {
124 minNumReplicas: 0,
125 maxNumReplicas: 2,
126 coolDownPeriodSec: 90,
127 mode: "OFF",
128 cpuUtilization: { utilizationTarget: 0.5 },
129 },
130 });
131 return { template, mig, scaler };
132 }),
133 );
134
135 expect(updated.scaler.autoscalerName).toEqual(
136 created.scaler.autoscalerName,
137 );
138 expect(updated.scaler.autoscalerId).toEqual(created.scaler.autoscalerId);
139 expect(updated.scaler.description).toEqual("updated scaler");
140 expect(updated.scaler.labels).toMatchObject({
141 env: "prod",
142 role: "scale",
143 });
144 expect(updated.scaler.autoscalingPolicy?.maxNumReplicas).toEqual(2);
145 expect(updated.scaler.autoscalingPolicy?.coolDownPeriodSec).toEqual(90);
146
147 const refetched = yield* compute.getAutoscalers({
148 project,
149 zone,
150 autoscaler: updated.scaler.autoscalerName,
151 });
152 expect(refetched.id).toEqual(created.scaler.autoscalerId);
153 expect(refetched.description).toContain("updated scaler");
154 expect(refetched.description).toContain("env=prod");
155 expect(refetched.autoscalingPolicy?.maxNumReplicas).toEqual(2);
156 expect(
157 refetched.autoscalingPolicy?.cpuUtilization?.utilizationTarget,
158 ).toEqual(0.5);
159
160 const replacedName = `${created.scaler.autoscalerName.replace(/-+$/, "").slice(0, 61)}-r`;
161 const replaced = yield* stack.deploy(
162 Effect.gen(function* () {
163 const template = yield* GCP.Compute.InstanceTemplate(
164 "Tpl",
165 templateProps,
166 );
167 const mig = yield* GCP.Compute.InstanceGroupManager("Mig", {
168 zone,
169 instanceTemplate: template.templateName.as<string>(),
170 targetSize: 0,
171 });
172 const scaler = yield* GCP.Compute.Autoscaler("Scale", {
173 autoscalerName: replacedName,
174 zone,
175 target: mig.selfLink.as<string>(),
176 description: "replaced scaler",
177 labels: { env: "prod", role: "scale" },
178 autoscalingPolicy: {
179 minNumReplicas: 0,
180 maxNumReplicas: 2,
181 coolDownPeriodSec: 90,
182 mode: "OFF",
183 cpuUtilization: { utilizationTarget: 0.5 },
184 },
185 });
186 return { template, mig, scaler };
187 }),
188 );
189
190 expect(replaced.scaler.autoscalerName).toEqual(replacedName);
191 expect(replaced.scaler.description).toEqual("replaced scaler");
192 expect(replaced.scaler.autoscalerId).not.toEqual(
193 created.scaler.autoscalerId,
194 );
195
196 const replacedFetched = yield* compute.getAutoscalers({
197 project,
198 zone,
199 autoscaler: replaced.scaler.autoscalerName,
200 });
201 expect(replacedFetched.name).toEqual(replacedName);
202 expect(replacedFetched.description).toContain("replaced scaler");
203
204 const oldGone = yield* waitUntilGone(created.scaler.autoscalerName);
205 expect(oldGone).toEqual("gone");
206
207 yield* stack.destroy();
208
209 const gone = yield* waitUntilGone(replaced.scaler.autoscalerName);
210 expect(gone).toEqual("gone");
211 }).pipe(logLevel),
212 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 120_000 },
213);