Skip to content

Commit c2bf3dc

Browse files
committed
[WIP] E2E Test Resource Setup for Multiport Pod
1 parent 79457c7 commit c2bf3dc

File tree

12 files changed

+401
-111
lines changed

12 files changed

+401
-111
lines changed

Makefile

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ IMAGE_NAME := epp
3636
IMAGE_REPO ?= $(IMAGE_REGISTRY)/$(IMAGE_NAME)
3737
IMAGE_TAG ?= $(IMAGE_REPO):$(GIT_TAG)
3838
PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))
39+
# PROJECT_DIR := $(shell pwd)
3940
# The path to the E2E manifest file. It can be overridden by setting the
4041
# E2E_MANIFEST_PATH environment variable. Note that HF_TOKEN must be set when using the GPU-based manifest.
4142
E2E_MANIFEST_PATH ?= config/manifests/vllm/sim-deployment.yaml
@@ -153,8 +154,8 @@ test-integration: envtest ## Run integration tests.
153154
CGO_ENABLED=1 KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./test/integration/epp/... -race -coverprofile cover.out
154155

155156
.PHONY: test-e2e
156-
test-e2e: ## Run end-to-end tests against an existing Kubernetes cluster.
157-
MANIFEST_PATH=$(PROJECT_DIR)/$(E2E_MANIFEST_PATH) E2E_IMAGE=$(E2E_IMAGE) USE_KIND=$(E2E_USE_KIND) ./hack/test-e2e.sh
157+
test-e2e: generate install ## Run end-to-end tests against an existing Kubernetes cluster.
158+
MANIFEST_PATH=$(PROJECT_DIR)/$(E2E_MANIFEST_PATH) E2E_IMAGE=$(E2E_IMAGE) USE_KIND=$(E2E_USE_KIND) ./hack/test-e2e.sh
158159

159160
.PHONY: lint
160161
lint: golangci-lint ## Run golangci-lint linter
@@ -245,16 +246,13 @@ syncer-image-local-push: syncer-image-local-build
245246

246247
.PHONY: syncer-image-build
247248
syncer-image-build:
248-
$ cd $(CURDIR)/tools/dynamic-lora-sidecar && $(IMAGE_BUILD_CMD) -t $(SYNCER_IMAGE_TAG) \
249-
--platform=$(PLATFORMS) \
250-
--build-arg BASE_IMAGE=$(BASE_IMAGE) \
251-
--build-arg BUILDER_IMAGE=$(BUILDER_IMAGE) \
252-
$(PUSH) \
253-
$(SYNCER_IMAGE_BUILD_EXTRA_OPTS) ./
254-
255-
.PHONY: syncer-image-push
256-
syncer-image-push: PUSH=--push
257-
syncer-image-push: syncer-image-build
249+
cd $(PROJECT_DIR)/tools/dynamic-lora-sidecar && \
250+
$(IMAGE_BUILD_CMD) -t $(SYNCER_IMAGE_TAG) \
251+
--platform=$(PLATFORMS) \
252+
--build-arg BASE_IMAGE=$(BASE_IMAGE) \
253+
--build-arg BUILDER_IMAGE=$(BUILDER_IMAGE) \
254+
$(PUSH) \
255+
$(SYNCER_IMAGE_BUILD_EXTRA_OPTS) .
258256

259257
##@ Body-based Routing extension
260258

config/manifests/inferenceobjective.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ metadata:
44
name: food-review
55
spec:
66
priority: 1
7+
subresources:
8+
status: {}
79
poolRef:
810
group: inference.networking.k8s.io
911
name: vllm-llama3-8b-instruct
@@ -14,6 +16,8 @@ metadata:
1416
name: base-model
1517
spec:
1618
priority: 2
19+
subresources:
20+
status: {}
1721
poolRef:
1822
group: inference.networking.k8s.io
1923
name: vllm-llama3-8b-instruct
@@ -24,6 +28,8 @@ metadata:
2428
name: base-model-cpu
2529
spec:
2630
priority: 2
31+
subresources:
32+
status: {}
2733
poolRef:
2834
group: inference.networking.k8s.io
2935
name: vllm-llama3-8b-instruct

