mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {
|
||||
startProductionClusterControlApplication,
|
||||
} = require('../packages/ql3-cluster-control/dist/application-runtime/productionApplication.js');
|
||||
|
||||
const REQUIRED_ENVIRONMENT = Object.freeze([
|
||||
'QL3_HA_DATABASE_URL',
|
||||
'QL3_HA_REPLICA_ID',
|
||||
'QL3_HA_APPLICATION_NAME',
|
||||
]);
|
||||
const HA_SCHEDULER_MISFIRE_GRACE_MS = 5 * 60_000;
|
||||
|
||||
let application;
|
||||
let stopping;
|
||||
|
||||
function send(message) {
|
||||
if (typeof process.send === 'function' && process.connected) {
|
||||
process.send(message);
|
||||
}
|
||||
}
|
||||
|
||||
function requiredEnvironment(name) {
|
||||
const value = process.env[name];
|
||||
if (!value) throw new Error(`missing ${name}`);
|
||||
return value;
|
||||
}
|
||||
|
||||
function addressRecord(address) {
|
||||
return Object.freeze({
|
||||
host: address.host,
|
||||
port: address.port,
|
||||
});
|
||||
}
|
||||
|
||||
async function stop() {
|
||||
stopping ??= (async () => {
|
||||
if (application) await application.stop();
|
||||
application = undefined;
|
||||
})();
|
||||
return stopping;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
for (const name of REQUIRED_ENVIRONMENT) requiredEnvironment(name);
|
||||
const replicaId = requiredEnvironment('QL3_HA_REPLICA_ID');
|
||||
const databaseUrl = requiredEnvironment('QL3_HA_DATABASE_URL');
|
||||
const applicationName = requiredEnvironment('QL3_HA_APPLICATION_NAME');
|
||||
|
||||
application = await startProductionClusterControlApplication({
|
||||
config: {
|
||||
enabled: true,
|
||||
profile: 'cluster-control',
|
||||
http: {
|
||||
host: '127.0.0.1',
|
||||
port: 0,
|
||||
drainTimeoutMs: 2_000,
|
||||
},
|
||||
database: {
|
||||
connection: {
|
||||
connectionString: databaseUrl,
|
||||
tls: { mode: 'disable' },
|
||||
},
|
||||
pool: {
|
||||
applicationName,
|
||||
maxConnections: 1,
|
||||
connectionTimeoutMs: 2_000,
|
||||
},
|
||||
},
|
||||
security: {
|
||||
apiCredentialPepper: 'A'.repeat(43),
|
||||
},
|
||||
},
|
||||
recovery: {
|
||||
ownerId: replicaId,
|
||||
providers: [],
|
||||
claimLimit: 8,
|
||||
maxStartupPasses: 4,
|
||||
},
|
||||
scheduler: {
|
||||
// The fixture deliberately performs promotion, rewind and synchronous
|
||||
// rejoin between the original claim and the takeover observation.
|
||||
misfireGraceMs: HA_SCHEDULER_MISFIRE_GRACE_MS,
|
||||
},
|
||||
audit() {},
|
||||
});
|
||||
if (application.status !== 'active') {
|
||||
throw new Error('HA replica unexpectedly remained disabled');
|
||||
}
|
||||
send({
|
||||
type: 'ready',
|
||||
replicaId,
|
||||
processId: process.pid,
|
||||
applicationName,
|
||||
address: addressRecord(application.address),
|
||||
availability: application.availabilityStatus(),
|
||||
evidence: {
|
||||
contractName: application.evidence.contractName,
|
||||
contractVersion: application.evidence.contractVersion,
|
||||
serverMajor: application.evidence.serverMajor,
|
||||
},
|
||||
});
|
||||
|
||||
process.on('message', async (message) => {
|
||||
if (!message || typeof message !== 'object') return;
|
||||
const requestId = message.requestId;
|
||||
try {
|
||||
if (message.type === 'status') {
|
||||
send({
|
||||
type: 'status',
|
||||
requestId,
|
||||
replicaId,
|
||||
availability: application?.availabilityStatus() ?? 'stopped',
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (message.type === 'stop') {
|
||||
await stop();
|
||||
send({ type: 'stopped', requestId, replicaId });
|
||||
setImmediate(() => process.exit(0));
|
||||
}
|
||||
} catch (error) {
|
||||
send({
|
||||
type: 'request-error',
|
||||
requestId,
|
||||
replicaId,
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (const signal of ['SIGINT', 'SIGTERM']) {
|
||||
process.once(signal, () => {
|
||||
void stop().finally(() => process.exit(0));
|
||||
});
|
||||
}
|
||||
|
||||
process.once('disconnect', () => {
|
||||
void stop().finally(() => process.exit(0));
|
||||
});
|
||||
|
||||
main().catch((error) => {
|
||||
send({
|
||||
type: 'fatal',
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
process.stderr.write(
|
||||
`ql3 PostgreSQL HA replica failed: ${
|
||||
error instanceof Error ? error.stack ?? error.message : String(error)
|
||||
}\n`,
|
||||
);
|
||||
setImmediate(() => process.exit(1));
|
||||
});
|
||||
Reference in New Issue
Block a user