openvibers/openvibe.media Commits
Media's readiness has no fake green (roadmap WS-Q task 7, Shared v1.24.0). events_outbox answered ok with { enabled: false } while the relay was off, and an unconfigured remote tier (b2, r2) had no check at all, so nothing showed it was not in use. The relay off is now skipped with the reason (detail { enabled: false } kept), and an unconfigured tier is listed as remote_<name> skipped "not configured"; neither is ever ok, and an optional skip leaves a service whose checks passed ready. Drill mode still lists no remote tier. openvibe-shared is pinned at v1.24.0 (from v1.22.0), named in the README. test/observability.test.js checks both skips (the configured-tier and failure cases are unchanged). npm test passes.
Changes
@@@ -107,7 +107,7 @@ server/vod.duration.reconcile (stored vs measured durations, local and B2/R2),storage.orphans.scan (monthly storage orphan report, service-wide, report only)client-ip.js trust proxy = loopback; req.ip is the only client address--(openvibe-shared v1.22.0, openvibe-contracts v0.71.0, openvibe-sdk v0.12.0: pinned release tarballs, installed by npm)++(openvibe-shared v1.24.0, openvibe-contracts v0.71.0, openvibe-sdk v0.12.0: pinned release tarballs, installed by npm)scripts/smoke-test.sh end-to-end smoke test (boots a temp instance)scripts/backfill-objects.js / reconcile-objects.js / object-invariant.js / no-good-copy-report.js object-model operator toolsscripts/media-jobs.js list jobs, run the size-invariant scan (dry run by default), approve/cancel proposals@@@ -18,7 +18,7 @@"multer": "^1.4.5-lts.1","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.71.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.12.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.22.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.24.0","sharp": "^0.33.4"}},@@@ -1944,9 +1944,9 @@}},"node_modules/openvibe-shared": {-- "version": "1.22.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.22.0",-- "integrity": "sha512-FtqbR8h6YphOnJNqklJED3HyqvTwZJl58gyiZ3QgzmZtZcA16O2NMGq67O4p7grLojwaIG5DhXDMgmZcpScxJQ==",++ "version": "1.24.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.24.0",++ "integrity": "sha512-VPzafrc07Pw0oqmXioOlDRtlX5xu82jNLBYkVYxXvhjkILMpWsyjExItIRt5Y6l2st0ozHm7bDQFTJpKEShMEA==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0"@@@ -21,7 +21,7 @@"dotenv": "^16.4.5","express": "^4.19.2","multer": "^1.4.5-lts.1",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.22.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.24.0","sharp": "^0.33.4"}}@@@ -20,7 +20,7 @@ const fs = require('fs');const path = require('path');const crypto = require('crypto');const metrics = require('openvibe-shared/metrics');--const { createReadiness } = require('openvibe-shared/ready');++const { createReadiness, skip } = require('openvibe-shared/ready');const copyReport = require('./objects/copy-report');@@@ -119,14 +119,16 @@ function createMediaReadiness({ release, db, config, auth, recorder, events, rem}checks.push({ name: 'network_jwks', required: false, description: 'user sign-in and service tokens (app keys work without it)', check: () => auth.jwksLoaded() || 'Network public key not loaded yet' });for (const name of ['b2', 'r2']) {-- if (drill || !remote.configured(name)) continue; // not configured: no check, rather than a pretend pass++ if (drill) continue;++ // Not configured: listed as skipped (never a pretend pass), so the status shows the tier is not in use.++ if (!remote.configured(name)) { checks.push({ name: `remote_${name}`, required: false, check: () => skip('not configured') }); continue; }checks.push({ name: `remote_${name}`, required: false, cacheMs: 60 * 1000, timeoutMs: 5000, check: () => remote.probe(name) });}checks.push({name: 'events_outbox', required: false, description: 'durable events to OpenVibe.Events (webhooks are separate)',check: () => {const s = events.status();-- if (!s.enabled) return { ok: true, detail: { enabled: false } };++ if (!s.enabled) return skip('relay off (EVENTS_URL or the service credentials unset)', { enabled: false }); // verified nothing: never ok (WS-Q task 7)if (s.pending > OUTBOX_BACKLOG_LIMIT) return { ok: false, error: `${s.pending} events waiting (limit ${OUTBOX_BACKLOG_LIMIT})`, detail: { pending: s.pending } };return { ok: true, detail: { pending: s.pending, rejected: s.rejected } };},@@@ -62,8 +62,13 @@ const listen = (app) => new Promise((resolve) => { const s = app.listen(0, '127.assert.strictEqual(body.service, 'media');assert.strictEqual(body.recordings_in_progress, 1);const names = Object.keys(body.checks).sort();-- assert.deepStrictEqual(names, ['db', 'events_outbox', 'network_jwks', 'object_copies', 'remote_b2', 'storage_clips', 'storage_files', 'storage_objects', 'storage_pastes', 'storage_thumbnails', 'storage_vods'], 'r2 is not configured, so it has no check');++ assert.deepStrictEqual(names, ['db', 'events_outbox', 'network_jwks', 'object_copies', 'remote_b2', 'remote_r2', 'storage_clips', 'storage_files', 'storage_objects', 'storage_pastes', 'storage_thumbnails', 'storage_vods']);++ // r2 is not configured: listed as skipped with the reason, never a pretend pass (WS-Q task 7), and an optional++ // skip leaves the verified service ready.++ assert.deepStrictEqual([body.checks.remote_r2.status, body.checks.remote_r2.reason], ['skipped', 'not configured']);++ assert.deepStrictEqual(body.skipped, ['remote_r2']);for (const [n, c] of Object.entries(body.checks)) {++ if (n === 'remote_r2') continue;assert.strictEqual(c.status, 'ok', n);assert.strictEqual(typeof c.latency_ms, 'number');assert.ok(Date.parse(c.checked_at));@@@ -89,6 +94,8 @@ const listen = (app) => new Promise((resolve) => { const s = app.listen(0, '127.assert.deepStrictEqual(body.degraded, ['remote_b2', 'remote_r2']);assert.strictEqual(body.checks.remote_r2.error, 'r2 HeadBucket failed: AccessDenied');assert.deepStrictEqual(body.checks.events_outbox.detail, { enabled: false });++ assert.strictEqual(body.checks.events_outbox.status, 'skipped', 'a relay that is off verified nothing: skipped, never ok');++ assert.match(body.checks.events_outbox.reason, /relay off/);// A storage directory that cannot be written: not ready, 503, named.jwks = true; outbox = { enabled: false };@@@ -140,7 +147,7 @@ const listen = (app) => new Promise((resolve) => { const s = app.listen(0, '127.assert.strictEqual(up.status, 200, up.body);assert.strictEqual(body.release, 'abcdef123456');assert.ok(body.checks.db && body.checks.storage_objects);-- assert.ok(!body.checks.remote_b2, 'unconfigured tiers are not checked');++ assert.strictEqual(body.checks.remote_b2.status, 'skipped', 'an unconfigured tier is not probed: skipped, never ok');const m = await request(b2, '/metrics');assert.strictEqual(m.status, 200);assert.ok(m.body.includes('http_requests_total{method="GET",route="/api/ready",status_class="2xx"} 1\n'), m.body.slice(0, 400));
@@ -107,7 +107,7 @@ server/
@@ -107,7 +107,7 @@ server/
vod.duration.reconcile (stored vs measured durations, local and B2/R2),
vod.duration.reconcile (stored vs measured durations, local and B2/R2),
storage.orphans.scan (monthly storage orphan report, service-wide, report only)
storage.orphans.scan (monthly storage orphan report, service-wide, report only)
client-ip.js trust proxy = loopback; req.ip is the only client address
client-ip.js trust proxy = loopback; req.ip is the only client address
-(openvibe-shared v1.22.0, openvibe-contracts v0.71.0, openvibe-sdk v0.12.0: pinned release tarballs, installed by npm)
+(openvibe-shared v1.24.0, openvibe-contracts v0.71.0, openvibe-sdk v0.12.0: pinned release tarballs, installed by npm)
scripts/smoke-test.sh end-to-end smoke test (boots a temp instance)
scripts/smoke-test.sh end-to-end smoke test (boots a temp instance)
scripts/backfill-objects.js / reconcile-objects.js / object-invariant.js / no-good-copy-report.js object-model operator tools
scripts/backfill-objects.js / reconcile-objects.js / object-invariant.js / no-good-copy-report.js object-model operator tools
scripts/media-jobs.js list jobs, run the size-invariant scan (dry run by default), approve/cancel proposals
scripts/media-jobs.js list jobs, run the size-invariant scan (dry run by default), approve/cancel proposals
@@ -18,7 +18,7 @@
@@ -18,7 +18,7 @@
"multer": "^1.4.5-lts.1",
"multer": "^1.4.5-lts.1",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.71.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.71.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.12.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.12.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.22.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.24.0",
"sharp": "^0.33.4"
"sharp": "^0.33.4"
}
}
},
},
@@ -1944,9 +1944,9 @@
@@ -1944,9 +1944,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "1.22.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.22.0",
- "integrity": "sha512-FtqbR8h6YphOnJNqklJED3HyqvTwZJl58gyiZ3QgzmZtZcA16O2NMGq67O4p7grLojwaIG5DhXDMgmZcpScxJQ==",
+ "version": "1.24.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.24.0",
+ "integrity": "sha512-VPzafrc07Pw0oqmXioOlDRtlX5xu82jNLBYkVYxXvhjkILMpWsyjExItIRt5Y6l2st0ozHm7bDQFTJpKEShMEA==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -21,7 +21,7 @@
@@ -21,7 +21,7 @@
"dotenv": "^16.4.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express": "^4.19.2",
"multer": "^1.4.5-lts.1",
"multer": "^1.4.5-lts.1",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.22.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.24.0",
"sharp": "^0.33.4"
"sharp": "^0.33.4"
}
}
}
}
@@ -20,7 +20,7 @@ const fs = require('fs');
@@ -20,7 +20,7 @@ const fs = require('fs');
const path = require('path');
const path = require('path');
const crypto = require('crypto');
const crypto = require('crypto');
const metrics = require('openvibe-shared/metrics');
const metrics = require('openvibe-shared/metrics');
-const { createReadiness } = require('openvibe-shared/ready');
+const { createReadiness, skip } = require('openvibe-shared/ready');
const copyReport = require('./objects/copy-report');
const copyReport = require('./objects/copy-report');
@@ -119,14 +119,16 @@ function createMediaReadiness({ release, db, config, auth, recorder, events, rem
@@ -119,14 +119,16 @@ function createMediaReadiness({ release, db, config, auth, recorder, events, rem
}
}
checks.push({ name: 'network_jwks', required: false, description: 'user sign-in and service tokens (app keys work without it)', check: () => auth.jwksLoaded() || 'Network public key not loaded yet' });
checks.push({ name: 'network_jwks', required: false, description: 'user sign-in and service tokens (app keys work without it)', check: () => auth.jwksLoaded() || 'Network public key not loaded yet' });
for (const name of ['b2', 'r2']) {
for (const name of ['b2', 'r2']) {
- if (drill || !remote.configured(name)) continue; // not configured: no check, rather than a pretend pass
+ if (drill) continue;
+ // Not configured: listed as skipped (never a pretend pass), so the status shows the tier is not in use.
+ if (!remote.configured(name)) { checks.push({ name: `remote_${name}`, required: false, check: () => skip('not configured') }); continue; }
checks.push({ name: `remote_${name}`, required: false, cacheMs: 60 * 1000, timeoutMs: 5000, check: () => remote.probe(name) });
checks.push({ name: `remote_${name}`, required: false, cacheMs: 60 * 1000, timeoutMs: 5000, check: () => remote.probe(name) });
}
}
checks.push({
checks.push({
name: 'events_outbox', required: false, description: 'durable events to OpenVibe.Events (webhooks are separate)',
name: 'events_outbox', required: false, description: 'durable events to OpenVibe.Events (webhooks are separate)',
check: () => {
check: () => {
const s = events.status();
const s = events.status();
- if (!s.enabled) return { ok: true, detail: { enabled: false } };
+ if (!s.enabled) return skip('relay off (EVENTS_URL or the service credentials unset)', { enabled: false }); // verified nothing: never ok (WS-Q task 7)
if (s.pending > OUTBOX_BACKLOG_LIMIT) return { ok: false, error: `${s.pending} events waiting (limit ${OUTBOX_BACKLOG_LIMIT})`, detail: { pending: s.pending } };
if (s.pending > OUTBOX_BACKLOG_LIMIT) return { ok: false, error: `${s.pending} events waiting (limit ${OUTBOX_BACKLOG_LIMIT})`, detail: { pending: s.pending } };
return { ok: true, detail: { pending: s.pending, rejected: s.rejected } };
return { ok: true, detail: { pending: s.pending, rejected: s.rejected } };
},
},
@@ -62,8 +62,13 @@ const listen = (app) => new Promise((resolve) => { const s = app.listen(0, '127.
@@ -62,8 +62,13 @@ const listen = (app) => new Promise((resolve) => { const s = app.listen(0, '127.
assert.strictEqual(body.service, 'media');
assert.strictEqual(body.service, 'media');
assert.strictEqual(body.recordings_in_progress, 1);
assert.strictEqual(body.recordings_in_progress, 1);
const names = Object.keys(body.checks).sort();
const names = Object.keys(body.checks).sort();
- assert.deepStrictEqual(names, ['db', 'events_outbox', 'network_jwks', 'object_copies', 'remote_b2', 'storage_clips', 'storage_files', 'storage_objects', 'storage_pastes', 'storage_thumbnails', 'storage_vods'], 'r2 is not configured, so it has no check');
+ assert.deepStrictEqual(names, ['db', 'events_outbox', 'network_jwks', 'object_copies', 'remote_b2', 'remote_r2', 'storage_clips', 'storage_files', 'storage_objects', 'storage_pastes', 'storage_thumbnails', 'storage_vods']);
+ // r2 is not configured: listed as skipped with the reason, never a pretend pass (WS-Q task 7), and an optional
+ // skip leaves the verified service ready.
+ assert.deepStrictEqual([body.checks.remote_r2.status, body.checks.remote_r2.reason], ['skipped', 'not configured']);
+ assert.deepStrictEqual(body.skipped, ['remote_r2']);
for (const [n, c] of Object.entries(body.checks)) {
for (const [n, c] of Object.entries(body.checks)) {
+ if (n === 'remote_r2') continue;
assert.strictEqual(c.status, 'ok', n);
assert.strictEqual(c.status, 'ok', n);
assert.strictEqual(typeof c.latency_ms, 'number');
assert.strictEqual(typeof c.latency_ms, 'number');
assert.ok(Date.parse(c.checked_at));
assert.ok(Date.parse(c.checked_at));
@@ -89,6 +94,8 @@ const listen = (app) => new Promise((resolve) => { const s = app.listen(0, '127.
@@ -89,6 +94,8 @@ const listen = (app) => new Promise((resolve) => { const s = app.listen(0, '127.
assert.deepStrictEqual(body.degraded, ['remote_b2', 'remote_r2']);
assert.deepStrictEqual(body.degraded, ['remote_b2', 'remote_r2']);
assert.strictEqual(body.checks.remote_r2.error, 'r2 HeadBucket failed: AccessDenied');
assert.strictEqual(body.checks.remote_r2.error, 'r2 HeadBucket failed: AccessDenied');
assert.deepStrictEqual(body.checks.events_outbox.detail, { enabled: false });
assert.deepStrictEqual(body.checks.events_outbox.detail, { enabled: false });
+ assert.strictEqual(body.checks.events_outbox.status, 'skipped', 'a relay that is off verified nothing: skipped, never ok');
+ assert.match(body.checks.events_outbox.reason, /relay off/);
// A storage directory that cannot be written: not ready, 503, named.
// A storage directory that cannot be written: not ready, 503, named.
jwks = true; outbox = { enabled: false };
jwks = true; outbox = { enabled: false };
@@ -140,7 +147,7 @@ const listen = (app) => new Promise((resolve) => { const s = app.listen(0, '127.
@@ -140,7 +147,7 @@ const listen = (app) => new Promise((resolve) => { const s = app.listen(0, '127.
assert.strictEqual(up.status, 200, up.body);
assert.strictEqual(up.status, 200, up.body);
assert.strictEqual(body.release, 'abcdef123456');
assert.strictEqual(body.release, 'abcdef123456');
assert.ok(body.checks.db && body.checks.storage_objects);
assert.ok(body.checks.db && body.checks.storage_objects);
- assert.ok(!body.checks.remote_b2, 'unconfigured tiers are not checked');
+ assert.strictEqual(body.checks.remote_b2.status, 'skipped', 'an unconfigured tier is not probed: skipped, never ok');
const m = await request(b2, '/metrics');
const m = await request(b2, '/metrics');
assert.strictEqual(m.status, 200);
assert.strictEqual(m.status, 200);
assert.ok(m.body.includes('http_requests_total{method="GET",route="/api/ready",status_class="2xx"} 1\n'), m.body.slice(0, 400));
assert.ok(m.body.includes('http_requests_total{method="GET",route="/api/ready",status_class="2xx"} 1\n'), m.body.slice(0, 400));