Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/pr/199'
Browse files Browse the repository at this point in the history
* origin/pr/199: (21 commits)
  updater: add --signal-no-updates flag
  updater: test apply and update flags
  updater: test is_stale(dom0)
  updater: make pylint happy
  updater: fix force updating
  updater: show only one confirmation to the user
  updater: updaate dom0 tests
  updater: make pylint happy
  updater: apply the same selection criteria to dom0
  updater: set progress as 100 if update fails or was cancelled
  updater: make test reproducible
  updater: return 130 if user cancels updating
  updater: get default value of '--update-if-stale' from dom0 settings
  updater: unify cli args with backend updater
  updater: fix selection if the intro page is skipped
  updater: show success dialog if nothing to do
  updater: show success dialog in non-interactive mode
  updater: update tests
  updater: return 100 if no vm was updated
  updater: unify cli options with cli updater
  ...
  • Loading branch information
marmarek committed Jun 28, 2024
2 parents 867b26d + 15dc1f1 commit 8d8839c
Show file tree
Hide file tree
Showing 11 changed files with 570 additions and 140 deletions.
9 changes: 9 additions & 0 deletions locale/en/LC_MESSAGES/desktop-linux-manager.po
Original file line number Diff line number Diff line change
Expand Up @@ -548,3 +548,12 @@ msgstr ""

msgid "failed to update."
msgstr ""

msgid "Qubes OS is up to date."
msgstr ""

msgid "All selected qubes have been updated."
msgstr ""

msgid "There are no updates available for the selected Qubes."
msgstr ""
55 changes: 41 additions & 14 deletions qui/updater/intro_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def refresh_update_list(self, update_if_stale):
if not self.active:
return

