Skip to content

Commit 56d5b04

Browse files
committed
chore: Refactor HTTPRoute and HTTPRoutePolicy tests for better reliability
Replace sleep-based waits with Eventually polling for condition checks, introduce helper functions for resource creation, and update test cases to use new assertion methods. This improves test stability and readability while maintaining the original test coverage.
1 parent 806c514 commit 56d5b04

File tree

4 files changed

+396
-329
lines changed

4 files changed

+396
-329
lines changed

test/e2e/framework/assertion.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package framework
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"slices"
8+
"strings"
9+
"time"
10+
11+
"github.com/gruntwork-io/terratest/modules/testing"
12+
"github.com/pkg/errors"
13+
"github.com/stretchr/testify/require"
14+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15+
"k8s.io/apimachinery/pkg/types"
16+
"k8s.io/apimachinery/pkg/util/wait"
17+
"sigs.k8s.io/controller-runtime/pkg/client"
18+
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
19+
"sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
20+
21+
"github.com/apache/apisix-ingress-controller/api/v1alpha1"
22+
)
23+
24+
func GatewayClassMustHaveCondition(t testing.TestingT, cli client.Client, timeout time.Duration, gcNN types.NamespacedName, condition metav1.Condition) {
25+
err := PollUntilGatewayClassMustHaveStatus(cli, timeout, gcNN, func(gc gatewayv1.GatewayClass) bool {
26+
if err := kubernetes.ConditionsHaveLatestObservedGeneration(&gc, gc.Status.Conditions); err != nil {
27+
log.Printf("GatewayClass %s %v", gcNN, err)
28+
return false
29+
}
30+
if findConditionInList(gc.Status.Conditions, condition) {
31+
return true
32+
}
33+
log.Printf("NOT FOUND condition %v in %v", condition, gc.Status.Conditions)
34+
return false
35+
})
36+
require.NoError(t, err, "waiting for GatewayClass to have condition %+v", condition)
37+
}
38+
39+
func PollUntilGatewayClassMustHaveStatus(cli client.Client, timeout time.Duration, gcNN types.NamespacedName, f func(gc gatewayv1.GatewayClass) bool) error {
40+
if err := gatewayv1.Install(cli.Scheme()); err != nil {
41+
return err
42+
}
43+
return wait.PollUntilContextTimeout(context.Background(), time.Second, timeout, true, func(ctx context.Context) (done bool, err error) {
44+
var gc gatewayv1.GatewayClass
45+
if err := cli.Get(ctx, gcNN, &gc); err != nil {
46+
return false, errors.Wrapf(err, "failed to get GatewayClass %s", gcNN)
47+
}
48+
return f(gc), nil
49+
})
50+
}
51+
52+
func GatewayMustHaveCondition(t testing.TestingT, cli client.Client, timeout time.Duration, gwNN types.NamespacedName, condition metav1.Condition) {
53+
err := PollUntilGatewayHaveStatus(cli, timeout, gwNN, func(gw gatewayv1.Gateway) bool {
54+
if err := kubernetes.ConditionsHaveLatestObservedGeneration(&gw, gw.Status.Conditions); err != nil {
55+
log.Printf("Gateway %s %v", gwNN, err)
56+
return false
57+
}
58+
if findConditionInList(gw.Status.Conditions, condition) {
59+
log.Printf("found condition %v in list [%v]", condition, gw.Status.Conditions)
60+
return true
61+
} else {
62+
log.Printf("not found condition %v in list [%v]", condition, gw.Status.Conditions)
63+
return false
64+
}
65+
})
66+
require.NoError(t, err, "waiting for Gateway to have condition %+v", condition)
67+
}
68+
69+
func PollUntilGatewayHaveStatus(cli client.Client, timeout time.Duration, gwNN types.NamespacedName, f func(gateway gatewayv1.Gateway) bool) error {
70+
if err := gatewayv1.Install(cli.Scheme()); err != nil {
71+
return err
72+
}
73+
return wait.PollUntilContextTimeout(context.Background(), time.Second, timeout, true, func(ctx context.Context) (done bool, err error) {
74+
var gw gatewayv1.Gateway
75+
if err := cli.Get(ctx, gwNN, &gw); err != nil {
76+
return false, errors.Wrapf(err, "failed to get Gateway %s", gwNN)
77+
}
78+
return f(gw), nil
79+
})
80+
}
81+
82+
func HTTPRouteMustHaveCondition(t testing.TestingT, cli client.Client, timeout time.Duration, refNN, hrNN types.NamespacedName, condition metav1.Condition) {
83+
err := PollUntilHTTPRouteHaveStatus(cli, timeout, hrNN, func(hr gatewayv1.HTTPRoute) bool {
84+
for _, parent := range hr.Status.Parents {
85+
if err := kubernetes.ConditionsHaveLatestObservedGeneration(&hr, parent.Conditions); err != nil {
86+
log.Printf("HTTPRoute %s (parentRef=%v) %v", hrNN, parentRefToString(parent.ParentRef), err)
87+
return false
88+
}
89+
if (refNN.Name == "" || parent.ParentRef.Name == gatewayv1.ObjectName(refNN.Name)) &&
90+
(refNN.Namespace == "" || (parent.ParentRef.Namespace != nil && string(*parent.ParentRef.Namespace) == refNN.Namespace)) {
91+
if findConditionInList(parent.Conditions, condition) {
92+
log.Printf("found condition %v in list [%v] for %s reference %s", condition, parent.Conditions, hrNN, refNN)
93+
return true
94+
} else {
95+
log.Printf("found condition %v in list [%v] for %s reference %s", condition, parent.Conditions, hrNN, refNN)
96+
}
97+
}
98+
}
99+
return false
100+
})
101+
require.NoError(t, err, "error waiting for HTTPRoute status to have a Condition matching %+v", condition)
102+
}
103+
104+
func PollUntilHTTPRouteHaveStatus(cli client.Client, timeout time.Duration, hrNN types.NamespacedName, f func(route gatewayv1.HTTPRoute) bool) error {
105+
if err := gatewayv1.Install(cli.Scheme()); err != nil {
106+
return err
107+
}
108+
return wait.PollUntilContextTimeout(context.Background(), time.Second, timeout, true, func(ctx context.Context) (done bool, err error) {
109+
var httpRoute gatewayv1.HTTPRoute
110+
if err := cli.Get(ctx, hrNN, &httpRoute); err != nil {
111+
return false, errors.Wrapf(err, "failed to get HTTPRoute %s", hrNN)
112+
}
113+
return f(httpRoute), nil
114+
})
115+
}
116+
117+
func HTTPRoutePolicyMustHaveCondition(t testing.TestingT, client client.Client, timeout time.Duration, refNN, hrpNN types.NamespacedName,
118+
condition metav1.Condition) {
119+
err := PollUntilHTTPRoutePolicyHaveStatus(client, timeout, hrpNN, func(httpRoutePolicy v1alpha1.HTTPRoutePolicy, status v1alpha1.PolicyStatus) bool {
120+
for _, ancestor := range status.Ancestors {
121+
if err := kubernetes.ConditionsHaveLatestObservedGeneration(&httpRoutePolicy, ancestor.Conditions); err != nil {
122+
log.Printf("HTTPRoutePolicy %s (parentRef=%v) %v", hrpNN, parentRefToString(ancestor.AncestorRef), err)
123+
return false
124+
}
125+
126+
if ancestor.AncestorRef.Name == gatewayv1.ObjectName(refNN.Name) &&
127+
(refNN.Namespace == "" || (ancestor.AncestorRef.Namespace != nil && string(*ancestor.AncestorRef.Namespace) == refNN.Namespace)) {
128+
if findConditionInList(ancestor.Conditions, condition) {
129+
log.Printf("found condition %v in list [%v] for %s reference %s", condition, ancestor.Conditions, hrpNN, refNN)
130+
return true
131+
} else {
132+
log.Printf("not found condition %v in list [%v] for %s reference %s", condition, ancestor.Conditions, hrpNN, refNN)
133+
}
134+
}
135+
}
136+
return false
137+
})
138+
139+
require.NoError(t, err, "error waiting for HTTPRoutePolicy status to have a Condition matching %+v", condition)
140+
}
141+
142+
func PollUntilHTTPRoutePolicyHaveStatus(client client.Client, timeout time.Duration, hrpNN types.NamespacedName,
143+
f func(httpRoutePolicy v1alpha1.HTTPRoutePolicy, status v1alpha1.PolicyStatus) bool) error {
144+
_ = v1alpha1.AddToScheme(client.Scheme())
145+
return wait.PollUntilContextTimeout(context.Background(), time.Second, timeout, true, func(ctx context.Context) (done bool, err error) {
146+
var httpRoutePolicy v1alpha1.HTTPRoutePolicy
147+
if err = client.Get(ctx, hrpNN, &httpRoutePolicy); err != nil {
148+
return false, errors.Wrapf(err, "error fetching HTTPRoutePolicy %s", hrpNN)
149+
}
150+
return f(httpRoutePolicy, httpRoutePolicy.Status), nil
151+
})
152+
}
153+
154+
func parentRefToString(p gatewayv1.ParentReference) string {
155+
if p.Namespace != nil && *p.Namespace != "" {
156+
return fmt.Sprintf("%v/%v", p.Namespace, p.Name)
157+
}
158+
return string(p.Name)
159+
}
160+
161+
func findConditionInList(conditions []metav1.Condition, expected metav1.Condition) bool {
162+
return slices.ContainsFunc(conditions, func(item metav1.Condition) bool {
163+
// an empty Status string means "Match any status".
164+
// an empty Reason string means "Match any reason".
165+
if expected.Type == item.Type &&
166+
(expected.Status == "" || expected.Status == item.Status) &&
167+
(expected.Reason == "" || expected.Reason == item.Reason) &&
168+
expected.Message != "" && !strings.Contains(item.Message, expected.Message) {
169+
log.Printf("condition message not match, item.Message: %s, expected.Message: %s", item.Message, expected.Message)
170+
}
171+
return expected.Type == item.Type &&
172+
(expected.Status == "" || expected.Status == item.Status) &&
173+
(expected.Reason == "" || expected.Reason == item.Reason) &&
174+
(expected.Message == "" || strings.Contains(item.Message, expected.Message))
175+
})
176+
}

