@shipfox/api-runners 12.6.0 → 12.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +8 -0
- package/dist/db/job-executions.d.ts +3 -0
- package/dist/db/job-executions.d.ts.map +1 -1
- package/dist/db/job-executions.js +23 -1
- package/dist/db/job-executions.js.map +1 -1
- package/dist/db/provisioner-tokens.d.ts.map +1 -1
- package/dist/db/provisioner-tokens.js +14 -2
- package/dist/db/provisioner-tokens.js.map +1 -1
- package/dist/db/reservations.d.ts +10 -0
- package/dist/db/reservations.d.ts.map +1 -1
- package/dist/db/reservations.js +137 -17
- package/dist/db/reservations.js.map +1 -1
- package/dist/db/runner-assignments.d.ts.map +1 -1
- package/dist/db/runner-assignments.js +8 -5
- package/dist/db/runner-assignments.js.map +1 -1
- package/dist/db/runner-instances.d.ts +0 -1
- package/dist/db/runner-instances.d.ts.map +1 -1
- package/dist/db/runner-instances.js +85 -100
- package/dist/db/runner-instances.js.map +1 -1
- package/dist/db/runner-states.d.ts +3 -1
- package/dist/db/runner-states.d.ts.map +1 -1
- package/dist/db/runner-states.js +6 -4
- package/dist/db/runner-states.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/db/job-executions.test.ts +119 -0
- package/src/db/job-executions.ts +24 -2
- package/src/db/provisioner-tokens.test.ts +19 -0
- package/src/db/provisioner-tokens.ts +13 -2
- package/src/db/reservations.test.ts +138 -0
- package/src/db/reservations.ts +311 -35
- package/src/db/runner-assignments.test.ts +20 -0
- package/src/db/runner-assignments.ts +6 -7
- package/src/db/runner-instances.test.ts +250 -15
- package/src/db/runner-instances.ts +136 -198
- package/src/db/runner-states.test.ts +16 -0
- package/src/db/runner-states.ts +9 -4
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -2,6 +2,10 @@ import {pgClient} from '@shipfox/node-postgres';
|
|
|
2
2
|
import {and, desc, eq, inArray, or, sql} from 'drizzle-orm';
|
|
3
3
|
import {db} from '#db/db.js';
|
|
4
4
|
import {createRunnerSessionConsumingEphemeralToken} from '#db/ephemeral-registration-tokens.js';
|
|
5
|
+
import {
|
|
6
|
+
pollDemandAndReserve,
|
|
7
|
+
releaseTerminalRunnerInstanceReservationsByIds,
|
|
8
|
+
} from '#db/reservations.js';
|
|
5
9
|
import {
|
|
6
10
|
attachRunnerInstanceProviderId,
|
|
7
11
|
countStaleEnrolledRunnerInstances,
|
|
@@ -22,6 +26,7 @@ import {runnerSessions} from '#db/schema/runner-sessions.js';
|
|
|
22
26
|
import {runningJobExecutions} from '#db/schema/running-job-executions.js';
|
|
23
27
|
import {
|
|
24
28
|
ephemeralRegistrationTokenFactory,
|
|
29
|
+
pendingJobFactory,
|
|
25
30
|
providerRunnerFactory,
|
|
26
31
|
provisionerTokenFactory,
|
|
27
32
|
reservationFactory,
|
|
@@ -221,6 +226,39 @@ describe('reportRunnerInstances', () => {
|
|
|
221
226
|
expect(rows[0]?.reason).toBe('late stale failure');
|
|
222
227
|
});
|
|
223
228
|
|
|
229
|
+
it('does not release a reservation for a terminal report older than the projection', async () => {
|
|
230
|
+
const reservationId = await createReservation(1);
|
|
231
|
+
const currentReportedAt = new Date('2025-01-01T00:01:00.000Z');
|
|
232
|
+
await providerRunnerFactory.create({
|
|
233
|
+
workspaceId,
|
|
234
|
+
provisionerId,
|
|
235
|
+
providerRunnerId: 'stale-terminal-runner',
|
|
236
|
+
reservationId,
|
|
237
|
+
state: 'running',
|
|
238
|
+
reportedAt: currentReportedAt,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const result = await reportRunnerInstances({
|
|
242
|
+
scope: 'workspace',
|
|
243
|
+
workspaceId,
|
|
244
|
+
provisionerId,
|
|
245
|
+
events: [
|
|
246
|
+
event({
|
|
247
|
+
providerRunnerId: 'stale-terminal-runner',
|
|
248
|
+
reservationId,
|
|
249
|
+
state: 'failed',
|
|
250
|
+
reportedAt: new Date(currentReportedAt.getTime() - 1_000),
|
|
251
|
+
}),
|
|
252
|
+
],
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const [providerRunner] = await providerRunnerRowsFor({workspaceId, provisionerId});
|
|
256
|
+
const [reservation] = await reservationRowsFor({workspaceId, provisionerId});
|
|
257
|
+
expect(result).toEqual({accepted: 1, reservationsReleased: 0, terminateIntentsHonored: []});
|
|
258
|
+
expect(providerRunner).toMatchObject({state: 'failed', reservationReleasedAt: null});
|
|
259
|
+
expect(reservation?.count).toBe(1);
|
|
260
|
+
});
|
|
261
|
+
|
|
224
262
|
it('rejects older out-of-order events in the same lifecycle state', async () => {
|
|
225
263
|
const newest = new Date();
|
|
226
264
|
await reportRunnerInstances({
|
|
@@ -1036,8 +1074,16 @@ describe('reportRunnerInstances', () => {
|
|
|
1036
1074
|
expect(providerRunnerRows[0]?.reservationReleasedAt).toBeInstanceOf(Date);
|
|
1037
1075
|
});
|
|
1038
1076
|
|
|
1039
|
-
it('
|
|
1077
|
+
it('releases a reservation for a provisioned runner that already has a runner session', async () => {
|
|
1040
1078
|
const reservationId = await createReservation(1);
|
|
1079
|
+
const runnerSession = await runnerSessionFactory.create({workspaceId});
|
|
1080
|
+
await providerRunnerFactory.create({
|
|
1081
|
+
workspaceId,
|
|
1082
|
+
provisionerId,
|
|
1083
|
+
providerRunnerId: 'provisioned-runner-1',
|
|
1084
|
+
reservationId,
|
|
1085
|
+
state: 'running',
|
|
1086
|
+
});
|
|
1041
1087
|
|
|
1042
1088
|
const result = await reportRunnerInstances({
|
|
1043
1089
|
scope: 'workspace',
|
|
@@ -1048,16 +1094,55 @@ describe('reportRunnerInstances', () => {
|
|
|
1048
1094
|
providerRunnerId: 'provisioned-runner-1',
|
|
1049
1095
|
reservationId,
|
|
1050
1096
|
state: 'failed',
|
|
1051
|
-
runnerSessionId:
|
|
1097
|
+
runnerSessionId: runnerSession.id,
|
|
1052
1098
|
}),
|
|
1053
1099
|
],
|
|
1054
1100
|
});
|
|
1055
1101
|
|
|
1056
1102
|
const reservationRows = await reservationRowsFor({workspaceId, provisionerId});
|
|
1057
1103
|
const providerRunnerRows = await providerRunnerRowsFor({workspaceId, provisionerId});
|
|
1104
|
+
expect(result).toEqual({accepted: 1, reservationsReleased: 1, terminateIntentsHonored: []});
|
|
1105
|
+
expect(reservationRows).toHaveLength(0);
|
|
1106
|
+
expect(providerRunnerRows[0]?.runnerSessionId).toBe(runnerSession.id);
|
|
1107
|
+
expect(providerRunnerRows[0]?.reservationReleasedAt).toBeInstanceOf(Date);
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
it('keeps a reservation while a terminal runner still has a running job', async () => {
|
|
1111
|
+
const reservationId = await createReservation(1);
|
|
1112
|
+
const runnerSession = await runnerSessionFactory.create({workspaceId});
|
|
1113
|
+
await providerRunnerFactory.create({
|
|
1114
|
+
workspaceId,
|
|
1115
|
+
provisionerId,
|
|
1116
|
+
providerRunnerId: 'busy-terminal-runner',
|
|
1117
|
+
reservationId,
|
|
1118
|
+
runnerSessionId: runnerSession.id,
|
|
1119
|
+
state: 'running',
|
|
1120
|
+
});
|
|
1121
|
+
await insertRunningJobRow({
|
|
1122
|
+
workspaceId,
|
|
1123
|
+
provisionerId,
|
|
1124
|
+
providerRunnerId: 'busy-terminal-runner',
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
const result = await reportRunnerInstances({
|
|
1128
|
+
scope: 'workspace',
|
|
1129
|
+
workspaceId,
|
|
1130
|
+
provisionerId,
|
|
1131
|
+
events: [
|
|
1132
|
+
event({
|
|
1133
|
+
providerRunnerId: 'busy-terminal-runner',
|
|
1134
|
+
reservationId,
|
|
1135
|
+
state: 'failed',
|
|
1136
|
+
runnerSessionId: runnerSession.id,
|
|
1137
|
+
}),
|
|
1138
|
+
],
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
const [providerRunner] = await providerRunnerRowsFor({workspaceId, provisionerId});
|
|
1142
|
+
const [reservation] = await reservationRowsFor({workspaceId, provisionerId});
|
|
1058
1143
|
expect(result).toEqual({accepted: 1, reservationsReleased: 0, terminateIntentsHonored: []});
|
|
1059
|
-
expect(
|
|
1060
|
-
expect(
|
|
1144
|
+
expect(providerRunner).toMatchObject({state: 'failed', reservationReleasedAt: null});
|
|
1145
|
+
expect(reservation?.count).toBe(1);
|
|
1061
1146
|
});
|
|
1062
1147
|
|
|
1063
1148
|
it('uses the consumed ephemeral token session before releasing a terminal report', async () => {
|
|
@@ -1068,6 +1153,13 @@ describe('reportRunnerInstances', () => {
|
|
|
1068
1153
|
reservationId,
|
|
1069
1154
|
providerRunnerId: 'provisioned-runner-1',
|
|
1070
1155
|
});
|
|
1156
|
+
await providerRunnerFactory.create({
|
|
1157
|
+
workspaceId,
|
|
1158
|
+
provisionerId,
|
|
1159
|
+
providerRunnerId: 'provisioned-runner-1',
|
|
1160
|
+
reservationId,
|
|
1161
|
+
state: 'running',
|
|
1162
|
+
});
|
|
1071
1163
|
const session = await createRunnerSessionConsumingEphemeralToken({
|
|
1072
1164
|
ephemeralTokenId: token.id,
|
|
1073
1165
|
workspaceId,
|
|
@@ -1091,16 +1183,24 @@ describe('reportRunnerInstances', () => {
|
|
|
1091
1183
|
|
|
1092
1184
|
const reservationRows = await reservationRowsFor({workspaceId, provisionerId});
|
|
1093
1185
|
const providerRunnerRows = await providerRunnerRowsFor({workspaceId, provisionerId});
|
|
1094
|
-
expect(result).toEqual({accepted: 1, reservationsReleased:
|
|
1095
|
-
expect(reservationRows
|
|
1186
|
+
expect(result).toEqual({accepted: 1, reservationsReleased: 1, terminateIntentsHonored: []});
|
|
1187
|
+
expect(reservationRows).toHaveLength(0);
|
|
1096
1188
|
expect(providerRunnerRows[0]?.runnerSessionId).toBe(session.id);
|
|
1097
|
-
expect(providerRunnerRows[0]?.reservationReleasedAt).
|
|
1189
|
+
expect(providerRunnerRows[0]?.reservationReleasedAt).toBeInstanceOf(Date);
|
|
1098
1190
|
});
|
|
1099
1191
|
|
|
1100
1192
|
it('preserves claimed runner session metadata when a terminal state wins the batch', async () => {
|
|
1101
1193
|
const reservationId = await createReservation(1);
|
|
1102
1194
|
const runnerSessionId = crypto.randomUUID();
|
|
1103
1195
|
const reportedAt = new Date('2025-01-01T00:00:00.000Z');
|
|
1196
|
+
await providerRunnerFactory.create({
|
|
1197
|
+
workspaceId,
|
|
1198
|
+
provisionerId,
|
|
1199
|
+
providerRunnerId: 'provisioned-runner-1',
|
|
1200
|
+
reservationId,
|
|
1201
|
+
state: 'running',
|
|
1202
|
+
reportedAt: new Date(reportedAt.getTime() - 1_000),
|
|
1203
|
+
});
|
|
1104
1204
|
|
|
1105
1205
|
const result = await reportRunnerInstances({
|
|
1106
1206
|
scope: 'workspace',
|
|
@@ -1125,11 +1225,11 @@ describe('reportRunnerInstances', () => {
|
|
|
1125
1225
|
|
|
1126
1226
|
const reservationRows = await reservationRowsFor({workspaceId, provisionerId});
|
|
1127
1227
|
const providerRunnerRows = await providerRunnerRowsFor({workspaceId, provisionerId});
|
|
1128
|
-
expect(result).toEqual({accepted: 1, reservationsReleased:
|
|
1129
|
-
expect(reservationRows
|
|
1228
|
+
expect(result).toEqual({accepted: 1, reservationsReleased: 1, terminateIntentsHonored: []});
|
|
1229
|
+
expect(reservationRows).toHaveLength(0);
|
|
1130
1230
|
expect(providerRunnerRows[0]?.state).toBe('failed');
|
|
1131
1231
|
expect(providerRunnerRows[0]?.runnerSessionId).toBe(runnerSessionId);
|
|
1132
|
-
expect(providerRunnerRows[0]?.reservationReleasedAt).
|
|
1232
|
+
expect(providerRunnerRows[0]?.reservationReleasedAt).toBeInstanceOf(Date);
|
|
1133
1233
|
});
|
|
1134
1234
|
|
|
1135
1235
|
it('returns honored terminate intents only for the first active-to-terminated transition', async () => {
|
|
@@ -1642,7 +1742,6 @@ describe('reapStaleRunnerInstances', () => {
|
|
|
1642
1742
|
.values({
|
|
1643
1743
|
provisionerId: installationProvisioner.id,
|
|
1644
1744
|
intendedReservationId: reservation.id,
|
|
1645
|
-
providerRunnerId: 'stale-installation-runner-with-reservation',
|
|
1646
1745
|
state: 'starting',
|
|
1647
1746
|
reportedAt: staleAt(),
|
|
1648
1747
|
updatedAt: staleAt(),
|
|
@@ -1746,6 +1845,30 @@ describe('reapStaleRunnerInstances', () => {
|
|
|
1746
1845
|
expect(reservationRows.find((row) => row.id === reservationId)).toBeUndefined();
|
|
1747
1846
|
});
|
|
1748
1847
|
|
|
1848
|
+
it('drains stale terminal rows with unreleased session-linked reservations', async () => {
|
|
1849
|
+
const reservationId = await createReservation(1);
|
|
1850
|
+
const session = await createLinkedSession({
|
|
1851
|
+
providerRunnerId: 'legacy-terminal-runner',
|
|
1852
|
+
updatedAt: staleAt(),
|
|
1853
|
+
});
|
|
1854
|
+
await createRunnerInstance({
|
|
1855
|
+
providerRunnerId: 'legacy-terminal-runner',
|
|
1856
|
+
reservationId,
|
|
1857
|
+
runnerSessionId: session.id,
|
|
1858
|
+
state: 'failed',
|
|
1859
|
+
reportedAt: staleAt(),
|
|
1860
|
+
updatedAt: staleAt(),
|
|
1861
|
+
});
|
|
1862
|
+
|
|
1863
|
+
const result = await reapStaleRunnerInstances({thresholdSeconds: 60, limit: 100});
|
|
1864
|
+
|
|
1865
|
+
const [providerRunner] = await providerRunnerRowsFor({workspaceId, provisionerId});
|
|
1866
|
+
const reservationRows = await reservationRowsFor({workspaceId, provisionerId});
|
|
1867
|
+
expect(result).toEqual({reaped: 0, reservationsReleased: 1});
|
|
1868
|
+
expect(providerRunner?.reservationReleasedAt).toBeInstanceOf(Date);
|
|
1869
|
+
expect(reservationRows).toHaveLength(0);
|
|
1870
|
+
});
|
|
1871
|
+
|
|
1749
1872
|
it('flags expired releases and drains only the configured batch size', async () => {
|
|
1750
1873
|
const activeReservationId = await createReservation(2);
|
|
1751
1874
|
const expiredReservationId = await createReservation(1, {
|
|
@@ -1786,6 +1909,52 @@ describe('reapStaleRunnerInstances', () => {
|
|
|
1786
1909
|
).toBe(true);
|
|
1787
1910
|
});
|
|
1788
1911
|
|
|
1912
|
+
it('waits for reservation assignment locks before releasing terminal runners', async () => {
|
|
1913
|
+
const reservationId = await createReservation(1);
|
|
1914
|
+
const runner = await createRunnerInstance({
|
|
1915
|
+
providerRunnerId: 'terminal-assignment-race',
|
|
1916
|
+
reservationId,
|
|
1917
|
+
state: 'failed',
|
|
1918
|
+
reportedAt: staleAt(),
|
|
1919
|
+
updatedAt: staleAt(),
|
|
1920
|
+
});
|
|
1921
|
+
const releaseLock = deferred<void>();
|
|
1922
|
+
const lockHolderReady = deferred<void>();
|
|
1923
|
+
const lockHolder = db().transaction(async (tx) => {
|
|
1924
|
+
await tx.execute(
|
|
1925
|
+
sql`select pg_advisory_xact_lock(hashtext(${`runners_assignment:${provisionerId}:${reservationId}`}))`,
|
|
1926
|
+
);
|
|
1927
|
+
lockHolderReady.resolve();
|
|
1928
|
+
await releaseLock.promise;
|
|
1929
|
+
});
|
|
1930
|
+
|
|
1931
|
+
const cleanup = (async () => {
|
|
1932
|
+
await lockHolderReady.promise;
|
|
1933
|
+
return await db().transaction((tx) =>
|
|
1934
|
+
releaseTerminalRunnerInstanceReservationsByIds(tx, {
|
|
1935
|
+
workspaceId,
|
|
1936
|
+
provisionerId,
|
|
1937
|
+
runnerInstanceIds: [runner.id],
|
|
1938
|
+
requireUnlinkedSession: false,
|
|
1939
|
+
}),
|
|
1940
|
+
);
|
|
1941
|
+
})();
|
|
1942
|
+
|
|
1943
|
+
try {
|
|
1944
|
+
await waitForLockWait({queryLike: '%pg_advisory_xact_lock%'});
|
|
1945
|
+
const [beforeRunner] = await providerRunnerRowsFor({workspaceId, provisionerId});
|
|
1946
|
+
const [beforeReservation] = await reservationRowsFor({workspaceId, provisionerId});
|
|
1947
|
+
expect(beforeRunner?.reservationReleasedAt).toBeNull();
|
|
1948
|
+
expect(beforeReservation?.count).toBe(1);
|
|
1949
|
+
} finally {
|
|
1950
|
+
releaseLock.resolve();
|
|
1951
|
+
await Promise.all([cleanup, lockHolder]);
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
const [providerRunner] = await providerRunnerRowsFor({workspaceId, provisionerId});
|
|
1955
|
+
expect(providerRunner?.reservationReleasedAt).toBeInstanceOf(Date);
|
|
1956
|
+
});
|
|
1957
|
+
|
|
1789
1958
|
it('does not double-release reservations when terminal report and reaper queue on the workspace lock', async () => {
|
|
1790
1959
|
const reservationId = await createReservation(2);
|
|
1791
1960
|
await createRunnerInstance({
|
|
@@ -1973,6 +2142,58 @@ describe('reconcileRunnerInstances', () => {
|
|
|
1973
2142
|
expect(reservation?.count).toBe(1);
|
|
1974
2143
|
});
|
|
1975
2144
|
|
|
2145
|
+
it('releases reservations when reconciling stale installation runners', async () => {
|
|
2146
|
+
const reservationWorkspaceId = crypto.randomUUID();
|
|
2147
|
+
const [reservation] = await db()
|
|
2148
|
+
.insert(reservations)
|
|
2149
|
+
.values({
|
|
2150
|
+
workspaceId: reservationWorkspaceId,
|
|
2151
|
+
provisionerId,
|
|
2152
|
+
requiredLabels: ['linux'],
|
|
2153
|
+
count: 1,
|
|
2154
|
+
expiresAt: new Date(Date.now() + 60_000),
|
|
2155
|
+
})
|
|
2156
|
+
.returning();
|
|
2157
|
+
if (!reservation) throw new Error('Expected reservation');
|
|
2158
|
+
const [runner] = await db()
|
|
2159
|
+
.insert(providerRunners)
|
|
2160
|
+
.values({
|
|
2161
|
+
workspaceId: null,
|
|
2162
|
+
provisionerId,
|
|
2163
|
+
providerRunnerId: 'stale-installation-runner',
|
|
2164
|
+
reservationId: reservation.id,
|
|
2165
|
+
runnerSessionId: crypto.randomUUID(),
|
|
2166
|
+
state: 'running',
|
|
2167
|
+
reportedAt: staleReportedAt(),
|
|
2168
|
+
updatedAt: staleReportedAt(),
|
|
2169
|
+
})
|
|
2170
|
+
.returning({id: providerRunners.id});
|
|
2171
|
+
if (!runner) throw new Error('Expected runner');
|
|
2172
|
+
|
|
2173
|
+
const result = await reconcileRunnerInstances({
|
|
2174
|
+
workspaceId: null,
|
|
2175
|
+
provisionerId,
|
|
2176
|
+
observedRunnerInstanceIds: ['observed-runner'],
|
|
2177
|
+
terminateGraceSeconds: 60,
|
|
2178
|
+
});
|
|
2179
|
+
|
|
2180
|
+
const [providerRunner] = await db()
|
|
2181
|
+
.select()
|
|
2182
|
+
.from(providerRunners)
|
|
2183
|
+
.where(eq(providerRunners.id, runner.id));
|
|
2184
|
+
const [remainingReservation] = await db()
|
|
2185
|
+
.select()
|
|
2186
|
+
.from(reservations)
|
|
2187
|
+
.where(eq(reservations.id, reservation.id));
|
|
2188
|
+
expect(result).toMatchObject({
|
|
2189
|
+
absentIds: ['stale-installation-runner'],
|
|
2190
|
+
reservationsReleased: 1,
|
|
2191
|
+
});
|
|
2192
|
+
expect(providerRunner).toMatchObject({state: 'terminated'});
|
|
2193
|
+
expect(providerRunner?.reservationReleasedAt).toBeInstanceOf(Date);
|
|
2194
|
+
expect(remainingReservation).toBeUndefined();
|
|
2195
|
+
});
|
|
2196
|
+
|
|
1976
2197
|
it('treats an empty observed set as read-only', async () => {
|
|
1977
2198
|
const reservationId = await createReservation(2);
|
|
1978
2199
|
await createRunnerInstance({
|
|
@@ -2219,8 +2440,9 @@ describe('reconcileRunnerInstances', () => {
|
|
|
2219
2440
|
]);
|
|
2220
2441
|
});
|
|
2221
2442
|
|
|
2222
|
-
it('terminates session-bound absent runners
|
|
2443
|
+
it('terminates session-bound absent runners and releases their reservation', async () => {
|
|
2223
2444
|
const reservationId = await createReservation(1);
|
|
2445
|
+
await pendingJobFactory.create({workspaceId, requiredLabels: ['linux']});
|
|
2224
2446
|
await createRunnerInstance({
|
|
2225
2447
|
providerRunnerId: 'provisioned-runner-1',
|
|
2226
2448
|
reservationId,
|
|
@@ -2237,10 +2459,23 @@ describe('reconcileRunnerInstances', () => {
|
|
|
2237
2459
|
|
|
2238
2460
|
const [providerRunner] = await providerRunnerRowsFor({workspaceId, provisionerId});
|
|
2239
2461
|
const [reservation] = await reservationRowsFor({workspaceId, provisionerId});
|
|
2240
|
-
expect(result.reservationsReleased).toBe(
|
|
2462
|
+
expect(result.reservationsReleased).toBe(1);
|
|
2241
2463
|
expect(providerRunner?.state).toBe('terminated');
|
|
2242
|
-
expect(providerRunner?.reservationReleasedAt).
|
|
2243
|
-
expect(reservation
|
|
2464
|
+
expect(providerRunner?.reservationReleasedAt).toBeInstanceOf(Date);
|
|
2465
|
+
expect(reservation).toBeUndefined();
|
|
2466
|
+
|
|
2467
|
+
const poll = await pollDemandAndReserve({
|
|
2468
|
+
workspaceId,
|
|
2469
|
+
provisionerId,
|
|
2470
|
+
maxReservations: 1,
|
|
2471
|
+
ttlSeconds: 60,
|
|
2472
|
+
templates: [
|
|
2473
|
+
{templateKey: 'linux', labels: ['linux'], availableSlots: 1, starting: 0, running: 0},
|
|
2474
|
+
],
|
|
2475
|
+
});
|
|
2476
|
+
expect(poll.reservations).toHaveLength(1);
|
|
2477
|
+
expect(poll.reservations[0]?.count).toBe(1);
|
|
2478
|
+
expect(poll.stats[0]).toMatchObject({labels: ['linux'], queued: 1, reserved: 1});
|
|
2244
2479
|
});
|
|
2245
2480
|
|
|
2246
2481
|
it('returns a deterministic newest running job execution bound to an observed provisioned runner', async () => {
|