mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 11:46:35 +00:00
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>
51 lines
1.7 KiB
Python
51 lines
1.7 KiB
Python
import unittest
|
|
from unittest.mock import patch
|
|
|
|
from ansible.errors import AnsibleError
|
|
|
|
from plugins.lookup.cpus import LookupModule
|
|
|
|
VARS = {"application_id": "web-app-docs"}
|
|
|
|
|
|
def _run(terms, configured, host_cpus=20):
|
|
with (
|
|
patch("plugins.lookup.cpus.ConfigLookup") as config,
|
|
patch("plugins.lookup.cpus.ResourceLookup") as resource,
|
|
):
|
|
config.return_value.run.return_value = [configured]
|
|
resource.return_value.run.return_value = [host_cpus]
|
|
result = LookupModule().run(terms, variables=VARS)
|
|
return result, config.return_value.run.call_args
|
|
|
|
|
|
class TestCpusLookup(unittest.TestCase):
|
|
def test_a_percentage_becomes_a_share_of_the_host(self):
|
|
result, _ = _run(["web-app-docs", "docs"], "95%")
|
|
self.assertEqual(result, [19.0])
|
|
|
|
def test_a_plain_value_passes_through(self):
|
|
result, _ = _run(["web-app-docs", "docs"], "2")
|
|
self.assertEqual(result, ["2"])
|
|
|
|
def test_the_service_name_builds_the_config_path(self):
|
|
_, call = _run(["web-app-hermes", "hermes"], "50%")
|
|
self.assertEqual(call.args[0], ["web-app-hermes", "services.hermes.cpus"])
|
|
|
|
@patch("plugins.lookup.cpus.get_entity_name", return_value="docs")
|
|
def test_the_entity_name_is_the_default_service(self, _entity):
|
|
_, call = _run(["web-app-docs"], "50%")
|
|
self.assertEqual(call.args[0], ["web-app-docs", "services.docs.cpus"])
|
|
|
|
def test_no_terms_is_refused(self):
|
|
with self.assertRaises(AnsibleError):
|
|
LookupModule().run([], variables=VARS)
|
|
|
|
def test_a_third_term_is_refused(self):
|
|
with self.assertRaises(AnsibleError):
|
|
LookupModule().run(["a", "b", "c"], variables=VARS)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|