mirror of
https://github.com/whyour/qinglong.git
synced 2026-11-02 15:56:59 +08:00
feat: ship verified platform upgrades with channel bundle releases
Package production dependencies per architecture for online and offline upgrades. Validate dependencies and isolated backend startup before readiness, repair once when permitted, and preserve service/data through failed preparation or rollback. Publish release bundles with master images and expose an opt-in develop build sharing the development index across Alpine and Debian images.
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const { EventEmitter } = require('node:events');
|
||||
const ts = require('typescript');
|
||||
|
||||
const source = ts.createSourceFile(
|
||||
'app.ts',
|
||||
fs.readFileSync('back/app.ts', 'utf8'),
|
||||
ts.ScriptTarget.Latest,
|
||||
true,
|
||||
);
|
||||
const application = source.statements.find(
|
||||
(node) => ts.isClassDeclaration(node) && node.name.text === 'Application',
|
||||
);
|
||||
const method = application.members.find(
|
||||
(node) => node.name?.getText(source) === 'startMasterProcess',
|
||||
);
|
||||
const compiled = ts.transpileModule(
|
||||
`class Application { ${method.getText(
|
||||
source,
|
||||
)} }\nmodule.exports = Application;`,
|
||||
{
|
||||
compilerOptions: {
|
||||
module: ts.ModuleKind.CommonJS,
|
||||
target: ts.ScriptTarget.ES2020,
|
||||
},
|
||||
},
|
||||
).outputText;
|
||||
|
||||
for (const [arch, armVersion, timeout] of [
|
||||
['arm', 6, 120000],
|
||||
['arm', 7, 30000],
|
||||
['arm64', undefined, 30000],
|
||||
]) {
|
||||
test(`${arch} v${
|
||||
armVersion ?? ''
|
||||
} waits for scheduler readiness before HTTP and job recovery`, async () => {
|
||||
const cluster = new EventEmitter();
|
||||
const module = { exports: {} };
|
||||
const logger = { info() {}, warn() {}, error() {} };
|
||||
new Function(
|
||||
'module',
|
||||
'cluster',
|
||||
'Logger',
|
||||
'errStack',
|
||||
'process',
|
||||
compiled,
|
||||
)(module, cluster, logger, String, {
|
||||
arch,
|
||||
config: { variables: { arm_version: armVersion } },
|
||||
exit() {
|
||||
assert.fail('unexpected application exit');
|
||||
},
|
||||
});
|
||||
const app = new module.exports();
|
||||
app.workerMetadataMap = new Map();
|
||||
const forks = [],
|
||||
waits = [],
|
||||
notifications = [];
|
||||
let ready;
|
||||
app.forkWorker = (serviceType) => {
|
||||
const worker = {
|
||||
id: forks.length + 1,
|
||||
process: { pid: 100 + forks.length },
|
||||
send: (message) => notifications.push(message),
|
||||
};
|
||||
forks.push({ serviceType, worker });
|
||||
app.workerMetadataMap.set(worker.id, { serviceType });
|
||||
return worker;
|
||||
};
|
||||
app.waitForWorkerReady = (worker, milliseconds) => {
|
||||
waits.push({ worker, milliseconds });
|
||||
return new Promise((resolve) => {
|
||||
ready = resolve;
|
||||
});
|
||||
};
|
||||
app.setupMasterShutdown = () => {};
|
||||
app.startMasterProcess();
|
||||
assert.deepEqual(
|
||||
forks.map((fork) => fork.serviceType),
|
||||
['grpc'],
|
||||
);
|
||||
assert.equal(waits[0].milliseconds, timeout);
|
||||
ready();
|
||||
await Promise.resolve();
|
||||
assert.deepEqual(
|
||||
forks.map((fork) => fork.serviceType),
|
||||
['grpc', 'http'],
|
||||
);
|
||||
cluster.emit('exit', forks[0].worker, 1);
|
||||
assert.equal(waits[1].milliseconds, timeout);
|
||||
assert.deepEqual(notifications, ['scheduler-unavailable']);
|
||||
ready();
|
||||
await Promise.resolve();
|
||||
assert.deepEqual(notifications, [
|
||||
'scheduler-unavailable',
|
||||
'reregister-crons',
|
||||
]);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user