Merge branch 'feature/svc-ai-litellm/remote-models-config' into feature/store-i18n

# Conflicts:
#	cli/build/docs/readme/generate.py
#	docs/requirements/035-mcp-proxy-expansion.md
#	roles/svc-ai-lmstudio/meta/services.yml
This commit is contained in:
kevinveenbirkenbach committed 2026-09-25 16:45:16 +02:00
commit f2cee9bb34
532 files changed
+12314 -546

No files matched your search

@@ -61,12 +61,12 @@ class TestOwningRole(unittest.TestCase):
def test_foreign_role_id_fails(self):
with self.assertRaises(AnsibleActionFail) as caught:
self._spec_for("nfs-ganesha", "desk-micro")
self._spec_for("nfs-ganesha", "dsk-micro")
self.assertIn("svc-storage-nfs-server", str(caught.exception))
def test_unknown_id_fails(self):
with self.assertRaises(AnsibleActionFail):
self._spec_for("nope", "desk-micro")
self._spec_for("nope", "dsk-micro")
def test_own_id_resolves(self):
with mock.patch(
@@ -78,7 +78,7 @@ class TestOwningRole(unittest.TestCase):
def test_shared_id_resolves_for_any_role(self):
with mock.patch("plugins.action.package_install.resolve", return_value="spec"):
spec = self._spec_for("git", "desk-micro")
spec = self._spec_for("git", "dsk-micro")
self.assertEqual(spec, "spec")
def test_no_role_context_skips_the_check(self):
@@ -30,7 +30,7 @@ class TestGetAllInvokableApps(unittest.TestCase):
"util": {
"title": "utils",
"invokable": False,
"desk": {"title": "Desktop utils", "invokable": True},
"dsk": {"title": "Desktop utils", "invokable": True},
},
}
}
@@ -22,7 +22,7 @@ class TestGetDeploymentTypesFromGroups(unittest.TestCase):
return [
"web-app",
"web-svc",
"desk",
"dsk",
"update",
]
@@ -34,7 +34,7 @@ class TestGetDeploymentTypesFromGroups(unittest.TestCase):
get_deployment_types_from_groups(
[
"web-app-nextcloud",
"desk-firefox",
"dsk-firefox",
]
),
["server", "workstation"],
@@ -48,7 +48,7 @@ class TestGetDeploymentTypesFromGroups(unittest.TestCase):
get_deployment_types_from_groups(
[
"web-svc-logout",
"desk-nextcloud",
"dsk-nextcloud",
]
),
["server", "workstation"],
@@ -22,9 +22,7 @@ class TestGetEntityNameFilter(unittest.TestCase):
"app": {"title": "Applications", "invokable": True},
"svc": {"title": "Services", "invokable": True},
},
"util": {
"desk": {"dev": {"title": "Dev Utilities", "invokable": True}}
},
"util": {"dsk": {"dev": {"title": "Dev Utilities", "invokable": True}}},
"sys": {
"ctl": {
"bkp": {"title": "Backup", "invokable": True},
Whitespace-only changes.
@@ -0,0 +1,118 @@
from __future__ import annotations
import unittest
from plugins.filter.litellm.model_routes import litellm_model_routes
OLLAMA_URL = "http://ollama:11434"
LMSTUDIO_URL = "http://lmstudio:1234"
SHARED = {"alias": "qwen2.5:0.5b", "name": "qwen2.5-0.5b-instruct", "context": 32768}
LMSTUDIO_ONLY = {"alias": "mistral:latest", "name": "mistral-7b-instruct-v0.3"}
REMOTE = {
"alias": "openrouter/auto",
"model": "openrouter/openrouter/auto",
"provider": "openrouter",
}
MOCK = {
"alias": "mock/deterministic",
"provider": "mock",
"response": "pong",
"context": 131072,
}
def routes(ollama=(), lmstudio=(), declared=()):
return litellm_model_routes(
list(ollama),
list(lmstudio),
list(declared),
ollama_url=OLLAMA_URL,
lmstudio_url=LMSTUDIO_URL,
max_tokens=512,
timeout=60,
mock_provider=MOCK["provider"],
)
def by_alias(built):
return {route["alias"]: route for route in built}
class TestPublicationOrder(unittest.TestCase):
"""The first route is what LITELLM_CHAT_MODEL names, so order is a contract."""
def test_a_local_model_outranks_a_declared_one(self) -> None:
built = routes(ollama=[SHARED], declared=[MOCK, REMOTE])
self.assertEqual(next(route["alias"] for route in built), SHARED["alias"])
def test_declared_models_keep_their_declaration_order(self) -> None:
built = routes(declared=[MOCK, REMOTE])
self.assertEqual(
[route["alias"] for route in built], [MOCK["alias"], REMOTE["alias"]]
)
def test_nothing_declared_publishes_nothing(self) -> None:
self.assertEqual(routes(), [])
class TestLocalBackends(unittest.TestCase):
def test_ollama_is_called_through_its_own_scheme(self) -> None:
params = by_alias(routes(ollama=[SHARED]))[SHARED["alias"]]["params"]
self.assertEqual(params["model"], f"ollama/{SHARED['alias']}")
self.assertEqual(params["api_base"], OLLAMA_URL)
def test_a_declared_window_reaches_the_backend_and_the_model_info(self) -> None:
route = by_alias(routes(ollama=[SHARED]))[SHARED["alias"]]
self.assertEqual(route["params"]["num_ctx"], SHARED["context"])
self.assertEqual(route["context"], SHARED["context"])
def test_a_model_without_a_window_sets_no_num_ctx(self) -> None:
route = by_alias(routes(lmstudio=[LMSTUDIO_ONLY]))[LMSTUDIO_ONLY["alias"]]
self.assertNotIn("num_ctx", route["params"])
self.assertIsNone(route["context"])
def test_lmstudio_publishes_the_window_it_declares(self) -> None:
route = by_alias(routes(lmstudio=[SHARED]))[SHARED["alias"]]
self.assertEqual(
route["context"],
SHARED["context"],
"the LM Studio branch used to drop the window its README promised",
)
def test_an_alias_both_backends_serve_is_published_once_by_ollama(self) -> None:
built = routes(ollama=[SHARED], lmstudio=[SHARED, LMSTUDIO_ONLY])
self.assertEqual(
[route["alias"] for route in built],
[SHARED["alias"], LMSTUDIO_ONLY["alias"]],
)
self.assertEqual(
by_alias(built)[SHARED["alias"]]["params"]["api_base"], OLLAMA_URL
)
class TestDeclaredModels(unittest.TestCase):
def test_a_provider_route_reads_its_key_from_the_environment(self) -> None:
params = by_alias(routes(declared=[REMOTE]))[REMOTE["alias"]]["params"]
self.assertEqual(params["api_key"], "os.environ/OPENROUTER_API_KEY")
self.assertEqual(params["model"], REMOTE["model"])
def test_a_mock_answers_from_its_own_response(self) -> None:
params = by_alias(routes(declared=[MOCK]))[MOCK["alias"]]["params"]
self.assertEqual(params["mock_response"], MOCK["response"])
def test_a_mock_names_no_upstream_to_call(self) -> None:
params = by_alias(routes(declared=[MOCK]))[MOCK["alias"]]["params"]
self.assertNotIn("api_base", params)
self.assertNotIn(
"timeout", params, "a mock cannot time out against a backend it never calls"
)
def test_a_mock_publishes_its_window(self) -> None:
self.assertEqual(
by_alias(routes(declared=[MOCK]))[MOCK["alias"]]["context"], MOCK["context"]
)
if __name__ == "__main__":
unittest.main()
@@ -94,14 +94,14 @@ class TestDotenvQuoteModeAware(unittest.TestCase):
def test_swarm_non_string_is_stringified(self):
self.assertEqual(self._render(42, mode="swarm"), "42")
def test_swarm_quotes_value_with_interior_whitespace(self):
def test_swarm_passes_a_value_with_spaces_through_unchanged(self):
self.assertEqual(
self._render("My Org Helpdesk", mode="swarm"), '"My Org Helpdesk"'
self._render('{"hermes": {"group": "/x"}}', mode="swarm"),
'{"hermes": {"group": "/x"}}',
"swarm delivers the value verbatim, so quoting it makes the "
"container read the quotes as part of the JSON and parse a string",
)
def test_swarm_whitespace_value_escapes_quotes_not_dollars(self):
self.assertEqual(self._render('a "b" $c', mode="swarm"), '"a \\"b\\" $c"')
def test_compose_mode_quotes_like_default(self):
self.assertEqual(self._render("abc", mode="compose"), '"abc"')
self.assertEqual(self._render("$ecret", mode="compose"), '"$$ecret"')
@@ -47,12 +47,23 @@ class TestCaInjectedLookup(unittest.TestCase):
def setUpClass(cls):
cls.mod = _load_lookup()
def _resolve(self, *, domains, enabled, mode, app="web-app-moodle"):
def _resolve(
self,
*,
domains,
enabled,
mode,
app="web-app-moodle",
services=None,
tls_mode="self_signed",
):
calls: list[tuple[str, list]] = []
def fake_get(name, **_kwargs):
def run(args, variables=None):
calls.append((name, list(args)))
if name == "config":
return [services or {}]
if name == "domains":
return [domains]
return [enabled if args[1] == "enabled" else mode]
@@ -63,7 +74,8 @@ class TestCaInjectedLookup(unittest.TestCase):
plugin._loader = None
plugin._templar = _DummyTemplar()
with mock.patch.object(self.mod.lookup_loader, "get", side_effect=fake_get):
result = plugin.run([app], variables={})
variables = {} if tls_mode is None else {"TLS_MODE": tls_mode}
result = plugin.run([app], variables=variables)
return result[0], calls
def test_a_self_signed_app_with_a_domain_gets_the_ca(self):
@@ -93,7 +105,64 @@ class TestCaInjectedLookup(unittest.TestCase):
def test_an_app_without_a_domain_never_asks_about_tls(self):
verdict, calls = self._resolve(domains={}, enabled=True, mode="self_signed")
self.assertIs(verdict, False)
self.assertEqual([name for name, _ in calls], ["domains"])
self.assertEqual([name for name, _ in calls], ["domains", "config"])
def test_a_domainless_ca_client_gets_the_ca_on_the_deployment_mode(self):
verdict, calls = self._resolve(
domains={},
enabled=True,
mode="self_signed",
services={"agent-broker": {"ca_client": True}, "socket-proxy": {}},
)
self.assertIs(
verdict,
True,
"a container that only calls the self-signed endpoint has no domain "
"of its own, and without the CA every outbound call fails the "
"handshake",
)
self.assertEqual([name for name, _ in calls], ["domains", "config"])
def test_a_ca_client_with_a_domain_follows_its_own_tls_mode(self):
verdict, calls = self._resolve(
domains={"web-app-moodle": "moodle.example"},
enabled=True,
mode="letsencrypt",
services={"agent-broker": {"ca_client": True}},
tls_mode="self_signed",
)
self.assertIs(
verdict,
False,
"an app that has a domain is served over its own resolved mode, which "
"may override the deployment default; the ca_client shortcut must not "
"answer for it on the global TLS_MODE",
)
self.assertEqual([name for name, _ in calls], ["domains", "tls", "tls"])
def test_a_ca_client_gets_nothing_when_the_ca_is_not_self_signed(self):
verdict, _ = self._resolve(
domains={},
enabled=True,
mode="self_signed",
services={"agent-broker": {"ca_client": True}},
tls_mode="letsencrypt",
)
self.assertIs(verdict, False)
def test_a_ca_client_refuses_an_absent_tls_mode(self):
with self.assertRaises(
AnsibleError,
msg="a missing mode read as 'not self signed' withholds the CA in "
"silence, and every handshake then fails far from here",
):
self._resolve(
domains={},
enabled=True,
mode="self_signed",
services={"agent-broker": {"ca_client": True}},
tls_mode=None,
)
def test_the_term_is_required(self):
plugin = self.mod.LookupModule()
@@ -30,6 +30,14 @@ class _DomainsResolveStub:
return [(variables or {}).get("domains", {})]
class _CaInjectedStub:
def __init__(self, injected: bool):
self._injected = injected
def run(self, terms, variables=None, **kwargs):
return [self._injected]
class TestComposeFArgs(unittest.TestCase):
def setUp(self):
self.m = _load_module(
@@ -48,18 +56,48 @@ class TestComposeFArgs(unittest.TestCase):
},
}
def _loader_dispatch(self, tls_stub):
# "domains" resolves through variables['domains'] to keep tests hermetic;
# "tls" resolves through the per-test stub.
def _loader_dispatch(self, tls_stub, ca_injected=None):
verdict = tls_stub.run(None)[0]
injected = (
(verdict["enabled"] and verdict["mode"] == "self_signed")
if ca_injected is None
else ca_injected
)
def _get(name, *args, **kwargs):
if name == "domains":
return _DomainsResolveStub()
if name == "tls":
return tls_stub
if name == "ca_injected":
return _CaInjectedStub(injected)
raise AssertionError(f"unexpected lookup name: {name}")
return _get
def test_a_domainless_ca_client_still_gets_the_ca_override_file(self):
with (
patch.object(
self.m.lookup_loader,
"get",
side_effect=self._loader_dispatch(
_TlsResolveStub(False, "off"), ca_injected=True
),
),
patch.object(
self.m, "get_docker_paths", side_effect=self._stub_get_docker_paths
),
patch.object(self.m, "_role_provides_override", return_value=False),
):
out = self.lookup.run(["web-app-a"], variables={**self.vars, "domains": {}})
self.assertIn(
"/x/compose.ca.override.yml",
out[0],
"ca_injected grants a domainless ca_client the override and the "
"handler writes it; a domain gate here leaves that file on disk "
"while the container runs without a trust store",
)
def _stub_get_docker_paths(self, application_id: str, base_dir: str) -> dict:
self.assertEqual(application_id, "web-app-a")
self.assertEqual(base_dir, "/x/")
@@ -0,0 +1,84 @@
"""The stage loop skips roles no host carries, and gives every host one list.
Dropping what nobody deploys is free: 132 of 139 entries were skipped one task
at a time on a single-app deploy, 278 task deltas, 41.8 seconds measured on
2026-09-23.
Dropping what *this* host does not carry is not free, and these cases forbid
it. 8aea5a9b35 measured that a per-host condition above the loop makes ansible
insert blocks in host order rather than loop order, after which
svc-storage-nfs-client mounted an export svc-storage-nfs-server had not
deployed yet (run 31645153977). The list therefore has to come out identical
for every host, and per-host narrowing stays in the body's ``when``.
"""
from __future__ import annotations
import unittest
from typing import ClassVar
from unittest import mock
from plugins.lookup.group_roles import LookupModule
ENTRIES = [
{"role": "web-app-alpha", "app": "alpha"},
{"role": "web-app-beta", "app": "beta"},
{"role": "web-app-gamma", "app": "gamma"},
]
def run(**kwargs):
with mock.patch(
"plugins.lookup.group_roles.ordered_roles", return_value=list(ENTRIES)
):
return LookupModule().run(["web-app"], **kwargs)[0]
class TestWithoutAnInventory(unittest.TestCase):
def test_no_groups_returns_the_whole_group(self) -> None:
self.assertEqual(run(), ENTRIES)
class TestDroppingWhatNobodyDeploys(unittest.TestCase):
def test_a_role_no_host_carries_is_dropped(self) -> None:
selected = run(inventory_groups={"alpha": ["h1"], "beta": []})
self.assertEqual([entry["app"] for entry in selected], ["alpha"])
def test_an_empty_inventory_deploys_nothing(self) -> None:
self.assertEqual(run(inventory_groups={}), [])
def test_run_after_order_survives_the_filter(self) -> None:
selected = run(inventory_groups={"gamma": ["h1"], "alpha": ["h1"]})
self.assertEqual(
[entry["app"] for entry in selected],
["alpha", "gamma"],
"run_after order is the contract; the inventory's key order is not",
)
class TestSameListForEveryHost(unittest.TestCase):
"""The property 8aea5a9b35 paid for; losing it inverts deploy order."""
GROUPS: ClassVar[dict] = {"alpha": ["h1"], "beta": ["h2"], "gamma": ["h1", "h2"]}
def test_a_role_only_one_host_carries_still_enters_the_loop(self) -> None:
selected = [entry["app"] for entry in run(inventory_groups=self.GROUPS)]
self.assertEqual(
selected,
["alpha", "beta", "gamma"],
"beta belongs to h2 only; dropping it for h1 would give the two "
"hosts different loops and let host order decide item order",
)
def test_the_lookup_reads_no_per_host_variable(self) -> None:
selected = run(inventory_groups=self.GROUPS, group_names=["alpha"])
self.assertEqual(
[entry["app"] for entry in selected],
["alpha", "beta", "gamma"],
"a host's own groups must not reach this lookup; narrowing to one "
"host belongs in the body's when",
)
if __name__ == "__main__":
unittest.main()
@@ -245,6 +245,29 @@ class RolesWithServiceLookupTests(unittest.TestCase):
}
self.assertEqual(self._run(["dashboard"], applications), [[]])
def test_include_headless_keeps_a_role_without_canonical_domain(self):
applications = {
"svc-ai-foo": {
"services": {
"litellm": {"enabled": True, "shared": True},
},
"domains": {},
},
}
self.assertEqual(
self._run(["litellm"], applications, include_headless=True),
[
[
{
"id": "svc-ai-foo",
"canonical_domain": "",
"canonical_url": "",
"iframe": True,
}
]
],
)
def test_canonical_as_string_is_accepted(self):
applications = {
"web-app-foo": {