Provision instance bank credentials automatically (Refs #146)
This commit is contained in:
@@ -1644,6 +1644,109 @@ class AgentTests(unittest.TestCase):
|
||||
self.assertEqual(self.agent.execute(self.operation("instance.remove"))["state"], "failed")
|
||||
self.assertFalse(any(call[:2] == ("container", "stop") for call in self.docker.calls))
|
||||
|
||||
def test_create_materializes_private_bank_config_from_instance_credential(self):
|
||||
secret = "ec_bank_" + "a" * 43
|
||||
payload = {"name": "server", "game": "t5", "libraryId": self.library_id,
|
||||
"image": "ghcr.io/ayymoss/plutainer:latest", "port": 28960,
|
||||
"bank": {"token": secret, "configPath": "configs/electric_cherry_bank.cfg"}}
|
||||
operation = self.operation("instance.create", payload, "create")
|
||||
self.assertEqual(self.agent.execute(operation)["state"], "completed")
|
||||
path = self.agent.instance_dir(INSTANCE, 1) / "app/configs/electric_cherry_bank.cfg"
|
||||
self.assertEqual(path.read_text(), f'set tws_bank_token "{secret}"\nset tws_bank_url "https://controller.example/api/bank"\n')
|
||||
self.assertIn("bank.settlement", self.agent.inventory()["capabilities"])
|
||||
payload["bank"]["token"] = secret + '\nset bad "1"'
|
||||
with self.assertRaises(AgentError):
|
||||
self.agent.initial_files(payload)
|
||||
|
||||
def bank_journal(self, decisions=("commit", "cancel"), game="t5"):
|
||||
path = self.agent.instance_dir(INSTANCE, 1) / f"app/runtime/tws-bank/{game}/scope.json"
|
||||
reports = [json.dumps({"server": "t5_zombie_test_1", "guid": "ace", "match": "match-one", "request_id": f"{game}-{i}", "operation": "deposit", "amount": 2000, "capacity": 1000000, "decision": decision}) for i, decision in enumerate(decisions)]
|
||||
module.atomic_json(path, {"version": 1, "server": "t5_zombie_test_1", "endpoint": "https://controller.example/api/bank", "reports": reports})
|
||||
return path
|
||||
|
||||
def bank_acknowledgment(self, method, path, payload):
|
||||
self.assertEqual((method, path), ("POST", "/api/agent/bank/settle"))
|
||||
self.assertFalse(self.agent.inspect(INSTANCE, 1)["exists"])
|
||||
return {"state": "committed" if payload["action"] == "commit" else "cancelled", "request_id": payload["transfer"]["request_id"]}
|
||||
|
||||
def test_remove_settles_both_game_journals_after_stop_before_deleting_storage(self):
|
||||
self.create()
|
||||
paths = [self.bank_journal(game=game) for game in ("t5", "t6")]
|
||||
def acknowledge(*args):
|
||||
self.assertTrue(all(path.exists() for path in paths))
|
||||
self.assertEqual(args[2]["operationId"], "remove")
|
||||
return self.bank_acknowledgment(*args)
|
||||
with patch.object(self.agent, "request", side_effect=acknowledge) as request:
|
||||
result = self.agent.execute(self.operation("instance.remove", {"safetyOverride": True}, "remove"))
|
||||
self.assertEqual(result["state"], "completed", result)
|
||||
self.assertEqual(request.call_count, 4)
|
||||
self.assertTrue(all(not path.exists() for path in paths))
|
||||
|
||||
def test_remove_retains_bank_journal_during_outage_and_resumes_after_agent_restart(self):
|
||||
self.create()
|
||||
path = self.bank_journal()
|
||||
operation = self.operation("instance.remove", {"safetyOverride": True}, "remove")
|
||||
with patch.object(self.agent, "request", side_effect=AgentError("Controller request failed")):
|
||||
self.assertEqual(self.agent.execute(operation)["state"], "unknown")
|
||||
self.assertEqual(self.agent.execute(operation)["state"], "unknown")
|
||||
self.assertTrue(path.exists())
|
||||
self.agent.journal.close()
|
||||
self.agent = Agent(self.config, self.docker)
|
||||
calls = list(self.docker.calls)
|
||||
with patch.object(self.agent, "request", side_effect=self.bank_acknowledgment):
|
||||
self.assertEqual(self.agent.execute(operation)["state"], "completed")
|
||||
self.assertFalse(path.exists())
|
||||
self.assertEqual(self.docker.calls, calls)
|
||||
|
||||
def test_rebuild_settles_before_starting_replacement_and_retains_unknown_decisions(self):
|
||||
payload = self.create()
|
||||
path = self.bank_journal(("commit",))
|
||||
operation = self.operation("instance.rebuild", {"spec": payload, "nextGeneration": 2, "safetyOverride": True}, "rebuild")
|
||||
with patch.object(self.agent, "request", return_value={"state": "committed", "request_id": "wrong"}):
|
||||
self.assertEqual(self.agent.execute(operation)["state"], "unknown")
|
||||
self.assertTrue(path.exists())
|
||||
self.assertFalse(self.agent.inspect(INSTANCE, 2)["exists"])
|
||||
with patch.object(self.agent, "request", side_effect=self.bank_acknowledgment):
|
||||
# Existing rebuild recovery records the interrupted create as failed;
|
||||
# a fresh operation can then rebuild without replaying the old command.
|
||||
self.assertEqual(self.agent.execute(operation)["state"], "failed")
|
||||
retry = self.operation("instance.rebuild", operation["payload"], "replacement")
|
||||
self.assertEqual(self.agent.execute(retry)["state"], "completed")
|
||||
self.assertFalse(path.exists())
|
||||
self.assertTrue(self.agent.inspect(INSTANCE, 2)["running"])
|
||||
|
||||
def test_cancel_creation_retries_bank_settlement_before_deleting_runtime(self):
|
||||
self.create()
|
||||
path = self.bank_journal(("cancel",))
|
||||
operation = self.operation("instance.cancel-create", {"createOperationId": "create"}, "cancel")
|
||||
with patch.object(self.agent, "request", side_effect=AgentError("offline")):
|
||||
self.assertEqual(self.agent.execute(operation)["state"], "unknown")
|
||||
self.assertTrue(path.exists())
|
||||
with patch.object(self.agent, "request", side_effect=self.bank_acknowledgment):
|
||||
self.assertEqual(self.agent.execute(operation)["state"], "completed")
|
||||
self.assertFalse(path.exists())
|
||||
|
||||
def test_malformed_or_symlinked_bank_journal_blocks_storage_deletion(self):
|
||||
self.create()
|
||||
path = self.bank_journal()
|
||||
path.write_text('{broken')
|
||||
operation = self.operation("instance.remove", {"safetyOverride": True}, "remove")
|
||||
with patch.object(self.agent, "request") as request:
|
||||
self.assertEqual(self.agent.execute(operation)["state"], "unknown")
|
||||
self.assertTrue(path.exists())
|
||||
outside = Path(self.temp.name) / "outside.json"
|
||||
outside.write_text('{"reports": []}')
|
||||
self.bank_journal()
|
||||
journal = json.loads(path.read_text())
|
||||
journal["endpoint"] = "https://untrusted.example/api/bank"
|
||||
path.write_text(json.dumps(journal))
|
||||
self.assertEqual(self.agent.execute(operation)["state"], "unknown")
|
||||
path.unlink()
|
||||
path.symlink_to(outside)
|
||||
self.assertEqual(self.agent.execute(operation)["state"], "unknown")
|
||||
self.assertTrue(path.is_symlink())
|
||||
request.assert_not_called()
|
||||
|
||||
def test_rebuild_moves_generation_and_rejects_old_commands(self):
|
||||
payload = self.create()
|
||||
self.agent.rcon = lambda *_: {"ok": True, "players": 0}
|
||||
|
||||
Reference in New Issue
Block a user