Skip to content

Commit fbc04d8

Browse files
authored
e2e cleanup (#988)
* e2e cleanup Signed-off-by: Nir Rozenbaum <[email protected]> * removed unused variable Signed-off-by: Nir Rozenbaum <[email protected]> * fixed e2e test to run against the changes in a PR in ci Signed-off-by: Nir Rozenbaum <[email protected]> * typos Signed-off-by: Nir Rozenbaum <[email protected]> --------- Signed-off-by: Nir Rozenbaum <[email protected]>
1 parent f8b2e19 commit fbc04d8

File tree

5 files changed

+68
-191
lines changed

5 files changed

+68
-191
lines changed

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))
3838
# The path to the E2E manifest file. It can be overridden by setting the
3939
# E2E_MANIFEST_PATH environment variable. Note that HF_TOKEN must be set when using the GPU-based manifest.
4040
E2E_MANIFEST_PATH ?= config/manifests/vllm/sim-deployment.yaml
41+
# E2E_USE_KIND is a flag used in test-e2e target. when set to true it will load the e2e image into the kind cluster.
42+
# it is possible though to run e2e tests against clusters other than kind. in such a case, it is the user's responsibility to load
43+
# the image into the cluster.
44+
E2E_USE_KIND ?= true
4145

4246
SYNCER_IMAGE_NAME := lora-syncer
4347
SYNCER_IMAGE_REPO ?= $(IMAGE_REGISTRY)/$(SYNCER_IMAGE_NAME)
@@ -138,7 +142,7 @@ test-integration: ## Run integration tests.
138142

139143
.PHONY: test-e2e
140144
test-e2e: ## Run end-to-end tests against an existing Kubernetes cluster.
141-
MANIFEST_PATH=$(PROJECT_DIR)/$(E2E_MANIFEST_PATH) ./hack/run-e2es.sh
145+
MANIFEST_PATH=$(PROJECT_DIR)/$(E2E_MANIFEST_PATH) E2E_IMAGE=$(IMAGE_TAG) USE_KIND=$(E2E_USE_KIND) ./hack/test-e2e.sh
142146

143147
.PHONY: lint
144148
lint: golangci-lint ## Run golangci-lint linter

hack/run-e2es.sh

Lines changed: 0 additions & 43 deletions
This file was deleted.

hack/test-e2e.sh

Lines changed: 35 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/bash
1+
#!/usr/bin/env bash
22

33
# Copyright 2025 The Kubernetes Authors.
44
#
@@ -14,138 +14,41 @@
1414
# See the License for the specific language governing permissions and
1515
# limitations under the License.
1616

