Files
computer-playbook/plugins/lookup/node_hosts.py
T
kevinveenbirkenbachandClaude Opus 5 7d9a63c98b fix(svc-ai): create the node-local model directory on every swarm node
Both roles mount their model cache as a `type: bind` volume, and swarm
rejects a task whose bind source is missing on the node it lands on. Neither
declares a `placement`, so the scheduler may put the container on any node,
while the directory was created by a single task that reached one host. CI
showed the consequence directly:

    s1_s1.1 desired=Ready current=Rejected error="invalid mount config for
    type "bind": bind source path does not exist:
    /var/cache/infinito/core/cache/models/s1"

with the creating task logging exactly one result line, for the manager.

`nfs_prep.yml` already fans directories out across the cluster but gates on
`driver_opts is defined`, which a bind volume never carries, so these two
volumes were never covered by it.

The host list moves into a `node_hosts` lookup rather than an inline
expression, because two roles already spelled the same idea by hand and a
third and fourth were about to. It answers one question - the hosts a
node-local path must exist on - and keeps the swarm group name in one place
per plugin. The call sites use `query()`, since `lookup()` joins several
results into one string and a loop needs the list.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-29 16:46:19 +02:00

37 lines
1.1 KiB
Python

"""Every host a node-local path must exist on before a container binds it.
Swarm rejects a task whose bind source is missing on the node it lands on, and
a role that declares no ``placement`` may be scheduled onto any node, so the
path has to exist on all of them. Without a swarm group there is one host, the
one the task already runs on.
"""
from __future__ import annotations
from typing import Any
from ansible.plugins.lookup import LookupBase
SWARM_GROUP = "svc-swarm-node"
def node_hosts(vars_: dict[str, Any]) -> list[str]:
"""Return every host a node-local path must exist on.
Args:
vars_: the task variables the lookup was called with.
"""
hosts = [str(host) for host in (vars_.get("groups") or {}).get(SWARM_GROUP) or []]
return hosts or [str(vars_["inventory_hostname"])]
class LookupModule(LookupBase):
def run(
self,
terms: list[Any],
variables: dict[str, Any] | None = None,
**kwargs: Any,
) -> list[str]:
vars_ = variables or getattr(self._templar, "available_variables", {}) or {}
return node_hosts(vars_)