fix(volume-lookup): force application-defaults build on registry miss so nfs_prep pre-creates the NFS subdir

lookup('volume', role) read the lazily-populated canonical-volumes registry directly, so a lookup running before any consumer built that role (nfs_prep's pre-create loop for an early unpinned engine such as ollama) saw an empty registry and skipped the subdir; the swarm bind mount then failed with 'no such file or directory'. Force get_application_defaults() on a miss, guarded so the populated hot path skips the deepcopy. Adds two guard tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 4.8 committed 2026-06-24 03:46:13 +02:00
1 parent 0e18481ce7
commit 687f1243cd
2 files changed
+38 -4

No files matched your search

+9 -1
View File
@@ -30,7 +30,7 @@ from typing import Any
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
from utils.cache.applications import get_canonical_volumes
from utils.cache.applications import get_application_defaults, get_canonical_volumes
class LookupModule(LookupBase):
@@ -50,6 +50,14 @@ class LookupModule(LookupBase):
raise AnsibleError("volume lookup: application_id must be non-empty")
canonical = get_canonical_volumes(application_id)
if not canonical:
# The canonical-volumes registry fills lazily as a side effect of
# building the application defaults; force it on a miss so a lookup
# that runs before any consumer built this role (e.g. nfs_prep's
# pre-create loop) does not see an empty registry and silently skip
# the subdir. Guarded so the populated hot path skips the deepcopy.
get_application_defaults()
canonical = get_canonical_volumes(application_id)
if len(terms) == 1:
return [dict(canonical)]
+29 -3
View File
@@ -29,7 +29,7 @@ def _run(
with patch(
"plugins.lookup.volume.get_canonical_volumes",
return_value=canonical if canonical is not None else {},
):
), patch("plugins.lookup.volume.get_application_defaults"):
return LookupModule().run([application_id, name], variables={})
@@ -168,7 +168,7 @@ class TestVolumeLookup(unittest.TestCase):
with patch(
"plugins.lookup.volume.get_canonical_volumes",
return_value=canonical,
):
), patch("plugins.lookup.volume.get_application_defaults"):
result = LookupModule().run(["web-app-mattermost"], variables={})
self.assertEqual(result, [canonical])
@@ -176,7 +176,7 @@ class TestVolumeLookup(unittest.TestCase):
with patch(
"plugins.lookup.volume.get_canonical_volumes",
return_value={},
):
), patch("plugins.lookup.volume.get_application_defaults"):
result = LookupModule().run(["web-app-no-meta"], variables={})
self.assertEqual(result, [{}])
@@ -188,6 +188,32 @@ class TestVolumeLookup(unittest.TestCase):
with self.assertRaises(AnsibleError):
_run("web-app-mattermost", "", canonical={"data": {}})
def test_forces_defaults_build_on_registry_miss(self):
# Empty on the first read (lookup runs before any consumer built the
# role); populated after the forced build — the nfs_prep/ollama case.
populated = {"data": {"type": "volume"}}
with patch(
"plugins.lookup.volume.get_canonical_volumes",
side_effect=[{}, populated],
) as gcv, patch(
"plugins.lookup.volume.get_application_defaults",
) as gad:
result = LookupModule().run(["svc-ai-ollama"], variables={})
self.assertEqual(result, [populated])
gad.assert_called_once()
self.assertEqual(gcv.call_count, 2)
def test_populated_registry_skips_build(self):
# Already populated: no forced build, no wasted defaults deepcopy.
with patch(
"plugins.lookup.volume.get_canonical_volumes",
return_value={"data": {"type": "volume"}},
), patch(
"plugins.lookup.volume.get_application_defaults",
) as gad:
LookupModule().run(["web-app-mattermost"], variables={})
gad.assert_not_called()
if __name__ == "__main__":
unittest.main()