60 lines
4.0 KiB
Python
60 lines
4.0 KiB
Python
"""Run one immutable local qualification lane; retain each attempt and teardown outcome."""
|
|
from pathlib import Path
|
|
import datetime
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
import uuid
|
|
from cleanup import container_absent
|
|
|
|
ROOT=Path(__file__).resolve().parents[5]
|
|
HERE=Path(__file__).resolve().parent
|
|
lane=sys.argv[1];attempt=sys.argv[2] if len(sys.argv)>2 else lane
|
|
assert Path(attempt).name==attempt and attempt not in ['.','..']
|
|
assert lane in ['safetensors','weights-only','cache','parsers','inventory','sidecar']
|
|
out=ROOT/'.artifacts/p9-sec-02/qualified'/attempt
|
|
assert not out.exists(),'Preserve earlier lane evidence.'
|
|
out.mkdir(parents=True)
|
|
runtime=json.loads((HERE/'RUNTIME.json').read_text())
|
|
image=runtime['qualificationImageId']
|
|
name='jt-sec02-'+lane+'-'+uuid.uuid4().hex[:8]
|
|
args=['docker','run','--rm','--name',name,'--label','jobtracker.qualification=p9-sec02','--entrypoint','python','--user','65534:65534','--network','none','--read-only','--cap-drop','ALL','--security-opt','no-new-privileges','--pids-limit','128','--cpus','2','--memory','2g','--memory-swap','2g','--tmpfs','/tmp:rw,nosuid,nodev,size=384m,mode=1777']
|
|
for value in ['HF_HUB_OFFLINE=1','TRANSFORMERS_OFFLINE=1','HF_HUB_DISABLE_TELEMETRY=1','HF_HOME=/tmp/hf-home','OMP_NUM_THREADS=1','MKL_NUM_THREADS=1','OLLAMA_BASE_URL=http://127.0.0.1:11434','OLLAMA_MODEL=','EXTERNAL_AI_ENABLED=0']:
|
|
args+=['-e',value]
|
|
args+=['--mount',f'type=bind,src={HERE},dst=/qualification,readonly','--mount',f'type=bind,src={out},dst=/evidence']
|
|
if lane in ['safetensors','weights-only','cache','parsers']:
|
|
fixture='artifact-bin' if lane=='weights-only' else 'artifact'
|
|
args+=['--mount',f'type=bind,src={ROOT/".artifacts/p9-sec-02"/fixture},dst=/fixture,readonly']
|
|
if lane=='sidecar':args+=['-e','AI_SERVICE_SKIP_MODEL_LOAD=1']
|
|
args+=[image]
|
|
if lane in ['safetensors','weights-only']:args+=['/qualification/probe.py',lane,'/fixture']
|
|
elif lane=='cache':args+=['/qualification/cache_probe.py','/fixture']
|
|
elif lane=='parsers':args+=['/qualification/parser_probe.py']
|
|
elif lane=='inventory':args+=['/qualification/runtime_inventory.py']
|
|
else:args+=['-m','pytest','-q','-o','cache_dir=/tmp/pytest-cache','/app/tests','--junitxml=/evidence/sidecar.xml']
|
|
started=datetime.datetime.now(datetime.timezone.utc).isoformat()
|
|
with (out/'process.log').open('w',encoding='utf-8') as log:
|
|
try:code=subprocess.run(['rtk','proxy',*args],cwd=ROOT,stdout=log,stderr=subprocess.STDOUT,timeout=180).returncode
|
|
except subprocess.TimeoutExpired:
|
|
code=124
|
|
try:
|
|
check=subprocess.run(['rtk','proxy','docker','inspect','--format','{{index .Config.Labels "jobtracker.qualification"}}',name],capture_output=True,text=True,timeout=5)
|
|
if check.returncode==0 and check.stdout.strip()=='p9-sec02':subprocess.run(['rtk','proxy','docker','kill',name],capture_output=True,timeout=10)
|
|
except (OSError,subprocess.TimeoutExpired):pass
|
|
deadline=time.monotonic()+5
|
|
cleanup_error=None
|
|
while True:
|
|
try:absent=container_absent(name)
|
|
except RuntimeError as error:
|
|
absent=None;cleanup_error=str(error);break
|
|
if absent or time.monotonic()>=deadline:break
|
|
time.sleep(.1)
|
|
record={'lane':lane,'attempt':attempt,'startedUtc':started,'finishedUtc':datetime.datetime.now(datetime.timezone.utc).isoformat(),'exitCode':code,'command':args,'localImageId':image,'runtimeImageId':runtime['runtimeImageId'],'containerAbsent':absent,'network':'none','readOnly':True,'user':'65534:65534','memoryLimitBytes':2147483648,'cpuLimit':2,'pidsLimit':128,'wallClockDeadlineSeconds':180,'scope':'Actual locally built source image plus exact test-only extras; no app source mount, production cache, external network, registry push or deployment.'}
|
|
record['cleanupStatus']='ABSENT' if absent else 'UNKNOWN' if absent is None else 'PRESENT'
|
|
record['cleanupError']=cleanup_error
|
|
(out/'process.json').write_text(json.dumps(record,indent=2)+'\n')
|
|
print(json.dumps(record))
|
|
print((out/'process.log').read_text(encoding='utf-8',errors='replace')[-2500:].encode('ascii','replace').decode())
|
|
raise SystemExit(code if absent else 1)
|