Skip to content
Merged
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
121 changes: 120 additions & 1 deletion .github/workflows/validate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
name: End Status
if: always()
runs-on: ubuntu-latest
needs: [discover-modules, test-modules, test-scenarios, validate-structure, test-charts, test-merge]
needs: [discover-modules, test-modules, test-scenarios, validate-structure, test-charts, test-merge, test-dns-resources]
permissions:
contents: read
statuses: write
Expand Down Expand Up @@ -320,6 +320,125 @@ jobs:
docker stop merge-test-server > /dev/null 2>&1 || true
docker rm merge-test-server > /dev/null 2>&1 || true

test-dns-resources:
name: Test DNS ${{ matrix.resource }} scripts
runs-on: ubuntu-latest
needs: [test-modules, start-status]
strategy:
matrix:
include:
- resource: ingresses
traffic: ingress
setup_script: ./scripts/setup/ingress.sh
install_script: ./scripts/install/nginx.sh
kind: Ingress
- resource: httproutes
traffic: gateway
setup_script: ./scripts/setup/gateway.sh
install_script: ./scripts/install/istio.sh
kind: HTTPRoute
fail-fast: false
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install dependencies
run: |
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
sudo mv kubectl /usr/local/bin/

curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

- name: Syntax check DNS scripts
run: |
bash -n scripts/setup/dns-ingresses.sh
bash -n scripts/cleanup/dns-ingresses.sh
bash -n scripts/setup/dns-httproutes.sh
bash -n scripts/cleanup/dns-httproutes.sh

- name: Setup Kind cluster
run: |
chmod +x ./modules/kind/install/install.sh ./modules/kind/run/run.sh
./modules/kind/install/install.sh
./modules/kind/run/run.sh

- name: Install ${{ matrix.traffic }} controller
run: |
chmod +x ${{ matrix.install_script }}
${{ matrix.install_script }}

- name: Deploy server chart
run: |
chmod +x ${{ matrix.setup_script }}
${{ matrix.setup_script }}

- name: Run setup/dns-${{ matrix.resource }} (count=5)
run: |
chmod +x scripts/setup/dns-${{ matrix.resource }}.sh
scripts/setup/dns-${{ matrix.resource }}.sh --namespace server --count 5 --domain extdns.telescope.test

- name: Assert 5 ${{ matrix.kind }} resources exist with dns-test=true
run: |
if [ "${{ matrix.resource }}" = "ingresses" ]; then
COUNT=$(kubectl get ingress -n server -l dns-test=true -o name | wc -l)
else
COUNT=$(kubectl get httproute.gateway.networking.k8s.io -n server -l dns-test=true -o name | wc -l)
fi
echo "Found ${COUNT} ${{ matrix.kind }} resources with dns-test=true"
if [ "${COUNT}" -ne 5 ]; then
echo "ERROR: Expected 5, got ${COUNT}"
exit 1
fi

- name: Run cleanup/dns-${{ matrix.resource }}
run: |
chmod +x scripts/cleanup/dns-${{ matrix.resource }}.sh
scripts/cleanup/dns-${{ matrix.resource }}.sh --namespace server

- name: Assert all dns-test resources are gone
run: |
# --wait=false means deletes are async; poll briefly
for i in {1..15}; do
if [ "${{ matrix.resource }}" = "ingresses" ]; then
COUNT=$(kubectl get ingress -n server -l dns-test=true -o name 2>/dev/null | wc -l)
else
COUNT=$(kubectl get httproute.gateway.networking.k8s.io -n server -l dns-test=true -o name 2>/dev/null | wc -l)
fi
[ "${COUNT}" -eq 0 ] && break
echo "Still ${COUNT} resources present, waiting... (attempt $i/15)"
sleep 2
done
if [ "${COUNT}" -ne 0 ]; then
echo "ERROR: Expected 0, got ${COUNT}"
exit 1
fi
echo "✓ All dns-test ${{ matrix.kind }} resources removed"

- name: Negative path - missing service
run: |
if scripts/setup/dns-${{ matrix.resource }}.sh --namespace server --count 1 --domain extdns.telescope.test --service-name nonexistent 2>/dev/null; then
echo "ERROR: Expected non-zero exit for missing service"
exit 1
fi
echo "✓ Correctly failed on missing service"

