@nosana/node 1.1.71-rc → 1.1.72-rc
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/README.md +2 -2
- package/dist/.env +2 -0
- package/dist/package.json +9 -6
- package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/docker/createDockerRunOptions.d.ts +1 -1
- package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/docker/createDockerRunOptions.js +5 -1
- package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/docker/dockerExtended/index.d.ts +3 -0
- package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/docker/dockerExtended/index.js +12 -5
- package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/docker/index.d.ts +11 -10
- package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/docker/index.js +18 -37
- package/dist/packages/container-orchestrator/src/hooks.d.ts +32 -0
- package/dist/packages/container-orchestrator/src/index.d.ts +17 -0
- package/dist/packages/container-orchestrator/src/index.js +16 -0
- package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/interface.d.ts +14 -5
- package/dist/packages/container-orchestrator/src/interface.js +1 -0
- package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/labels.d.ts +2 -0
- package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/labels.js +2 -0
- package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/podman/index.d.ts +3 -3
- package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/podman/index.js +6 -17
- package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/podman/utils/createPodmanRunOptions.d.ts +12 -0
- package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/podman/utils/createPodmanRunOptions.js +4 -2
- package/dist/packages/container-orchestrator/src/selectContainerOrchestration.d.ts +3 -0
- package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/selectContainerOrchestration.js +3 -3
- package/dist/packages/container-orchestrator/src/types.d.ts +15 -0
- package/dist/packages/container-orchestrator/src/types.js +1 -0
- package/dist/packages/container-orchestrator/src/utils/ifStringCastToArray.d.ts +2 -0
- package/dist/packages/container-orchestrator/src/utils/ifStringCastToArray.js +2 -0
- package/dist/src/NodeManager/configs/configs.d.ts +6 -0
- package/dist/src/NodeManager/configs/configs.js +6 -0
- package/dist/src/NodeManager/index.js +7 -0
- package/dist/src/NodeManager/node/Node.d.ts +5 -0
- package/dist/src/NodeManager/node/Node.js +23 -2
- package/dist/src/NodeManager/node/health/healthHandler.d.ts +1 -1
- package/dist/src/NodeManager/node/resource/definition/index.d.ts +0 -1
- package/dist/src/NodeManager/node/resource/definition/index.js +0 -1
- package/dist/src/NodeManager/node/resource/helpers/createHFArgs.d.ts +1 -1
- package/dist/src/NodeManager/node/resource/helpers/createHFArgs.js +2 -3
- package/dist/src/NodeManager/node/resource/helpers/createOllamaArgs.d.ts +1 -1
- package/dist/src/NodeManager/node/resource/helpers/createOllamaArgs.js +2 -3
- package/dist/src/NodeManager/node/resource/helpers/createS3Args.d.ts +1 -1
- package/dist/src/NodeManager/node/resource/helpers/createS3Args.js +3 -3
- package/dist/src/NodeManager/node/resource/image/imageManager.d.ts +1 -1
- package/dist/src/NodeManager/node/resource/resourceManager.d.ts +1 -1
- package/dist/src/NodeManager/node/resource/volume/volumeManager.d.ts +1 -1
- package/dist/src/NodeManager/node/resource/volume/volumeManager.js +11 -8
- package/dist/src/NodeManager/node/task/TaskManager.js +25 -0
- package/dist/src/NodeManager/provider/ContainerStateManager.js +1 -1
- package/dist/src/NodeManager/provider/Provider.d.ts +34 -10
- package/dist/src/NodeManager/provider/Provider.js +217 -93
- package/dist/src/NodeManager/provider/orchestrationHooks.d.ts +8 -0
- package/dist/src/NodeManager/provider/orchestrationHooks.js +13 -0
- package/dist/src/NodeManager/provider/sshSidecar.d.ts +2 -2
- package/dist/src/NodeManager/provider/sshSidecar.js +2 -2
- package/dist/src/NodeManager/provider/types.d.ts +0 -9
- package/dist/src/NodeManager/provider/webTerminal.d.ts +1 -1
- package/dist/src/NodeManager/provider/webTerminal.js +1 -1
- package/dist/src/NodeManager/utils/utils.d.ts +1 -7
- package/dist/src/NodeManager/utils/utils.js +1 -7
- package/dist/src/cli/commands/prune/action.js +3 -2
- package/dist/src/cli/commands/prune/command.js +1 -1
- package/dist/src/cli/commands/run/action.js +3 -2
- package/dist/src/cli/commands/run/command.js +1 -1
- package/dist/src/cli/commands/start/command.js +1 -1
- package/npm-shrinkwrap.json +21 -2
- package/package.json +9 -6
- package/dist/src/NodeManager/provider/containerOrchestration/docker/utils/createServerObject.d.ts +0 -5
- package/dist/src/NodeManager/provider/containerOrchestration/docker/utils/createServerObject.js +0 -15
- package/dist/src/NodeManager/provider/containerOrchestration/docker/utils/deadline.d.ts +0 -1
- package/dist/src/NodeManager/provider/containerOrchestration/docker/utils/deadline.js +0 -15
- package/dist/src/NodeManager/provider/containerOrchestration/selectContainerOrchestration.d.ts +0 -3
- /package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/docker/utils/repoTagsContainsImage.d.ts +0 -0
- /package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/docker/utils/repoTagsContainsImage.js +0 -0
- /package/dist/{src/NodeManager/provider/containerOrchestration/interface.js → packages/container-orchestrator/src/hooks.js} +0 -0
- /package/dist/{src/NodeManager → packages/container-orchestrator/src}/utils/liveAbortSignal.d.ts +0 -0
- /package/dist/{src/NodeManager → packages/container-orchestrator/src}/utils/liveAbortSignal.js +0 -0
|
@@ -16,6 +16,12 @@ export const configs = (options) => {
|
|
|
16
16
|
api: {
|
|
17
17
|
port: parseInt(loadConfigurationValue('API_PORT')),
|
|
18
18
|
},
|
|
19
|
+
jobRuntime: {
|
|
20
|
+
containerImage: loadConfigurationValue('JOB_RUNTIME_IMAGE'),
|
|
21
|
+
},
|
|
22
|
+
resourceManager: {
|
|
23
|
+
containerImage: loadConfigurationValue('RESOURCE_MANAGER_IMAGE'),
|
|
24
|
+
},
|
|
19
25
|
minDiskSpace: parseInt(loadConfigurationValue('MIN_DISK_SPACE')),
|
|
20
26
|
network: options?.network ?? nodeConfigs.getNetwork(),
|
|
21
27
|
};
|
|
@@ -138,6 +138,13 @@ export default class NodeManager {
|
|
|
138
138
|
if (market) {
|
|
139
139
|
await this.node.setup(market);
|
|
140
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* job runtime image
|
|
143
|
+
*
|
|
144
|
+
* pulled here, with or without a requested market, so a job's sidecar
|
|
145
|
+
* never waits on a registry
|
|
146
|
+
*/
|
|
147
|
+
await this.node.pullJobRuntimeImage();
|
|
141
148
|
/**
|
|
142
149
|
* this variable was added to know when the node has gone past the setup/checks stages
|
|
143
150
|
* and is now starting job work and queueing,
|
|
@@ -34,6 +34,11 @@ export declare class BasicNode {
|
|
|
34
34
|
clean(): Promise<void>;
|
|
35
35
|
start(): Promise<void>;
|
|
36
36
|
setup(market: Market): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Best effort: on failure jobs run without the sidecar, so it is shown to the
|
|
39
|
+
* operator and reported off the node rather than stopping the node.
|
|
40
|
+
*/
|
|
41
|
+
pullJobRuntimeImage(): Promise<void>;
|
|
37
42
|
run(): Promise<void>;
|
|
38
43
|
pending(): Promise<boolean>;
|
|
39
44
|
queue(market?: string): Promise<void>;
|
|
@@ -13,13 +13,15 @@ import { KeyHandler } from './key/keyHandler.js';
|
|
|
13
13
|
import { ExpiryHandler } from './expiry/expiryHandler.js';
|
|
14
14
|
import { GridHandler } from './grid/gridHandler.js';
|
|
15
15
|
import { ResourceManager } from './resource/resourceManager.js';
|
|
16
|
-
import { selectContainerOrchestrationProvider } from '
|
|
16
|
+
import { selectContainerOrchestrationProvider } from '../../../packages/container-orchestrator/src/index.js';
|
|
17
|
+
import { nodeOrchestrationHooks } from '../provider/orchestrationHooks.js';
|
|
17
18
|
import { RegisterHandler } from './register/index.js';
|
|
18
19
|
import { BalanceHandler } from './balance/balanceHandler.js';
|
|
19
20
|
import { abortControllerSelector, nodeAbortControllerSelector, } from './abort/abortControllerSelector.js';
|
|
20
21
|
import { pollForRun } from './utils/poll.js';
|
|
21
22
|
import { configs } from '../configs/configs.js';
|
|
22
23
|
import { TaskManagerRegistry } from './task/TaskManagerRegistry.js';
|
|
24
|
+
import { reportError } from '../monitoring/reportError.js';
|
|
23
25
|
import { StopReasons } from './task/TaskManager.js';
|
|
24
26
|
export class BasicNode {
|
|
25
27
|
constructor(options) {
|
|
@@ -30,7 +32,7 @@ export class BasicNode {
|
|
|
30
32
|
this.sdk = getSDK();
|
|
31
33
|
const db = new DB(options.config).db;
|
|
32
34
|
this.repository = new NodeRepository(db);
|
|
33
|
-
this.containerOrchestration = selectContainerOrchestrationProvider(options.provider, options.podman, options.gpu, options.trustedExecutionRuntime, this.repository);
|
|
35
|
+
this.containerOrchestration = selectContainerOrchestrationProvider(options.provider, options.podman, options.gpu, options.trustedExecutionRuntime, nodeOrchestrationHooks(this.repository));
|
|
34
36
|
this.resourceManager = new ResourceManager(this.containerOrchestration, this.repository);
|
|
35
37
|
this.provider = new Provider(this.containerOrchestration, this.repository, this.resourceManager);
|
|
36
38
|
this.apiHandler = new ApiHandler(this.sdk, this.repository, this.provider, options.port);
|
|
@@ -117,6 +119,25 @@ export class BasicNode {
|
|
|
117
119
|
await this.resourceManager.resyncResourcesDB();
|
|
118
120
|
await this.resourceManager.fetchMarketRequiredResources(market);
|
|
119
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Best effort: on failure jobs run without the sidecar, so it is shown to the
|
|
124
|
+
* operator and reported off the node rather than stopping the node.
|
|
125
|
+
*/
|
|
126
|
+
async pullJobRuntimeImage() {
|
|
127
|
+
try {
|
|
128
|
+
await this.provider.pullJobRuntimeImage();
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
132
|
+
console.error(`Failed to pull the job runtime image, jobs will run without it: ${failure.message}`);
|
|
133
|
+
void reportError({
|
|
134
|
+
error_type: 'jobRuntimeImagePull',
|
|
135
|
+
error_name: failure.name,
|
|
136
|
+
error_message: failure.message,
|
|
137
|
+
error_stack: failure.stack ?? '',
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
120
141
|
run() {
|
|
121
142
|
return new Promise(async (resolve, reject) => {
|
|
122
143
|
try {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Market, Client as SDK } from '@nosana/sdk';
|
|
2
2
|
import { MarketHandler } from '../market/marketHandler.js';
|
|
3
3
|
import { KeyHandler } from '../key/keyHandler.js';
|
|
4
|
-
import { ContainerOrchestrationInterface } from '
|
|
4
|
+
import { ContainerOrchestrationInterface } from '../../../../packages/container-orchestrator/src/index.js';
|
|
5
5
|
export declare class HealthHandler {
|
|
6
6
|
private sdk;
|
|
7
7
|
private containerOrchestration;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export const createHFArgs = (volumeName, hf, accessToken) => ({
|
|
1
|
+
export const createHFArgs = (volumeName, hf, accessToken, image) => ({
|
|
3
2
|
AttachStdin: false,
|
|
4
3
|
AttachStdout: true,
|
|
5
4
|
AttachStderr: true,
|
|
@@ -12,7 +11,7 @@ export const createHFArgs = (volumeName, hf, accessToken) => ({
|
|
|
12
11
|
hf.revision ?? (hf.files ? 'main' : ''),
|
|
13
12
|
hf.files ? hf.files.join(',') : '',
|
|
14
13
|
],
|
|
15
|
-
Image:
|
|
14
|
+
Image: image,
|
|
16
15
|
Env: accessToken ? [`HUGGING_FACE_TOKEN=${accessToken}`] : undefined,
|
|
17
16
|
HostConfig: {
|
|
18
17
|
Mounts: [
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { ContainerCreateOptions } from 'dockerode';
|
|
2
|
-
export declare const createOllamaArgs: (volumeName: string, model: string) => ContainerCreateOptions;
|
|
2
|
+
export declare const createOllamaArgs: (volumeName: string, model: string, image: string) => ContainerCreateOptions;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export const createOllamaArgs = (volumeName, model) => ({
|
|
1
|
+
export const createOllamaArgs = (volumeName, model, image) => ({
|
|
3
2
|
AttachStdin: false,
|
|
4
3
|
AttachStdout: true,
|
|
5
4
|
AttachStderr: true,
|
|
@@ -7,7 +6,7 @@ export const createOllamaArgs = (volumeName, model) => ({
|
|
|
7
6
|
OpenStdin: false,
|
|
8
7
|
StdinOnce: false,
|
|
9
8
|
Cmd: ['Ollama', model],
|
|
10
|
-
Image:
|
|
9
|
+
Image: image,
|
|
11
10
|
HostConfig: {
|
|
12
11
|
Mounts: [
|
|
13
12
|
{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { nosanaBucket
|
|
2
|
-
export const createS3Args = (volumeName, s3, s3Auth) => ({
|
|
1
|
+
import { nosanaBucket } from '../definition/index.js';
|
|
2
|
+
export const createS3Args = (volumeName, s3, s3Auth, image) => ({
|
|
3
3
|
AttachStdin: false,
|
|
4
4
|
AttachStdout: true,
|
|
5
5
|
AttachStderr: true,
|
|
@@ -11,7 +11,7 @@ export const createS3Args = (volumeName, s3, s3Auth) => ({
|
|
|
11
11
|
s3.url.replace('s3://nos-ai-models-qllsn32u', nosanaBucket),
|
|
12
12
|
s3.files ? s3.files.join(',') : '',
|
|
13
13
|
],
|
|
14
|
-
Image:
|
|
14
|
+
Image: image,
|
|
15
15
|
Env: s3Auth
|
|
16
16
|
? [
|
|
17
17
|
s3.bucket ? `BUCKET=${s3.bucket}` : '',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DockerAuth } from '@nosana/sdk';
|
|
2
|
-
import { ContainerOrchestrationInterface } from '
|
|
2
|
+
import { ContainerOrchestrationInterface } from '../../../../../packages/container-orchestrator/src/index.js';
|
|
3
3
|
import { NodeRepository } from '../../../repository/NodeRepository.js';
|
|
4
4
|
/**
|
|
5
5
|
* An image the market requires, and the registry credentials it takes to pull
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Market, RequiredResource } from '@nosana/sdk';
|
|
2
2
|
import { Resource } from '@nosana/sdk/dist/types/resources.js';
|
|
3
|
-
import { ContainerOrchestrationInterface } from '
|
|
3
|
+
import { ContainerOrchestrationInterface } from '../../../../packages/container-orchestrator/src/index.js';
|
|
4
4
|
import { NodeRepository } from '../../repository/NodeRepository.js';
|
|
5
5
|
import { ImageManager, RequiredImage } from './image/imageManager.js';
|
|
6
6
|
import { JobContext, VolumeManager } from './volume/volumeManager.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ContainerInspectInfo } from 'dockerode';
|
|
2
2
|
import { Resource } from '@nosana/sdk/dist/types/resources.js';
|
|
3
3
|
import { RequiredResource } from '@nosana/sdk';
|
|
4
|
-
import { ContainerOrchestrationInterface } from '
|
|
4
|
+
import { ContainerOrchestrationInterface } from '../../../../../packages/container-orchestrator/src/index.js';
|
|
5
5
|
import { NodeRepository } from '../../../repository/NodeRepository.js';
|
|
6
6
|
/**
|
|
7
7
|
* Renders the runtime state that says why the download stopped, for inclusion in
|
|
@@ -5,12 +5,13 @@ import { ProgressBarReporter } from '../../utils/progressBarReporter.js';
|
|
|
5
5
|
import { createResourceName } from '../helpers/createResourceName.js';
|
|
6
6
|
import { hasDockerVolume } from '../helpers/hasDockerVolume.js';
|
|
7
7
|
import { hoursSinceDate } from '../helpers/hoursSunceDate.js';
|
|
8
|
-
import {
|
|
8
|
+
import { configs } from '../../../configs/configs.js';
|
|
9
|
+
import { nosanaBucket } from '../definition/index.js';
|
|
9
10
|
import { convertFromBytes } from '../../utils/convertFromBytes.js';
|
|
10
11
|
import { createHFArgs } from '../helpers/createHFArgs.js';
|
|
11
12
|
import { createS3Args } from '../helpers/createS3Args.js';
|
|
12
13
|
import { createOllamaArgs } from '../helpers/createOllamaArgs.js';
|
|
13
|
-
import { liveAbortSignal } from '
|
|
14
|
+
import { liveAbortSignal } from '../../../../../packages/container-orchestrator/src/index.js';
|
|
14
15
|
import { formatTime } from '../../../utils/formatTime.js';
|
|
15
16
|
/**
|
|
16
17
|
* Renders the runtime state that says why the download stopped, for inclusion in
|
|
@@ -100,9 +101,10 @@ export class VolumeManager {
|
|
|
100
101
|
async pullMarketRequiredVolumes(remoteResources) {
|
|
101
102
|
this.fetched = true;
|
|
102
103
|
this.market_required_volumes = remoteResources;
|
|
103
|
-
const
|
|
104
|
+
const resourceManagerImage = configs().resourceManager.containerImage;
|
|
105
|
+
const hasResourceManagerImage = await this.containerOrchestration.hasImage(resourceManagerImage);
|
|
104
106
|
if (!hasResourceManagerImage) {
|
|
105
|
-
await this.containerOrchestration.pullImage(
|
|
107
|
+
await this.containerOrchestration.pullImage(resourceManagerImage);
|
|
106
108
|
}
|
|
107
109
|
const controller = new AbortController();
|
|
108
110
|
const savedVolumes = this.repository.getVolumesResources();
|
|
@@ -199,6 +201,7 @@ export class VolumeManager {
|
|
|
199
201
|
project,
|
|
200
202
|
});
|
|
201
203
|
}
|
|
204
|
+
const resourceManagerImage = configs().resourceManager.containerImage;
|
|
202
205
|
switch (resource.type) {
|
|
203
206
|
case 'S3':
|
|
204
207
|
const s3Resource = resource;
|
|
@@ -208,12 +211,12 @@ export class VolumeManager {
|
|
|
208
211
|
url: s3Resource.url,
|
|
209
212
|
files: s3Resource.files,
|
|
210
213
|
bucket: s3Resource.bucket,
|
|
211
|
-
}, s3Resource.IAM);
|
|
214
|
+
}, s3Resource.IAM, resourceManagerImage);
|
|
212
215
|
await this.runResourceManagerContainer(volumeName, resourceName, args, controller, sync, job);
|
|
213
216
|
}
|
|
214
217
|
else {
|
|
215
218
|
for (const bucket of s3Resource.buckets) {
|
|
216
|
-
const args = createS3Args(volumeName, { url: bucket.url, files: bucket.files }, s3Resource.IAM);
|
|
219
|
+
const args = createS3Args(volumeName, { url: bucket.url, files: bucket.files }, s3Resource.IAM, resourceManagerImage);
|
|
217
220
|
await this.runResourceManagerContainer(volumeName, resourceName, args, controller, sync, job);
|
|
218
221
|
}
|
|
219
222
|
}
|
|
@@ -226,7 +229,7 @@ export class VolumeManager {
|
|
|
226
229
|
case 'HF':
|
|
227
230
|
try {
|
|
228
231
|
const { repo, revision, files: hfFiles, accessToken, } = resource;
|
|
229
|
-
const args = createHFArgs(volumeName, { repo, revision, files: hfFiles }, accessToken);
|
|
232
|
+
const args = createHFArgs(volumeName, { repo, revision, files: hfFiles }, accessToken, resourceManagerImage);
|
|
230
233
|
await this.runResourceManagerContainer(volumeName, resourceName, args, controller, sync, job);
|
|
231
234
|
this.setVolume(resourceName, volumeName, project);
|
|
232
235
|
}
|
|
@@ -237,7 +240,7 @@ export class VolumeManager {
|
|
|
237
240
|
case 'Ollama':
|
|
238
241
|
try {
|
|
239
242
|
const { model } = resource;
|
|
240
|
-
const args = createOllamaArgs(volumeName, model);
|
|
243
|
+
const args = createOllamaArgs(volumeName, model, resourceManagerImage);
|
|
241
244
|
await this.runResourceManagerContainer(volumeName, resourceName, args, controller, sync, job);
|
|
242
245
|
this.setVolume(resourceName, volumeName, project);
|
|
243
246
|
}
|
|
@@ -43,6 +43,19 @@ export const OperationProgressStatuses = {
|
|
|
43
43
|
PENDING: 'pending',
|
|
44
44
|
INIT: 'init',
|
|
45
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* The job runtime sidecar is best effort: its failure is reported off the
|
|
48
|
+
* node (the provider's logging proxy has the entry too) and the job runs on.
|
|
49
|
+
*/
|
|
50
|
+
function reportJobRuntimeSidecarFailure(stage, error) {
|
|
51
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
52
|
+
void reportError({
|
|
53
|
+
error_type: stage === 'start' ? 'jobRuntimeSidecarStart' : 'jobRuntimeSidecarStop',
|
|
54
|
+
error_name: failure.name,
|
|
55
|
+
error_message: failure.message,
|
|
56
|
+
error_stack: failure.stack ?? '',
|
|
57
|
+
});
|
|
58
|
+
}
|
|
46
59
|
export default class TaskManager {
|
|
47
60
|
constructor(provider, repository, job, project, definition) {
|
|
48
61
|
this.provider = provider;
|
|
@@ -298,6 +311,11 @@ export default class TaskManager {
|
|
|
298
311
|
});
|
|
299
312
|
// update all operations status to pending since we have started
|
|
300
313
|
[...this.opMap.keys()].forEach((op) => this.operationStatus.set(op, OperationProgressStatuses.PENDING));
|
|
314
|
+
// Best effort. After the ops are pending, so a stop that lands during
|
|
315
|
+
// it marks them stopped and the loop skips them.
|
|
316
|
+
await this.provider
|
|
317
|
+
.startJobRuntimeSidecar(flow)
|
|
318
|
+
.catch((error) => reportJobRuntimeSidecarFailure('start', error));
|
|
301
319
|
// Loop through execution plan, group by group
|
|
302
320
|
for (const p of this.executionPlan) {
|
|
303
321
|
this.currentGroup = p.group;
|
|
@@ -403,6 +421,9 @@ export default class TaskManager {
|
|
|
403
421
|
for (const op of this.opMap.values()) {
|
|
404
422
|
closingOperationPromises.push(this.provider.stopTaskManagerOperation(flow, op));
|
|
405
423
|
}
|
|
424
|
+
closingOperationPromises.push(this.provider
|
|
425
|
+
.stopJobRuntimeSidecar(this.job)
|
|
426
|
+
.catch((error) => reportJobRuntimeSidecarFailure('stop', error)));
|
|
406
427
|
await Promise.all(closingOperationPromises);
|
|
407
428
|
}
|
|
408
429
|
})();
|
|
@@ -439,6 +460,10 @@ export default class TaskManager {
|
|
|
439
460
|
// We ignore start errors during shutdown — cleanup should proceed regardless
|
|
440
461
|
}
|
|
441
462
|
}
|
|
463
|
+
// start() normally removed it already.
|
|
464
|
+
await this.provider
|
|
465
|
+
.stopJobRuntimeSidecar(this.job)
|
|
466
|
+
.catch((error) => reportJobRuntimeSidecarFailure('stop', error));
|
|
442
467
|
}
|
|
443
468
|
finally {
|
|
444
469
|
// Cleanup the lock regardless of outcome
|
|
@@ -2,7 +2,7 @@ import { Readable } from 'stream';
|
|
|
2
2
|
import { createInterface } from 'readline';
|
|
3
3
|
import { parseLogFrames } from './utils/parseBuffer.js';
|
|
4
4
|
import { parseDockerStat } from './utils/parseDockerStat.js';
|
|
5
|
-
import { liveAbortSignal } from '
|
|
5
|
+
import { liveAbortSignal } from '../../../packages/container-orchestrator/src/index.js';
|
|
6
6
|
function destroyStream(stream) {
|
|
7
7
|
if (!stream)
|
|
8
8
|
return;
|
|
@@ -3,13 +3,14 @@ import { Flow, Operation, OperationArgsMap, OperationType } from '@nosana/sdk';
|
|
|
3
3
|
import EventEmitter from 'events';
|
|
4
4
|
import { NodeRepository } from '../repository/NodeRepository.js';
|
|
5
5
|
import { ResourceManager } from '../node/resource/resourceManager.js';
|
|
6
|
-
import { ContainerOrchestrationInterface } from '
|
|
6
|
+
import { ContainerOrchestrationInterface } from '../../../packages/container-orchestrator/src/index.js';
|
|
7
7
|
import { type TerminalSession } from './webTerminal.js';
|
|
8
8
|
export declare class Provider {
|
|
9
9
|
containerOrchestration: ContainerOrchestrationInterface;
|
|
10
10
|
private repository;
|
|
11
11
|
private resourceManager;
|
|
12
12
|
private readonly frpcImage;
|
|
13
|
+
private jobRuntimeVolumesSwept;
|
|
13
14
|
private readonly sshAccess;
|
|
14
15
|
private proxyStartupAbortController;
|
|
15
16
|
constructor(containerOrchestration: ContainerOrchestrationInterface, repository: NodeRepository, resourceManager: ResourceManager);
|
|
@@ -17,11 +18,41 @@ export declare class Provider {
|
|
|
17
18
|
stopReverseProxyApi(address: string): Promise<boolean>;
|
|
18
19
|
setUpReverseProxyApi(address: string, port: string): Promise<boolean>;
|
|
19
20
|
generateApiFrpcProxies(address: string, port: string): any[];
|
|
21
|
+
/**
|
|
22
|
+
* Starts the job's API sidecar (packages/job-runtime) and publishes it at
|
|
23
|
+
* https://<jobId>.<frp server>. The sidecar serves the job API's runtime
|
|
24
|
+
* routes from the container runtime alone; the node's own API is unchanged.
|
|
25
|
+
*
|
|
26
|
+
* Throws on failure; the task manager treats it as best effort and the job
|
|
27
|
+
* runs on without it. Only onchain jobs get one. The job handler registers
|
|
28
|
+
* those in the task manager registry, and a market benchmark or
|
|
29
|
+
* `nosana node run` never does.
|
|
30
|
+
*/
|
|
31
|
+
startJobRuntimeSidecar(flow: Flow): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Pulls the job runtime image, so the next job's sidecar finds it local.
|
|
34
|
+
* Runs before each job loop; a cached image costs no registry round trip.
|
|
35
|
+
*/
|
|
36
|
+
pullJobRuntimeImage(): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Removes the job's API sidecar, its proxy and its volume. Throws on
|
|
39
|
+
* failure; a container that will not go stays tracked for the next resync.
|
|
40
|
+
*/
|
|
41
|
+
stopJobRuntimeSidecar(flowId: string): Promise<void>;
|
|
42
|
+
/** Removes the sidecar and its proxy by name; one already gone is fine. */
|
|
43
|
+
private removeJobRuntimeContainers;
|
|
44
|
+
/**
|
|
45
|
+
* Removes job-runtime-<jobId> volumes left by a job whose stop never ran (the
|
|
46
|
+
* node died mid-job). The volume resync does not know them, and a reaped
|
|
47
|
+
* sidecar container leaves its named volume behind. Runs once, on the first
|
|
48
|
+
* sidecar start after boot. A job still registered keeps its volume, and a
|
|
49
|
+
* volume still in use fails to delete and is kept for the next boot.
|
|
50
|
+
*/
|
|
51
|
+
private removeOrphanedJobRuntimeVolumes;
|
|
20
52
|
/**
|
|
21
53
|
* Opens an interactive PTY exec session into a running job container for the
|
|
22
54
|
* wallet-authorized web terminal. The authorization check is performed by the
|
|
23
|
-
* caller (the terminal websocket route)
|
|
24
|
-
* is enabled and that we can reach the runtime over a socket.
|
|
55
|
+
* caller (the terminal websocket route).
|
|
25
56
|
*/
|
|
26
57
|
createJobTerminalSession(jobId: string, options?: {
|
|
27
58
|
op?: string;
|
|
@@ -31,13 +62,6 @@ export declare class Provider {
|
|
|
31
62
|
authorizeSshKeyForJob(jobId: string, publicKey: string, expiresAt?: string): Promise<void>;
|
|
32
63
|
listSshKeysForJob(jobId: string): Promise<import("../db/index.js").SshKeyGrant[]>;
|
|
33
64
|
revokeSshKeyForJob(jobId: string, publicKey: string): Promise<void>;
|
|
34
|
-
/**
|
|
35
|
-
* SSH requires the runtime to be reached over a socket the sidecar can mount
|
|
36
|
-
* to exec into the workload. Every SSH entry point, including the web
|
|
37
|
-
* terminal, checks this and nothing else.
|
|
38
|
-
*/
|
|
39
|
-
private get sshEnabled();
|
|
40
|
-
private assertSshEnabled;
|
|
41
65
|
/** The SSH sidecars of a job, one per operation, created ones included. */
|
|
42
66
|
private getSshSidecars;
|
|
43
67
|
private hostSocketPath?;
|