Files
archy/tests/regression/node-cutover-custody.py

74 lines
4.4 KiB
Python

"""Install and recover the real custody owner in a disposable PID1 realm."""
import json
import hashlib
import os
from pathlib import Path
import time
def qualify(c):
assert os.geteuid()==0 and Path('/run/.containerenv').exists()
run,write,dump=c['run'],c['write'],c['dump']
root=Path('/var/lib/archipelago');root.mkdir(mode=0o755,exist_ok=True)
assert not list(root.iterdir()),'new disposable data root required'
run('mount','-t','tmpfs','-o','size=64m,mode=0755','c01-delivery-data',str(root))
os.chown(root,1000,1000)
support=root/'support';support.mkdir(mode=0o755)
write(support/'old-receipt',b'preserved disposable evidence',0o644)
from os_update_mount_custody import identity
fd=os.open(support,os.O_PATH|os.O_DIRECTORY)
try:expected=identity(fd)
finally:os.close(fd)
appointment=Path('/etc/archipelago-os-custody-appointment.json')
dump(appointment,{'version':1,'node':os.uname().nodename,'source':str(support),
'source_identity':expected,'alias':'/var/lib/archipelago-delivery-support',
'os_root':'/var/lib/archipelago-os-update','stage_name':'delivery-os-custody'})
installed=run('/usr/bin/python3','-B',str(c['PACKAGE']/'scripts/provision-os-custody.py'),
'--appointment',str(appointment),'--sha256',hashlib.sha256(appointment.read_bytes()).hexdigest())
dump(c['EVIDENCE']/'durable-os-custody-install.json',json.loads(installed.stdout))
run('systemctl','daemon-reload');run('systemctl','start','archipelago-os-custody.service')
first=json.loads(Path('/var/lib/archipelago-delivery-coordinator/mounts.json').read_text())
target=Path('/var/lib/archipelago-os-update');marker=target/'preserved-journal'
write(marker,b'never reset',0o600);before=marker.stat()
run('systemctl','stop','archipelago-os-custody.service')
run('umount',str(target));run('umount','/var/lib/archipelago-delivery-support')
run('systemctl','start','archipelago-os-custody.service')
after=marker.stat()
assert (before.st_dev,before.st_ino,before.st_uid,before.st_gid)==(after.st_dev,after.st_ino,0,0)
assert marker.read_bytes()==b'never reset'
recovered=json.loads(Path('/var/lib/archipelago-delivery-coordinator/mounts.json').read_text())
assert recovered['objects']==first['objects'] and recovered['sequence']>first['sequence']
# Root consumer tied to the live owner; product drop-in uses identical unit semantics.
c['unit']('c01-custody-consumer.service','[Unit]\nBindsTo=archipelago-os-custody.service\nAfter=archipelago-os-custody.service\n'
'[Service]\nExecStart=/usr/bin/sleep infinity\nRestart=no\n')
run('systemctl','daemon-reload');run('systemctl','start','c01-custody-consumer.service')
pid=os.fork()
if pid==0:
try:
os.setgroups([]);os.setgid(1000);os.setuid(1000)
(root/'runtime-positive').write_text('ok')
try:(target/'forbidden').write_text('bad')
except PermissionError:pass
else:os._exit(10)
support.rename(root/'retained-support');support.mkdir();os._exit(0)
except BaseException:os._exit(11)
assert os.waitpid(pid,0)[1]==0
end=time.monotonic()+10
while time.monotonic()<end:
state=run('systemctl','show','c01-custody-consumer.service','-p','MainPID','--value').stdout.strip()
if state==b'0':break
time.sleep(.1)
else:raise AssertionError('custody loss did not retire dependent consumer')
failed=run('systemctl','start','archipelago-os-custody.service',check=False)
assert failed.returncode!=0 and marker.read_bytes()==b'never reset'
# Test-owned path restored by root with the original inode, then a new
# explicit start; no product search/adoption of renamed sources is allowed.
support.rmdir();(root/'retained-support').rename(support)
run('systemctl','reset-failed','archipelago-os-custody.service')
run('systemctl','start','archipelago-os-custody.service')
result={'passed':True,'actual_systemd_owner':True,'mount_state_recovered_in_new_process':True,
'uid1000_substitution_revoked_owner':True,'dependent_consumer_retired':True,'existing_journal_preserved':True,
'original_data_uid_gid':[root.stat().st_uid,root.stat().st_gid],'objects':recovered['objects'],
'scope':'disposable PID1 data-only coordinator integration; no installed private data'}
dump(c['EVIDENCE']/'durable-os-custody.json',result)
return result