- name: Negative path - invalid domain
run: |
if scripts/setup/dns-${{ matrix.resource }}.sh --namespace server --count 1 --domain "Bad_Domain!" 2>/dev/null; then
echo "ERROR: Expected non-zero exit for invalid domain"
exit 1
fi
echo "✓ Correctly failed on invalid domain"

- name: Negative path - count=0
run: |
if scripts/setup/dns-${{ matrix.resource }}.sh --namespace server --count 0 --domain extdns.telescope.test 2>/dev/null; then
echo "ERROR: Expected non-zero exit for count=0"
exit 1
fi
echo "✓ Correctly failed on count=0"

validate-structure:
name: Validate Project Structure
runs-on: ubuntu-latest
Expand Down
6 changes: 5 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,11 @@ The server image is `ghcr.io/azure/aks-traffic-ingress-competitive-testing` (a G

### CI (validate.yaml)

The validation workflow runs on PRs to main. The `test-scenarios` job uses a matrix over `traffic: [ingress, gateway]` × `scenario: [basic-rps, restarting-backend-rps]` × `variant: [default]` — each combination gets its own runner and Kind cluster. An additional `scheduling-e2e` variant tests pod placement with node selectors and tolerations on a multi-node Kind cluster. The `test-merge` job validates multi-pod merge by running 4 simultaneous vegeta attacks against a Docker server and verifying the merged output (RPS totals, code histograms, JSON structure). Other jobs: module tests (matrix over discovered modules), chart validation (including scheduling render checks), and project structure validation.
The validation workflow runs on PRs to main. The `test-scenarios` job uses a matrix over `traffic: [ingress, gateway]` × `scenario: [basic-rps, restarting-backend-rps]` × `variant: [default]` — each combination gets its own runner and Kind cluster. An additional `scheduling-e2e` variant tests pod placement with node selectors and tolerations on a multi-node Kind cluster. The `test-merge` job validates multi-pod merge by running 4 simultaneous vegeta attacks against a Docker server and verifying the merged output (RPS totals, code histograms, JSON structure). The `test-dns-resources` job runs a matrix over `resource: [ingresses, httproutes]`, deploys the server chart to namespace `server`, and exercises the setup/cleanup DNS scripts with positive (count=5, asserting created/deleted via `kubectl get -n server`) and negative paths (missing service, invalid domain, count=0). Because the chart deploys into namespace `server`, all DNS script invocations must pass `--namespace server`. Other jobs: module tests (matrix over discovered modules), chart validation (including scheduling render checks), and project structure validation.

### DNS test scripts

`scripts/setup/dns-ingresses.sh`, `scripts/setup/dns-httproutes.sh`, `scripts/cleanup/dns-ingresses.sh`, and `scripts/cleanup/dns-httproutes.sh` bulk-create or delete N `Ingress` / `HTTPRoute` resources (each with a unique `test-{i}.{domain}` hostname, all labeled `dns-test=true`) for external-DNS reconciliation testing in downstream telescope pipelines (app-routing-nginx, app-routing-istio). They are intentionally **not** wired into `master.sh` — that script remains RPS-focused. The httproutes setup script requires an existing parent `Gateway`; the ingresses setup script requires an existing backend `Service`. Defaults: namespace `default`, service/gateway `server`, port `8080`. Cleanup uses label selector `dns-test=true` with `--wait=false --ignore-not-found` and leaves the parent Gateway intact.

## Conventions

Expand Down
12 changes: 12 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ RUN printf '%s\n' \
' echo " scenario/<name> [args...] Run a scenario (e.g. scenario/basic_rps)"' \
' echo " install/<name> [args...] Run an install script (e.g. install/nginx)"' \
' echo " setup/<name> [args...] Run a setup script (e.g. setup/ingress)"' \
' echo " cleanup/<name> [args...] Run a cleanup script (e.g. cleanup/dns-ingresses)"' \
' echo " module/<name>/<action> [args...] Run a module script (e.g. module/vegeta/run)"' \
' echo " merge [args...] Merge vegeta .bin files (modules/vegeta/merge/merge.sh)"' \
' echo " server Start the HTTP server"' \
Expand Down Expand Up @@ -155,6 +156,17 @@ RUN printf '%s\n' \
' ls /app/scripts/setup/*.sh 2>/dev/null | sed "s|/app/scripts/setup/||;s|\.sh||" | sed "s|^| |"' \
' exit 1' \
' fi' \
'elif [[ "$1" == cleanup/* ]]; then' \
' name="${1#cleanup/}"' \
' shift' \
' if [ -f "/app/scripts/cleanup/${name}.sh" ]; then' \
' exec bash "/app/scripts/cleanup/${name}.sh" "$@"' \
' else' \
' echo "ERROR: Unknown cleanup script: ${name}"' \
' echo "Available cleanup scripts:"' \
' ls /app/scripts/cleanup/*.sh 2>/dev/null | sed "s|/app/scripts/cleanup/||;s|\.sh||" | sed "s|^| |"' \
' exit 1' \
' fi' \
'elif [[ "$1" == module/* ]]; then' \
' path="${1#module/}"' \
' module_name="${path%%/*}"' \
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ docker run <image> scenario/basic_rps --ingress-url http://localhost:8080 --rate
docker run <image> install/nginx
docker run <image> setup/ingress --ingress-class nginx --replica-count 3

# Bulk-create dns-test Ingresses or HTTPRoutes (for external-DNS testing)
docker run <image> setup/dns-ingresses --count 100 --domain extdns.telescope.test
docker run <image> setup/dns-httproutes --count 100 --domain extdns.telescope.test
docker run <image> cleanup/dns-ingresses
docker run <image> cleanup/dns-httproutes

# Run module scripts
docker run <image> module/vegeta/install
docker run <image> module/vegeta/run --target-url http://localhost:8080 --rate 50 --duration 30s
Expand Down Expand Up @@ -153,6 +159,7 @@ Note: all modules expect to be **run from the root directory of this project**.
- `/install` — traffic controller install scripts (`nginx.sh`, `istio.sh`)
- `/setup` — server deployment scripts with readiness checks (`ingress.sh`, `gateway.sh`)
- `/scenarios` — load test scenario scripts. These assume the cluster, traffic controller, and server are already running. Their output is JSON so that consumers can decide on the final display format themselves.
- `/setup/dns-ingresses.sh`, `/setup/dns-httproutes.sh` — bulk-create N `Ingress` or Gateway API `HTTPRoute` resources (each with a unique `test-{i}.{domain}` hostname, all labeled `dns-test=true`) for external-DNS reconciliation testing. Paired with `/cleanup/dns-ingresses.sh` and `/cleanup/dns-httproutes.sh`, which delete by label.

### /server

Expand Down
40 changes: 40 additions & 0 deletions scripts/cleanup/dns-httproutes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/bin/bash

# Script to delete all HTTPRoute resources created by setup/dns-httproutes.
# Matches by label: dns-test=true. Does NOT touch the parent Gateway.
#
# This script expects to be run from the root directory of this project.

set -eo pipefail

show_usage() {
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Deletes all dns-test HTTPRoutes (label dns-test=true) in the namespace."
echo "The parent Gateway is left intact."
echo ""
echo "Options:"
echo " --namespace <ns> Kubernetes namespace (default: default)"
echo " -h, --help Show this help message"
exit 1
}

NAMESPACE="default"

while [[ $# -gt 0 ]]; do
case "$1" in
--namespace) NAMESPACE="$2"; shift 2 ;;
-h|--help) show_usage ;;
*)
echo "Error: Unknown option: $1"
show_usage
;;
esac
done

