Add cancellation and cleanup for native server creation
This commit is contained in:
@@ -209,6 +209,97 @@ class AgentTests(unittest.TestCase):
|
||||
self.assertTrue((self.library / "content/game.ff").exists())
|
||||
self.assertFalse(self.agent.instance_dir(INSTANCE, 1).exists())
|
||||
|
||||
def test_cancel_creation_bypasses_occupancy_and_preserves_library(self):
|
||||
self.create()
|
||||
self.agent.rcon = lambda *_: self.fail("Cancellation must not wait for player evidence")
|
||||
operation = self.operation("instance.cancel-create", {"createOperationId": "create"}, "cancel")
|
||||
self.assertEqual(self.agent.execute(operation)["state"], "completed")
|
||||
self.assertFalse(self.agent.inspect(INSTANCE, 1)["exists"])
|
||||
self.assertFalse(self.agent.instance_dir(INSTANCE, 1).exists())
|
||||
self.assertTrue((self.library / "content/game.ff").exists())
|
||||
self.assertEqual(self.agent.execute(operation)["state"], "completed")
|
||||
self.assertEqual(sum(call[:2] == ("container", "rm") for call in self.docker.calls), 1)
|
||||
|
||||
def test_heartbeat_cancellation_stops_create_before_starting_container(self):
|
||||
original_call = self.docker.call
|
||||
def cancel_during_create(*args, **kwargs):
|
||||
result = original_call(*args, **kwargs)
|
||||
if args[:2] == ("container", "create"):
|
||||
self.agent.cancelled_creations = frozenset(["create"])
|
||||
return result
|
||||
self.docker.call = cancel_during_create
|
||||
payload = {"name": "test", "game": "t5", "libraryId": self.library_id, "image": "ghcr.io/ayymoss/plutainer:latest", "port": 28960, "env": {}, "rconPassword": "secret", "files": []}
|
||||
self.assertEqual(self.agent.execute(self.operation("instance.create", payload, "create"))["state"], "unknown")
|
||||
self.assertFalse(any(call[:2] == ("container", "start") for call in self.docker.calls))
|
||||
result = self.agent.execute(self.operation("instance.cancel-create", {"createOperationId": "create"}))
|
||||
self.assertEqual(result["state"], "completed")
|
||||
self.assertFalse(self.agent.inspect(INSTANCE, 1)["exists"])
|
||||
|
||||
def test_cancellation_cleanup_resumes_after_agent_restart(self):
|
||||
self.create()
|
||||
self.docker.lose_reply_after = "rm"
|
||||
cancel = self.operation("instance.cancel-create", {"createOperationId": "create"}, "cancel")
|
||||
self.assertEqual(self.agent.execute(cancel)["state"], "unknown")
|
||||
self.agent.journal.db.close()
|
||||
self.agent = Agent(self.config, self.docker)
|
||||
self.assertEqual(self.agent.execute(cancel)["state"], "completed")
|
||||
self.assertFalse(self.agent.instance_dir(INSTANCE, 1).exists())
|
||||
|
||||
def test_cancel_before_creation_delivery_fences_delayed_create(self):
|
||||
result = self.agent.execute(self.operation("instance.cancel-create", {"createOperationId": "late-create"}))
|
||||
self.assertEqual(result["state"], "completed", result)
|
||||
result = self.agent.execute(self.operation("instance.create", {}, "late-create"))
|
||||
self.assertEqual(result["state"], "failed")
|
||||
self.assertFalse(any(call[:2] == ("container", "create") for call in self.docker.calls))
|
||||
|
||||
def test_cancel_removes_partial_files_without_a_container(self):
|
||||
directory = self.agent.instance_dir(INSTANCE, 1)
|
||||
(directory / "app").mkdir(parents=True)
|
||||
(directory / "app/partial.cfg").write_text("partial")
|
||||
self.agent.journal.accept(self.operation("instance.create", {}, "create"))
|
||||
result = self.agent.execute(self.operation("instance.cancel-create", {"createOperationId": "create"}))
|
||||
self.assertEqual(result["state"], "completed", result)
|
||||
self.assertFalse(directory.exists())
|
||||
self.assertTrue((self.library / "content/game.ff").exists())
|
||||
|
||||
def test_cancel_rejects_a_creation_id_from_another_instance(self):
|
||||
other = self.operation("instance.create", {}, "other-create")
|
||||
other["instanceId"] = "another-instance"
|
||||
self.agent.journal.accept(other)
|
||||
result = self.agent.execute(self.operation("instance.cancel-create", {"createOperationId": "other-create"}))
|
||||
self.assertEqual(result["state"], "failed", result)
|
||||
self.assertFalse((self.agent.instance_dir(INSTANCE, 1).parent / "current.json").exists())
|
||||
|
||||
def test_cancel_creation_rejects_other_creation_and_newer_generation(self):
|
||||
self.create()
|
||||
result = self.agent.execute(self.operation("instance.cancel-create", {"createOperationId": "wrong"}))
|
||||
self.assertEqual(result["state"], "failed")
|
||||
module.atomic_json(self.agent.instance_dir(INSTANCE, 1).parent / "current.json", {"generation": 2})
|
||||
result = self.agent.execute(self.operation("instance.cancel-create", {"createOperationId": "create"}, "old-generation"))
|
||||
self.assertEqual(result["state"], "failed")
|
||||
self.assertTrue(self.agent.inspect(INSTANCE, 1)["running"])
|
||||
self.assertFalse(any(call[:2] == ("container", "rm") for call in self.docker.calls))
|
||||
|
||||
def test_cancel_creation_reconciles_lost_removal_reply_and_fences_unknown_create(self):
|
||||
self.docker.lose_reply_after = "start"
|
||||
payload = {"name": "test", "game": "t5", "libraryId": self.library_id, "image": "ghcr.io/ayymoss/plutainer:latest", "port": 28960, "env": {}, "rconPassword": "secret", "files": []}
|
||||
create = self.operation("instance.create", payload, "create")
|
||||
self.assertEqual(self.agent.execute(create)["state"], "unknown")
|
||||
self.docker.lose_reply_after = "rm"
|
||||
cancel = self.operation("instance.cancel-create", {"createOperationId": "create"}, "cancel")
|
||||
self.assertEqual(self.agent.execute(cancel)["state"], "unknown")
|
||||
self.assertEqual(self.agent.execute(cancel)["state"], "completed")
|
||||
self.assertEqual(self.agent.execute(create)["state"], "failed")
|
||||
self.assertFalse(self.agent.instance_dir(INSTANCE, 1).exists())
|
||||
self.assertTrue(json.loads((self.agent.instance_dir(INSTANCE, 1).parent / "current.json").read_text())["removed"])
|
||||
|
||||
def test_cancel_creation_rejects_unowned_container(self):
|
||||
self.create()
|
||||
self.docker.containers[self.agent.container_name(INSTANCE, 1)]["Config"]["Labels"] = {}
|
||||
result = self.agent.execute(self.operation("instance.cancel-create", {"createOperationId": "create"}))
|
||||
self.assertEqual(result["state"], "failed")
|
||||
self.assertFalse(any(call[:2] == ("container", "rm") for call in self.docker.calls))
|
||||
|
||||
def test_unowned_container_never_mutated(self):
|
||||
self.create()
|
||||
self.docker.containers[self.agent.container_name(INSTANCE, 1)]["Config"]["Labels"] = {}
|
||||
|
||||
Reference in New Issue
Block a user