Fix non-deterministic ordering of the partition capacity response - #656
Fix non-deterministic ordering of the partition capacity response#656muhittink wants to merge 1 commit into
Conversation
calcPartitionCapacity collects partitions in a map and appends them to the result slice by ranging over it. Go randomizes map iteration, so the response order varies between calls and TestPartitionCapacity fails once a fixture contains two partitions: 17 of 100 runs on afae26f with go1.26.0. The map is the only unstable axis in the test, but not in production: SearchMachines builds its term from Filter calls only and never calls OrderBy, so RethinkDB row order is unspecified and ServerCapacities, FaultyMachines and OtherMachines inherit that order as well. Sorting in the service rather than in the test makes the API response stable instead of only silencing cmp.Diff. This changes the ordering clients see in the response. Generated-By: Claude Code (Opus 5) Generated-By: deepseek-v4-flash
|
Thanks for taking care of this, but we do not want to introduce much new code here, please try instead fixing it here: https://github.com/metal-stack/metal-apiserver |
|
OK, thx for the hint. I will prepare the PR for v2 and close this one as soon as the PR is ready. |
|
Closing in favour of metal-stack/metal-apiserver#303, as suggested. Thanks for the quick pointer. For anyone finding this later: in metal-apiserver the partition list is already sorted ( #634 stays open: |
Description
TestPartitionCapacity/non_filter_considers_all_machinesfails in roughly one out of six runs. It is an ordering problem, not an arithmetic one.calcPartitionCapacitycollects the partitions in a map and appends them to the result slice by ranging over it, with no sort before the return. Go randomizes map iteration and the assertion compares withcmp.Diff, which is order sensitive. As soon as a fixture holds two partitions, the test is a coin flip.Measured on
master(afae26f, go1.26.0 linux/amd64), 100 runs of that subtest: 83 PASS, 17 FAIL. With this change: 100 PASS, 0 FAIL.This is not a regression of #632. That pull request only swapped
Machines: msforallMs; the map and the unsorted append are older. What #632 added was the first fixture with two partitions inwant- the other 15 cases hold one and are trivially stable.It is not only the test
SearchMachinesbuilds its term frommachineTable()and chainsFiltercalls only;OrderByappears nowhere in thedatastorepackage. The row order RethinkDB returns is therefore unspecified, and four fields of the response inherit it:[]PartitionCapacitypc.ServerCapacitiescap.FaultyMachinescap.OtherMachinesAn operator does not see the first two, because metalctl repairs them on the client:
partitionCapacity()callsPartitionCapacitySorter().SortBy(...), whose default key is the partition ID, and then sortspc.Serversby size. That has been in place since metalctl #157 (aac9723, 2022-08-24). The sort keys in this pull request are deliberately the same two, so the API starts returning what metalctl has been re-sorting for three years.What no client repairs is
FaultyMachinesandOtherMachines, and what no client should have to repair is the response itself. Inside the organisation, metal-metrics-exporter keys its gauges by partition and size and is indifferent to order, but metal-python, direct API consumers and anything else reading the endpoint get an order that can change between two identical calls.That is also the reason the sort sits in the service and not in the test. Sorting the two slices in
partition-service_test.gobefore comparing would fix the flake just as well, and leave the response as unspecified as it is today. If you prefer the endpoint to stay unordered and the test to do the sorting, say so and I will turn it around.This changes the ordering clients see, although for metalctl the printed table stays exactly as it is. Partitions come back sorted by ID, server capacities by size, the machine ID lists ascending.
Verification
The sort over
ServerCapacitiesis not passing vacuously: the fixtures already listsize-abeforesize-b, so reversing the machine order handed tomockMachinesin that case (m5, m4, m3, m2, m1) putssize-bfirst. That reversed fixture fails 5 of 5 runs onafae26fand passes 5 of 5 with this change.Known limits
slices.SortonFaultyMachinesandOtherMachinesis defensive. Every fixture holds at most one entry in those slices, so no assertion can make either line fail. They are argued correct, not shown correct.ReservationsandUsedReservationsaccumulate with+=, every delta is non-negative,Freeclamps at zero - not from a measurement. No fixture holds two reservations for the same size and partition.calcPartitionCapacity. It is evidence that nothing else broke, not evidence for this change. It was run locally; theintegrationjob on this pull request failed while pullingtestcontainers/ryukfrom Docker Hub, before a single test started.Used AI-Tools ✨
Closes #634.