openvibers/openvibe.host Commits
Wait for protected sessions before Bot deploys (#368)
Changes
@@@ -2040,6 +2040,19 @@"url": "http://127.0.0.1:4630/api/ready","timeoutSeconds": 30},++ "protected": {++ "kind": "http-json-count",++ "url": "http://127.0.0.1:4630/api/ready",++ "field": "details.protected_sessions",++ "label": "robot control sessions"++ },++ "drain": {++ "policy": "wait",++ "waitMaxSeconds": 600,++ "pollSeconds": 10,++ "quietChecks": 2,++ "timeoutPolicy": "continue"++ },"noRestartPaths": ["docs/","test/",@@@ -349,6 +349,7 @@ function normaliseService(id, raw, defaults) {if (!Array.isArray(out.install.command) || !out.install.command.length) throw new InventoryError(`${where}.install.command must be an argv array`);for (const b of out.build) if (!Array.isArray(b) || !b.length) throw new InventoryError(`${where}.build entries must be argv arrays`);if (!DRAIN_POLICIES.includes(out.drain.policy)) throw new InventoryError(`${where}.drain.policy must be one of ${DRAIN_POLICIES.join(', ')}`);++ if (out.drain.timeoutPolicy != null && !['refuse', 'continue'].includes(out.drain.timeoutPolicy)) throw new InventoryError(`${where}.drain.timeoutPolicy must be refuse or continue`);if (out.protected) {const checkProbe = (p, at, nested) => {const kinds = nested ? PROBE_KINDS.filter((k) => k !== 'sum') : PROBE_KINDS;@@@ -320,6 +320,12 @@ async function guardSessions(ctx, svc, opts, record, when) {}log(`${describe(last)}; still holding (${Math.round((exec.now() - start) / 1000)}s)`);}++ if (svc.drain.timeoutPolicy === 'continue') {++ record.protectedSessions.waitedSeconds = Math.round((exec.now() - start) / 1000);++ record.protectedSessions.timedOut = true;++ log(`!!! protected-session wait reached ${svc.drain.waitMaxSeconds}s; proceeding with ${describe(last)} still active`);++ return;++ }throw new OpError(`${describe(last)} after waiting ${svc.drain.waitMaxSeconds}s — gave up; nothing was restarted`, EXIT.PROTECTED, { result: 'gave-up-waiting' });};return opts.onIdleWait ? opts.onIdleWait(poll) : poll();@@@ -245,6 +245,17 @@ runTests([assert.strictEqual((await lastRecord(host2, 'live')).forced, true);}),++ test('a service configured to continue at the wait limit logs the active sessions and deploys', async () => {++ const host = await liveHost({ drain: { policy: 'wait', waitMaxSeconds: 2, pollSeconds: 1, timeoutPolicy: 'continue' } });++ const to = host.push({ 'server/index.js': 'v2();' }, 'v2');++ host.liveStreams = [{ is_live: true }];++ const r = await host.cli('deploy', 'live');++ assert.strictEqual(r.code, 0, r.out);++ assert.match(r.out, /protected-session wait reached 2s; proceeding with 1 live streams still active/);++ assert.strictEqual(host.currentSha(), to);++ assert.deepStrictEqual(host.restarts(), ['openvibe-live.service']);++ }),++test('live: a freeze refuses the deploy (exit 6) before anything is prepared; --force goes through loudly', async () => {const host = await liveHost();host.push({ 'server/index.js': 'v2();' }, 'v2');
@@ -2040,6 +2040,19 @@
@@ -2040,6 +2040,19 @@
"url": "http://127.0.0.1:4630/api/ready",
"url": "http://127.0.0.1:4630/api/ready",
"timeoutSeconds": 30
"timeoutSeconds": 30
},
},
+ "protected": {
+ "kind": "http-json-count",
+ "url": "http://127.0.0.1:4630/api/ready",
+ "field": "details.protected_sessions",
+ "label": "robot control sessions"
+ },
+ "drain": {
+ "policy": "wait",
+ "waitMaxSeconds": 600,
+ "pollSeconds": 10,
+ "quietChecks": 2,
+ "timeoutPolicy": "continue"
+ },
"noRestartPaths": [
"noRestartPaths": [
"docs/",
"docs/",
"test/",
"test/",
@@ -349,6 +349,7 @@ function normaliseService(id, raw, defaults) {
@@ -349,6 +349,7 @@ function normaliseService(id, raw, defaults) {
if (!Array.isArray(out.install.command) || !out.install.command.length) throw new InventoryError(`${where}.install.command must be an argv array`);
if (!Array.isArray(out.install.command) || !out.install.command.length) throw new InventoryError(`${where}.install.command must be an argv array`);
for (const b of out.build) if (!Array.isArray(b) || !b.length) throw new InventoryError(`${where}.build entries must be argv arrays`);
for (const b of out.build) if (!Array.isArray(b) || !b.length) throw new InventoryError(`${where}.build entries must be argv arrays`);
if (!DRAIN_POLICIES.includes(out.drain.policy)) throw new InventoryError(`${where}.drain.policy must be one of ${DRAIN_POLICIES.join(', ')}`);
if (!DRAIN_POLICIES.includes(out.drain.policy)) throw new InventoryError(`${where}.drain.policy must be one of ${DRAIN_POLICIES.join(', ')}`);
+ if (out.drain.timeoutPolicy != null && !['refuse', 'continue'].includes(out.drain.timeoutPolicy)) throw new InventoryError(`${where}.drain.timeoutPolicy must be refuse or continue`);
if (out.protected) {
if (out.protected) {
const checkProbe = (p, at, nested) => {
const checkProbe = (p, at, nested) => {
const kinds = nested ? PROBE_KINDS.filter((k) => k !== 'sum') : PROBE_KINDS;
const kinds = nested ? PROBE_KINDS.filter((k) => k !== 'sum') : PROBE_KINDS;
@@ -320,6 +320,12 @@ async function guardSessions(ctx, svc, opts, record, when) {
@@ -320,6 +320,12 @@ async function guardSessions(ctx, svc, opts, record, when) {
}
}
log(`${describe(last)}; still holding (${Math.round((exec.now() - start) / 1000)}s)`);
log(`${describe(last)}; still holding (${Math.round((exec.now() - start) / 1000)}s)`);
}
}
+ if (svc.drain.timeoutPolicy === 'continue') {
+ record.protectedSessions.waitedSeconds = Math.round((exec.now() - start) / 1000);
+ record.protectedSessions.timedOut = true;
+ log(`!!! protected-session wait reached ${svc.drain.waitMaxSeconds}s; proceeding with ${describe(last)} still active`);
+ return;
+ }
throw new OpError(`${describe(last)} after waiting ${svc.drain.waitMaxSeconds}s — gave up; nothing was restarted`, EXIT.PROTECTED, { result: 'gave-up-waiting' });
throw new OpError(`${describe(last)} after waiting ${svc.drain.waitMaxSeconds}s — gave up; nothing was restarted`, EXIT.PROTECTED, { result: 'gave-up-waiting' });
};
};
return opts.onIdleWait ? opts.onIdleWait(poll) : poll();
return opts.onIdleWait ? opts.onIdleWait(poll) : poll();
@@ -245,6 +245,17 @@ runTests([
@@ -245,6 +245,17 @@ runTests([
assert.strictEqual((await lastRecord(host2, 'live')).forced, true);
assert.strictEqual((await lastRecord(host2, 'live')).forced, true);
}),
}),
+ test('a service configured to continue at the wait limit logs the active sessions and deploys', async () => {
+ const host = await liveHost({ drain: { policy: 'wait', waitMaxSeconds: 2, pollSeconds: 1, timeoutPolicy: 'continue' } });
+ const to = host.push({ 'server/index.js': 'v2();' }, 'v2');
+ host.liveStreams = [{ is_live: true }];
+ const r = await host.cli('deploy', 'live');
+ assert.strictEqual(r.code, 0, r.out);
+ assert.match(r.out, /protected-session wait reached 2s; proceeding with 1 live streams still active/);
+ assert.strictEqual(host.currentSha(), to);
+ assert.deepStrictEqual(host.restarts(), ['openvibe-live.service']);
+ }),
+
test('live: a freeze refuses the deploy (exit 6) before anything is prepared; --force goes through loudly', async () => {
test('live: a freeze refuses the deploy (exit 6) before anything is prepared; --force goes through loudly', async () => {
const host = await liveHost();
const host = await liveHost();
host.push({ 'server/index.js': 'v2();' }, 'v2');
host.push({ 'server/index.js': 'v2();' }, 'v2');