# Copyright 2015 Canonical, Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . import logging import os import subprocess from tornado.gen import coroutine import subiquitycore.utils as utils from subiquitycore.models import InstallProgressModel from subiquitycore.ui.views import ProgressView from subiquitycore.controller import ControllerPolicy from subiquitycore.curtin import (CURTIN_CONFIGS, CURTIN_INSTALL_LOG, CURTIN_POSTINSTALL_LOG, curtin_reboot, curtin_install_cmd) log = logging.getLogger("subiquitycore.controller.installprogress") class InstallProgressController(ControllerPolicy): def __init__(self, common): super().__init__(common) self.model = InstallProgressModel() self.progress_view = None self.alarm = None self.install_log = None # state flags self.install_error = False self.install_config = False self.install_spawned = False self.install_complete = False self.postinstall_config = False self.postinstall_spawned = False self.postinstall_complete = False def curtin_wrote_install(self): self.install_config = True def curtin_wrote_postinstall(self): self.postinstall_config = True @property def is_complete(self): log.debug('Checking is_complete: {} and {}'.format( self.install_complete, self.postinstall_complete)) return (self.install_complete and self.postinstall_complete) def curtin_tail_install_log(self): if os.path.exists(self.install_log): tail_cmd = ['tail', '-n', '10', self.install_log] log.debug('tail cmd: {}'.format(" ".join(tail_cmd))) install_tail = subprocess.check_output(tail_cmd) return install_tail else: log.debug(('Install log not yet present:') + '{}'.format(self.install_log)) return '' def curtin_error(self): log.debug('curtin_error') # just the last ten lines errmsg = self.curtin_tail_install_log()[-800:].decode('utf-8') # Holy Unescaping Batman! errmsg = errmsg.replace("\\\'", "") errmsg = errmsg.replace("\'\'", "") errmsg = errmsg.replace("\\n\'\n", "\n") errmsg = errmsg.replace('\\n', '\n') log.error(errmsg) title = ('An error occurred during installation') self.ui.set_header(title, 'Please report this error in Launchpad') self.progress_view.text.set_text(errmsg) self.ui.set_footer("An error as occurred.", 100) self.progress_view.show_finished_button() log.debug('curtin_error: refreshing final error screen') self.signal.emit_signal('refresh') @coroutine def curtin_install(self): log.debug('Curtin Install: calling curtin with ' 'storage/net/postinstall config') if self.install_config is False: log.error('Attempting to spawn curtin install without a config') raise Exception('AIEEE!') self.install_spawned = True self.install_log = CURTIN_INSTALL_LOG if self.opts.dry_run: log.debug("Installprogress: this is a dry-run") curtin_cmd = ["top", "-d", "0.5", "-n", "20", "-b", "-p", str(os.getpid()), ">", self.install_log] else: log.debug("Installprogress: this is the *REAL* thing") configs = [CURTIN_CONFIGS['network'], CURTIN_CONFIGS['storage']] curtin_cmd = curtin_install_cmd(configs) log.debug('Curtin install cmd: {}'.format(curtin_cmd)) result = yield utils.run_command_async(" ".join(curtin_cmd)) log.debug('curtin_install: result: {}'.format(result)) if result['status'] > 0: msg = ("Problem with curtin " "install: {}".format(result)) log.error(msg) # stop the update and clear the screen self.install_error = True log.debug('curtin_install: clearing screen') self.progress_view.text.set_text('') self.signal.emit_signal('refresh') return log.debug('After curtin install OK') self.install_complete = True @coroutine def curtin_postinstall(self): log.debug('Curtin Post Install: calling curtin ' 'with postinstall config') if self.postinstall_config is False: log.error('Attempting to spawn curtin install without a config') raise Exception('AIEEE!') self.postinstall_spawned = True self.install_log = CURTIN_POSTINSTALL_LOG if self.opts.dry_run: log.debug("Installprogress: this is a dry-run") curtin_cmd = ["top", "-d", "0.5", "-n", "20", "-b", "-p", str(os.getpid()), ">", self.install_log] else: log.debug("Installprogress: this is the *REAL* thing") configs = [ CURTIN_CONFIGS['postinstall'], CURTIN_CONFIGS['preserved'], ] curtin_cmd = curtin_install_cmd(configs) log.debug('Curtin postinstall cmd: {}'.format(curtin_cmd)) result = yield utils.run_command_async(" ".join(curtin_cmd)) if result['status'] > 0: msg = ("Problem with curtin " "post-install: {}".format(result)) log.error(msg) # stop the update and clear the screen self.install_error = True log.debug('curtin_postinstall: clearing screen') self.progress_view.text.set_text('') self.signal.emit_signal('refresh') return log.debug('After curtin postinstall OK') self.postinstall_complete = True def progress_indicator(self, *args, **kwargs): log.debug('progress_indicator') if self.install_error: log.debug('progress_indicator: error detected') self.curtin_error() return if self.is_complete: log.debug('progress_indicator: complete!') self.progress_view.text.set_text("Finished install!") self.ui.set_footer("", 100) self.progress_view.show_finished_button() self.loop.remove_alarm(self.alarm) return elif (self.postinstall_config and self.install_complete and not self.postinstall_spawned): # kick off postinstall self.signal.emit_signal('installprogress:curtin-postinstall') else: log.debug('progress_indicator: looping') install_tail = self.curtin_tail_install_log() self.progress_view.text.set_text(install_tail) if not self.install_error: log.debug('progress_indicator: setting alarm') self.alarm = self.loop.set_alarm_in(0.3, self.progress_indicator) def reboot(self): if self.opts.dry_run: log.debug('dry-run enabled, skipping reboot, quiting instead') self.signal.emit_signal('quit') curtin_reboot() @coroutine def show_progress(self): log.debug('show_progress called') title = ("Installing system") excerpt = ("Please wait for the installation " "to finish before rebooting.") footer = ("Thank you for using Ubuntu!") self.ui.set_header(title, excerpt) self.ui.set_footer(footer, 90) self.progress_view = ProgressView(self.model, self.signal) self.ui.set_body(self.progress_view) if self.opts.dry_run: banner = [ "**** DRY_RUN ****", "" "", "", "", "Press (Control-x) to Quit." ] self.progress_view.text.set_text("\n".join(banner)) self.alarm = self.loop.set_alarm_in(0.3, self.progress_indicator) self.ui.set_footer(footer, 90)