Name the sizes a node can ask for

Raw cpus and gpus are a property of the machines an installation has, so a node
written against a cluster quietly stops meaning anything when the cluster is
replaced. A node says "gpu-small" instead, and what that is stored here —
editable, and read again every time the node is built, so changing the flavor
changes what the next run gets.

Memory joins the schema properly (`ram`, in MB, accepting "2G"), along with
`duration_s` for how long a node is expected to take. That one is recorded and
shown and nothing else yet: a statement for whoever is planning around the node,
not a limit — the limit is still `timeout`.

A flavor and a number for the same thing is refused, compared by value so an
editor writing the whole object back with its defaults still round-trips. A name
nothing stores is refused at the save, which covers the canvas and `fluksio
sync` at once, and deleting one a node still asks for says which node.

Four sizes are seeded on an installation that has none, and never re-seeded:
re-adding one somebody deliberately removed is an argument nobody wins.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6HeySA27EkGANZN95QySW
This commit is contained in:
2026-08-27 08:59:10 +02:00
co-authored by Claude Opus 5
parent 6ff56533f5
commit a82f88cf0a
15 changed files with 720 additions and 19 deletions
+33
View File
@@ -150,3 +150,36 @@ def test_declaring_nothing_stays_exactly_as_it_was():
def test_a_misspelled_resource_is_refused():
with pytest.raises(ValueError):
Resources(cpu=4)
# -----------------------------------------------------------------------------
# Sizes with names, and sizes written out
# -----------------------------------------------------------------------------
def test_a_size_can_be_written_the_way_people_write_sizes():
assert Resources(ram="2G").ram == 2048
assert Resources(ram="512M").ram == 512
assert Resources(ram="512").ram == 512
assert Resources(ram=512).ram == 512
with pytest.raises(ValueError, match="not a size"):
Resources(ram="a lot")
def test_a_duration_is_one_unit_and_says_so_when_it_is_not():
assert Resources(duration_s="2h").duration_s == 7200
assert Resources(duration_s="30m").duration_s == 1800
assert Resources(duration_s="90").duration_s == 90
with pytest.raises(ValueError, match="not a duration"):
Resources(duration_s="1h30m")
def test_a_flavor_and_a_number_for_the_same_thing_is_two_answers():
with pytest.raises(ValueError, match="already says how much"):
Resources(flavor="gpu-small", cpus=4)
# The defaults are not an answer: an editor writing the whole object back
# sends them, and that must survive the round trip.
assert Resources(flavor="gpu-small", cpus=1, gpus=0).flavor == "gpu-small"
# Neither is a duration, which a flavor says nothing about.
assert Resources(flavor="gpu-small", duration_s="2h").duration_s == 7200