Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion tests/framework/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ func WithLoggingDisabled() Option {
}

func LogOptions(opts ...Option) *Options {
options := &Options{logEnabled: true}
options := &Options{
logEnabled: true,
}
for _, opt := range opts {
opt(options)
}
Expand Down
11 changes: 6 additions & 5 deletions tests/framework/ngf.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ type InstallationConfig struct {

// InstallGatewayAPI installs the specified version of the Gateway API resources.
func InstallGatewayAPI(apiVersion string) ([]byte, error) {
apiPath := fmt.Sprintf("%s/v%s/standard-install.yaml", gwInstallBasePath, apiVersion)
GinkgoWriter.Printf("Installing Gateway API version %q at API path %q\n", apiVersion, apiPath)
apiPath := fmt.Sprintf("%s/v%s/experimental-install.yaml", gwInstallBasePath, apiVersion)
GinkgoWriter.Printf("Installing Gateway API CRDs from experimental channel %q", apiVersion, apiPath)

cmd := exec.CommandContext(
context.Background(),
"kubectl", "apply", "-f", apiPath,
"kubectl", "apply", "--server-side", "--force-conflicts", "-f", apiPath,
)
output, err := cmd.CombinedOutput()
if err != nil {
Expand All @@ -59,8 +59,8 @@ func InstallGatewayAPI(apiVersion string) ([]byte, error) {

// UninstallGatewayAPI uninstalls the specified version of the Gateway API resources.
func UninstallGatewayAPI(apiVersion string) ([]byte, error) {
apiPath := fmt.Sprintf("%s/v%s/standard-install.yaml", gwInstallBasePath, apiVersion)
GinkgoWriter.Printf("Uninstalling Gateway API version %q at API path %q\n", apiVersion, apiPath)
apiPath := fmt.Sprintf("%s/v%s/experimental-install.yaml", gwInstallBasePath, apiVersion)
GinkgoWriter.Printf("Uninstalling Gateway API CRDs from experimental channel for version %q\n", apiVersion)

output, err := exec.CommandContext(context.Background(), "kubectl", "delete", "-f", apiPath).CombinedOutput()
if err != nil && !strings.Contains(string(output), "not found") {
Expand All @@ -84,6 +84,7 @@ func InstallNGF(cfg InstallationConfig, extraArgs ...string) ([]byte, error) {
"--namespace", cfg.Namespace,
"--wait",
"--set", "nginxGateway.snippetsFilters.enable=true",
"--set", "nginxGateway.gwAPIExperimentalFeatures.enable=true",
}
if cfg.ChartVersion != "" {
args = append(args, "--version", cfg.ChartVersion)
Expand Down
12 changes: 9 additions & 3 deletions tests/framework/prometheus.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"log/slog"
"net/http"
"os"
"os/exec"
"time"
Expand Down Expand Up @@ -542,7 +543,12 @@ func CreateResponseChecker(url, address string, requestTimeout time.Duration, op
}

return func() error {
status, _, err := Get(url, address, requestTimeout, nil, nil, opts...)
request := Request{
URL: url,
Address: address,
Timeout: requestTimeout,
}
resp, err := Get(request, opts...)
if err != nil {
badReqErr := fmt.Errorf("bad response: %w", err)
if options.logEnabled {
Expand All @@ -552,8 +558,8 @@ func CreateResponseChecker(url, address string, requestTimeout time.Duration, op
return badReqErr
}

if status != 200 {
statusErr := fmt.Errorf("unexpected status code: %d", status)
if resp.StatusCode != http.StatusOK {
statusErr := fmt.Errorf("unexpected status code: %d", resp.StatusCode)
if options.logEnabled {
GinkgoWriter.Printf("ERROR during creating response checker: %v\n", statusErr)
}
Expand Down
79 changes: 42 additions & 37 deletions tests/framework/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,28 @@ import (
. "github.com/onsi/ginkgo/v2"
)

type Response struct {
Headers http.Header
Body string
StatusCode int
}

type Request struct {
Body io.Reader
Headers map[string]string
QueryParams map[string]string
URL string
Address string
Timeout time.Duration
}

// Get sends a GET request to the specified url.
// It resolves to the specified address instead of using DNS.
// The status and body of the response is returned, or an error.
func Get(
url, address string,
timeout time.Duration,
headers, queryParams map[string]string,
opts ...Option,
) (int, string, error) {
// It returns the response body, headers, and status code.
func Get(request Request, opts ...Option) (Response, error) {
options := LogOptions(opts...)

resp, err := makeRequest(http.MethodGet, url, address, nil, timeout, headers, queryParams, opts...)
resp, err := makeRequest(http.MethodGet, request, opts...)
if err != nil {
if options.logEnabled {
GinkgoWriter.Printf(
Expand All @@ -35,46 +45,39 @@ func Get(
)
}

return 0, "", err
return Response{StatusCode: 0}, err
}
defer resp.Body.Close()

body := new(bytes.Buffer)
_, err = body.ReadFrom(resp.Body)
if err != nil {
GinkgoWriter.Printf("ERROR in Body content: %v returning body: ''\n", err)
return resp.StatusCode, "", err
return Response{StatusCode: resp.StatusCode}, err
}
if options.logEnabled {
GinkgoWriter.Printf("Successfully received response and parsed body: %s\n", body.String())
}

return resp.StatusCode, body.String(), nil
return Response{
Body: body.String(),
Headers: resp.Header,
StatusCode: resp.StatusCode,
}, nil
}

// Post sends a POST request to the specified url with the body as the payload.
// It resolves to the specified address instead of using DNS.
func Post(
url, address string,
body io.Reader,
timeout time.Duration,
headers, queryParams map[string]string,
) (*http.Response, error) {
response, err := makeRequest(http.MethodPost, url, address, body, timeout, headers, queryParams)
func Post(request Request) (*http.Response, error) {
response, err := makeRequest(http.MethodPost, request)
if err != nil {
GinkgoWriter.Printf("ERROR occurred during getting response, error: %s\n", err)
}

return response, err
}

func makeRequest(
method, url, address string,
body io.Reader,
timeout time.Duration,
headers, queryParams map[string]string,
opts ...Option,
) (*http.Response, error) {
func makeRequest(method string, request Request, opts ...Option) (*http.Response, error) {
dialer := &net.Dialer{}

transport, ok := http.DefaultTransport.(*http.Transport)
Expand All @@ -90,50 +93,52 @@ func makeRequest(
) (net.Conn, error) {
split := strings.Split(addr, ":")
port := split[len(split)-1]
return dialer.DialContext(ctx, network, fmt.Sprintf("%s:%s", address, port))
return dialer.DialContext(ctx, network, fmt.Sprintf("%s:%s", request.Address, port))
}

ctx, cancel := context.WithTimeout(context.Background(), timeout)
ctx, cancel := context.WithTimeout(context.Background(), request.Timeout)
defer cancel()

options := LogOptions(opts...)
if options.logEnabled {
requestDetails := fmt.Sprintf(
"Method: %s, URL: %s, Address: %s, Headers: %v, QueryParams: %v\n",
strings.ToUpper(method),
url,
address,
headers,
queryParams,
request.URL,
request.Address,
request.Headers,
request.QueryParams,
)
GinkgoWriter.Printf("Sending request: %s", requestDetails)
}

req, err := http.NewRequestWithContext(ctx, method, url, body)
req, err := http.NewRequestWithContext(ctx, method, request.URL, request.Body)
if err != nil {
return nil, err
}

for key, value := range headers {
for key, value := range request.Headers {
req.Header.Add(key, value)
}

if queryParams != nil {
if request.QueryParams != nil {
q := req.URL.Query()
for key, value := range queryParams {
for key, value := range request.QueryParams {
q.Add(key, value)
}
req.URL.RawQuery = q.Encode()
}

var resp *http.Response
if strings.HasPrefix(url, "https") {
if strings.HasPrefix(request.URL, "https") {
// similar to how in our examples with https requests we run our curl command
// we turn off verification of the certificate, we do the same here
customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec // for https test traffic
}

client := &http.Client{Transport: customTransport}
client := &http.Client{
Transport: customTransport,
}
resp, err = client.Do(req)
if err != nil {
return nil, err
Expand Down
15 changes: 12 additions & 3 deletions tests/suite/advanced_routing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,19 +120,28 @@ func expectRequestToRespondFromExpectedServer(
headers, queryParams map[string]string,
) error {
GinkgoWriter.Printf("Expecting request to respond from the server %q\n", expServerName)
status, body, err := framework.Get(appURL, address, timeoutConfig.RequestTimeout, headers, queryParams)

request := framework.Request{
URL: appURL,
Address: address,
Timeout: timeoutConfig.RequestTimeout,
Headers: headers,
QueryParams: queryParams,
}

resp, err := framework.Get(request)
if err != nil {
return err
}

if status != http.StatusOK {
if resp.StatusCode != http.StatusOK {
statusErr := errors.New("http status was not 200")
GinkgoWriter.Printf("ERROR: %v\n", statusErr)

return statusErr
}

actualServerName, err := extractServerName(body)
actualServerName, err := extractServerName(resp.Body)
if err != nil {
GinkgoWriter.Printf("ERROR extracting server name from response body: %v\n", err)

Expand Down
8 changes: 7 additions & 1 deletion tests/suite/client_settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,13 @@ var _ = Describe("ClientSettingsPolicy", Ordered, Label("functional", "cspolicy"
_, err := rand.Read(payload)
Expect(err).ToNot(HaveOccurred())

resp, err := framework.Post(url, address, bytes.NewReader(payload), timeoutConfig.RequestTimeout, nil, nil)
request := framework.Request{
URL: url,
Address: address,
Body: bytes.NewReader(payload),
Timeout: timeoutConfig.RequestTimeout,
}
resp, err := framework.Post(request)
Expect(err).ToNot(HaveOccurred())
Expect(resp).To(HaveHTTPStatus(expStatus))

Expand Down
28 changes: 19 additions & 9 deletions tests/suite/graceful_recovery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@

It("recovers when node is restarted abruptly", func() {
if *plusEnabled {
Skip(fmt.Sprintf("Skipping test when using NGINX Plus due to known issue:" +

Check notice on line 550 in tests/suite/graceful_recovery_test.go

View workflow job for this annotation

GitHub Actions / Functional tests (plus, v1.25.16) / Run Tests

It 12/05/25 22:25:02.972

Check notice on line 550 in tests/suite/graceful_recovery_test.go

View workflow job for this annotation

GitHub Actions / Functional tests (plus, v1.34.0) / Run Tests

It 12/05/25 22:25:17.466

Check notice on line 550 in tests/suite/graceful_recovery_test.go

View workflow job for this annotation

GitHub Actions / Functional tests (plus, ubi, v1.25.16) / Run Tests

It 12/05/25 22:25:57.172

Check notice on line 550 in tests/suite/graceful_recovery_test.go

View workflow job for this annotation

GitHub Actions / Functional tests (plus, ubi, v1.34.0) / Run Tests

It 12/05/25 22:24:17.487
" https://github.com/nginx/nginx-gateway-fabric/issues/3248"))
}
runRestartNodeAbruptlyTest(teaURL, coffeeURL, files, &ns)
Expand All @@ -555,14 +555,19 @@
})

func expectRequestToSucceed(appURL, address string, responseBodyMessage string) error {
status, body, err := framework.Get(appURL, address, timeoutConfig.RequestTimeout, nil, nil)
request := framework.Request{
URL: appURL,
Address: address,
Timeout: timeoutConfig.RequestTimeout,
}
resp, err := framework.Get(request)

if status != http.StatusOK {
return fmt.Errorf("http status was not 200, got %d: %w", status, err)
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("http status was not 200, got %d: %w", resp.StatusCode, err)
}

if !strings.Contains(body, responseBodyMessage) {
return fmt.Errorf("expected response body to contain correct body message, got: %s", body)
if !strings.Contains(resp.Body, responseBodyMessage) {
return fmt.Errorf("expected response body to contain correct body message, got: %s", resp.Body)
}

return err
Expand All @@ -577,13 +582,18 @@
// We only want an error returned from this particular function if it does not appear that NGINX has
// stopped serving traffic.
func expectRequestToFail(appURL, address string) error {
status, body, err := framework.Get(appURL, address, timeoutConfig.RequestTimeout, nil, nil)
if status != 0 {
request := framework.Request{
URL: appURL,
Address: address,
Timeout: timeoutConfig.RequestTimeout,
}
resp, err := framework.Get(request)
if resp.StatusCode != 0 {
return errors.New("expected http status to be 0")
}

if body != "" {
return fmt.Errorf("expected response body to be empty, instead received: %s", body)
if resp.Body != "" {
return fmt.Errorf("expected response body to be empty, instead received: %s", resp.Body)
}

if err == nil {
Expand Down
65 changes: 65 additions & 0 deletions tests/suite/manifests/session-persistence/cafe.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: coffee
spec:
replicas: 3
selector:
matchLabels:
app: coffee
template:
metadata:
labels:
app: coffee
spec:
containers:
- name: coffee
image: nginxdemos/nginx-hello:plain-text
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: coffee
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: coffee
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tea
spec:
replicas: 3
selector:
matchLabels:
app: tea
template:
metadata:
labels:
app: tea
spec:
containers:
- name: tea
image: nginxdemos/nginx-hello:plain-text
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: tea
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: tea
Loading
Loading