71 lines
3.0 KiB
Python
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))
|