openvibers/openvibe.host Commits

Wait for protected sessions before Bot deploys (#368)

OP OpenVibers committed 3 days ago · 4f053189d0 · 4 files, +31 −0

Changes

ddiff --git a/host.example.json b/host.example.json
iindex 33e7d85..f1081ed 100644
---- a/host.example.json
++++ b/host.example.json
@@@ -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/",
ddiff --git a/lib/inventory.js b/lib/inventory.js
iindex 6fa109d..00b0111 100644
---- a/lib/inventory.js
++++ b/lib/inventory.js
@@@ -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;
ddiff --git a/lib/release-ops.js b/lib/release-ops.js
iindex 901ee1e..05be618 100644
---- a/lib/release-ops.js
++++ b/lib/release-ops.js
@@@ -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();
ddiff --git a/test/strategy-release-layout.test.js b/test/strategy-release-layout.test.js
iindex b1aed88..2e57033 100644
---- a/test/strategy-release-layout.test.js
++++ b/test/strategy-release-layout.test.js
@@@ -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');