LABEL="dns-test=true"

echo "Deleting dns-test HTTPRoutes in namespace $NAMESPACE..."
kubectl delete httproute.gateway.networking.k8s.io -n "$NAMESPACE" -l "$LABEL" --wait=false --ignore-not-found

echo "Delete request submitted (--wait=false). Gateway left intact."
39 changes: 39 additions & 0 deletions scripts/cleanup/dns-ingresses.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash

# Script to delete all Ingress resources created by setup/dns-ingresses.
# Matches by label: dns-test=true
#
# This script expects to be run from the root directory of this project.

set -eo pipefail

show_usage() {
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Deletes all dns-test ingresses (label dns-test=true) in the namespace."
echo ""
echo "Options:"
echo " --namespace <ns> Kubernetes namespace (default: default)"
echo " -h, --help Show this help message"
exit 1
}

NAMESPACE="default"

while [[ $# -gt 0 ]]; do
case "$1" in
--namespace) NAMESPACE="$2"; shift 2 ;;
-h|--help) show_usage ;;
*)
echo "Error: Unknown option: $1"
show_usage
;;
esac
done

LABEL="dns-test=true"

echo "Deleting dns-test ingresses in namespace $NAMESPACE..."
kubectl delete ingress -n "$NAMESPACE" -l "$LABEL" --wait=false --ignore-not-found

