Skip to content

Commit a1d46ca

Browse files
committed
add functional tests
1 parent b40d70f commit a1d46ca

11 files changed

+627
-11
lines changed

tests/framework/prometheus.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,7 @@ func CreateEndTimeFinder(
566566
// CreateResponseChecker returns a function that checks if there is a successful response from a url.
567567
func CreateResponseChecker(url, address string, requestTimeout time.Duration) func() error {
568568
return func() error {
569-
status, _, err := Get(url, address, requestTimeout)
569+
status, _, err := Get(url, address, requestTimeout, nil, nil)
570570
if err != nil {
571571
return fmt.Errorf("bad response: %w", err)
572572
}

tests/framework/request.go

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@ import (
1616
// Get sends a GET request to the specified url.
1717
// It resolves to the specified address instead of using DNS.
1818
// The status and body of the response is returned, or an error.
19-
func Get(url, address string, timeout time.Duration) (int, string, error) {
20-
resp, err := makeRequest(http.MethodGet, url, address, nil, timeout)
19+
func Get(
20+
url, address string,
21+
timeout time.Duration,
22+
headers, queryParams map[string]string,
23+
) (int, string, error) {
24+
resp, err := makeRequest(http.MethodGet, url, address, nil, timeout, headers, queryParams)
2125
if err != nil {
2226
return 0, "", err
2327
}
@@ -35,11 +39,21 @@ func Get(url, address string, timeout time.Duration) (int, string, error) {
3539

3640
// Post sends a POST request to the specified url with the body as the payload.
3741
// It resolves to the specified address instead of using DNS.
38-
func Post(url, address string, body io.Reader, timeout time.Duration) (*http.Response, error) {
39-
return makeRequest(http.MethodPost, url, address, body, timeout)
42+
func Post(
43+
url, address string,
44+
body io.Reader,
45+
timeout time.Duration,
46+
headers, queryParams map[string]string,
47+
) (*http.Response, error) {
48+
return makeRequest(http.MethodPost, url, address, body, timeout, headers, queryParams)
4049
}
4150

42-
func makeRequest(method, url, address string, body io.Reader, timeout time.Duration) (*http.Response, error) {
51+
func makeRequest(
52+
method, url, address string,
53+
body io.Reader,
54+
timeout time.Duration,
55+
headers, queryParams map[string]string,
56+
) (*http.Response, error) {
4357
dialer := &net.Dialer{}
4458

4559
transport, ok := http.DefaultTransport.(*http.Transport)
@@ -65,6 +79,18 @@ func makeRequest(method, url, address string, body io.Reader, timeout time.Durat
6579
return nil, err
6680
}
6781

82+
for key, value := range headers {
83+
req.Header.Add(key, value)
84+
}
85+
86+
if queryParams != nil {
87+
q := req.URL.Query()
88+
for key, value := range queryParams {
89+
q.Add(key, value)
90+
}
91+
req.URL.RawQuery = q.Encode()
92+
}
93+
6894
var resp *http.Response
6995
if strings.HasPrefix(url, "https") {
7096
transport, ok := http.DefaultTransport.(*http.Transport)

tests/suite/advanced_routing_test.go

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"net/http"
8+
"regexp"
9+
"strings"
10+
"time"
11+
12+
. "github.com/onsi/ginkgo/v2"
13+
. "github.com/onsi/gomega"
14+
core "k8s.io/api/core/v1"
15+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
"k8s.io/apimachinery/pkg/types"
17+
"k8s.io/apimachinery/pkg/util/wait"
18+
"sigs.k8s.io/controller-runtime/pkg/client"
19+
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
20+
21+
"github.com/nginx/nginx-gateway-fabric/tests/framework"
22+
)
23+
24+
var _ = Describe("AdvancedRouting", Ordered, Label("functional", "routing"), func() {
25+
var (
26+
files = []string{
27+
"advanced-routing/cafe.yaml",
28+
"advanced-routing/gateway.yaml",
29+
"advanced-routing/grpc-backends.yaml",
30+
"advanced-routing/routes.yaml",
31+
}
32+
33+
namespace = "routing"
34+
)
35+
36+
BeforeAll(func() {
37+
ns := &core.Namespace{
38+
ObjectMeta: metav1.ObjectMeta{
39+
Name: namespace,
40+
},
41+
}
42+
43+
Expect(resourceManager.Apply([]client.Object{ns})).To(Succeed())
44+
Expect(resourceManager.ApplyFromFiles(files, namespace)).To(Succeed())
45+
Expect(resourceManager.WaitForAppsToBeReady(namespace)).To(Succeed())
46+
})
47+
48+
AfterAll(func() {
49+
Expect(resourceManager.DeleteFromFiles(files, namespace)).To(Succeed())
50+
Expect(resourceManager.DeleteNamespace(namespace)).To(Succeed())
51+
})
52+
53+
When("valid advanced routing settings are configured for Routes", func() {
54+
var baseURL string
55+
BeforeAll(func() {
56+
port := 80
57+
if portFwdPort != 0 {
58+
port = portFwdPort
59+
}
60+
61+
baseURL = fmt.Sprintf("http://cafe.example.com:%d", port)
62+
})
63+
64+
Specify("routes have the Accepted/True/Accepted condition", func() {
65+
httpRoute := "http-header-and-query-matching"
66+
grpcRoute := "grpc-header-matching"
67+
68+
nsname := types.NamespacedName{Name: httpRoute, Namespace: namespace}
69+
err := waitForHTTPRouteToBeAccepted(nsname)
70+
Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("%s was not accepted", httpRoute))
71+
72+
nsname = types.NamespacedName{Name: grpcRoute, Namespace: namespace}
73+
err = waitForGRPCRouteToBeAccepted(nsname)
74+
Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("%s was not accepted", httpRoute))
75+
})
76+
77+
DescribeTable("verify working traffic for HTTPRoute",
78+
func(uri string, serverName string, headers map[string]string, queryParams map[string]string) {
79+
url := baseURL + uri
80+
Eventually(
81+
func() error {
82+
return expectRequestToRespondFromExpectedServer(url, address, serverName, headers, queryParams)
83+
}).
84+
WithTimeout(timeoutConfig.ContainerRestartTimeout).
85+
WithPolling(500 * time.Millisecond).
86+
Should(Succeed())
87+
},
88+
Entry("request with no headers or params", "/coffee", "coffee-v1", nil, nil),
89+
Entry("request with Exact match header", "/coffee", "coffee-v2", map[string]string{"version": "v2"}, nil),
90+
Entry("request with Exact match query param", "/coffee", "coffee-v2", nil, map[string]string{"TEST": "v2"}),
91+
Entry(
92+
"request with RegularExpression match header",
93+
"/coffee",
94+
"coffee-v3",
95+
map[string]string{"headerRegex": "header-regex"},
96+
nil,
97+
),
98+
Entry(
99+
"request with RegularExpression match query param",
100+
"/coffee",
101+
"coffee-v3",
102+
nil,
103+
map[string]string{"queryRegex": "query-regex"},
104+
),
105+
Entry(
106+
"request with non-matching regex header",
107+
"/coffee",
108+
"coffee-v1",
109+
map[string]string{"headerRegex": "headerInvalid"},
110+
nil,
111+
),
112+
Entry(
113+
"request with non-matching regex query param",
114+
"/coffee",
115+
"coffee-v1",
116+
nil,
117+
map[string]string{"queryRegex": "queryInvalid"},
118+
),
119+
)
120+
})
121+
})
122+
123+
func expectRequestToRespondFromExpectedServer(
124+
appURL, address, expServerName string,
125+
headers, queryParams map[string]string,
126+
) error {
127+
status, body, err := framework.Get(appURL, address, timeoutConfig.RequestTimeout, headers, queryParams)
128+
if err != nil {
129+
return err
130+
}
131+
132+
if status != http.StatusOK {
133+
return errors.New("http status was not 200")
134+
}
135+
136+
actualServerName, err := extractServerName(body)
137+
if err != nil {
138+
return err
139+
}
140+
141+
fmt.Println("server", actualServerName, expServerName)
142+
if !strings.Contains(actualServerName, expServerName) {
143+
return errors.New("expected response body to contain correct server name")
144+
}
145+
146+
return err
147+
}
148+
149+
func extractServerName(responseBody string) (string, error) {
150+
re := regexp.MustCompile(`Server name:\s*(\S+)`)
151+
matches := re.FindStringSubmatch(responseBody)
152+
if len(matches) < 2 {
153+
return "", errors.New("server name not found")
154+
}
155+
return matches[1], nil
156+
}
157+
158+
func waitForHTTPRouteToBeAccepted(nsname types.NamespacedName) error {
159+
ctx, cancel := context.WithTimeout(context.Background(), timeoutConfig.GetStatusTimeout*2)
160+
defer cancel()
161+
162+
GinkgoWriter.Printf(
163+
"Waiting for HTTPRoute %q to have the condition Accepted/True/Accepted\n",
164+
nsname,
165+
)
166+
167+
return waitForHTTPRouteToHaveExpectedCondition(
168+
ctx,
169+
nsname,
170+
metav1.ConditionTrue,
171+
string(gatewayv1.RouteConditionAccepted),
172+
)
173+
}
174+
175+
func waitForGRPCRouteToBeAccepted(nsname types.NamespacedName) error {
176+
ctx, cancel := context.WithTimeout(context.Background(), timeoutConfig.GetStatusTimeout*2)
177+
defer cancel()
178+
179+
GinkgoWriter.Printf(
180+
"Waiting for GRPCRoute %q to have the condition Accepted/True/Accepted\n",
181+
nsname,
182+
)
183+
184+
return waitForGRPCRouteToHaveExpectedCondition(
185+
ctx,
186+
nsname,
187+
metav1.ConditionTrue,
188+
string(gatewayv1.RouteConditionAccepted),
189+
)
190+
}
191+
192+
func waitForHTTPRouteToHaveExpectedCondition(
193+
ctx context.Context,
194+
nsname types.NamespacedName,
195+
conditionStatus metav1.ConditionStatus,
196+
reason string,
197+
) error {
198+
return wait.PollUntilContextCancel(
199+
ctx,
200+
500*time.Millisecond,
201+
true, /* poll immediately */
202+
func(ctx context.Context) (bool, error) {
203+
var route gatewayv1.HTTPRoute
204+
205+
if err := k8sClient.Get(ctx, nsname, &route); err != nil {
206+
return false, err
207+
}
208+
209+
return verifyConditions(route, nsname, conditionStatus, reason)
210+
},
211+
)
212+
}
213+
214+
func waitForGRPCRouteToHaveExpectedCondition(
215+
ctx context.Context,
216+
nsname types.NamespacedName,
217+
conditionStatus metav1.ConditionStatus,
218+
reason string,
219+
) error {
220+
return wait.PollUntilContextCancel(
221+
ctx,
222+
500*time.Millisecond,
223+
true, /* poll immediately */
224+
func(ctx context.Context) (bool, error) {
225+
var route gatewayv1.GRPCRoute
226+
227+
if err := k8sClient.Get(ctx, nsname, &route); err != nil {
228+
return false, err
229+
}
230+
231+
return verifyConditions(route, nsname, conditionStatus, reason)
232+
},
233+
)
234+
}
235+
236+
func verifyConditions(
237+
route interface{},
238+
nsname types.NamespacedName,
239+
conditionStatus metav1.ConditionStatus,
240+
reason string,
241+
) (bool, error) {
242+
var err error
243+
var parents []gatewayv1.RouteParentStatus
244+
245+
switch r := route.(type) {
246+
case gatewayv1.HTTPRoute:
247+
parents = r.Status.Parents
248+
case gatewayv1.GRPCRoute:
249+
parents = r.Status.Parents
250+
default:
251+
return false, fmt.Errorf("unsupported route type")
252+
}
253+
254+
if len(parents) == 0 {
255+
GinkgoWriter.Printf("Route %q does not have a parent status yet\n", nsname)
256+
257+
return false, nil
258+
}
259+
260+
if len(parents) != 1 {
261+
return false, fmt.Errorf("route has %d parents, expected 1", len(parents))
262+
}
263+
264+
parent := parents[0]
265+
if parent.Conditions == nil {
266+
return false, fmt.Errorf("expected parent conditions to not be nil")
267+
}
268+
269+
cond := parent.Conditions[0]
270+
if cond.Type != string(gatewayv1.RouteConditionAccepted) {
271+
return false, fmt.Errorf("expected condition type to be Accepted, got %s", cond.Type)
272+
}
273+
274+
if cond.Status != conditionStatus {
275+
return false, fmt.Errorf("expected condition status to be True, got %s", cond.Status)
276+
}
277+
278+
if cond.Reason != reason {
279+
return false, fmt.Errorf("expected condition reason to be Accepted, got %s", cond.Reason)
280+
}
281+
282+
return err == nil, err
283+
}

tests/suite/client_settings_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ var _ = Describe("ClientSettingsPolicy", Ordered, Label("functional", "cspolicy"
220220
_, err := rand.Read(payload)
221221
Expect(err).ToNot(HaveOccurred())
222222

223-
resp, err := framework.Post(url, address, bytes.NewReader(payload), timeoutConfig.RequestTimeout)
223+
resp, err := framework.Post(url, address, bytes.NewReader(payload), timeoutConfig.RequestTimeout, nil, nil)
224224
Expect(err).ToNot(HaveOccurred())
225225
Expect(resp).To(HaveHTTPStatus(expStatus))
226226

tests/suite/graceful_recovery_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ func checkForFailingTraffic(teaURL, coffeeURL string) error {
294294
}
295295

296296
func expectRequestToSucceed(appURL, address string, responseBodyMessage string) error {
297-
status, body, err := framework.Get(appURL, address, timeoutConfig.RequestTimeout)
297+
status, body, err := framework.Get(appURL, address, timeoutConfig.RequestTimeout, nil, nil)
298298

299299
if status != http.StatusOK {
300300
return errors.New("http status was not 200")
@@ -308,7 +308,7 @@ func expectRequestToSucceed(appURL, address string, responseBodyMessage string)
308308
}
309309

310310
func expectRequestToFail(appURL, address string) error {
311-
status, body, err := framework.Get(appURL, address, timeoutConfig.RequestTimeout)
311+
status, body, err := framework.Get(appURL, address, timeoutConfig.RequestTimeout, nil, nil)
312312
if status != 0 {
313313
return errors.New("expected http status to be 0")
314314
}

0 commit comments

Comments
 (0)