@nosana/node 1.1.41-rc → 1.1.43-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/dist/package.json +1 -1
- package/dist/src/NodeManager/node/task/TaskManager.js +6 -0
- package/dist/src/NodeManager/provider/Provider.js +137 -138
- package/dist/src/NodeManager/provider/containerOrchestration/docker/index.d.ts +0 -1
- package/dist/src/NodeManager/provider/containerOrchestration/docker/index.js +0 -4
- package/dist/src/NodeManager/provider/containerOrchestration/interface.d.ts +0 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/package.json
CHANGED
|
@@ -307,6 +307,12 @@ export default class TaskManager {
|
|
|
307
307
|
});
|
|
308
308
|
// Queue each operation in the group for execution
|
|
309
309
|
for (const id of p.ops) {
|
|
310
|
+
// Only an operation still pending has yet to run: one released by
|
|
311
|
+
// the operation it depends on runs in that operation's group, which
|
|
312
|
+
// can be earlier than this one.
|
|
313
|
+
if (this.operationStatus.get(id) !== OperationProgressStatuses.PENDING) {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
310
316
|
const dependencyContext = this.dependecyMap.get(id);
|
|
311
317
|
const dependencies = dependencyContext.dependencies || [];
|
|
312
318
|
const depsSatisfied = dependencies.length === 0 ||
|
|
@@ -131,151 +131,150 @@ export class Provider {
|
|
|
131
131
|
try {
|
|
132
132
|
emitter.emit('start');
|
|
133
133
|
let frpcContainer;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
await this.containerOrchestration.pullImage(op.args.image, op.args.authentication?.docker, controller);
|
|
146
|
-
if (!op.args.authentication?.docker) {
|
|
147
|
-
this.resourceManager.images.setImage(op.args.image);
|
|
148
|
-
}
|
|
149
|
-
const index = getOpStateIndex(flow.jobDefinition.ops, op.id);
|
|
150
|
-
const name = flow.id + '-' + index;
|
|
151
|
-
const volumes = getVolumes(op.args, flow);
|
|
152
|
-
const gpu = getGpu(op.args, flow);
|
|
153
|
-
const entrypoint = getEntrypoint(op.args, flow);
|
|
154
|
-
const work_dir = getWorkingDir(op.args, flow);
|
|
155
|
-
const cmd = parseOpArgsCmd(op.args.cmd);
|
|
156
|
-
const env = {
|
|
157
|
-
...getGlobalEnv(flow),
|
|
158
|
-
...op.args.env,
|
|
159
|
-
NOSANA_ID: flow.id,
|
|
160
|
-
};
|
|
161
|
-
const aliases = getAliases(op.args);
|
|
162
|
-
const networks = {};
|
|
163
|
-
let idMaps = new Map();
|
|
164
|
-
const ports = getExposePorts(op);
|
|
165
|
-
networks[name] = {};
|
|
166
|
-
await this.containerOrchestration.createNetwork(name, controller);
|
|
167
|
-
if (isOpExposed(op)) {
|
|
168
|
-
await this.containerOrchestration.pullImage(this.frpcImage, undefined, controller);
|
|
169
|
-
this.resourceManager.images.setImage(this.frpcImage);
|
|
170
|
-
/**
|
|
171
|
-
* because of the introduction of deployment_id(load balancing key) instead of using flow.id we use deployment_id for more
|
|
172
|
-
* deterministic url generation
|
|
173
|
-
*/
|
|
174
|
-
const config = NodeConfigsSingleton.getInstance();
|
|
175
|
-
const isLoadBalanced = !!flow.jobDefinition.deployment_id;
|
|
176
|
-
const deploymentHash = isLoadBalanced
|
|
177
|
-
? // @ts-ignore
|
|
178
|
-
config.options.isNodeRun
|
|
179
|
-
? flow.jobDefinition.deployment_id
|
|
180
|
-
: createHash(`${flow.jobDefinition.deployment_id}:${flow.project}`, 45)
|
|
181
|
-
: undefined;
|
|
182
|
-
const { proxies, idMap } = generateProxies(flow.id, op, index, ports, name, op.id, deploymentHash);
|
|
183
|
-
idMaps = idMap;
|
|
184
|
-
frpcContainer = await this.containerOrchestration.runFlowContainer(this.frpcImage, this.generateFrpcContainerConfig(name, networks, flow, proxies, isLoadBalanced, deploymentHash));
|
|
185
|
-
if (op.args.private) {
|
|
186
|
-
this.repository.updateflowStateSecret(flow.id, {
|
|
187
|
-
[flow.id]: generateUrlSecretObject(idMap, op.id),
|
|
188
|
-
urlmode: 'private',
|
|
189
|
-
});
|
|
190
|
-
emitter.emit('flow:secrets-updated', {
|
|
191
|
-
flowId: flow.id,
|
|
192
|
-
opId: op.id,
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
const newSecret = generateUrlSecretObject(idMap, op.id);
|
|
197
|
-
const currentSecrets = this.repository.getFlowSecret(flow.id, flow.id) || {};
|
|
198
|
-
const mergedSecrets = {
|
|
199
|
-
...currentSecrets,
|
|
200
|
-
...newSecret,
|
|
201
|
-
};
|
|
202
|
-
this.repository.updateflowStateSecret(flow.id, {
|
|
203
|
-
[flow.id]: mergedSecrets,
|
|
204
|
-
urlmode: 'public',
|
|
205
|
-
});
|
|
206
|
-
emitter.emit('flow:secrets-updated', {
|
|
207
|
-
flowId: flow.id,
|
|
208
|
-
opId: op.id,
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
if (op.args.resources) {
|
|
213
|
-
await this.containerOrchestration.pullImage(s3HelperImage, undefined, controller);
|
|
214
|
-
this.resourceManager.images.setImage(s3HelperImage);
|
|
215
|
-
// transformCollections has already resolved any SpreadMarker objects by this point
|
|
216
|
-
const resourceVolumes = await this.resourceManager.getResourceVolumes(op.args.resources ?? [], controller, { id: flow.id, opIndex: index });
|
|
217
|
-
volumes.push(...resourceVolumes);
|
|
134
|
+
// A resumed flow re-runs operations that may already have a container, and
|
|
135
|
+
// the name below is the only handle on it.
|
|
136
|
+
const index = getOpStateIndex(flow.jobDefinition.ops, op.id);
|
|
137
|
+
const name = flow.id + '-' + index;
|
|
138
|
+
// An operation runs the same way whether or not the flow it belongs to has
|
|
139
|
+
// already run, so whatever a previous run left behind under these names is
|
|
140
|
+
// removed rather than resumed from. The runtime refuses to create a
|
|
141
|
+
// container whose name is still taken, and all three are recreated below.
|
|
142
|
+
for (const leftover of [name, `frpc-${name}`, `bridge-${name}`]) {
|
|
143
|
+
if (await this.containerOrchestration.doesContainerExist(leftover)) {
|
|
144
|
+
await this.containerOrchestration.stopAndDeleteContainer(leftover);
|
|
218
145
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
146
|
+
}
|
|
147
|
+
await this.containerOrchestration.pullImage(op.args.image, op.args.authentication?.docker, controller);
|
|
148
|
+
if (!op.args.authentication?.docker) {
|
|
149
|
+
this.resourceManager.images.setImage(op.args.image);
|
|
150
|
+
}
|
|
151
|
+
const volumes = getVolumes(op.args, flow);
|
|
152
|
+
const gpu = getGpu(op.args, flow);
|
|
153
|
+
const entrypoint = getEntrypoint(op.args, flow);
|
|
154
|
+
const work_dir = getWorkingDir(op.args, flow);
|
|
155
|
+
const cmd = parseOpArgsCmd(op.args.cmd);
|
|
156
|
+
const env = {
|
|
157
|
+
...getGlobalEnv(flow),
|
|
158
|
+
...op.args.env,
|
|
159
|
+
NOSANA_ID: flow.id,
|
|
160
|
+
};
|
|
161
|
+
const aliases = getAliases(op.args);
|
|
162
|
+
const networks = {};
|
|
163
|
+
let idMaps = new Map();
|
|
164
|
+
const ports = getExposePorts(op);
|
|
165
|
+
networks[name] = {};
|
|
166
|
+
await this.containerOrchestration.createNetwork(name, controller);
|
|
167
|
+
if (isOpExposed(op)) {
|
|
168
|
+
await this.containerOrchestration.pullImage(this.frpcImage, undefined, controller);
|
|
169
|
+
this.resourceManager.images.setImage(this.frpcImage);
|
|
170
|
+
/**
|
|
171
|
+
* because of the introduction of deployment_id(load balancing key) instead of using flow.id we use deployment_id for more
|
|
172
|
+
* deterministic url generation
|
|
173
|
+
*/
|
|
174
|
+
const config = NodeConfigsSingleton.getInstance();
|
|
175
|
+
const isLoadBalanced = !!flow.jobDefinition.deployment_id;
|
|
176
|
+
const deploymentHash = isLoadBalanced
|
|
177
|
+
? // @ts-ignore
|
|
178
|
+
config.options.isNodeRun
|
|
179
|
+
? flow.jobDefinition.deployment_id
|
|
180
|
+
: createHash(`${flow.jobDefinition.deployment_id}:${flow.project}`, 45)
|
|
223
181
|
: undefined;
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
restart_policy: op.args.restart_policy,
|
|
236
|
-
runtime: teeRuntime,
|
|
237
|
-
};
|
|
238
|
-
if (teeRuntime === 'SEV-SNP') {
|
|
239
|
-
await this.containerOrchestration.pullImage("busybox");
|
|
240
|
-
await this.resourceManager.images.setImage("busybox");
|
|
241
|
-
const bridgeContainer = await this.containerOrchestration.runFlowContainer("busybox", {
|
|
242
|
-
name: `bridge-${name}`,
|
|
243
|
-
cmd: ['sleep', 'infinity'],
|
|
244
|
-
aliases: createContainerOptions.aliases
|
|
182
|
+
const { proxies, idMap } = generateProxies(flow.id, op, index, ports, name, op.id, deploymentHash);
|
|
183
|
+
idMaps = idMap;
|
|
184
|
+
frpcContainer = await this.containerOrchestration.runFlowContainer(this.frpcImage, this.generateFrpcContainerConfig(name, networks, flow, proxies, isLoadBalanced, deploymentHash));
|
|
185
|
+
if (op.args.private) {
|
|
186
|
+
this.repository.updateflowStateSecret(flow.id, {
|
|
187
|
+
[flow.id]: generateUrlSecretObject(idMap, op.id),
|
|
188
|
+
urlmode: 'private',
|
|
189
|
+
});
|
|
190
|
+
emitter.emit('flow:secrets-updated', {
|
|
191
|
+
flowId: flow.id,
|
|
192
|
+
opId: op.id,
|
|
245
193
|
});
|
|
246
|
-
delete createContainerOptions.aliases;
|
|
247
|
-
createContainerOptions.bind_network_to_container = `container:bridge-${name}`;
|
|
248
|
-
const bridgeInfo = await bridgeContainer.inspect();
|
|
249
|
-
if (bridgeInfo.NetworkSettings?.Networks["NOSANA_GATEWAY"].IPAddress) {
|
|
250
|
-
emitter.emit('setContainerInternalIp', bridgeInfo.NetworkSettings.Networks["NOSANA_GATEWAY"].IPAddress);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
container = await this.containerOrchestration.runFlowContainer(op.args.image ?? flow.jobDefinition.global?.image, createContainerOptions);
|
|
254
|
-
// The container is now running (image pull is already done). This is the
|
|
255
|
-
// point the op-execution timeout clock starts from, so pull time isn't
|
|
256
|
-
// counted against it.
|
|
257
|
-
emitter.emit('container:started');
|
|
258
|
-
const info = await container.inspect();
|
|
259
|
-
emitter.emit('updateOpState', { providerId: container.id });
|
|
260
|
-
if (teeRuntime !== 'SEV-SNP' && info.NetworkSettings?.Networks["NOSANA_GATEWAY"].IPAddress) {
|
|
261
|
-
emitter.emit('setContainerInternalIp', info.NetworkSettings.Networks["NOSANA_GATEWAY"].IPAddress);
|
|
262
|
-
}
|
|
263
|
-
stateManager = new ContainerStateManager(container, controller, emitter, op.args.restart_policy);
|
|
264
|
-
await stateManager.startMonitoring();
|
|
265
|
-
if (isOpExposed(op)) {
|
|
266
|
-
exposedPortHealthCheck = new ExposedPortHealthCheck(flow.id, frpcContainer, emitter, name);
|
|
267
|
-
exposedPortHealthCheck.addExposedPortsMap(idMaps);
|
|
268
|
-
exposedPortHealthCheck.startServiceExposedUrlHealthCheck();
|
|
269
194
|
}
|
|
270
195
|
else {
|
|
271
|
-
|
|
196
|
+
const newSecret = generateUrlSecretObject(idMap, op.id);
|
|
197
|
+
const currentSecrets = this.repository.getFlowSecret(flow.id, flow.id) || {};
|
|
198
|
+
const mergedSecrets = {
|
|
199
|
+
...currentSecrets,
|
|
200
|
+
...newSecret,
|
|
201
|
+
};
|
|
202
|
+
this.repository.updateflowStateSecret(flow.id, {
|
|
203
|
+
[flow.id]: mergedSecrets,
|
|
204
|
+
urlmode: 'public',
|
|
205
|
+
});
|
|
206
|
+
emitter.emit('flow:secrets-updated', {
|
|
207
|
+
flowId: flow.id,
|
|
208
|
+
opId: op.id,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (op.args.resources) {
|
|
213
|
+
await this.containerOrchestration.pullImage(s3HelperImage, undefined, controller);
|
|
214
|
+
this.resourceManager.images.setImage(s3HelperImage);
|
|
215
|
+
// transformCollections has already resolved any SpreadMarker objects by this point
|
|
216
|
+
const resourceVolumes = await this.resourceManager.getResourceVolumes(op.args.resources ?? [], controller, { id: flow.id, opIndex: index });
|
|
217
|
+
volumes.push(...resourceVolumes);
|
|
218
|
+
}
|
|
219
|
+
// The op only declares that it wants a TEE; the node's configured
|
|
220
|
+
// runtime decides how that is fulfilled.
|
|
221
|
+
const teeRuntime = op.args.trusted_execution_env
|
|
222
|
+
? this.containerOrchestration.teeRuntime
|
|
223
|
+
: undefined;
|
|
224
|
+
const createContainerOptions = {
|
|
225
|
+
name,
|
|
226
|
+
cmd,
|
|
227
|
+
env,
|
|
228
|
+
networks,
|
|
229
|
+
requires_network_mode: isOpExposed(op),
|
|
230
|
+
gpu,
|
|
231
|
+
entrypoint,
|
|
232
|
+
work_dir,
|
|
233
|
+
volumes,
|
|
234
|
+
aliases,
|
|
235
|
+
restart_policy: op.args.restart_policy,
|
|
236
|
+
runtime: teeRuntime,
|
|
237
|
+
};
|
|
238
|
+
if (teeRuntime === 'SEV-SNP') {
|
|
239
|
+
await this.containerOrchestration.pullImage("busybox");
|
|
240
|
+
await this.resourceManager.images.setImage("busybox");
|
|
241
|
+
const bridgeContainer = await this.containerOrchestration.runFlowContainer("busybox", {
|
|
242
|
+
name: `bridge-${name}`,
|
|
243
|
+
cmd: ['sleep', 'infinity'],
|
|
244
|
+
aliases: createContainerOptions.aliases
|
|
245
|
+
});
|
|
246
|
+
delete createContainerOptions.aliases;
|
|
247
|
+
createContainerOptions.bind_network_to_container = `container:bridge-${name}`;
|
|
248
|
+
const bridgeInfo = await bridgeContainer.inspect();
|
|
249
|
+
if (bridgeInfo.NetworkSettings?.Networks["NOSANA_GATEWAY"].IPAddress) {
|
|
250
|
+
emitter.emit('setContainerInternalIp', bridgeInfo.NetworkSettings.Networks["NOSANA_GATEWAY"].IPAddress);
|
|
272
251
|
}
|
|
273
|
-
await stateManager.waitForExit();
|
|
274
|
-
// `wait()` reporting the exit and the log stream finishing its final
|
|
275
|
-
// flush are independent; drain the stream before results are read off
|
|
276
|
-
// the logs so the last line (e.g. the op's result) isn't lost.
|
|
277
|
-
await stateManager.waitForLogsDrained();
|
|
278
252
|
}
|
|
253
|
+
const container = await this.containerOrchestration.runFlowContainer(op.args.image ?? flow.jobDefinition.global?.image, createContainerOptions);
|
|
254
|
+
// The container is now running (image pull is already done). This is the
|
|
255
|
+
// point the op-execution timeout clock starts from, so pull time isn't
|
|
256
|
+
// counted against it.
|
|
257
|
+
emitter.emit('container:started');
|
|
258
|
+
const startedInfo = await container.inspect();
|
|
259
|
+
emitter.emit('updateOpState', { providerId: container.id });
|
|
260
|
+
if (teeRuntime !== 'SEV-SNP' && startedInfo.NetworkSettings?.Networks["NOSANA_GATEWAY"].IPAddress) {
|
|
261
|
+
emitter.emit('setContainerInternalIp', startedInfo.NetworkSettings.Networks["NOSANA_GATEWAY"].IPAddress);
|
|
262
|
+
}
|
|
263
|
+
stateManager = new ContainerStateManager(container, controller, emitter, op.args.restart_policy);
|
|
264
|
+
await stateManager.startMonitoring();
|
|
265
|
+
if (isOpExposed(op)) {
|
|
266
|
+
exposedPortHealthCheck = new ExposedPortHealthCheck(flow.id, frpcContainer, emitter, name);
|
|
267
|
+
exposedPortHealthCheck.addExposedPortsMap(idMaps);
|
|
268
|
+
exposedPortHealthCheck.startServiceExposedUrlHealthCheck();
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
emitter.emit('healthcheck:startup:success');
|
|
272
|
+
}
|
|
273
|
+
await stateManager.waitForExit();
|
|
274
|
+
// `wait()` reporting the exit and the log stream finishing its final
|
|
275
|
+
// flush are independent; drain the stream before results are read off
|
|
276
|
+
// the logs so the last line (e.g. the op's result) isn't lost.
|
|
277
|
+
await stateManager.waitForLogsDrained();
|
|
279
278
|
const info = await promiseTimeoutWrapper(container.inspect({
|
|
280
279
|
abortSignal: liveAbortSignal(controller.signal),
|
|
281
280
|
}), 360, controller);
|
|
@@ -32,7 +32,6 @@ export declare class DockerContainerOrchestration implements ContainerOrchestrat
|
|
|
32
32
|
getRawVolume(name: string): Promise<Volume>;
|
|
33
33
|
deleteVolume(name: string, controller?: AbortController): Promise<void>;
|
|
34
34
|
healthy(): Promise<ReturnedStatus>;
|
|
35
|
-
getContainer(id: string): Promise<Container>;
|
|
36
35
|
getProtocol(): string;
|
|
37
36
|
setupContainerAbortListener(containerId: string, controller: AbortController): void;
|
|
38
37
|
runContainer(args: ContainerCreateOptions, controller?: AbortController): Promise<Container>;
|
|
@@ -193,10 +193,6 @@ export class DockerContainerOrchestration {
|
|
|
193
193
|
return { status: false, error };
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
|
-
async getContainer(id) {
|
|
197
|
-
const container = this.docker.getContainer(id);
|
|
198
|
-
return container;
|
|
199
|
-
}
|
|
200
196
|
getProtocol() {
|
|
201
197
|
return this.protocol;
|
|
202
198
|
}
|
|
@@ -43,7 +43,6 @@ export interface ContainerOrchestrationInterface {
|
|
|
43
43
|
deleteVolume(name: string, controller?: AbortController): Promise<void>;
|
|
44
44
|
getContainerByName(name: string): Promise<Container | undefined>;
|
|
45
45
|
getContainersByName(names: string[]): Promise<Container[]>;
|
|
46
|
-
getContainer(id: string): Promise<Container>;
|
|
47
46
|
runContainer(args: ContainerCreateOptions, controller?: AbortController): Promise<Container>;
|
|
48
47
|
runFlowContainer(image: string, args: RunContainerArgs): Promise<Container>;
|
|
49
48
|
stopContainer(id: string, controller?: AbortController): Promise<void>;
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nosana/node",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.43-rc",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@nosana/node",
|
|
9
|
-
"version": "1.1.
|
|
9
|
+
"version": "1.1.43-rc",
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@coral-xyz/anchor": "^0.28.1-beta.1",
|