mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 19:29:13 +08:00
feat(ql3): expose bounded worker session observation
This commit is contained in:
+97
-1
@@ -3,6 +3,7 @@ import {
|
||||
PostgresApprovalRequestRepository,
|
||||
PostgresProjectPolicyRepository,
|
||||
PostgresWorkerCredentialManagementPlanRepository,
|
||||
PostgresWorkerSessionObservationRepository,
|
||||
} from '@qinglong/cluster-postgres/worker-credential-manager';
|
||||
import type { PostgresPool } from '@qinglong/runtime-core';
|
||||
import {
|
||||
@@ -20,6 +21,10 @@ import {
|
||||
type SecuritySubject,
|
||||
} from '@qinglong/runtime-core/security';
|
||||
import type { SecurityAuditRecord } from '@qinglong/runtime-core/security-audit';
|
||||
import type {
|
||||
WorkerSessionInspection,
|
||||
WorkerSessionObservationPage,
|
||||
} from '@qinglong/runtime-core/worker-session-observation';
|
||||
import {
|
||||
InvalidWorkerCredentialManagementPlanError,
|
||||
MAX_WORKER_CREDENTIAL_MANAGEMENT_PLAN_LIFETIME_MS,
|
||||
@@ -93,7 +98,8 @@ export type WorkerCredentialManagementQuotaOperation =
|
||||
| 'worker-credential.plan'
|
||||
| 'worker-credential.propose'
|
||||
| 'worker-credential.decide'
|
||||
| 'worker-credential.inspect';
|
||||
| 'worker-credential.inspect'
|
||||
| 'worker-session.observe';
|
||||
|
||||
export interface WorkerCredentialManagementQuotaPort {
|
||||
consume(
|
||||
@@ -166,6 +172,20 @@ export interface InspectClusterWorkerCredentialResult {
|
||||
readonly stale: boolean;
|
||||
}
|
||||
|
||||
export interface InspectClusterWorkerSessionRequest {
|
||||
readonly authorityProjectId: string;
|
||||
readonly workerId: string;
|
||||
readonly inspectionId: string;
|
||||
readonly principal: SecurityPrincipal;
|
||||
}
|
||||
|
||||
export interface ListClusterWorkerSessionsRequest {
|
||||
readonly authorityProjectId: string;
|
||||
readonly afterWorkerId: string | null;
|
||||
readonly inspectionId: string;
|
||||
readonly principal: SecurityPrincipal;
|
||||
}
|
||||
|
||||
export interface ClusterWorkerCredentialManagementService {
|
||||
plan(
|
||||
request: PlanClusterWorkerCredentialRequest,
|
||||
@@ -179,6 +199,12 @@ export interface ClusterWorkerCredentialManagementService {
|
||||
inspectAuthorized(
|
||||
request: InspectClusterWorkerCredentialRequest,
|
||||
): Promise<Readonly<InspectClusterWorkerCredentialResult>>;
|
||||
inspectSession(
|
||||
request: InspectClusterWorkerSessionRequest,
|
||||
): Promise<Readonly<WorkerSessionInspection>>;
|
||||
listSessions(
|
||||
request: ListClusterWorkerSessionsRequest,
|
||||
): Promise<Readonly<WorkerSessionObservationPage>>;
|
||||
}
|
||||
|
||||
export interface ClusterWorkerCredentialManagementOptions {
|
||||
@@ -324,6 +350,7 @@ export function createClusterWorkerCredentialManagementService(
|
||||
const plans = new PostgresWorkerCredentialManagementPlanRepository(
|
||||
options.pool,
|
||||
);
|
||||
const sessions = new PostgresWorkerSessionObservationRepository(options.pool);
|
||||
const approvals = new PostgresApprovalRequestRepository(options.pool);
|
||||
const policy = new ProjectPolicyEngine(
|
||||
new PostgresProjectPolicyRepository(options.pool),
|
||||
@@ -796,5 +823,74 @@ export function createClusterWorkerCredentialManagementService(
|
||||
observedAtMs > plan.expiresAtMs,
|
||||
});
|
||||
},
|
||||
|
||||
async inspectSession(request: InspectClusterWorkerSessionRequest) {
|
||||
exact(
|
||||
request,
|
||||
['authorityProjectId', 'inspectionId', 'principal', 'workerId'],
|
||||
'Session inspection request',
|
||||
);
|
||||
const projectId = identifier(
|
||||
request.authorityProjectId,
|
||||
'authorityProjectId',
|
||||
);
|
||||
const inspectionId = identifier(request.inspectionId, 'inspectionId');
|
||||
const workerId = identifier(request.workerId, 'workerId');
|
||||
const authorization = await authorize(
|
||||
request.principal,
|
||||
projectId,
|
||||
'worker.manage',
|
||||
currentTime(now),
|
||||
);
|
||||
await consumeQuota(
|
||||
projectId,
|
||||
authorization.principal,
|
||||
'worker-session.observe',
|
||||
`session-inspect:${inspectionId}`,
|
||||
);
|
||||
try {
|
||||
return await sessions.inspect(workerId);
|
||||
} catch (error) {
|
||||
throw new WorkerCredentialManagementUnavailableError({
|
||||
cause: error instanceof Error ? error : undefined,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async listSessions(request: ListClusterWorkerSessionsRequest) {
|
||||
exact(
|
||||
request,
|
||||
['afterWorkerId', 'authorityProjectId', 'inspectionId', 'principal'],
|
||||
'Session list request',
|
||||
);
|
||||
const projectId = identifier(
|
||||
request.authorityProjectId,
|
||||
'authorityProjectId',
|
||||
);
|
||||
const inspectionId = identifier(request.inspectionId, 'inspectionId');
|
||||
const afterWorkerId =
|
||||
request.afterWorkerId === null
|
||||
? null
|
||||
: identifier(request.afterWorkerId, 'afterWorkerId');
|
||||
const authorization = await authorize(
|
||||
request.principal,
|
||||
projectId,
|
||||
'worker.manage',
|
||||
currentTime(now),
|
||||
);
|
||||
await consumeQuota(
|
||||
projectId,
|
||||
authorization.principal,
|
||||
'worker-session.observe',
|
||||
`session-list:${inspectionId}`,
|
||||
);
|
||||
try {
|
||||
return await sessions.list(afterWorkerId);
|
||||
} catch (error) {
|
||||
throw new WorkerCredentialManagementUnavailableError({
|
||||
cause: error instanceof Error ? error : undefined,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
+81
-1
@@ -74,11 +74,33 @@ export interface InspectClusterWorkerCredentialCommand {
|
||||
};
|
||||
}
|
||||
|
||||
export interface InspectClusterWorkerSessionCommand {
|
||||
readonly schemaVersion: 1;
|
||||
readonly operation: 'worker-session.inspect';
|
||||
readonly request: {
|
||||
readonly authorityProjectId: string;
|
||||
readonly workerId: string;
|
||||
readonly inspectionId: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ListClusterWorkerSessionsCommand {
|
||||
readonly schemaVersion: 1;
|
||||
readonly operation: 'worker-session.list';
|
||||
readonly request: {
|
||||
readonly authorityProjectId: string;
|
||||
readonly afterWorkerId: string | null;
|
||||
readonly inspectionId: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type ClusterWorkerCredentialManagementCommand =
|
||||
| PlanClusterWorkerCredentialCommand
|
||||
| ProposeClusterWorkerCredentialCommand
|
||||
| DecideClusterWorkerCredentialCommand
|
||||
| InspectClusterWorkerCredentialCommand;
|
||||
| InspectClusterWorkerCredentialCommand
|
||||
| InspectClusterWorkerSessionCommand
|
||||
| ListClusterWorkerSessionsCommand;
|
||||
|
||||
type PlanSummary = ReturnType<typeof planSummary>;
|
||||
type ApprovalSummary = ReturnType<typeof approvalSummary>;
|
||||
@@ -109,6 +131,23 @@ export type ClusterWorkerCredentialManagementTransportResult =
|
||||
plan: PlanSummary | null;
|
||||
approval: ApprovalSummary | null;
|
||||
stale: boolean;
|
||||
}>
|
||||
| Readonly<{
|
||||
schemaVersion: 1;
|
||||
operation: 'worker-session.inspect';
|
||||
observedAtMs: number;
|
||||
worker: Awaited<
|
||||
ReturnType<ClusterWorkerCredentialManagementService['inspectSession']>
|
||||
>['worker'];
|
||||
}>
|
||||
| Readonly<{
|
||||
schemaVersion: 1;
|
||||
operation: 'worker-session.list';
|
||||
observedAtMs: number;
|
||||
workers: Awaited<
|
||||
ReturnType<ClusterWorkerCredentialManagementService['listSessions']>
|
||||
>['workers'];
|
||||
nextCursor: string | null;
|
||||
}>;
|
||||
|
||||
export interface ClusterWorkerCredentialManagementTransport {
|
||||
@@ -254,6 +293,20 @@ export function normalizeClusterWorkerCredentialManagementCommand(
|
||||
'inspection request',
|
||||
);
|
||||
break;
|
||||
case 'worker-session.inspect':
|
||||
exactObject(
|
||||
value.request,
|
||||
['authorityProjectId', 'inspectionId', 'workerId'],
|
||||
'Session inspection request',
|
||||
);
|
||||
break;
|
||||
case 'worker-session.list':
|
||||
exactObject(
|
||||
value.request,
|
||||
['afterWorkerId', 'authorityProjectId', 'inspectionId'],
|
||||
'Session list request',
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new ClusterWorkerCredentialManagementTransportRequestError(
|
||||
'operation is not publicly available',
|
||||
@@ -317,6 +370,8 @@ export function createClusterWorkerCredentialManagementTransport(
|
||||
typeof options.service.propose !== 'function' ||
|
||||
typeof options.service.decide !== 'function' ||
|
||||
typeof options.service.inspectAuthorized !== 'function' ||
|
||||
typeof options.service.inspectSession !== 'function' ||
|
||||
typeof options.service.listSessions !== 'function' ||
|
||||
(options.now !== undefined && typeof options.now !== 'function')
|
||||
) {
|
||||
throw new ClusterWorkerCredentialManagementTransportConfigurationError(
|
||||
@@ -420,6 +475,31 @@ export function createClusterWorkerCredentialManagementTransport(
|
||||
stale: result.stale,
|
||||
});
|
||||
}
|
||||
case 'worker-session.inspect': {
|
||||
const result = await options.service.inspectSession({
|
||||
...command.request,
|
||||
principal,
|
||||
});
|
||||
return Object.freeze({
|
||||
schemaVersion: 1 as const,
|
||||
operation: command.operation,
|
||||
observedAtMs: result.observedAtMs,
|
||||
worker: result.worker,
|
||||
});
|
||||
}
|
||||
case 'worker-session.list': {
|
||||
const result = await options.service.listSessions({
|
||||
...command.request,
|
||||
principal,
|
||||
});
|
||||
return Object.freeze({
|
||||
schemaVersion: 1 as const,
|
||||
operation: command.operation,
|
||||
observedAtMs: result.observedAtMs,
|
||||
workers: result.workers,
|
||||
nextCursor: result.nextCursor,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
+164
-2
@@ -57,6 +57,23 @@ function boundedScalar(value: unknown): void {
|
||||
}
|
||||
}
|
||||
|
||||
function boundedText(value: unknown, nullable = false): void {
|
||||
if (value === null && nullable) return;
|
||||
if (
|
||||
typeof value !== 'string' ||
|
||||
value.length === 0 ||
|
||||
value.length > 2_048 ||
|
||||
CONTROL_PATTERN.test(value)
|
||||
) {
|
||||
invalid();
|
||||
}
|
||||
}
|
||||
|
||||
function nonNegativeInteger(value: unknown, nullable = false): void {
|
||||
if (value === null && nullable) return;
|
||||
if (!Number.isSafeInteger(value) || (value as number) < 0) invalid();
|
||||
}
|
||||
|
||||
function subject(value: unknown): void {
|
||||
const record = exactRecord(value, ['type', 'id']);
|
||||
if (record.type !== 'user') invalid();
|
||||
@@ -131,6 +148,106 @@ function approval(value: unknown): void {
|
||||
}
|
||||
}
|
||||
|
||||
const WORKER_OBSERVATION_KEYS = [
|
||||
'architecture',
|
||||
'availableSlots',
|
||||
'compatibility',
|
||||
'generation',
|
||||
'lastHeartbeatAtMs',
|
||||
'leaseExpiresAtMs',
|
||||
'lifecycle',
|
||||
'maxConcurrentRuns',
|
||||
'observedAtMs',
|
||||
'operatingSystem',
|
||||
'protocolVersion',
|
||||
'registeredAtMs',
|
||||
'sessionId',
|
||||
'sessionVersion',
|
||||
'supportTier',
|
||||
'updatedAtMs',
|
||||
'workerId',
|
||||
] as const;
|
||||
|
||||
function workerObservation(value: unknown, detailed: boolean): void {
|
||||
const record = exactRecord(
|
||||
value,
|
||||
detailed
|
||||
? [...WORKER_OBSERVATION_KEYS, 'declaredCapacity', 'runtimes']
|
||||
: WORKER_OBSERVATION_KEYS,
|
||||
);
|
||||
boundedText(record.workerId);
|
||||
boundedText(record.sessionId);
|
||||
boundedText(record.protocolVersion);
|
||||
boundedText(record.operatingSystem, true);
|
||||
for (const key of [
|
||||
'generation',
|
||||
'sessionVersion',
|
||||
'maxConcurrentRuns',
|
||||
'availableSlots',
|
||||
'registeredAtMs',
|
||||
'lastHeartbeatAtMs',
|
||||
'leaseExpiresAtMs',
|
||||
'updatedAtMs',
|
||||
'observedAtMs',
|
||||
] as const) {
|
||||
nonNegativeInteger(record[key]);
|
||||
}
|
||||
if (
|
||||
!['online', 'draining', 'offline', 'lease_expired'].includes(
|
||||
String(record.lifecycle),
|
||||
) ||
|
||||
![
|
||||
'default_placement',
|
||||
'explicit_placement_required',
|
||||
'protocol_incompatible',
|
||||
].includes(String(record.compatibility)) ||
|
||||
!['tier1', 'candidate', 'experimental', 'legacy-only'].includes(
|
||||
String(record.supportTier),
|
||||
) ||
|
||||
!['amd64', 'arm64', 'ppc64le', 's390x', 'arm/v7', 'arm/v6', '386'].includes(
|
||||
String(record.architecture),
|
||||
)
|
||||
) {
|
||||
invalid();
|
||||
}
|
||||
if (
|
||||
(record.generation as number) < 1 ||
|
||||
(record.maxConcurrentRuns as number) < 1 ||
|
||||
(record.availableSlots as number) > (record.maxConcurrentRuns as number) ||
|
||||
(record.lastHeartbeatAtMs as number) < (record.registeredAtMs as number) ||
|
||||
(record.updatedAtMs as number) < (record.lastHeartbeatAtMs as number) ||
|
||||
(record.observedAtMs as number) < (record.updatedAtMs as number) ||
|
||||
(record.lifecycle !== 'offline' &&
|
||||
(record.leaseExpiresAtMs as number) <=
|
||||
(record.lastHeartbeatAtMs as number)) ||
|
||||
(['online', 'draining'].includes(String(record.lifecycle)) &&
|
||||
(record.leaseExpiresAtMs as number) <= (record.observedAtMs as number)) ||
|
||||
(record.lifecycle === 'lease_expired' &&
|
||||
(record.leaseExpiresAtMs as number) > (record.observedAtMs as number)) ||
|
||||
(['draining', 'offline'].includes(String(record.lifecycle)) &&
|
||||
(record.availableSlots as number) !== 0)
|
||||
) {
|
||||
invalid();
|
||||
}
|
||||
if (!detailed) return;
|
||||
if (!Array.isArray(record.runtimes) || record.runtimes.length > 32) invalid();
|
||||
for (const runtime of record.runtimes) {
|
||||
const item = exactRecord(runtime, ['name', 'version']);
|
||||
boundedText(item.name);
|
||||
boundedText(item.version);
|
||||
}
|
||||
const capacity = exactRecord(record.declaredCapacity, [
|
||||
'cpuCores',
|
||||
'diskBytes',
|
||||
'gpuCount',
|
||||
'memoryBytes',
|
||||
]);
|
||||
nonNegativeInteger(capacity.cpuCores, true);
|
||||
nonNegativeInteger(capacity.memoryBytes, true);
|
||||
nonNegativeInteger(capacity.diskBytes, true);
|
||||
nonNegativeInteger(capacity.gpuCount);
|
||||
}
|
||||
|
||||
export function validateClusterWorkerCredentialManagementClientResult(
|
||||
value: unknown,
|
||||
command: Readonly<ClusterWorkerCredentialManagementCommand>,
|
||||
@@ -143,7 +260,17 @@ export function validateClusterWorkerCredentialManagementClientResult(
|
||||
? ['schemaVersion', 'operation', 'approvalStatus', 'plan', 'approval']
|
||||
: operation === 'worker-credential.decide'
|
||||
? ['schemaVersion', 'operation', 'status', 'approval']
|
||||
: ['schemaVersion', 'operation', 'plan', 'approval', 'stale'];
|
||||
: operation === 'worker-credential.inspect'
|
||||
? ['schemaVersion', 'operation', 'plan', 'approval', 'stale']
|
||||
: operation === 'worker-session.inspect'
|
||||
? ['schemaVersion', 'operation', 'observedAtMs', 'worker']
|
||||
: [
|
||||
'schemaVersion',
|
||||
'operation',
|
||||
'observedAtMs',
|
||||
'workers',
|
||||
'nextCursor',
|
||||
];
|
||||
const record = exactRecord(value, keys);
|
||||
if (record.schemaVersion !== 1 || record.operation !== operation) invalid();
|
||||
if (operation === 'worker-credential.plan') {
|
||||
@@ -157,10 +284,45 @@ export function validateClusterWorkerCredentialManagementClientResult(
|
||||
} else if (operation === 'worker-credential.decide') {
|
||||
if (!['decided', 'existing'].includes(String(record.status))) invalid();
|
||||
approval(record.approval);
|
||||
} else {
|
||||
} else if (operation === 'worker-credential.inspect') {
|
||||
if (typeof record.stale !== 'boolean') invalid();
|
||||
if (record.plan !== null) plan(record.plan);
|
||||
if (record.approval !== null) approval(record.approval);
|
||||
} else if (operation === 'worker-session.inspect') {
|
||||
nonNegativeInteger(record.observedAtMs);
|
||||
if (record.worker !== null) {
|
||||
workerObservation(record.worker, true);
|
||||
if (
|
||||
(record.worker as Record<string, unknown>).observedAtMs !==
|
||||
record.observedAtMs
|
||||
) {
|
||||
invalid();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nonNegativeInteger(record.observedAtMs);
|
||||
boundedText(record.nextCursor, true);
|
||||
if (!Array.isArray(record.workers) || record.workers.length > 16) invalid();
|
||||
let previous = '';
|
||||
for (const worker of record.workers) {
|
||||
workerObservation(worker, false);
|
||||
const item = worker as Record<string, unknown>;
|
||||
if (
|
||||
item.observedAtMs !== record.observedAtMs ||
|
||||
typeof item.workerId !== 'string' ||
|
||||
item.workerId <= previous
|
||||
) {
|
||||
invalid();
|
||||
}
|
||||
previous = item.workerId;
|
||||
}
|
||||
if (
|
||||
(record.nextCursor !== null &&
|
||||
(record.workers.length === 0 || record.nextCursor !== previous)) ||
|
||||
(record.nextCursor === null && record.workers.length > 16)
|
||||
) {
|
||||
invalid();
|
||||
}
|
||||
}
|
||||
return Object.freeze(
|
||||
record as unknown as ClusterWorkerCredentialManagementTransportResult,
|
||||
|
||||
@@ -4,6 +4,9 @@ const { test } = require('node:test');
|
||||
const {
|
||||
createClusterWorkerCredentialManagementService,
|
||||
} = require('@qinglong/cluster-admin/worker-credential-management');
|
||||
const {
|
||||
canonicalRemoteWorkerCapabilities,
|
||||
} = require('@qinglong/runtime-core/remote-dispatch');
|
||||
|
||||
const REQUESTER = Object.freeze({
|
||||
subject: Object.freeze({ type: 'user', id: 'operator-a' }),
|
||||
@@ -20,6 +23,32 @@ const REVIEWER = Object.freeze({
|
||||
assurance: 'hardware',
|
||||
});
|
||||
|
||||
function sessionRow() {
|
||||
const capabilities = canonicalRemoteWorkerCapabilities({
|
||||
architecture: 'arm64',
|
||||
executors: ['remote-worker'],
|
||||
protocolVersion: '1.0.0',
|
||||
supportTier: 'tier1',
|
||||
runtimes: [{ name: 'node', version: '24.18.0' }],
|
||||
});
|
||||
return {
|
||||
observedAtMs: 2_000,
|
||||
workerId: 'worker-a',
|
||||
sessionId: '018f0f5d-7b6a-7a11-8f4d-2f7b4f477001',
|
||||
generation: 2,
|
||||
status: 'online',
|
||||
version: 5,
|
||||
capabilitiesJson: capabilities.json,
|
||||
capabilitiesHash: capabilities.hash,
|
||||
maxConcurrentRuns: 2,
|
||||
availableSlots: 1,
|
||||
registeredAtMs: 1_000,
|
||||
lastHeartbeatAtMs: 1_900,
|
||||
leaseExpiresAtMs: 3_000,
|
||||
updatedAtMs: 1_900,
|
||||
};
|
||||
}
|
||||
|
||||
function approvalFixture() {
|
||||
const plans = new Map();
|
||||
const approvals = new Map();
|
||||
@@ -76,6 +105,9 @@ function approvalFixture() {
|
||||
const stored = audits.get(values[0]);
|
||||
return { rows: stored ? [stored] : [], rowCount: stored ? 1 : 0 };
|
||||
}
|
||||
if (text.includes('FROM "ql3"."worker_sessions"')) {
|
||||
return { rows: [sessionRow()], rowCount: 1 };
|
||||
}
|
||||
if (text.includes('"ql3"."lock_approval_policy_fence"')) {
|
||||
return { rows: [{ matches: true }], rowCount: 1 };
|
||||
}
|
||||
@@ -277,3 +309,50 @@ test('authorizes and consumes durable quota before reading management state', as
|
||||
assert.equal(quotaCalls[1].operation, 'worker-credential.propose');
|
||||
assert.equal(quotaCalls[1].planReads, readsBefore);
|
||||
});
|
||||
|
||||
test('observes one Session and one bounded page only after worker.manage quota', async () => {
|
||||
const state = approvalFixture();
|
||||
const quotaCalls = [];
|
||||
const service = createClusterWorkerCredentialManagementService({
|
||||
pool: state.pool,
|
||||
now: () => 2_000,
|
||||
quota: {
|
||||
async consume(command) {
|
||||
quotaCalls.push(command);
|
||||
return { admitted: true, retryAfterMs: null };
|
||||
},
|
||||
},
|
||||
});
|
||||
const inspection = await service.inspectSession({
|
||||
authorityProjectId: 'cluster-authority',
|
||||
workerId: 'worker-a',
|
||||
inspectionId: 'worker-session-inspection-a',
|
||||
principal: REQUESTER,
|
||||
});
|
||||
const page = await service.listSessions({
|
||||
authorityProjectId: 'cluster-authority',
|
||||
afterWorkerId: null,
|
||||
inspectionId: 'worker-session-list-a',
|
||||
principal: REQUESTER,
|
||||
});
|
||||
assert.equal(inspection.worker.workerId, 'worker-a');
|
||||
assert.equal(inspection.worker.compatibility, 'default_placement');
|
||||
assert.equal(page.workers.length, 1);
|
||||
assert.equal(page.workers[0].workerId, 'worker-a');
|
||||
assert.deepEqual(
|
||||
quotaCalls.map(({ operation, idempotencyKey }) => ({
|
||||
operation,
|
||||
idempotencyKey,
|
||||
})),
|
||||
[
|
||||
{
|
||||
operation: 'worker-session.observe',
|
||||
idempotencyKey: 'session-inspect:worker-session-inspection-a',
|
||||
},
|
||||
{
|
||||
operation: 'worker-session.observe',
|
||||
idempotencyKey: 'session-list:worker-session-list-a',
|
||||
},
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -82,6 +82,39 @@ const approval = Object.freeze({
|
||||
actionDigest: 'd'.repeat(64),
|
||||
previewDigest: plan.previewDigest,
|
||||
});
|
||||
const session = Object.freeze({
|
||||
workerId: 'worker-1',
|
||||
sessionId: '018f0f5d-7b6a-7a11-8f4d-2f7b4f477001',
|
||||
generation: 2,
|
||||
sessionVersion: 5,
|
||||
lifecycle: 'online',
|
||||
compatibility: 'default_placement',
|
||||
architecture: 'arm64',
|
||||
supportTier: 'tier1',
|
||||
protocolVersion: '1.0.0',
|
||||
operatingSystem: 'linux',
|
||||
maxConcurrentRuns: 2,
|
||||
availableSlots: 1,
|
||||
registeredAtMs: 900,
|
||||
lastHeartbeatAtMs: 1_050,
|
||||
leaseExpiresAtMs: 2_000,
|
||||
updatedAtMs: 1_050,
|
||||
observedAtMs: 1_100,
|
||||
runtimes: Object.freeze([{ name: 'node', version: '24.18.0' }]),
|
||||
declaredCapacity: Object.freeze({
|
||||
cpuCores: 1,
|
||||
memoryBytes: 268_435_456,
|
||||
diskBytes: 1_073_741_824,
|
||||
gpuCount: 0,
|
||||
}),
|
||||
});
|
||||
const sessionSummary = Object.freeze(
|
||||
Object.fromEntries(
|
||||
Object.entries(session).filter(
|
||||
([key]) => key !== 'runtimes' && key !== 'declaredCapacity',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
function command(operation) {
|
||||
return {
|
||||
@@ -152,7 +185,7 @@ afterEach(() => {
|
||||
}
|
||||
});
|
||||
|
||||
test('validates all four low-sensitive Worker management results', () => {
|
||||
test('validates all six low-sensitive Worker management results', () => {
|
||||
const fixtures = [
|
||||
[
|
||||
'worker-credential.plan',
|
||||
@@ -187,6 +220,25 @@ test('validates all four low-sensitive Worker management results', () => {
|
||||
stale: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
'worker-session.inspect',
|
||||
{
|
||||
schemaVersion: 1,
|
||||
operation: 'worker-session.inspect',
|
||||
observedAtMs: 1_100,
|
||||
worker: session,
|
||||
},
|
||||
],
|
||||
[
|
||||
'worker-session.list',
|
||||
{
|
||||
schemaVersion: 1,
|
||||
operation: 'worker-session.list',
|
||||
observedAtMs: 1_100,
|
||||
workers: [sessionSummary],
|
||||
nextCursor: null,
|
||||
},
|
||||
],
|
||||
];
|
||||
for (const [operation, result] of fixtures) {
|
||||
assert.equal(
|
||||
@@ -223,6 +275,51 @@ test('rejects widened and secret-bearing response shapes', () => {
|
||||
command('worker-credential.inspect'),
|
||||
),
|
||||
);
|
||||
assert.throws(() =>
|
||||
validateClusterWorkerCredentialManagementClientResult(
|
||||
{
|
||||
schemaVersion: 1,
|
||||
operation: 'worker-session.inspect',
|
||||
observedAtMs: 1_100,
|
||||
worker: { ...session, labels: { secret: 'value' } },
|
||||
},
|
||||
command('worker-session.inspect'),
|
||||
),
|
||||
);
|
||||
assert.throws(() =>
|
||||
validateClusterWorkerCredentialManagementClientResult(
|
||||
{
|
||||
schemaVersion: 1,
|
||||
operation: 'worker-session.list',
|
||||
observedAtMs: 1_100,
|
||||
workers: [
|
||||
{ ...sessionSummary, workerId: 'worker-b' },
|
||||
{ ...sessionSummary, workerId: 'worker-a' },
|
||||
],
|
||||
nextCursor: null,
|
||||
},
|
||||
command('worker-session.list'),
|
||||
),
|
||||
);
|
||||
for (const worker of [
|
||||
{ ...session, workerId: null },
|
||||
{ ...session, generation: -1 },
|
||||
{ ...session, availableSlots: 3 },
|
||||
{ ...session, observedAtMs: 1_000 },
|
||||
{ ...session, declaredCapacity: { ...session.declaredCapacity, gpuCount: -1 } },
|
||||
]) {
|
||||
assert.throws(() =>
|
||||
validateClusterWorkerCredentialManagementClientResult(
|
||||
{
|
||||
schemaVersion: 1,
|
||||
operation: 'worker-session.inspect',
|
||||
observedAtMs: worker.observedAtMs,
|
||||
worker,
|
||||
},
|
||||
command('worker-session.inspect'),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('requires one matching private client certificate identity before connect', async () => {
|
||||
|
||||
@@ -69,6 +69,41 @@ function approval(planValue) {
|
||||
});
|
||||
}
|
||||
|
||||
const SESSION_OBSERVATION = Object.freeze({
|
||||
workerId: 'worker-a',
|
||||
sessionId: '018f0f5d-7b6a-7a11-8f4d-2f7b4f477001',
|
||||
generation: 2,
|
||||
sessionVersion: 5,
|
||||
lifecycle: 'online',
|
||||
compatibility: 'default_placement',
|
||||
architecture: 'arm64',
|
||||
supportTier: 'tier1',
|
||||
protocolVersion: '1.0.0',
|
||||
operatingSystem: 'linux',
|
||||
maxConcurrentRuns: 2,
|
||||
availableSlots: 1,
|
||||
registeredAtMs: 900,
|
||||
lastHeartbeatAtMs: 1_050,
|
||||
leaseExpiresAtMs: 2_000,
|
||||
updatedAtMs: 1_050,
|
||||
observedAtMs: 1_100,
|
||||
runtimes: Object.freeze([{ name: 'node', version: '24.18.0' }]),
|
||||
declaredCapacity: Object.freeze({
|
||||
cpuCores: 1,
|
||||
memoryBytes: 268_435_456,
|
||||
diskBytes: 1_073_741_824,
|
||||
gpuCount: 0,
|
||||
}),
|
||||
});
|
||||
|
||||
const SESSION_SUMMARY = Object.freeze(
|
||||
Object.fromEntries(
|
||||
Object.entries(SESSION_OBSERVATION).filter(
|
||||
([key]) => key !== 'runtimes' && key !== 'declaredCapacity',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
function commands() {
|
||||
return [
|
||||
{
|
||||
@@ -122,10 +157,28 @@ function commands() {
|
||||
inspectionId: 'inspection-worker-a-generation-2',
|
||||
},
|
||||
},
|
||||
{
|
||||
schemaVersion: 1,
|
||||
operation: 'worker-session.inspect',
|
||||
request: {
|
||||
authorityProjectId: 'cluster-authority',
|
||||
workerId: 'worker-a',
|
||||
inspectionId: 'inspection-worker-session-a',
|
||||
},
|
||||
},
|
||||
{
|
||||
schemaVersion: 1,
|
||||
operation: 'worker-session.list',
|
||||
request: {
|
||||
authorityProjectId: 'cluster-authority',
|
||||
afterWorkerId: null,
|
||||
inspectionId: 'inspection-worker-session-list-a',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
test('routes the four public commands with strong User authority and low-sensitive results', async () => {
|
||||
test('routes six public commands with strong User authority and low-sensitive results', async () => {
|
||||
const planValue = plan();
|
||||
const approvalValue = approval(planValue);
|
||||
const calls = [];
|
||||
@@ -154,6 +207,18 @@ test('routes the four public commands with strong User authority and low-sensiti
|
||||
stale: false,
|
||||
};
|
||||
},
|
||||
async inspectSession(request) {
|
||||
calls.push(['inspectSession', request]);
|
||||
return { observedAtMs: 1_100, worker: SESSION_OBSERVATION };
|
||||
},
|
||||
async listSessions(request) {
|
||||
calls.push(['listSessions', request]);
|
||||
return {
|
||||
observedAtMs: 1_100,
|
||||
workers: [SESSION_SUMMARY],
|
||||
nextCursor: null,
|
||||
};
|
||||
},
|
||||
};
|
||||
const transport = createClusterWorkerCredentialManagementTransport({
|
||||
service,
|
||||
@@ -170,7 +235,7 @@ test('routes the four public commands with strong User authority and low-sensiti
|
||||
}
|
||||
assert.deepEqual(
|
||||
calls.map(([kind]) => kind),
|
||||
['plan', 'propose', 'decide', 'inspect'],
|
||||
['plan', 'propose', 'decide', 'inspect', 'inspectSession', 'listSessions'],
|
||||
);
|
||||
for (const [, request] of calls) {
|
||||
assert.deepEqual(request.principal, principal());
|
||||
@@ -182,11 +247,15 @@ test('routes the four public commands with strong User authority and low-sensiti
|
||||
'worker-credential.propose',
|
||||
'worker-credential.decide',
|
||||
'worker-credential.inspect',
|
||||
'worker-session.inspect',
|
||||
'worker-session.list',
|
||||
],
|
||||
);
|
||||
assert.equal(results[0].plan.planDigest, planValue.planDigest);
|
||||
assert.equal(results[1].approval.actionDigest, planValue.planDigest);
|
||||
assert.equal(results[3].stale, false);
|
||||
assert.equal(results[4].worker.compatibility, 'default_placement');
|
||||
assert.equal(results[5].workers[0].workerId, 'worker-a');
|
||||
const serialized = JSON.stringify(results);
|
||||
assert.doesNotMatch(serialized, /authenticationId|credential-token|secret/i);
|
||||
});
|
||||
@@ -194,7 +263,14 @@ test('routes the four public commands with strong User authority and low-sensiti
|
||||
test('rejects weak or unavailable identity before management authority', async () => {
|
||||
let calls = 0;
|
||||
const service = Object.fromEntries(
|
||||
['plan', 'propose', 'decide', 'inspectAuthorized'].map((name) => [
|
||||
[
|
||||
'plan',
|
||||
'propose',
|
||||
'decide',
|
||||
'inspectAuthorized',
|
||||
'inspectSession',
|
||||
'listSessions',
|
||||
].map((name) => [
|
||||
name,
|
||||
async () => {
|
||||
calls += 1;
|
||||
@@ -232,6 +308,8 @@ test('rejects widened and internal commands before authentication', async () =>
|
||||
async propose() {},
|
||||
async decide() {},
|
||||
async inspectAuthorized() {},
|
||||
async inspectSession() {},
|
||||
async listSessions() {},
|
||||
},
|
||||
});
|
||||
let authentications = 0;
|
||||
|
||||
Reference in New Issue
Block a user