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";
9import { DEFAULT_NETWORK, defaultNetworkSelfLink } from "../networkQuota.ts";
10
11const { test } = Test.make({ providers: GCP.providers() });
12
13const logLevel = Effect.provideService(
14 MinimumLogLevel,
15 process.env.DEBUG ? "Debug" : "Info",
16);
17
18const region = "us-central1";
19
20const waitUntilGone = (compositeHealthCheck: string) =>
21 GcpEnvironment.current.pipe(
22 Effect.flatMap(({ project }) =>
23 compute
24 .getRegionCompositeHealthChecks({
25 project,
26 region,
27 compositeHealthCheck,
28 })
29 .pipe(
30 Effect.as("found" as const),
31 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
32 Effect.repeat({
33 schedule: Schedule.spaced("1 second"),
34 until: (status) => status === "gone",
35 times: 10,
36 }),
37 ),
38 ),
39 );
40
41test.provider(
42 "getRegionCompositeHealthChecks on a missing check fails with NotFound",
43 (stack) =>
44 Effect.gen(function* () {
45 const { project } = yield* GcpEnvironment.current;
46 yield* stack.destroy();
47
48 const error = yield* Effect.flip(
49 compute.getRegionCompositeHealthChecks({
50 project,
51 region,
52 compositeHealthCheck: "alchemy-missing-chc",
53 }),
54 );
55 expect(error._tag).toBe("NotFound");
56
57 yield* stack.destroy();
58 }).pipe(logLevel),
59 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 90_000 },
60);
61
62test.provider(
63 "insertRegionCompositeHealthChecks with a missing health source fails with NotFound",
64 () =>
65 Effect.gen(function* () {
66 const { project } = yield* GcpEnvironment.current;
67 const error = yield* Effect.flip(
68 compute.insertRegionCompositeHealthChecks({
69 project,
70 region,
71 body: {
72 name: "alchemy-chc-probe",
73 description: "alchemy entitlement probe",
74 healthDestination: `projects/${project}/regions/${region}/forwardingRules/does-not-exist`,
75 healthSources: [
76 `projects/${project}/regions/${region}/healthSources/does-not-exist`,
77 ],
78 },
79 }),
80 );
81 expect(error._tag).toEqual("NotFound");
82 }).pipe(logLevel),
83 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 60_000 },
84);
85
86test.provider(
87 "create, update, and delete a regional composite health check",
88 (stack) =>
89 Effect.gen(function* () {
90 const { project } = yield* GcpEnvironment.current;
91 yield* stack.destroy();
92
93 const created = yield* stack.deploy(
94 Effect.gen(function* () {
95 const subnet = yield* GCP.Compute.Subnetwork("IlbSubnet", {
96 network: DEFAULT_NETWORK,
97 region,
98 ipCidrRange: "172.20.1.0/24",
99 });
100 const check = yield* GCP.Compute.RegionHealthCheck("Probe", {
101 region,
102 description: "tcp probe",
103 type: "TCP",
104 tcpHealthCheck: { port: 80 },
105 });
106 const backend = yield* GCP.Compute.RegionBackendService(
107 "IlbBackend",
108 {
109 region,
110 protocol: "TCP",
111 loadBalancingScheme: "INTERNAL",
112 network: defaultNetworkSelfLink(project),
113 healthChecks: [check.selfLink.as<string>()],
114 description: "ilb backend",
115 },
116 );
117 const rule = yield* GCP.Compute.ForwardingRule("IlbRule", {
118 region,
119 loadBalancingScheme: "INTERNAL",
120 backendService: backend.selfLink.as<string>(),
121 network: defaultNetworkSelfLink(project),
122 subnetwork: subnet.selfLink.as<string>(),
123 ipProtocol: "TCP",
124 allPorts: true,
125 });
126 const policy = yield* GCP.Compute.RegionHealthAggregationPolicy(
127 "Agg",
128 { region, description: "backend rollup" },
129 );
130 const source = yield* GCP.Compute.RegionHealthSource("Src", {
131 region,
132 sources: [backend.selfLink.as<string>()],
133 healthAggregationPolicy: policy.selfLink.as<string>(),
134 description: "ilb source",
135 });
136 const composite = yield* GCP.Compute.RegionCompositeHealthCheck(
137 "Comp",
138 {
139 region,
140 healthDestination: rule.selfLink.as<string>(),
141 healthSources: [source.selfLink.as<string>()],
142 description: "and backends",
143 },
144 );
145 return {
146 subnet,
147 check,
148 backend,
149 rule,
150 policy,
151 source,
152 composite,
153 };
154 }),
155 );
156
157 expect(created.composite.healthCheckName).toEqual(expect.any(String));
158 expect(created.composite.region).toEqual(region);
159 expect(created.composite.description).toEqual("and backends");
160 expect(created.composite.healthSources.length).toBeGreaterThan(0);
161
162 const fetched = yield* compute.getRegionCompositeHealthChecks({
163 project: created.composite.project,
164 region,
165 compositeHealthCheck: created.composite.healthCheckName,
166 });
167 expect(fetched.name).toEqual(created.composite.healthCheckName);
168 expect(fetched.description).toContain("[alchemy ");
169 expect(fetched.description).toContain("and backends");
170
171 const updated = yield* stack.deploy(
172 Effect.gen(function* () {
173 const subnet = yield* GCP.Compute.Subnetwork("IlbSubnet", {
174 subnetworkName: created.subnet.subnetworkName,
175 network: DEFAULT_NETWORK,
176 region,
177 ipCidrRange: "172.20.1.0/24",
178 });
179 const check = yield* GCP.Compute.RegionHealthCheck("Probe", {
180 healthCheckName: created.check.healthCheckName,
181 region,
182 description: "tcp probe",
183 type: "TCP",
184 tcpHealthCheck: { port: 80 },
185 });
186 const backend = yield* GCP.Compute.RegionBackendService(
187 "IlbBackend",
188 {
189 name: created.backend.name,
190 region,
191 protocol: "TCP",
192 loadBalancingScheme: "INTERNAL",
193 network: defaultNetworkSelfLink(project),
194 healthChecks: [check.selfLink.as<string>()],
195 description: "ilb backend",
196 },
197 );
198 const rule = yield* GCP.Compute.ForwardingRule("IlbRule", {
199 forwardingRuleName: created.rule.forwardingRuleName,
200 region,
201 loadBalancingScheme: "INTERNAL",
202 backendService: backend.selfLink.as<string>(),
203 network: defaultNetworkSelfLink(project),
204 subnetwork: subnet.selfLink.as<string>(),
205 ipProtocol: "TCP",
206 allPorts: true,
207 });
208 const policy = yield* GCP.Compute.RegionHealthAggregationPolicy(
209 "Agg",
210 {
211 policyName: created.policy.policyName,
212 region,
213 description: "backend rollup",
214 },
215 );
216 const source = yield* GCP.Compute.RegionHealthSource("Src", {
217 sourceName: created.source.sourceName,
218 region,
219 sources: [backend.selfLink.as<string>()],
220 healthAggregationPolicy: policy.selfLink.as<string>(),
221 description: "ilb source",
222 });
223 return yield* GCP.Compute.RegionCompositeHealthCheck("Comp", {
224 healthCheckName: created.composite.healthCheckName,
225 region,
226 healthDestination: rule.selfLink.as<string>(),
227 healthSources: [source.selfLink.as<string>()],
228 description: "updated composite",
229 });
230 }),
231 );
232
233 expect(updated.healthCheckName).toEqual(
234 created.composite.healthCheckName,
235 );
236 expect(updated.description).toEqual("updated composite");
237
238 yield* stack.destroy();
239
240 const gone = yield* waitUntilGone(created.composite.healthCheckName);
241 expect(gone).toEqual("gone");
242 }).pipe(logLevel),
243 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 240_000 },
244);