cmd = ['qubes-vm-update', '--dry-run',
cmd = ['qubes-vm-update', '--quiet', '--dry-run',
'--update-if-stale', str(update_if_stale)]

to_update = self._get_stale_qubes(cmd)
Expand Down Expand Up @@ -175,12 +175,14 @@ def select_rows(self):
in self.head_checkbox.allowed

def select_rows_ignoring_conditions(self, cliargs, dom0):
cmd = ['qubes-vm-update', '--dry-run']
cmd = ['qubes-vm-update', '--dry-run', '--quiet']

args = [a for a in dir(cliargs) if not a.startswith("_")]
for arg in args:
if arg in ("dom0", "no-restart", "restart", "max_concurrency",
"log"):
if arg in ("non_interactive", "non_default_select",
"dom0",
"restart", "apply_to_sys", "apply_to_all", "no_apply",
"max_concurrency", "log"):
continue
value = getattr(cliargs, arg)
if value:
Expand All @@ -189,13 +191,16 @@ def select_rows_ignoring_conditions(self, cliargs, dom0):
vms_without_dom0 = vms.difference({"dom0"})
if not vms_without_dom0:
continue
value = ",".join(vms_without_dom0)
value = ",".join(sorted(vms_without_dom0))
cmd.append(f"--{arg.replace('_', '-')}")
if isinstance(value, str):
cmd.append(value)
if not isinstance(value, bool):
cmd.append(str(value))

to_update = set()
if cmd[2:]:
non_default_select = [
'--' + arg for arg in cliargs.non_default_select if arg != 'dom0']
non_default = [a for a in cmd if a in non_default_select]
if non_default or cliargs.non_interactive:
to_update = self._get_stale_qubes(cmd)

to_update = self._handle_cli_dom0(dom0, to_update, cliargs)
Expand Down Expand Up @@ -224,19 +229,41 @@ def _get_stale_qubes(self, cmd):

@staticmethod
def _handle_cli_dom0(dom0, to_update, cliargs):
if not cliargs.targets and not cliargs.all:
if bool(dom0.features.get(
'updates-available', False)):
to_update.add('dom0')
if cliargs.dom0 or cliargs.all:
to_update.add("dom0")
default_select = not any(
(getattr(cliargs, arg)
for arg in cliargs.non_default_select if arg != 'all'))
if ((
default_select and cliargs.non_interactive
or cliargs.all
or cliargs.dom0
) and (
cliargs.force_update
or bool(dom0.features.get('updates-available', False))
or is_stale(dom0, cliargs.update_if_stale)
)):
to_update.add('dom0')

if cliargs.targets and "dom0" in cliargs.targets.split(","):
to_update.add("dom0")
if cliargs.skip and "dom0" in cliargs.skip.split(","):
to_update = to_update.difference({"dom0"})
return to_update


def is_stale(vm, expiration_period):
if expiration_period is None:
return False
today = datetime.today()
last_update_str = vm.features.get(
'last-updates-check',
datetime.fromtimestamp(0).strftime('%Y-%m-%d %H:%M:%S')
)
last_update = datetime.fromisoformat(last_update_str)
if (today - last_update).days > expiration_period:
return True
return False


class UpdateRowWrapper(RowWrapper):
COLUMN_NUM = 9
_SELECTION = 1
Expand Down
22 changes: 15 additions & 7 deletions qui/updater/progress_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ def __init__(
log,
header_label,
next_button,
cancel_button
cancel_button,
callback
):
self.builder = builder
self.log = log
Expand All @@ -54,6 +55,7 @@ def __init__(
self.vms_to_update = None
self.exit_triggered = False
self.update_thread = None
self.after_update_callback = callback

self.update_details = QubeUpdateDetails(self.builder)

Expand Down Expand Up @@ -127,9 +129,10 @@ def perform_update(self, settings):
if templs:
self.update_templates(templs, settings)

GLib.idle_add(self.next_button.set_sensitive, True)
GLib.idle_add(self.header_label.set_text, l("Update finished"))
GLib.idle_add(self.cancel_button.set_visible, False)
GLib.idle_add(self.next_button.set_sensitive, True)
self.after_update_callback()

def update_admin_vm(self, admins):
"""Runs command to update dom0."""
Expand Down Expand Up @@ -327,6 +330,7 @@ def do_update_templates(
['qubes-vm-update',
'--show-output',
'--just-print-progress',
'--force-update',
*args,
'--targets', targets],
stderr=subprocess.PIPE, stdout=subprocess.PIPE)
Expand Down Expand Up @@ -378,6 +382,7 @@ def handle_err_line(self, untrusted_line, rows):
try:
if status == "done":
update_status = UpdateStatus.from_name(info)
GLib.idle_add(rows[name].set_update_progress, 100)
GLib.idle_add(rows[name].set_status, update_status)
except KeyError:
return
Expand Down Expand Up @@ -458,16 +463,19 @@ def get_update_summary(self):
2. number of vms that tried to update but no update was found,
3. vms that update was canceled before starting.
"""
vm_updated_num = len(
updated = len(
[row for row in self.vms_to_update
if row.status == UpdateStatus.Success])
vm_no_updates_num = len(
no_updates = len(
[row for row in self.vms_to_update
if row.status == UpdateStatus.NoUpdatesFound])
vm_failed_num = len(
failed = len(
[row for row in self.vms_to_update
if row.status == UpdateStatus.Error])
cancelled = len(
[row for row in self.vms_to_update
if row.status in (UpdateStatus.Error, UpdateStatus.Cancelled)])
return vm_updated_num, vm_no_updates_num, vm_failed_num
if row.status == UpdateStatus.Cancelled])
return updated, no_updates, failed, cancelled


class Ticker:
Expand Down
34 changes: 21 additions & 13 deletions qui/updater/summary_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ def select_rows(self):
AppVMType.EXCLUDED in self.head_checkbox.allowed
)

def restart_selected_vms(self):
def restart_selected_vms(self, show_only_error: bool):
self.log.debug("Start restarting")
self.restart_thread = threading.Thread(
target=self.perform_restart
Expand All @@ -251,7 +251,7 @@ def restart_selected_vms(self):
time.sleep(0.01)

if self.restart_thread.is_alive():
# show wainting dialog
# show waiting dialog
spinner = Gtk.Spinner()
spinner.start()
dialog = show_dialog(None, l("Applying updates to qubes"), l(
Expand All @@ -272,7 +272,7 @@ def restart_selected_vms(self):
spinner.stop()
dialog.destroy()
self.log.debug("Hide restart dialog")
self._show_status_dialog()
self._show_status_dialog(show_only_error)

def perform_restart(self):

Expand All @@ -298,10 +298,8 @@ def perform_restart(self):
self.restart_vms(to_restart)
self.shutdown_domains(to_shutdown)

if not self.err:
if self.status is RestartStatus.NONE:
self.status = RestartStatus.OK
else:
self.status = RestartStatus.ERROR

def shutdown_domains(self, to_shutdown):
"""
Expand All @@ -317,6 +315,7 @@ def shutdown_domains(self, to_shutdown):
self.err += vm.name + " cannot shutdown: " + str(err) + '\n'
self.log.error("Cannot shutdown %s because %s",
vm.name, str(err))
self.status = RestartStatus.ERROR_TMPL_DOWN

asyncio.run(wait_for_domain_shutdown(wait_for))

Expand All @@ -336,22 +335,24 @@ def restart_vms(self, to_restart):
except qubesadmin.exc.QubesVMError as err:
self.err += vm.name + " cannot start: " + str(err) + '\n'
self.log.error("Cannot start %s because %s", vm.name, str(err))
self.status = RestartStatus.ERROR_APP_DOWN

def _show_status_dialog(self):
if self.status == RestartStatus.OK:
def _show_status_dialog(self, show_only_error: bool):
if self.status == RestartStatus.OK and not show_only_error:
show_dialog_with_icon(
None,
l("Success"),
l("All qubes were restarted/shutdown successfully."),
buttons=RESPONSES_OK,
icon_name="qubes-check-yes"
)
elif self.status == RestartStatus.ERROR:
elif self.status.is_error():
show_error(None, "Failure",
l("During restarting following errors occurs: ")
+ self.err
)
self.log.error("Restart error: %s", self.err)
self.status = RestartStatus.ERROR_APP_START


class RestartRowWrapper(RowWrapper):
Expand Down Expand Up @@ -421,10 +422,17 @@ class AppVMType:


class RestartStatus(Enum):
ERROR = 0
OK = 1
NOTHING_TO_DO = 2
NONE = 3
OK = 0
NOTHING_TO_DO = 100
NONE = -1
ERROR_TMPL_DOWN = 11
ERROR_APP_DOWN = 12
ERROR_APP_START = 13

def is_error(self: 'RestartStatus') -> bool:
return self in [
RestartStatus.ERROR_TMPL_DOWN, RestartStatus.ERROR_APP_DOWN,
RestartStatus.ERROR_APP_START]


class RestartHeaderCheckbox(HeaderCheckbox):
Expand Down
4 changes: 4 additions & 0 deletions qui/updater/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@

@pytest.fixture
def test_qapp():
return test_qapp_impl()


def test_qapp_impl():
"""Test QubesApp"""
qapp = QubesTest()
qapp._local_name = 'dom0' # pylint: disable=protected-access
Expand Down
Loading

0 comments on commit 8d8839c

Please sign in to comment.