split installprogress controller into install (server) and progress (client)
This commit is contained in:
parent
92252f8119
commit
3c30e14313
|
@ -1,6 +1,8 @@
|
||||||
[encoding: UTF-8]
|
[encoding: UTF-8]
|
||||||
subiquity/client/client.py
|
subiquity/client/client.py
|
||||||
subiquity/client/controller.py
|
subiquity/client/controller.py
|
||||||
|
subiquity/client/controllers/__init__.py
|
||||||
|
subiquity/client/controllers/progress.py
|
||||||
subiquity/client/__init__.py
|
subiquity/client/__init__.py
|
||||||
subiquity/client/keycodes.py
|
subiquity/client/keycodes.py
|
||||||
subiquity/cmd/common.py
|
subiquity/cmd/common.py
|
||||||
|
@ -30,7 +32,6 @@ subiquity/controllers/error.py
|
||||||
subiquity/controllers/filesystem.py
|
subiquity/controllers/filesystem.py
|
||||||
subiquity/controllers/identity.py
|
subiquity/controllers/identity.py
|
||||||
subiquity/controllers/__init__.py
|
subiquity/controllers/__init__.py
|
||||||
subiquity/controllers/installprogress.py
|
|
||||||
subiquity/controllers/keyboard.py
|
subiquity/controllers/keyboard.py
|
||||||
subiquity/controllers/mirror.py
|
subiquity/controllers/mirror.py
|
||||||
subiquity/controllers/network.py
|
subiquity/controllers/network.py
|
||||||
|
@ -122,6 +123,7 @@ subiquity/models/tests/test_mirror.py
|
||||||
subiquity/models/tests/test_subiquity.py
|
subiquity/models/tests/test_subiquity.py
|
||||||
subiquity/server/controller.py
|
subiquity/server/controller.py
|
||||||
subiquity/server/controllers/__init__.py
|
subiquity/server/controllers/__init__.py
|
||||||
|
subiquity/server/controllers/install.py
|
||||||
subiquity/server/dryrun.py
|
subiquity/server/dryrun.py
|
||||||
subiquity/server/errors.py
|
subiquity/server/errors.py
|
||||||
subiquity/server/__init__.py
|
subiquity/server/__init__.py
|
||||||
|
|
|
@ -89,7 +89,9 @@ class SubiquityClient(TuiApplication):
|
||||||
def make_ui(self):
|
def make_ui(self):
|
||||||
return SubiquityUI(self, self.help_menu)
|
return SubiquityUI(self, self.help_menu)
|
||||||
|
|
||||||
controllers = []
|
controllers = [
|
||||||
|
"Progress",
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, opts):
|
def __init__(self, opts):
|
||||||
if is_linux_tty():
|
if is_linux_tty():
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Copyright 2020 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from .progress import ProgressController
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'ProgressController',
|
||||||
|
]
|
|
@ -0,0 +1,121 @@
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
from subiquitycore.context import with_context
|
||||||
|
|
||||||
|
from subiquity.client.controller import SubiquityTuiController
|
||||||
|
from subiquity.common.types import InstallState
|
||||||
|
from subiquity.ui.views.installprogress import (
|
||||||
|
InstallRunning,
|
||||||
|
ProgressView,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger("subiquity.client.controllers.progress")
|
||||||
|
|
||||||
|
|
||||||
|
class ProgressController(SubiquityTuiController):
|
||||||
|
|
||||||
|
endpoint_name = 'install'
|
||||||
|
|
||||||
|
def __init__(self, app):
|
||||||
|
super().__init__(app)
|
||||||
|
self.progress_view = ProgressView(self)
|
||||||
|
self.install_state = None
|
||||||
|
self.crash_report_ref = None
|
||||||
|
self.answers = app.answers.get("InstallProgress", {})
|
||||||
|
|
||||||
|
def event(self, event):
|
||||||
|
if event["SUBIQUITY_EVENT_TYPE"] == "start":
|
||||||
|
self.progress_view.event_start(
|
||||||
|
event["SUBIQUITY_CONTEXT_ID"],
|
||||||
|
event.get("SUBIQUITY_CONTEXT_PARENT_ID"),
|
||||||
|
event["MESSAGE"])
|
||||||
|
elif event["SUBIQUITY_EVENT_TYPE"] == "finish":
|
||||||
|
self.progress_view.event_finish(
|
||||||
|
event["SUBIQUITY_CONTEXT_ID"])
|
||||||
|
|
||||||
|
def log_line(self, event):
|
||||||
|
log_line = event['MESSAGE']
|
||||||
|
self.progress_view.add_log_line(log_line)
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.app.aio_loop.create_task(self._wait_status())
|
||||||
|
|
||||||
|
def click_reboot(self):
|
||||||
|
self.app.aio_loop.create_task(self.send_reboot_and_wait())
|
||||||
|
|
||||||
|
async def send_reboot_and_wait(self):
|
||||||
|
try:
|
||||||
|
await self.app.client.reboot.POST()
|
||||||
|
except aiohttp.ClientError:
|
||||||
|
pass
|
||||||
|
self.app.exit()
|
||||||
|
|
||||||
|
@with_context()
|
||||||
|
async def _wait_status(self, context):
|
||||||
|
install_running = None
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
install_status = await self.endpoint.status.GET(
|
||||||
|
cur=self.install_state)
|
||||||
|
except aiohttp.ClientError:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
continue
|
||||||
|
self.install_state = install_status.state
|
||||||
|
|
||||||
|
self.progress_view.update_for_state(self.install_state)
|
||||||
|
if self.ui.body is self.progress_view:
|
||||||
|
self.ui.set_header(self.progress_view.title)
|
||||||
|
|
||||||
|
if install_status.error is not None:
|
||||||
|
if self.crash_report_ref is None:
|
||||||
|
self.crash_report_ref = install_status.error
|
||||||
|
self.ui.set_body(self.progress_view)
|
||||||
|
self.app.show_error_report(self.crash_report_ref)
|
||||||
|
|
||||||
|
if self.install_state == InstallState.NEEDS_CONFIRMATION:
|
||||||
|
if self.showing:
|
||||||
|
self.app.show_confirm_install()
|
||||||
|
|
||||||
|
if self.install_state == InstallState.RUNNING:
|
||||||
|
if install_status.confirming_tty != self.app.our_tty:
|
||||||
|
install_running = InstallRunning(
|
||||||
|
self.app, install_status.confirming_tty)
|
||||||
|
self.app.add_global_overlay(install_running)
|
||||||
|
else:
|
||||||
|
if install_running is not None:
|
||||||
|
self.app.remove_global_overlay(install_running)
|
||||||
|
install_running = None
|
||||||
|
|
||||||
|
if self.install_state == InstallState.DONE:
|
||||||
|
if self.answers.get('reboot', False):
|
||||||
|
self.click_reboot()
|
||||||
|
|
||||||
|
def make_ui(self):
|
||||||
|
if self.install_state == InstallState.NEEDS_CONFIRMATION:
|
||||||
|
self.app.show_confirm_install()
|
||||||
|
return self.progress_view
|
||||||
|
|
||||||
|
def run_answers(self):
|
||||||
|
pass
|
|
@ -20,6 +20,8 @@ from subiquity.common.types import (
|
||||||
ApplicationState,
|
ApplicationState,
|
||||||
ApplicationStatus,
|
ApplicationStatus,
|
||||||
ErrorReportRef,
|
ErrorReportRef,
|
||||||
|
InstallState,
|
||||||
|
InstallStatus,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,3 +57,7 @@ class API:
|
||||||
class crash:
|
class crash:
|
||||||
def GET() -> None:
|
def GET() -> None:
|
||||||
"""Requests to this method will fail with a HTTP 500."""
|
"""Requests to this method will fail with a HTTP 500."""
|
||||||
|
|
||||||
|
class install:
|
||||||
|
class status:
|
||||||
|
def GET(cur: Optional[InstallState] = None) -> InstallStatus: ...
|
||||||
|
|
|
@ -183,3 +183,10 @@ class InstallState(enum.Enum):
|
||||||
UU_CANCELLING = enum.auto()
|
UU_CANCELLING = enum.auto()
|
||||||
DONE = enum.auto()
|
DONE = enum.auto()
|
||||||
ERROR = enum.auto()
|
ERROR = enum.auto()
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(auto_attribs=True)
|
||||||
|
class InstallStatus:
|
||||||
|
state: InstallState
|
||||||
|
confirming_tty: str = ''
|
||||||
|
error: Optional[ErrorReportRef] = None
|
||||||
|
|
|
@ -19,7 +19,6 @@ from .debconf import DebconfController
|
||||||
from .error import ErrorController
|
from .error import ErrorController
|
||||||
from .filesystem import FilesystemController
|
from .filesystem import FilesystemController
|
||||||
from .identity import IdentityController
|
from .identity import IdentityController
|
||||||
from .installprogress import InstallProgressController
|
|
||||||
from .keyboard import KeyboardController
|
from .keyboard import KeyboardController
|
||||||
from .mirror import MirrorController
|
from .mirror import MirrorController
|
||||||
from .network import NetworkController
|
from .network import NetworkController
|
||||||
|
@ -40,7 +39,6 @@ __all__ = [
|
||||||
'ErrorController',
|
'ErrorController',
|
||||||
'FilesystemController',
|
'FilesystemController',
|
||||||
'IdentityController',
|
'IdentityController',
|
||||||
'InstallProgressController',
|
|
||||||
'KeyboardController',
|
'KeyboardController',
|
||||||
'LateController',
|
'LateController',
|
||||||
'MirrorController',
|
'MirrorController',
|
||||||
|
|
|
@ -12,3 +12,9 @@
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from .install import InstallController
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'InstallController',
|
||||||
|
]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright 2015 Canonical, Ltd.
|
# Copyright 2020 Canonical, Ltd.
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -21,7 +21,7 @@ import re
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import traceback
|
from typing import Optional
|
||||||
|
|
||||||
from curtin.commands.install import (
|
from curtin.commands.install import (
|
||||||
ERROR_TARFILE,
|
ERROR_TARFILE,
|
||||||
|
@ -29,13 +29,10 @@ from curtin.commands.install import (
|
||||||
)
|
)
|
||||||
from curtin.util import write_file
|
from curtin.util import write_file
|
||||||
|
|
||||||
from systemd import journal
|
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from subiquitycore.async_helpers import (
|
from subiquitycore.async_helpers import (
|
||||||
run_in_thread,
|
run_in_thread,
|
||||||
schedule_task,
|
|
||||||
)
|
)
|
||||||
from subiquitycore.context import Status, with_context
|
from subiquitycore.context import Status, with_context
|
||||||
from subiquitycore.utils import (
|
from subiquitycore.utils import (
|
||||||
|
@ -43,14 +40,18 @@ from subiquitycore.utils import (
|
||||||
astart_command,
|
astart_command,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from subiquity.common.apidef import API
|
||||||
from subiquity.common.errorreport import ErrorReportKind
|
from subiquity.common.errorreport import ErrorReportKind
|
||||||
from subiquity.common.types import InstallState
|
from subiquity.server.controller import (
|
||||||
from subiquity.controller import SubiquityTuiController
|
SubiquityController,
|
||||||
|
)
|
||||||
|
from subiquity.common.types import (
|
||||||
|
InstallState,
|
||||||
|
InstallStatus,
|
||||||
|
)
|
||||||
from subiquity.journald import journald_listen
|
from subiquity.journald import journald_listen
|
||||||
from subiquity.ui.views.installprogress import ProgressView
|
|
||||||
|
|
||||||
|
log = logging.getLogger("subiquity.server.controllers.install")
|
||||||
log = logging.getLogger("subiquitycore.controller.installprogress")
|
|
||||||
|
|
||||||
|
|
||||||
class TracebackExtractor:
|
class TracebackExtractor:
|
||||||
|
@ -72,18 +73,16 @@ class TracebackExtractor:
|
||||||
self.traceback.append(line)
|
self.traceback.append(line)
|
||||||
|
|
||||||
|
|
||||||
class InstallProgressController(SubiquityTuiController):
|
class InstallController(SubiquityController):
|
||||||
|
|
||||||
|
endpoint = API.install
|
||||||
|
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
super().__init__(app)
|
super().__init__(app)
|
||||||
self.model = app.base_model
|
self.model = app.base_model
|
||||||
self.progress_view = ProgressView(self)
|
|
||||||
self.crash_report_ref = None
|
|
||||||
self._install_state = InstallState.NOT_STARTED
|
self._install_state = InstallState.NOT_STARTED
|
||||||
|
self._install_state_event = asyncio.Event()
|
||||||
self.reboot_clicked = asyncio.Event()
|
self.error_ref = None
|
||||||
if self.answers.get('reboot', False):
|
|
||||||
self.reboot_clicked.set()
|
|
||||||
|
|
||||||
self.unattended_upgrades_proc = None
|
self.unattended_upgrades_proc = None
|
||||||
self.unattended_upgrades_ctx = None
|
self.unattended_upgrades_ctx = None
|
||||||
|
@ -91,71 +90,53 @@ class InstallProgressController(SubiquityTuiController):
|
||||||
self.tb_extractor = TracebackExtractor()
|
self.tb_extractor = TracebackExtractor()
|
||||||
self.curtin_event_contexts = {}
|
self.curtin_event_contexts = {}
|
||||||
|
|
||||||
def event(self, event):
|
|
||||||
if event["SUBIQUITY_EVENT_TYPE"] == "start":
|
|
||||||
self.progress_view.event_start(
|
|
||||||
event["SUBIQUITY_CONTEXT_ID"],
|
|
||||||
event.get("SUBIQUITY_CONTEXT_PARENT_ID"),
|
|
||||||
event["MESSAGE"])
|
|
||||||
elif event["SUBIQUITY_EVENT_TYPE"] == "finish":
|
|
||||||
self.progress_view.event_finish(
|
|
||||||
event["SUBIQUITY_CONTEXT_ID"])
|
|
||||||
|
|
||||||
def log_line(self, event):
|
|
||||||
log_line = event['MESSAGE']
|
|
||||||
self.progress_view.add_log_line(log_line)
|
|
||||||
|
|
||||||
def interactive(self):
|
def interactive(self):
|
||||||
return self.app.interactive()
|
return True
|
||||||
|
|
||||||
|
async def status_GET(
|
||||||
|
self, cur: Optional[InstallState] = None) -> InstallStatus:
|
||||||
|
if cur == self.install_state:
|
||||||
|
await self._install_state_event.wait()
|
||||||
|
return InstallStatus(
|
||||||
|
self.install_state,
|
||||||
|
self.app.confirming_tty,
|
||||||
|
self.error_ref)
|
||||||
|
|
||||||
|
def stop_uu(self):
|
||||||
|
if self.install_state == InstallState.UU_RUNNING:
|
||||||
|
self.update_state(InstallState.UU_CANCELLING)
|
||||||
|
self.app.aio_loop.create_task(self.stop_unattended_upgrades())
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self.install_task = schedule_task(self.install())
|
self.install_task = self.app.aio_loop.create_task(self.install())
|
||||||
|
|
||||||
@with_context()
|
|
||||||
async def apply_autoinstall_config(self, context):
|
|
||||||
await self.install_task
|
|
||||||
self.app.reboot_on_exit = True
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def install_state(self):
|
def install_state(self):
|
||||||
return self._install_state
|
return self._install_state
|
||||||
|
|
||||||
def update_state(self, state):
|
def update_state(self, state):
|
||||||
|
self._install_state_event.set()
|
||||||
|
self._install_state_event.clear()
|
||||||
self._install_state = state
|
self._install_state = state
|
||||||
self.progress_view.update_for_state(state)
|
|
||||||
|
|
||||||
def tpath(self, *path):
|
def tpath(self, *path):
|
||||||
return os.path.join(self.model.target, *path)
|
return os.path.join(self.model.target, *path)
|
||||||
|
|
||||||
def curtin_error(self):
|
def curtin_error(self):
|
||||||
|
self.update_state(InstallState.ERROR)
|
||||||
kw = {}
|
kw = {}
|
||||||
if sys.exc_info()[0] is not None:
|
if sys.exc_info()[0] is not None:
|
||||||
log.exception("curtin_error")
|
log.exception("curtin_error")
|
||||||
self.progress_view.add_log_line(traceback.format_exc())
|
# send traceback.format_exc() to journal?
|
||||||
if self.tb_extractor.traceback:
|
if self.tb_extractor.traceback:
|
||||||
kw["Traceback"] = "\n".join(self.tb_extractor.traceback)
|
kw["Traceback"] = "\n".join(self.tb_extractor.traceback)
|
||||||
crash_report = self.app.make_apport_report(
|
self.error_ref = self.app.make_apport_report(
|
||||||
ErrorReportKind.INSTALL_FAIL, "install failed", interrupt=False,
|
ErrorReportKind.INSTALL_FAIL, "install failed", **kw).ref()
|
||||||
**kw)
|
|
||||||
if crash_report is not None:
|
|
||||||
self.crash_report_ref = crash_report.ref()
|
|
||||||
self.progress_view.finish_all()
|
|
||||||
self.progress_view.set_status(
|
|
||||||
('info_error', _("An error has occurred")))
|
|
||||||
if not self.showing:
|
|
||||||
self.app.controllers.index = self.controller_index - 1
|
|
||||||
self.app.next_screen()
|
|
||||||
self.update_state(InstallState.ERROR)
|
|
||||||
if self.crash_report_ref is not None:
|
|
||||||
self.app.show_error_report(self.crash_report_ref)
|
|
||||||
|
|
||||||
def logged_command(self, cmd):
|
def logged_command(self, cmd):
|
||||||
return ['systemd-cat', '--level-prefix=false',
|
return ['systemd-cat', '--level-prefix=false',
|
||||||
'--identifier=' + self.app.log_syslog_id] + cmd
|
'--identifier=' + self.app.log_syslog_id] + cmd
|
||||||
|
|
||||||
def log_event(self, event):
|
|
||||||
self.curtin_log(event)
|
|
||||||
|
|
||||||
def curtin_event(self, event):
|
def curtin_event(self, event):
|
||||||
e = {
|
e = {
|
||||||
"EVENT_TYPE": "???",
|
"EVENT_TYPE": "???",
|
||||||
|
@ -189,7 +170,7 @@ class InstallProgressController(SubiquityTuiController):
|
||||||
if curtin_ctx is not None:
|
if curtin_ctx is not None:
|
||||||
curtin_ctx.exit(result=status)
|
curtin_ctx.exit(result=status)
|
||||||
|
|
||||||
def curtin_log(self, event):
|
def log_event(self, event):
|
||||||
self.tb_extractor.feed(event['MESSAGE'])
|
self.tb_extractor.feed(event['MESSAGE'])
|
||||||
|
|
||||||
def _write_config(self, path, config):
|
def _write_config(self, path, config):
|
||||||
|
@ -202,7 +183,7 @@ class InstallProgressController(SubiquityTuiController):
|
||||||
def _get_curtin_command(self):
|
def _get_curtin_command(self):
|
||||||
config_file_name = 'subiquity-curtin-install.conf'
|
config_file_name = 'subiquity-curtin-install.conf'
|
||||||
|
|
||||||
if self.opts.dry_run:
|
if self.app.opts.dry_run:
|
||||||
config_location = os.path.join('.subiquity/', config_file_name)
|
config_location = os.path.join('.subiquity/', config_file_name)
|
||||||
log_location = '.subiquity/install.log'
|
log_location = '.subiquity/install.log'
|
||||||
event_file = "examples/curtin-events.json"
|
event_file = "examples/curtin-events.json"
|
||||||
|
@ -219,9 +200,9 @@ class InstallProgressController(SubiquityTuiController):
|
||||||
config_location, 'install']
|
config_location, 'install']
|
||||||
log_location = INSTALL_LOG
|
log_location = INSTALL_LOG
|
||||||
|
|
||||||
self._write_config(
|
ident = self._event_syslog_id
|
||||||
config_location,
|
self._write_config(config_location,
|
||||||
self.model.render(syslog_identifier=self._event_syslog_id))
|
self.model.render(syslog_identifier=ident))
|
||||||
|
|
||||||
self.app.note_file_for_apport("CurtinConfig", config_location)
|
self.app.note_file_for_apport("CurtinConfig", config_location)
|
||||||
self.app.note_file_for_apport("CurtinLog", log_location)
|
self.app.note_file_for_apport("CurtinLog", log_location)
|
||||||
|
@ -235,10 +216,10 @@ class InstallProgressController(SubiquityTuiController):
|
||||||
sys.executable, '-m', 'curtin', 'unmount',
|
sys.executable, '-m', 'curtin', 'unmount',
|
||||||
'-t', target,
|
'-t', target,
|
||||||
]
|
]
|
||||||
if self.opts.dry_run:
|
if self.app.opts.dry_run:
|
||||||
cmd = ['sleep', str(0.2/self.app.scale_factor)]
|
cmd = ['sleep', str(0.2/self.app.scale_factor)]
|
||||||
await arun_command(cmd)
|
await arun_command(cmd)
|
||||||
if not self.opts.dry_run:
|
if not self.app.opts.dry_run:
|
||||||
shutil.rmtree(target)
|
shutil.rmtree(target)
|
||||||
|
|
||||||
@with_context(
|
@with_context(
|
||||||
|
@ -250,7 +231,7 @@ class InstallProgressController(SubiquityTuiController):
|
||||||
loop = self.app.aio_loop
|
loop = self.app.aio_loop
|
||||||
|
|
||||||
fds = [
|
fds = [
|
||||||
journald_listen(loop, [self.app.log_syslog_id], self.curtin_log),
|
journald_listen(loop, [self.app.log_syslog_id], self.log_event),
|
||||||
journald_listen(loop, [self._event_syslog_id], self.curtin_event),
|
journald_listen(loop, [self._event_syslog_id], self.curtin_event),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -258,32 +239,24 @@ class InstallProgressController(SubiquityTuiController):
|
||||||
|
|
||||||
log.debug('curtin install cmd: {}'.format(curtin_cmd))
|
log.debug('curtin install cmd: {}'.format(curtin_cmd))
|
||||||
|
|
||||||
async with self.app.install_lock_file.exclusive():
|
try:
|
||||||
try:
|
cp = await arun_command(
|
||||||
our_tty = os.ttyname(0)
|
self.logged_command(curtin_cmd), check=True)
|
||||||
except OSError:
|
finally:
|
||||||
# This is a gross hack for testing in travis.
|
for fd in fds:
|
||||||
our_tty = "/dev/not a tty"
|
loop.remove_reader(fd)
|
||||||
self.app.install_lock_file.write_content(our_tty)
|
|
||||||
journal.send("starting install", SYSLOG_IDENTIFIER="subiquity")
|
|
||||||
try:
|
|
||||||
cp = await arun_command(
|
|
||||||
self.logged_command(curtin_cmd), check=True)
|
|
||||||
finally:
|
|
||||||
for fd in fds:
|
|
||||||
loop.remove_reader(fd)
|
|
||||||
|
|
||||||
log.debug('curtin_install completed: %s', cp.returncode)
|
log.debug('curtin_install completed: %s', cp.returncode)
|
||||||
|
|
||||||
def cancel(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@with_context()
|
@with_context()
|
||||||
async def install(self, *, context):
|
async def install(self, *, context):
|
||||||
context.set('is-install-context', True)
|
context.set('is-install-context', True)
|
||||||
try:
|
try:
|
||||||
await asyncio.wait(
|
await asyncio.wait({e.wait() for e in self.model.install_events})
|
||||||
{e.wait() for e in self.model.install_events})
|
|
||||||
|
if not self.app.interactive():
|
||||||
|
if 'autoinstall' in self.app.kernel_cmdline:
|
||||||
|
self.model.confirm()
|
||||||
|
|
||||||
self.update_state(InstallState.NEEDS_CONFIRMATION)
|
self.update_state(InstallState.NEEDS_CONFIRMATION)
|
||||||
|
|
||||||
|
@ -315,16 +288,10 @@ class InstallProgressController(SubiquityTuiController):
|
||||||
self.update_state(InstallState.DONE)
|
self.update_state(InstallState.DONE)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.curtin_error()
|
self.curtin_error()
|
||||||
if not self.interactive():
|
|
||||||
raise
|
|
||||||
|
|
||||||
async def move_on(self):
|
|
||||||
await self.install_task
|
|
||||||
self.app.next_screen()
|
|
||||||
|
|
||||||
async def drain_curtin_events(self, *, context):
|
async def drain_curtin_events(self, *, context):
|
||||||
waited = 0.0
|
waited = 0.0
|
||||||
while self.progress_view.ongoing and waited < 5.0:
|
while len(self.curtin_event_contexts) > 1 and waited < 5.0:
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
waited += 0.1
|
waited += 0.1
|
||||||
log.debug("waited %s seconds for events to drain", waited)
|
log.debug("waited %s seconds for events to drain", waited)
|
||||||
|
@ -356,7 +323,7 @@ class InstallProgressController(SubiquityTuiController):
|
||||||
name="install_{package}",
|
name="install_{package}",
|
||||||
description="installing {package}")
|
description="installing {package}")
|
||||||
async def install_package(self, *, context, package):
|
async def install_package(self, *, context, package):
|
||||||
if self.opts.dry_run:
|
if self.app.opts.dry_run:
|
||||||
cmd = ["sleep", str(2/self.app.scale_factor)]
|
cmd = ["sleep", str(2/self.app.scale_factor)]
|
||||||
else:
|
else:
|
||||||
cmd = [
|
cmd = [
|
||||||
|
@ -368,7 +335,7 @@ class InstallProgressController(SubiquityTuiController):
|
||||||
|
|
||||||
@with_context(description="restoring apt configuration")
|
@with_context(description="restoring apt configuration")
|
||||||
async def restore_apt_config(self, context):
|
async def restore_apt_config(self, context):
|
||||||
if self.opts.dry_run:
|
if self.app.opts.dry_run:
|
||||||
cmds = [["sleep", str(1/self.app.scale_factor)]]
|
cmds = [["sleep", str(1/self.app.scale_factor)]]
|
||||||
else:
|
else:
|
||||||
cmds = [
|
cmds = [
|
||||||
|
@ -395,7 +362,7 @@ class InstallProgressController(SubiquityTuiController):
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env["APT_CONFIG"] = apt_conf.name[len(self.model.target):]
|
env["APT_CONFIG"] = apt_conf.name[len(self.model.target):]
|
||||||
self.unattended_upgrades_ctx = context
|
self.unattended_upgrades_ctx = context
|
||||||
if self.opts.dry_run:
|
if self.app.opts.dry_run:
|
||||||
self.unattended_upgrades_proc = await astart_command(
|
self.unattended_upgrades_proc = await astart_command(
|
||||||
self.logged_command(
|
self.logged_command(
|
||||||
["sleep", str(5/self.app.scale_factor)]), env=env)
|
["sleep", str(5/self.app.scale_factor)]), env=env)
|
||||||
|
@ -411,11 +378,10 @@ class InstallProgressController(SubiquityTuiController):
|
||||||
os.remove(apt_conf.name)
|
os.remove(apt_conf.name)
|
||||||
|
|
||||||
async def stop_unattended_upgrades(self):
|
async def stop_unattended_upgrades(self):
|
||||||
self.progress_view.event_finish(self.unattended_upgrades_ctx)
|
|
||||||
with self.unattended_upgrades_ctx.parent.child(
|
with self.unattended_upgrades_ctx.parent.child(
|
||||||
"stop_unattended_upgrades",
|
"stop_unattended_upgrades",
|
||||||
"cancelling update"):
|
"cancelling update"):
|
||||||
if self.opts.dry_run:
|
if self.app.opts.dry_run:
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
self.unattended_upgrades_proc.terminate()
|
self.unattended_upgrades_proc.terminate()
|
||||||
else:
|
else:
|
||||||
|
@ -426,22 +392,6 @@ class InstallProgressController(SubiquityTuiController):
|
||||||
'--stop-only',
|
'--stop-only',
|
||||||
]), check=True)
|
]), check=True)
|
||||||
|
|
||||||
async def _click_reboot(self):
|
|
||||||
if self.unattended_upgrades_ctx is not None:
|
|
||||||
self.update_state(InstallState.UU_CANCELLING)
|
|
||||||
await self.stop_unattended_upgrades()
|
|
||||||
self.reboot_clicked.set()
|
|
||||||
|
|
||||||
def click_reboot(self):
|
|
||||||
schedule_task(self._click_reboot())
|
|
||||||
|
|
||||||
def make_ui(self):
|
|
||||||
schedule_task(self.move_on())
|
|
||||||
return self.progress_view
|
|
||||||
|
|
||||||
def run_answers(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
uu_apt_conf = """\
|
uu_apt_conf = """\
|
||||||
# Config for the unattended-upgrades run to avoid failing on battery power or
|
# Config for the unattended-upgrades run to avoid failing on battery power or
|
|
@ -111,7 +111,9 @@ class SubiquityServer(Application):
|
||||||
|
|
||||||
project = "subiquity"
|
project = "subiquity"
|
||||||
from subiquity.server import controllers as controllers_mod
|
from subiquity.server import controllers as controllers_mod
|
||||||
controllers = []
|
controllers = [
|
||||||
|
"Install",
|
||||||
|
]
|
||||||
|
|
||||||
def make_model(self):
|
def make_model(self):
|
||||||
root = '/'
|
root = '/'
|
||||||
|
|
|
@ -3,15 +3,15 @@ from unittest import mock
|
||||||
|
|
||||||
from subiquitycore.testing import view_helpers
|
from subiquitycore.testing import view_helpers
|
||||||
|
|
||||||
|
from subiquity.client.controllers.progress import ProgressController
|
||||||
from subiquity.common.types import InstallState
|
from subiquity.common.types import InstallState
|
||||||
from subiquity.controllers.installprogress import InstallProgressController
|
|
||||||
from subiquity.ui.views.installprogress import ProgressView
|
from subiquity.ui.views.installprogress import ProgressView
|
||||||
|
|
||||||
|
|
||||||
class IdentityViewTests(unittest.TestCase):
|
class IdentityViewTests(unittest.TestCase):
|
||||||
|
|
||||||
def make_view(self):
|
def make_view(self):
|
||||||
controller = mock.create_autospec(spec=InstallProgressController)
|
controller = mock.create_autospec(spec=ProgressController)
|
||||||
controller.app = mock.Mock()
|
controller.app = mock.Mock()
|
||||||
controller.app.aio_loop = None
|
controller.app.aio_loop = None
|
||||||
return ProgressView(controller)
|
return ProgressView(controller)
|
||||||
|
|
Loading…
Reference in New Issue