@@ -283,6 +283,116 @@ func TestGatewayController_reconcileFilterConfigSecret(t *testing.T) {
283283 }
284284}
285285
286+ func TestGatewayController_reconcileFilterConfigSecret_SkipsDeletedRoutes (t * testing.T ) {
287+ fakeClient := requireNewFakeClientWithIndexes (t )
288+ kube := fake2 .NewClientset ()
289+ ctrl .SetLogger (zap .New (zap .UseFlagOptions (& zap.Options {Development : true , Level : zapcore .DebugLevel })))
290+ c := NewGatewayController (fakeClient , kube , ctrl .Log ,
291+ "docker.io/envoyproxy/ai-gateway-extproc:latest" , false , nil )
292+
293+ const gwNamespace = "ns"
294+ now := metav1 .Now ()
295+
296+ // Create routes: one active, one being deleted.
297+ routes := []aigv1a1.AIGatewayRoute {
298+ {
299+ ObjectMeta : metav1.ObjectMeta {
300+ Name : "active-route" ,
301+ Namespace : gwNamespace ,
302+ DeletionTimestamp : nil , // Active route.
303+ },
304+ Spec : aigv1a1.AIGatewayRouteSpec {
305+ Rules : []aigv1a1.AIGatewayRouteRule {
306+ {
307+ BackendRefs : []aigv1a1.AIGatewayRouteRuleBackendRef {
308+ {Name : "apple" },
309+ },
310+ Matches : []aigv1a1.AIGatewayRouteRuleMatch {
311+ {
312+ Headers : []gwapiv1.HTTPHeaderMatch {
313+ {
314+ Name : aigv1a1 .AIModelHeaderKey ,
315+ Value : "mymodel" ,
316+ },
317+ },
318+ },
319+ },
320+ },
321+ },
322+ },
323+ },
324+ {
325+ ObjectMeta : metav1.ObjectMeta {
326+ Name : "deleting-route" ,
327+ Namespace : gwNamespace ,
328+ DeletionTimestamp : & now , // Route being deleted.
329+ },
330+ Spec : aigv1a1.AIGatewayRouteSpec {
331+ Rules : []aigv1a1.AIGatewayRouteRule {
332+ {
333+ BackendRefs : []aigv1a1.AIGatewayRouteRuleBackendRef {
334+ {Name : "orange" },
335+ },
336+ Matches : []aigv1a1.AIGatewayRouteRuleMatch {
337+ {
338+ Headers : []gwapiv1.HTTPHeaderMatch {
339+ {
340+ Name : aigv1a1 .AIModelHeaderKey ,
341+ Value : "deletedmodel" ,
342+ },
343+ },
344+ },
345+ },
346+ },
347+ },
348+ },
349+ },
350+ }
351+
352+ // Create AIServiceBackends for both routes.
353+ for _ , backend := range []* aigv1a1.AIServiceBackend {
354+ {
355+ ObjectMeta : metav1.ObjectMeta {Name : "apple" , Namespace : gwNamespace },
356+ Spec : aigv1a1.AIServiceBackendSpec {
357+ BackendRef : gwapiv1.BackendObjectReference {Name : "some-backend1" , Namespace : ptr.To [gwapiv1.Namespace ](gwNamespace )},
358+ },
359+ },
360+ {
361+ ObjectMeta : metav1.ObjectMeta {Name : "orange" , Namespace : gwNamespace },
362+ Spec : aigv1a1.AIServiceBackendSpec {
363+ BackendRef : gwapiv1.BackendObjectReference {Name : "some-backend2" , Namespace : ptr.To [gwapiv1.Namespace ](gwNamespace )},
364+ },
365+ },
366+ } {
367+ err := fakeClient .Create (t .Context (), backend )
368+ require .NoError (t , err )
369+ }
370+
371+ const someNamespace = "some-namespace"
372+ configName := FilterConfigSecretPerGatewayName ("gw" , gwNamespace )
373+
374+ // Reconcile filter config secret.
375+ err := c .reconcileFilterConfigSecret (t .Context (), configName , someNamespace , routes , "foouuid" )
376+ require .NoError (t , err )
377+
378+ // Verify the secret was created and only contains data from the active route.
379+ secret , err := kube .CoreV1 ().Secrets (someNamespace ).Get (t .Context (), configName , metav1.GetOptions {})
380+ require .NoError (t , err )
381+ configStr , ok := secret .StringData [FilterConfigKeyInSecret ]
382+ require .True (t , ok )
383+
384+ var fc filterapi.Config
385+ require .NoError (t , yaml .Unmarshal ([]byte (configStr ), & fc ))
386+
387+ // Should only have one model (from the active route), not two (deleted route should be skipped).
388+ require .Len (t , fc .Models , 1 )
389+ require .Equal (t , "mymodel" , fc .Models [0 ].Name )
390+
391+ // Should only have one backend (from the active route).
392+ require .Len (t , fc .Backends , 1 )
393+ require .Contains (t , fc .Backends [0 ].Name , "apple" )
394+ }
395+
286396func TestGatewayController_bspToFilterAPIBackendAuth (t * testing.T ) {
287397 fakeClient := requireNewFakeClientWithIndexes (t )
288398 kube := fake2 .NewClientset ()
0 commit comments