Merge pull request #550 from mwhudson/reprobe-block-devices-on-shell-exit
Reprobe block devices on debug shell exit
This commit is contained in:
commit
f2918fa30b
|
@ -18,6 +18,8 @@ import json
|
|||
import logging
|
||||
import os
|
||||
|
||||
import urwid
|
||||
|
||||
from subiquitycore.controller import BaseController
|
||||
|
||||
from subiquity.models.filesystem import (
|
||||
|
@ -49,11 +51,64 @@ UEFI_GRUB_SIZE_BYTES = 512 * 1024 * 1024 # 512MiB EFI partition
|
|||
class ProbeState(enum.IntEnum):
|
||||
NOT_STARTED = enum.auto()
|
||||
PROBING = enum.auto()
|
||||
REPROBING = enum.auto()
|
||||
FAILED = enum.auto()
|
||||
DONE = enum.auto()
|
||||
|
||||
|
||||
class Probe:
|
||||
|
||||
def __init__(self, controller, restricted, timeout, cb):
|
||||
self.controller = controller
|
||||
self.restricted = restricted
|
||||
self.timeout = timeout
|
||||
self.cb = cb
|
||||
self.state = ProbeState.NOT_STARTED
|
||||
self.result = None
|
||||
|
||||
def start(self):
|
||||
block_discover_log.debug(
|
||||
"starting probe restricted=%s", self.restricted)
|
||||
self.state = ProbeState.PROBING
|
||||
self.controller.run_in_bg(self._bg_probe, self._probed)
|
||||
self.controller.loop.set_alarm_in(self.timeout, self._check_timeout)
|
||||
|
||||
def _bg_probe(self):
|
||||
if self.restricted:
|
||||
probe_types = {'blockdev'}
|
||||
else:
|
||||
probe_types = None
|
||||
# Should consider invoking probert in a subprocess here (so we
|
||||
# can kill it if it gets stuck).
|
||||
return self.controller.app.prober.get_storage(probe_types=probe_types)
|
||||
|
||||
def _probed(self, fut):
|
||||
if self.state == ProbeState.FAILED:
|
||||
block_discover_log.debug(
|
||||
"ignoring result %s for timed out probe", fut)
|
||||
return
|
||||
try:
|
||||
self.result = fut.result()
|
||||
except Exception:
|
||||
block_discover_log.exception(
|
||||
"probing failed restricted=%s", self.restricted)
|
||||
# Should make a crash report here!
|
||||
self.state = ProbeState.FAILED
|
||||
else:
|
||||
block_discover_log.exception(
|
||||
"probing successful restricted=%s", self.restricted)
|
||||
self.state = ProbeState.DONE
|
||||
self.cb(self)
|
||||
|
||||
def _check_timeout(self, loop, ud):
|
||||
if self.state != ProbeState.PROBING:
|
||||
return
|
||||
# Should make a crash report here!
|
||||
block_discover_log.exception(
|
||||
"probing timed out restricted=%s", self.restricted)
|
||||
self.state = ProbeState.FAILED
|
||||
self.cb(self)
|
||||
|
||||
|
||||
class FilesystemController(BaseController):
|
||||
|
||||
def __init__(self, app):
|
||||
|
@ -65,72 +120,67 @@ class FilesystemController(BaseController):
|
|||
self.answers.setdefault('guided', False)
|
||||
self.answers.setdefault('guided-index', 0)
|
||||
self.answers.setdefault('manual', [])
|
||||
self._probe_state = ProbeState.NOT_STARTED
|
||||
self._cur_probe = None
|
||||
self.ui_shown = False
|
||||
|
||||
def start(self):
|
||||
block_discover_log.info("starting probe")
|
||||
self._probe_state = ProbeState.PROBING
|
||||
self.run_in_bg(self._bg_probe, self._probed)
|
||||
self.loop.set_alarm_in(
|
||||
5.0, lambda loop, ud: self._check_probe_timeout())
|
||||
urwid.connect_signal(
|
||||
self.app, 'debug-shell-exited', self._maybe_reprobe_block)
|
||||
self._start_probe(restricted=False)
|
||||
|
||||
def _bg_probe(self, probe_types=None):
|
||||
return self.app.prober.get_storage(probe_types=probe_types)
|
||||
def _maybe_reprobe_block(self):
|
||||
if not self.ui_shown:
|
||||
self._start_probe(restricted=False)
|
||||
|
||||
def _probed(self, fut, restricted=False):
|
||||
if not restricted and self._probe_state != ProbeState.PROBING:
|
||||
def _start_probe(self, *, restricted=False):
|
||||
self._cur_probe = Probe(self, restricted, 5.0, self._probe_done)
|
||||
self._cur_probe.start()
|
||||
|
||||
def _probe_done(self, probe):
|
||||
if probe is not self._cur_probe:
|
||||
block_discover_log.debug(
|
||||
"ignoring result %s for timed out probe", fut)
|
||||
"ignoring result %s for superseded probe", probe.result)
|
||||
return
|
||||
try:
|
||||
storage = fut.result()
|
||||
if restricted:
|
||||
fname = 'probe-data-restricted.json'
|
||||
if probe.state == ProbeState.FAILED:
|
||||
if not probe.restricted:
|
||||
self._start_probe(restricted=True)
|
||||
else:
|
||||
fname = 'probe-data.json'
|
||||
with open(os.path.join(self.app.block_log_dir, fname), 'w') as fp:
|
||||
json.dump(storage, fp, indent=4)
|
||||
self.model.load_probe_data(storage)
|
||||
if self.showing:
|
||||
self.start_ui()
|
||||
return
|
||||
if probe.restricted:
|
||||
fname = 'probe-data-restricted.json'
|
||||
else:
|
||||
fname = 'probe-data.json'
|
||||
with open(os.path.join(self.app.block_log_dir, fname), 'w') as fp:
|
||||
json.dump(probe.result, fp, indent=4)
|
||||
try:
|
||||
self.model.load_probe_data(probe.result)
|
||||
except Exception:
|
||||
block_discover_log.exception(
|
||||
"probing failed restricted=%s", restricted)
|
||||
if not restricted:
|
||||
block_discover_log.info("reprobing for blockdev only")
|
||||
# Should make a crash file for apport, arrange for it to be
|
||||
# copied onto the installed system and tell user all this
|
||||
# happened!
|
||||
self._reprobe()
|
||||
"load_probe_data failed restricted=%s", probe.restricted)
|
||||
# Should make a crash report here!
|
||||
if not probe.restricted:
|
||||
self._start_probe(restricted=True)
|
||||
else:
|
||||
self._probe_state = ProbeState.FAILED
|
||||
# OK, this is a hack
|
||||
self._cur_probe.state = ProbeState.FAILED
|
||||
if self.showing:
|
||||
self.start_ui()
|
||||
else:
|
||||
self._probe_state = ProbeState.DONE
|
||||
# Should do something here if probing found no devices.
|
||||
if self.showing:
|
||||
self.start_ui()
|
||||
|
||||
def _check_probe_timeout(self):
|
||||
log.debug("_check_probe_timeout")
|
||||
if self._probe_state == ProbeState.PROBING:
|
||||
log.info(
|
||||
"unrestricted probing timed out, reprobing for blockdev only")
|
||||
self._reprobe()
|
||||
|
||||
def _reprobe(self):
|
||||
self._probe_state = ProbeState.REPROBING
|
||||
self.run_in_bg(
|
||||
lambda: self._bg_probe({"blockdev"}),
|
||||
lambda fut: self._probed(fut, True),
|
||||
)
|
||||
|
||||
def start_ui(self):
|
||||
if self._probe_state in [ProbeState.PROBING,
|
||||
ProbeState.REPROBING]:
|
||||
if self._cur_probe.state == ProbeState.PROBING:
|
||||
self.ui.set_body(SlowProbing(self))
|
||||
elif self._probe_state == ProbeState.FAILED:
|
||||
elif self._cur_probe.state == ProbeState.FAILED:
|
||||
self.ui.set_body(ProbingFailed(self))
|
||||
else:
|
||||
self.ui_shown = True
|
||||
# Should display a message if self._cur_probe.restricted,
|
||||
# i.e. full device probing failed.
|
||||
self.ui.set_body(GuidedFilesystemView(self))
|
||||
if self.answers['guided']:
|
||||
self.guided(self.answers.get('guided-method', 'direct'))
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
import logging
|
||||
import os
|
||||
import platform
|
||||
import shlex
|
||||
|
||||
import urwid
|
||||
|
||||
from subiquitycore.core import Application
|
||||
|
||||
|
@ -45,6 +46,8 @@ installed system will be mounted at /target.""")
|
|||
|
||||
class Subiquity(Application):
|
||||
|
||||
signals = ['debug-shell-exited']
|
||||
|
||||
snapd_socket_path = '/run/snapd.socket'
|
||||
|
||||
from subiquity.palette import COLORS, STYLES, STYLES_MONO
|
||||
|
@ -117,6 +120,13 @@ class Subiquity(Application):
|
|||
super().unhandled_input(key)
|
||||
|
||||
def debug_shell(self):
|
||||
|
||||
def _before():
|
||||
os.system("clear")
|
||||
print(DEBUG_SHELL_INTRO)
|
||||
|
||||
def _after():
|
||||
urwid.emit_signal(self, 'debug-shell-exited')
|
||||
|
||||
self.run_command_in_foreground(
|
||||
"clear && echo {} && bash".format(shlex.quote(DEBUG_SHELL_INTRO)),
|
||||
shell=True)
|
||||
"bash", before_hook=_before, after_hook=_after, cwd='/')
|
||||
|
|
|
@ -268,7 +268,7 @@ class DummyKeycodesFilter:
|
|||
return keys
|
||||
|
||||
|
||||
class Application:
|
||||
class Application(metaclass=urwid.MetaSignals):
|
||||
|
||||
# A concrete subclass must set project and controllers attributes, e.g.:
|
||||
#
|
||||
|
@ -359,7 +359,8 @@ class Application:
|
|||
os.write(pipe, b'x')
|
||||
fut.add_done_callback(in_random_thread)
|
||||
|
||||
def run_command_in_foreground(self, cmd, **kw):
|
||||
def run_command_in_foreground(self, cmd, before_hook=None, after_hook=None,
|
||||
**kw):
|
||||
screen = self.loop.screen
|
||||
|
||||
# Calling screen.stop() sends the INPUT_DESCRIPTORS_CHANGED
|
||||
|
@ -386,10 +387,14 @@ class Application:
|
|||
urwid.emit_signal(
|
||||
screen, urwid.display_common.INPUT_DESCRIPTORS_CHANGED)
|
||||
tty.setraw(0)
|
||||
if after_hook is not None:
|
||||
after_hook()
|
||||
|
||||
screen.stop()
|
||||
urwid.emit_signal(
|
||||
screen, urwid.display_common.INPUT_DESCRIPTORS_CHANGED)
|
||||
if before_hook is not None:
|
||||
before_hook()
|
||||
self.run_in_bg(run, restore)
|
||||
|
||||
def _connect_base_signals(self):
|
||||
|
|
Loading…
Reference in New Issue