@nosana/node 1.1.58-rc → 1.1.59-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/.env +2 -2
- package/dist/package.json +1 -1
- package/dist/src/NodeManager/provider/ContainerStateManager.d.ts +19 -1
- package/dist/src/NodeManager/provider/ContainerStateManager.js +41 -3
- package/dist/src/NodeManager/provider/ExposedPortHealthCheck.d.ts +36 -2
- package/dist/src/NodeManager/provider/ExposedPortHealthCheck.js +90 -12
- package/dist/src/NodeManager/provider/Provider.js +8 -3
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/.env
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
BACKEND_SOLANA_ADDRESS=7rFPFnxjXHC2sfDy3qrDa9pEb4j49oivMuV7e8sYDPmB
|
|
2
|
-
FRPC_CONTAINER_IMAGE=registry.hub.docker.com/nosana/frpc:v1.0.
|
|
3
|
-
FRP_SERVER_IMAGE=registry.hub.docker.com/nosana/frpc:v1.0.
|
|
2
|
+
FRPC_CONTAINER_IMAGE=registry.hub.docker.com/nosana/frpc:v1.0.44
|
|
3
|
+
FRP_SERVER_IMAGE=registry.hub.docker.com/nosana/frpc:v1.0.44
|
|
4
4
|
FRP_SERVER_PORT=7000
|
|
5
5
|
MIN_DISK_SPACE=25
|
|
6
6
|
SIGN_MESSAGE="Hello Nosana Node!"
|
package/dist/package.json
CHANGED
|
@@ -8,6 +8,12 @@ export declare class ContainerStateManager {
|
|
|
8
8
|
private controller;
|
|
9
9
|
private emitter;
|
|
10
10
|
private restartPolicy;
|
|
11
|
+
/**
|
|
12
|
+
* Whether a failed liveness probe may restart this container. Like a
|
|
13
|
+
* restart policy it means a stop is not necessarily the op finishing, so
|
|
14
|
+
* the container has to be polled rather than waited on.
|
|
15
|
+
*/
|
|
16
|
+
private restartsOnUnhealthy;
|
|
11
17
|
private state;
|
|
12
18
|
private exitedCheckCount;
|
|
13
19
|
private lastLogTimestamp;
|
|
@@ -16,10 +22,22 @@ export declare class ContainerStateManager {
|
|
|
16
22
|
private currentLogStream;
|
|
17
23
|
private currentStatsStream;
|
|
18
24
|
private pollingInterval;
|
|
19
|
-
|
|
25
|
+
private restarting;
|
|
26
|
+
constructor(container: Dockerode.Container, controller: AbortController, emitter: EventEmitter, restartPolicy: RestartPolicy | undefined,
|
|
27
|
+
/**
|
|
28
|
+
* Whether a failed liveness probe may restart this container. Like a
|
|
29
|
+
* restart policy it means a stop is not necessarily the op finishing, so
|
|
30
|
+
* the container has to be polled rather than waited on.
|
|
31
|
+
*/
|
|
32
|
+
restartsOnUnhealthy?: boolean);
|
|
20
33
|
getState(): ContainerState;
|
|
21
34
|
startMonitoring(): Promise<void>;
|
|
22
35
|
private attachLogStream;
|
|
36
|
+
/**
|
|
37
|
+
* Restart the container in place after a failed liveness probe, keeping the
|
|
38
|
+
* op alive across it. Returns whether the container came back.
|
|
39
|
+
*/
|
|
40
|
+
restartContainer(): Promise<boolean>;
|
|
23
41
|
waitForExit(): Promise<void>;
|
|
24
42
|
/**
|
|
25
43
|
* Wait for the followed log stream to finish delivering its final buffered
|
|
@@ -12,11 +12,18 @@ function destroyStream(stream) {
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
export class ContainerStateManager {
|
|
15
|
-
constructor(container, controller, emitter, restartPolicy
|
|
15
|
+
constructor(container, controller, emitter, restartPolicy,
|
|
16
|
+
/**
|
|
17
|
+
* Whether a failed liveness probe may restart this container. Like a
|
|
18
|
+
* restart policy it means a stop is not necessarily the op finishing, so
|
|
19
|
+
* the container has to be polled rather than waited on.
|
|
20
|
+
*/
|
|
21
|
+
restartsOnUnhealthy = false) {
|
|
16
22
|
this.container = container;
|
|
17
23
|
this.controller = controller;
|
|
18
24
|
this.emitter = emitter;
|
|
19
25
|
this.restartPolicy = restartPolicy;
|
|
26
|
+
this.restartsOnUnhealthy = restartsOnUnhealthy;
|
|
20
27
|
this.state = 'starting';
|
|
21
28
|
this.exitedCheckCount = 0;
|
|
22
29
|
this.lastLogTimestamp = 0; // Unix timestamp in seconds from actual container logs
|
|
@@ -25,6 +32,7 @@ export class ContainerStateManager {
|
|
|
25
32
|
this.currentLogStream = null;
|
|
26
33
|
this.currentStatsStream = null;
|
|
27
34
|
this.pollingInterval = null;
|
|
35
|
+
this.restarting = false;
|
|
28
36
|
this.STATS_INTERVAL_MS = 5000;
|
|
29
37
|
}
|
|
30
38
|
getState() {
|
|
@@ -32,7 +40,7 @@ export class ContainerStateManager {
|
|
|
32
40
|
}
|
|
33
41
|
async startMonitoring() {
|
|
34
42
|
await Promise.all([this.attachLogStream(), this.attachStatsStream()]);
|
|
35
|
-
if (!this.restartPolicy) {
|
|
43
|
+
if (!this.restartPolicy && !this.restartsOnUnhealthy) {
|
|
36
44
|
this.container
|
|
37
45
|
.wait({ abortSignal: liveAbortSignal(this.controller.signal) })
|
|
38
46
|
.finally(() => {
|
|
@@ -51,6 +59,12 @@ export class ContainerStateManager {
|
|
|
51
59
|
this.stopMonitoring();
|
|
52
60
|
return;
|
|
53
61
|
}
|
|
62
|
+
// A restart takes the container through a stop, which is not the op
|
|
63
|
+
// ending; hold off on exit detection until it is back.
|
|
64
|
+
if (this.restarting) {
|
|
65
|
+
this.exitedCheckCount = 0;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
54
68
|
try {
|
|
55
69
|
const inspectInfo = await this.container.inspect();
|
|
56
70
|
let inspectedState = inspectInfo.State.Status.toLowerCase();
|
|
@@ -107,7 +121,10 @@ export class ContainerStateManager {
|
|
|
107
121
|
});
|
|
108
122
|
this.currentLogStream.on('close', () => {
|
|
109
123
|
this.currentLogStream = null;
|
|
110
|
-
|
|
124
|
+
// A restart closes the stream while the container is briefly stopped;
|
|
125
|
+
// re-attach so the logs it writes on the way back up aren't lost.
|
|
126
|
+
if ((this.state === 'running' || this.restarting) &&
|
|
127
|
+
!this.controller.signal.aborted) {
|
|
111
128
|
setTimeout(() => this.attachLogStream(), 100);
|
|
112
129
|
}
|
|
113
130
|
});
|
|
@@ -126,6 +143,27 @@ export class ContainerStateManager {
|
|
|
126
143
|
}
|
|
127
144
|
}
|
|
128
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Restart the container in place after a failed liveness probe, keeping the
|
|
148
|
+
* op alive across it. Returns whether the container came back.
|
|
149
|
+
*/
|
|
150
|
+
async restartContainer() {
|
|
151
|
+
if (this.controller.signal.aborted || this.state === 'exited')
|
|
152
|
+
return false;
|
|
153
|
+
this.restarting = true;
|
|
154
|
+
try {
|
|
155
|
+
await this.container.restart();
|
|
156
|
+
this.state = 'running';
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
this.exitedCheckCount = 0;
|
|
164
|
+
this.restarting = false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
129
167
|
async waitForExit() {
|
|
130
168
|
return new Promise((resolve) => {
|
|
131
169
|
const checkInterval = setInterval(() => {
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import { ExposedPort } from '@nosana/sdk';
|
|
2
|
+
import { ExposedPort, HealthCheck } from '@nosana/sdk';
|
|
3
3
|
import Dockerode from 'dockerode';
|
|
4
4
|
import EventEmitter from 'events';
|
|
5
|
+
/**
|
|
6
|
+
* `restart_on_failure` and `max_restarts` ship in the @nosana/sdk release that
|
|
7
|
+
* accompanies this change; widening the SDK type here keeps the node building
|
|
8
|
+
* against an SDK that predates them.
|
|
9
|
+
*/
|
|
10
|
+
export type RestartableHealthCheck = HealthCheck & {
|
|
11
|
+
restart_on_failure?: boolean;
|
|
12
|
+
max_restarts?: number;
|
|
13
|
+
};
|
|
5
14
|
export declare class ExposedPortHealthCheck {
|
|
6
15
|
private exposedPortMap;
|
|
7
16
|
private frpcContainer;
|
|
@@ -14,15 +23,40 @@ export declare class ExposedPortHealthCheck {
|
|
|
14
23
|
private healthStatus;
|
|
15
24
|
private jobEmitter;
|
|
16
25
|
private intervals;
|
|
26
|
+
/**
|
|
27
|
+
* Restarts the container in place. Set only when an exposed port declares a
|
|
28
|
+
* `restart_on_failure` check; without it a failure is reported and no more.
|
|
29
|
+
*/
|
|
30
|
+
private restartContainer?;
|
|
31
|
+
/** Health-triggered restarts so far, counted across every exposed port. */
|
|
32
|
+
private restartCount;
|
|
33
|
+
private restartInFlight;
|
|
34
|
+
private restartLimitLogged;
|
|
35
|
+
private stopped;
|
|
17
36
|
private readonly connectTimeoutSeconds;
|
|
18
37
|
private readonly startupMaxTimeSeconds;
|
|
19
38
|
private readonly continuousMaxTimeSeconds;
|
|
20
39
|
private flowId;
|
|
21
|
-
constructor(flowId: string, frpcContainer: Dockerode.Container, jobEmitter: EventEmitter, containerName: string, startupIntervalMs?: number, continuousIntervalMs?: number);
|
|
40
|
+
constructor(flowId: string, frpcContainer: Dockerode.Container, jobEmitter: EventEmitter, containerName: string, restartContainer?: () => Promise<boolean>, startupIntervalMs?: number, continuousIntervalMs?: number);
|
|
22
41
|
addExposedPortsMap(exposedPortMap: Map<string, ExposedPort>): void;
|
|
42
|
+
/**
|
|
43
|
+
* A continuous pass only covers checks up to the first non-continuous one, so
|
|
44
|
+
* a liveness probe that isn't continuous, or that sits behind a startup-only
|
|
45
|
+
* check, never runs. Say so rather than let `restart_on_failure` look active
|
|
46
|
+
* while doing nothing.
|
|
47
|
+
*/
|
|
48
|
+
private reportUnreachableRestartChecks;
|
|
23
49
|
startServiceExposedUrlHealthCheck(): void;
|
|
24
50
|
private startStartupHealthCheck;
|
|
25
51
|
private startContinuousHealthCheck;
|
|
52
|
+
/**
|
|
53
|
+
* Restart the container behind a failed liveness probe.
|
|
54
|
+
*
|
|
55
|
+
* The restart is container-wide even though the probe belongs to one port, so
|
|
56
|
+
* every port drops back to startup probing and reports itself online again as
|
|
57
|
+
* it starts answering — the same path a first start takes.
|
|
58
|
+
*/
|
|
59
|
+
private restartUnhealthyContainer;
|
|
26
60
|
stopHealthCheckForId(id: string): void;
|
|
27
61
|
stopAllHealthChecks(): void;
|
|
28
62
|
private checkPortHealth;
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
export class ExposedPortHealthCheck {
|
|
2
|
-
constructor(flowId, frpcContainer, jobEmitter, containerName, startupIntervalMs = 5000, continuousIntervalMs = 30000) {
|
|
2
|
+
constructor(flowId, frpcContainer, jobEmitter, containerName, restartContainer, startupIntervalMs = 5000, continuousIntervalMs = 30000) {
|
|
3
3
|
this.exposedPortMap = new Map();
|
|
4
4
|
/**
|
|
5
5
|
* Tracks job previous state to show change in state of job mainly when going from failed to success.
|
|
6
6
|
*/
|
|
7
7
|
this.healthStatus = new Map();
|
|
8
8
|
this.intervals = new Map();
|
|
9
|
+
/** Health-triggered restarts so far, counted across every exposed port. */
|
|
10
|
+
this.restartCount = 0;
|
|
11
|
+
this.restartInFlight = false;
|
|
12
|
+
this.restartLimitLogged = false;
|
|
13
|
+
this.stopped = false;
|
|
9
14
|
// Every curl is bounded: an unbounded one holds the startup check open and
|
|
10
15
|
// nothing waiting on the service ever starts. The startup budget is generous
|
|
11
16
|
// because a health check can be a real request against a model that is still
|
|
@@ -18,11 +23,34 @@ export class ExposedPortHealthCheck {
|
|
|
18
23
|
this.frpcContainer = frpcContainer;
|
|
19
24
|
this.jobEmitter = jobEmitter;
|
|
20
25
|
this.containerName = containerName;
|
|
26
|
+
this.restartContainer = restartContainer;
|
|
21
27
|
this.startupIntervalMs = startupIntervalMs;
|
|
22
28
|
this.continuousIntervalMs = continuousIntervalMs;
|
|
23
29
|
}
|
|
24
30
|
addExposedPortsMap(exposedPortMap) {
|
|
25
31
|
this.exposedPortMap = exposedPortMap;
|
|
32
|
+
this.reportUnreachableRestartChecks();
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A continuous pass only covers checks up to the first non-continuous one, so
|
|
36
|
+
* a liveness probe that isn't continuous, or that sits behind a startup-only
|
|
37
|
+
* check, never runs. Say so rather than let `restart_on_failure` look active
|
|
38
|
+
* while doing nothing.
|
|
39
|
+
*/
|
|
40
|
+
reportUnreachableRestartChecks() {
|
|
41
|
+
for (const [id, exposedPort] of this.exposedPortMap) {
|
|
42
|
+
const checks = (exposedPort.health_checks ??
|
|
43
|
+
[]);
|
|
44
|
+
const startupOnly = checks.findIndex((check) => !check.continuous);
|
|
45
|
+
checks.forEach((check, index) => {
|
|
46
|
+
if (!check.restart_on_failure)
|
|
47
|
+
return;
|
|
48
|
+
if (check.continuous && (startupOnly === -1 || index < startupOnly)) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
this.jobEmitter.emit('log', `restart_on_failure on ${id} is never reached: a liveness check has to be continuous and declared before any check that is not`, 'info');
|
|
52
|
+
});
|
|
53
|
+
}
|
|
26
54
|
}
|
|
27
55
|
startServiceExposedUrlHealthCheck() {
|
|
28
56
|
for (const [id, exposedPort] of this.exposedPortMap) {
|
|
@@ -37,10 +65,10 @@ export class ExposedPortHealthCheck {
|
|
|
37
65
|
if (checkInFlight)
|
|
38
66
|
return;
|
|
39
67
|
checkInFlight = true;
|
|
40
|
-
const
|
|
68
|
+
const result = await this.checkPortHealth(exposedPort, true).finally(() => {
|
|
41
69
|
checkInFlight = false;
|
|
42
70
|
});
|
|
43
|
-
if (
|
|
71
|
+
if (result == null) {
|
|
44
72
|
clearInterval(startupCheckInterval);
|
|
45
73
|
this.jobEmitter.emit('healthcheck:url:exposed', {
|
|
46
74
|
id,
|
|
@@ -50,8 +78,12 @@ export class ExposedPortHealthCheck {
|
|
|
50
78
|
});
|
|
51
79
|
return;
|
|
52
80
|
}
|
|
53
|
-
if (
|
|
81
|
+
if (result.healthy) {
|
|
54
82
|
clearInterval(startupCheckInterval);
|
|
83
|
+
// Seed the tracked state, so the *first* continuous failure reads as a
|
|
84
|
+
// change. Left unset it defaults to unhealthy, and a service that goes
|
|
85
|
+
// down and stays down never reports a transition at all.
|
|
86
|
+
this.healthStatus.set(id, true);
|
|
55
87
|
this.jobEmitter.emit('healthcheck:startup:success', {
|
|
56
88
|
id,
|
|
57
89
|
flowId: this.flowId,
|
|
@@ -67,15 +99,15 @@ export class ExposedPortHealthCheck {
|
|
|
67
99
|
}
|
|
68
100
|
startContinuousHealthCheck(id, exposedPort) {
|
|
69
101
|
let continuousCheckInterval = setInterval(async () => {
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
102
|
+
const result = await this.checkPortHealth(exposedPort, false);
|
|
103
|
+
if (result === null) {
|
|
72
104
|
clearInterval(continuousCheckInterval);
|
|
73
105
|
return; // Skip logging or emitting events if no check was done.
|
|
74
106
|
}
|
|
75
107
|
const previousState = this.healthStatus.get(id) || false;
|
|
76
|
-
if (
|
|
77
|
-
this.healthStatus.set(id,
|
|
78
|
-
if (!
|
|
108
|
+
if (result.healthy !== previousState) {
|
|
109
|
+
this.healthStatus.set(id, result.healthy);
|
|
110
|
+
if (!result.healthy) {
|
|
79
111
|
this.jobEmitter.emit('healthcheck:continuous:failure', {
|
|
80
112
|
id,
|
|
81
113
|
flowId: this.flowId,
|
|
@@ -84,9 +116,54 @@ export class ExposedPortHealthCheck {
|
|
|
84
116
|
});
|
|
85
117
|
}
|
|
86
118
|
}
|
|
119
|
+
// A continuous check only ever runs once the service has passed startup,
|
|
120
|
+
// so reaching here unhealthy is the liveness case: it worked, then stopped.
|
|
121
|
+
if (!result.healthy && result.failedCheck?.restart_on_failure) {
|
|
122
|
+
await this.restartUnhealthyContainer(id, result.failedCheck);
|
|
123
|
+
}
|
|
87
124
|
}, this.continuousIntervalMs);
|
|
88
125
|
this.intervals.set(id, continuousCheckInterval);
|
|
89
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Restart the container behind a failed liveness probe.
|
|
129
|
+
*
|
|
130
|
+
* The restart is container-wide even though the probe belongs to one port, so
|
|
131
|
+
* every port drops back to startup probing and reports itself online again as
|
|
132
|
+
* it starts answering — the same path a first start takes.
|
|
133
|
+
*/
|
|
134
|
+
async restartUnhealthyContainer(id, failedCheck) {
|
|
135
|
+
if (!this.restartContainer || this.restartInFlight || this.stopped)
|
|
136
|
+
return;
|
|
137
|
+
const maxRestarts = failedCheck.max_restarts;
|
|
138
|
+
if (maxRestarts !== undefined && this.restartCount >= maxRestarts) {
|
|
139
|
+
// The probe keeps running on its interval; say this once rather than
|
|
140
|
+
// every time it comes back unhealthy.
|
|
141
|
+
if (!this.restartLimitLogged) {
|
|
142
|
+
this.restartLimitLogged = true;
|
|
143
|
+
this.jobEmitter.emit('log', `Health check for ${id} failed but its restart limit (${maxRestarts}) is reached, leaving the container alone`, 'info');
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
this.restartInFlight = true;
|
|
148
|
+
this.restartCount++;
|
|
149
|
+
// Every port on this container is about to fail for the same reason, so
|
|
150
|
+
// drop all the probes rather than let them race the restart.
|
|
151
|
+
this.intervals.forEach((interval) => clearInterval(interval));
|
|
152
|
+
this.intervals.clear();
|
|
153
|
+
this.healthStatus.clear();
|
|
154
|
+
this.jobEmitter.emit('log', `Health check for ${id} failed, restarting container (restart ${this.restartCount}${maxRestarts !== undefined ? `/${maxRestarts}` : ''})`, 'info');
|
|
155
|
+
const restarted = await this.restartContainer().catch(() => false);
|
|
156
|
+
this.restartInFlight = false;
|
|
157
|
+
if (!restarted) {
|
|
158
|
+
this.jobEmitter.emit('log', 'Restarting the container after a failed health check did not succeed', 'error');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
// The op can be torn down while the restart is in flight; re-arming then
|
|
162
|
+
// would leave probes running against a container that is going away.
|
|
163
|
+
if (this.stopped)
|
|
164
|
+
return;
|
|
165
|
+
this.startServiceExposedUrlHealthCheck();
|
|
166
|
+
}
|
|
90
167
|
stopHealthCheckForId(id) {
|
|
91
168
|
if (this.intervals.has(id)) {
|
|
92
169
|
clearInterval(this.intervals.get(id));
|
|
@@ -96,6 +173,7 @@ export class ExposedPortHealthCheck {
|
|
|
96
173
|
}
|
|
97
174
|
}
|
|
98
175
|
stopAllHealthChecks() {
|
|
176
|
+
this.stopped = true;
|
|
99
177
|
this.intervals.forEach((interval, id) => {
|
|
100
178
|
clearInterval(interval);
|
|
101
179
|
});
|
|
@@ -117,16 +195,16 @@ export class ExposedPortHealthCheck {
|
|
|
117
195
|
if (healthCheck.type === 'http' && typeof exposedPort.port === 'number') {
|
|
118
196
|
const success = await this.runHttpHealthCheck(exposedPort.port, healthCheck, maxTimeSeconds);
|
|
119
197
|
if (!success)
|
|
120
|
-
return false;
|
|
198
|
+
return { healthy: false, failedCheck: healthCheck };
|
|
121
199
|
}
|
|
122
200
|
else if (healthCheck.type === 'websocket' &&
|
|
123
201
|
typeof exposedPort.port === 'number') {
|
|
124
202
|
const success = await this.runWebSocketHealthCheck(exposedPort.port, healthCheck, maxTimeSeconds);
|
|
125
203
|
if (!success)
|
|
126
|
-
return false;
|
|
204
|
+
return { healthy: false, failedCheck: healthCheck };
|
|
127
205
|
}
|
|
128
206
|
}
|
|
129
|
-
return true;
|
|
207
|
+
return { healthy: true };
|
|
130
208
|
}
|
|
131
209
|
async runHttpHealthCheck(port, healthCheck, maxTimeSeconds) {
|
|
132
210
|
const url = `http://${this.containerName}:${port}${healthCheck.path}`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, getExposePorts, isOpExposed, } from '@nosana/sdk';
|
|
2
2
|
import { configs } from '../configs/configs.js';
|
|
3
3
|
import { NodeConfigsSingleton } from '../configs/NodeConfigs.js';
|
|
4
|
-
import { ExposedPortHealthCheck } from './ExposedPortHealthCheck.js';
|
|
4
|
+
import { ExposedPortHealthCheck, } from './ExposedPortHealthCheck.js';
|
|
5
5
|
import { s3HelperImage } from '../node/resource/definition/index.js';
|
|
6
6
|
import { applyLoggingProxyToClass } from '../monitoring/proxy/loggingProxy.js';
|
|
7
7
|
import { promiseTimeoutWrapper } from '../utils/timeoutPromiseWrapper.js';
|
|
@@ -168,6 +168,9 @@ export class Provider {
|
|
|
168
168
|
const networks = {};
|
|
169
169
|
let idMaps = new Map();
|
|
170
170
|
const ports = getExposePorts(op);
|
|
171
|
+
// A liveness probe restarts the container under the running op, so the
|
|
172
|
+
// state manager has to know a stop may not be the op finishing.
|
|
173
|
+
const restartsOnUnhealthy = ports.some((port) => port.health_checks?.some((check) => check.restart_on_failure));
|
|
171
174
|
networks[name] = {};
|
|
172
175
|
await this.containerOrchestration.createNetwork(name, controller);
|
|
173
176
|
if (isOpExposed(op)) {
|
|
@@ -266,10 +269,12 @@ export class Provider {
|
|
|
266
269
|
if (teeRuntime !== 'SEV-SNP' && startedInfo.NetworkSettings?.Networks["NOSANA_GATEWAY"].IPAddress) {
|
|
267
270
|
emitter.emit('setContainerInternalIp', startedInfo.NetworkSettings.Networks["NOSANA_GATEWAY"].IPAddress);
|
|
268
271
|
}
|
|
269
|
-
stateManager = new ContainerStateManager(container, controller, emitter, op.args.restart_policy);
|
|
272
|
+
stateManager = new ContainerStateManager(container, controller, emitter, op.args.restart_policy, restartsOnUnhealthy);
|
|
270
273
|
await stateManager.startMonitoring();
|
|
271
274
|
if (isOpExposed(op)) {
|
|
272
|
-
exposedPortHealthCheck = new ExposedPortHealthCheck(flow.id, frpcContainer, emitter, name
|
|
275
|
+
exposedPortHealthCheck = new ExposedPortHealthCheck(flow.id, frpcContainer, emitter, name, restartsOnUnhealthy
|
|
276
|
+
? () => stateManager.restartContainer()
|
|
277
|
+
: undefined);
|
|
273
278
|
exposedPortHealthCheck.addExposedPortsMap(idMaps);
|
|
274
279
|
exposedPortHealthCheck.startServiceExposedUrlHealthCheck();
|
|
275
280
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nosana/node",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.59-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.59-rc",
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@coral-xyz/anchor": "^0.28.1-beta.1",
|