hack/test-e2e.sh

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,12 @@ if [ "$USE_KIND" = "true" ]; then
3434
if ! kubectl config current-context >/dev/null 2>&1; then # if no active kind cluster found
3535
echo "No active kubecontext found. creating a kind cluster for running the tests..."
3636
kind create cluster --name inference-e2e
37-
KIND_CLUSTER=inference-e2e IMAGE_TAG=${E2E_IMAGE} make image-kind
37+
KIND_CLUSTER=inference-e2e IMAGE_TAG=${E2E_IMAGE} make s="" image-kind
3838
else
3939
current_context=$(kubectl config current-context)
4040
current_kind_cluster="${current_context#kind-}"
4141
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
42+
KIND_CLUSTER=${current_kind_cluster} IMAGE_TAG=${E2E_IMAGE} make s="" image-kind
4343
fi
4444
else
4545
# don't use kind. it's the caller responsibility to load the image into the cluster, we just run the tests.
@@ -51,4 +51,19 @@ else
5151
fi
5252

5353
echo "Found an active cluster. Running Go e2e tests in ./epp..."
54+
55+
# CRD and Namespace deletion BEFORE THE TEST RUN.
56+
# This ensures a clean state for the subsequent 'make install' dependency
57+
# (which runs BEFORE this script starts).
58+
59+
kubectl delete namespace inf-ext-e2e --ignore-not-found=true --wait=true
60+
# Removing the CRD deletion here is safe, as the Makefile now runs 'install'
61+
# which depends on 'generate' and 'kustomize build' then 'kubectl apply'.
62+
63+
kubectl delete namespace inf-ext-e2e --ignore-not-found=true --wait=true
64+
kubectl delete crd inferencepools.inference.networking.k8s.io --ignore-not-found=true --wait=true
65+
# kubectl delete namespace inf-ext-e2e
66+
# kubectl delete crd inferencepools.inference.networking.k8s.io
67+
# kubectl delete inferencepool vllm-llama3-8b-instruct -n inf-ext-e2e
68+
# MANIFEST_PATH=${E2E_MANIFEST_PATH}
5469
go test ./test/e2e/epp/ -v -ginkgo.v

image/Dockerfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
FROM python:3.12-alpine
2+
WORKDIR /app
3+
COPY server.py /app/server.py
4+
5+
# ENTRYPOINT ensures that arguments passed at runtime are appended to this command.
6+
ENTRYPOINT ["python", "-u", "server.py"]

image/server.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import threading
2+
import sys
3+
from http.server import HTTPServer, BaseHTTPRequestHandler
4+
5+
class MultiPortHandler(BaseHTTPRequestHandler):
6+
def do_GET(self):
7+
self._handle_request()
8+
9+
# FIX: Add this method to handle the test's POST requests
10+
def do_POST(self):
11+
# We need to read the body to keep the socket clean, even if we ignore it
12+
content_length = int(self.headers.get('Content-Length', 0))
13+
self.rfile.read(content_length)
14+
self._handle_request()
15+
16+
def _handle_request(self):
17+
self.send_response(200)
18+
self.send_header('Content-type', 'text/plain')
19+
self.end_headers()
20+
response = f"Handled by port: {self.server.server_port}\n"
21+
self.wfile.write(response.encode('utf-8'))
22+
23+
def log_message(self, format, *args):
24+
pass
25+
26+
def start_server(port):
27+
print(f"Starting server on port {port}...")
28+
server = HTTPServer(('0.0.0.0', port), MultiPortHandler)
29+
server.serve_forever()
30+
31+
if __name__ == "__main__":
32+
start_port = 8000
33+
port_count = 1
34+
if len(sys.argv) >= 3:
35+
start_port = int(sys.argv[1])
36+
port_count = int(sys.argv[2])
37+
38+
threads = []
39+
for i in range(port_count):
40+
port = start_port + i
41+
thread = threading.Thread(target=start_server, args=(port,))
42+
thread.daemon = True
43+
thread.start()
44+
threads.append(thread)
45+
46+
for thread in threads:
47+
thread.join()

pkg/epp/controller/inferenceobjective_reconciler.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,16 @@ import (
2727
"sigs.k8s.io/controller-runtime/pkg/log"
2828
"sigs.k8s.io/controller-runtime/pkg/predicate"
2929

30+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31+
3032
"sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
3133
"sigs.k8s.io/gateway-api-inference-extension/pkg/common"
3234
"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datastore"
3335
logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
3436
)
3537

