mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 00:17:47 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,409 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const { readFileSync } = require('node:fs');
|
||||
const { request: httpsRequest } = require('node:https');
|
||||
const { resolve } = require('node:path');
|
||||
const { test } = require('node:test');
|
||||
|
||||
const {
|
||||
ClusterPluginPackageIdentityKeysetUnavailableError,
|
||||
} = require('@qinglong/cluster-admin/plugin-package-identity-keyset');
|
||||
const {
|
||||
ClusterPluginPackageManagementHttpConfigurationError,
|
||||
startClusterPluginPackageManagementHttp,
|
||||
} = require('@qinglong/cluster-admin/plugin-package-management-http');
|
||||
const {
|
||||
PluginPackageManagementQuotaExceededError,
|
||||
} = require('@qinglong/runtime-core/plugin-package-management');
|
||||
|
||||
const SERVER_KEY = resolve(
|
||||
__dirname,
|
||||
'../../ql3-cluster-control/test/fixtures/mtls/server-key.pem',
|
||||
);
|
||||
const SERVER_CERT = resolve(
|
||||
__dirname,
|
||||
'../../ql3-cluster-control/test/fixtures/mtls/server-cert.pem',
|
||||
);
|
||||
|
||||
function principal() {
|
||||
return {
|
||||
subject: { type: 'user', id: 'cluster-reviewer' },
|
||||
authenticationId: 'ql3oidc.authentication-id',
|
||||
authenticatedAtMs: 900,
|
||||
expiresAtMs: 10_000,
|
||||
assurance: 'multi_factor',
|
||||
};
|
||||
}
|
||||
|
||||
function command() {
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
operation: 'plugin-package.inspect',
|
||||
request: {
|
||||
actionRef: 'package:cluster-monitor:1',
|
||||
approvalRequestId: 'approval-cluster-monitor-1',
|
||||
inspectionId: 'inspection-cluster-monitor-1',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function identityProvider(overrides = {}) {
|
||||
const calls = {
|
||||
reload: 0,
|
||||
bind: [],
|
||||
authenticate: 0,
|
||||
};
|
||||
return {
|
||||
calls,
|
||||
provider: {
|
||||
async reload() {
|
||||
calls.reload += 1;
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
generation: 1,
|
||||
digest: 'digest',
|
||||
issuer: 'https://identity.example.test/',
|
||||
audience: 'qinglong3-package-management',
|
||||
activeKeyIds: ['key-1'],
|
||||
revokedKeyIds: [],
|
||||
};
|
||||
},
|
||||
bind(assertion) {
|
||||
calls.bind.push(assertion);
|
||||
return {
|
||||
async authenticate() {
|
||||
calls.authenticate += 1;
|
||||
if (overrides.authenticate) {
|
||||
return overrides.authenticate();
|
||||
}
|
||||
return principal();
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function transportFixture(overrides = {}) {
|
||||
const calls = [];
|
||||
return {
|
||||
calls,
|
||||
transport: {
|
||||
async execute(value, authentication) {
|
||||
calls.push({
|
||||
command: value,
|
||||
principal: await authentication.authenticate(),
|
||||
});
|
||||
if (overrides.execute) return overrides.execute(value);
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
operation: 'plugin-package.inspect',
|
||||
proposal: null,
|
||||
approval: null,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function startFixture(overrides = {}) {
|
||||
const identities = overrides.identities ?? identityProvider();
|
||||
const transport = overrides.transport ?? transportFixture();
|
||||
const privateKey = Buffer.from(readFileSync(SERVER_KEY));
|
||||
const application = await startClusterPluginPackageManagementHttp({
|
||||
host: '127.0.0.1',
|
||||
port: 0,
|
||||
tls: {
|
||||
privateKey,
|
||||
certificate: Buffer.from(readFileSync(SERVER_CERT)),
|
||||
},
|
||||
identities: identities.provider,
|
||||
transport: transport.transport,
|
||||
limits: {
|
||||
requestTimeoutMs: 2_000,
|
||||
drainTimeoutMs: 500,
|
||||
...(overrides.limits ?? {}),
|
||||
},
|
||||
now: overrides.now ?? (() => 1_000),
|
||||
createRequestId: overrides.createRequestId ?? (() => 'request-1'),
|
||||
onError: overrides.onError,
|
||||
});
|
||||
assert.equal(
|
||||
privateKey.every((value) => value === 0),
|
||||
true,
|
||||
);
|
||||
return { application, identities, transport };
|
||||
}
|
||||
|
||||
async function request(application, options = {}) {
|
||||
const body =
|
||||
options.body === undefined
|
||||
? Buffer.from(JSON.stringify(command()))
|
||||
: Buffer.isBuffer(options.body)
|
||||
? options.body
|
||||
: Buffer.from(options.body);
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
const outgoing = httpsRequest(
|
||||
{
|
||||
host: '127.0.0.1',
|
||||
port: application.address.port,
|
||||
path: options.path ?? '/api/v3/plugin-packages/management',
|
||||
method: options.method ?? 'POST',
|
||||
rejectUnauthorized: false,
|
||||
agent: false,
|
||||
headers: {
|
||||
...(options.authorization === false
|
||||
? {}
|
||||
: { authorization: 'Bearer assertion-value' }),
|
||||
...(options.contentType === false
|
||||
? {}
|
||||
: { 'content-type': 'application/json' }),
|
||||
...(options.omitLength
|
||||
? {}
|
||||
: { 'content-length': String(body.length) }),
|
||||
...(options.headers ?? {}),
|
||||
},
|
||||
},
|
||||
(incoming) => {
|
||||
const chunks = [];
|
||||
incoming.on('data', (chunk) => chunks.push(chunk));
|
||||
incoming.once('end', () => {
|
||||
const bytes = Buffer.concat(chunks);
|
||||
resolvePromise({
|
||||
statusCode: incoming.statusCode,
|
||||
headers: incoming.headers,
|
||||
body:
|
||||
bytes.length === 0 ? null : JSON.parse(bytes.toString('utf8')),
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
outgoing.once('error', reject);
|
||||
if (body.length > 0) outgoing.write(body);
|
||||
outgoing.end();
|
||||
});
|
||||
}
|
||||
|
||||
test('rejects request and connection ceilings above hard bounds', async () => {
|
||||
for (const limits of [
|
||||
{ maxBodyBytes: 256 * 1024 + 1 },
|
||||
{ maxConnections: 513 },
|
||||
]) {
|
||||
const privateKey = Buffer.from(readFileSync(SERVER_KEY));
|
||||
try {
|
||||
await assert.rejects(
|
||||
startClusterPluginPackageManagementHttp({
|
||||
host: '127.0.0.1',
|
||||
port: 0,
|
||||
tls: {
|
||||
privateKey,
|
||||
certificate: Buffer.from(readFileSync(SERVER_CERT)),
|
||||
},
|
||||
identities: identityProvider().provider,
|
||||
transport: transportFixture().transport,
|
||||
limits,
|
||||
}),
|
||||
ClusterPluginPackageManagementHttpConfigurationError,
|
||||
);
|
||||
} finally {
|
||||
privateKey.fill(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('serves health and authenticates before one exact management command', async () => {
|
||||
const fixture = await startFixture();
|
||||
try {
|
||||
const live = await request(fixture.application, {
|
||||
method: 'GET',
|
||||
path: '/livez',
|
||||
body: Buffer.alloc(0),
|
||||
authorization: false,
|
||||
contentType: false,
|
||||
});
|
||||
assert.equal(live.statusCode, 200);
|
||||
assert.equal(live.headers['x-request-id'], 'request-1');
|
||||
assert.equal(live.headers['cache-control'], 'no-store');
|
||||
assert.deepEqual(live.body, { schemaVersion: 1, status: 'live' });
|
||||
const response = await request(fixture.application);
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert.equal(response.body.result.operation, 'plugin-package.inspect');
|
||||
assert.deepEqual(fixture.identities.calls.bind, ['assertion-value']);
|
||||
assert.equal(fixture.identities.calls.authenticate, 1);
|
||||
assert.equal(fixture.transport.calls.length, 1);
|
||||
assert.deepEqual(fixture.transport.calls[0].command, command());
|
||||
assert.deepEqual(fixture.transport.calls[0].principal.subject, {
|
||||
type: 'user',
|
||||
id: 'cluster-reviewer',
|
||||
});
|
||||
} finally {
|
||||
await fixture.application.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('maps durable quota exhaustion to 429 with a bounded Retry-After', async () => {
|
||||
const fixture = await startFixture({
|
||||
transport: transportFixture({
|
||||
async execute() {
|
||||
throw new PluginPackageManagementQuotaExceededError(1_250);
|
||||
},
|
||||
}),
|
||||
});
|
||||
try {
|
||||
const response = await request(fixture.application);
|
||||
assert.equal(response.statusCode, 429);
|
||||
assert.equal(response.headers['retry-after'], '2');
|
||||
assert.equal(response.body.error.code, 'quota_exceeded');
|
||||
} finally {
|
||||
await fixture.application.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('rejects routes, missing authentication and media type before transport', async () => {
|
||||
const fixture = await startFixture();
|
||||
try {
|
||||
assert.equal(
|
||||
(
|
||||
await request(fixture.application, {
|
||||
path: '/api/v3/plugin-packages/unknown',
|
||||
})
|
||||
).statusCode,
|
||||
404,
|
||||
);
|
||||
assert.equal(
|
||||
(await request(fixture.application, { authorization: false })).statusCode,
|
||||
401,
|
||||
);
|
||||
assert.equal(
|
||||
(await request(fixture.application, { contentType: false })).statusCode,
|
||||
415,
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await request(fixture.application, {
|
||||
headers: { expect: '100-continue' },
|
||||
})
|
||||
).statusCode,
|
||||
417,
|
||||
);
|
||||
assert.equal(fixture.transport.calls.length, 0);
|
||||
assert.equal(fixture.identities.calls.authenticate, 1);
|
||||
} finally {
|
||||
await fixture.application.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('fails closed when the live keyset is unavailable', async () => {
|
||||
const identities = identityProvider({
|
||||
authenticate() {
|
||||
throw new ClusterPluginPackageIdentityKeysetUnavailableError();
|
||||
},
|
||||
});
|
||||
const fixture = await startFixture({ identities });
|
||||
try {
|
||||
const response = await request(fixture.application);
|
||||
assert.equal(response.statusCode, 503);
|
||||
assert.equal(response.body.error.code, 'unavailable');
|
||||
assert.equal(fixture.transport.calls.length, 0);
|
||||
} finally {
|
||||
await fixture.application.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('enforces peer limits before authentication with bounded retry evidence', async () => {
|
||||
const fixture = await startFixture({
|
||||
limits: {
|
||||
rateWindowMs: 60_000,
|
||||
peerRequestLimit: 1,
|
||||
globalRequestLimit: 10,
|
||||
maxRateLimitPeers: 2,
|
||||
},
|
||||
});
|
||||
try {
|
||||
assert.equal((await request(fixture.application)).statusCode, 200);
|
||||
const limited = await request(fixture.application);
|
||||
assert.equal(limited.statusCode, 429);
|
||||
assert.equal(limited.body.error.code, 'rate_limited');
|
||||
assert.equal(limited.headers['retry-after'], '60');
|
||||
assert.equal(fixture.identities.calls.authenticate, 1);
|
||||
assert.equal(fixture.transport.calls.length, 1);
|
||||
} finally {
|
||||
await fixture.application.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('bounds request bodies and concurrent management work', async () => {
|
||||
let release;
|
||||
const gate = new Promise((resolvePromise) => {
|
||||
release = resolvePromise;
|
||||
});
|
||||
const fixture = await startFixture({
|
||||
limits: {
|
||||
maxBodyBytes: 1_024,
|
||||
maxConcurrentRequests: 1,
|
||||
},
|
||||
transport: transportFixture({
|
||||
async execute() {
|
||||
await gate;
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
operation: 'plugin-package.inspect',
|
||||
proposal: null,
|
||||
approval: null,
|
||||
};
|
||||
},
|
||||
}),
|
||||
});
|
||||
try {
|
||||
assert.equal(
|
||||
(
|
||||
await request(fixture.application, {
|
||||
body: Buffer.alloc(1_025, 0x20),
|
||||
})
|
||||
).statusCode,
|
||||
413,
|
||||
);
|
||||
const first = request(fixture.application);
|
||||
await new Promise((resolvePromise) => setImmediate(resolvePromise));
|
||||
const overloaded = await request(fixture.application);
|
||||
assert.equal(overloaded.statusCode, 503);
|
||||
assert.equal(overloaded.body.error.code, 'overloaded');
|
||||
release();
|
||||
assert.equal((await first).statusCode, 200);
|
||||
} finally {
|
||||
release();
|
||||
await fixture.application.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('withdraws readiness without killing liveness and closes idempotently', async () => {
|
||||
const fixture = await startFixture();
|
||||
fixture.application.withdraw(new Error('database unavailable'));
|
||||
assert.equal(fixture.application.availabilityStatus(), 'unavailable');
|
||||
assert.equal(
|
||||
(
|
||||
await request(fixture.application, {
|
||||
method: 'GET',
|
||||
path: '/readyz',
|
||||
body: Buffer.alloc(0),
|
||||
authorization: false,
|
||||
contentType: false,
|
||||
})
|
||||
).statusCode,
|
||||
503,
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await request(fixture.application, {
|
||||
method: 'GET',
|
||||
path: '/livez',
|
||||
body: Buffer.alloc(0),
|
||||
authorization: false,
|
||||
contentType: false,
|
||||
})
|
||||
).statusCode,
|
||||
200,
|
||||
);
|
||||
assert.equal((await request(fixture.application)).statusCode, 503);
|
||||
await Promise.all([fixture.application.close(), fixture.application.close()]);
|
||||
assert.equal(fixture.application.availabilityStatus(), 'stopped');
|
||||
});
|
||||
Reference in New Issue
Block a user