84 lines
3.9 KiB
Python
84 lines
3.9 KiB
Python
"""Startup handoff ordering; filesystem is real, service manager is explicit double."""
|
|
import contextlib
|
|
import os
|
|
from pathlib import Path
|
|
import tempfile
|
|
import unittest
|
|
|
|
SCRIPTS = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
class StopAfterReload(BaseException):
|
|
pass
|
|
|
|
|
|
class AdoptionScheduling(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.temp.cleanup)
|
|
self.root = Path(self.temp.name)
|
|
self.ns = {'__name__': 'schedule_fixture'}
|
|
for name in ('prepare', 'runtime', 'adopt', 'worker'):
|
|
exec(compile((SCRIPTS / f'public_update_{name}.py').read_text(), name, 'exec'), self.ns)
|
|
units = self.root / 'units'; units.mkdir()
|
|
transactions = self.root / 'transactions'; transactions.mkdir()
|
|
self.tx = transactions / ('a' * 32); self.tx.mkdir()
|
|
dropin = units / 'archipelago.service.d/70-public-application-update.conf'
|
|
self.ns.update(TRANSACTIONS=transactions, UNIT_DIR=units, BOOT_WANTS=units/'basic.target.wants',
|
|
DROPIN=dropin, OWNER_DROPINS=(dropin,), MAINTENANCE_UNITS=())
|
|
self.ns['trusted_open'] = lambda path, directory=False: os.open(path, os.O_RDONLY | os.O_NOFOLLOW | (os.O_DIRECTORY if directory else 0))
|
|
self.ns['current'] = lambda: (None, None)
|
|
self.starting = True
|
|
self.events = []
|
|
self.ns['command'] = self.command
|
|
|
|
def command(self, *args, **kwargs):
|
|
self.events.append(args[1:])
|
|
if args[1:] == ('daemon-reload',):
|
|
self.assertFalse(self.starting, 'reload loses EXTEND_TIMEOUT_USEC on a starting file-backed unit')
|
|
if args[1:] == ('start', 'archipelago-public-recovery-' + self.tx.name + '.service'):
|
|
arm = (self.ns['UNIT_DIR'] / args[-1]).read_text()
|
|
self.assertNotIn('Before=', arm, 'live arm must not wait behind the starting manager')
|
|
self.assertIn(' arm ', arm)
|
|
if args[1:3] == ('start', '--no-block'):
|
|
recovery = 'archipelago-public-recovery-' + self.tx.name + '.service'
|
|
self.assertIn(' recover-boot ', (self.ns['UNIT_DIR'] / recovery).read_text())
|
|
self.assertIn('Before=basic.target archipelago.service', (self.ns['UNIT_DIR'] / recovery).read_text())
|
|
self.assertTrue((self.ns['BOOT_WANTS'] / recovery).is_symlink())
|
|
self.assertEqual((self.ns['TRANSACTIONS'] / 'current').read_text(), self.tx.name)
|
|
return ''
|
|
|
|
def schedule(self):
|
|
return self.ns['schedule_transaction'](self.tx, {'id': self.tx.name, 'adoption': True,
|
|
'runtime_requested': True, 'initial': 'signed-pair'}, '# immutable fixture worker\n')
|
|
|
|
def test_admission_arms_durable_boot_recovery_without_reloading_starting_manager(self):
|
|
state = self.schedule()
|
|
self.assertEqual(state['phase'], 'queued')
|
|
self.assertEqual(len(self.events), 2)
|
|
|
|
def test_worker_stops_starting_manager_before_first_reload(self):
|
|
state = self.schedule()
|
|
self.ns['current'] = lambda: (self.tx, state)
|
|
self.ns['owner_lock'] = contextlib.nullcontext
|
|
for name in ('require_legacy_service', 'custody_frontend_parent'):
|
|
self.ns[name] = lambda: None
|
|
self.ns['manager_adoptable'] = lambda: self.starting
|
|
self.ns['pair_identity'] = lambda *args: 'signed-pair'
|
|
self.ns['check_preimages'] = lambda *args: None
|
|
self.ns['require_space'] = lambda *args: None
|
|
def stop():
|
|
self.events.append(('stop-manager',))
|
|
self.starting = False
|
|
self.ns['stop_manager'] = stop
|
|
def paused(*args):
|
|
self.assertEqual(self.events[-2:], [('stop-manager',), ('daemon-reload',)])
|
|
raise StopAfterReload()
|
|
self.ns['runtime_pause_units'] = paused
|
|
with self.assertRaises(StopAfterReload):
|
|
self.ns['run_worker'](self.tx.name)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|