Files
archy/tests/regression/os-ota-installed-apply.py

71 lines
3.0 KiB
Python

#!/usr/bin/env python3
"""Apply the actual prepared OS stage; cold boot remains a separate invocation."""
import json
import os
from pathlib import Path
import subprocess
import sys
import time
assert 'QEMU' in Path('/sys/class/dmi/id/sys_vendor').read_text()
assert subprocess.check_output(['findmnt', '-n', '-o', 'SOURCE', '/'], text=True).strip() == '/dev/vda'
sys.path.insert(0, '/opt/ota-source/scripts')
import os_update_executor as executor
evidence = Path('/var/log/os-ota-stage')
binding = json.loads((evidence/'binding.json').read_text())
plan = json.loads((evidence/'plan.json').read_text())
before = json.loads((evidence/'canaries-before.json').read_text())
canaries = Path('/var/lib/archipelago/ota-synthetic-canaries')
hashes = lambda: {p.name: executor.file_hash(p) for p in canaries.iterdir()}
assert before == hashes()
# Interrupt the real invoking process after durable completion but before it can
# return a reply. Re-entry must observe completion, never rerun maintainer scripts.
log_path=evidence/'interrupted-apply.log'
if log_path.exists():
log_path.rename(log_path.with_name('interrupted-apply-retained-'+str(time.time_ns())+'.log'))
log = log_path.open('x')
child = subprocess.Popen([sys.executable, '-B', '-c', '''
import json,sys,time
sys.path.insert(0,'/opt/ota-source/scripts')
import os_update_executor as e
b=json.load(open('/var/log/os-ota-stage/binding.json'))
e.stage(b,'APPLY','apply')
time.sleep(600)
'''], stdout=log, stderr=subprocess.STDOUT)
try:
deadline = time.monotonic()+1500
while time.monotonic() < deadline:
status = executor.Executor(binding['os_update']).status
if status['phase'] == 'REBOOT_REQUIRED':
break
if child.poll() is not None:
raise RuntimeError('actual stage failed; inspect interrupted-apply.log')
time.sleep(.2)
else:
raise TimeoutError('actual stage did not complete')
finally:
if child.poll() is None:
child.kill()
child.wait()
log.close()
assert child.returncode == -9
assert status['phase'] == 'REBOOT_REQUIRED'
assert before == hashes()
with (canaries/'wallet.db').open('a') as stream:
stream.write('FORWARD-AFTER-OS-APPLY-sequence-18\n')
after = hashes()
(evidence/'canaries-after.json').write_text(json.dumps(after, sort_keys=True))
inventory = executor.inventory()
assert executor.stage(binding, 'APPLY', 'apply') == status
assert executor.inventory() == inventory and hashes() == after
grub = Path('/boot/grub/grub.cfg').read_text()
assert plan['boot']['running_release'] in grub and plan['boot']['expected_release'] in grub
result = dict(phase=status['phase'], repeat_idempotent=True, canaries_preserved=True,
forward_wallet_write_preserved=True, retained_old_grub_entry=True,
target_kernel=plan['boot']['expected_release'], boot_id=executor.boot_id(),
interrupted_after_stage_exit=child.returncode,
production_approved=False, native_D09_transport=False)
(evidence/'apply-result.json').write_text(json.dumps(result, indent=2)+'\n')
print(json.dumps(result, indent=2))