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
17// A sole-tenant node bills for the whole host (several dollars per hour).
18// Set GCP_TEST_COMPUTE_NODE=1 to opt in.
19const runLifecycle = !!process.env.GCP_TEST_COMPUTE_NODE && !process.env.FAST;
20
21const zone = "us-central1-a";
22const region = "us-central1";
23
24const waitUntilGone = (nodeGroup: string) =>
25 GcpEnvironment.current.pipe(
26 Effect.flatMap(({ project }) =>
27 compute.getNodeGroups({ project, zone, nodeGroup }).pipe(
28 Effect.as("found" as const),
29 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
30 Effect.repeat({
31 schedule: Schedule.spaced("1 second"),
32 until: (status) => status === "gone",
33 times: 10,
34 }),
35 ),
36 ),
37 );
38
39test.provider(
40 "getNodeGroups on a missing group fails with NotFound",
41 (stack) =>
42 Effect.gen(function* () {
43 const { project } = yield* GcpEnvironment.current;
44 yield* stack.destroy();
45
46 const error = yield* Effect.flip(
47 compute.getNodeGroups({
48 project,
49 zone,
50 nodeGroup: "alchemy-missing-ng",
51 }),
52 );
53 expect(error._tag).toBe("NotFound");
54
55 yield* stack.destroy();
56 }).pipe(logLevel),
57 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 90_000 },
58);
59
60test.provider(
61 "insertNodeGroups with a malformed node template fails with BadRequest",
62 () =>
63 Effect.gen(function* () {
64 const { project } = yield* GcpEnvironment.current;
65 const error = yield* Effect.flip(
66 compute.insertNodeGroups({
67 project,
68 zone,
69 initialNodeCount: 0,
70 body: {
71 name: "alchemy-ng-probe",
72 description: "alchemy entitlement probe",
73 nodeTemplate: "does-not-exist",
74 },
75 }),
76 );
77 expect(error._tag).toEqual("BadRequest");
78 }).pipe(logLevel),
79 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 60_000 },
80);
81
82test.provider.skipIf(!runLifecycle)(
83 "create, update, and delete a node group",
84 (stack) =>
85 Effect.gen(function* () {
86 yield* stack.destroy();
87
88 const created = yield* stack.deploy(
89 Effect.gen(function* () {
90 const template = yield* GCP.Compute.NodeTemplate("SoleTenant", {
91 region,
92 nodeType: "n2-node-80-640",
93 description: "prod sole tenant",
94 });
95 const group = yield* GCP.Compute.NodeGroup("SoleTenant", {
96 zone,
97 nodeTemplate: template.nodeTemplateName,
98 initialNodeCount: 0,
99 description: "prod sole tenant",
100 });
101 return { template, group };
102 }),
103 );
104
105 expect(created.group.nodeGroupName).toEqual(expect.any(String));
106 expect(created.group.zone).toEqual(zone);
107 expect(created.group.description).toEqual("prod sole tenant");
108
109 const fetched = yield* compute.getNodeGroups({
110 project: created.group.project,
111 zone,
112 nodeGroup: created.group.nodeGroupName,
113 });
114 expect(fetched.name).toEqual(created.group.nodeGroupName);
115 expect(fetched.description).toContain("[alchemy ");
116
117 const updated = yield* stack.deploy(
118 Effect.gen(function* () {
119 const template = yield* GCP.Compute.NodeTemplate("SoleTenant", {
120 nodeTemplateName: created.template.nodeTemplateName,
121 region,
122 nodeType: "n2-node-80-640",
123 description: "prod sole tenant",
124 });
125 return yield* GCP.Compute.NodeGroup("SoleTenant", {
126 nodeGroupName: created.group.nodeGroupName,
127 zone,
128 nodeTemplate: template.nodeTemplateName,
129 initialNodeCount: 0,
130 description: "updated sole tenant",
131 });
132 }),
133 );
134 expect(updated.description).toEqual("updated sole tenant");
135
136 yield* stack.destroy();
137 const gone = yield* waitUntilGone(created.group.nodeGroupName);
138 expect(gone).toEqual("gone");
139 }).pipe(logLevel),
140 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 120_000 },
141);