mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-02 19:02:31 +00:00
fix: guard OCI scheduled updates after stop
This commit is contained in:
@@ -22772,6 +22772,15 @@ def _run_scheduled_update(vmid: int, sched: dict) -> dict:
|
|||||||
if requested_targets == ['oci_image']:
|
if requested_targets == ['oci_image']:
|
||||||
# A container installed by OCI manager Apps is updated by replacing
|
# A container installed by OCI manager Apps is updated by replacing
|
||||||
# its image, through the same flow as the OCI menu, unattended.
|
# its image, through the same flow as the OCI menu, unattended.
|
||||||
|
# OCI updates have their own runner rather than apply_updates.sh.
|
||||||
|
# Check once more immediately before handing control to it: a user
|
||||||
|
# may have intentionally stopped the CT after the scheduler's first
|
||||||
|
# observation, and the OCI transaction can start a replacement CT
|
||||||
|
# while validating its new image.
|
||||||
|
if _fast_guest_status(vmid, 'lxc') != 'running':
|
||||||
|
reason_code = 'stopped_container'
|
||||||
|
reasons.append('container was stopped before scheduled update could begin')
|
||||||
|
return finish('skipped', 'oci_image', [])
|
||||||
command = ['bash', '/usr/local/share/proxmenux/scripts/oci/oci_manager_apps.sh',
|
command = ['bash', '/usr/local/share/proxmenux/scripts/oci/oci_manager_apps.sh',
|
||||||
'manage', str(vmid), '--action', 'update', '--unattended']
|
'manage', str(vmid), '--action', 'update', '--unattended']
|
||||||
storage = (sched.get('backup_storage') or '').strip()
|
storage = (sched.get('backup_storage') or '').strip()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import ast
|
import ast
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
@@ -429,6 +430,43 @@ class ScheduledStoppedContainerTests(unittest.TestCase):
|
|||||||
stopped_marker.assert_called_once()
|
stopped_marker.assert_called_once()
|
||||||
reboot_probe.assert_called_once()
|
reboot_probe.assert_called_once()
|
||||||
|
|
||||||
|
def test_oci_race_to_stopped_skips_before_the_oci_engine_runs(self):
|
||||||
|
runner = MagicMock(return_value=SimpleNamespace(returncode=0, stdout="", stderr=""))
|
||||||
|
finalizer = MagicMock(return_value={"recorded": True})
|
||||||
|
status = MagicMock(side_effect=["running", "stopped"])
|
||||||
|
scope = {
|
||||||
|
"time": SimpleNamespace(monotonic=MagicMock(return_value=10.0)),
|
||||||
|
"uuid": SimpleNamespace(uuid4=lambda: SimpleNamespace(hex="c" * 32)),
|
||||||
|
"datetime": __import__("datetime").datetime,
|
||||||
|
"subprocess": SimpleNamespace(
|
||||||
|
run=runner,
|
||||||
|
STDOUT=object(),
|
||||||
|
DEVNULL=object(),
|
||||||
|
TimeoutExpired=subprocess.TimeoutExpired,
|
||||||
|
),
|
||||||
|
"os": os,
|
||||||
|
"re": re,
|
||||||
|
"_create_lxc_update_log": lambda _vmid, _run_id: (None, None),
|
||||||
|
"_fast_guest_status": status,
|
||||||
|
"_normalise_schedule_targets": lambda _sched: ["oci_image"],
|
||||||
|
"_lxc_update_snapshot": lambda _vmid: {"ct_name": "fixture"},
|
||||||
|
"_lxc_update_target_labels": lambda *_: ["OCI image"],
|
||||||
|
"_append_lxc_update_log": MagicMock(),
|
||||||
|
"_prune_lxc_update_logs": MagicMock(),
|
||||||
|
"_finalize_lxc_update": finalizer,
|
||||||
|
}
|
||||||
|
|
||||||
|
outcome = self._worker(scope)(101, {"targets": ["oci_image"]})
|
||||||
|
|
||||||
|
self.assertEqual(outcome["status"], "skipped")
|
||||||
|
self.assertEqual(outcome["target"], "oci_image")
|
||||||
|
self.assertEqual(outcome["executed_targets"], [])
|
||||||
|
self.assertEqual(outcome["reason_code"], "stopped_container")
|
||||||
|
self.assertIn("before scheduled update could begin", outcome["reason"])
|
||||||
|
self.assertEqual(status.call_count, 2)
|
||||||
|
runner.assert_not_called()
|
||||||
|
self.assertEqual(finalizer.call_args.kwargs["status"], "skipped")
|
||||||
|
|
||||||
|
|
||||||
class DockerStackNotificationTests(unittest.TestCase):
|
class DockerStackNotificationTests(unittest.TestCase):
|
||||||
def _docker_app(self):
|
def _docker_app(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user