johannes-engler-mw opened a new issue, #2833:
URL: https://github.com/apache/apisix-ingress-controller/issues/2833

   ## Current Behavior
   
   When an HTTPRoute's `parentRefs` are repointed from a Gateway managed by 
controller A to a
   Gateway managed by controller B (a different 
`GatewayClass.spec.controllerName`), controller B
   picks the route up correctly — but **controller A never removes the 
configuration it had already
   pushed**. Its data plane keeps serving the route indefinitely.
   
   The chain, on `master` (`be4aaef`) and on the released `2.1.0`:
   
   1. `httproute_controller.go:75` — the reconciler is registered with
      `For(&gatewayv1.HTTPRoute{})` and no controller-name predicate, so 
**every** controller
      instance watches **every** HTTPRoute. Repointing `parentRefs` bumps the 
generation, so
      `GenerationChangedPredicate` passes and controller A does reconcile the 
change.
   
   2. `utils.go:357-359` — `ParseRouteParentRefs` skips parentRefs whose 
GatewayClass belongs to
      another controller:
   
      ```go
      if string(gatewayClass.Spec.ControllerName) != 
config.ControllerConfig.ControllerName {
          continue
      }
      ```
   
      For controller A the route's only remaining parentRef is now controller 
B's Gateway, so the
      returned slice is empty.
   
   3. `httproute_controller.go:172-174` — the empty slice returns early with no 
cleanup:
   
      ```go
      if len(gateways) == 0 {
          return ctrl.Result{}, nil
      }
      ```
   
      `Provider.Delete` is only reachable from the `IgnoreNotFound` branch at 
:147, i.e. when the
      object is deleted from the cluster. There is no path for "this object 
used to be mine and no
      longer is".
   
   4. `adc/client/client.go:290,303` — the periodic sync replays the in-memory 
store
      (`ConfigManager.List()` → `GetResources(name)`); it never re-derives 
state from Kubernetes.
      The orphaned service therefore gets re-pushed to the data plane every 
sync period, forever.
   
   Restarting the controller pod clears it, because startup rebuilds the store 
from the informer's
   initial list and the route no longer produces an entry for controller A.
   
   The same early return exists in all five Gateway API route reconcilers:
   
   ```
   httproute_controller.go:172
   grpcroute_controller.go:190
   tcproute_controller.go:283
   udproute_controller.go:283
   tlsroute_controller.go:283
   ```
   
   ### Why this is hard to notice
   
   Every Kubernetes-level signal is correct and green while it happens:
   
   - the HTTPRoute's `status.parents` lists only controller B, with 
`Accepted=True`
   - controller B's Gateway shows the route in 
`status.listeners[].attachedRoutes`
   - controller A's Gateway shows the correct (reduced) `attachedRoutes` count
   - controller A logs no errors — its sync loop reports success every period
   - GitOps tooling reports the manifests as fully synced
   
   The stale configuration is visible **only** in the old data plane's own 
control API
   (`:9090/v1/services`). In our case the orphaned routes carried public 
hostnames, so this failed
   *open*: hosts that were supposed to have been taken off the public gateway 
stayed publicly
   reachable for over 21 hours, with the label metadata and `modifiedIndex` 
still showing values
   from weeks before the change.
   
   ### Prior art
   
   PR #2543 ("fix: residual data issue when updating ingressClassName", merged 
2025-09-08) fixed
   exactly this failure mode for Ingress, and its description states the 
problem in general terms:
   
   > the controller simply ignores the Ingress resource. However, this behavior 
is problematic: the
   > corresponding configuration is not removed and remains in the data plane, 
which may result in
   > incorrect routing or stale configurations.
   
   The resulting code in `ingress_controller.go:159-166` calls 
`Provider.Delete` when the
   IngressClass no longer matches. The Gateway API route reconcilers were never 
given the
   equivalent.
   
   ## Expected Behavior
   
   When a route no longer references any Gateway managed by this controller, 
the controller removes
   the configuration it previously pushed for that route, so the data plane 
stops serving it —
   matching the Ingress/IngressClass behaviour from #2543.
   
   ## Error Logs
   
   None. That is a significant part of the problem: the controller logs no 
error at any log level,
   and the ADC sync reports success on every tick while re-pushing the orphaned 
configuration.
   
   ## Steps to Reproduce
   
   1. Install two APISIX ingress controller instances with distinct controller 
names, e.g.
      `apisix.apache.org/apisix-ingress-controller` and
      `apisix.apache.org/apisix-ingress-controller-internal`, each with its own 
GatewayClass and
      Gateway (`gateway-a` in namespace `apisix`, `gateway-b` in namespace 
`apisix-internal`).
   
   2. Create an HTTPRoute with `parentRefs: [gateway-a]` and a hostname, and 
confirm it serves:
   
      ```console
      $ curl -H 'Host: demo.example.com' http://<gateway-a>/
      200
      ```
   
   3. Change **only** the `parentRefs` to `gateway-b` (keep the same 
name/namespace) and apply.
   
   4. Observe that controller B accepts the route and `gateway-b` serves it — 
and that `gateway-a`
      **still** serves it too:
   
      ```console
      $ curl -H 'Host: demo.example.com' http://<gateway-a>/
      200        # expected: 404
      ```
   
   5. Confirm the orphan is in controller A's data plane, and that it is never 
removed:
   
      ```console
      $ kubectl port-forward -n apisix pod/<apisix-pod> 9090:9090
      $ curl -s localhost:9090/v1/services | jq -r '.[].value | 
"\(.labels["k8s/name"]) \(.hosts)"'
      demo ["demo.example.com"]        # still present, indefinitely
      ```
   
   6. `kubectl rollout restart deploy/apisix-ingress-controller -n apisix` — 
the entry disappears,
      confirming the store is the only thing holding it.
   
   The same reproduction applies to GRPCRoute, TCPRoute, UDPRoute and TLSRoute.
   
   ## Environment
   
   - APISIX Ingress controller version: `2.1.0` (also reproduces on `master`, 
`be4aaef`)
   - ADC version: `0.26.0`
   - Mode: standalone / ADC (`config_provider: yaml`)
   - Kubernetes cluster version: `v1.34.9`
   - Gateway API version: `v1.3.0`
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to