What needs to happen
The gateway engine reserves per-tenant quota before it programs a rule into the datapath. Three failure paths leave state behind when that programming does not go to plan.
A rule that passes the quota check and then fails to program holds its reservation. The engine does not add it to the active set, so nothing will ever release it. If the operator then deletes the rule, the reservation is held until the process restarts. Each cycle of that shrinks the node's remaining capacity, and the enforcer denies admission long before the datapath is actually full.
Teardown releases quota only after the datapath removal succeeds. A removal that fails leaves the reservation held. That one self-heals on the next pass, because the rule stays in the active set and gets retried.
Shutdown handles teardown failures asymmetrically. The first failure keeps its rule in the active set. Every later failure drops its rule from the set anyway, so the engine reports state gone that the datapath still holds.
There is one more, a layer down. Programming a rule with several virtual addresses writes them one at a time, and the bookkeeping that lets teardown find them is written only after all of them succeed. A failure partway through leaves earlier writes in the table with nothing tracking them. Orphan recovery does sweep those up, so this is bounded, not permanent.
Why this matters
Quota enforcement is live in the gateway binary with real limits, not a stub, so the drift is real once a rule ever fails to program. The symptom shows up far from the cause: rules get refused on a node whose table is mostly empty, and a restart clears it, which is the worst shape a bug can have for on-call.
Why it is not urgent
Every one of these needs a programming failure to trigger, and the whole feature is pre-production. None of them corrupts forwarding state.
Desired outcome
- Release the reservation on the failure path so a rule that never programmed does not hold capacity.
- Handle teardown failures the same way regardless of whether an earlier one already failed.
- Track partial programming so teardown can find what was written, or unwind it on failure rather than relying on orphan recovery.
- Cover all of it with tests. The current suite exercises the happy paths and quota denial, but no test asserts what quota or the active set look like after a failure.
Found while reviewing #350.
What needs to happen
The gateway engine reserves per-tenant quota before it programs a rule into the datapath. Three failure paths leave state behind when that programming does not go to plan.
A rule that passes the quota check and then fails to program holds its reservation. The engine does not add it to the active set, so nothing will ever release it. If the operator then deletes the rule, the reservation is held until the process restarts. Each cycle of that shrinks the node's remaining capacity, and the enforcer denies admission long before the datapath is actually full.
Teardown releases quota only after the datapath removal succeeds. A removal that fails leaves the reservation held. That one self-heals on the next pass, because the rule stays in the active set and gets retried.
Shutdown handles teardown failures asymmetrically. The first failure keeps its rule in the active set. Every later failure drops its rule from the set anyway, so the engine reports state gone that the datapath still holds.
There is one more, a layer down. Programming a rule with several virtual addresses writes them one at a time, and the bookkeeping that lets teardown find them is written only after all of them succeed. A failure partway through leaves earlier writes in the table with nothing tracking them. Orphan recovery does sweep those up, so this is bounded, not permanent.
Why this matters
Quota enforcement is live in the gateway binary with real limits, not a stub, so the drift is real once a rule ever fails to program. The symptom shows up far from the cause: rules get refused on a node whose table is mostly empty, and a restart clears it, which is the worst shape a bug can have for on-call.
Why it is not urgent
Every one of these needs a programming failure to trigger, and the whole feature is pre-production. None of them corrupts forwarding state.
Desired outcome
Found while reviewing #350.