17-
# This script verifies end-to-end connectivity for an example inference extension test environment based on
18-
# resources from the quickstart guide or e2e test framework. It can optionally launch a "curl" client pod to
19-
# run these tests within the cluster.
20-
#
21-
# USAGE: ./hack/e2e-test.sh
22-
#
23-
# OPTIONAL ENVIRONMENT VARIABLES:
24-
# - TIME: The duration (in seconds) for which the test will run. Defaults to 1 second.
25-
# - CURL_POD: If set to "true", the script will use a Kubernetes pod named "curl" for making requests.
26-
# - IP: Override the detected IP address. If not provided, the script attempts to use a Gateway based on
27-
# the quickstart guide or an Envoy service IP based on the e2e test framework.
28-
# - PORT: Override the detected port. If not provided, the script attempts to use a Gateway based on the
29-
# quickstart guide or an Envoy service IP based on the e2e test framework.
30-
#
31-
# WHAT THE SCRIPT DOES:
32-
# 1. Determines if there is a Gateway named "inference-gateway" in the "default" namespace. If found, it extracts the IP
33-
# address and port from the Gateway's "llm-gw" listener. Otherwise, it falls back to the Envoy service in the "default" namespace.
34-
# 2. Optionally checks for (or creates) a "curl" pod, ensuring it is ready to execute requests.
35-
# 3. Loops for $TIME seconds, sending requests every 5 seconds to the /v1/completions endpoint to confirm successful connectivity.
36-
37-
set -euo pipefail
38-
39-
# Determine the directory of this script and build an absolute path to client.yaml.
40-
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
41-
CLIENT_YAML="$SCRIPT_DIR/../test/testdata/client.yaml"
42-
43-
# TIME is the amount of time, in seconds, to run the test.
44-
TIME=${TIME:-1}
45-
# Optionally use a client curl pod for executing the curl command.
46-
CURL_POD=${CURL_POD:-false}
47-
48-
check_resource_exists() {
49-
local type=$1
50-
local name=$2
51-
local namespace=$3
52-
53-
if kubectl get "$type" "$name" -n "$namespace" &>/dev/null; then
54-
return 0
55-
else
56-
return 1
57-
fi
17+
set -euox pipefail
18+
19+
install_kind() {
20+
if ! command -v kind &>/dev/null; then
21+
echo "kind not found, installing..."
22+
[ $(uname -m) = x86_64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.29.0/kind-linux-amd64
23+
# For ARM64
24+
[ $(uname -m) = aarch64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.29.0/kind-linux-arm64
25+
chmod +x ./kind
26+
mv ./kind /usr/local/bin/kind
27+
else
28+
echo "kind is already installed."
29+
fi
5830
}
5931

60-
check_pod_ready() {
61-
local pod_name=$1
62-
local namespace=$2
63-
# Check the Ready condition using jsonpath. Default to False if not found.
64-
local ready_status
65-
ready_status=$(kubectl get pod "$pod_name" -n "$namespace" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "False")
66-
if [[ "$ready_status" == "True" ]]; then
67-
return 0
68-
else
69-
return 1
70-
fi
71-
}
72-
73-
# Try to get the Gateway's IP and the port from the listener named "llm-gw" if it exists.
74-
if check_resource_exists "gateway" "inference-gateway" "default"; then
75-
GATEWAY_IP=$(kubectl get gateway inference-gateway -n default -o jsonpath='{.status.addresses[0].value}')
76-
# Use JSONPath to select the port from the listener with name "http"
77-
GATEWAY_PORT=$(kubectl get gateway inference-gateway -n default -o jsonpath='{.spec.listeners[?(@.name=="http")].port}')
78-
else
79-
GATEWAY_IP=""
80-
GATEWAY_PORT=""
32+
if [ "$USE_KIND" = "true" ]; then
33+
install_kind # make sure kind cli is installed
34+
if ! kubectl config current-context >/dev/null 2>&1; then # if no active kind cluster found
35+
echo "No active kubecontext found. creating a kind cluster for running the tests..."
36+
kind create cluster --name inference-e2e
37+
KIND_CLUSTER=inference-e2e IMAGE_TAG=${E2E_IMAGE} make image-kind
38+
else
39+
current_context=$(kubectl config current-context)
40+
current_kind_cluster="${current_context#kind-}"
41+
echo "Found an active kind cluster ${current_kind_cluster} for running the tests..."
42+
KIND_CLUSTER=${current_kind_cluster} IMAGE_TAG=${E2E_IMAGE} make image-kind
43+
fi
44+
else
45+
# don't use kind. it's the caller responsibility to load the image into the cluster, we just run the tests.
46+
# this section is useful when one wants to run an official release or latest main against a cluster other than kind.
47+
if ! kubectl config current-context >/dev/null 2>&1; then # if no active cluster found
48+
echo "No active kubecontext found. exiting..."
49+
exit
50+
fi
8151
fi
8252

83-
if [[ -n "$GATEWAY_IP" && -n "$GATEWAY_PORT" ]]; then
84-
echo "Using Gateway inference-gateway IP and port from listener 'llm-gw'."
85-
IP=${IP:-$GATEWAY_IP}
86-
PORT=${PORT:-$GATEWAY_PORT}
87-
else
88-
echo "Gateway inference-gateway not found or missing IP/port. Falling back to Envoy service."
89-
# Ensure the Envoy service exists.
90-
if ! check_resource_exists "svc" "envoy" "default"; then
91-
echo "Error: Envoy service not found in namespace 'default'."
92-
exit 1
93-
fi
94-
IP=${IP:-$(kubectl get svc envoy -n default -o jsonpath='{.spec.clusterIP}')}
95-
PORT=${PORT:-$(kubectl get svc envoy -n default -o jsonpath='{.spec.ports[0].port}')}
96-
fi
97-
98-
# Optionally verify that the curl pod exists and is ready.
99-
if [[ "$CURL_POD" == "true" ]]; then
100-
if ! check_resource_exists "pod" "curl" "default"; then
101-
echo "Pod 'curl' not found in namespace 'default'. Applying client.yaml from $CLIENT_YAML..."
102-
kubectl apply -f "$CLIENT_YAML"
103-
fi
104-
echo "Waiting for pod 'curl' to be ready..."
105-
# Retry every 5 seconds for up to 30 seconds (6 attempts)
106-
for i in {1..6}; do
107-
if check_pod_ready "curl" "default"; then
108-
echo "Pod 'curl' is now ready."
109-
break
110-
fi
111-
echo "Retry attempt $i: Pod 'curl' not ready; waiting 5 seconds..."
112-
sleep 5
113-
done
114-
115-
if ! check_pod_ready "curl" "default"; then
116-
echo "Error: Pod 'curl' is still not ready in namespace 'default' after 30 seconds."
117-
exit 1
118-
fi
119-
fi
120-
121-
# Validate that we have a non-empty IP and PORT.
122-
if [[ -z "$IP" ]]; then
123-
echo "Error: Unable to determine a valid IP from either Gateway or Envoy service."
124-
exit 1
125-
fi
126-
127-
if [[ -z "$PORT" ]]; then
128-
echo "Error: Unable to determine a valid port from either Gateway or Envoy service."
129-
exit 1
130-
fi
131-
132-
echo "Using IP: $IP"
133-
echo "Using PORT: $PORT"
134-
135-
# Run the test for the specified duration.
136-
end=$((SECONDS + TIME))
137-
if [[ "$CURL_POD" == "true" ]]; then
138-
while [ $SECONDS -lt $end ]; do
139-
kubectl exec po/curl -- curl -i "$IP:$PORT/v1/completions" \
140-
-H 'Content-Type: application/json' \
141-
-d '{"model": "food-review","prompt": "Write as if you were a critic: San Francisco","max_tokens": 100,"temperature": 0}'
142-
sleep 5
143-
done
144-
else
145-
while [ $SECONDS -lt $end ]; do
146-
curl -i "$IP:$PORT/v1/completions" \
147-
-H 'Content-Type: application/json' \
148-
-d '{"model": "food-review","prompt": "Write as if you were a critic: San Francisco","max_tokens": 100,"temperature": 0}'
149-
sleep 5
150-
done
151-
fi
53+
echo "Found an active cluster. Running Go e2e tests in ./epp..."
54+
go test ./test/e2e/epp/ -v -ginkgo.v

test/e2e/epp/e2e_suite_test.go

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,14 @@ const (
9292
)
9393

9494
var (
95-
ctx context.Context
95+
ctx = context.Background()
9696
cli client.Client
9797
// Required for exec'ing in curl pod
98-
kubeCli *kubernetes.Clientset
99-
scheme = runtime.NewScheme()
100-
cfg = config.GetConfigOrDie()
101-
nsName string
98+
kubeCli *kubernetes.Clientset
99+
scheme = runtime.NewScheme()
100+
cfg = config.GetConfigOrDie()
101+
nsName string
102+
e2eImage string
102103
)
103104

104105
func TestAPIs(t *testing.T) {
@@ -113,6 +114,8 @@ var _ = ginkgo.BeforeSuite(func() {
113114
if nsName == "" {
114115
nsName = defaultNsName
115116
}
117+
e2eImage = os.Getenv("E2E_IMAGE")
118+
gomega.Expect(e2eImage).NotTo(gomega.BeEmpty(), "E2E_IMAGE environment variable is not set")
116119

117120
ginkgo.By("Setting up the test suite")
118121
setupSuite()
@@ -122,9 +125,12 @@ var _ = ginkgo.BeforeSuite(func() {
122125
})
123126

124127
func setupInfra() {
128+
// this function ensures ModelServer manifest path exists.
129+
// run this before createNs to fail fast in case it doesn't.
130+
modelServerManifestPath := readModelServerManifestPath()
131+
125132
createNamespace(cli, nsName)
126133

127-
modelServerManifestPath := readModelServerManifestPath()
128134
modelServerManifestArray := getYamlsFromModelServerManifest(modelServerManifestPath)
129135
if strings.Contains(modelServerManifestArray[0], "hf-token") {
130136
createHfSecret(cli, modelServerSecretManifest)
@@ -140,7 +146,8 @@ func setupInfra() {
140146
createEnvoy(cli, envoyManifest)
141147
createMetricsRbac(cli, metricsRbacManifest)
142148
// Run this step last, as it requires additional time for the model server to become ready.
143-
createModelServer(cli, modelServerManifestArray, modelServerManifestPath)
149+
ginkgo.By("Creating model server resources from manifest: " + modelServerManifestPath)
150+
createModelServer(cli, modelServerManifestArray)
144151
}
145152

146153
var _ = ginkgo.AfterSuite(func() {
@@ -151,7 +158,6 @@ var _ = ginkgo.AfterSuite(func() {
151158
// setupSuite initializes the test suite by setting up the Kubernetes client,
152159
// loading required API schemes, and validating configuration.
153160
func setupSuite() {
154-
ctx = context.Background()
155161
gomega.ExpectWithOffset(1, cfg).NotTo(gomega.BeNil())
156162

157163
err := clientgoscheme.AddToScheme(scheme)
@@ -173,6 +179,10 @@ func setupSuite() {
173179
}
174180

175181
func cleanupResources() {
182+
if cli == nil {
183+
return // could happen if BeforeSuite had an error
184+
}
185+
176186
gomega.Expect(testutils.DeleteClusterResources(ctx, cli)).To(gomega.Succeed())
177187
gomega.Expect(testutils.DeleteNamespacedResources(ctx, cli, nsName)).To(gomega.Succeed())
178188
}
@@ -290,8 +300,7 @@ func createMetricsRbac(k8sClient client.Client, filePath string) {
290300
}
291301

292302
// createModelServer creates the model server resources used for testing from the given filePaths.
293-
func createModelServer(k8sClient client.Client, modelServerManifestArray []string, deployPath string) {
294-
ginkgo.By("Creating model server resources from manifest: " + deployPath)
303+
func createModelServer(k8sClient client.Client, modelServerManifestArray []string) {
295304
createObjsFromYaml(k8sClient, modelServerManifestArray)
296305

297306
// Wait for the deployment to exist.
@@ -362,10 +371,14 @@ func createEnvoy(k8sClient client.Client, filePath string) {
362371
// createInferExt creates the inference extension resources used for testing from the given filePath.
363372
func createInferExt(k8sClient client.Client, filePath string) {
364373
inManifests := readYaml(filePath)
365-
ginkgo.By("Replacing placeholder namespace with E2E_NS environment variable")
374+
ginkgo.By("Replacing placeholders with environment variables")
366375
outManifests := []string{}
367-
for _, m := range inManifests {
368-
outManifests = append(outManifests, strings.ReplaceAll(m, "$E2E_NS", nsName))
376+
for _, manifest := range inManifests {
377+
replacer := strings.NewReplacer(
378+
"$E2E_NS", nsName,
379+
"$E2E_IMAGE", e2eImage,
380+
)
381+
outManifests = append(outManifests, replacer.Replace(manifest))
369382
}
370383

371384
ginkgo.By("Creating inference extension resources from manifest: " + filePath)

test/testdata/inferencepool-e2e.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ spec:
4747
terminationGracePeriodSeconds: 130
4848
containers:
4949
- name: epp
50-
image: us-central1-docker.pkg.dev/k8s-staging-images/gateway-api-inference-extension/epp:main
51-
imagePullPolicy: Always
50+
image: $E2E_IMAGE
51+
imagePullPolicy: IfNotPresent
5252
args:
5353
- -poolName
5454
- "vllm-llama3-8b-instruct"

0 commit comments

Comments
 (0)