Files
archy/scripts/tests/run_public_update_pid1.py

122 lines
7.1 KiB
Python

#!/usr/bin/env python3
"""Create a disconnected, mount-free disposable fixture; retain every attempt."""
import hashlib
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import time
source = Path(__file__).resolve().parents[2]
report = Path(sys.argv[1]).resolve()
assert not report.exists() and report.parent.is_dir()
report.mkdir(mode=0o700)
name = 'public-worker-' + report.name
assert all(c.isalnum() or c == '-' for c in name)
immutable_d09 = os.environ.get('ARCHY_TEST_IMMUTABLE_D09') == '1'
def run(*args, **kwargs):
result = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs)
with (report / 'commands.log').open('ab') as log:
log.write(('COMMAND ' + ' '.join(map(str, args)) + '\n').encode())
log.write(result.stdout)
if result.returncode:
raise RuntimeError(f'command failed ({result.returncode}): {args}; retained {report}')
return result.stdout
files = ['scripts/public_update_prepare.py', 'scripts/public_update_runtime.py', 'scripts/public_update_adopt.py', 'scripts/public_update_worker.py',
'scripts/tests/public_update_fixture.c', 'scripts/tests/public_update_worker_pid1.py']
digests = {}
for relative in files:
target = report / Path(relative).name
shutil.copyfile(source / relative, target)
digests[relative] = hashlib.sha256(target.read_bytes()).hexdigest()
(report / 'source-sha256.json').write_text(json.dumps(digests, indent=2))
if immutable_d09:
commit = run('git', '-C', str(source), 'rev-parse', 'v2.0.3-alpha^{commit}').decode().strip()
assert commit == '1a4ea15df9c8800b6d288277d2839c7b93662ff7'
(report / 'immutable-source-commit').write_text(commit)
run('git', '-C', str(source), 'archive', '--format=tar', '--output=' + str(report / 'immutable-source.tar'),
commit, 'scripts', 'image-recipe/configs', 'tests/regression/d09-systemd.py',
'tests/regression/d09-systemd-reboot.py')
else:
for name_part, version, extra in [('old', '1.9.1-alpha', []), ('new', '2.0.4-alpha', []),
('bad', '2.0.4-alpha', ['-DFAIL_START']),
('health', '2.0.4-alpha', ['-DHEALTH_VERSION="wrong-fixture-version"'])]:
run('cc', '-static', '-O2', '-Wall', '-Wextra', f'-DVERSION="{version}"', *extra,
str(report / 'public_update_fixture.c'), '-o', str(report / (name_part + '-backend')))
created = False
engine = ['podman']
extra = []
try:
image = run('podman', 'image', 'inspect', 'localhost/archipelago-rootfs:latest', '--format', '{{.Id}}').decode().strip()
(report / 'image-id').write_text(image)
if sys.argv[2:] == ['--rootful']:
parent = Path(Path('/proc/self/cgroup').read_text().strip().split(':', 2)[2]).parent
assert parent.name.startswith('public-worker-fixture-') and parent.suffix == '.service'
store = report.parent / 'public-worker-store'
run('sudo', '-n', 'install', '-d', '-m', '0700', str(store))
engine = ['sudo', '-n', 'podman', '--root=' + str(store / 'root'), '--runroot=' + str(store / 'run'),
'--storage-driver=overlay', '--cgroup-manager=cgroupfs', '--events-backend=file']
if subprocess.run([*engine, 'image', 'exists', image]).returncode:
run('podman', 'save', '--format', 'oci-archive', '--output', str(report / 'base-image.tar'), image)
run(*engine, 'load', '--input', str(report / 'base-image.tar'))
extra = ['--cgroup-parent=' + str(parent), '--security-opt=apparmor=unconfined']
run(*engine, 'run', '--detach', '--tty', '--pull=never', '--name', name, *extra,
'--hostname', 'd09-qualification' if immutable_d09 else 'public-ota-worker-fixture', '--network=none', '--cgroupns=private',
'--systemd=always', '--cap-add=SYS_ADMIN', '--cap-add=SYS_PTRACE', '--security-opt=unmask=ALL',
'--memory=1g', '--memory-swap=1g', '--cpus=1', '--pids-limit=256',
'--tmpfs', '/run', '--tmpfs', '/tmp', '--entrypoint', '/sbin/init', image,
'--unit=basic.target', '--log-target=console')
created = True
if immutable_d09:
run(*engine, 'exec', name, 'mkdir', '-p', '/opt/d09-fixture')
run(*engine, 'cp', str(report / 'immutable-source.tar'), name + ':/opt/d09-fixture/source.tar')
run(*engine, 'exec', name, 'tar', '-xf', '/opt/d09-fixture/source.tar', '-C', '/opt/d09-fixture')
run(*engine, 'exec', '--env', 'D09_DISPOSABLE_SYSTEMD=1', name, 'python3',
'/opt/d09-fixture/tests/regression/d09-systemd.py', timeout=900)
else:
run(*engine, 'exec', name, 'mkdir', '-p', '/opt/public-fixture')
for filename in ['public_update_prepare.py', 'public_update_runtime.py', 'public_update_adopt.py', 'public_update_worker.py', 'public_update_worker_pid1.py',
'old-backend', 'new-backend', 'bad-backend', 'health-backend']:
run(*engine, 'cp', str(report / filename), name + ':/opt/public-fixture/' + filename)
run(*engine, 'exec', '--env', 'ARCHY_PUBLIC_WORKER_PID1=1',
'--env', 'ARCHY_PUBLIC_WORKER_RUNTIME_ONLY=' + os.environ.get('ARCHY_PUBLIC_WORKER_RUNTIME_ONLY', ''), name,
'/usr/bin/python3', '-B', '/opt/public-fixture/public_update_worker_pid1.py', timeout=900)
run(*engine, 'restart', '--time', '10', name)
for _ in range(120):
probe = subprocess.run([*engine, 'exec', name, 'systemctl', 'show', '-p', 'Version'],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
with (report / 'reboot-bus-readiness.log').open('ab') as stream:
stream.write(probe.stdout)
if probe.returncode == 0:
break
time.sleep(.25)
else:
raise RuntimeError('disposable PID1 bus did not become ready after restart')
if immutable_d09:
run(*engine, 'exec', '--env', 'D09_DISPOSABLE_SYSTEMD=1', name, 'python3',
'/opt/d09-fixture/tests/regression/d09-systemd-reboot.py', timeout=180)
else:
run(*engine, 'exec', name, 'systemctl', 'start', 'public-fixture-frontend.service', 'archipelago.service')
run(*engine, 'exec', name, 'python3', '-I', '/opt/public-fixture/after-reboot.py', timeout=90)
(report / 'result').write_text('PASS\n')
finally:
if created:
for command, output in [([*engine, 'exec', name, 'journalctl', '--no-pager'], 'journal.log'),
([*engine, 'logs', name], 'container.log')]:
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
(report / output).write_bytes(result.stdout)
retained = (['/var/lib/archipelago-d09', '/opt/d09-fixture/external-history'] if immutable_d09 else
['/var/lib/public-worker-evidence', '/var/lib/archipelago-public-transactions'])
for path in retained:
subprocess.run([*engine, 'cp', name + ':' + path, str(report / Path(path).name)])
subprocess.run([*engine, 'stop', '--time', '10', name], stdout=subprocess.DEVNULL)
if sys.argv[2:] == ['--rootful']:
run('sudo', '-n', 'chown', '-R', f'{os.getuid()}:{os.getgid()}', str(report))
print(report)