Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
    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)
Ejemplo n.º 5
0
    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
Ejemplo n.º 6
0
    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
Ejemplo n.º 7
0
    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
Ejemplo n.º 8
0
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 = {}