echo "Delete request submitted (--wait=false)."
120 changes: 120 additions & 0 deletions scripts/setup/dns-httproutes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/bin/bash

# Script to create N Gateway API HTTPRoute resources, each with a unique hostname,
# all attached to an existing Gateway and routed to the existing backend service.
# Used to test external-dns record reconciliation at scale.
#
# This script expects to be run from the root directory of this project.

set -eo pipefail

show_usage() {
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Creates N HTTPRoute resources with hostnames test-{i}.{domain} attached"
echo "to an existing Gateway. All resources are labeled dns-test=true."
echo ""
echo "Options:"
echo " --count <N> (required) Number of HTTPRoutes to create"
echo " --domain <domain> (required) DNS zone domain (e.g. extdns.telescope.test)"
echo " --namespace <ns> Kubernetes namespace (default: default)"
echo " --gateway <name> Parent Gateway name (default: server)"
echo " --gateway-section-name <sec> Parent Gateway listener section name (default: http)"
echo " --service-name <name> Backend service name (default: server)"
echo " --service-port <port> Backend service port (default: 8080)"
echo " -h, --help Show this help message"
exit 1
}

COUNT=""
DOMAIN=""
NAMESPACE="default"
GATEWAY="server"
GATEWAY_SECTION_NAME="http"
SERVICE_NAME="server"
SERVICE_PORT="8080"

while [[ $# -gt 0 ]]; do
case "$1" in
--count) COUNT="$2"; shift 2 ;;
--domain) DOMAIN="$2"; shift 2 ;;
--namespace) NAMESPACE="$2"; shift 2 ;;
--gateway) GATEWAY="$2"; shift 2 ;;
--gateway-section-name) GATEWAY_SECTION_NAME="$2"; shift 2 ;;
--service-name) SERVICE_NAME="$2"; shift 2 ;;
--service-port) SERVICE_PORT="$2"; shift 2 ;;
-h|--help) show_usage ;;
*)
echo "Error: Unknown option: $1"
show_usage
;;
esac
done

if [ -z "$COUNT" ] || [ -z "$DOMAIN" ]; then
echo "Error: --count and --domain are required"
show_usage
fi

if ! [[ "$COUNT" =~ ^[1-9][0-9]*$ ]]; then
echo "Error: --count must be a positive integer (>= 1)"
exit 1
fi

DNS_LABEL='[a-z0-9]([-a-z0-9]*[a-z0-9])?'
if ! [[ "$DOMAIN" =~ ^${DNS_LABEL}(\.${DNS_LABEL})*$ ]]; then
echo "Error: --domain '$DOMAIN' is not a valid DNS subdomain (RFC 1123)"
exit 1
fi

if ! kubectl get gateway.gateway.networking.k8s.io "$GATEWAY" -n "$NAMESPACE" >/dev/null 2>&1; then
echo "Error: Gateway '$GATEWAY' not found in namespace '$NAMESPACE'"
exit 1
fi

if ! kubectl get service "$SERVICE_NAME" -n "$NAMESPACE" >/dev/null 2>&1; then
echo "Error: Service '$SERVICE_NAME' not found in namespace '$NAMESPACE'"
exit 1
fi

echo "Creating $COUNT dns-test HTTPRoutes:"
echo " Domain: $DOMAIN"
echo " Namespace: $NAMESPACE"
echo " Gateway: $GATEWAY (section: $GATEWAY_SECTION_NAME)"
echo " Service: $SERVICE_NAME:$SERVICE_PORT"

MANIFEST=$(
for i in $(seq 1 "$COUNT"); do
if [ "$i" -gt 1 ]; then
printf '%s\n' '---'
fi
cat <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: dns-test-${i}
namespace: ${NAMESPACE}
labels:
dns-test: "true"
spec:
parentRefs:
- name: ${GATEWAY}
sectionName: ${GATEWAY_SECTION_NAME}
hostnames:
- test-${i}.${DOMAIN}
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: ${SERVICE_NAME}
port: ${SERVICE_PORT}
EOF
done
)

echo "Applying $COUNT HTTPRoutes in a single bulk request..."
echo "$MANIFEST" | kubectl apply --server-side -f -

echo "Created $COUNT HTTPRoutes with hostnames test-1.${DOMAIN} through test-${COUNT}.${DOMAIN}"
Loading
Loading