diff --git a/Makefile b/Makefile index 0a0154c07c..e8a5902449 100644 --- a/Makefile +++ b/Makefile @@ -43,6 +43,8 @@ HELM_SCHEMA_VERSION = 0.18.1 PREFIX ?= nginx-gateway-fabric## The name of the NGF image. For example, nginx-gateway-fabric NGINX_PREFIX ?= $(PREFIX)/nginx## The name of the nginx image. For example: nginx-gateway-fabric/nginx NGINX_PLUS_PREFIX ?= $(PREFIX)/nginx-plus## The name of the nginx plus image. For example: nginx-gateway-fabric/nginx-plus +NGINX_SERVICE_TYPE ?= NodePort## The type of the nginx service. Possible values: NodePort, LoadBalancer, ClusterIP +PULL_POLICY ?= Never## The pull policy of the images. Possible values: Always, IfNotPresent, Never TAG ?= $(VERSION:v%=%)## The tag of the image. For example, 1.1.0 TARGET ?= local## The target of the build. Possible values: local and container OUT_DIR ?= build/out## The folder where the binary will be stored @@ -226,13 +228,13 @@ install-ngf-local-build-with-plus: check-for-plus-usage-endpoint build-images-wi .PHONY: helm-install-local helm-install-local: install-gateway-crds ## Helm install NGF on configured kind cluster with local images. To build, load, and install with helm run make install-ngf-local-build. - helm install nginx-gateway $(CHART_DIR) --set nginx.image.repository=$(NGINX_PREFIX) --create-namespace --wait --set nginxGateway.image.pullPolicy=Never --set nginx.service.type=NodePort --set nginxGateway.image.repository=$(PREFIX) --set nginxGateway.image.tag=$(TAG) --set nginx.image.tag=$(TAG) --set nginx.image.pullPolicy=Never --set nginxGateway.gwAPIExperimentalFeatures.enable=$(ENABLE_EXPERIMENTAL) -n nginx-gateway $(HELM_PARAMETERS) + helm install nginx-gateway $(CHART_DIR) --set nginx.image.repository=$(NGINX_PREFIX) --create-namespace --wait --set nginxGateway.image.pullPolicy=$(PULL_POLICY) --set nginx.service.type=$(NGINX_SERVICE_TYPE) --set nginxGateway.image.repository=$(PREFIX) --set nginxGateway.image.tag=$(TAG) --set nginx.image.tag=$(TAG) --set nginx.image.pullPolicy=$(PULL_POLICY) --set nginxGateway.gwAPIExperimentalFeatures.enable=$(ENABLE_EXPERIMENTAL) -n nginx-gateway $(HELM_PARAMETERS) .PHONY: helm-install-local-with-plus helm-install-local-with-plus: check-for-plus-usage-endpoint install-gateway-crds ## Helm install NGF with NGINX Plus on configured kind cluster with local images. To build, load, and install with helm run make install-ngf-local-build-with-plus. kubectl create namespace nginx-gateway || true kubectl -n nginx-gateway create secret generic nplus-license --from-file $(PLUS_LICENSE_FILE) || true - helm install nginx-gateway $(CHART_DIR) --set nginx.image.repository=$(NGINX_PLUS_PREFIX) --wait --set nginxGateway.image.pullPolicy=Never --set nginx.service.type=NodePort --set nginxGateway.image.repository=$(PREFIX) --set nginxGateway.image.tag=$(TAG) --set nginx.image.tag=$(TAG) --set nginx.image.pullPolicy=Never --set nginxGateway.gwAPIExperimentalFeatures.enable=$(ENABLE_EXPERIMENTAL) -n nginx-gateway --set nginx.plus=true --set nginx.usage.endpoint=$(PLUS_USAGE_ENDPOINT) $(HELM_PARAMETERS) + helm install nginx-gateway $(CHART_DIR) --set nginx.image.repository=$(NGINX_PLUS_PREFIX) --wait --set nginxGateway.image.pullPolicy=$(PULL_POLICY) --set nginx.service.type=$(NGINX_SERVICE_TYPE) --set nginxGateway.image.repository=$(PREFIX) --set nginxGateway.image.tag=$(TAG) --set nginx.image.tag=$(TAG) --set nginx.image.pullPolicy=$(PULL_POLICY) --set nginxGateway.gwAPIExperimentalFeatures.enable=$(ENABLE_EXPERIMENTAL) -n nginx-gateway --set nginx.plus=true --set nginx.usage.endpoint=$(PLUS_USAGE_ENDPOINT) $(HELM_PARAMETERS) .PHONY: check-for-plus-usage-endpoint check-for-plus-usage-endpoint: ## Checks that the PLUS_USAGE_ENDPOINT is set in the environment. This env var is required when deploying or testing with N+. diff --git a/docs/developer/release-process.md b/docs/developer/release-process.md index 7cd056c9a3..1a2d25a099 100644 --- a/docs/developer/release-process.md +++ b/docs/developer/release-process.md @@ -44,6 +44,7 @@ To create a new release, follow these steps: 4. Once the release branch pipeline completes, run tests using the `release-X.X-rc` images that are pushed to Github (for example, `release-1.3-rc`). 1. Kick off the [longevity tests](https://github.com/nginx/nginx-gateway-fabric/blob/main/tests/README.md#longevity-testing) for both OSS and Plus. You'll need to create two clusters and VMs for this. Before running, update your `vars.env` file with the proper image tag and prefixes. NGF and nginx images will be available from `ghcr.io`, and nginx plus will be available in GCP (`us-docker.pkg.dev//nginx-gateway-fabric/nginx-plus`). These tests need to run for 4 days before releasing. The results should be committed to the main branch and then cherry-picked to the release branch. 2. Kick off the [NFR workflow](https://github.com/nginx/nginx-gateway-fabric/actions/workflows/nfr.yml) in the browser. For `image_tag`, use `release-X.X-rc`, and for `version`, use the upcoming `X.Y.Z` NGF version. Run the workflow on the new release branch. This will run all of the NFR tests which are automated and open a PR with the results files when it is complete. Review this PR and make any necessary changes before merging. Once merged, be sure to cherry-pick the commit to the main branch as well (the original PR targets the release branch). + 3. Run the IPv6 tests using the `make ipv6-tests` target. This must be run from within the `tests` directory. An example of running this script for release 2.1.0 would look like this: `make ipv6-test TAG=release-2.1-rc` 5. Run the [Release PR](https://github.com/nginx/nginx-gateway-fabric/actions/workflows/release-pr.yml) workflow to update the repo files for the release. Then there are a few manual steps to complete: 1. Update the [README](/README.md) to include information about the release. 2. Update the [changelog](/CHANGELOG.md). There is going to be a new blank section generated by the automation that needs to be adjusted accordingly. diff --git a/tests/Makefile b/tests/Makefile index dcea49c341..ec0fe2edb5 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -118,6 +118,11 @@ start-longevity-test: nfr-test ## Start the longevity test to run for 4 days in stop-longevity-test: export STOP_LONGEVITY=true stop-longevity-test: nfr-test ## Stop the longevity test and collects results +.PHONY: ipv6-tests +ipv6-tests: GOARCH=amd64 +ipv6-tests: ## Example usage: make ipv6-tests TAG=release-X.Y-rc + ./ipv6/run-ipv6-test.sh $(TAG) + .PHONY: .vm-nfr-test .vm-nfr-test: ## Runs the NFR tests on the GCP VM (called by `nfr-test`) diff --git a/tests/ipv6/config/kind-ipv6-only.yaml b/tests/ipv6/config/kind-ipv6-only.yaml new file mode 100644 index 0000000000..ab17d5f307 --- /dev/null +++ b/tests/ipv6/config/kind-ipv6-only.yaml @@ -0,0 +1,8 @@ +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +networking: + ipFamily: ipv6 # Explicitly set the cluster to use IPv6 + apiServerAddress: "::1" + disableDefaultCNI: false # Use Kind's default CNI +nodes: +- role: control-plane diff --git a/tests/ipv6/manifests/gateway.yaml b/tests/ipv6/manifests/gateway.yaml new file mode 100644 index 0000000000..e6507f613b --- /dev/null +++ b/tests/ipv6/manifests/gateway.yaml @@ -0,0 +1,11 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: gateway +spec: + gatewayClassName: nginx + listeners: + - name: http + port: 80 + protocol: HTTP + hostname: "*.example.com" diff --git a/tests/ipv6/manifests/ipv6-test-app.yaml b/tests/ipv6/manifests/ipv6-test-app.yaml new file mode 100644 index 0000000000..6593fabdb2 --- /dev/null +++ b/tests/ipv6/manifests/ipv6-test-app.yaml @@ -0,0 +1,62 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-app-ipv6 + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + app: test-app-ipv6 + template: + metadata: + labels: + app: test-app-ipv6 + spec: + containers: + - name: nginx + image: nginxdemos/nginx-hello:plain-text + ports: + - containerPort: 80 + resources: + limits: + cpu: "100m" + memory: "128Mi" + requests: + cpu: "50m" + memory: "64Mi" +--- +apiVersion: v1 +kind: Service +metadata: + name: test-app-ipv6-service + namespace: default +spec: + selector: + app: test-app-ipv6 + ports: + - port: 80 + targetPort: 80 + ipFamilies: [IPv6] + ipFamilyPolicy: SingleStack +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: test-route-ipv6 + namespace: default +spec: + parentRefs: + - name: gateway + sectionName: http + namespace: default + hostnames: + - "ipv6-test.example.com" + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: test-app-ipv6-service + port: 80 diff --git a/tests/ipv6/manifests/ipv6-test-client.yaml b/tests/ipv6/manifests/ipv6-test-client.yaml new file mode 100644 index 0000000000..fffecbf840 --- /dev/null +++ b/tests/ipv6/manifests/ipv6-test-client.yaml @@ -0,0 +1,33 @@ +apiVersion: v1 +kind: Pod +metadata: + name: ipv6-test-client + namespace: default + labels: + app: ipv6-test-client +spec: + restartPolicy: Never + containers: + - name: test-client + image: curlimages/curl:8.16.0 + imagePullPolicy: IfNotPresent + command: ["sleep", "3600"] # Keep pod alive for exec commands + resources: + limits: + cpu: "100m" + memory: "128Mi" + requests: + cpu: "50m" + memory: "64Mi" + securityContext: + allowPrivilegeEscalation: false + runAsNonRoot: true + runAsUser: 65534 + capabilities: + drop: + - ALL + dnsConfig: + options: + - name: single-request-reopen + - name: ndots + value: "2" diff --git a/tests/ipv6/run-ipv6-test.sh b/tests/ipv6/run-ipv6-test.sh new file mode 100755 index 0000000000..b558f528a0 --- /dev/null +++ b/tests/ipv6/run-ipv6-test.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash + +set -e # Exit immediately if a command exits with a non-zero status + +SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +REPO_DIR=$(dirname $(dirname "$SCRIPT_DIR")) + +TAG=$1 + +if [[ -z $TAG ]]; then + echo "Usage: $0 [RELEASE_NAME] [NAMESPACE] [CLUSTER_NAME]" + echo "Error: TAG is a required parameter. Example usage: $(make ipv6-test TAG=release-X.Y-rc)" + exit 1 +fi + +RELEASE_NAME=${2:-nginx-gateway} +NAMESPACE=${3:-nginx-gateway} +CLUSTER_NAME=${4:-ipv6-only-${TAG}} +RELEASE_REPO=ghcr.io/nginx/nginx-gateway-fabric + +cleanup() { + echo "Cleaning up resources..." + kind delete cluster --name ${CLUSTER_NAME} || true +} + +trap cleanup EXIT + +kind create cluster --name ${CLUSTER_NAME} --config ipv6/config/kind-ipv6-only.yaml + +echo "== Installing NGINX Gateway Fabric..." +echo "== Using NGF from ${RELEASE_REPO}:${TAG}..." +echo "== Using NGINX from ${RELEASE_REPO}/nginx:${TAG}..." + +HELM_PARAMETERS="--set nginx.config.ipFamily=ipv6" +make helm-install-local HELM_PARAMETERS="${HELM_PARAMETERS}" \ + PREFIX="${RELEASE_REPO}" \ + TAG="${TAG}" \ + SELF_DIR="${REPO_DIR}/" \ + NGINX_SERVICE_TYPE="ClusterIP" \ + PULL_POLICY="Always" + +echo "== Deploying Gateway..." +kubectl apply -f ipv6/manifests/gateway.yaml + +kubectl wait --for=condition=accepted --timeout=300s gateway/gateway +POD_NAME=$(kubectl get pods -l app.kubernetes.io/instance=${RELEASE_NAME} -o jsonpath='{.items[0].metadata.name}') +kubectl wait --for=condition=ready --timeout=300s pod/${POD_NAME} + +echo "== Deploying IPv6 test application" +kubectl apply -f ipv6/manifests/ipv6-test-app.yaml + +echo "== Waiting for test applications to be ready..." +kubectl wait --for=condition=available --timeout=300s deployment/test-app-ipv6 + +echo "== Getting NGF service IPv6 address from gateway status" +NGF_IPV6=$(kubectl get gateway -o jsonpath='{.items[0].status.addresses[0].value}') +echo "NGF IPv6 Address: $NGF_IPV6" + +echo "=== Running IPv6-Only Tests ===" + +echo "== Starting IPv6 test client" +kubectl apply -f ipv6/manifests/ipv6-test-client.yaml +kubectl wait --for=condition=ready --timeout=300s pod/ipv6-test-client + +echo "== Test 1: Basic IPv6 connectivity ==" +kubectl exec ipv6-test-client -- curl --version +kubectl exec ipv6-test-client -- nslookup gateway-nginx.default.svc.cluster.local || echo "Test 1: Basic IPv6 connectivity failed" +test1_status=$? + +if [[ $test1_status -eq 0 ]]; then + echo "✅ Test 1: Basic IPv6 connectivity succeeded" +fi + +echo "== Test 2: NGF Service IPv6 connectivity ==" +kubectl exec ipv6-test-client -- curl -6 --connect-timeout 30 --max-time 60 -v \ + -H "Host: ipv6-test.example.com" \ + "http://[${NGF_IPV6}]:80/" || echo "Test 2: NGF Service IPv6 connectivity failed" +test2_status=$? + +if [[ $test2_status -eq 0 ]]; then + echo "✅ Test 2: NGF Service IPv6 connectivity succeeded" +fi + +echo "== Test 3: Service DNS IPv6 connectivity ==" +kubectl exec ipv6-test-client -- curl -6 --connect-timeout 30 --max-time 60 -v \ + -H "Host: ipv6-test.example.com" \ + "http://gateway-nginx.default.svc.cluster.local:80/" || echo "Test 3: Service DNS IPv6 connectivity failed" +test3_status=$? + +if [[ $test3_status -eq 0 ]]; then + echo "✅ Test 3: Service DNS IPv6 connectivity succeeded" +fi + +echo "=== Displaying IPv6-Only Configuration ===" +echo "NGF Pod IPv6 addresses:" +kubectl get pods -n nginx-gateway -o wide || true +echo "NGF Service configuration:" +kubectl get service ${HELM_RELEASE_NAME}-nginx-gateway-fabric -n nginx-gateway -o yaml || true + +if [[ $test1_status -eq 0 && $test2_status -eq 0 && $test3_status -eq 0 ]]; then + echo -e "✅ All tests passed!" +else + echo -e "❌ One or more tests failed. Check the output above to help debug any issues." +fi diff --git a/tests/suite/manifests/snippets-filter/invalid-duplicate-sf.yaml b/tests/suite/manifests/snippets-filter/invalid-duplicate-sf.yaml index ba7e5b6c55..137771ed4e 100644 --- a/tests/suite/manifests/snippets-filter/invalid-duplicate-sf.yaml +++ b/tests/suite/manifests/snippets-filter/invalid-duplicate-sf.yaml @@ -19,21 +19,21 @@ metadata: name: tea spec: parentRefs: - - name: gateway - sectionName: http + - name: gateway + sectionName: http hostnames: - - "cafe.example.com" + - "cafe.example.com" rules: - - matches: - - path: - type: Exact - value: /tea - filters: - - type: ExtensionRef - extensionRef: - group: gateway.nginx.org - kind: SnippetsFilter - name: duplicate-directive - backendRefs: - - name: tea - port: 80 + - matches: + - path: + type: Exact + value: /tea + filters: + - type: ExtensionRef + extensionRef: + group: gateway.nginx.org + kind: SnippetsFilter + name: duplicate-directive + backendRefs: + - name: tea + port: 80