mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 19:29:13 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import dayjs from 'dayjs';
|
||||
import config from '../../../config';
|
||||
import { getUniqPath } from '../../../config/util';
|
||||
import { sequelize } from '../../../data';
|
||||
import { logStreamManager } from '../../../shared/logStreamManager';
|
||||
import {
|
||||
ManualPrimaryRuntime,
|
||||
type ManualPrimaryLogFiles,
|
||||
type PreparedManualPrimaryLog,
|
||||
} from '../../application/manualPrimaryRuntime';
|
||||
import type { ManualPrimaryStartInput } from '../../compatibility/manualPrimaryExecutionBridge';
|
||||
import { createLegacyLogOutputRef } from '../../compatibility/legacyLogOutputRef';
|
||||
import type { RuntimeRolloutPolicy } from '../../domain/runtimeRollout';
|
||||
import { PrimaryCronProjection } from '../legacy-sequelize/primaryCronProjection';
|
||||
import { LegacySequelizeProjectedRunRepository } from '../legacy-sequelize/projectedRunRepository';
|
||||
import { LocalProcessExecutor } from '../local-process/localProcessExecutor';
|
||||
import { enableDurableLocalProcessOutput } from '../local-process/durableLocalProcessOutput';
|
||||
import { CompletionReceiptFileStore } from '../fs/completionReceiptFileStore';
|
||||
import type { CompletionReceiptJournal } from '../../ports/completionReceiptJournal';
|
||||
|
||||
export const DEFAULT_COMPLETION_RECEIPT_ROOT = path.join(
|
||||
config.dataPath,
|
||||
'runtime',
|
||||
'completion-receipts',
|
||||
);
|
||||
export const DEFAULT_LOCAL_PROCESS_LAUNCHER_PATH = path.join(
|
||||
config.rootPath,
|
||||
'shell',
|
||||
'ql3-launcher.sh',
|
||||
);
|
||||
|
||||
function isWithin(root: string, candidate: string): boolean {
|
||||
return candidate === root || candidate.startsWith(root + path.sep);
|
||||
}
|
||||
|
||||
function relativeLogDirectory(root: string, value: string): string {
|
||||
const candidate = path.isAbsolute(value) ? value : path.resolve(root, value);
|
||||
if (!isWithin(root, candidate)) {
|
||||
throw new Error('Manual Primary log directory escapes the configured root');
|
||||
}
|
||||
const relative = path.relative(root, candidate).split(path.sep).join('/');
|
||||
if (!relative || relative === '.') {
|
||||
throw new Error('Manual Primary log directory must be below the log root');
|
||||
}
|
||||
return relative;
|
||||
}
|
||||
|
||||
export class LegacyManualPrimaryLogFiles implements ManualPrimaryLogFiles {
|
||||
private readonly completionReceipts: CompletionReceiptFileStore;
|
||||
private readonly completionReceiptRoot: string;
|
||||
|
||||
constructor(
|
||||
private readonly logRoot = path.resolve(config.logPath),
|
||||
completionReceiptRoot = DEFAULT_COMPLETION_RECEIPT_ROOT,
|
||||
private readonly completionReceiptJournal?: Pick<
|
||||
CompletionReceiptJournal,
|
||||
'resolve'
|
||||
>,
|
||||
) {
|
||||
this.completionReceiptRoot = path.resolve(completionReceiptRoot);
|
||||
this.completionReceipts = new CompletionReceiptFileStore(
|
||||
this.completionReceiptRoot,
|
||||
);
|
||||
}
|
||||
|
||||
async prepare(
|
||||
input: ManualPrimaryStartInput,
|
||||
): Promise<PreparedManualPrimaryLog> {
|
||||
const configured =
|
||||
!input.cron.logName || input.cron.logName === '/dev/null'
|
||||
? await getUniqPath(input.cron.command, String(input.cron.id))
|
||||
: input.cron.logName;
|
||||
const directory = relativeLogDirectory(this.logRoot, configured);
|
||||
const logPath = path.posix.join(
|
||||
directory,
|
||||
dayjs(input.acceptedAtMs).format('YYYY-MM-DD-HH-mm-ss-SSS') + '.log',
|
||||
);
|
||||
createLegacyLogOutputRef(logPath);
|
||||
const absolutePath = path.resolve(this.logRoot, ...logPath.split('/'));
|
||||
if (!isWithin(this.logRoot, absolutePath)) {
|
||||
throw new Error('Manual Primary log file escapes the configured root');
|
||||
}
|
||||
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
||||
const output = enableDurableLocalProcessOutput(
|
||||
{
|
||||
async write(output) {
|
||||
await logStreamManager.write(
|
||||
absolutePath,
|
||||
Buffer.from(output.chunk).toString('utf8'),
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
outputFilePath: absolutePath,
|
||||
completionReceiptRoot: this.completionReceiptRoot,
|
||||
},
|
||||
);
|
||||
const completionReceipts = this.completionReceipts;
|
||||
const completionReceiptJournal = this.completionReceiptJournal;
|
||||
return {
|
||||
logPath,
|
||||
output,
|
||||
async completionCommitted(attemptId) {
|
||||
await completionReceipts.remove(attemptId);
|
||||
await completionReceiptJournal?.resolve(attemptId);
|
||||
},
|
||||
async close() {
|
||||
await logStreamManager.closeStream(absolutePath);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** Legacy factory retained for focused tests; production activation uses the
|
||||
* shared lifecycle stack in defaultManualPrimaryActivation.ts. */
|
||||
export function createDefaultManualPrimaryRuntime(
|
||||
rollout: RuntimeRolloutPolicy,
|
||||
): ManualPrimaryRuntime {
|
||||
const repository = new LegacySequelizeProjectedRunRepository(sequelize, [
|
||||
new PrimaryCronProjection(sequelize),
|
||||
]);
|
||||
return new ManualPrimaryRuntime(
|
||||
repository,
|
||||
new LocalProcessExecutor({
|
||||
durableLauncherPath: DEFAULT_LOCAL_PROCESS_LAUNCHER_PATH,
|
||||
}),
|
||||
rollout,
|
||||
new LegacyManualPrimaryLogFiles(),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user