Skip to content

Commit ed0457d

Browse files
bug: prune stale HTTPRoutes when tags are removed (#901)
Signed-off-by: kahirokunn <[email protected]> Co-authored-by: kahirokunn <[email protected]>
1 parent d8bde1c commit ed0457d

File tree

4 files changed

+110
-0
lines changed

4 files changed

+110
-0
lines changed

pkg/reconciler/ingress/fixtures_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ func (r HTTPRoute) Build() *gatewayapi.HTTPRoute {
5757
networking.IngressClassAnnotationKey: gatewayAPIIngressClassName,
5858
},
5959
Labels: map[string]string{
60+
networking.IngressLabelKey: "name",
6061
networking.VisibilityLabelKey: "",
6162
},
6263
OwnerReferences: []metav1.OwnerReference{{

pkg/reconciler/ingress/ingress.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@ import (
2323

2424
apierrs "k8s.io/apimachinery/pkg/api/errors"
2525
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"k8s.io/apimachinery/pkg/labels"
27+
"k8s.io/apimachinery/pkg/util/sets"
2628

2729
"knative.dev/net-gateway-api/pkg/reconciler/ingress/config"
30+
"knative.dev/net-gateway-api/pkg/reconciler/ingress/resources"
2831
"knative.dev/net-gateway-api/pkg/status"
32+
"knative.dev/networking/pkg/apis/networking"
2933
"knative.dev/networking/pkg/apis/networking/v1alpha1"
3034
ingressreconciler "knative.dev/networking/pkg/client/injection/reconciler/networking/v1alpha1/ingress"
3135
"knative.dev/networking/pkg/ingress"
@@ -102,7 +106,10 @@ func (c *Reconciler) reconcileIngress(ctx context.Context, ing *v1alpha1.Ingress
102106

103107
routesReady := true
104108

109+
desiredRouteNames := sets.New[string]()
105110
for _, rule := range ing.Spec.Rules {
111+
desiredRouteNames.Insert(resources.LongestHost(rule.Hosts))
112+
106113
httproute, probeTargets, err := c.reconcileHTTPRoute(ctx, ingressHash, ing, &rule)
107114
if err != nil {
108115
return err
@@ -123,6 +130,25 @@ func (c *Reconciler) reconcileIngress(ctx context.Context, ing *v1alpha1.Ingress
123130
}
124131
}
125132

133+
// Delete HTTPRoutes that don't exist in the current Spec (i.e., tags removed and no longer referenced)
134+
{
135+
selector := labels.SelectorFromSet(labels.Set{
136+
networking.IngressLabelKey: ing.Name,
137+
})
138+
existingRoutes, err := c.httprouteLister.HTTPRoutes(ing.Namespace).List(selector)
139+
if err != nil {
140+
return fmt.Errorf("failed to list HTTPRoutes: %w", err)
141+
}
142+
for _, r := range existingRoutes {
143+
// Not in the desired set = unnecessary
144+
if !desiredRouteNames.Has(r.Name) {
145+
if err := c.gwapiclient.GatewayV1().HTTPRoutes(r.Namespace).Delete(ctx, r.Name, metav1.DeleteOptions{}); err != nil && !apierrs.IsNotFound(err) {
146+
return fmt.Errorf("failed to delete stale HTTPRoute %s/%s: %w", r.Namespace, r.Name, err)
147+
}
148+
}
149+
}
150+
}
151+
126152
externalIngressTLS := ing.GetIngressTLSForVisibility(v1alpha1.IngressVisibilityExternalIP)
127153
listeners := make([]*gatewayapi.Listener, 0, len(externalIngressTLS))
128154
for _, tls := range externalIngressTLS {

pkg/reconciler/ingress/ingress_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ import (
2323
"time"
2424

2525
corev1 "k8s.io/api/core/v1"
26+
apierrs "k8s.io/apimachinery/pkg/api/errors"
2627
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2728
"k8s.io/apimachinery/pkg/runtime"
29+
"k8s.io/apimachinery/pkg/runtime/schema"
2830
"k8s.io/apimachinery/pkg/types"
2931
clientgotesting "k8s.io/client-go/testing"
3032
"k8s.io/utils/ptr"
@@ -196,6 +198,86 @@ func TestReconcile(t *testing.T) {
196198
httpRoute(t, ing(withBasicSpec, withGatewayAPIclass), httpRouteReady),
197199
}, servicesAndEndpoints...),
198200
// no extra update
201+
}, {
202+
Name: "prune stale HTTPRoute when rule removed",
203+
Key: "ns/name",
204+
Objects: append([]runtime.Object{
205+
ing(withBasicSpec, withGatewayAPIclass, withFinalizer, makeItReady),
206+
httpRoute(t, ing(withBasicSpec, withGatewayAPIclass), httpRouteReady),
207+
HTTPRoute{
208+
Name: "stale.example.com",
209+
Namespace: "ns",
210+
Hostname: "stale.example.com",
211+
}.Build(),
212+
}, servicesAndEndpoints...),
213+
WantDeletes: []clientgotesting.DeleteActionImpl{
214+
clientgotesting.NewDeleteAction(
215+
schema.GroupVersionResource{
216+
Group: "gateway.networking.k8s.io",
217+
Version: "v1",
218+
Resource: "httproutes",
219+
},
220+
"ns",
221+
"stale.example.com",
222+
),
223+
},
224+
}, {
225+
Name: "prune skips non-owned HTTPRoute",
226+
Key: "ns/name",
227+
Objects: append(func() []runtime.Object {
228+
route := HTTPRoute{
229+
Name: "stale.example.com",
230+
Namespace: "ns",
231+
Hostname: "stale.example.com",
232+
}.Build()
233+
// Remove controller ownership and the ingress label so this HTTPRoute
234+
// is neither controlled by nor labeled for this Ingress. It should
235+
// therefore be ignored by the prune logic.
236+
route.OwnerReferences = nil
237+
delete(route.Labels, networking.IngressLabelKey)
238+
return []runtime.Object{
239+
ing(withBasicSpec, withGatewayAPIclass, withFinalizer, makeItReady),
240+
httpRoute(t, ing(withBasicSpec, withGatewayAPIclass), httpRouteReady),
241+
route,
242+
}
243+
}(), servicesAndEndpoints...),
244+
// No deletes expected
245+
WantDeletes: []clientgotesting.DeleteActionImpl{},
246+
}, {
247+
Name: "prune delete NotFound tolerated",
248+
Key: "ns/name",
249+
WithReactors: []clientgotesting.ReactionFunc{
250+
func(a clientgotesting.Action) (bool, runtime.Object, error) {
251+
if a.GetVerb() == "delete" && a.GetResource().Resource == "httproutes" {
252+
name := a.(clientgotesting.DeleteActionImpl).Name
253+
return true, nil, apierrs.NewNotFound(
254+
schema.GroupResource{Group: "gateway.networking.k8s.io", Resource: "httproutes"},
255+
name,
256+
)
257+
}
258+
return false, nil, nil
259+
},
260+
},
261+
Objects: append([]runtime.Object{
262+
ing(withBasicSpec, withGatewayAPIclass, withFinalizer, makeItReady),
263+
httpRoute(t, ing(withBasicSpec, withGatewayAPIclass), httpRouteReady),
264+
HTTPRoute{
265+
Name: "stale.example.com",
266+
Namespace: "ns",
267+
Hostname: "stale.example.com",
268+
}.Build(),
269+
}, servicesAndEndpoints...),
270+
WantDeletes: []clientgotesting.DeleteActionImpl{
271+
clientgotesting.NewDeleteAction(
272+
schema.GroupVersionResource{
273+
Group: "gateway.networking.k8s.io",
274+
Version: "v1",
275+
Resource: "httproutes",
276+
},
277+
"ns",
278+
"stale.example.com",
279+
),
280+
},
199281
}}
200282

201283
table.Test(t, MakeFactory(func(ctx context.Context, listers *Listers, cmw configmap.Watcher) controller.Reconciler {

pkg/reconciler/ingress/resources/httproute.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ func MakeHTTPRoute(
204204
Name: LongestHost(rule.Hosts),
205205
Namespace: ing.Namespace,
206206
Labels: kmeta.UnionMaps(ing.Labels, map[string]string{
207+
networking.IngressLabelKey: ing.Name,
207208
networking.VisibilityLabelKey: visibility,
208209
}),
209210
Annotations: kmeta.FilterMap(ing.GetAnnotations(), func(key string) bool {

0 commit comments

Comments
 (0)