test/e2e/framework/utils.go

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -20,32 +20,20 @@ import (
2020
"encoding/json"
2121
"fmt"
2222
"io"
23-
"log"
2423
"net/http"
2524
"net/url"
26-
"slices"
2725
"strings"
2826
"sync"
2927
"time"
3028

3129
"github.com/gavv/httpexpect/v2"
32-
"github.com/gruntwork-io/terratest/modules/testing"
3330
"github.com/onsi/gomega"
34-
"github.com/pkg/errors"
35-
"github.com/stretchr/testify/require"
3631
"golang.org/x/net/html"
3732
corev1 "k8s.io/api/core/v1"
3833
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
39-
"k8s.io/apimachinery/pkg/types"
40-
"k8s.io/apimachinery/pkg/util/wait"
4134
"k8s.io/client-go/kubernetes/scheme"
4235
"k8s.io/client-go/tools/remotecommand"
4336
"k8s.io/utils/ptr"
44-
"sigs.k8s.io/controller-runtime/pkg/client"
45-
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
46-
"sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
47-
48-
"github.com/apache/apisix-ingress-controller/api/v1alpha1"
4937
)
5038

5139
func (f *Framework) NewExpectResponse(httpBody any) *httpexpect.Response {
@@ -381,43 +369,6 @@ func CreateTestZipFile(sourceCode, metadata string) ([]byte, error) {
381369
return zipBuffer.Bytes(), nil
382370
}
383371

384-
func HTTPRoutePolicyMustHaveCondition(t testing.TestingT, client client.Client, timeout time.Duration, refNN, hrpNN types.NamespacedName,
385-
condition metav1.Condition) {
386-
err := EventuallyHTTPRoutePolicyHaveStatus(client, timeout, hrpNN, func(httpRoutePolicy v1alpha1.HTTPRoutePolicy, status v1alpha1.PolicyStatus) bool {
387-
for _, ancestor := range status.Ancestors {
388-
if err := kubernetes.ConditionsHaveLatestObservedGeneration(&httpRoutePolicy, ancestor.Conditions); err != nil {
389-
log.Printf("HTTPRoutePolicy %s (parentRef=%v) %v", hrpNN, parentRefToString(ancestor.AncestorRef), err)
390-
return false
391-
}
392-
393-
if ancestor.AncestorRef.Name == gatewayv1.ObjectName(refNN.Name) &&
394-
(ancestor.AncestorRef.Namespace == nil || refNN.Namespace == "" || string(*ancestor.AncestorRef.Namespace) == refNN.Namespace) {
395-
if findConditionInList(ancestor.Conditions, condition) {
396-
log.Printf("found condition %v in list [%v] for %s reference %s", condition, ancestor.Conditions, hrpNN, refNN)
397-
return true
398-
} else {
399-
log.Printf("not found condition %v in list [%v] for %s reference %s", condition, ancestor.Conditions, hrpNN, refNN)
400-
}
401-
}
402-
}
403-
return false
404-
})
405-
406-
require.NoError(t, err, "error waiting for HTTPRoutePolicy status to have a Condition matching expectations")
407-
}
408-
409-
func EventuallyHTTPRoutePolicyHaveStatus(client client.Client, timeout time.Duration, hrpNN types.NamespacedName,
410-
f func(httpRoutePolicy v1alpha1.HTTPRoutePolicy, status v1alpha1.PolicyStatus) bool) error {
411-
_ = v1alpha1.AddToScheme(client.Scheme())
412-
return wait.PollUntilContextTimeout(context.Background(), time.Second, timeout, true, func(ctx context.Context) (done bool, err error) {
413-
var httpRoutePolicy v1alpha1.HTTPRoutePolicy
414-
if err = client.Get(ctx, hrpNN, &httpRoutePolicy); err != nil {
415-
return false, errors.Errorf("error fetching HTTPRoutePolicy %v: %v", hrpNN, err)
416-
}
417-
return f(httpRoutePolicy, httpRoutePolicy.Status), nil
418-
})
419-
}
420-
421372
func addFileToZip(zipWriter *zip.Writer, fileName, fileContent string) error {
422373
file, err := zipWriter.Create(fileName)
423374
if err != nil {
@@ -427,18 +378,3 @@ func addFileToZip(zipWriter *zip.Writer, fileName, fileContent string) error {
427378
_, err = file.Write([]byte(fileContent))
428379
return err
429380
}
430-
431-
func parentRefToString(p gatewayv1.ParentReference) string {
432-
if p.Namespace != nil && *p.Namespace != "" {
433-
return fmt.Sprintf("%v/%v", p.Namespace, p.Name)
434-
}
435-
return string(p.Name)
436-
}
437-
438-
func findConditionInList(conditions []metav1.Condition, expected metav1.Condition) bool {
439-
return slices.ContainsFunc(conditions, func(item metav1.Condition) bool {
440-
// an empty Status string means "Match any status".
441-
// an empty Reason string means "Match any reason".
442-
return expected.Type == item.Type && (expected.Status == "" || expected.Status == item.Status) && (expected.Reason == "" || expected.Reason == item.Reason)
443-
})
444-
}

0 commit comments

Comments
 (0)