Skip to content

fix(lb): retry reconcile changes rejected as busy - #66

Open
lexfrei wants to merge 1 commit into
fix/retry-busy-balancerfrom
fix/retry-busy-balancer-changes
Open

lexfrei wants to merge 1 commit into
fix/retry-busy-balancerfrom
fix/retry-busy-balancer-changes

Conversation

@lexfrei

@lexfrei lexfrei commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator

Changes that a reconcile makes to a busy balancer now retry, not only adding targets. Before this, a locked, conflict, robot_unavailable or 5xx answer on any other call failed the whole reconcile. A reconcile that changes the type and then the services often hit the lock the type change had just taken.

Removing targets, changing the type or the algorithm, attaching or detaching a network, and adding, updating or deleting a service now use the same retry as adding targets. That is two more tries, after 1s and 2s. The constants are renamed to BUSY_RETRIES and BUSY_RETRY_UNIT, since they are not about targets any more.

Adding a target that is already there now counts as success. Hetzner answers target_already_defined when a retry follows a 5xx that it had applied anyway. Before, a balancer with a single target then reported "No target could be added" although the target was there.

The other calls have no such special case. If Hetzner applies one of them and still answers 5xx, the retry may get a permanent error, for example a service that already exists. The reconcile then fails the same way it did before this change, and the next run reads the new state. A type change can also hold the lock longer than the three seconds of retries, and then the reconcile still waits for the next run.

Two smaller changes come with it. With the wrappers the services reconcile went over the clippy line limit, so the "service already matches" check moved into service_is_current. Deleting the balancer in cleanup() gets no retry here, because another open PR rewrites that function. #65 tracks it.

Tests cover the target_already_defined classification only. Removing that arm from reconcile_targets, or a wrapped call that skips the retry, keeps the suite green. The call sites are async and the repo has no HTTP mock.

This adds a one-line conflict with the pipeline port in ci/port-pipeline, in reconcile_algorithm. Keep the retry wrapper and use Some(self.algorithm.clone()).

Stacked on #59.

Closes #60

Adding targets already retried when Hetzner answered locked, conflict,
robot_unavailable or 5xx, but the other changes a reconcile makes to a
balancer failed the whole reconcile on the first such answer. A
reconcile that changes the type and then the services is likely to hit
the lock the type change just took, and then waits for the next retry
of the service.

During a reconcile, removing targets, changing the type or the
algorithm, attaching or detaching a network, and adding, updating or
deleting a service now go through the same short retry. The retry
constants lose their add-target names since they now cover all of
these calls. Deleting a balancer when its service goes away is left as
it was.

A target Hetzner reports as already defined now counts as live. That
answer comes when an earlier attempt was applied but answered with a
5xx, and without it a balancer with a single target reported that no
target could be added although the target was there.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant