change curtin runner api to be more one-shot

This commit is contained in:
Michael Hudson-Doyle 2021-11-05 10:05:47 +13:00
parent 9442c4ad07
commit 85f0c62ae4
3 changed files with 92 additions and 65 deletions

View File

@ -40,7 +40,8 @@ from subiquity.journald import (
journald_listen,
)
from subiquity.server.curtin import (
get_curtin_command_runner,
run_curtin_command,
start_curtin_command,
)
from subiquity.server.controller import (
SubiquityController,
@ -75,11 +76,9 @@ class InstallController(SubiquityController):
super().__init__(app)
self.model = app.base_model
self.unattended_upgrades_proc = None
self.unattended_upgrades_cmd = None
self.unattended_upgrades_ctx = None
self._event_syslog_id = 'curtin_event.%s' % (os.getpid(),)
self.tb_extractor = TracebackExtractor()
self.curtin_runner = None
def stop_uu(self):
if self.app.state == ApplicationState.UU_RUNNING:
@ -97,7 +96,7 @@ class InstallController(SubiquityController):
def log_event(self, event):
self.tb_extractor.feed(event['MESSAGE'])
def make_curtin_command_runner(self):
def write_config(self):
config = self.model.render()
config_location = '/var/log/installer/subiquity-curtin-install.conf'
log_location = INSTALL_LOG
@ -114,19 +113,19 @@ class InstallController(SubiquityController):
self.app.note_file_for_apport("CurtinConfig", config_location)
self.app.note_file_for_apport("CurtinErrors", ERROR_TARFILE)
self.app.note_file_for_apport("CurtinLog", log_location)
self.curtin_runner = get_curtin_command_runner(
self.app, config_location)
return config_location
@with_context(description="umounting /target dir")
async def unmount_target(self, *, context, target):
await self.curtin_runner.run(context, 'unmount', '-t', target)
await run_curtin_command(self.app, context, 'unmount', '-t', target)
if not self.app.opts.dry_run:
shutil.rmtree(target)
@with_context(
description="installing system", level="INFO", childlevel="DEBUG")
async def curtin_install(self, *, context):
await self.curtin_runner.run(context, 'install')
async def curtin_install(self, *, context, config_location):
await run_curtin_command(
self.app, context, 'install', config=config_location)
@with_context()
async def install(self, *, context):
@ -148,13 +147,14 @@ class InstallController(SubiquityController):
self.app.update_state(ApplicationState.RUNNING)
self.make_curtin_command_runner()
config_location = self.write_config()
if os.path.exists(self.model.target):
await self.unmount_target(
context=context, target=self.model.target)
await self.curtin_install(context=context)
await self.curtin_install(
context=context, config_location=config_location)
self.app.update_state(ApplicationState.POST_WAIT)
@ -205,14 +205,15 @@ class InstallController(SubiquityController):
name="install_{package}",
description="installing {package}")
async def install_package(self, *, context, package):
await self.curtin_runner.run(context, 'system-install', '--', package)
await run_curtin_command(
self.app, context, 'system-install', '--', package)
@with_context(description="restoring apt configuration")
async def restore_apt_config(self, context):
await self.app.command_runner.run(["umount", self.tpath('etc/apt')])
if self.model.network.has_network:
await self.curtin_runner.run(
context, "in-target", "-t", self.tpath(),
await run_curtin_command(
self.app, context, "in-target", "-t", self.tpath(),
"--", "apt-get", "update")
else:
await self.app.command_runner.run(
@ -235,13 +236,11 @@ class InstallController(SubiquityController):
apt_conf.write(apt_conf_contents)
apt_conf.close()
self.unattended_upgrades_ctx = context
self.unattended_upgrades_proc = \
await self.app.command_runner.start(
self.curtin_runner.make_command(
"in-target", "-t", self.tpath(),
"--", "unattended-upgrades", "-v"))
await self.unattended_upgrades_proc.communicate()
self.unattended_upgrades_proc = None
self.unattended_upgrades_cmd = await start_curtin_command(
self.app, context, "in-target", "-t", self.tpath(),
"--", "unattended-upgrades", "-v")
await self.unattended_upgrades_cmd.wait()
self.unattended_upgrades_cmd = None
self.unattended_upgrades_ctx = None
async def stop_unattended_upgrades(self):
@ -255,8 +254,8 @@ class InstallController(SubiquityController):
'--stop-only',
])
if self.app.opts.dry_run and \
self.unattended_upgrades_proc is not None:
self.unattended_upgrades_proc.terminate()
self.unattended_upgrades_cmd is not None:
self.unattended_upgrades_cmd.proc.terminate()
uu_apt_conf = b"""\

View File

@ -33,19 +33,19 @@ from subiquity.journald import (
log = logging.getLogger('subiquity.server.curtin')
class CurtinCommandRunner:
class _CurtinCommand:
_count = 0
def __init__(self, runner, config_location=None):
def __init__(self, runner, command, *args, config=None):
self.runner = runner
self._event_syslog_id = 'curtin_event.%s.%s' % (
os.getpid(), CurtinCommandRunner._count)
CurtinCommandRunner._count += 1
self.config_location = config_location
self._event_contexts = {}
journald_listen(
asyncio.get_event_loop(), [self._event_syslog_id], self._event)
_CurtinCommand._count += 1
self._event_syslog_id = 'curtin_event.%s.%s' % (
os.getpid(), _CurtinCommand._count)
self._fd = None
self.proc = None
self._cmd = self.make_command(command, *args, config=config)
def _event(self, event):
e = {
@ -80,62 +80,85 @@ class CurtinCommandRunner:
if curtin_ctx is not None:
curtin_ctx.exit(result=status)
def make_command(self, command, *args, **conf):
cmd = [
sys.executable, '-m', 'curtin', '--showtrace',
]
if self.config_location is not None:
cmd.extend([
'-c', self.config_location,
])
conf.setdefault('reporting', {})['subiquity'] = {
'type': 'journald',
'identifier': self._event_syslog_id,
def make_command(self, command, *args, config=None):
reporting_conf = {
'subiquity': {
'type': 'journald',
'identifier': self._event_syslog_id,
},
}
conf['verbosity'] = 3
for k, v in conf.items():
cmd.extend(['--set', 'json:' + k + '=' + json.dumps(v)])
cmd = [
sys.executable, '-m', 'curtin', '--showtrace', '-vvv',
'--set', 'json:reporting=' + json.dumps(reporting_conf),
]
if config is not None:
cmd.extend([
'-c', config,
])
cmd.append(command)
cmd.extend(args)
return cmd
async def run(self, context, command, *args, **conf):
async def start(self, context):
self._fd = journald_listen(
asyncio.get_event_loop(), [self._event_syslog_id], self._event)
# Yield to the event loop before starting curtin to avoid missing the
# first couple of events.
await asyncio.sleep(0)
self._event_contexts[''] = context
await self.runner.run(self.make_command(command, *args, **conf))
self.proc = await self.runner.start(self._cmd)
async def wait(self):
await self.runner.wait(self.proc)
waited = 0.0
while len(self._event_contexts) > 1 and waited < 5.0:
await asyncio.sleep(0.1)
waited += 0.1
log.debug("waited %s seconds for events to drain", waited)
self._event_contexts.pop('', None)
asyncio.get_event_loop().remove_reader(self._fd)
async def run(self, context):
await self.start(context)
await self.wait()
class DryRunCurtinCommandRunner(CurtinCommandRunner):
class _DryRunCurtinCommand(_CurtinCommand):
event_file = 'examples/curtin-events.json'
def make_command(self, command, *args, **conf):
def make_command(self, command, *args, config=None):
if command == 'install':
return [
sys.executable, "scripts/replay-curtin-log.py",
self.event_file, self._event_syslog_id,
sys.executable,
"scripts/replay-curtin-log.py",
self.event_file,
self._event_syslog_id,
'.subiquity' + INSTALL_LOG,
]
else:
return super().make_command(command, *args, **conf)
return super().make_command(command, *args, config=config)
class FailingDryRunCurtinCommandRunner(DryRunCurtinCommandRunner):
class _FailingDryRunCurtinCommand(_DryRunCurtinCommand):
event_file = 'examples/curtin-events-fail.json'
def get_curtin_command_runner(app, config_location=None):
async def start_curtin_command(app, context, command, *args, config=None):
if app.opts.dry_run:
if 'install-fail' in app.debug_flags:
cls = FailingDryRunCurtinCommandRunner
cls = _FailingDryRunCurtinCommand
else:
cls = DryRunCurtinCommandRunner
cls = _DryRunCurtinCommand
else:
cls = CurtinCommandRunner
return cls(app.command_runner, config_location)
cls = _CurtinCommand
curtin_cmd = cls(app.command_runner, command, *args, config=config)
await curtin_cmd.start(context)
return curtin_cmd
async def run_curtin_command(app, context, command, *args, config=None):
cmd = await start_curtin_command(
app, context, command, *args, config=config)
await cmd.wait()

View File

@ -25,17 +25,22 @@ class LoggedCommandRunner:
self.ident = ident
async def start(self, cmd):
return await astart_command([
proc = await astart_command([
'systemd-cat', '--level-prefix=false', '--identifier='+self.ident,
] + cmd)
proc.args = cmd
return proc
async def wait(self, proc):
await proc.communicate()
if proc.returncode != 0:
raise subprocess.CalledProcessError(proc.returncode, proc.args)
else:
return subprocess.CompletedProcess(proc.args, proc.returncode)
async def run(self, cmd):
proc = await self.start(cmd)
await proc.communicate()
if proc.returncode != 0:
raise subprocess.CalledProcessError(proc.returncode, cmd)
else:
return subprocess.CompletedProcess(cmd, proc.returncode)
return await self.wait(proc)
class DryRunCommandRunner(LoggedCommandRunner):