Files
computer-playbook/tests/unit/python/roles/sys-token-store/test_write_many.py
T
kevinveenbirkenbachandClaude Opus 5.5 96ebefb9db perf(spot): re-render only the users SPOT after a token store write
A token store write re-rendered the whole applications SPOT: merged_applications_cache_key carried the token store digest, so the applications_carrier park in sys-token-store/tasks/write_many.yml missed its key and rendered all 269 applications again (stalwart compose roundtrip 18.3 s and 10.2 s per pass, swarm pass 1 12.2 s and 12.6 s).

Rendered from the real roles tree under an empty, a realistic and an exhaustive token store (298 users x 269 applications), the applications tree differs only under <app>.users.<key>.tokens (28783 leaves in 46 applications, 0 elsewhere); the 69 password leaves that also differ change between two renders of the same store. No code reads a token through the applications tree: every reader goes through lookup('users'), lookup('config', <app>, 'users.<key>...') (served from get_merged_users), lookup('email') or the sys_token_store_users fact, and the users carrier keys on the store.

The token digest therefore moves from merged_applications_cache_key into merged_users_cache_key, whose value stays the same. A store write keeps the applications carrier and the write_many park re-renders the users alone; the task is renamed to match. applications.md now says that user records inside the payload can predate every store write and that application config must not derive a value from a token.

Real ansible-playbook harness on 2.20.6 with the real roles, group_vars and write_many.yml: the re-park after a first mint and after a rotation drops from 4.0 s to 0.55 s on one host (applications renders 1 -> 0), and every token probe (users, config users path, email password, stalwart_users) equals the old code in compose and swarm mode. Dropping the digest from the users key as well leaves those probes stale, which the harness and two unit tests catch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-29 15:56:08 +01:00

165 lines
5.7 KiB
Python

from __future__ import annotations
import unittest
from ansible.parsing.dataloader import DataLoader
from ansible.template import Templar
from utils.cache.files import PROJECT_ROOT
from utils.cache.yaml import load_yaml_any
from utils.templating.ansible import _trust_as_template
TASKS = PROJECT_ROOT / "roles" / "sys-token-store" / "tasks"
MERGE = "🔨 Build merged structure"
EMPTY = "🛑 Abort if a token is empty (do not persist empties)"
PERSIST = "💾 Persist store (idempotent, single-writer via STACK_HOST)"
MIRROR = "🪞 Mirror the store onto the controller, where every users lookup reads"
HAND_OVER = "🔄 Hand the written store to the lookups of the rest of the play"
RERENDER = "🧊 Re-render the users SPOT the new tokens invalidated"
USERS = {
"alice": {"tokens": {"web-app-a": "old-a"}, "note": "kept"},
"carol": {"tokens": {"web-app-c": "token-c"}},
}
SKIPPED = {"changed": False, "skipped": True}
def _tasks(name: str) -> list[dict]:
return load_yaml_any(str(TASKS / name), default_if_missing=[])
def _task(name: str) -> dict:
return next(t for t in _tasks("write_many.yml") if t.get("name") == name)
def _render(template, **variables):
templar = Templar(loader=DataLoader(), variables=variables)
return templar.template(_trust_as_template(template))
def _merged(entries: list[dict], users: dict = USERS) -> dict:
return _render(
_task(MERGE)["ansible.builtin.set_fact"]["sys_token_store_users_merged"],
sys_token_store_entries=entries,
sys_token_store_users=users,
)
def _when(name: str, **variables) -> bool:
when = _task(name)["when"]
conditions = when if isinstance(when, list) else [when]
return _render(
"{{ ((" + ") and (".join(conditions) + ")) | bool }}",
DEPLOYMENT_MODE="swarm",
**variables,
)
def _entry(user_key: str, app: str, token: str) -> dict:
return {"user_key": user_key, "app": app, "token": token}
class TestWriteMany(unittest.TestCase):
def test_every_entry_lands_in_one_merge(self):
merged = _merged(
[
_entry("alice", "web-app-b", " new-b "),
_entry("alice", "web-app-a", "new-a"),
_entry("bob", "web-app-b", "token-b"),
]
)
self.assertEqual(
merged,
{
"alice": {
"tokens": {"web-app-a": "new-a", "web-app-b": "new-b"},
"note": "kept",
},
"bob": {"tokens": {"web-app-b": "token-b"}},
"carol": {"tokens": {"web-app-c": "token-c"}},
},
)
def test_the_later_entry_of_one_user_and_app_wins(self):
merged = _merged(
[_entry("bob", "web-app-b", "first"), _entry("bob", "web-app-b", "second")]
)
self.assertEqual(merged["bob"], {"tokens": {"web-app-b": "second"}})
def test_an_unchanged_store_skips_the_write_and_the_rerender(self):
merged = _merged(
[
_entry("alice", "web-app-a", "old-a"),
_entry("carol", "web-app-c", "token-c"),
]
)
self.assertEqual(merged, USERS)
state = {"sys_token_store_users_merged": merged, "sys_token_store_users": USERS}
self.assertFalse(_when(PERSIST, **state))
self.assertFalse(_when(MIRROR, **state))
self.assertFalse(_when(HAND_OVER, **state))
self.assertFalse(
_when(
RERENDER,
sys_token_store_persisted=SKIPPED,
sys_token_store_mirrored=SKIPPED,
)
)
def test_a_changed_store_is_written_and_rerendered_once(self):
merged = _merged([_entry("bob", "web-app-b", "token-b")])
state = {"sys_token_store_users_merged": merged, "sys_token_store_users": USERS}
self.assertTrue(_when(PERSIST, **state))
self.assertTrue(_when(HAND_OVER, **state))
self.assertTrue(
_when(
RERENDER,
sys_token_store_persisted={"changed": True},
sys_token_store_mirrored=SKIPPED,
)
)
rerenders = [t for t in _tasks("write_many.yml") if "applications_carrier" in t]
self.assertEqual(len(rerenders), 1)
self.assertNotIn("loop", rerenders[0])
def test_the_store_has_one_writer(self):
persist = _task(PERSIST)
self.assertEqual(persist["delegate_to"], "{{ STACK_HOST }}")
self.assertIs(persist["run_once"], True)
mirror = _task(MIRROR)
self.assertEqual(mirror["delegate_to"], "localhost")
self.assertIs(mirror["run_once"], True)
def test_an_empty_token_is_refused_by_name(self):
empty = _render(
_task(EMPTY)["vars"]["sys_token_store_empty"],
sys_token_store_entries=[
_entry("alice", "web-app-a", "set"),
_entry("bob", "web-app-b", " "),
],
)
self.assertEqual(empty, ["bob/web-app-b"])
class TestWriteIsAOneEntryBatch(unittest.TestCase):
def test_the_single_token_contract_feeds_write_many(self):
tasks = _tasks("write.yml")
self.assertEqual(len(tasks), 1)
self.assertEqual(tasks[0]["ansible.builtin.import_tasks"], "write_many.yml")
single = {
"sys_token_store_user_key": "administrator",
"sys_token_store_app": "web-app-matomo",
"sys_token_store_token": "secret-token",
}
entries = [
{field: _render(value, **single) for field, value in entry.items()}
for entry in tasks[0]["vars"]["sys_token_store_entries"]
]
self.assertEqual(
entries, [_entry("administrator", "web-app-matomo", "secret-token")]
)
if __name__ == "__main__":
unittest.main()