mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-01 10:22:08 +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']:
|
||||
# A container installed by OCI manager Apps is updated by replacing
|
||||
# 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',
|
||||
'manage', str(vmid), '--action', 'update', '--unattended']
|
||||
storage = (sched.get('backup_storage') or '').strip()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import ast
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import subprocess
|
||||
import tempfile
|
||||
@@ -429,6 +430,43 @@ class ScheduledStoppedContainerTests(unittest.TestCase):
|
||||
stopped_marker.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):
|
||||
def _docker_app(self):
|
||||
|
||||
Reference in New Issue
Block a user