3638
type InferenceObjectiveReconciler struct {
37-
client.Reader
39+
client.Client
3840
Datastore datastore.Datastore
3941
PoolGKNN common.GKNN
4042
}
@@ -65,6 +67,33 @@ func (c *InferenceObjectiveReconciler) Reconcile(ctx context.Context, req ctrl.R
6567
c.Datastore.ObjectiveSet(infObjective)
6668
logger.Info("Added/Updated InferenceObjective")
6769

70+
newCondition := metav1.Condition{
71+
Type: "Accepted",
72+
Status: metav1.ConditionTrue, // Use metav1.ConditionTrue in real code
73+
Reason: "PoolReferenceValid",
74+
Message: "InferenceObjective successfully configured and pool reference is valid.",
75+
LastTransitionTime: metav1.Now(),
76+
}
77+
78+
// Find and update the status condition list
79+
found := false
80+
for i, cond := range infObjective.Status.Conditions {
81+
if cond.Type == "Accepted" {
82+
infObjective.Status.Conditions[i] = newCondition
83+
found = true
84+
break
85+
}
86+
}
87+
if !found {
88+
infObjective.Status.Conditions = append(infObjective.Status.Conditions, newCondition)
89+
}
90+
91+
// Write the status update back to the API server
92+
if err := c.Status().Update(ctx, infObjective); err != nil {
93+
return ctrl.Result{}, fmt.Errorf("failed to update InferenceObjective status: %w", err)
94+
}
95+
logger.Info("InferenceObjective Status updated successfully to Accepted: True")
96+
6897
return ctrl.Result{}, nil
6998
}
7099

pkg/epp/controller/inferenceobjective_reconciler_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ func TestInferenceObjectiveReconciler(t *testing.T) {
166166
}
167167
_ = ds.PoolSet(context.Background(), fakeClient, pool)
168168
reconciler := &InferenceObjectiveReconciler{
169-
Reader: fakeClient,
169+
Client: fakeClient,
170170
Datastore: ds,
171171
PoolGKNN: common.GKNN{
172172
NamespacedName: types.NamespacedName{Name: pool.Name, Namespace: pool.Namespace},

pkg/epp/server/runserver.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func (r *ExtProcServerRunner) SetupWithManager(ctx context.Context, mgr ctrl.Man
123123

124124
if err := (&controller.InferenceObjectiveReconciler{
125125
Datastore: r.Datastore,
126-
Reader: mgr.GetClient(),
126+
Client: mgr.GetClient(),
127127
PoolGKNN: r.PoolGKNN,
128128
}).SetupWithManager(ctx, mgr); err != nil {
129129
return fmt.Errorf("failed setting up InferenceObjectiveReconciler: %w", err)

pkg/epp/server/server_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import (
3434
)
3535

3636
const (
37-
bufSize = 1024 * 1024
37+
// bufSize = 1024 * 1024
3838
podName = "pod1"
3939
podAddress = "1.2.3.4"
4040
poolPort = int32(5678)

test/e2e/epp/e2e_suite_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,11 @@ var _ = ginkgo.BeforeSuite(func() {
114114
}
115115

116116
ginkgo.By("Setting up the test suite")
117-
setupSuite()
117+
setupSuite() // <--- Creates the Client (Initial)
118118

119119
ginkgo.By("Creating test infrastructure")
120-
setupInfra()
120+
setupInfra() // <--- Installs the CRDs
121+
121122
})
122123

123124
func setupInfra() {
@@ -127,6 +128,11 @@ func setupInfra() {
127128

128129
createNamespace(testConfig)
129130

131+
// Add this section to apply the RBAC AFTER the namespace is created
132+
ginkgo.By("Applying InferenceObjective Status RBAC")
133+
testutils.ApplyYAMLFile(testConfig, "../../testdata/inferenceobjective-status-rbac.yaml")
134+
// gomega.Expect(err).To(gomega.Succeed(), "Failed to apply inferenceobjective-status-rbac.yaml")
135+
130136
modelServerManifestArray := getYamlsFromModelServerManifest(modelServerManifestPath)
131137
if strings.Contains(modelServerManifestArray[0], "hf-token") {
132138
createHfSecret(testConfig, modelServerSecretManifest)

0 commit comments

Comments
 (0)