Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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.34.0) / Run Tests

It 12/08/25 19:06:07.934

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/08/25 19:07:34.464

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/08/25 19:09:09.846

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/08/25 19:06:58.134
" 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