⋯ 50 lines Show the 50 lines above this hunk @@ -51,17 +51,20 @@ async function workerInput(path: string) { 51 51 const item = object ( await Bun . file ( path ) . json ( ) ) , marker = object ( await Bun . file ( join ( root , 'fixture.json' ) ) . json ( ) ) 52 52 assert . equal ( marker . kind , 'host-full-volume-v1' ) ; assert . equal ( item . nonce , marker . nonce ) 53 53 assert . equal ( item . namespace , namespace ) ; identity ( item . nonce , 32 ) 54 + assert . ok ( item . imageWriteBudgetBytes === 0 || item . imageWriteBudgetBytes === reserveBytes ) 55 + assert . equal ( item . imageWriteBudgetBytes , marker . imageWriteBudgetBytes ) 54 56 assert . equal ( readFileSync ( join ( volume , 'nonce' ) , 'utf8' ) , item . nonce ) 55 57 checkedVolume ( volume ) 56 58 const reference = object ( item . reference ) 57 59 assert . equal ( reference . directory , join ( volume , 'store' ) ) ; assert . equal ( reference . enrollmentDirectory , join ( root , 'enrollment' ) ) 58 60 assert . equal ( item . project , join ( root , 'project' ) ) ; assert . equal ( item . repository , join ( root , 'repository' ) ) 59 - return { root , volume , project : text ( item . project ) , repository : text ( item . repository ) , candidateId : identity ( item . candidateId , 32 ) , trialId : item . trialId === undefined ? undefined : identity ( item . trialId , 32 ) , reference : { 61 + return { root , volume , imageWriteBudgetBytes : item . imageWriteBudgetBytes , project : text ( item . project ) , repository : text ( item . repository ) , candidateId : identity ( item . candidateId , 32 ) , trialId : item . trialId === undefined ? undefined : identity ( item . trialId , 32 ) , reference : { 60 62 directory : text ( reference . directory ) , enrollmentDirectory : text ( reference . enrollmentDirectory ) , enrollmentSha256 : identity ( reference . enrollmentSha256 , 64 ) , storeSha256 : identity ( reference . storeSha256 , 64 ) , 61 63 } } 62 64 } 63 65 64 - function fillVolume ( volume : string ) { 66 + function fillVolume ( volume : string , imageWriteBudgetBytes : number ) { 67 + assert . ok ( imageWriteBudgetBytes === 0 || imageWriteBudgetBytes === reserveBytes ) 65 68 const before = checkedVolume ( volume ) , path = join ( volume , 'filler.bin' ) 66 69 const fd = openSync ( path , constants . O_WRONLY | constants . O_CREAT | constants . O_EXCL | constants . O_NOFOLLOW , 0o600 ) 67 70 let written = 0 , full = false ⋯ 4 lines Show the 4 lines above this hunk @@ -72,10 +75,11 @@ function fillVolume(volume: string) { 72 75 catch ( error ) { assert . ok ( storageErrorCodes ( error ) . includes ( 'ENOSPC' ) ) ; full = true ; break } 73 76 } 74 77 assert . equal ( full , true ) ; assert . equal ( checkedVolume ( volume ) . freeBytes , 0 ) 75 - assert . ok ( written > reserveBytes ) ; ftruncateSync ( fd , written - reserveBytes ) 78 + assert . ok ( written > reserveBytes ) 79 + if ( imageWriteBudgetBytes > 0 ) ftruncateSync ( fd , written - imageWriteBudgetBytes ) 76 80 const after = checkedVolume ( volume ) 77 - assert . equal ( after . freeBytes , reserveBytes ) ; assert . equal ( after . device , before . device ) 78 - return { before , after , written , fillerBytes : written - reserveBytes , fillError : 'ENOSPC' } 81 + assert . equal ( after . freeBytes , imageWriteBudgetBytes ) ; assert . equal ( after . device , before . device ) 82 + return { before , after , written , fillerBytes : written - imageWriteBudgetBytes , fillError : 'ENOSPC' } 79 83 } 80 84 finally { closeSync ( fd ) } 81 85 } ⋯ 4 lines Show the 4 lines above this hunk @@ -86,7 +90,7 @@ async function limited(inputPath: string) { 86 90 try { 87 91 await trialRuntimeFirmwareCandidate ( { ... input . reference , repository : input . repository , project : input . project , candidateId : input . candidateId , clock , async onProgress ( event ) { 88 92 assert . ok ( phases . length < 512 ) ; phases . push ( event . phase ) 89 - if ( event . phase === 'slot-selected' ) await writeFile ( join ( input . root , 'fill.json' ) , JSON . stringify ( fillVolume ( input . volume ) , null , 2 ) ) 93 + if ( event . phase === 'slot-selected' ) await writeFile ( join ( input . root , 'fill.json' ) , JSON . stringify ( fillVolume ( input . volume , input . imageWriteBudgetBytes ) , null , 2 ) ) 90 94 } } ) 91 95 } 92 96 catch ( error ) { rejected = true ; codes = storageErrorCodes ( error ) } ⋯ 28 lines Show the 28 lines above this hunk @@ -121,13 +125,14 @@ async function archiveVolume(volume: string, output: string) { 121 125 } 122 126 123 127 124 - export async function qualifyHostFullVolume ( directory : string ) { 128 + export async function qualifyHostFullVolume ( directory : string , imageWriteBudgetBytes : 0 | 1048576 = reserveBytes ) { 125 129 const namespace = privateNamespace ( ) 130 + assert . ok ( imageWriteBudgetBytes === 0 || imageWriteBudgetBytes === reserveBytes ) 126 131 directory = resolve ( directory ) 127 132 await mkdir ( directory , { mode : 0o700 } ) 128 133 const volume = join ( directory , 'volume' ) , nonce = randomBytes ( 16 ) . toString ( 'hex' ) 129 134 await mkdir ( volume , { mode : 0o700 } ) 130 - await writeFile ( join ( directory , 'fixture.json' ) , JSON . stringify ( { kind : 'host-full-volume-v1' , nonce , namespace } ) , { mode : 0o600 } ) 135 + await writeFile ( join ( directory , 'fixture.json' ) , JSON . stringify ( { kind : 'host-full-volume-v1' , nonce , namespace , imageWriteBudgetBytes } ) , { mode : 0o600 } ) 131 136 async function command ( argv : string [ ] , label : string , timeoutMs = 10000 ) { 132 137 const result = await runBoundedHostProcess ( argv , { cwd : directory , env : { ... process . env , BUN_RUNTIME_TRANSPILER_CACHE_PATH : '0' } , timeoutMs , stdoutBytes : 16384 , stderrBytes : 16384 , label } ) 133 138 assert . equal ( result . exitCode , 0 , result . stderr ) ⋯ 19 lines Show the 19 lines above this hunk @@ -153,7 +158,7 @@ export async function qualifyHostFullVolume(directory: string) { 153 158 const candidate = await reserveRuntimeFirmwareCandidate ( { ... reference , repository : source , clock } ) 154 159 await signed . publish ( { image , version : 3 , custom : { releaseSequence : 9 } , rotateTargets : true } ) 155 160 const inputPath = join ( directory , 'worker-input.json' ) , config = join ( directory , 'worker.bunfig.toml' ) 156 - const input = { nonce , namespace , reference , project , repository : source , candidateId : candidate . candidate ! . id } 161 + const input = { nonce , namespace , imageWriteBudgetBytes , reference , project , repository : source , candidateId : candidate . candidate ! . id } 157 162 await writeFile ( inputPath , JSON . stringify ( input ) , { mode : 0o600 } ) ; await writeFile ( config , 'preload=[]\n' , { mode : 0o600 } ) 158 163 async function worker ( mode : 'limited' | 'recover' | 'abandon' | 'fresh' ) { 159 164 const result = await runBoundedHostProcess ( [ process . execPath , '--no-env-file' , `--config=${config}` , import . meta . path , mode , inputPath ] , { cwd : directory , env : { ... process . env , BUN_RUNTIME_TRANSPILER_CACHE_PATH : '0' } , timeoutMs : 60000 , stdoutBytes : 16384 , stderrBytes : 16384 , label : `Full update filesystem ${mode}` } ) ⋯ 22 lines Show the 22 lines above this hunk @@ -182,7 +187,8 @@ export async function qualifyHostFullVolume(directory: string) { 182 187 const version = ( role : string ) => failed . store . metadata . find ( row => row . role === role ) ! . version 183 188 assert . equal ( version ( 'root' ) , 2 ) ; assert . equal ( version ( 'targets' ) , 3 ) 184 189 const partialPath = join ( reference . directory , 'host-slots' , failed . trial ! . id , 'image.bin' ) , partial = await readFile ( partialPath ) 185 - assert . ok ( partial . length > 0 && partial . length < image . length ) ; assert . deepEqual ( partial , image . subarray ( 0 , partial . length ) ) 190 + assert . equal ( partial . length , imageWriteBudgetBytes ) ; assert . ok ( partial . length < image . length ) 191 + assert . deepEqual ( partial , image . subarray ( 0 , partial . length ) ) 186 192 const previousImage = join ( reference . directory , 'host-slots' , good . knownGood ! . id , 'image.bin' ) 187 193 assert . deepEqual ( await readFile ( previousImage ) , image ) 188 194 await archiveVolume ( volume , join ( directory , 'failed-volume' ) ) ⋯ 19 lines Show the 19 lines above this hunk @@ -208,7 +214,7 @@ export async function qualifyHostFullVolume(directory: string) { 208 214 assert . notEqual ( fresh . knownGood ! . health ! . sessionId , good . knownGood ! . health ! . sessionId ) 209 215 assert . deepEqual ( await readFile ( partialPath ) , partial ) ; assert . deepEqual ( await readFile ( previousImage ) , image ) 210 216 assert . deepEqual ( await readFile ( join ( reference . directory , 'host-slots' , fresh . knownGood ! . id , 'image.bin' ) ) , image ) 211 - const result = { evidence : 'host-actual-enospc-v1' , filesystemType : 'tmpfs' , filesystemBytes : mounted . capacityBytes , freeBytesAfterFailure : fullVolume . freeBytes , sourceRevision , authorizedImageBytes : image . length , authorizedImageSha256 : signed . imageHash , partialImageBytes : partial . length , partialImageSha256 : digest ( partial ) , errorCodes : rejection . errorCodes , interruptedPhase : failed . trial ! . phase , interruptedAttempt : failed . store . attempt ! . phase , acceptedRootVersion : version ( 'root' ) , acceptedTargetsVersion : version ( 'targets' ) , releaseFloor : fresh . store . releaseFloor , abandonedPhase : abandoned . trial ! . phase , previousSlotId : good . knownGood ! . id , failedSlotId : failed . trial ! . id , confirmedSlotId : fresh . knownGood ! . id , previousBootId : good . knownGood ! . health ! . bootId , freshBootId : fresh . knownGood ! . health ! . bootId , physicalDeploymentAllowed : fresh . physicalDeploymentAllowed } 217 + const result = { evidence : 'host-actual-enospc-v1' , filesystemType : 'tmpfs' , filesystemBytes : mounted . capacityBytes , imageWriteBudgetBytes , freeBytesAfterFailure : fullVolume . freeBytes , sourceRevision , authorizedImageBytes : image . length , authorizedImageSha256 : signed . imageHash , partialImageBytes : partial . length , partialImageSha256 : digest ( partial ) , errorCodes : rejection . errorCodes , interruptedPhase : failed . trial ! . phase , interruptedAttempt : failed . store . attempt ! . phase , acceptedRootVersion : version ( 'root' ) , acceptedTargetsVersion : version ( 'targets' ) , releaseFloor : fresh . store . releaseFloor , abandonedPhase : abandoned . trial ! . phase , previousSlotId : good . knownGood ! . id , failedSlotId : failed . trial ! . id , confirmedSlotId : fresh . knownGood ! . id , previousBootId : good . knownGood ! . health ! . bootId , freshBootId : fresh . knownGood ! . health ! . bootId , physicalDeploymentAllowed : fresh . physicalDeploymentAllowed } 212 218 await writeFile ( join ( directory , 'qualification.json' ) , JSON . stringify ( result , null , 2 ) ) 213 219 return result 214 220 }