fix(oci): refuse an HA-managed container before downloading the image and explain how to proceed

This commit is contained in:
MacRimi
2026-11-03 22:49:47 +01:00
parent 97da4321c8
commit a89eb921cc
11 changed files with 80 additions and 10 deletions
+2 -1
View File
@@ -9014,5 +9014,6 @@
"⚠ Disk data will NOT be erased.": "⚠ Festplattendaten werden NICHT gelöscht.", "⚠ Disk data will NOT be erased.": "⚠ Festplattendaten werden NICHT gelöscht.",
"⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ Die Festplatte wird ausgehängt und aus /etc/fstab entfernt.", "⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ Die Festplatte wird ausgehängt und aus /etc/fstab entfernt.",
"⚠ The /etc/fstab entry will be removed.": "⚠ Der /etc/fstab-Eintrag wird entfernt.", "⚠ The /etc/fstab entry will be removed.": "⚠ Der /etc/fstab-Eintrag wird entfernt.",
"⚠ The disk will be unmounted.": "⚠ Die Festplatte wird ausgehängt." "⚠ The disk will be unmounted.": "⚠ Die Festplatte wird ausgehängt.",
"This container is managed by high availability. Remove ct:{vmid} from HA (Datacenter → HA, or ha-manager remove ct:{vmid}), repeat the operation and add it back to HA with the same settings.": "Dieser Container wird von der Hochverfügbarkeit verwaltet. Entfernen Sie ct:{vmid} aus HA (Rechenzentrum → HA oder ha-manager remove ct:{vmid}), wiederholen Sie den Vorgang und fügen Sie ihn mit denselben Einstellungen wieder zu HA hinzu."
} }
+2 -1
View File
@@ -8984,5 +8984,6 @@
"⚠ Disk data will NOT be erased.": "⚠ Los datos del disco NO se borrarán.", "⚠ Disk data will NOT be erased.": "⚠ Los datos del disco NO se borrarán.",
"⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ El disco se desmontará y se eliminará de /etc/fstab.", "⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ El disco se desmontará y se eliminará de /etc/fstab.",
"⚠ The /etc/fstab entry will be removed.": "⚠ Se eliminará la entrada /etc/fstab.", "⚠ The /etc/fstab entry will be removed.": "⚠ Se eliminará la entrada /etc/fstab.",
"⚠ The disk will be unmounted.": "⚠ El disco se desmontará." "⚠ The disk will be unmounted.": "⚠ El disco se desmontará.",
"This container is managed by high availability. Remove ct:{vmid} from HA (Datacenter → HA, or ha-manager remove ct:{vmid}), repeat the operation and add it back to HA with the same settings.": "Este contenedor está gestionado por alta disponibilidad. Elimina ct:{vmid} de HA (Centro de datos → HA, o ha-manager remove ct:{vmid}), repite la operación y vuelve a añadirlo a HA con la misma configuración."
} }
+2 -1
View File
@@ -9013,5 +9013,6 @@
"⚠ Disk data will NOT be erased.": "⚠ Les données du disque ne seront PAS effacées.", "⚠ Disk data will NOT be erased.": "⚠ Les données du disque ne seront PAS effacées.",
"⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ Le disque sera démonté et supprimé de /etc/fstab.", "⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ Le disque sera démonté et supprimé de /etc/fstab.",
"⚠ The /etc/fstab entry will be removed.": "⚠ L'entrée /etc/fstab sera supprimée.", "⚠ The /etc/fstab entry will be removed.": "⚠ L'entrée /etc/fstab sera supprimée.",
"⚠ The disk will be unmounted.": "⚠ Le disque sera démonté." "⚠ The disk will be unmounted.": "⚠ Le disque sera démonté.",
"This container is managed by high availability. Remove ct:{vmid} from HA (Datacenter → HA, or ha-manager remove ct:{vmid}), repeat the operation and add it back to HA with the same settings.": "Ce conteneur est géré par la haute disponibilité. Retirez ct:{vmid} de HA (Centre de données → HA, ou ha-manager remove ct:{vmid}), relancez l'opération et rajoutez-le à HA avec les mêmes paramètres."
} }
+2 -1
View File
@@ -9014,5 +9014,6 @@
"⚠ Disk data will NOT be erased.": "⚠ I dati del disco NON verranno cancellati.", "⚠ Disk data will NOT be erased.": "⚠ I dati del disco NON verranno cancellati.",
"⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ Il disco verrà smontato e rimosso da /etc/fstab.", "⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ Il disco verrà smontato e rimosso da /etc/fstab.",
"⚠ The /etc/fstab entry will be removed.": "⚠ La voce /etc/fstab verrà rimossa.", "⚠ The /etc/fstab entry will be removed.": "⚠ La voce /etc/fstab verrà rimossa.",
"⚠ The disk will be unmounted.": "⚠ Il disco verrà smontato." "⚠ The disk will be unmounted.": "⚠ Il disco verrà smontato.",
"This container is managed by high availability. Remove ct:{vmid} from HA (Datacenter → HA, or ha-manager remove ct:{vmid}), repeat the operation and add it back to HA with the same settings.": "Questo container è gestito dall'alta disponibilità. Rimuovi ct:{vmid} da HA (Datacenter → HA, oppure ha-manager remove ct:{vmid}), ripeti l'operazione e aggiungilo di nuovo a HA con le stesse impostazioni."
} }
+2 -1
View File
@@ -9014,5 +9014,6 @@
"⚠ Disk data will NOT be erased.": "⚠ Os dados do disco NÃO serão apagados.", "⚠ Disk data will NOT be erased.": "⚠ Os dados do disco NÃO serão apagados.",
"⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ O disco será desmontado e removido de /etc/fstab.", "⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ O disco será desmontado e removido de /etc/fstab.",
"⚠ The /etc/fstab entry will be removed.": "⚠ A entrada /etc/fstab será removida.", "⚠ The /etc/fstab entry will be removed.": "⚠ A entrada /etc/fstab será removida.",
"⚠ The disk will be unmounted.": "⚠ O disco será desmontado." "⚠ The disk will be unmounted.": "⚠ O disco será desmontado.",
"This container is managed by high availability. Remove ct:{vmid} from HA (Datacenter → HA, or ha-manager remove ct:{vmid}), repeat the operation and add it back to HA with the same settings.": "Este contentor é gerido pela alta disponibilidade. Remova ct:{vmid} da HA (Datacenter → HA, ou ha-manager remove ct:{vmid}), repita a operação e volte a adicioná-lo à HA com as mesmas definições."
} }
+2 -1
View File
@@ -9014,5 +9014,6 @@
"⚠ Disk data will NOT be erased.": "⚠ Dáta na disku sa NEVYMAŽÚ.", "⚠ Disk data will NOT be erased.": "⚠ Dáta na disku sa NEVYMAŽÚ.",
"⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ Disk sa odpojí a odstráni z /etc/fstab.", "⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ Disk sa odpojí a odstráni z /etc/fstab.",
"⚠ The /etc/fstab entry will be removed.": "⚠ Záznam v /etc/fstab bude odstránený.", "⚠ The /etc/fstab entry will be removed.": "⚠ Záznam v /etc/fstab bude odstránený.",
"⚠ The disk will be unmounted.": "⚠ Disk bude odpojený." "⚠ The disk will be unmounted.": "⚠ Disk bude odpojený.",
"This container is managed by high availability. Remove ct:{vmid} from HA (Datacenter → HA, or ha-manager remove ct:{vmid}), repeat the operation and add it back to HA with the same settings.": "Tento kontajner spravuje vysoká dostupnosť. Odstráňte ct:{vmid} z HA (Dátové centrum → HA alebo ha-manager remove ct:{vmid}), zopakujte operáciu a potom ho znova pridajte do HA s rovnakými nastaveniami."
} }
+4 -1
View File
@@ -9012,5 +9012,8 @@
"⚠ Disk data will NOT be erased.": "⚠ Diskdata kommer INTE att raderas.", "⚠ Disk data will NOT be erased.": "⚠ Diskdata kommer INTE att raderas.",
"⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ Disken kommer att avmonteras och tas bort från /etc/fstab.", "⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ Disken kommer att avmonteras och tas bort från /etc/fstab.",
"⚠ The /etc/fstab entry will be removed.": "⚠ /etc/fstab-posten kommer att tas bort.", "⚠ The /etc/fstab entry will be removed.": "⚠ /etc/fstab-posten kommer att tas bort.",
"⚠ The disk will be unmounted.": "⚠ Disken kommer att avmonteras." "⚠ The disk will be unmounted.": "⚠ Disken kommer att avmonteras.",
"This container is managed by high availability. Remove ct:{vmid} from HA (Datacenter → HA, or ha-manager remove ct:{vmid}), repeat the operation and add it back to HA with the same settings.": "Den här containern hanteras av hög tillgänglighet. Ta bort ct:{vmid} från HA (Datacenter → HA, eller ha-manager remove ct:{vmid}), upprepa åtgärden och lägg sedan tillbaka den i HA med samma inställningar.",
"Krusader Web UI": "Krusaders webbgränssnitt",
"Z-Wave JS UI admin login": "Administratörsinloggning för Z-Wave JS UI"
} }
+12 -3
View File
@@ -621,6 +621,17 @@ def without_public_routes(config, vmid):
return b'\n'.join(line for line in config.splitlines() if not public_routes.own(line, vmid)) + b'\n' return b'\n'.join(line for line in config.splitlines() if not public_routes.own(line, vmid)) + b'\n'
def refuse_high_availability(vmid):
"""Recreating a container that HA manages would race the HA manager, which
restarts or relocates it in the middle of the replacement."""
resources = json.loads(run('pvesh', 'get', '/cluster/ha/resources', '--output-format', 'json'))
if any(r.get('sid') == f'ct:{vmid}' for r in resources):
raise ValueError(translate(
'This container is managed by high availability. Remove ct:{vmid} from HA (Datacenter → HA, '
'or ha-manager remove ct:{vmid}), repeat the operation and add it back to HA with the same settings.'
).replace('{vmid}', str(vmid)))
def preflight(record, candidate, config, coordinated=None, in_place=False, baseline=True): def preflight(record, candidate, config, coordinated=None, in_place=False, baseline=True):
deployment = record['deployment'] deployment = record['deployment']
desired = candidate['deployment'] desired = candidate['deployment']
@@ -1202,9 +1213,7 @@ def apply(root, vmid, archive, operation, proposal=None, registry_digest=None, i
runtime_settings.file_binds(before)) runtime_settings.file_binds(before))
original_gpu = gpu_devices.planned(record['deployment']) original_gpu = gpu_devices.planned(record['deployment'])
desired_gpu = gpu_devices.planned(candidate['deployment']) desired_gpu = gpu_devices.planned(candidate['deployment'])
resources = json.loads(run('pvesh', 'get', '/cluster/ha/resources', '--output-format', 'json')) refuse_high_availability(vmid)
if any(r.get('sid') == f'ct:{vmid}' for r in resources):
raise ValueError(translate('High availability resources are not supported by this profile'))
archive = archive.resolve(strict=True) archive = archive.resolve(strict=True)
check_archive(archive) check_archive(archive)
image = image_from_archive(str(archive)) image = image_from_archive(str(archive))
+2
View File
@@ -233,6 +233,8 @@ def update(vmid, acknowledge_external_data=False, proposal=None, keep_backup=Non
# the route before changing anything; actual validation/API failures # the route before changing anything; actual validation/API failures
# must not silently switch to a destructive fallback. # must not silently switch to a destructive fallback.
transaction.preflight(record, desired, config) transaction.preflight(record, desired, config)
# Refused before the image is downloaded, not after.
transaction.refuse_high_availability(vmid)
msg_ok(translate('Container checked')) msg_ok(translate('Container checked'))
current = record['observed']['image'] if operation == 'update' else None current = record['observed']['image'] if operation == 'update' else None
check = lambda: work_backup.locate( check = lambda: work_backup.locate(
+1
View File
@@ -151,6 +151,7 @@ class NativeApplyTests(unittest.TestCase):
self.commands.append(args) self.commands.append(args)
if args[:2]==('pct','status'):return b'status: running' if self.state['running'] else b'status: stopped' if args[:2]==('pct','status'):return b'status: running' if self.state['running'] else b'status: stopped'
if args[:2]==('pct','start'):self.state['running']=True if args[:2]==('pct','start'):self.state['running']=True
if args[:3]==('pvesh','get','/cluster/ha/resources'):return b'[]'
return b'' return b''
def stop(vmid):self.state['running']=False def stop(vmid):self.state['running']=False
def setter(vmid,identity,expected,values,delete=()): def setter(vmid,identity,expected,values,delete=()):
@@ -0,0 +1,49 @@
"""A container that HA manages is refused before its new image is downloaded."""
import contextlib
import json
from pathlib import Path
import sys
import unittest
from unittest.mock import Mock, patch
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'remote'))
import oci_instance_transaction as transaction
import oci_update_current as update
def ha_resources(*vmids):
answer = json.dumps([{'sid': f'ct:{vmid}', 'state': 'started'} for vmid in vmids]).encode()
return patch.object(transaction, 'run', return_value=answer)
class HighAvailabilityTests(unittest.TestCase):
def test_the_refusal_names_the_resource_and_how_to_proceed(self):
with ha_resources(101), self.assertRaises(ValueError) as refused:
transaction.refuse_high_availability(101)
self.assertIn('ha-manager remove ct:101', str(refused.exception))
def test_a_container_outside_ha_is_not_refused(self):
with ha_resources(200):
transaction.refuse_high_availability(101)
def test_an_update_stops_before_downloading_the_image(self):
record = {'status': 'installed', 'observed': {'image': {}}}
resolve = Mock()
with contextlib.ExitStack() as stack:
stack.enter_context(patch.object(update.instances, 'locked', return_value=contextlib.nullcontext()))
stack.enter_context(patch.object(update.instances, 'read', return_value=record))
stack.enter_context(patch.object(update.instances, 'command', return_value=b''))
stack.enter_context(patch.object(update.transaction, 'candidate_contract', return_value={}))
stack.enter_context(patch.object(update.transaction, 'external_changes', return_value={}))
stack.enter_context(patch.object(update.transaction, 'preflight'))
stack.enter_context(patch.object(update, 'resolve_archive', resolve))
stack.enter_context(patch.object(update, 'msg_info'))
stack.enter_context(patch.object(update, 'msg_ok'))
stack.enter_context(ha_resources(101))
with self.assertRaises(ValueError):
update.update(101)
resolve.assert_not_called()
if __name__ == '__main__':
unittest.main()