mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-10-09 06:56:37 +00:00
- New service that starts again an OCI application that stopped on its own, such as Frigate after Save & Restart. A stop or shutdown asked by the user, a backup, a migration and a ProxMenux operation are never undone. - The installer asks it in both modes and proposes what the recipe declares; it is changed from the management menu or with the Watchdog switch of the container modal in the Monitor. - Notifications when the watchdog restarts an application and when it keeps stopping. - The service is recorded in the change journal, and the feature is documented.
72 lines
3.4 KiB
Python
72 lines
3.4 KiB
Python
"""The coordinated Recreate path must remain distinct from Update."""
|
|
|
|
from pathlib import Path
|
|
import sys
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT / "remote"))
|
|
|
|
import oci_stack_native as native
|
|
|
|
|
|
DIGEST = "sha256:" + "a" * 64
|
|
MEMBER = {
|
|
"vmid": 138,
|
|
"installation_id": "blinko-installation",
|
|
"template": {"id": "blinko", "container_contract": {"image": {"reference": "blinkospace/blinko:latest"}}},
|
|
"deployment": {"mounts": []},
|
|
"observed": {"config_sha256": "saved-config", "resolved_registry_digest": DIGEST,
|
|
"image": {"architecture": "amd64", "os": "linux", "defaults": {}}},
|
|
"stack": {"deployment": {"services": [{"vmid": 138, "name": "Blinko",
|
|
"healthcheck": {"type": "running", "timeout_seconds": 120}}]}},
|
|
}
|
|
|
|
|
|
class StackRecreateV1Tests(unittest.TestCase):
|
|
def adapter(self):
|
|
plan = {"operation": "recreate", "primary_vmid": 138, "members": [MEMBER]}
|
|
return native.NativeAdapter(Path("/registry"), Path("/journal"), plan)
|
|
|
|
def test_prepare_resolves_the_saved_digest_and_builds_an_unchanged_proposal(self):
|
|
adapter = self.adapter()
|
|
image = {"architecture": "amd64", "os": "linux", "defaults": {}}
|
|
with patch.object(native.instances, "read", return_value={}), \
|
|
patch.object(native.instances, "write"), \
|
|
patch.object(native.instances, "location", return_value=Path("/registry/138.json")), \
|
|
patch.object(native.instances, "command", return_value=b"arch: amd64\n"), \
|
|
patch.object(native, "resolve_archive", return_value=(Path("/cache/blinko.tar"), DIGEST)) as resolve, \
|
|
patch.object(native, "image_from_archive", return_value=image):
|
|
prepared = adapter.prepare(MEMBER, "recreate")
|
|
|
|
resolve.assert_called_once_with(MEMBER, b"arch: amd64\n", required_digest=DIGEST)
|
|
self.assertEqual(prepared["digest"], DIGEST)
|
|
self.assertEqual(prepared["proposal"]["operation"], "recreate")
|
|
self.assertEqual(prepared["proposal"]["candidate"], MEMBER)
|
|
self.assertEqual(prepared["proposal"]["base_config_sha256"], "saved-config")
|
|
|
|
def test_replace_passes_the_recreate_operation_and_proposal_to_every_member(self):
|
|
adapter = self.adapter()
|
|
prepared = {"archive": "/cache/blinko.tar", "digest": DIGEST,
|
|
"proposal": {"operation": "recreate", "candidate": MEMBER,
|
|
"base_config_sha256": "saved-config"}}
|
|
with patch.object(adapter, "validate"), \
|
|
patch.object(adapter, "state", return_value={"backups": {"138": {"archive": "/backup"}}}), \
|
|
patch.object(native, "translate", side_effect=lambda text: text), \
|
|
patch.object(native.member_tx, "apply") as apply:
|
|
adapter.replace(138, prepared, "transaction-id")
|
|
|
|
self.assertEqual(apply.call_args.args[3], "recreate")
|
|
self.assertEqual(apply.call_args.kwargs["proposal"], prepared["proposal"])
|
|
self.assertEqual(apply.call_args.kwargs["registry_digest"], DIGEST)
|
|
self.assertIn("Recreating", apply.call_args.kwargs["progress"])
|
|
|
|
def test_an_unknown_stack_operation_is_rejected_before_the_stack_is_touched(self):
|
|
with self.assertRaises(ValueError):
|
|
native.run(138, operation="not-a-lifecycle-operation")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|