Stabilize diagnostic export and preserve authorized policy continuity
This commit is contained in:
1 parent
0a5df7398e
commit
09bf616d98
18 files changed
+364
-57
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import test from 'node:test';import assert from 'node:assert/strict';import {mkdtempSync,rmSync,readdirSync,readFileSync} from 'node:fs';import {tmpdir} from 'node:os';import {join,resolve} from 'node:path';import {spawn} from 'node:child_process';import {createServer} from 'node:net';
|
||||
import test from 'node:test';import assert from 'node:assert/strict';import {mkdtempSync,rmSync,readdirSync,readFileSync} from 'node:fs';import {tmpdir} from 'node:os';import {join,resolve} from 'node:path';import {spawn} from 'node:child_process';import {createServer} from 'node:net';import {request} from 'node:http';
|
||||
test('actual Windows protected collector rotates encrypted exports and rejects oversized storage without acknowledgement',{skip:process.platform!=='win32'},async t=>{
|
||||
const root=mkdtempSync(join(tmpdir(),'safetynet-collector-http-'));const probe=createServer();await new Promise(r=>probe.listen(0,'127.0.0.1',r));const port=probe.address().port;await new Promise(r=>probe.close(r));
|
||||
const child=spawn(process.execPath,[resolve('scripts/debug-collector.mjs')],{windowsHide:true,stdio:'pipe',env:{...process.env,DEBUG_COLLECTOR_DIRECTORY:root,DEBUG_COLLECTOR_PORT:String(port),DEBUG_COLLECTOR_SEGMENT_BYTES:'1024',DEBUG_COLLECTOR_RETIRED_SEGMENTS:'1'}});
|
||||
let child=spawn(process.execPath,[resolve('scripts/debug-collector.mjs')],{windowsHide:true,stdio:'pipe',env:{...process.env,DEBUG_COLLECTOR_DIRECTORY:root,DEBUG_COLLECTOR_PORT:String(port),DEBUG_COLLECTOR_SEGMENT_BYTES:'1024',DEBUG_COLLECTOR_RETIRED_SEGMENTS:'1',DEBUG_COLLECTOR_MAX_REQUESTS:'2',DEBUG_COLLECTOR_MAX_BUFFERED_BYTES:'4096'}});
|
||||
let output='';child.stdout.on('data',b=>output+=b);child.stderr.on('data',b=>output+=b);t.after(async()=>{if(child.exitCode===null){child.kill();await new Promise(r=>child.once('exit',r));}assert.ok(root.startsWith(join(tmpdir(),'safetynet-collector-http-')));rmSync(root,{recursive:true,force:true});});
|
||||
const base=`http://127.0.0.1:${port}`;let health;
|
||||
for(let i=0;i<80;i++){if(child.exitCode!==null)throw Error('Collector test process failed: '+output);try{health=await (await fetch(base+'/health')).json();break;}catch{await new Promise(r=>setTimeout(r,100));}}
|
||||
@@ -9,6 +9,12 @@ test('actual Windows protected collector rotates encrypted exports and rejects o
|
||||
for(let i=0;i<12;i++)assert.equal((await post({resourceLogs:[{scopeLogs:[{logRecords:[{body:{stringValue:marker}}]}]}]})).status,200);
|
||||
health=await (await fetch(base+'/api/summary')).json();assert.equal(health.requests,12);assert.equal(health.counts.logs,12);assert.ok(health.rotations>1);assert.ok(health.droppedSegments>0);assert.equal(health.retiredSegments,1);assert.ok(health.activeBytes+health.retiredBytes<=2048);
|
||||
for(const name of readdirSync(root).filter(n=>n.endsWith('.ndjson'))){const content=readFileSync(join(root,name),'utf8');assert.equal(content.includes(marker),false);for(const line of content.trim().split('\n')){const envelope=JSON.parse(line);assert.equal(envelope.v,1);assert.ok(envelope.nonce&&envelope.tag&&envelope.ciphertext);}}
|
||||
const waitAdmission=async check=>{for(let n=0;n<80;n++){const value=await (await fetch(base+'/health')).json();if(check(value.admission))return value;await new Promise(resolve=>setTimeout(resolve,10));}throw Error('Collector admission state timed out');};
|
||||
const hold=()=>{let respond;const response=new Promise(resolve=>{respond=resolve;}),req=request(base+'/v1/logs',{method:'POST',headers:{'content-type':'application/json','transfer-encoding':'chunked'}},res=>{let body='';res.on('data',chunk=>body+=chunk);res.on('end',()=>respond({status:res.statusCode,body}));});req.on('error',()=>{});req.write(' ');return {req,response};};
|
||||
const a=hold(),b=hold();await waitAdmission(value=>value.activeRequests===2);
|
||||
const busy=await post({resourceLogs:[]});assert.equal(busy.status,503);assert.deepEqual(await busy.json(),{error:'Collector busy'});a.req.destroy();b.req.destroy();await waitAdmission(value=>value.activeRequests===0&&value.bufferedBytes===0);
|
||||
const c=hold();c.req.write(' '.repeat(2999));await waitAdmission(value=>value.bufferedBytes===3000);const d=hold();d.req.write(' '.repeat(1099));const overflow=await d.response;assert.equal(overflow.status,503);assert.deepEqual(JSON.parse(overflow.body),{error:'Collector busy'});c.req.destroy();d.req.destroy();
|
||||
health=await waitAdmission(value=>value.activeRequests===0&&value.bufferedBytes===0);assert.equal(health.requests,12);assert.equal(health.admission.requestLimitRejected,1);assert.equal(health.admission.bufferLimitRejected,1);assert.ok(health.admission.peakBufferedBytes<=4096);assert.ok(health.admission.maxQueueWaitMs>=0);assert.ok(health.admission.maxCommitMs>=0);assert.ok(health.eventLoopLag.maxMs>=0);
|
||||
assert.equal((await post({large:'x'.repeat(2048)})).status,507);health=await (await fetch(base+'/health')).json();assert.equal(health.requests,12);assert.equal(health.rejectedWrites,1);
|
||||
const before=structuredClone(health);const tracePost=body=>fetch(base+'/v1/traces',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(body)});
|
||||
const span={traceId:'1234567890abcdef1234567890abcdef',spanId:'1234567890abcdef',name:'synthetic.stage',startTimeUnixNano:'10',endTimeUnixNano:'11'};
|
||||
@@ -16,4 +22,8 @@ test('actual Windows protected collector rotates encrypted exports and rejects o
|
||||
assert.equal((await tracePost({resourceSpans:{}})).status,400);assert.equal((await post({resourceLogs:[{scopeLogs:{}}]})).status,400);
|
||||
health=await (await fetch(base+'/health')).json();for(const field of ['requests','storedBytes','activeBytes','legacyBytes','retiredBytes','rotations','droppedSegments','droppedBytes','rejectedWrites','retainedSpans'])assert.equal(health[field],before[field]);assert.deepEqual(health.counts,before.counts);
|
||||
assert.equal((await tracePost({resourceSpans:[]})).status,200);assert.equal((await tracePost({resourceSpans:[{scopeSpans:[{spans:[span]}]}]})).status,200);const trace=await (await fetch(base+'/api/trace/'+span.traceId)).json();assert.equal(trace.spans[0].durationMs,0.000001);
|
||||
const beforeRestart=await (await fetch(base+'/health')).json();child.kill();await new Promise(resolve=>child.once('exit',resolve));
|
||||
child=spawn(process.execPath,[resolve('scripts/debug-collector.mjs')],{windowsHide:true,stdio:'pipe',env:{...process.env,DEBUG_COLLECTOR_DIRECTORY:root,DEBUG_COLLECTOR_PORT:String(port),DEBUG_COLLECTOR_SEGMENT_BYTES:'1024',DEBUG_COLLECTOR_RETIRED_SEGMENTS:'1',DEBUG_COLLECTOR_MAX_REQUESTS:'2',DEBUG_COLLECTOR_MAX_BUFFERED_BYTES:'4096'}});child.stdout.on('data',buffer=>output+=buffer);child.stderr.on('data',buffer=>output+=buffer);
|
||||
let restarted;for(let n=0;n<80;n++){if(child.exitCode!==null)throw Error('Protected collector restart failed');try{restarted=await (await fetch(base+'/health')).json();break;}catch{await new Promise(resolve=>setTimeout(resolve,100));}}assert.ok(restarted);assert.equal(restarted.requests,0);for(const field of ['storedBytes','activeBytes','legacyBytes','retiredBytes','retiredSegments'])assert.equal(restarted[field],beforeRestart[field]);
|
||||
|
||||
});
|
||||
Reference in new issue
Block a user