Files
computer-playbook/tests/unit/python/plugins/lookup/test_webserver_resource.py
T
kevinveenbirkenbachandClaude Opus 5 f7b6f0d84e feat(resources): let cpus name a percentage of the host
A container cap had to be an absolute number, and docker refuses one above
the host's core count, so "use this machine" could only be written as a
number that breaks on a smaller machine. cpus now also takes a percentage,
read against the host's raw count: 100% is every CPU and lands exactly on the
ceiling docker accepts, 50% is half of it.

The share is read against that raw count rather than against the fair-share
default, because the two CPUs the resource lookup reserves exist to bound
what a service gets when it declares nothing. A service writing a percentage
overrides that default on purpose, and subtracting the reserve a second time
would count it twice and leave no way to name the whole machine.

A bare cpus: 0 is refused from here on. Docker reads a zero as uncapped
rather than as none, so the value most likely to be typed by mistake was also
the one that removed the limit; the error names the fix. No role declared
one: all 315 cpus values in the tree are plain numbers, the smallest 0.01.

Two lookups carry the resolution so that no caller repeats it or has to know
what a percentage is read against. lookup('cpus', app, service) replaces six
copies of lookup('config', ...) piped through the resolver; those six size
worker pools and thread counts from their own cpus, and left alone a
percentage would have given libretranslate a single thread and handed the
agent broker an uncapped container. lookup('webserver_resource', key)
replaces the two blocks that took the smaller of the proxy's value and the
app's, which were the same eight lines written twice for cpus and for
pids_limit.

resolve_cpus is no longer registered as a jinja filter. Nothing referenced it
from a template once the lookups existed, and keeping it would have left two
public ways to resolve one value.

Measured: 96 tests across the filter, both lookups and the container render
templates, including that a percentage resolves before the smaller of the two
is chosen, and that pids_limit stays an integer because nginx renders it
straight into worker_connections.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-28 10:14:43 +02:00

54 lines
2.0 KiB
Python

import unittest
from unittest.mock import patch
from ansible.errors import AnsibleError
from plugins.lookup.webserver_resource import PROXY_ROLE, LookupModule
RESOURCE = {"cpus": "0.5", "pids_limit": 512, "host_cpus": 20}
def _run(key, proxy, app, application_id="web-app-docs", service_name=""):
applications = {
PROXY_ROLE: {"services": {"openresty": {key: proxy}}},
application_id: {"services": {"docs": {key: app}}},
}
variables = {"application_id": application_id, "service_name": service_name}
with (
patch("plugins.lookup.webserver_resource.ApplicationsLookup") as apps,
patch("plugins.lookup.webserver_resource.ResourceLookup") as resource,
):
apps.return_value.run.return_value = [applications]
resource.return_value.run.side_effect = lambda terms, **_k: [RESOURCE[terms[0]]]
return LookupModule().run([key], variables=variables)
class TestWebserverResource(unittest.TestCase):
def test_the_tighter_of_the_two_wins(self):
self.assertEqual(_run("cpus", proxy="4", app="2"), [2.0])
self.assertEqual(_run("cpus", proxy="1", app="2"), [1.0])
def test_a_percentage_is_resolved_before_the_comparison(self):
"""95% of 20 is 19, so a literal 4 is the tighter of the pair."""
self.assertEqual(_run("cpus", proxy="4", app="95%"), [4.0])
def test_a_percentage_can_be_the_tighter_one(self):
self.assertEqual(_run("cpus", proxy="50%", app="16"), [10.0])
def test_pids_limit_stays_an_integer(self):
result = _run("pids_limit", proxy=1024, app=512)
self.assertEqual(result, [512])
self.assertIsInstance(result[0], int)
def test_an_unknown_key_is_refused(self):
with self.assertRaises(AnsibleError):
LookupModule().run(["mem_limit"], variables={})
def test_more_than_one_term_is_refused(self):
with self.assertRaises(AnsibleError):
LookupModule().run(["cpus", "pids_limit"], variables={})
if __name__ == "__main__":
unittest.main()