1
16
17 package kubernetes
18
19 import (
20 "context"
21 "errors"
22 "fmt"
23 "net"
24 "reflect"
25 "strconv"
26 "strings"
27 "sync"
28 "testing"
29 "time"
30
31 "github.com/stretchr/testify/require"
32
33 v1 "k8s.io/api/core/v1"
34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
35 "k8s.io/apimachinery/pkg/types"
36 "k8s.io/apimachinery/pkg/util/wait"
37 "sigs.k8s.io/controller-runtime/pkg/client"
38
39 gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
40 "sigs.k8s.io/gateway-api/apis/v1alpha2"
41 "sigs.k8s.io/gateway-api/conformance/utils/config"
42 )
43
44
45
46
47
48 const GatewayExcludedFromReadinessChecks = "gateway-api/skip-this-for-readiness"
49
50
51
52 type GatewayRef struct {
53 types.NamespacedName
54 listenerNames []*gatewayv1.SectionName
55 }
56
57
58 func NewGatewayRef(nn types.NamespacedName, listenerNames ...string) GatewayRef {
59 var listeners []*gatewayv1.SectionName
60
61 if len(listenerNames) == 0 {
62 listenerNames = append(listenerNames, "")
63 }
64
65 for _, listener := range listenerNames {
66 sectionName := gatewayv1.SectionName(listener)
67 listeners = append(listeners, §ionName)
68 }
69 return GatewayRef{
70 NamespacedName: nn,
71 listenerNames: listeners,
72 }
73 }
74
75
76 func GWCMustHaveAcceptedConditionTrue(t *testing.T, c client.Client, timeoutConfig config.TimeoutConfig, gwcName string) string {
77 return gwcMustBeAccepted(t, c, timeoutConfig, gwcName, string(metav1.ConditionTrue))
78 }
79
80
81 func GWCMustHaveAcceptedConditionAny(t *testing.T, c client.Client, timeoutConfig config.TimeoutConfig, gwcName string) string {
82 return gwcMustBeAccepted(t, c, timeoutConfig, gwcName, "")
83 }
84
85
86
87
88
89 func gwcMustBeAccepted(t *testing.T, c client.Client, timeoutConfig config.TimeoutConfig, gwcName, expectedStatus string) string {
90 t.Helper()
91
92 var controllerName string
93 waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, timeoutConfig.GWCMustBeAccepted, true, func(ctx context.Context) (bool, error) {
94 gwc := &gatewayv1.GatewayClass{}
95 err := c.Get(ctx, types.NamespacedName{Name: gwcName}, gwc)
96 if err != nil {
97 return false, fmt.Errorf("error fetching GatewayClass: %w", err)
98 }
99
100 controllerName = string(gwc.Spec.ControllerName)
101
102 if err := ConditionsHaveLatestObservedGeneration(gwc, gwc.Status.Conditions); err != nil {
103 t.Log("GatewayClass", err)
104 return false, nil
105 }
106
107
108 return findConditionInList(t, gwc.Status.Conditions, "Accepted", expectedStatus, ""), nil
109 })
110 require.NoErrorf(t, waitErr, "error waiting for %s GatewayClass to have Accepted condition to be set: %v", gwcName, waitErr)
111
112 return controllerName
113 }
114
115
116
117 func GatewayMustHaveLatestConditions(t *testing.T, c client.Client, timeoutConfig config.TimeoutConfig, gwNN types.NamespacedName) {
118 t.Helper()
119
120 waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, timeoutConfig.LatestObservedGenerationSet, true, func(ctx context.Context) (bool, error) {
121 gw := &gatewayv1.Gateway{}
122 err := c.Get(ctx, gwNN, gw)
123 if err != nil {
124 return false, fmt.Errorf("error fetching Gateway: %w", err)
125 }
126
127 if err := ConditionsHaveLatestObservedGeneration(gw, gw.Status.Conditions); err != nil {
128 t.Logf("Gateway %s latest conditions not set yet: %v", gwNN.String(), err)
129 return false, nil
130 }
131
132 return true, nil
133 })
134
135 require.NoErrorf(t, waitErr, "error waiting for Gateway %s to have Latest ObservedGeneration to be set: %v", gwNN.String(), waitErr)
136 }
137
138
139
140 func GatewayClassMustHaveLatestConditions(t *testing.T, gwc *gatewayv1.GatewayClass) {
141 t.Helper()
142
143 if err := ConditionsHaveLatestObservedGeneration(gwc, gwc.Status.Conditions); err != nil {
144 t.Fatalf("GatewayClass %v", err)
145 }
146 }
147
148
149
150 func HTTPRouteMustHaveLatestConditions(t *testing.T, r *gatewayv1.HTTPRoute) {
151 t.Helper()
152
153 for _, parent := range r.Status.Parents {
154 if err := ConditionsHaveLatestObservedGeneration(r, parent.Conditions); err != nil {
155 t.Fatalf("HTTPRoute(controller=%v, parentRef=%#v) %v", parent.ControllerName, parent, err)
156 }
157 }
158 }
159
160 func ConditionsHaveLatestObservedGeneration(obj metav1.Object, conditions []metav1.Condition) error {
161 staleConditions := FilterStaleConditions(obj, conditions)
162
163 if len(staleConditions) == 0 {
164 return nil
165 }
166
167 wantGeneration := obj.GetGeneration()
168 var b strings.Builder
169 fmt.Fprintf(&b, "expected observedGeneration to be updated to %d for all conditions", wantGeneration)
170 fmt.Fprintf(&b, ", only %d/%d were updated.", len(conditions)-len(staleConditions), len(conditions))
171 fmt.Fprintf(&b, " stale conditions are: ")
172
173 for i, c := range staleConditions {
174 fmt.Fprintf(&b, "%s (generation %d)", c.Type, c.ObservedGeneration)
175 if i != len(staleConditions)-1 {
176 fmt.Fprintf(&b, ", ")
177 }
178 }
179
180 return errors.New(b.String())
181 }
182
183
184
185 func FilterStaleConditions(obj metav1.Object, conditions []metav1.Condition) []metav1.Condition {
186 stale := make([]metav1.Condition, 0, len(conditions))
187 for _, condition := range conditions {
188 if obj.GetGeneration() != condition.ObservedGeneration {
189 stale = append(stale, condition)
190 }
191 }
192 return stale
193 }
194
195
196
197
198 func NamespacesMustBeReady(t *testing.T, c client.Client, timeoutConfig config.TimeoutConfig, namespaces []string) {
199 t.Helper()
200
201 waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, timeoutConfig.NamespacesMustBeReady, true, func(ctx context.Context) (bool, error) {
202 for _, ns := range namespaces {
203 gwList := &gatewayv1.GatewayList{}
204 err := c.List(ctx, gwList, client.InNamespace(ns))
205 if err != nil {
206 t.Errorf("Error listing Gateways: %v", err)
207 }
208 for _, gw := range gwList.Items {
209 gw := gw
210
211 if val, ok := gw.Annotations[GatewayExcludedFromReadinessChecks]; ok && val == "true" {
212 t.Logf("Gateway %s/%s is skipped for setup and wont be tested", ns, gw.Name)
213 continue
214 }
215
216 if err = ConditionsHaveLatestObservedGeneration(&gw, gw.Status.Conditions); err != nil {
217 t.Logf("Gateway %s/%s %v", ns, gw.Name, err)
218 return false, nil
219 }
220
221
222 if !findConditionInList(t, gw.Status.Conditions, string(gatewayv1.GatewayConditionAccepted), "True", "") {
223 t.Logf("%s/%s Gateway not Accepted yet", ns, gw.Name)
224 return false, nil
225 }
226
227
228 if !findConditionInList(t, gw.Status.Conditions, string(gatewayv1.GatewayConditionProgrammed), "True", "") {
229 t.Logf("%s/%s Gateway not Programmed yet", ns, gw.Name)
230 return false, nil
231 }
232 }
233
234 podList := &v1.PodList{}
235 err = c.List(ctx, podList, client.InNamespace(ns))
236 if err != nil {
237 t.Errorf("Error listing Pods: %v", err)
238 }
239 for _, pod := range podList.Items {
240 if !findPodConditionInList(t, pod.Status.Conditions, "Ready", "True") &&
241 pod.Status.Phase != v1.PodSucceeded &&
242 pod.DeletionTimestamp == nil {
243 t.Logf("%s/%s Pod not ready yet", ns, pod.Name)
244 return false, nil
245 }
246 }
247 }
248 t.Logf("Gateways and Pods in %s namespaces ready", strings.Join(namespaces, ", "))
249 return true, nil
250 })
251 require.NoErrorf(t, waitErr, "error waiting for %s namespaces to be ready", strings.Join(namespaces, ", "))
252 }
253
254
255
256 func GatewayMustHaveCondition(
257 t *testing.T,
258 client client.Client,
259 timeoutConfig config.TimeoutConfig,
260 gwNN types.NamespacedName,
261 expectedCondition metav1.Condition,
262 ) {
263 t.Helper()
264
265 waitErr := wait.PollUntilContextTimeout(
266 context.Background(),
267 1*time.Second,
268 timeoutConfig.GatewayMustHaveCondition,
269 true,
270 func(ctx context.Context) (bool, error) {
271 gw := &gatewayv1.Gateway{}
272 err := client.Get(ctx, gwNN, gw)
273 if err != nil {
274 return false, fmt.Errorf("error fetching Gateway: %w", err)
275 }
276
277 if err := ConditionsHaveLatestObservedGeneration(gw, gw.Status.Conditions); err != nil {
278 return false, err
279 }
280
281 if findConditionInList(t,
282 gw.Status.Conditions,
283 expectedCondition.Type,
284 string(expectedCondition.Status),
285 expectedCondition.Reason,
286 ) {
287 return true, nil
288 }
289
290 return false, nil
291 },
292 )
293
294 require.NoErrorf(t, waitErr, "error waiting for Gateway status to have a Condition matching expectations")
295 }
296
297
298
299
300 func MeshNamespacesMustBeReady(t *testing.T, c client.Client, timeoutConfig config.TimeoutConfig, namespaces []string) {
301 t.Helper()
302
303 waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, timeoutConfig.NamespacesMustBeReady, true, func(ctx context.Context) (bool, error) {
304 for _, ns := range namespaces {
305 podList := &v1.PodList{}
306 err := c.List(ctx, podList, client.InNamespace(ns))
307 if err != nil {
308 t.Errorf("Error listing Pods: %v", err)
309 }
310 for _, pod := range podList.Items {
311 if !findPodConditionInList(t, pod.Status.Conditions, "Ready", "True") &&
312 pod.Status.Phase != v1.PodSucceeded &&
313 pod.DeletionTimestamp == nil {
314 t.Logf("%s/%s Pod not ready yet", ns, pod.Name)
315 return false, nil
316 }
317 }
318 }
319 t.Logf("Pods in %s namespaces ready", strings.Join(namespaces, ", "))
320 return true, nil
321 })
322 require.NoErrorf(t, waitErr, "error waiting for %s namespaces to be ready", strings.Join(namespaces, ", "))
323 }
324
325
326
327
328
329
330
331
332
333
334 func GatewayAndHTTPRoutesMustBeAccepted(t *testing.T, c client.Client, timeoutConfig config.TimeoutConfig, controllerName string, gw GatewayRef, routeNNs ...types.NamespacedName) string {
335 t.Helper()
336
337 gwAddr, err := WaitForGatewayAddress(t, c, timeoutConfig, gw.NamespacedName)
338 require.NoErrorf(t, err, "timed out waiting for Gateway address to be assigned")
339
340 ns := gatewayv1.Namespace(gw.Namespace)
341 kind := gatewayv1.Kind("Gateway")
342
343 for _, routeNN := range routeNNs {
344 namespaceRequired := true
345 if routeNN.Namespace == gw.Namespace {
346 namespaceRequired = false
347 }
348
349 var parents []gatewayv1.RouteParentStatus
350 for _, listener := range gw.listenerNames {
351 parents = append(parents, gatewayv1.RouteParentStatus{
352 ParentRef: gatewayv1.ParentReference{
353 Group: (*gatewayv1.Group)(&gatewayv1.GroupVersion.Group),
354 Kind: &kind,
355 Name: gatewayv1.ObjectName(gw.Name),
356 Namespace: &ns,
357 SectionName: listener,
358 },
359 ControllerName: gatewayv1.GatewayController(controllerName),
360 Conditions: []metav1.Condition{{
361 Type: string(gatewayv1.RouteConditionAccepted),
362 Status: metav1.ConditionTrue,
363 Reason: string(gatewayv1.RouteReasonAccepted),
364 }},
365 })
366 }
367 HTTPRouteMustHaveParents(t, c, timeoutConfig, routeNN, parents, namespaceRequired)
368 }
369
370 requiredListenerConditions := []metav1.Condition{
371 {
372 Type: string(gatewayv1.ListenerConditionResolvedRefs),
373 Status: metav1.ConditionTrue,
374 Reason: "",
375 },
376 {
377 Type: string(gatewayv1.ListenerConditionAccepted),
378 Status: metav1.ConditionTrue,
379 Reason: "",
380 },
381 {
382 Type: string(gatewayv1.ListenerConditionProgrammed),
383 Status: metav1.ConditionTrue,
384 Reason: "",
385 },
386 }
387 GatewayListenersMustHaveConditions(t, c, timeoutConfig, gw.NamespacedName, requiredListenerConditions)
388
389 return gwAddr
390 }
391
392
393
394 func WaitForGatewayAddress(t *testing.T, client client.Client, timeoutConfig config.TimeoutConfig, gwName types.NamespacedName) (string, error) {
395 t.Helper()
396
397 var ipAddr, port string
398 waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, timeoutConfig.GatewayMustHaveAddress, true, func(ctx context.Context) (bool, error) {
399 gw := &gatewayv1.Gateway{}
400 err := client.Get(ctx, gwName, gw)
401 if err != nil {
402 t.Logf("error fetching Gateway: %v", err)
403 return false, fmt.Errorf("error fetching Gateway: %w", err)
404 }
405
406 if err := ConditionsHaveLatestObservedGeneration(gw, gw.Status.Conditions); err != nil {
407 t.Log("Gateway", err)
408 return false, nil
409 }
410
411 port = strconv.FormatInt(int64(gw.Spec.Listeners[0].Port), 10)
412
413
414 for _, address := range gw.Status.Addresses {
415 if address.Type != nil && *address.Type == gatewayv1.IPAddressType {
416 ipAddr = address.Value
417 return true, nil
418 }
419 }
420
421 return false, nil
422 })
423 require.NoErrorf(t, waitErr, "error waiting for Gateway to have at least one IP address in status")
424 return net.JoinHostPort(ipAddr, port), waitErr
425 }
426
427
428
429 func GatewayListenersMustHaveConditions(t *testing.T, client client.Client, timeoutConfig config.TimeoutConfig, gwName types.NamespacedName, conditions []metav1.Condition) {
430 t.Helper()
431
432 var wg sync.WaitGroup
433 wg.Add(len(conditions))
434
435 for _, condition := range conditions {
436 go func(condition metav1.Condition) {
437 defer wg.Done()
438
439 waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, timeoutConfig.GatewayListenersMustHaveCondition, true, func(ctx context.Context) (bool, error) {
440 var gw gatewayv1.Gateway
441 if err := client.Get(ctx, gwName, &gw); err != nil {
442 return false, fmt.Errorf("error fetching Gateway: %w", err)
443 }
444
445 for _, listener := range gw.Status.Listeners {
446 if !findConditionInList(t, listener.Conditions, condition.Type, string(condition.Status), condition.Reason) {
447 return false, nil
448 }
449 }
450
451 return true, nil
452 })
453
454 require.NoErrorf(t, waitErr, "error waiting for Gateway status to have the %s condition set to %s on all listeners",
455 condition.Type, condition.Status)
456 }(condition)
457 }
458
459 wg.Wait()
460 }
461
462
463
464 func GatewayMustHaveZeroRoutes(t *testing.T, client client.Client, timeoutConfig config.TimeoutConfig, gwName types.NamespacedName) {
465 var gotStatus *gatewayv1.GatewayStatus
466
467 waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, timeoutConfig.GatewayStatusMustHaveListeners, true, func(ctx context.Context) (bool, error) {
468 gw := &gatewayv1.Gateway{}
469
470 err := client.Get(ctx, gwName, gw)
471 require.NoError(t, err, "error fetching Gateway")
472
473 if err := ConditionsHaveLatestObservedGeneration(gw, gw.Status.Conditions); err != nil {
474 t.Log("Gateway ", err)
475 return false, nil
476 }
477
478
479
480
481 if len(gw.Status.Listeners) == 0 {
482
483 return true, nil
484 }
485 if len(gw.Status.Listeners) == 1 && gw.Status.Listeners[0].AttachedRoutes == 0 {
486
487 return true, nil
488 }
489 gotStatus = &gw.Status
490 return false, nil
491 })
492 if waitErr != nil {
493 t.Errorf("Error waiting for gateway, got Gateway Status %v, want zero listeners or exactly 1 listener with zero routes", gotStatus)
494 }
495 }
496
497
498
499 func HTTPRouteMustHaveNoAcceptedParents(t *testing.T, client client.Client, timeoutConfig config.TimeoutConfig, routeName types.NamespacedName) {
500 t.Helper()
501
502 var actual []gatewayv1.RouteParentStatus
503 emptyChecked := false
504 waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, timeoutConfig.HTTPRouteMustNotHaveParents, true, func(ctx context.Context) (bool, error) {
505 route := &gatewayv1.HTTPRoute{}
506 err := client.Get(ctx, routeName, route)
507 if err != nil {
508 return false, fmt.Errorf("error fetching HTTPRoute: %w", err)
509 }
510
511 actual = route.Status.Parents
512
513 if len(actual) == 0 {
514
515
516 if !emptyChecked {
517 emptyChecked = true
518 return false, nil
519 }
520 return true, nil
521 }
522 if len(actual) > 1 {
523
524 return false, nil
525 }
526
527 for _, parent := range actual {
528 if err := ConditionsHaveLatestObservedGeneration(route, parent.Conditions); err != nil {
529 t.Logf("HTTPRoute(controller=%v,ref=%#v) %v", parent.ControllerName, parent, err)
530 return false, nil
531 }
532 }
533
534 return conditionsMatch(t, []metav1.Condition{{
535 Type: string(gatewayv1.RouteConditionAccepted),
536 Status: "False",
537 }}, actual[0].Conditions), nil
538 })
539 require.NoErrorf(t, waitErr, "error waiting for HTTPRoute to have no accepted parents")
540 }
541
542
543
544
545 func HTTPRouteMustHaveParents(t *testing.T, client client.Client, timeoutConfig config.TimeoutConfig, routeName types.NamespacedName, parents []gatewayv1.RouteParentStatus, namespaceRequired bool) {
546 t.Helper()
547
548 var actual []gatewayv1.RouteParentStatus
549 waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, timeoutConfig.RouteMustHaveParents, true, func(ctx context.Context) (bool, error) {
550 route := &gatewayv1.HTTPRoute{}
551 err := client.Get(ctx, routeName, route)
552 if err != nil {
553 return false, fmt.Errorf("error fetching HTTPRoute: %w", err)
554 }
555
556 for _, parent := range actual {
557 if err := ConditionsHaveLatestObservedGeneration(route, parent.Conditions); err != nil {
558 t.Logf("HTTPRoute(controller=%v,ref=%#v) %v", parent.ControllerName, parent, err)
559 return false, nil
560 }
561 }
562
563 actual = route.Status.Parents
564 return parentsForRouteMatch(t, routeName, parents, actual, namespaceRequired), nil
565 })
566 require.NoErrorf(t, waitErr, "error waiting for HTTPRoute to have parents matching expectations")
567 }
568
569
570
571
572 func TLSRouteMustHaveParents(t *testing.T, client client.Client, timeoutConfig config.TimeoutConfig, routeName types.NamespacedName, parents []v1alpha2.RouteParentStatus, namespaceRequired bool) v1alpha2.TLSRoute {
573 t.Helper()
574
575 var actual []gatewayv1.RouteParentStatus
576 var route v1alpha2.TLSRoute
577
578 waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, timeoutConfig.RouteMustHaveParents, true, func(ctx context.Context) (bool, error) {
579 err := client.Get(ctx, routeName, &route)
580 if err != nil {
581 return false, fmt.Errorf("error fetching TLSRoute: %w", err)
582 }
583 actual = route.Status.Parents
584 match := parentsForRouteMatch(t, routeName, parents, actual, namespaceRequired)
585
586 return match, nil
587 })
588 require.NoErrorf(t, waitErr, "error waiting for TLSRoute to have parents matching expectations")
589
590 return route
591 }
592
593 func parentsForRouteMatch(t *testing.T, routeName types.NamespacedName, expected, actual []gatewayv1.RouteParentStatus, namespaceRequired bool) bool {
594 t.Helper()
595
596 if len(expected) != len(actual) {
597 t.Logf("Route %s/%s expected %d Parents got %d", routeName.Namespace, routeName.Name, len(expected), len(actual))
598 return false
599 }
600
601
602 for i, eParent := range expected {
603 aParent := actual[i]
604 if aParent.ControllerName != eParent.ControllerName {
605 t.Logf("Route %s/%s ControllerName doesn't match", routeName.Namespace, routeName.Name)
606 return false
607 }
608 if !reflect.DeepEqual(aParent.ParentRef.Group, eParent.ParentRef.Group) {
609 t.Logf("Route %s/%s expected ParentReference.Group to be %v, got %v", routeName.Namespace, routeName.Name, eParent.ParentRef.Group, aParent.ParentRef.Group)
610 return false
611 }
612 if !reflect.DeepEqual(aParent.ParentRef.Kind, eParent.ParentRef.Kind) {
613 t.Logf("Route %s/%s expected ParentReference.Kind to be %v, got %v", routeName.Namespace, routeName.Name, eParent.ParentRef.Kind, aParent.ParentRef.Kind)
614 return false
615 }
616 if aParent.ParentRef.Name != eParent.ParentRef.Name {
617 t.Logf("Route %s/%s ParentReference.Name doesn't match", routeName.Namespace, routeName.Name)
618 return false
619 }
620 if !reflect.DeepEqual(aParent.ParentRef.Namespace, eParent.ParentRef.Namespace) {
621 if namespaceRequired || aParent.ParentRef.Namespace != nil {
622 t.Logf("Route %s/%s expected ParentReference.Namespace to be %v, got %v", routeName.Namespace, routeName.Name, eParent.ParentRef.Namespace, aParent.ParentRef.Namespace)
623 return false
624 }
625 }
626 if !conditionsMatch(t, eParent.Conditions, aParent.Conditions) {
627 return false
628 }
629 }
630
631 t.Logf("Route %s/%s Parents matched expectations", routeName.Namespace, routeName.Name)
632 return true
633 }
634
635
636
637
638 func GatewayStatusMustHaveListeners(t *testing.T, client client.Client, timeoutConfig config.TimeoutConfig, gwNN types.NamespacedName, listeners []gatewayv1.ListenerStatus) {
639 t.Helper()
640
641 var actual []gatewayv1.ListenerStatus
642 waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, timeoutConfig.GatewayStatusMustHaveListeners, true, func(ctx context.Context) (bool, error) {
643 gw := &gatewayv1.Gateway{}
644 err := client.Get(ctx, gwNN, gw)
645 if err != nil {
646 return false, fmt.Errorf("error fetching Gateway: %w", err)
647 }
648
649 if err := ConditionsHaveLatestObservedGeneration(gw, gw.Status.Conditions); err != nil {
650 t.Log("Gateway", err)
651 return false, nil
652 }
653
654 actual = gw.Status.Listeners
655 return listenersMatch(t, listeners, actual), nil
656 })
657 require.NoErrorf(t, waitErr, "error waiting for Gateway status to have listeners matching expectations")
658 }
659
660
661
662 func HTTPRouteMustHaveCondition(t *testing.T, client client.Client, timeoutConfig config.TimeoutConfig, routeNN types.NamespacedName, gwNN types.NamespacedName, condition metav1.Condition) {
663 t.Helper()
664
665 waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, timeoutConfig.HTTPRouteMustHaveCondition, true, func(ctx context.Context) (bool, error) {
666 route := &gatewayv1.HTTPRoute{}
667 err := client.Get(ctx, routeNN, route)
668 if err != nil {
669 return false, fmt.Errorf("error fetching HTTPRoute: %w", err)
670 }
671
672 parents := route.Status.Parents
673 var conditionFound bool
674 for _, parent := range parents {
675 if err := ConditionsHaveLatestObservedGeneration(route, parent.Conditions); err != nil {
676 t.Logf("HTTPRoute(parentRef=%v) %v", parentRefToString(parent.ParentRef), err)
677 return false, nil
678 }
679
680 if parent.ParentRef.Name == gatewayv1.ObjectName(gwNN.Name) && (parent.ParentRef.Namespace == nil || string(*parent.ParentRef.Namespace) == gwNN.Namespace) {
681 if findConditionInList(t, parent.Conditions, condition.Type, string(condition.Status), condition.Reason) {
682 conditionFound = true
683 }
684 }
685 }
686
687 return conditionFound, nil
688 })
689
690 require.NoErrorf(t, waitErr, "error waiting for HTTPRoute status to have a Condition matching expectations")
691 }
692
693
694
695 func HTTPRouteMustHaveResolvedRefsConditionsTrue(t *testing.T, client client.Client, timeoutConfig config.TimeoutConfig, routeNN types.NamespacedName, gwNN types.NamespacedName) {
696 HTTPRouteMustHaveCondition(t, client, timeoutConfig, routeNN, gwNN, metav1.Condition{
697 Type: string(gatewayv1.RouteConditionResolvedRefs),
698 Status: metav1.ConditionTrue,
699 Reason: string(gatewayv1.RouteReasonResolvedRefs),
700 })
701 }
702
703 func parentRefToString(p gatewayv1.ParentReference) string {
704 if p.Namespace != nil && *p.Namespace != "" {
705 return fmt.Sprintf("%v/%v", p.Namespace, p.Name)
706 }
707 return string(p.Name)
708 }
709
710
711
712
713
714 func GatewayAndTLSRoutesMustBeAccepted(t *testing.T, c client.Client, timeoutConfig config.TimeoutConfig, controllerName string, gw GatewayRef, routeNNs ...types.NamespacedName) (string, []gatewayv1.Hostname) {
715 t.Helper()
716
717 var hostnames []gatewayv1.Hostname
718
719 gwAddr, err := WaitForGatewayAddress(t, c, timeoutConfig, gw.NamespacedName)
720 require.NoErrorf(t, err, "timed out waiting for Gateway address to be assigned")
721
722 ns := gatewayv1.Namespace(gw.Namespace)
723 kind := gatewayv1.Kind("Gateway")
724
725 for _, routeNN := range routeNNs {
726 namespaceRequired := true
727 if routeNN.Namespace == gw.Namespace {
728 namespaceRequired = false
729 }
730
731 var parents []gatewayv1.RouteParentStatus
732 for _, listener := range gw.listenerNames {
733 parents = append(parents, gatewayv1.RouteParentStatus{
734 ParentRef: gatewayv1.ParentReference{
735 Group: (*gatewayv1.Group)(&gatewayv1.GroupVersion.Group),
736 Kind: &kind,
737 Name: gatewayv1.ObjectName(gw.Name),
738 Namespace: &ns,
739 SectionName: listener,
740 },
741 ControllerName: gatewayv1.GatewayController(controllerName),
742 Conditions: []metav1.Condition{
743 {
744 Type: string(gatewayv1.RouteConditionAccepted),
745 Status: metav1.ConditionTrue,
746 Reason: string(gatewayv1.RouteReasonAccepted),
747 },
748 },
749 })
750 }
751 route := TLSRouteMustHaveParents(t, c, timeoutConfig, routeNN, parents, namespaceRequired)
752 hostnames = route.Spec.Hostnames
753 }
754
755 return gwAddr, hostnames
756 }
757
758
759
760 func TLSRouteMustHaveCondition(t *testing.T, client client.Client, timeoutConfig config.TimeoutConfig, routeNN types.NamespacedName, gwNN types.NamespacedName, condition metav1.Condition) {
761 t.Helper()
762
763 waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, timeoutConfig.TLSRouteMustHaveCondition, true, func(ctx context.Context) (bool, error) {
764 route := &v1alpha2.TLSRoute{}
765 err := client.Get(ctx, routeNN, route)
766 if err != nil {
767 return false, fmt.Errorf("error fetching TLSRoute: %w", err)
768 }
769
770 parents := route.Status.Parents
771 var conditionFound bool
772 for _, parent := range parents {
773 if err := ConditionsHaveLatestObservedGeneration(route, parent.Conditions); err != nil {
774 t.Logf("TLSRoute(parentRef=%v) %v", parentRefToString(parent.ParentRef), err)
775 return false, nil
776 }
777
778 if parent.ParentRef.Name == gatewayv1.ObjectName(gwNN.Name) && (parent.ParentRef.Namespace == nil || string(*parent.ParentRef.Namespace) == gwNN.Namespace) {
779 if findConditionInList(t, parent.Conditions, condition.Type, string(condition.Status), condition.Reason) {
780 conditionFound = true
781 }
782 }
783 }
784
785 return conditionFound, nil
786 })
787
788 require.NoErrorf(t, waitErr, "error waiting for TLSRoute status to have a Condition matching expectations")
789 }
790
791
792 func listenersMatch(t *testing.T, expected, actual []gatewayv1.ListenerStatus) bool {
793 t.Helper()
794
795 if len(expected) != len(actual) {
796 t.Logf("Expected %d Gateway status listeners, got %d", len(expected), len(actual))
797 return false
798 }
799
800 for _, eListener := range expected {
801 var aListener *gatewayv1.ListenerStatus
802 for i := range actual {
803 if actual[i].Name == eListener.Name {
804 aListener = &actual[i]
805 break
806 }
807 }
808 if aListener == nil {
809 t.Logf("Expected status for listener %s to be present", eListener.Name)
810 return false
811 }
812
813 if len(eListener.SupportedKinds) == 0 && len(aListener.SupportedKinds) != 0 {
814 t.Logf("Expected list of SupportedKinds was empty, but the actual list for comparison was not: %v",
815 aListener.SupportedKinds)
816 return false
817 }
818
819
820
821 for _, eKind := range eListener.SupportedKinds {
822 found := false
823
824 for _, aKind := range aListener.SupportedKinds {
825 if eKind.Group == nil {
826 eKind.Group = (*gatewayv1.Group)(&gatewayv1.GroupVersion.Group)
827 }
828
829 if aKind.Group == nil {
830 aKind.Group = (*gatewayv1.Group)(&gatewayv1.GroupVersion.Group)
831 }
832
833 if *eKind.Group == *aKind.Group && eKind.Kind == aKind.Kind {
834 found = true
835 break
836 }
837 }
838 if !found {
839 t.Logf("Expected Group:%s Kind:%s to be present in SupportedKinds", *eKind.Group, eKind.Kind)
840 return false
841 }
842 }
843
844 if aListener.AttachedRoutes != eListener.AttachedRoutes {
845 t.Logf("Expected AttachedRoutes to be %v, got %v", eListener.AttachedRoutes, aListener.AttachedRoutes)
846 return false
847 }
848 if !conditionsMatch(t, eListener.Conditions, aListener.Conditions) {
849 t.Logf("Expected Conditions to be %v, got %v", eListener.Conditions, aListener.Conditions)
850 return false
851 }
852 }
853
854 t.Logf("Gateway status listeners matched expectations")
855 return true
856 }
857
858 func conditionsMatch(t *testing.T, expected, actual []metav1.Condition) bool {
859 t.Helper()
860
861 if len(actual) < len(expected) {
862 t.Logf("Expected more conditions to be present")
863 return false
864 }
865 for _, condition := range expected {
866 if !findConditionInList(t, actual, condition.Type, string(condition.Status), condition.Reason) {
867 return false
868 }
869 }
870
871 t.Logf("Conditions matched expectations")
872 return true
873 }
874
875
876
877
878 func findConditionInList(t *testing.T, conditions []metav1.Condition, condName, expectedStatus, expectedReason string) bool {
879 t.Helper()
880
881 for _, cond := range conditions {
882 if cond.Type == condName {
883
884 if expectedStatus == "" || cond.Status == metav1.ConditionStatus(expectedStatus) {
885
886 if expectedReason == "" || cond.Reason == expectedReason {
887 return true
888 }
889 t.Logf("%s condition Reason set to %s, expected %s", condName, cond.Reason, expectedReason)
890 }
891
892 t.Logf("%s condition set to Status %s with Reason %v, expected Status %s", condName, cond.Status, cond.Reason, expectedStatus)
893 }
894 }
895
896 t.Logf("%s was not in conditions list [%v]", condName, conditions)
897 return false
898 }
899
900 func findPodConditionInList(t *testing.T, conditions []v1.PodCondition, condName, condValue string) bool {
901 t.Helper()
902
903 for _, cond := range conditions {
904 if cond.Type == v1.PodConditionType(condName) {
905 if cond.Status == v1.ConditionStatus(condValue) {
906 return true
907 }
908 t.Logf("%s condition set to %s, expected %s", condName, cond.Status, condValue)
909 }
910 }
911
912 t.Logf("%s was not in conditions list", condName)
913 return false
914 }
915
View as plain text