1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as baremetalsolution from "@distilled.cloud/gcp/baremetalsolution_v2";
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// Bare Metal Solution is contracted physical hardware (real cost) and the API
18// is off on the testing project (ServiceDisabled: "Bare Metal Solution API has not
19// been used in project ... or it is disabled."). Set
20// GCP_TEST_BAREMETALSOLUTION_NETWORK to a provisioned BMS client network on an
21// entitled project to run the lifecycle.
22const bmsNetwork = process.env.GCP_TEST_BAREMETALSOLUTION_NETWORK;
23const runLifecycle = !!bmsNetwork;
24
25const missingNetwork = (projectId: string) =>
26 `projects/${projectId}/locations/us-central1/networks/alchemy-missing-bms-net`;
27
28const dummyClientsFor = (project: string) => [
29 {
30 network: missingNetwork(project),
31 allowedClientsCidr: "10.200.0.0/28",
32 mountPermissions: "READ_WRITE" as const,
33 noRootSquash: true,
34 },
35];
36
37const waitUntilGone = (name: string) =>
38 baremetalsolution.getProjectsLocationsNfsShares({ name }).pipe(
39 Effect.as("found" as const),
40 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
41 Effect.repeat({
42 schedule: Schedule.spaced("2 seconds"),
43 until: (status) => status === "gone",
44 times: 10,
45 }),
46 );
47
48test.provider(
49 "getProjectsLocationsNfsShares on a missing share fails with a typed tag",
50 (stack) =>
51 Effect.gen(function* () {
52 const { project } = yield* GcpEnvironment.current;
53 yield* stack.destroy();
54
55 const error = yield* Effect.flip(
56 baremetalsolution.getProjectsLocationsNfsShares({
57 name: `projects/${project}/locations/us-central1/nfsShares/alchemy-bms-missing`,
58 }),
59 );
60 expect(error._tag).toEqual("ServiceDisabled");
61
62 yield* stack.destroy();
63 }).pipe(logLevel),
64 {
65 tags: ["provider:gcp", "provider:gcp:baremetalsolution", "live"],
66 timeout: 90_000,
67 },
68);
69
70test.provider.skipIf(runLifecycle)(
71 "create is rejected with ServiceDisabled when the Bare Metal Solution API is disabled",
72 (stack) =>
73 Effect.gen(function* () {
74 const { project } = yield* GcpEnvironment.current;
75 yield* stack.destroy();
76
77 const error = yield* Effect.flip(
78 stack.deploy(
79 Effect.gen(function* () {
80 return yield* GCP.BareMetalSolution.NfsShare("Share", {
81 requestedSizeGib: 100,
82 storageType: "SSD",
83 allowedClients: dummyClientsFor(project),
84 labels: { env: "test" },
85 });
86 }),
87 ),
88 );
89 expect(error._tag).toEqual("ServiceDisabled");
90
91 yield* stack.destroy();
92 }).pipe(logLevel),
93 {
94 tags: ["provider:gcp", "provider:gcp:baremetalsolution", "live"],
95 timeout: 90_000,
96 },
97);
98
99test.provider.skipIf(!runLifecycle)(
100 "create, update, and delete an NFS share",
101 (stack) =>
102 Effect.gen(function* () {
103 yield* stack.destroy();
104
105 const network = bmsNetwork!;
106
107 const created = yield* stack.deploy(
108 Effect.gen(function* () {
109 return yield* GCP.BareMetalSolution.NfsShare("Share", {
110 requestedSizeGib: 100,
111 storageType: "SSD",
112 allowedClients: [
113 {
114 network,
115 allowedClientsCidr: "10.200.0.0/28",
116 mountPermissions: "READ_WRITE",
117 noRootSquash: true,
118 },
119 ],
120 labels: { env: "test" },
121 });
122 }),
123 );
124
125 expect(created.name).toContain("/nfsShares/");
126 expect(created.nfsShareId).toEqual(expect.any(String));
127 expect(created.location).toEqual("us-central1");
128 expect(created.labels).toMatchObject({ env: "test" });
129
130 const fetched = yield* baremetalsolution.getProjectsLocationsNfsShares({
131 name: created.name,
132 });
133 expect(fetched.name).toEqual(created.name);
134 expect(fetched.labels?.env).toEqual("test");
135
136 const updated = yield* stack.deploy(
137 Effect.gen(function* () {
138 return yield* GCP.BareMetalSolution.NfsShare("Share", {
139 nfsShareId: created.nfsShareId,
140 location: created.location,
141 requestedSizeGib: created.requestedSizeGib,
142 storageType: created.storageType,
143 allowedClients: [
144 {
145 network,
146 allowedClientsCidr: "10.200.0.0/28",
147 mountPermissions: "READ_ONLY",
148 noRootSquash: true,
149 },
150 ],
151 labels: { env: "prod", team: "storage" },
152 });
153 }),
154 );
155
156 expect(updated.name).toEqual(created.name);
157 expect(updated.labels).toMatchObject({ env: "prod", team: "storage" });
158
159 const refetched = yield* baremetalsolution.getProjectsLocationsNfsShares({
160 name: created.name,
161 });
162 expect(refetched.labels?.env).toEqual("prod");
163 expect(refetched.labels?.team).toEqual("storage");
164
165 yield* stack.destroy();
166
167 const gone = yield* waitUntilGone(created.name);
168 expect(gone).toEqual("gone");
169 }).pipe(logLevel),
170 {
171 tags: ["provider:gcp", "provider:gcp:baremetalsolution", "live"],
172 timeout: 120_000,
173 },
174);