@shipfox/api-runners 5.0.0 → 7.0.1
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 +6 -6
- package/CHANGELOG.md +80 -0
- package/dist/config.d.ts +5 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +54 -7
- package/dist/config.js.map +1 -1
- package/dist/core/demand.d.ts +6 -6
- package/dist/core/demand.d.ts.map +1 -1
- package/dist/core/demand.js +10 -10
- package/dist/core/demand.js.map +1 -1
- package/dist/core/entities/ephemeral-registration-token.d.ts +1 -1
- package/dist/core/entities/ephemeral-registration-token.d.ts.map +1 -1
- package/dist/core/entities/ephemeral-registration-token.js.map +1 -1
- package/dist/core/entities/provisioner-capability-snapshot.d.ts +13 -0
- package/dist/core/entities/provisioner-capability-snapshot.d.ts.map +1 -0
- package/dist/core/entities/provisioner-capability-snapshot.js +3 -0
- package/dist/core/entities/provisioner-capability-snapshot.js.map +1 -0
- package/dist/core/entities/provisioner-token.d.ts +10 -2
- package/dist/core/entities/provisioner-token.d.ts.map +1 -1
- package/dist/core/entities/provisioner-token.js.map +1 -1
- package/dist/core/entities/{provisioned-runner.d.ts → runner-instance.d.ts} +7 -6
- package/dist/core/entities/runner-instance.d.ts.map +1 -0
- package/dist/core/entities/runner-instance.js +3 -0
- package/dist/core/entities/runner-instance.js.map +1 -0
- package/dist/core/entities/runner-session.d.ts +4 -2
- package/dist/core/entities/runner-session.d.ts.map +1 -1
- package/dist/core/entities/runner-session.js.map +1 -1
- package/dist/core/ephemeral-registration-tokens.d.ts +5 -5
- package/dist/core/ephemeral-registration-tokens.d.ts.map +1 -1
- package/dist/core/ephemeral-registration-tokens.js +12 -12
- package/dist/core/ephemeral-registration-tokens.js.map +1 -1
- package/dist/core/errors.d.ts +16 -4
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +22 -4
- package/dist/core/errors.js.map +1 -1
- package/dist/core/index.d.ts +9 -5
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +6 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/job-executions.d.ts +2 -0
- package/dist/core/job-executions.d.ts.map +1 -1
- package/dist/core/job-executions.js +1 -2
- package/dist/core/job-executions.js.map +1 -1
- package/dist/core/maintenance.d.ts +1 -1
- package/dist/core/maintenance.d.ts.map +1 -1
- package/dist/core/maintenance.js +5 -5
- package/dist/core/maintenance.js.map +1 -1
- package/dist/core/provisioner-capability-snapshots.d.ts +5 -0
- package/dist/core/provisioner-capability-snapshots.d.ts.map +1 -0
- package/dist/core/provisioner-capability-snapshots.js +10 -0
- package/dist/core/provisioner-capability-snapshots.js.map +1 -0
- package/dist/core/provisioner-tokens.d.ts +10 -0
- package/dist/core/provisioner-tokens.d.ts.map +1 -1
- package/dist/core/provisioner-tokens.js +23 -1
- package/dist/core/provisioner-tokens.js.map +1 -1
- package/dist/core/rate-limit.d.ts.map +1 -1
- package/dist/core/rate-limit.js +2 -7
- package/dist/core/rate-limit.js.map +1 -1
- package/dist/core/runner-activation.d.ts +13 -0
- package/dist/core/runner-activation.d.ts.map +1 -0
- package/dist/core/runner-activation.js +36 -0
- package/dist/core/runner-activation.js.map +1 -0
- package/dist/core/runner-assignments.d.ts +6 -0
- package/dist/core/runner-assignments.d.ts.map +1 -0
- package/dist/core/runner-assignments.js +6 -0
- package/dist/core/runner-assignments.js.map +1 -0
- package/dist/core/runner-control-sessions.d.ts +46 -0
- package/dist/core/runner-control-sessions.d.ts.map +1 -0
- package/dist/core/runner-control-sessions.js +121 -0
- package/dist/core/runner-control-sessions.js.map +1 -0
- package/dist/core/runner-instances.d.ts +72 -0
- package/dist/core/runner-instances.d.ts.map +1 -0
- package/dist/core/runner-instances.js +165 -0
- package/dist/core/runner-instances.js.map +1 -0
- package/dist/core/runner-sessions.d.ts +8 -2
- package/dist/core/runner-sessions.d.ts.map +1 -1
- package/dist/core/runner-sessions.js +8 -5
- package/dist/core/runner-sessions.js.map +1 -1
- package/dist/db/db.d.ts +2698 -1054
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +9 -2
- package/dist/db/db.js.map +1 -1
- package/dist/db/ephemeral-registration-tokens.d.ts +2 -2
- package/dist/db/ephemeral-registration-tokens.d.ts.map +1 -1
- package/dist/db/ephemeral-registration-tokens.js +21 -21
- package/dist/db/ephemeral-registration-tokens.js.map +1 -1
- package/dist/db/index.d.ts +7 -4
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +5 -2
- package/dist/db/index.js.map +1 -1
- package/dist/db/job-executions.d.ts +6 -6
- package/dist/db/job-executions.d.ts.map +1 -1
- package/dist/db/job-executions.js +20 -19
- package/dist/db/job-executions.js.map +1 -1
- package/dist/db/provisioner-capability-snapshots.d.ts +21 -0
- package/dist/db/provisioner-capability-snapshots.d.ts.map +1 -0
- package/dist/db/provisioner-capability-snapshots.js +57 -0
- package/dist/db/provisioner-capability-snapshots.js.map +1 -0
- package/dist/db/provisioner-tokens.d.ts +7 -2
- package/dist/db/provisioner-tokens.d.ts.map +1 -1
- package/dist/db/provisioner-tokens.js +59 -10
- package/dist/db/provisioner-tokens.js.map +1 -1
- package/dist/db/reservations.d.ts +18 -0
- package/dist/db/reservations.d.ts.map +1 -1
- package/dist/db/reservations.js +88 -1
- package/dist/db/reservations.js.map +1 -1
- package/dist/db/runner-assignments.d.ts +7 -0
- package/dist/db/runner-assignments.d.ts.map +1 -0
- package/dist/db/runner-assignments.js +55 -0
- package/dist/db/runner-assignments.js.map +1 -0
- package/dist/db/runner-instances.d.ts +84 -0
- package/dist/db/runner-instances.d.ts.map +1 -0
- package/dist/db/runner-instances.js +497 -0
- package/dist/db/runner-instances.js.map +1 -0
- package/dist/db/runner-sessions.d.ts +5 -0
- package/dist/db/runner-sessions.d.ts.map +1 -1
- package/dist/db/runner-sessions.js +48 -2
- package/dist/db/runner-sessions.js.map +1 -1
- package/dist/db/schema/ephemeral-registration-tokens.d.ts +2 -2
- package/dist/db/schema/ephemeral-registration-tokens.js +3 -3
- package/dist/db/schema/ephemeral-registration-tokens.js.map +1 -1
- package/dist/db/schema/provisioner-capability-snapshots.d.ts +207 -0
- package/dist/db/schema/provisioner-capability-snapshots.d.ts.map +1 -0
- package/dist/db/schema/provisioner-capability-snapshots.js +38 -0
- package/dist/db/schema/provisioner-capability-snapshots.js.map +1 -0
- package/dist/db/schema/provisioner-tokens.d.ts +19 -1
- package/dist/db/schema/provisioner-tokens.d.ts.map +1 -1
- package/dist/db/schema/provisioner-tokens.js +29 -3
- package/dist/db/schema/provisioner-tokens.js.map +1 -1
- package/dist/db/schema/runner-activation-tokens.d.ts +161 -0
- package/dist/db/schema/runner-activation-tokens.d.ts.map +1 -0
- package/dist/db/schema/runner-activation-tokens.js +27 -0
- package/dist/db/schema/runner-activation-tokens.js.map +1 -0
- package/dist/db/schema/runner-control-sessions.d.ts +338 -0
- package/dist/db/schema/runner-control-sessions.d.ts.map +1 -0
- package/dist/db/schema/runner-control-sessions.js +52 -0
- package/dist/db/schema/runner-control-sessions.js.map +1 -0
- package/dist/db/schema/{provisioned-runners.d.ts → runner-instances.d.ts} +105 -34
- package/dist/db/schema/runner-instances.d.ts.map +1 -0
- package/dist/db/schema/{provisioned-runners.js → runner-instances.js} +23 -14
- package/dist/db/schema/runner-instances.js.map +1 -0
- package/dist/db/schema/runner-sessions.d.ts +39 -5
- package/dist/db/schema/runner-sessions.d.ts.map +1 -1
- package/dist/db/schema/runner-sessions.js +13 -6
- package/dist/db/schema/runner-sessions.js.map +1 -1
- package/dist/db/schema/running-job-executions.d.ts +2 -2
- package/dist/db/schema/running-job-executions.js +4 -4
- package/dist/db/schema/running-job-executions.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +45 -39
- package/dist/index.js.map +1 -1
- package/dist/installation-provisioning.d.ts +14 -0
- package/dist/installation-provisioning.d.ts.map +1 -0
- package/dist/installation-provisioning.js +7 -0
- package/dist/installation-provisioning.js.map +1 -0
- package/dist/metrics/index.d.ts +1 -1
- package/dist/metrics/index.d.ts.map +1 -1
- package/dist/metrics/index.js +1 -1
- package/dist/metrics/index.js.map +1 -1
- package/dist/metrics/instance.d.ts +11 -7
- package/dist/metrics/instance.d.ts.map +1 -1
- package/dist/metrics/instance.js +13 -7
- package/dist/metrics/instance.js.map +1 -1
- package/dist/presentation/auth/index.d.ts +1 -0
- package/dist/presentation/auth/index.d.ts.map +1 -1
- package/dist/presentation/auth/index.js +1 -0
- package/dist/presentation/auth/index.js.map +1 -1
- package/dist/presentation/auth/provisioner-token-auth.d.ts.map +1 -1
- package/dist/presentation/auth/provisioner-token-auth.js +5 -1
- package/dist/presentation/auth/provisioner-token-auth.js.map +1 -1
- package/dist/presentation/auth/runner-control-session-auth.d.ts +4 -0
- package/dist/presentation/auth/runner-control-session-auth.d.ts.map +1 -0
- package/dist/presentation/auth/runner-control-session-auth.js +29 -0
- package/dist/presentation/auth/runner-control-session-auth.js.map +1 -0
- package/dist/presentation/auth/runner-registration-token-auth.d.ts +5 -1
- package/dist/presentation/auth/runner-registration-token-auth.d.ts.map +1 -1
- package/dist/presentation/auth/runner-registration-token-auth.js +21 -1
- package/dist/presentation/auth/runner-registration-token-auth.js.map +1 -1
- package/dist/presentation/dto/index.d.ts +1 -2
- package/dist/presentation/dto/index.d.ts.map +1 -1
- package/dist/presentation/dto/index.js +1 -2
- package/dist/presentation/dto/index.js.map +1 -1
- package/dist/presentation/dto/poll-demand.d.ts.map +1 -1
- package/dist/presentation/dto/poll-demand.js +7 -1
- package/dist/presentation/dto/poll-demand.js.map +1 -1
- package/dist/presentation/dto/provisioner-token.d.ts +1 -0
- package/dist/presentation/dto/provisioner-token.d.ts.map +1 -1
- package/dist/presentation/dto/provisioner-token.js +5 -1
- package/dist/presentation/dto/provisioner-token.js.map +1 -1
- package/dist/presentation/dto/{provisioned-runners.d.ts → runner-instances.d.ts} +9 -9
- package/dist/presentation/dto/runner-instances.d.ts.map +1 -0
- package/dist/presentation/dto/{provisioned-runners.js → runner-instances.js} +6 -6
- package/dist/presentation/dto/runner-instances.js.map +1 -0
- package/dist/presentation/index.d.ts +2 -2
- package/dist/presentation/index.d.ts.map +1 -1
- package/dist/presentation/index.js +2 -2
- package/dist/presentation/index.js.map +1 -1
- package/dist/presentation/inter-module.d.ts +5 -0
- package/dist/presentation/inter-module.d.ts.map +1 -0
- package/dist/presentation/inter-module.js +43 -0
- package/dist/presentation/inter-module.js.map +1 -0
- package/dist/presentation/routes/assign-runner-instances.d.ts +2 -0
- package/dist/presentation/routes/assign-runner-instances.d.ts.map +1 -0
- package/dist/presentation/routes/assign-runner-instances.js +40 -0
- package/dist/presentation/routes/assign-runner-instances.js.map +1 -0
- package/dist/presentation/routes/get-provisioner-me.d.ts.map +1 -1
- package/dist/presentation/routes/get-provisioner-me.js +5 -1
- package/dist/presentation/routes/get-provisioner-me.js.map +1 -1
- package/dist/presentation/routes/heartbeat.d.ts +2 -1
- package/dist/presentation/routes/heartbeat.d.ts.map +1 -1
- package/dist/presentation/routes/heartbeat.js +60 -50
- package/dist/presentation/routes/heartbeat.js.map +1 -1
- package/dist/presentation/routes/index.d.ts +3 -1
- package/dist/presentation/routes/index.d.ts.map +1 -1
- package/dist/presentation/routes/index.js +80 -51
- package/dist/presentation/routes/index.js.map +1 -1
- package/dist/presentation/routes/poll-demand.d.ts +2 -0
- package/dist/presentation/routes/poll-demand.d.ts.map +1 -1
- package/dist/presentation/routes/poll-demand.js +86 -45
- package/dist/presentation/routes/poll-demand.js.map +1 -1
- package/dist/presentation/routes/reconcile-runner-instances.d.ts +2 -0
- package/dist/presentation/routes/reconcile-runner-instances.d.ts.map +1 -0
- package/dist/presentation/routes/reconcile-runner-instances.js +27 -0
- package/dist/presentation/routes/reconcile-runner-instances.js.map +1 -0
- package/dist/presentation/routes/register.d.ts +2 -1
- package/dist/presentation/routes/register.d.ts.map +1 -1
- package/dist/presentation/routes/register.js +55 -52
- package/dist/presentation/routes/register.js.map +1 -1
- package/dist/presentation/routes/report-runner-instances.d.ts +2 -0
- package/dist/presentation/routes/report-runner-instances.d.ts.map +1 -0
- package/dist/presentation/routes/report-runner-instances.js +37 -0
- package/dist/presentation/routes/report-runner-instances.js.map +1 -0
- package/dist/presentation/routes/request-job.d.ts +2 -1
- package/dist/presentation/routes/request-job.d.ts.map +1 -1
- package/dist/presentation/routes/request-job.js +39 -36
- package/dist/presentation/routes/request-job.js.map +1 -1
- package/dist/presentation/routes/runner-enrollment.d.ts +8 -0
- package/dist/presentation/routes/runner-enrollment.d.ts.map +1 -0
- package/dist/presentation/routes/runner-enrollment.js +212 -0
- package/dist/presentation/routes/runner-enrollment.js.map +1 -0
- package/dist/temporal/activities/index.d.ts +2 -2
- package/dist/temporal/activities/index.d.ts.map +1 -1
- package/dist/temporal/activities/index.js +2 -2
- package/dist/temporal/activities/index.js.map +1 -1
- package/dist/temporal/activities/maintenance-activities.d.ts +1 -1
- package/dist/temporal/activities/maintenance-activities.d.ts.map +1 -1
- package/dist/temporal/activities/maintenance-activities.js +3 -3
- package/dist/temporal/activities/maintenance-activities.js.map +1 -1
- package/dist/temporal/workflows/index.bundle.js +3 -3
- package/dist/temporal/workflows/stuck-job-detector.js +2 -2
- package/dist/temporal/workflows/stuck-job-detector.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0000_initial.sql +91 -24
- package/drizzle/meta/0000_snapshot.json +392 -63
- package/package.json +21 -16
- package/src/config.test.ts +0 -26
- package/src/config.ts +48 -7
- package/src/core/demand.test.ts +29 -29
- package/src/core/demand.ts +22 -22
- package/src/core/entities/ephemeral-registration-token.ts +1 -1
- package/src/core/entities/provisioner-capability-snapshot.ts +12 -0
- package/src/core/entities/provisioner-token.ts +8 -2
- package/src/core/entities/{provisioned-runner.ts → runner-instance.ts} +6 -5
- package/src/core/entities/runner-session.ts +4 -2
- package/src/core/ephemeral-registration-tokens.test.ts +11 -11
- package/src/core/ephemeral-registration-tokens.ts +18 -20
- package/src/core/errors.ts +23 -4
- package/src/core/index.ts +37 -22
- package/src/core/job-executions.ts +3 -2
- package/src/core/maintenance.test.ts +13 -17
- package/src/core/maintenance.ts +5 -5
- package/src/core/provisioner-capability-snapshots.test.ts +35 -0
- package/src/core/provisioner-capability-snapshots.ts +12 -0
- package/src/core/provisioner-tokens.test.ts +20 -0
- package/src/core/provisioner-tokens.ts +33 -0
- package/src/core/rate-limit.test.ts +11 -27
- package/src/core/rate-limit.ts +3 -11
- package/src/core/runner-activation.ts +72 -0
- package/src/core/runner-assignments.ts +9 -0
- package/src/core/runner-control-sessions.ts +190 -0
- package/src/core/{provisioned-runners.test.ts → runner-instances.test.ts} +24 -24
- package/src/core/runner-instances.ts +301 -0
- package/src/core/runner-sessions.test.ts +112 -22
- package/src/core/runner-sessions.ts +25 -14
- package/src/db/db.ts +9 -2
- package/src/db/ephemeral-registration-tokens.test.ts +9 -9
- package/src/db/ephemeral-registration-tokens.ts +26 -26
- package/src/db/index.ts +30 -21
- package/src/db/job-executions.test.ts +13 -11
- package/src/db/job-executions.ts +33 -27
- package/src/db/provisioner-capability-snapshots.test.ts +175 -0
- package/src/db/provisioner-capability-snapshots.ts +130 -0
- package/src/db/provisioner-tokens.test.ts +139 -2
- package/src/db/provisioner-tokens.ts +123 -15
- package/src/db/reservations.test.ts +49 -0
- package/src/db/reservations.ts +146 -1
- package/src/db/runner-assignments.test.ts +163 -0
- package/src/db/runner-assignments.ts +115 -0
- package/src/db/{provisioned-runners.test.ts → runner-instances.test.ts} +442 -353
- package/src/db/runner-instances.ts +922 -0
- package/src/db/runner-sessions.test.ts +19 -6
- package/src/db/runner-sessions.ts +78 -2
- package/src/db/schema/ephemeral-registration-tokens.ts +4 -4
- package/src/db/schema/provisioner-capability-snapshots.ts +46 -0
- package/src/db/schema/provisioner-tokens.ts +33 -2
- package/src/db/schema/runner-activation-tokens.ts +22 -0
- package/src/db/schema/runner-control-sessions.ts +46 -0
- package/src/db/schema/{provisioned-runners.ts → runner-instances.ts} +28 -27
- package/src/db/schema/runner-sessions.ts +11 -7
- package/src/db/schema/running-job-executions.ts +5 -5
- package/src/index.test.ts +26 -0
- package/src/index.ts +38 -27
- package/src/installation-provisioning.ts +14 -0
- package/src/metrics/index.ts +5 -5
- package/src/metrics/instance.ts +25 -14
- package/src/presentation/auth/index.ts +4 -0
- package/src/presentation/auth/provisioner-token-auth.ts +8 -4
- package/src/presentation/auth/runner-control-session-auth.ts +31 -0
- package/src/presentation/auth/runner-registration-token-auth.ts +39 -3
- package/src/presentation/dto/index.ts +4 -8
- package/src/presentation/dto/poll-demand.ts +3 -1
- package/src/presentation/dto/provisioner-token.ts +5 -0
- package/src/presentation/dto/{provisioned-runners.ts → runner-instances.ts} +13 -15
- package/src/presentation/index.ts +2 -1
- package/src/presentation/inter-module.test.ts +15 -0
- package/src/presentation/inter-module.ts +53 -0
- package/src/presentation/routes/assign-runner-instances.ts +49 -0
- package/src/presentation/routes/get-provisioner-me.ts +3 -4
- package/src/presentation/routes/heartbeat.test.ts +4 -3
- package/src/presentation/routes/heartbeat.ts +67 -64
- package/src/presentation/routes/index.ts +91 -44
- package/src/presentation/routes/list-active-runners.test.ts +23 -23
- package/src/presentation/routes/manual-registration-tokens.test.ts +4 -3
- package/src/presentation/routes/poll-demand-release.test.ts +4 -4
- package/src/presentation/routes/poll-demand.test.ts +161 -22
- package/src/presentation/routes/poll-demand.ts +105 -45
- package/src/presentation/routes/provisioner-me.test.ts +37 -3
- package/src/presentation/routes/{reconcile-provisioned-runners.test.ts → reconcile-runner-instances.test.ts} +66 -61
- package/src/presentation/routes/reconcile-runner-instances.ts +30 -0
- package/src/presentation/routes/register.test.ts +9 -5
- package/src/presentation/routes/register.ts +61 -57
- package/src/presentation/routes/{report-provisioned-runners.test.ts → report-runner-instances.test.ts} +28 -28
- package/src/presentation/routes/{report-provisioned-runners.ts → report-runner-instances.ts} +14 -14
- package/src/presentation/routes/request-job.test.ts +5 -3
- package/src/presentation/routes/request-job.ts +41 -37
- package/src/presentation/routes/runner-enrollment.test.ts +319 -0
- package/src/presentation/routes/runner-enrollment.ts +196 -0
- package/src/temporal/activities/index.ts +2 -2
- package/src/temporal/activities/maintenance-activities.test.ts +7 -7
- package/src/temporal/activities/maintenance-activities.ts +3 -3
- package/src/temporal/workflows/stuck-job-detector.test.ts +3 -3
- package/src/temporal/workflows/stuck-job-detector.ts +2 -2
- package/test/env.ts +1 -3
- package/test/factories/ephemeral-registration-token.ts +2 -2
- package/test/factories/provisioner-token.ts +3 -1
- package/test/factories/runner-instance.ts +57 -0
- package/test/factories/runner-session.ts +3 -1
- package/test/fixtures/auth-inter-module.ts +30 -0
- package/test/globalSetup.ts +4 -1
- package/test/index.ts +2 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/core/entities/provisioned-runner.d.ts.map +0 -1
- package/dist/core/entities/provisioned-runner.js +0 -3
- package/dist/core/entities/provisioned-runner.js.map +0 -1
- package/dist/core/provisioned-runners.d.ts +0 -67
- package/dist/core/provisioned-runners.d.ts.map +0 -1
- package/dist/core/provisioned-runners.js +0 -162
- package/dist/core/provisioned-runners.js.map +0 -1
- package/dist/db/provisioned-runners.d.ts +0 -79
- package/dist/db/provisioned-runners.d.ts.map +0 -1
- package/dist/db/provisioned-runners.js +0 -466
- package/dist/db/provisioned-runners.js.map +0 -1
- package/dist/db/schema/provisioned-runners.d.ts.map +0 -1
- package/dist/db/schema/provisioned-runners.js.map +0 -1
- package/dist/presentation/dto/mint-registration-tokens.d.ts +0 -5
- package/dist/presentation/dto/mint-registration-tokens.d.ts.map +0 -1
- package/dist/presentation/dto/mint-registration-tokens.js +0 -19
- package/dist/presentation/dto/mint-registration-tokens.js.map +0 -1
- package/dist/presentation/dto/provisioned-runners.d.ts.map +0 -1
- package/dist/presentation/dto/provisioned-runners.js.map +0 -1
- package/dist/presentation/routes/mint-registration-tokens.d.ts +0 -2
- package/dist/presentation/routes/mint-registration-tokens.d.ts.map +0 -1
- package/dist/presentation/routes/mint-registration-tokens.js +0 -74
- package/dist/presentation/routes/mint-registration-tokens.js.map +0 -1
- package/dist/presentation/routes/reconcile-provisioned-runners.d.ts +0 -2
- package/dist/presentation/routes/reconcile-provisioned-runners.d.ts.map +0 -1
- package/dist/presentation/routes/reconcile-provisioned-runners.js +0 -27
- package/dist/presentation/routes/reconcile-provisioned-runners.js.map +0 -1
- package/dist/presentation/routes/report-provisioned-runners.d.ts +0 -2
- package/dist/presentation/routes/report-provisioned-runners.d.ts.map +0 -1
- package/dist/presentation/routes/report-provisioned-runners.js +0 -37
- package/dist/presentation/routes/report-provisioned-runners.js.map +0 -1
- package/src/core/provisioned-runners.ts +0 -301
- package/src/db/provisioned-runners.ts +0 -886
- package/src/presentation/dto/mint-registration-tokens.ts +0 -35
- package/src/presentation/routes/mint-registration-tokens.test.ts +0 -453
- package/src/presentation/routes/mint-registration-tokens.ts +0 -82
- package/src/presentation/routes/reconcile-provisioned-runners.ts +0 -30
- package/test/factories/provisioned-runner.ts +0 -78
|
@@ -3,6 +3,7 @@ import {db} from '#db/db.js';
|
|
|
3
3
|
import {
|
|
4
4
|
deleteReservationsByIds,
|
|
5
5
|
pollDemandAndReserve,
|
|
6
|
+
pollInstallationDemandAndReserve,
|
|
6
7
|
releaseReservationUnits,
|
|
7
8
|
} from '#db/reservations.js';
|
|
8
9
|
import {reservations} from '#db/schema/reservations.js';
|
|
@@ -75,6 +76,54 @@ describe('pollDemandAndReserve', () => {
|
|
|
75
76
|
expect(reserved).toBeLessThanOrEqual(5);
|
|
76
77
|
});
|
|
77
78
|
|
|
79
|
+
it('allocates eligible workspace heads in oldest-demand order without overselling templates', async () => {
|
|
80
|
+
const olderWorkspaceId = crypto.randomUUID();
|
|
81
|
+
const newerWorkspaceId = crypto.randomUUID();
|
|
82
|
+
await pendingJobFactory.create({workspaceId: olderWorkspaceId, requiredLabels: ['linux']});
|
|
83
|
+
await pendingJobFactory.create({workspaceId: newerWorkspaceId, requiredLabels: ['linux']});
|
|
84
|
+
|
|
85
|
+
const result = await pollInstallationDemandAndReserve({
|
|
86
|
+
provisionerId,
|
|
87
|
+
maxReservations: 5,
|
|
88
|
+
ttlSeconds: 60,
|
|
89
|
+
templates: [template('linux', ['linux'], 1)],
|
|
90
|
+
capabilityWindowSeconds: 60,
|
|
91
|
+
eligibleWorkspaceIds: new Set([olderWorkspaceId, newerWorkspaceId]),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(result.reservations).toEqual([
|
|
95
|
+
expect.objectContaining({workspaceId: olderWorkspaceId, labels: ['linux'], count: 1}),
|
|
96
|
+
]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('reports committed installation grants before an aborted poll stops allocating', async () => {
|
|
100
|
+
const firstWorkspaceId = crypto.randomUUID();
|
|
101
|
+
const secondWorkspaceId = crypto.randomUUID();
|
|
102
|
+
const abortController = new AbortController();
|
|
103
|
+
const reportedReservations: string[] = [];
|
|
104
|
+
await pendingJobFactory.create({workspaceId: firstWorkspaceId, requiredLabels: ['linux']});
|
|
105
|
+
await pendingJobFactory.create({workspaceId: secondWorkspaceId, requiredLabels: ['linux']});
|
|
106
|
+
|
|
107
|
+
const result = await pollInstallationDemandAndReserve({
|
|
108
|
+
provisionerId,
|
|
109
|
+
maxReservations: 2,
|
|
110
|
+
ttlSeconds: 60,
|
|
111
|
+
templates: [template('linux', ['linux'], 2)],
|
|
112
|
+
capabilityWindowSeconds: 60,
|
|
113
|
+
eligibleWorkspaceIds: new Set([firstWorkspaceId, secondWorkspaceId]),
|
|
114
|
+
signal: abortController.signal,
|
|
115
|
+
onReservations: (reservations) => {
|
|
116
|
+
reportedReservations.push(...reservations.map((reservation) => reservation.reservationId));
|
|
117
|
+
abortController.abort();
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
expect(result.reservations).toHaveLength(1);
|
|
122
|
+
expect(reportedReservations).toEqual(
|
|
123
|
+
result.reservations.map((reservation) => reservation.reservationId),
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
78
127
|
it('does not count expired reservations against demand', async () => {
|
|
79
128
|
await createPendingJobs(1, ['linux']);
|
|
80
129
|
await reservationFactory.create({
|
package/src/db/reservations.ts
CHANGED
|
@@ -3,6 +3,8 @@ import {and, asc, eq, gt, inArray, lt, sql} from 'drizzle-orm';
|
|
|
3
3
|
import type {Tx} from './db.js';
|
|
4
4
|
import {db} from './db.js';
|
|
5
5
|
import {pendingJobExecutions} from './schema/pending-job-executions.js';
|
|
6
|
+
import {provisionerCapabilitySnapshots} from './schema/provisioner-capability-snapshots.js';
|
|
7
|
+
import {provisionerTokens} from './schema/provisioner-tokens.js';
|
|
6
8
|
import {reservations} from './schema/reservations.js';
|
|
7
9
|
|
|
8
10
|
export interface ReservationTemplate {
|
|
@@ -14,6 +16,7 @@ export interface ReservationTemplate {
|
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export interface DemandStat {
|
|
19
|
+
workspaceId?: string;
|
|
17
20
|
labels: string[];
|
|
18
21
|
queued: number;
|
|
19
22
|
reserved: number;
|
|
@@ -22,6 +25,7 @@ export interface DemandStat {
|
|
|
22
25
|
|
|
23
26
|
export interface ReservationGrant {
|
|
24
27
|
reservationId: string;
|
|
28
|
+
workspaceId?: string;
|
|
25
29
|
labels: string[];
|
|
26
30
|
count: number;
|
|
27
31
|
expiresAt: Date;
|
|
@@ -33,6 +37,18 @@ export interface PollDemandAndReserveParams {
|
|
|
33
37
|
maxReservations: number;
|
|
34
38
|
ttlSeconds: number;
|
|
35
39
|
templates: ReservationTemplate[];
|
|
40
|
+
capabilityWindowSeconds?: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface InstallationPollDemandAndReserveParams {
|
|
44
|
+
provisionerId: string;
|
|
45
|
+
maxReservations: number;
|
|
46
|
+
ttlSeconds: number;
|
|
47
|
+
templates: ReservationTemplate[];
|
|
48
|
+
capabilityWindowSeconds: number;
|
|
49
|
+
eligibleWorkspaceIds: ReadonlySet<string>;
|
|
50
|
+
signal?: AbortSignal;
|
|
51
|
+
onReservations?: (reservations: ReservationGrant[]) => void;
|
|
36
52
|
}
|
|
37
53
|
|
|
38
54
|
interface NormalizedTemplate {
|
|
@@ -60,8 +76,75 @@ export async function pollDemandAndReserveTx(
|
|
|
60
76
|
params: PollDemandAndReserveParams,
|
|
61
77
|
): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {
|
|
62
78
|
await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${params.workspaceId}))`);
|
|
79
|
+
return await pollDemandAndReserveLockedTx(tx, params);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function pollInstallationDemandAndReserve(
|
|
83
|
+
params: InstallationPollDemandAndReserveParams,
|
|
84
|
+
): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {
|
|
85
|
+
const candidateWorkspaceIds = await listInstallationDemandWorkspaceIds(
|
|
86
|
+
params.eligibleWorkspaceIds,
|
|
87
|
+
);
|
|
88
|
+
const results: Array<{stats: DemandStat[]; reservations: ReservationGrant[]}> = [];
|
|
89
|
+
let remainingMaxReservations = params.maxReservations;
|
|
90
|
+
const remainingTemplates = params.templates.map((template) => ({
|
|
91
|
+
...template,
|
|
92
|
+
labels: [...canonicalizeLabels(template.labels)],
|
|
93
|
+
remainingSlots: template.availableSlots,
|
|
94
|
+
}));
|
|
95
|
+
for (const workspaceId of candidateWorkspaceIds) {
|
|
96
|
+
if (params.signal?.aborted || remainingMaxReservations === 0) break;
|
|
97
|
+
const result = await db().transaction(async (tx) => {
|
|
98
|
+
const lockResult = await tx.execute<{locked: boolean}>(
|
|
99
|
+
sql`select pg_try_advisory_xact_lock(hashtext(${workspaceId})) as locked`,
|
|
100
|
+
);
|
|
101
|
+
const locked = lockResult.rows[0];
|
|
102
|
+
if (!locked?.locked) return {stats: [], reservations: []};
|
|
103
|
+
return await pollDemandAndReserveLockedTx(tx, {
|
|
104
|
+
workspaceId,
|
|
105
|
+
provisionerId: params.provisionerId,
|
|
106
|
+
maxReservations: remainingMaxReservations,
|
|
107
|
+
ttlSeconds: params.ttlSeconds,
|
|
108
|
+
templates: remainingTemplates.map((template) => ({
|
|
109
|
+
...template,
|
|
110
|
+
availableSlots: template.remainingSlots,
|
|
111
|
+
})),
|
|
112
|
+
capabilityWindowSeconds: params.capabilityWindowSeconds,
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
results.push(result);
|
|
116
|
+
consumeInstallationTemplateSlots(remainingTemplates, result.reservations);
|
|
117
|
+
params.onReservations?.(result.reservations);
|
|
118
|
+
remainingMaxReservations -= result.reservations.reduce(
|
|
119
|
+
(total, reservation) => total + reservation.count,
|
|
120
|
+
0,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
stats: results.flatMap((result) => result.stats),
|
|
125
|
+
reservations: results.flatMap((result) => result.reservations),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
63
128
|
|
|
64
|
-
|
|
129
|
+
function consumeInstallationTemplateSlots(
|
|
130
|
+
templates: NormalizedTemplate[],
|
|
131
|
+
reservations: ReservationGrant[],
|
|
132
|
+
): void {
|
|
133
|
+
for (const reservation of reservations) {
|
|
134
|
+
const satisfyingTemplates = templates
|
|
135
|
+
.filter((template) => isSubset(reservation.labels, template.labels))
|
|
136
|
+
.sort(
|
|
137
|
+
(a, b) => a.labels.length - b.labels.length || a.templateKey.localeCompare(b.templateKey),
|
|
138
|
+
);
|
|
139
|
+
drawSlots(satisfyingTemplates, reservation.count);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function pollDemandAndReserveLockedTx(
|
|
144
|
+
tx: Tx,
|
|
145
|
+
params: PollDemandAndReserveParams,
|
|
146
|
+
): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {
|
|
147
|
+
let demandRows = (
|
|
65
148
|
await tx
|
|
66
149
|
.select({
|
|
67
150
|
requiredLabels: pendingJobExecutions.requiredLabels,
|
|
@@ -75,6 +158,15 @@ export async function pollDemandAndReserveTx(
|
|
|
75
158
|
...row,
|
|
76
159
|
oldestQueuedAt: new Date(row.oldestQueuedAt),
|
|
77
160
|
}));
|
|
161
|
+
if (params.capabilityWindowSeconds !== undefined) {
|
|
162
|
+
const capabilityLabels = await listActiveWorkspaceCapabilityLabelsTx(tx, {
|
|
163
|
+
workspaceId: params.workspaceId,
|
|
164
|
+
windowSeconds: params.capabilityWindowSeconds,
|
|
165
|
+
});
|
|
166
|
+
demandRows = demandRows.filter(
|
|
167
|
+
(demand) => !capabilityLabels.some((labels) => isSubset(demand.requiredLabels, labels)),
|
|
168
|
+
);
|
|
169
|
+
}
|
|
78
170
|
|
|
79
171
|
const activeReservationRows = await tx
|
|
80
172
|
.select({
|
|
@@ -114,6 +206,8 @@ export async function pollDemandAndReserveTx(
|
|
|
114
206
|
const stats: DemandStat[] = [];
|
|
115
207
|
const grants: ReservationGrant[] = [];
|
|
116
208
|
let remainingMaxReservations = params.maxReservations;
|
|
209
|
+
const workspaceIdField =
|
|
210
|
+
params.capabilityWindowSeconds === undefined ? {} : {workspaceId: params.workspaceId};
|
|
117
211
|
|
|
118
212
|
for (const demand of sortDemandRows(demandRows)) {
|
|
119
213
|
const satisfyingTemplates = templates
|
|
@@ -152,6 +246,7 @@ export async function pollDemandAndReserveTx(
|
|
|
152
246
|
reservedAfterGrant += grant;
|
|
153
247
|
grants.push({
|
|
154
248
|
reservationId: inserted.id,
|
|
249
|
+
...workspaceIdField,
|
|
155
250
|
labels: demand.requiredLabels,
|
|
156
251
|
count: grant,
|
|
157
252
|
expiresAt: inserted.expiresAt,
|
|
@@ -159,6 +254,7 @@ export async function pollDemandAndReserveTx(
|
|
|
159
254
|
}
|
|
160
255
|
|
|
161
256
|
stats.push({
|
|
257
|
+
...workspaceIdField,
|
|
162
258
|
labels: demand.requiredLabels,
|
|
163
259
|
queued: demand.queued,
|
|
164
260
|
reserved: reservedAfterGrant,
|
|
@@ -169,6 +265,55 @@ export async function pollDemandAndReserveTx(
|
|
|
169
265
|
return {stats, reservations: grants};
|
|
170
266
|
}
|
|
171
267
|
|
|
268
|
+
async function listInstallationDemandWorkspaceIds(eligibleWorkspaceIds: ReadonlySet<string>) {
|
|
269
|
+
if (eligibleWorkspaceIds.size === 0) return [];
|
|
270
|
+
const rows = await db()
|
|
271
|
+
.select({
|
|
272
|
+
workspaceId: pendingJobExecutions.workspaceId,
|
|
273
|
+
oldestQueuedAt: sql<Date>`min(${pendingJobExecutions.createdAt})`,
|
|
274
|
+
})
|
|
275
|
+
.from(pendingJobExecutions)
|
|
276
|
+
.where(inArray(pendingJobExecutions.workspaceId, [...eligibleWorkspaceIds]))
|
|
277
|
+
.groupBy(pendingJobExecutions.workspaceId)
|
|
278
|
+
.orderBy(
|
|
279
|
+
asc(sql`min(${pendingJobExecutions.createdAt})`),
|
|
280
|
+
asc(pendingJobExecutions.workspaceId),
|
|
281
|
+
);
|
|
282
|
+
return rows.map((row) => row.workspaceId);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export async function listQueuedDemandWorkspaceIds(): Promise<string[]> {
|
|
286
|
+
const rows = await db()
|
|
287
|
+
.select({workspaceId: pendingJobExecutions.workspaceId})
|
|
288
|
+
.from(pendingJobExecutions)
|
|
289
|
+
.groupBy(pendingJobExecutions.workspaceId);
|
|
290
|
+
return rows.map((row) => row.workspaceId);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function listActiveWorkspaceCapabilityLabelsTx(
|
|
294
|
+
tx: Tx,
|
|
295
|
+
params: {workspaceId: string; windowSeconds: number},
|
|
296
|
+
): Promise<string[][]> {
|
|
297
|
+
const rows = await tx
|
|
298
|
+
.select({labels: provisionerCapabilitySnapshots.labels})
|
|
299
|
+
.from(provisionerCapabilitySnapshots)
|
|
300
|
+
.innerJoin(
|
|
301
|
+
provisionerTokens,
|
|
302
|
+
eq(provisionerTokens.id, provisionerCapabilitySnapshots.provisionerId),
|
|
303
|
+
)
|
|
304
|
+
.where(
|
|
305
|
+
and(
|
|
306
|
+
eq(provisionerCapabilitySnapshots.workspaceId, params.workspaceId),
|
|
307
|
+
eq(provisionerTokens.workspaceId, params.workspaceId),
|
|
308
|
+
eq(provisionerTokens.scope, 'workspace'),
|
|
309
|
+
sql`${provisionerCapabilitySnapshots.advertisedAt} > now() - (${params.windowSeconds} || ' seconds')::interval`,
|
|
310
|
+
sql`${provisionerTokens.revokedAt} is null`,
|
|
311
|
+
sql`(${provisionerTokens.expiresAt} is null or ${provisionerTokens.expiresAt} > now())`,
|
|
312
|
+
),
|
|
313
|
+
);
|
|
314
|
+
return rows.map((row) => row.labels);
|
|
315
|
+
}
|
|
316
|
+
|
|
172
317
|
export async function deleteExpiredReservations(params?: {limit?: number}): Promise<number> {
|
|
173
318
|
const expiredIds = db()
|
|
174
319
|
.select({id: reservations.id})
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import {eq} from 'drizzle-orm';
|
|
2
|
+
import {
|
|
3
|
+
ReservationExpiredError,
|
|
4
|
+
RunnerInstanceAlreadyAssignedError,
|
|
5
|
+
RunnerInstanceNotAssignableError,
|
|
6
|
+
} from '#core/errors.js';
|
|
7
|
+
import {db} from '#db/db.js';
|
|
8
|
+
import {assignRunnerInstances} from '#db/runner-assignments.js';
|
|
9
|
+
import {reservations} from '#db/schema/reservations.js';
|
|
10
|
+
import {runnerControlSessions} from '#db/schema/runner-control-sessions.js';
|
|
11
|
+
import {providerRunners} from '#db/schema/runner-instances.js';
|
|
12
|
+
|
|
13
|
+
describe('assignRunnerInstances', () => {
|
|
14
|
+
let workspaceId: string;
|
|
15
|
+
let provisionerId: string;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
workspaceId = crypto.randomUUID();
|
|
19
|
+
provisionerId = crypto.randomUUID();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('writes the reservation and workspace after enrollment', async () => {
|
|
23
|
+
const reservation = await createReservation();
|
|
24
|
+
const runner = await createEnrolledRunner();
|
|
25
|
+
|
|
26
|
+
const assigned = await assignRunnerInstances({
|
|
27
|
+
provisionerId,
|
|
28
|
+
reservationId: reservation.id,
|
|
29
|
+
runnerInstanceIds: [runner.id],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(assigned).toEqual([runner.id]);
|
|
33
|
+
const [stored] = await db()
|
|
34
|
+
.select()
|
|
35
|
+
.from(providerRunners)
|
|
36
|
+
.where(eq(providerRunners.id, runner.id));
|
|
37
|
+
expect(stored).toMatchObject({
|
|
38
|
+
workspaceId,
|
|
39
|
+
reservationId: reservation.id,
|
|
40
|
+
assignedAt: expect.any(Date),
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('is idempotent for concurrent retries of the same assignment', async () => {
|
|
45
|
+
const reservation = await createReservation();
|
|
46
|
+
const runner = await createEnrolledRunner();
|
|
47
|
+
|
|
48
|
+
const results = await Promise.all([
|
|
49
|
+
assignRunnerInstances({
|
|
50
|
+
provisionerId,
|
|
51
|
+
reservationId: reservation.id,
|
|
52
|
+
runnerInstanceIds: [runner.id],
|
|
53
|
+
}),
|
|
54
|
+
assignRunnerInstances({
|
|
55
|
+
provisionerId,
|
|
56
|
+
reservationId: reservation.id,
|
|
57
|
+
runnerInstanceIds: [runner.id],
|
|
58
|
+
}),
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
expect(results).toEqual([[runner.id], [runner.id]]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('rejects expired reservations', async () => {
|
|
65
|
+
const reservation = await createReservation({expiresAt: new Date(Date.now() - 1_000)});
|
|
66
|
+
const runner = await createEnrolledRunner();
|
|
67
|
+
|
|
68
|
+
const assignment = assignRunnerInstances({
|
|
69
|
+
provisionerId,
|
|
70
|
+
reservationId: reservation.id,
|
|
71
|
+
runnerInstanceIds: [runner.id],
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
await expect(assignment).rejects.toThrow(ReservationExpiredError);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('rejects unenrolled or incompatible runners', async () => {
|
|
78
|
+
const reservation = await createReservation({requiredLabels: ['linux', 'gpu']});
|
|
79
|
+
const runner = await createEnrolledRunner({labels: ['linux']});
|
|
80
|
+
|
|
81
|
+
const assignment = assignRunnerInstances({
|
|
82
|
+
provisionerId,
|
|
83
|
+
reservationId: reservation.id,
|
|
84
|
+
runnerInstanceIds: [runner.id],
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
await expect(assignment).rejects.toThrow(RunnerInstanceNotAssignableError);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('rejects a runner assigned to a different reservation', async () => {
|
|
91
|
+
const firstReservation = await createReservation();
|
|
92
|
+
const secondReservation = await createReservation();
|
|
93
|
+
const runner = await createEnrolledRunner();
|
|
94
|
+
await assignRunnerInstances({
|
|
95
|
+
provisionerId,
|
|
96
|
+
reservationId: firstReservation.id,
|
|
97
|
+
runnerInstanceIds: [runner.id],
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const assignment = assignRunnerInstances({
|
|
101
|
+
provisionerId,
|
|
102
|
+
reservationId: secondReservation.id,
|
|
103
|
+
runnerInstanceIds: [runner.id],
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
await expect(assignment).rejects.toThrow(RunnerInstanceAlreadyAssignedError);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('rejects assignments that exceed reservation capacity', async () => {
|
|
110
|
+
const reservation = await createReservation();
|
|
111
|
+
const firstRunner = await createEnrolledRunner();
|
|
112
|
+
const secondRunner = await createEnrolledRunner();
|
|
113
|
+
|
|
114
|
+
const assignment = assignRunnerInstances({
|
|
115
|
+
provisionerId,
|
|
116
|
+
reservationId: reservation.id,
|
|
117
|
+
runnerInstanceIds: [firstRunner.id, secondRunner.id],
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
await expect(assignment).rejects.toThrow(RunnerInstanceNotAssignableError);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
async function createReservation(
|
|
124
|
+
overrides: Partial<{expiresAt: Date; requiredLabels: string[]}> = {},
|
|
125
|
+
) {
|
|
126
|
+
const [reservation] = await db()
|
|
127
|
+
.insert(reservations)
|
|
128
|
+
.values({
|
|
129
|
+
workspaceId,
|
|
130
|
+
provisionerId,
|
|
131
|
+
requiredLabels: overrides.requiredLabels ?? ['linux'],
|
|
132
|
+
count: 1,
|
|
133
|
+
expiresAt: overrides.expiresAt ?? new Date(Date.now() + 60_000),
|
|
134
|
+
})
|
|
135
|
+
.returning();
|
|
136
|
+
if (!reservation) throw new Error('Reservation insert returned no row');
|
|
137
|
+
return reservation;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function createEnrolledRunner(overrides: Partial<{labels: string[]}> = {}) {
|
|
141
|
+
const [runner] = await db()
|
|
142
|
+
.insert(providerRunners)
|
|
143
|
+
.values({
|
|
144
|
+
provisionerId,
|
|
145
|
+
providerRunnerId: crypto.randomUUID(),
|
|
146
|
+
labels: overrides.labels ?? ['linux'],
|
|
147
|
+
state: 'running',
|
|
148
|
+
reportedAt: new Date(),
|
|
149
|
+
})
|
|
150
|
+
.returning();
|
|
151
|
+
if (!runner) throw new Error('Runner instance insert returned no row');
|
|
152
|
+
await db()
|
|
153
|
+
.insert(runnerControlSessions)
|
|
154
|
+
.values({
|
|
155
|
+
runnerInstanceId: runner.id,
|
|
156
|
+
provisionerId,
|
|
157
|
+
hashedToken: crypto.randomUUID(),
|
|
158
|
+
prefix: 'test',
|
|
159
|
+
expiresAt: new Date(Date.now() + 60_000),
|
|
160
|
+
});
|
|
161
|
+
return runner;
|
|
162
|
+
}
|
|
163
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {and, eq, inArray, isNull, sql} from 'drizzle-orm';
|
|
2
|
+
import {
|
|
3
|
+
ReservationExpiredError,
|
|
4
|
+
ReservationNotFoundError,
|
|
5
|
+
RunnerInstanceAlreadyAssignedError,
|
|
6
|
+
RunnerInstanceNotAssignableError,
|
|
7
|
+
} from '#core/errors.js';
|
|
8
|
+
import {db} from './db.js';
|
|
9
|
+
import {reservations} from './schema/reservations.js';
|
|
10
|
+
import {runnerControlSessions} from './schema/runner-control-sessions.js';
|
|
11
|
+
import {providerRunners} from './schema/runner-instances.js';
|
|
12
|
+
|
|
13
|
+
/** Atomically consumes reservation units by writing the immutable assignment on each runner. */
|
|
14
|
+
export async function assignRunnerInstances(params: {
|
|
15
|
+
provisionerId: string;
|
|
16
|
+
reservationId: string;
|
|
17
|
+
runnerInstanceIds: string[];
|
|
18
|
+
}): Promise<string[]> {
|
|
19
|
+
const runnerInstanceIds = [...params.runnerInstanceIds].sort();
|
|
20
|
+
return await db().transaction(async (tx) => {
|
|
21
|
+
await tx.execute(
|
|
22
|
+
sql`select pg_advisory_xact_lock(hashtext(${`runners_assignment:${params.provisionerId}:${params.reservationId}`}))`,
|
|
23
|
+
);
|
|
24
|
+
const [reservation] = await tx
|
|
25
|
+
.select()
|
|
26
|
+
.from(reservations)
|
|
27
|
+
.where(
|
|
28
|
+
and(
|
|
29
|
+
eq(reservations.id, params.reservationId),
|
|
30
|
+
eq(reservations.provisionerId, params.provisionerId),
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
.limit(1)
|
|
34
|
+
.for('update');
|
|
35
|
+
if (!reservation) throw new ReservationNotFoundError(params.reservationId);
|
|
36
|
+
if (reservation.expiresAt <= new Date())
|
|
37
|
+
throw new ReservationExpiredError(params.reservationId);
|
|
38
|
+
|
|
39
|
+
const runnerRows = await tx
|
|
40
|
+
.select({
|
|
41
|
+
id: providerRunners.id,
|
|
42
|
+
reservationId: providerRunners.reservationId,
|
|
43
|
+
workspaceId: providerRunners.workspaceId,
|
|
44
|
+
providerRunnerId: providerRunners.providerRunnerId,
|
|
45
|
+
labels: providerRunners.labels,
|
|
46
|
+
state: providerRunners.state,
|
|
47
|
+
})
|
|
48
|
+
.from(providerRunners)
|
|
49
|
+
.where(
|
|
50
|
+
and(
|
|
51
|
+
eq(providerRunners.provisionerId, params.provisionerId),
|
|
52
|
+
inArray(providerRunners.id, runnerInstanceIds),
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
.for('update');
|
|
56
|
+
const activeControlSessions = await tx
|
|
57
|
+
.select({runnerInstanceId: runnerControlSessions.runnerInstanceId})
|
|
58
|
+
.from(runnerControlSessions)
|
|
59
|
+
.where(
|
|
60
|
+
and(
|
|
61
|
+
inArray(
|
|
62
|
+
runnerControlSessions.runnerInstanceId,
|
|
63
|
+
runnerRows.map((runner) => runner.id),
|
|
64
|
+
),
|
|
65
|
+
isNull(runnerControlSessions.closedAt),
|
|
66
|
+
),
|
|
67
|
+
);
|
|
68
|
+
const runnerInstanceIdsWithControlSession = new Set(
|
|
69
|
+
activeControlSessions.map((session) => session.runnerInstanceId),
|
|
70
|
+
);
|
|
71
|
+
const runners = runnerRows.map((runner) => ({
|
|
72
|
+
...runner,
|
|
73
|
+
controlSessionId: runnerInstanceIdsWithControlSession.has(runner.id) ? runner.id : null,
|
|
74
|
+
}));
|
|
75
|
+
if (runners.length !== runnerInstanceIds.length)
|
|
76
|
+
throw new RunnerInstanceNotAssignableError(runnerInstanceIds[0] ?? '');
|
|
77
|
+
|
|
78
|
+
const alreadyAssigned = runners.filter((runner) => runner.reservationId !== null);
|
|
79
|
+
if (alreadyAssigned.some((runner) => runner.reservationId !== reservation.id))
|
|
80
|
+
throw new RunnerInstanceAlreadyAssignedError(alreadyAssigned[0]?.id ?? '');
|
|
81
|
+
const newRunners = runners.filter((runner) => runner.reservationId === null);
|
|
82
|
+
const assignedCount = await tx
|
|
83
|
+
.select({count: sql<number>`count(*)::int`})
|
|
84
|
+
.from(providerRunners)
|
|
85
|
+
.where(eq(providerRunners.reservationId, reservation.id));
|
|
86
|
+
if ((assignedCount[0]?.count ?? 0) + newRunners.length > reservation.count)
|
|
87
|
+
throw new RunnerInstanceNotAssignableError(newRunners[0]?.id ?? '');
|
|
88
|
+
for (const runner of newRunners) {
|
|
89
|
+
if (
|
|
90
|
+
runner.state !== 'running' ||
|
|
91
|
+
!runner.providerRunnerId ||
|
|
92
|
+
!runner.controlSessionId ||
|
|
93
|
+
!reservation.requiredLabels.every((label) => runner.labels.includes(label))
|
|
94
|
+
)
|
|
95
|
+
throw new RunnerInstanceNotAssignableError(runner.id);
|
|
96
|
+
}
|
|
97
|
+
if (newRunners.length > 0) {
|
|
98
|
+
await tx
|
|
99
|
+
.update(providerRunners)
|
|
100
|
+
.set({
|
|
101
|
+
workspaceId: reservation.workspaceId,
|
|
102
|
+
reservationId: reservation.id,
|
|
103
|
+
assignedAt: sql`now()`,
|
|
104
|
+
updatedAt: sql`now()`,
|
|
105
|
+
})
|
|
106
|
+
.where(
|
|
107
|
+
inArray(
|
|
108
|
+
providerRunners.id,
|
|
109
|
+
newRunners.map((runner) => runner.id),
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return params.runnerInstanceIds;
|
|
114
|
+
});
|
|
115
|
+
}
|