smoosh InstallState into ApplicationState
Having these be separate was slightly confusing, particular as I need to add an "ERROR" state that isn't necessarily due to the install step itself failing. Now we have one enum for the overall status of the installer and a separate field that indicates whether the client should be in interactive or non-interactive mode (or "not-yet-known" which is handled more or less like non-interactive mode).
This commit is contained in:
parent
4798b7f9a5
commit
0ad21b7c96
35
DESIGN.md
35
DESIGN.md
|
@ -168,8 +168,8 @@ The API takes a "long poll" approach to status updates. For example,
|
|||
`wait=False` and if the result indicates that the check for updates is still in
|
||||
progress it shows a screen indicating this and calls it again with `wait=True`,
|
||||
which will not return until the check has completed (or failed). In a similar
|
||||
vein, `install.status.GET()` takes an argument indicating what the client
|
||||
thinks the install state currently is and will block until that changes.
|
||||
vein, `meta.status.GET()` takes an argument indicating what the client
|
||||
thinks the application state currently is and will block until that changes.
|
||||
|
||||
### Examples and common patterns
|
||||
|
||||
|
@ -332,24 +332,31 @@ controllers have methods that are called to load and apply the autoinstall data
|
|||
for each controller. The only real difference to the client is that it behaves
|
||||
totally differently if the install is to be totally automated: in this case it
|
||||
does not start the urwid-based UI at all and mostly just "listens" to install
|
||||
progress via journald and the `install.status.GET()` API call.
|
||||
progress via journald and the `meta.status.GET()` API call.
|
||||
|
||||
### Starting and confirming the install
|
||||
### The server state machine
|
||||
|
||||
The installation code proceeds in stages:
|
||||
The server code proceeds in stages:
|
||||
|
||||
1. First it waits for all the model objects that feed into the curtin config
|
||||
to be configured.
|
||||
2. It waits for confirmation.
|
||||
3. It runs "curtin install" and waits for that to finish.
|
||||
4. It waits for the model objects that feed into the cloud-init config to be
|
||||
1. It starts up, checks for an autoinstall config and runs any early
|
||||
commands.
|
||||
2. Then it waits for all the model objects that feed into the curtin
|
||||
config to be configured.
|
||||
3. It waits for confirmation.
|
||||
4. It runs "curtin install" and waits for that to finish.
|
||||
5. It waits for the model objects that feed into the cloud-init config to be
|
||||
configured.
|
||||
5. If there appears to be a working network connection, it downloads and
|
||||
6. It creates the cloud-init config for the first boot of the
|
||||
installed system.
|
||||
7. If there appears to be a working network connection, it downloads and
|
||||
installs security updates.
|
||||
6. It waits for the user to click "reboot".
|
||||
8. It runs any late commands.
|
||||
9. It waits for the user to click "reboot".
|
||||
|
||||
Each of these states gets a different value of the `InstallState` enum, so the
|
||||
client gets notified via long-polling `install.status.GET()` of progress.
|
||||
Each of these states gets a different value of the `ApplicationState`
|
||||
enum, so the client gets notified via long-polling `meta.status.GET()`
|
||||
of progress. In addition, `ApplicationState.ERROR` indicates something
|
||||
has gone wrong.
|
||||
|
||||
### Refreshing the snap
|
||||
|
||||
|
|
|
@ -46,7 +46,6 @@ from subiquity.common.types import (
|
|||
ApplicationState,
|
||||
ErrorReportKind,
|
||||
ErrorReportRef,
|
||||
InstallState,
|
||||
)
|
||||
from subiquity.journald import journald_listen
|
||||
from subiquity.ui.frame import SubiquityUI
|
||||
|
@ -206,24 +205,24 @@ class SubiquityClient(TuiApplication):
|
|||
answer = await run_in_thread(input)
|
||||
await self.confirm_install()
|
||||
|
||||
async def noninteractive_watch_install_state(self):
|
||||
install_state = None
|
||||
async def noninteractive_watch_app_state(self, initial_status):
|
||||
app_status = initial_status
|
||||
confirm_task = None
|
||||
while True:
|
||||
try:
|
||||
install_status = await self.client.install.status.GET(
|
||||
cur=install_state)
|
||||
install_state = install_status.state
|
||||
except aiohttp.ClientError:
|
||||
await asyncio.sleep(1)
|
||||
continue
|
||||
if install_state == InstallState.NEEDS_CONFIRMATION:
|
||||
if confirm_task is not None:
|
||||
app_state = app_status.state
|
||||
if app_state == ApplicationState.NEEDS_CONFIRMATION:
|
||||
if confirm_task is None:
|
||||
confirm_task = self.aio_loop.create_task(
|
||||
self.noninteractive_confirmation())
|
||||
elif confirm_task is not None:
|
||||
confirm_task.cancel()
|
||||
confirm_task = None
|
||||
try:
|
||||
app_status = await self.client.meta.status.GET(
|
||||
cur=app_state)
|
||||
except aiohttp.ClientError:
|
||||
await asyncio.sleep(1)
|
||||
continue
|
||||
|
||||
def subiquity_event_noninteractive(self, event):
|
||||
if event['SUBIQUITY_EVENT_TYPE'] == 'start':
|
||||
|
@ -244,28 +243,20 @@ class SubiquityClient(TuiApplication):
|
|||
print(".", end='', flush=True)
|
||||
else:
|
||||
break
|
||||
print()
|
||||
print("\nconnected")
|
||||
journald_listen(
|
||||
self.aio_loop,
|
||||
[status.echo_syslog_id],
|
||||
lambda e: print(e['MESSAGE']))
|
||||
if status.state == ApplicationState.STARTING:
|
||||
print("server is starting...", end='', flush=True)
|
||||
while status.state == ApplicationState.STARTING:
|
||||
await asyncio.sleep(1)
|
||||
print(".", end='', flush=True)
|
||||
status = await self.client.meta.status.GET()
|
||||
print()
|
||||
if status.state == ApplicationState.EARLY_COMMANDS:
|
||||
print("running early commands...")
|
||||
if status.state == ApplicationState.STARTING_UP:
|
||||
status = await self.client.meta.status.GET(cur=status.state)
|
||||
await asyncio.sleep(0.5)
|
||||
return status
|
||||
|
||||
async def start(self):
|
||||
status = await self.connect()
|
||||
if status.state == ApplicationState.INTERACTIVE:
|
||||
self.interactive = True
|
||||
self.interactive = status.interactive
|
||||
if self.interactive:
|
||||
await super().start()
|
||||
journald_listen(
|
||||
self.aio_loop,
|
||||
|
@ -283,7 +274,6 @@ class SubiquityClient(TuiApplication):
|
|||
self.show_error_report(report.ref())
|
||||
break
|
||||
else:
|
||||
self.interactive = False
|
||||
if self.opts.run_on_serial:
|
||||
# Thanks to the fact that we are launched with agetty's
|
||||
# --skip-login option, on serial lines we can end up starting
|
||||
|
@ -299,7 +289,7 @@ class SubiquityClient(TuiApplication):
|
|||
self.subiquity_event_noninteractive,
|
||||
seek=True)
|
||||
self.aio_loop.create_task(
|
||||
self.noninteractive_watch_install_state())
|
||||
self.noninteractive_watch_app_state(status))
|
||||
|
||||
def _exception_handler(self, loop, context):
|
||||
exc = context.get('exception')
|
||||
|
|
|
@ -21,7 +21,7 @@ import aiohttp
|
|||
from subiquitycore.context import with_context
|
||||
|
||||
from subiquity.client.controller import SubiquityTuiController
|
||||
from subiquity.common.types import InstallState
|
||||
from subiquity.common.types import ApplicationState
|
||||
from subiquity.ui.views.installprogress import (
|
||||
InstallRunning,
|
||||
ProgressView,
|
||||
|
@ -33,12 +33,10 @@ 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.app_state = None
|
||||
self.crash_report_ref = None
|
||||
self.answers = app.answers.get("InstallProgress", {})
|
||||
|
||||
|
@ -77,43 +75,43 @@ class ProgressController(SubiquityTuiController):
|
|||
install_running = None
|
||||
while True:
|
||||
try:
|
||||
install_status = await self.endpoint.status.GET(
|
||||
cur=self.install_state)
|
||||
app_status = await self.app.client.meta.status.GET(
|
||||
cur=self.app_state)
|
||||
except aiohttp.ClientError:
|
||||
await asyncio.sleep(1)
|
||||
continue
|
||||
self.install_state = install_status.state
|
||||
self.app_state = app_status.state
|
||||
|
||||
self.progress_view.update_for_state(self.install_state)
|
||||
self.progress_view.update_for_state(self.app_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 app_status.error is not None:
|
||||
if self.crash_report_ref is None:
|
||||
self.crash_report_ref = install_status.error
|
||||
self.crash_report_ref = app_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.app_state == ApplicationState.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:
|
||||
if self.app_state == ApplicationState.RUNNING:
|
||||
if app_status.confirming_tty != self.app.our_tty:
|
||||
install_running = InstallRunning(
|
||||
self.app, install_status.confirming_tty)
|
||||
self.app, app_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.app_state == ApplicationState.DONE:
|
||||
if self.answers.get('reboot', False):
|
||||
self.click_reboot()
|
||||
|
||||
def make_ui(self):
|
||||
if self.install_state == InstallState.NEEDS_CONFIRMATION:
|
||||
if self.app_state == ApplicationState.NEEDS_CONFIRMATION:
|
||||
self.app.show_confirm_install()
|
||||
return self.progress_view
|
||||
|
||||
|
|
|
@ -29,8 +29,6 @@ from subiquity.common.types import (
|
|||
ErrorReportRef,
|
||||
KeyboardSetting,
|
||||
IdentityData,
|
||||
InstallState,
|
||||
InstallStatus,
|
||||
RefreshStatus,
|
||||
SnapInfo,
|
||||
SnapListResponse,
|
||||
|
@ -79,6 +77,7 @@ class API:
|
|||
class crash:
|
||||
def GET() -> None:
|
||||
"""Requests to this method will fail with a HTTP 500."""
|
||||
|
||||
class refresh:
|
||||
def GET(wait: bool = False) -> RefreshStatus:
|
||||
"""Get information about the snap refresh status.
|
||||
|
@ -182,10 +181,6 @@ class API:
|
|||
class snap_info:
|
||||
def GET(snap_name: str) -> SnapInfo: ...
|
||||
|
||||
class install:
|
||||
class status:
|
||||
def GET(cur: Optional[InstallState] = None) -> InstallStatus: ...
|
||||
|
||||
class reboot:
|
||||
def POST(): ...
|
||||
|
||||
|
|
|
@ -25,22 +25,6 @@ from typing import List, Optional
|
|||
import attr
|
||||
|
||||
|
||||
class ApplicationState(enum.Enum):
|
||||
STARTING = enum.auto()
|
||||
EARLY_COMMANDS = enum.auto()
|
||||
INTERACTIVE = enum.auto()
|
||||
NON_INTERACTIVE = enum.auto()
|
||||
|
||||
|
||||
@attr.s(auto_attribs=True)
|
||||
class ApplicationStatus:
|
||||
state: ApplicationState
|
||||
cloud_init_ok: bool
|
||||
echo_syslog_id: str
|
||||
log_syslog_id: str
|
||||
event_syslog_id: str
|
||||
|
||||
|
||||
class ErrorReportState(enum.Enum):
|
||||
INCOMPLETE = enum.auto()
|
||||
LOADING = enum.auto()
|
||||
|
@ -68,6 +52,31 @@ class ErrorReportRef:
|
|||
oops_id: Optional[str]
|
||||
|
||||
|
||||
class ApplicationState(enum.Enum):
|
||||
STARTING_UP = enum.auto()
|
||||
WAITING = enum.auto()
|
||||
NEEDS_CONFIRMATION = enum.auto()
|
||||
RUNNING = enum.auto()
|
||||
POST_WAIT = enum.auto()
|
||||
POST_RUNNING = enum.auto()
|
||||
UU_RUNNING = enum.auto()
|
||||
UU_CANCELLING = enum.auto()
|
||||
DONE = enum.auto()
|
||||
ERROR = enum.auto()
|
||||
|
||||
|
||||
@attr.s(auto_attribs=True)
|
||||
class ApplicationStatus:
|
||||
state: ApplicationState
|
||||
confirming_tty: str
|
||||
error: Optional[ErrorReportRef]
|
||||
cloud_init_ok: bool
|
||||
interactive: Optional[bool]
|
||||
echo_syslog_id: str
|
||||
log_syslog_id: str
|
||||
event_syslog_id: str
|
||||
|
||||
|
||||
class RefreshCheckState(enum.Enum):
|
||||
UNKNOWN = enum.auto()
|
||||
AVAILABLE = enum.auto()
|
||||
|
@ -195,22 +204,3 @@ class SnapListResponse:
|
|||
status: SnapCheckState
|
||||
snaps: List[SnapInfo] = attr.Factory(list)
|
||||
selections: List[SnapSelection] = attr.Factory(list)
|
||||
|
||||
|
||||
class InstallState(enum.Enum):
|
||||
NOT_STARTED = enum.auto()
|
||||
NEEDS_CONFIRMATION = enum.auto()
|
||||
RUNNING = enum.auto()
|
||||
POST_WAIT = enum.auto()
|
||||
POST_RUNNING = enum.auto()
|
||||
UU_RUNNING = enum.auto()
|
||||
UU_CANCELLING = enum.auto()
|
||||
DONE = enum.auto()
|
||||
ERROR = enum.auto()
|
||||
|
||||
|
||||
@attr.s(auto_attribs=True)
|
||||
class InstallStatus:
|
||||
state: InstallState
|
||||
confirming_tty: str = ''
|
||||
error: Optional[ErrorReportRef] = None
|
||||
|
|
|
@ -21,7 +21,7 @@ from systemd import journal
|
|||
from subiquitycore.context import with_context
|
||||
from subiquitycore.utils import arun_command
|
||||
|
||||
from subiquity.common.types import InstallState
|
||||
from subiquity.common.types import ApplicationState
|
||||
from subiquity.server.controller import NonInteractiveController
|
||||
|
||||
|
||||
|
@ -102,7 +102,7 @@ class LateController(CmdListController):
|
|||
async def _run(self):
|
||||
Install = self.app.controllers.Install
|
||||
await Install.install_task
|
||||
if Install.install_state == InstallState.DONE:
|
||||
if self.app.state == ApplicationState.DONE:
|
||||
await self.run()
|
||||
|
||||
|
||||
|
@ -113,7 +113,7 @@ class ErrorController(CmdListController):
|
|||
|
||||
@with_context()
|
||||
async def run(self, context):
|
||||
if self.app.interactive():
|
||||
if self.app.interactive:
|
||||
self.syslog_id = self.app.log_syslog_id
|
||||
else:
|
||||
self.syslog_id = self.app.echo_syslog_id
|
||||
|
|
|
@ -21,7 +21,6 @@ import re
|
|||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
from typing import Optional
|
||||
|
||||
from curtin.commands.install import (
|
||||
ERROR_TARFILE,
|
||||
|
@ -40,14 +39,12 @@ from subiquitycore.utils import (
|
|||
astart_command,
|
||||
)
|
||||
|
||||
from subiquity.common.apidef import API
|
||||
from subiquity.common.errorreport import ErrorReportKind
|
||||
from subiquity.server.controller import (
|
||||
SubiquityController,
|
||||
)
|
||||
from subiquity.common.types import (
|
||||
InstallState,
|
||||
InstallStatus,
|
||||
ApplicationState,
|
||||
)
|
||||
from subiquity.journald import journald_listen
|
||||
|
||||
|
@ -75,14 +72,9 @@ class TracebackExtractor:
|
|||
|
||||
class InstallController(SubiquityController):
|
||||
|
||||
endpoint = API.install
|
||||
|
||||
def __init__(self, app):
|
||||
super().__init__(app)
|
||||
self.model = app.base_model
|
||||
self._install_state = InstallState.NOT_STARTED
|
||||
self._install_state_event = asyncio.Event()
|
||||
self.error_ref = None
|
||||
|
||||
self.unattended_upgrades_proc = None
|
||||
self.unattended_upgrades_ctx = None
|
||||
|
@ -90,48 +82,27 @@ class InstallController(SubiquityController):
|
|||
self.tb_extractor = TracebackExtractor()
|
||||
self.curtin_event_contexts = {}
|
||||
|
||||
def interactive(self):
|
||||
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)
|
||||
if self.app.state == ApplicationState.UU_RUNNING:
|
||||
self.app.update_state(ApplicationState.UU_CANCELLING)
|
||||
self.app.aio_loop.create_task(self.stop_unattended_upgrades())
|
||||
|
||||
def start(self):
|
||||
self.install_task = self.app.aio_loop.create_task(self.install())
|
||||
|
||||
@property
|
||||
def install_state(self):
|
||||
return self._install_state
|
||||
|
||||
def update_state(self, state):
|
||||
self._install_state_event.set()
|
||||
self._install_state_event.clear()
|
||||
self._install_state = state
|
||||
|
||||
def tpath(self, *path):
|
||||
return os.path.join(self.model.target, *path)
|
||||
|
||||
def curtin_error(self):
|
||||
self.update_state(InstallState.ERROR)
|
||||
kw = {}
|
||||
if sys.exc_info()[0] is not None:
|
||||
log.exception("curtin_error")
|
||||
# send traceback.format_exc() to journal?
|
||||
if self.tb_extractor.traceback:
|
||||
kw["Traceback"] = "\n".join(self.tb_extractor.traceback)
|
||||
self.error_ref = self.app.make_apport_report(
|
||||
ErrorReportKind.INSTALL_FAIL, "install failed", **kw).ref()
|
||||
self.app.fatal_error = self.app.make_apport_report(
|
||||
ErrorReportKind.INSTALL_FAIL, "install failed", **kw)
|
||||
self.app.update_state(ApplicationState.ERROR)
|
||||
|
||||
def logged_command(self, cmd):
|
||||
return ['systemd-cat', '--level-prefix=false',
|
||||
|
@ -254,15 +225,15 @@ class InstallController(SubiquityController):
|
|||
try:
|
||||
await asyncio.wait({e.wait() for e in self.model.install_events})
|
||||
|
||||
if not self.app.interactive():
|
||||
if not self.app.interactive:
|
||||
if 'autoinstall' in self.app.kernel_cmdline:
|
||||
self.model.confirm()
|
||||
|
||||
self.update_state(InstallState.NEEDS_CONFIRMATION)
|
||||
self.app.update_state(ApplicationState.NEEDS_CONFIRMATION)
|
||||
|
||||
await self.model.confirmation.wait()
|
||||
|
||||
self.update_state(InstallState.RUNNING)
|
||||
self.app.update_state(ApplicationState.RUNNING)
|
||||
|
||||
if os.path.exists(self.model.target):
|
||||
await self.unmount_target(
|
||||
|
@ -270,22 +241,22 @@ class InstallController(SubiquityController):
|
|||
|
||||
await self.curtin_install(context=context)
|
||||
|
||||
self.update_state(InstallState.POST_WAIT)
|
||||
self.app.update_state(ApplicationState.POST_WAIT)
|
||||
|
||||
await asyncio.wait(
|
||||
{e.wait() for e in self.model.postinstall_events})
|
||||
|
||||
await self.drain_curtin_events(context=context)
|
||||
|
||||
self.update_state(InstallState.POST_RUNNING)
|
||||
self.app.update_state(ApplicationState.POST_RUNNING)
|
||||
|
||||
await self.postinstall(context=context)
|
||||
|
||||
if self.model.network.has_network:
|
||||
self.update_state(InstallState.UU_RUNNING)
|
||||
self.app.update_state(ApplicationState.UU_RUNNING)
|
||||
await self.run_unattended_upgrades(context=context)
|
||||
|
||||
self.update_state(InstallState.DONE)
|
||||
self.app.update_state(ApplicationState.DONE)
|
||||
except Exception:
|
||||
self.curtin_error()
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ class LocaleController(SubiquityController):
|
|||
autoinstall_default = 'en_US.UTF-8'
|
||||
|
||||
def interactive(self):
|
||||
return self.app.interactive()
|
||||
return self.app.interactive
|
||||
|
||||
def load_autoinstall_data(self, data):
|
||||
os.environ["LANG"] = data
|
||||
|
|
|
@ -24,7 +24,7 @@ from subiquitycore.utils import arun_command, run_command
|
|||
|
||||
from subiquity.common.apidef import API
|
||||
from subiquity.server.controller import SubiquityController
|
||||
from subiquity.server.controllers.install import InstallState
|
||||
from subiquity.server.controllers.install import ApplicationState
|
||||
|
||||
log = logging.getLogger("subiquity.controllers.restart")
|
||||
|
||||
|
@ -51,10 +51,10 @@ class RebootController(SubiquityController):
|
|||
await Install.install_task
|
||||
await self.app.controllers.Late.run_event.wait()
|
||||
await self.copy_logs_to_target()
|
||||
if self.app.interactive():
|
||||
if self.app.interactive:
|
||||
await self.user_reboot_event.wait()
|
||||
self.reboot()
|
||||
elif Install.install_state == InstallState.DONE:
|
||||
elif self.app.state == ApplicationState.DONE:
|
||||
self.reboot()
|
||||
|
||||
@with_context()
|
||||
|
|
|
@ -69,7 +69,7 @@ class ReportingController(NonInteractiveController):
|
|||
app.add_event_listener(self)
|
||||
|
||||
def load_autoinstall_data(self, data):
|
||||
if self.app.interactive():
|
||||
if self.app.interactive:
|
||||
return
|
||||
self.config.update(copy.deepcopy(NON_INTERACTIVE_CONFIG))
|
||||
if data is not None:
|
||||
|
|
|
@ -47,7 +47,6 @@ from subiquity.common.types import (
|
|||
ApplicationState,
|
||||
ApplicationStatus,
|
||||
ErrorReportRef,
|
||||
InstallState,
|
||||
)
|
||||
from subiquity.server.controller import SubiquityController
|
||||
from subiquity.models.subiquity import SubiquityModel
|
||||
|
@ -73,8 +72,11 @@ class MetaController:
|
|||
if cur == self.app.state:
|
||||
await self.app.state_event.wait()
|
||||
return ApplicationStatus(
|
||||
self.app.state,
|
||||
state=self.app.state,
|
||||
confirming_tty=self.app.confirming_tty,
|
||||
error=self.app.fatal_error,
|
||||
cloud_init_ok=self.app.cloud_init_ok,
|
||||
interactive=self.app.interactive,
|
||||
echo_syslog_id=self.app.echo_syslog_id,
|
||||
event_syslog_id=self.app.event_syslog_id,
|
||||
log_syslog_id=self.app.log_syslog_id)
|
||||
|
@ -145,9 +147,11 @@ class SubiquityServer(Application):
|
|||
super().__init__(opts)
|
||||
self.block_log_dir = block_log_dir
|
||||
self.cloud_init_ok = cloud_init_ok
|
||||
self._state = ApplicationState.STARTING
|
||||
self._state = ApplicationState.STARTING_UP
|
||||
self.state_event = asyncio.Event()
|
||||
self.interactive = None
|
||||
self.confirming_tty = ''
|
||||
self.fatal_error = None
|
||||
|
||||
self.echo_syslog_id = 'subiquity_echo.{}'.format(os.getpid())
|
||||
self.event_syslog_id = 'subiquity_event.{}'.format(os.getpid())
|
||||
|
@ -184,14 +188,14 @@ class SubiquityServer(Application):
|
|||
self.event_listeners.append(listener)
|
||||
|
||||
def _maybe_push_to_journal(self, event_type, context, description):
|
||||
if not context.get('is-install-context') and self.interactive():
|
||||
if not context.get('is-install-context') and self.interactive:
|
||||
controller = context.get('controller')
|
||||
if controller is None or controller.interactive():
|
||||
return
|
||||
if context.get('request'):
|
||||
return
|
||||
indent = context.full_name().count('/') - 2
|
||||
if context.get('is-install-context') and self.interactive():
|
||||
if context.get('is-install-context') and self.interactive:
|
||||
indent -= 1
|
||||
msg = context.description
|
||||
else:
|
||||
|
@ -241,20 +245,14 @@ class SubiquityServer(Application):
|
|||
return self.error_reporter.make_apport_report(
|
||||
kind, thing, wait=wait, **kw)
|
||||
|
||||
def interactive(self):
|
||||
if not self.autoinstall_config:
|
||||
return True
|
||||
return bool(self.autoinstall_config.get('interactive-sections'))
|
||||
|
||||
@web.middleware
|
||||
async def middleware(self, request, handler):
|
||||
override_status = None
|
||||
controller = await controller_for_request(request)
|
||||
if isinstance(controller, SubiquityController):
|
||||
install_state = self.controllers.Install.install_state
|
||||
if not controller.interactive():
|
||||
override_status = 'skip'
|
||||
elif install_state == InstallState.NEEDS_CONFIRMATION:
|
||||
elif self.state == ApplicationState.NEEDS_CONFIRMATION:
|
||||
if self.base_model.needs_configuration(controller.model_name):
|
||||
override_status = 'confirm'
|
||||
if override_status is not None:
|
||||
|
@ -326,18 +324,19 @@ class SubiquityServer(Application):
|
|||
if self.autoinstall_config and self.controllers.Early.cmds:
|
||||
stamp_file = self.state_path("early-commands")
|
||||
if not os.path.exists(stamp_file):
|
||||
self.update_state(ApplicationState.EARLY_COMMANDS)
|
||||
await self.controllers.Early.run()
|
||||
open(stamp_file, 'w').close()
|
||||
await asyncio.sleep(1)
|
||||
self.load_autoinstall_config(only_early=False)
|
||||
if not self.interactive() and not self.opts.dry_run:
|
||||
if self.autoinstall_config:
|
||||
self.interactive = bool(
|
||||
self.autoinstall_config.get('interactive-sections'))
|
||||
else:
|
||||
self.interactive = True
|
||||
if not self.interactive and not self.opts.dry_run:
|
||||
open('/run/casper-no-prompt', 'w').close()
|
||||
self.load_serialized_state()
|
||||
if self.interactive():
|
||||
self.update_state(ApplicationState.INTERACTIVE)
|
||||
else:
|
||||
self.update_state(ApplicationState.NON_INTERACTIVE)
|
||||
await asyncio.sleep(1)
|
||||
self.update_state(ApplicationState.WAITING)
|
||||
await super().start()
|
||||
await self.apply_autoinstall_config()
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ from subiquitycore.ui.utils import button_pile, Padding, rewrap
|
|||
from subiquitycore.ui.stretchy import Stretchy
|
||||
from subiquitycore.ui.width import widget_width
|
||||
|
||||
from subiquity.common.types import InstallState
|
||||
from subiquity.common.types import ApplicationState
|
||||
|
||||
|
||||
log = logging.getLogger("subiquity.views.installprogress")
|
||||
|
@ -138,22 +138,22 @@ class ProgressView(BaseView):
|
|||
self._set_button_width()
|
||||
|
||||
def update_for_state(self, state):
|
||||
if state == InstallState.NOT_STARTED:
|
||||
if state == ApplicationState.WAITING:
|
||||
self.title = _("Installing system")
|
||||
btns = []
|
||||
elif state == InstallState.NEEDS_CONFIRMATION:
|
||||
elif state == ApplicationState.NEEDS_CONFIRMATION:
|
||||
self.title = _("Installing system")
|
||||
btns = []
|
||||
elif state == InstallState.RUNNING:
|
||||
elif state == ApplicationState.RUNNING:
|
||||
self.title = _("Installing system")
|
||||
btns = [self.view_log_btn]
|
||||
elif state == InstallState.POST_WAIT:
|
||||
elif state == ApplicationState.POST_WAIT:
|
||||
self.title = _("Installing system")
|
||||
btns = [self.view_log_btn]
|
||||
elif state == InstallState.POST_RUNNING:
|
||||
elif state == ApplicationState.POST_RUNNING:
|
||||
self.title = _("Installing system")
|
||||
btns = [self.view_log_btn]
|
||||
elif state == InstallState.UU_RUNNING:
|
||||
elif state == ApplicationState.UU_RUNNING:
|
||||
self.title = _("Install complete!")
|
||||
self.reboot_btn.base_widget.set_label(
|
||||
_("Cancel update and reboot"))
|
||||
|
@ -161,7 +161,7 @@ class ProgressView(BaseView):
|
|||
self.view_log_btn,
|
||||
self.reboot_btn,
|
||||
]
|
||||
elif state == InstallState.UU_CANCELLING:
|
||||
elif state == ApplicationState.UU_CANCELLING:
|
||||
self.title = _("Install complete!")
|
||||
self.reboot_btn.base_widget.set_label(_("Rebooting..."))
|
||||
self.reboot_btn.enabled = False
|
||||
|
@ -169,14 +169,14 @@ class ProgressView(BaseView):
|
|||
self.view_log_btn,
|
||||
self.reboot_btn,
|
||||
]
|
||||
elif state == InstallState.DONE:
|
||||
elif state == ApplicationState.DONE:
|
||||
self.title = _("Install complete!")
|
||||
self.reboot_btn.base_widget.set_label(_("Reboot Now"))
|
||||
btns = [
|
||||
self.view_log_btn,
|
||||
self.reboot_btn,
|
||||
]
|
||||
elif state == InstallState.ERROR:
|
||||
elif state == ApplicationState.ERROR:
|
||||
self.title = _('An error occurred during installation')
|
||||
self.reboot_btn.base_widget.set_label(_("Reboot Now"))
|
||||
self.reboot_btn.enabled = True
|
||||
|
|
|
@ -4,7 +4,7 @@ from unittest import mock
|
|||
from subiquitycore.testing import view_helpers
|
||||
|
||||
from subiquity.client.controllers.progress import ProgressController
|
||||
from subiquity.common.types import InstallState
|
||||
from subiquity.common.types import ApplicationState
|
||||
from subiquity.ui.views.installprogress import ProgressView
|
||||
|
||||
|
||||
|
@ -28,7 +28,7 @@ class IdentityViewTests(unittest.TestCase):
|
|||
view = self.make_view()
|
||||
btn = view_helpers.find_button_matching(view, "^Reboot Now$")
|
||||
self.assertIs(btn, None)
|
||||
view.update_for_state(InstallState.DONE)
|
||||
view.update_for_state(ApplicationState.DONE)
|
||||
btn = view_helpers.find_button_matching(view, "^Reboot Now$")
|
||||
self.assertIsNot(btn, None)
|
||||
view_helpers.click(btn)
|
||||
|
|
Loading…
Reference in New Issue