def __init__(self, ui, opts): try: prober = Prober(opts) except ProberException as e: err = "Prober init failed: {}".format(e) log.exception(err) raise ApplicationError(err) opts.project = self.project answers = {} if opts.answers is not None: answers = yaml.safe_load(open(opts.answers).read()) log.debug("Loaded answers %s", answers) self.common = { "ui": ui, "opts": opts, "signal": Signal(), "prober": prober, "loop": None, "pool": futures.ThreadPoolExecutor(1), "answers": answers, } if opts.screens: self.controllers = [ c for c in self.controllers if c in opts.screens ] ui.progress_completion = len(self.controllers) self.common['controllers'] = dict.fromkeys(self.controllers) self.controller_index = -1
def __init__(self, opts): self._exc = None self.debug_flags = () if opts.dry_run: # Recognized flags are: # - install-fail: makes curtin install fail by replaying curtin # events from a failed installation, see # subiquity/controllers/installprogress.py # - bpfail-full, bpfail-restricted: makes block probing fail, see # subiquitycore/prober.py # - copy-logs-fail: makes post-install copying of logs fail, see # subiquity/controllers/installprogress.py self.debug_flags = os.environ.get('SUBIQUITY_DEBUG', '').split(',') self.opts = opts opts.project = self.project self.root = '/' if opts.dry_run: self.root = '.subiquity' self.state_dir = os.path.join(self.root, 'run', self.project) os.makedirs(self.state_path('states'), exist_ok=True) self.scale_factor = float( os.environ.get('SUBIQUITY_REPLAY_TIMESCALE', "1")) self.updated = os.path.exists(self.state_path('updating')) self.signal = Signal() self.aio_loop = asyncio.get_event_loop() self.aio_loop.set_exception_handler(self._exception_handler) self.controllers = ControllerSet(self.controllers_mod, self.controllers, init_args=(self, )) self.context = Context.new(self)
def __init__(self, opts): prober = Prober(opts) self.ui = self.make_ui() self.opts = opts opts.project = self.project self.root = '/' if opts.dry_run: self.root = '.subiquity' self.state_dir = os.path.join(self.root, 'run', self.project) os.makedirs(os.path.join(self.state_dir, 'states'), exist_ok=True) self.answers = {} if opts.answers is not None: self.answers = yaml.safe_load(opts.answers.read()) log.debug("Loaded answers %s", self.answers) if not opts.dry_run: open('/run/casper-no-prompt', 'w').close() self.is_color = False self.color_palette = make_palette(self.COLORS, self.STYLES, opts.ascii) self.is_linux_tty = is_linux_tty() if self.is_linux_tty: self.input_filter = KeyCodesFilter() else: self.input_filter = DummyKeycodesFilter() self.scale_factor = float( os.environ.get('SUBIQUITY_REPLAY_TIMESCALE', "1")) self.updated = os.path.exists(os.path.join(self.state_dir, 'updating')) self.signal = Signal() self.prober = prober self.loop = None self.pool = futures.ThreadPoolExecutor(10) if opts.screens: self.controllers = [ c for c in self.controllers if c in opts.screens ] else: self.controllers = self.controllers[:] self.ui.progress_completion = len(self.controllers) self.controller_instances = dict.fromkeys(self.controllers) self.controller_index = -1
def __init__(self, opts): self.debug_flags = () if opts.dry_run: # Recognized flags are: # - install-fail: makes curtin install fail by replaying curtin # events from a failed installation, see # subiquity/controllers/installprogress.py # - bpfail-full, bpfail-restricted: makes block probing fail, see # subiquitycore/prober.py # - copy-logs-fail: makes post-install copying of logs fail, see # subiquity/controllers/installprogress.py self.debug_flags = os.environ.get('SUBIQUITY_DEBUG', '').split(',') prober = Prober(opts.machine_config, self.debug_flags) self.ui = self.make_ui() self.opts = opts opts.project = self.project self.root = '/' if opts.dry_run: self.root = '.subiquity' self.state_dir = os.path.join(self.root, 'run', self.project) os.makedirs(self.state_path('states'), exist_ok=True) self.answers = {} if opts.answers is not None: self.answers = yaml.safe_load(opts.answers.read()) log.debug("Loaded answers %s", self.answers) if not opts.dry_run: open('/run/casper-no-prompt', 'w').close() # Set rich_mode to the opposite of what we want, so we can # call toggle_rich to get the right things set up. self.rich_mode = opts.run_on_serial self.color_palette = make_palette(self.COLORS, self.STYLES) self.is_linux_tty = is_linux_tty() if self.is_linux_tty: self.input_filter = KeyCodesFilter() else: self.input_filter = DummyKeycodesFilter() self.scale_factor = float( os.environ.get('SUBIQUITY_REPLAY_TIMESCALE', "1")) self.updated = os.path.exists(self.state_path('updating')) self.signal = Signal() self.prober = prober self.new_event_loop() self.urwid_loop = None self.controllers = ControllerSet(self, self.controllers) self.context = Context.new(self)
def __init__(self, ui, opts): try: prober = Prober(opts) except ProberException as e: err = "Prober init failed: {}".format(e) log.exception(err) raise ApplicationError(err) opts.project = self.project self.root = '/' if opts.dry_run: self.root = '.subiquity' self.state_dir = os.path.join(self.root, 'run', self.project) os.makedirs(os.path.join(self.state_dir, 'states'), exist_ok=True) answers = {} if opts.answers is not None: answers = yaml.safe_load(opts.answers.read()) log.debug("Loaded answers %s", answers) if not opts.dry_run: open('/run/casper-no-prompt', 'w').close() if is_linux_tty(): log.debug("is_linux_tty") input_filter = KeyCodesFilter() else: input_filter = DummyKeycodesFilter() scale = float(os.environ.get('SUBIQUITY_REPLAY_TIMESCALE', "1")) updated = os.path.exists(os.path.join(self.state_dir, 'updating')) self.common = { "application": self, "updated": updated, "ui": ui, "opts": opts, "signal": Signal(), "prober": prober, "loop": None, "pool": futures.ThreadPoolExecutor(10), "answers": answers, "input_filter": input_filter, "scale_factor": scale, "run_in_bg": self.run_in_bg, } if opts.screens: self.controllers = [c for c in self.controllers if c in opts.screens] else: self.controllers = self.controllers[:] ui.progress_completion = len(self.controllers) self.common['controllers'] = dict.fromkeys(self.controllers) self.controller_index = -1
def __init__(self, ui, opts): try: prober = Prober(opts) except ProberException as e: err = "Prober init failed: {}".format(e) log.exception(err) raise ApplicationError(err) self.common = { "ui": ui, "opts": opts, "signal": Signal(), "prober": prober, "loop": None } self.common['controllers'] = dict.fromkeys(self.controllers) self.controller_index = -1
def __init__(self, ui, opts): try: prober = Prober(opts) except ProberException as e: err = "Prober init failed: {}".format(e) log.exception(err) raise ApplicationError(err) opts.project = self.project answers = {} if opts.answers is not None: answers = yaml.safe_load(open(opts.answers).read()) log.debug("Loaded answers %s", answers) if not opts.dry_run: open('/run/casper-no-prompt', 'w').close() if is_linux_tty(): log.debug("is_linux_tty") input_filter = KeyCodesFilter() else: input_filter = DummyKeycodesFilter() self.common = { "ui": ui, "opts": opts, "signal": Signal(), "prober": prober, "loop": None, "pool": futures.ThreadPoolExecutor(4), "answers": answers, "input_filter": input_filter, } if opts.screens: self.controllers = [ c for c in self.controllers if c in opts.screens ] ui.progress_completion = len(self.controllers) self.common['controllers'] = dict.fromkeys(self.controllers) self.controller_index = -1
class Application(metaclass=urwid.MetaSignals): # A concrete subclass must set project and controllers attributes, e.g.: # # project = "subiquity" # controllers = [ # "Welcome", # "Network", # "Filesystem", # "Identity", # "InstallProgress", # ] # The 'next-screen' and 'prev-screen' signals move through the list of # controllers in order, calling the default method on the controller # instance. make_ui = SubiquityCoreUI def __init__(self, opts): prober = Prober(opts) self.ui = self.make_ui() self.opts = opts opts.project = self.project self.root = '/' if opts.dry_run: self.root = '.subiquity' self.state_dir = os.path.join(self.root, 'run', self.project) os.makedirs(os.path.join(self.state_dir, 'states'), exist_ok=True) self.answers = {} if opts.answers is not None: self.answers = yaml.safe_load(opts.answers.read()) log.debug("Loaded answers %s", self.answers) if not opts.dry_run: open('/run/casper-no-prompt', 'w').close() self.is_color = False self.color_palette = make_palette(self.COLORS, self.STYLES, opts.ascii) self.is_linux_tty = is_linux_tty() if self.is_linux_tty: self.input_filter = KeyCodesFilter() else: self.input_filter = DummyKeycodesFilter() self.scale_factor = float( os.environ.get('SUBIQUITY_REPLAY_TIMESCALE', "1")) self.updated = os.path.exists(os.path.join(self.state_dir, 'updating')) self.signal = Signal() self.prober = prober self.loop = None self.pool = futures.ThreadPoolExecutor(10) if opts.screens: self.controllers = [ c for c in self.controllers if c in opts.screens ] else: self.controllers = self.controllers[:] self.ui.progress_completion = len(self.controllers) self.controller_instances = dict.fromkeys(self.controllers) self.controller_index = -1 @property def cur_controller(self): if self.controller_index < 0: return None controller_name = self.controllers[self.controller_index] return self.controller_instances[controller_name] def run_in_bg(self, func, callback): """Run func() in a thread and call callback on UI thread. callback will be passed a concurrent.futures.Future containing the result of func(). The result of callback is discarded. An exception will crash the process so be careful! """ fut = self.pool.submit(func) def in_main_thread(ignored): self.loop.remove_watch_pipe(pipe) os.close(pipe) callback(fut) pipe = self.loop.watch_pipe(in_main_thread) def in_random_thread(ignored): os.write(pipe, b'x') fut.add_done_callback(in_random_thread) 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 # signal. This calls _reset_input_descriptors() which calls # unhook_event_loop / hook_event_loop on the screen. But this all # happens before _started is set to False on the screen and so this # does not actually do anything -- we end up attempting to read from # stdin while in a background process group, something that gets the # kernel upset at us. # # The cleanest fix seems to be to just send the signal again once # stop() has returned which, now that screen._started is False, # correctly stops listening from stdin. # # There is an exactly analagous problem with screen.start() except # there the symptom is that we are running in the foreground but not # listening to stdin! The fix is the same. def run(): subprocess.run(cmd, **kw) def restore(fut): screen.start() 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): """Connect signals used in the core controller.""" signals = [ ('quit', self.exit), ('next-screen', self.next_screen), ('prev-screen', self.prev_screen), ] if self.opts.dry_run: signals.append(('control-x-quit', self.exit)) self.signal.connect_signals(signals) # Registers signals from each controller for controller_class in self.controller_instances.values(): controller_class.register_signals() log.debug("known signals: %s", self.signal.known_signals) def save_state(self): cur_controller = self.cur_controller if cur_controller is None: return state_path = os.path.join(self.state_dir, 'states', cur_controller._controller_name()) with open(state_path, 'w') as fp: json.dump(cur_controller.serialize(), fp) def select_screen(self, index): if self.cur_controller is not None: self.cur_controller.end_ui() self.controller_index = index self.ui.progress_current = index log.debug("moving to screen %s", self.cur_controller._controller_name()) self.cur_controller.start_ui() state_path = os.path.join(self.state_dir, 'last-screen') with open(state_path, 'w') as fp: fp.write(self.cur_controller._controller_name()) def next_screen(self, *args): self.save_state() while True: if self.controller_index == len(self.controllers) - 1: self.exit() try: self.select_screen(self.controller_index + 1) except Skip: controller_name = self.controllers[self.controller_index] log.debug("skipping screen %s", controller_name) continue else: return def prev_screen(self, *args): self.save_state() while True: if self.controller_index == 0: self.exit() try: self.select_screen(self.controller_index - 1) except Skip: controller_name = self.controllers[self.controller_index] log.debug("skipping screen %s", controller_name) continue else: return # EventLoop ------------------------------------------------------------------- def exit(self): raise urwid.ExitMainLoop() def run_scripts(self, scripts): # run_scripts runs (or rather arranges to run, it's all async) # a series of python snippets in a helpful namespace. This is # all in aid of being able to test some part of the UI without # having to click the same buttons over and over again to get # the UI to the part you are working on. # # In the namespace are: # * everything from view_helpers # * wait, delay execution of subsequent scripts for a while # * c, a function that finds a button and clicks it. uses # wait, above to wait for the button to appear in case it # takes a while. from subiquitycore.testing import view_helpers class ScriptState: def __init__(self): self.ns = view_helpers.__dict__.copy() self.waiting = False self.wait_count = 0 self.scripts = scripts ss = ScriptState() def _run_script(*args): log.debug("running %s", ss.scripts[0]) exec(ss.scripts[0], ss.ns) if ss.waiting: return ss.scripts = ss.scripts[1:] if ss.scripts: self.loop.set_alarm_in(0.01, _run_script) def c(pat): but = view_helpers.find_button_matching(self.ui, '.*' + pat + '.*') if not but: ss.wait_count += 1 if ss.wait_count > 10: raise Exception("no button found matching %r after" "waiting for 10 secs" % pat) wait(1, func=lambda: c(pat)) return ss.wait_count = 0 view_helpers.click(but) def wait(delay, func=None): ss.waiting = True def next(loop, user_data): ss.waiting = False if func is not None: func() if not ss.waiting: ss.scripts = ss.scripts[1:] if ss.scripts: _run_script() self.loop.set_alarm_in(delay, next) ss.ns['c'] = c ss.ns['wait'] = wait ss.ns['ui'] = self.ui self.loop.set_alarm_in(0.06, _run_script) def toggle_color(self): if self.is_color: new_palette = self.STYLES_MONO self.is_color = False else: new_palette = self.color_palette self.is_color = True self.loop.screen.register_palette(new_palette) self.loop.screen.clear() def unhandled_input(self, key): if key == 'ctrl x': self.signal.emit_signal('control-x-quit') elif key == 'f3': self.loop.screen.clear() elif key in ['ctrl t', 'f4']: self.toggle_color() def load_controllers(self): log.debug("load_controllers") controllers_mod = __import__('{}.controllers'.format(self.project), None, None, ['']) for i, k in enumerate(self.controllers): if self.controller_instances[k] is None: log.debug("Importing controller: {}".format(k)) klass = getattr(controllers_mod, k + "Controller") self.controller_instances[k] = klass(self) else: count = 1 for k2 in self.controllers[:i]: if k2 == k or k2.startswith(k + '-'): count += 1 orig = self.controller_instances[k] k += '-' + str(count) self.controllers[i] = k self.controller_instances[k] = RepeatedController(orig, count) log.debug("load_controllers done") def load_serialized_state(self): for k in self.controllers: state_path = os.path.join(self.state_dir, 'states', k) if not os.path.exists(state_path): continue with open(state_path) as fp: self.controller_instances[k].deserialize(json.load(fp)) last_screen = None state_path = os.path.join(self.state_dir, 'last-screen') if os.path.exists(state_path): with open(state_path) as fp: last_screen = fp.read().strip() if last_screen in self.controllers: return self.controllers.index(last_screen) else: return 0 def run(self): log.debug("Application.run") screen = make_screen(self.COLORS, self.is_linux_tty, self.opts.ascii) self.loop = urwid.MainLoop(self.ui, palette=self.color_palette, screen=screen, handle_mouse=False, pop_ups=True, input_filter=self.input_filter.filter, unhandled_input=self.unhandled_input) if self.opts.ascii: urwid.util.set_encoding('ascii') extend_dec_special_charmap() self.toggle_color() self.base_model = self.make_model() try: if self.opts.scripts: self.run_scripts(self.opts.scripts) self.load_controllers() initial_controller_index = 0 if self.updated: initial_controller_index = self.load_serialized_state() def select_initial_screen(loop, index): try: self.select_screen(index) except Skip: self.next_screen() self.loop.set_alarm_in(0.00, lambda loop, ud: tty.setraw(0)) self.loop.set_alarm_in(0.05, select_initial_screen, initial_controller_index) self._connect_base_signals() log.debug("starting controllers") for k in self.controllers: self.controller_instances[k].start() log.debug("controllers started") self.loop.run() except Exception: log.exception("Exception in controller.run():") raise finally: # concurrent.futures.ThreadPoolExecutor tries to join all # threads before exiting. We don't want that and this # ghastly hack prevents it. from concurrent.futures import thread thread._threads_queues = {}