feat(ql3): version worker support tier admission

This commit is contained in:
whyour
2026-08-20 12:02:15 +08:00
parent 6e11258d45
commit 6265e31dce
28 changed files with 616 additions and 57 deletions
@@ -8,6 +8,8 @@ import {
} from 'node:path';
import {
canonicalRemoteWorkerCapabilities,
REMOTE_WORKER_EXECUTOR_CAPABILITY,
remoteWorkerArchitectureForNodeRuntime,
type RemoteWorkerCapabilities,
} from '@qinglong/runtime-core/remote-dispatch';
@@ -206,7 +208,18 @@ async function capabilities(path: string): Promise<RemoteWorkerCapabilities> {
const parsed = JSON.parse(
new TextDecoder('utf-8', { fatal: true }).decode(bytes),
) as unknown;
return canonicalRemoteWorkerCapabilities(parsed).capabilities;
const normalized = canonicalRemoteWorkerCapabilities(parsed).capabilities;
if (!normalized.executors.includes(REMOTE_WORKER_EXECUTOR_CAPABILITY))
throw new Error('remote-worker executor capability is required');
const runtimeArchitecture = remoteWorkerArchitectureForNodeRuntime(
process.arch,
(process.config.variables as Readonly<Record<string, unknown>>)[
'arm_version'
],
);
if (normalized.architecture !== runtimeArchitecture)
throw new Error('capability architecture does not match the Node runtime');
return normalized;
} catch (error) {
throw new WorkerProcessConfigError(
'QL3_WORKER_CAPABILITIES_FILE is unavailable or invalid',
@@ -40,9 +40,11 @@ async function temporaryStorage() {
function capabilities() {
return {
architecture: 'x64',
architecture: 'amd64',
operatingSystem: 'linux',
executors: ['local_process'],
executors: ['remote-worker'],
protocolVersion: '1.0.0',
supportTier: 'tier1',
runtimes: [{ name: 'node', version: '24.14.0' }],
labels: {},
capacity: { cpuCores: 1, memoryBytes: 256 * 1024 * 1024 },
@@ -16,15 +16,24 @@ const {
WorkerProcessError,
runProductionWorkerProcess,
} = require('@qinglong/worker-runtime/process');
const {
remoteWorkerArchitectureForNodeRuntime,
remoteWorkerSupportTierForArchitecture,
} = require('@qinglong/runtime-core/remote-dispatch');
async function environment(t) {
const root = await mkdtemp(path.join(os.tmpdir(), 'ql3-worker-process-'));
t.after(() => rm(root, { recursive: true, force: true }));
const capabilities = path.join(root, 'capabilities.json');
const architecture = remoteWorkerArchitectureForNodeRuntime(
process.arch, process.config.variables.arm_version,
);
await writeFile(capabilities, JSON.stringify({
architecture: 'x64',
architecture,
operatingSystem: 'linux',
executors: ['local_process'],
executors: ['remote-worker'],
protocolVersion: '1.0.0',
supportTier: remoteWorkerSupportTierForArchitecture(architecture),
}));
await chmod(capabilities, 0o444);
return {
@@ -14,6 +14,16 @@ const {
WorkerProcessConfigError,
loadWorkerProcessConfig,
} = require('@qinglong/worker-runtime/process-config');
const {
remoteWorkerArchitectureForNodeRuntime,
remoteWorkerSupportTierForArchitecture,
} = require('@qinglong/runtime-core/remote-dispatch');
const RUNTIME_ARCHITECTURE = remoteWorkerArchitectureForNodeRuntime(
process.arch, process.config.variables.arm_version,
);
const RUNTIME_SUPPORT_TIER =
remoteWorkerSupportTierForArchitecture(RUNTIME_ARCHITECTURE);
async function fixture(t) {
const root = await mkdtemp(path.join(os.tmpdir(), 'ql3-worker-config-'));
@@ -22,9 +32,11 @@ async function fixture(t) {
await writeFile(
capabilitiesFile,
JSON.stringify({
architecture: 'arm64',
architecture: RUNTIME_ARCHITECTURE,
operatingSystem: 'linux',
executors: ['local_process'],
executors: ['remote-worker'],
protocolVersion: '1.0.0',
supportTier: RUNTIME_SUPPORT_TIER,
runtimes: [{ name: 'node', version: '24.18.0' }],
labels: { site: 'edge-a' },
capacity: {
@@ -97,8 +109,10 @@ test('loads canonical edge defaults and bounded node overrides', async (t) => {
assert.equal(edge.workerId, 'router-worker-1');
assert.equal(edge.origin, 'https://control.example.internal:5801');
assert.deepEqual(edge.capabilities, {
architecture: 'arm64',
executors: ['local_process'],
architecture: RUNTIME_ARCHITECTURE,
executors: ['remote-worker'],
protocolVersion: '1.0.0',
supportTier: RUNTIME_SUPPORT_TIER,
operatingSystem: 'linux',
runtimes: [{ name: 'node', version: '24.18.0' }],
labels: { site: 'edge-a' },
@@ -182,3 +196,19 @@ test('rejects widened profiles, origins, heartbeat and filesystem configuration'
WorkerProcessConfigError,
);
});
test('rejects a Worker capability file that cannot satisfy remote placement', async (t) => {
const current = await fixture(t);
await chmod(current.capabilitiesFile, 0o644);
await writeFile(current.capabilitiesFile, JSON.stringify({
architecture: RUNTIME_ARCHITECTURE,
executors: ['local_process'],
protocolVersion: '1.0.0',
supportTier: RUNTIME_SUPPORT_TIER,
}));
await chmod(current.capabilitiesFile, 0o444);
await assert.rejects(
loadWorkerProcessConfig(current.environment),
WorkerProcessConfigError,
);
});
@@ -17,9 +17,11 @@ const SESSION_ID = '018f5c64-9b9d-7f1a-8c2d-1234567890ac';
function capabilities() {
return {
architecture: 'x64',
architecture: 'amd64',
operatingSystem: 'linux',
executors: ['local_process'],
executors: ['remote-worker'],
protocolVersion: '1.0.0',
supportTier: 'tier1',
runtimes: [{ name: 'node', version: '24.14.0' }],
labels: {},
capacity: { cpuCores: 1, memoryBytes: 256 * 1024 * 1024 },
@@ -1,6 +1,7 @@
'use strict';
const assert = require('node:assert/strict');
const { createHash } = require('node:crypto');
const { test } = require('node:test');
const {
WORKER_SESSION_HEARTBEAT_SCHEMA,
@@ -19,9 +20,10 @@ const authority = {
workerId: 'edge-1',
sessionId: '018f5c64-9b9d-7f1a-8c2d-1234567890ac',
};
const capabilitiesJson = '{}';
const capabilitiesHash =
'44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a';
const capabilitiesJson =
'{"architecture":"arm64","executors":["remote-worker"],"protocolVersion":"1.0.0","supportTier":"tier1"}';
const capabilitiesHash = createHash('sha256')
.update(capabilitiesJson).digest('hex');
function client(exchange) {
const transport = new WorkerIngressHttpsClient({