fix: make messages say what was verified (source questions from the Italian review)

This commit is contained in:
MacRimi
2026-10-06 17:08:18 +02:00
parent 12632c027e
commit 85b88f709a
21 changed files with 166 additions and 50 deletions
+15 -8
View File
@@ -228,16 +228,18 @@ def apply(root, primary_id, vmid, images, acceleration, render_device, gfx_overr
def revert(root, primary_id, vmid, images, previous, was_running):
"""Give the container and the records the choice they had."""
"""Give the container and the records the choice they had. Returns
whether they have it again."""
try:
if oci_stack_modify.is_running(vmid):
stop(vmid)
apply(root, primary_id, vmid, images, *previous)
except (OSError, ValueError, KeyError, RuntimeError, subprocess.SubprocessError) as error:
msg_warn(f"{translate('The previous recognition choice could not be put back:')} {error}")
return
return False
if was_running:
subprocess.run(['pct', 'start', str(vmid)], capture_output=True, check=False)
return True
def change(root, primary_id, acceleration, render_device=None, gfx_override=None):
@@ -262,17 +264,17 @@ def change(root, primary_id, acceleration, render_device=None, gfx_override=None
msg_info(translate('Preparing the machine learning container for the new choice...'))
apply(root, primary_id, vmid, images, acceleration, render_device, gfx_override)
msg_ok(translate('Machine learning container prepared'))
except BaseException:
revert(root, primary_id, vmid, images, previous, was_running)
except BaseException as error:
error.recognition_kept = revert(root, primary_id, vmid, images, previous, was_running)
raise
try:
# The image of the new choice replaces the one in use, as an update does.
oci_stack_native.run(primary_id, acknowledge_external_data=True)
except BaseException:
except BaseException as error:
with instances.locked(root):
# An update left halfway keeps its own record of what to restore.
if not instances.read(root, primary_id).get('pending_stack_transaction'):
revert(root, primary_id, vmid, images, previous, was_running)
error.recognition_kept = (not instances.read(root, primary_id).get('pending_stack_transaction')
and revert(root, primary_id, vmid, images, previous, was_running))
raise
if was_running and not oci_stack_modify.is_running(vmid):
# An update leaves each container as it found it, and this one was
@@ -297,7 +299,12 @@ def main():
return 1
except (OSError, ValueError, KeyError, RuntimeError, StopIteration, subprocess.SubprocessError) as error:
if not getattr(error, 'oci_reported', False):
msg_error(f"{translate('The recognition of Immich was not changed:')} {error}")
# Only an error raised before anything was touched, or one that
# was put back, leaves the previous choice in place.
kept = getattr(error, 'recognition_kept', True)
message = (translate('The recognition of Immich was not changed:') if kept
else translate('The recognition change of Immich did not complete:'))
msg_error(f"{message} {error}")
return 1
msg_ok(translate('Recognition of Immich changed; the application was updated with the new choice.'))
return 0
+17 -8
View File
@@ -768,29 +768,33 @@ def release_stage(state, discard=False):
"""After a commit the holder CT only keeps its own rootfs: every parked
volume went back to the application. After a verified recovery it holds
the disks of the failed attempt, which the restored backup replaced:
`discard` removes them with it. Otherwise anything still attached keeps it."""
`discard` removes them with it. Otherwise anything still attached keeps it.
Returns whether the holder CT was removed."""
stage = state.get('stage')
if not stage:
return
return False
try:
config = owned(stage, 'proxmenux-transaction=' + state['id'])
except (ValueError, RuntimeError, subprocess.CalledProcessError):
return
return False
held = mounts(config) or any(re.fullmatch(r'unused[0-9]+', key) for key in parse_config(config))
if held and not discard:
return
return False
if discard:
stop(stage)
run('pct', 'destroy', str(stage))
return True
def discard_stage(state):
"""Remove the holder CT of an operation that ended in a verified recovery.
A failure here leaves it in place and does not undo the recovery."""
A failure here leaves it in place and does not undo the recovery.
Returns whether the holder CT was removed."""
try:
release_stage(state, discard=True)
return release_stage(state, discard=True)
except (OSError, ValueError, RuntimeError, subprocess.SubprocessError) as error:
log(f'cleanup: {error}')
return False
def gib(size):
@@ -1139,8 +1143,13 @@ def complete_recovery(root, journal, state):
checkpoint(journal, state, 'rolled-back')
if show:
# A member of a stack is released when the whole stack is back.
discard_stage(state)
msg_ok(translate('Recovery completed. The disks of the failed attempt were removed; the backup is kept.'))
if discard_stage(state):
msg_ok(translate('Recovery completed. The disks of the failed attempt were removed; the backup is kept.'))
else:
msg_ok(translate('Recovery completed; the backup is kept.'))
if state.get('stage'):
msg_warn(f"{translate('The disks of the failed attempt could not be removed and stay in the temporary container:')} "
f"CT {state['stage']}")
if state.get('original_host_sources') or state.get('desired_host_sources'):
msg_info2(translate('Shared host files are kept as they are; the backup does not restore their content.'))
+7 -5
View File
@@ -943,7 +943,7 @@ def start_order(entry):
return int(order[1]) if order else 9999
def recover(root, plan, start=False, host_firewall=False):
def recover(root, plan, start=False, host_firewall=()):
for bridge, subnet in plan['bridges'].items():
msg_info(f"{translate('Creating the private network...')} {bridge}")
create_bridge(bridge, subnet)
@@ -974,7 +974,8 @@ def recover(root, plan, start=False, host_firewall=False):
if not rule:
continue
text = f"TCP {rule['port']} {translate('from')} {rule['source']}"
if not host_firewall:
# Each rule is confirmed on its own; one answer does not cover the rest.
if rule['vmid'] not in host_firewall:
plan['notes'].append(f"CT {entry['vmid']}: {translate('its host firewall rule was not added:')} {text}")
elif add_firewall_rule(rule):
msg_ok(f"{translate('Host firewall rule added:')} {text}")
@@ -1020,7 +1021,8 @@ def main():
parser.add_argument('action', choices=('list', 'plan', 'recover'))
parser.add_argument('--root', type=Path, default=instances.ROOT)
parser.add_argument('--start', action='store_true')
parser.add_argument('--host-firewall', action='store_true')
parser.add_argument('--host-firewall', type=int, action='append', default=[], metavar='VMID',
help='Add the host firewall rule of this container; one option for each confirmed rule')
parser.add_argument('--automatic', action='store_true',
help='Only the applications that need no question: the ones the cluster keeps a record of')
args = parser.parse_args()
@@ -1041,13 +1043,13 @@ def main():
failed = 0
if args.automatic:
plans = [plan for plan in plans if automatic(plan)]
args.start = args.host_firewall = False
args.start, args.host_firewall = False, []
for plan in plans:
containers = ', '.join(f"CT {entry['vmid']}" for entry in plan['restored'])
msg_info2(f"{plan['title']} · {containers}")
if plan['blockers']:
failed += 1
msg_error(translate('This application cannot be recovered yet; nothing was changed:'))
msg_error(translate('This application cannot be recovered yet:'))
for line in plan['blockers']:
msg_info2(f" - {line}")
lost = [entry for entry in plan['restored'] if entry.get('unrecoverable')]