mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): add local run log retention
This commit is contained in:
@@ -367,6 +367,7 @@ test('coalesces completion notifications and owns one idempotent shutdown drain'
|
||||
let scans = 0;
|
||||
let drains = 0;
|
||||
let cleanups = 0;
|
||||
let retentionSweeps = 0;
|
||||
const lifecycle = new LocalExecutionControlLifecycle(
|
||||
{
|
||||
async process(attemptId) {
|
||||
@@ -420,6 +421,25 @@ test('coalesces completion notifications and owns one idempotent shutdown drain'
|
||||
stopTimeoutMs: 1_000,
|
||||
maxDrainPages: 1,
|
||||
clock: { now: () => 100 },
|
||||
artifactRetention: {
|
||||
async sweep() {
|
||||
retentionSweeps += 1;
|
||||
return {
|
||||
status: 'complete',
|
||||
pressure: false,
|
||||
observedAtMs: 100,
|
||||
retentionMs: 60_000,
|
||||
availableBytes: '100',
|
||||
totalBytes: '100',
|
||||
candidatesScanned: 0,
|
||||
deletionsAttempted: 0,
|
||||
recordsWritten: 0,
|
||||
failedCandidates: 0,
|
||||
bytesReclaimed: 0,
|
||||
entries: [],
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
assert.equal(lifecycle.notifyCompletion(IDS.completionAttempt), true);
|
||||
@@ -428,10 +448,12 @@ test('coalesces completion notifications and owns one idempotent shutdown drain'
|
||||
assert.deepEqual(completions, [IDS.completionAttempt]);
|
||||
assert.equal(scans, 1);
|
||||
assert.equal(cleanups, 1);
|
||||
assert.equal(retentionSweeps, 1);
|
||||
const first = lifecycle.stopAndDrain();
|
||||
const second = lifecycle.stopAndDrain();
|
||||
assert.equal(first, second);
|
||||
assert.equal((await first).status, 'stopped');
|
||||
assert.equal(drains, 1);
|
||||
assert.equal(cleanups, 2);
|
||||
assert.equal(retentionSweeps, 1);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { test } = require('node:test');
|
||||
|
||||
const {
|
||||
LocalRunAttemptLogCapacityProbe,
|
||||
LocalRunAttemptLogRetirementError,
|
||||
LocalRunAttemptLogRetirementStore,
|
||||
} = require('../dist/artifact-read/localRunAttemptLogRetirementStore.js');
|
||||
|
||||
const ARTIFACT_ID = `local-${'a'.repeat(30)}`;
|
||||
|
||||
function fixture(t) {
|
||||
const parent = fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-log-retire-'));
|
||||
const root = path.join(parent, 'artifacts');
|
||||
fs.mkdirSync(root, { mode: 0o700 });
|
||||
fs.chmodSync(root, 0o700);
|
||||
const directory = path.join(root, 'aa');
|
||||
fs.mkdirSync(directory, { mode: 0o700 });
|
||||
fs.chmodSync(directory, 0o700);
|
||||
t.after(() => fs.rmSync(parent, { recursive: true, force: true }));
|
||||
return {
|
||||
parent,
|
||||
root,
|
||||
directory,
|
||||
target: path.join(directory, `${ARTIFACT_ID}.log`),
|
||||
fact: path.join(directory, `.${ARTIFACT_ID}.log.truncated.json`),
|
||||
};
|
||||
}
|
||||
|
||||
function candidate() {
|
||||
return {
|
||||
projectId: 'prj_default',
|
||||
runId: 'run_1',
|
||||
attemptId: 'attempt_1',
|
||||
logArtifactId: ARTIFACT_ID,
|
||||
executorType: 'local_process',
|
||||
finishedAtMs: 1,
|
||||
};
|
||||
}
|
||||
|
||||
function privateFile(filePath, content) {
|
||||
fs.writeFileSync(filePath, content, { mode: 0o600 });
|
||||
fs.chmodSync(filePath, 0o600);
|
||||
}
|
||||
|
||||
function fact() {
|
||||
return JSON.stringify({
|
||||
schemaVersion: 1,
|
||||
runId: 'run_1',
|
||||
attemptId: 'attempt_1',
|
||||
logArtifactId: ARTIFACT_ID,
|
||||
maximumBytes: 64 * 1024,
|
||||
quotaReached: true,
|
||||
observedAtMs: 2,
|
||||
});
|
||||
}
|
||||
|
||||
test('deletes only the exact private log and truncation fact', async (t) => {
|
||||
const value = fixture(t);
|
||||
privateFile(value.target, 'hello');
|
||||
privateFile(value.fact, fact());
|
||||
privateFile(path.join(value.directory, 'unrelated'), 'keep');
|
||||
|
||||
const retired = await new LocalRunAttemptLogRetirementStore(
|
||||
value.root,
|
||||
).retire(candidate());
|
||||
assert.deepEqual(retired, {
|
||||
disposition: 'deleted',
|
||||
byteLength: 5,
|
||||
truncation: {
|
||||
truncated: true,
|
||||
maximumBytes: 64 * 1024,
|
||||
observedAtMs: 2,
|
||||
},
|
||||
});
|
||||
assert.equal(fs.existsSync(value.target), false);
|
||||
assert.equal(fs.existsSync(value.fact), false);
|
||||
assert.equal(fs.existsSync(path.join(value.directory, 'unrelated')), true);
|
||||
});
|
||||
|
||||
test('converges an unlink-before-tombstone crash and removes its orphan fact', async (t) => {
|
||||
const value = fixture(t);
|
||||
privateFile(value.fact, fact());
|
||||
const retired = await new LocalRunAttemptLogRetirementStore(
|
||||
value.root,
|
||||
).retire(candidate());
|
||||
assert.equal(retired.disposition, 'already_absent');
|
||||
assert.equal(retired.byteLength, 0);
|
||||
assert.equal(retired.truncation.truncated, true);
|
||||
assert.equal(fs.existsSync(value.fact), false);
|
||||
});
|
||||
|
||||
test('fails closed for links, unsafe modes and fact identity drift', async (t) => {
|
||||
const cases = [];
|
||||
|
||||
const hardLink = fixture(t);
|
||||
privateFile(hardLink.target, 'hello');
|
||||
fs.linkSync(hardLink.target, path.join(hardLink.directory, 'second-link'));
|
||||
cases.push(hardLink);
|
||||
|
||||
const unsafeMode = fixture(t);
|
||||
privateFile(unsafeMode.target, 'hello');
|
||||
fs.chmodSync(unsafeMode.target, 0o644);
|
||||
cases.push(unsafeMode);
|
||||
|
||||
const drift = fixture(t);
|
||||
privateFile(drift.target, 'hello');
|
||||
privateFile(drift.fact, fact().replace('"attempt_1"', '"attempt_other"'));
|
||||
cases.push(drift);
|
||||
|
||||
for (const value of cases) {
|
||||
await assert.rejects(
|
||||
new LocalRunAttemptLogRetirementStore(value.root).retire(candidate()),
|
||||
LocalRunAttemptLogRetirementError,
|
||||
);
|
||||
assert.equal(fs.existsSync(value.target), true);
|
||||
}
|
||||
});
|
||||
|
||||
test('capacity probe uses the nearest existing parent without creating roots', async (t) => {
|
||||
const value = fixture(t);
|
||||
const missing = path.join(value.parent, 'future', 'artifacts');
|
||||
const snapshot = await new LocalRunAttemptLogCapacityProbe(missing).inspect();
|
||||
assert.equal(snapshot.totalBytes > 0n, true);
|
||||
assert.equal(snapshot.availableBytes >= 0n, true);
|
||||
assert.equal(snapshot.availableBytes <= snapshot.totalBytes, true);
|
||||
assert.equal(fs.existsSync(missing), false);
|
||||
});
|
||||
Reference in New Issue
Block a user