Skip to content

Commit e4236ff

Browse files
authored
test: Parallelize the End to End tests - Part one infrastructure (llm-d#1976)
* Removed unused service Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Added ginkgo CLI to the builder container Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Removed the namespace from the TestConfig struct Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Namespace name is now a parameter to utility functions Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Create envoy and metrics ports based on the number of parallel processes Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Use envoy and metrics port based on process number Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Update Cleanup calls with new parameters Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Fixed lint issues Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Updated co-ordinator tests with new utility interfaces Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Check that E2E_NUM_PROCS equals the total number of processes Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * If only one process is being used, dump using the basse namespace Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Comment corrected Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Update from review comment Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Added comments to descibe important helper functions Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> --------- Signed-off-by: Shmuel Kallner <kallner@il.ibm.com>
1 parent 21f3af8 commit e4236ff

13 files changed

Lines changed: 284 additions & 203 deletions

File tree

Dockerfile.builder

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ ENV ENVTEST_K8S_VERSION=${ENVTEST_K8S_VERSION}
6363
# /usr/local/bin.
6464
RUN GOBIN=/usr/local/bin go install golang.org/x/vuln/cmd/govulncheck@${GOVULNCHECK_VERSION}
6565

66+
# Install the ginkgo CLI. Build-time install ensures runtime invocations under
67+
# --userns=keep-id / -u <uid> can use the binary without writing to root-owned
68+
# /usr/local/bin.
69+
RUN GOBIN=/usr/local/bin go install github.com/onsi/ginkgo/v2/ginkgo@v2.28.3
70+
6671
# Go caches are mounted as volumes at runtime for persistence across image rebuilds.
6772
# Directories are created with open permissions so non-root users (docker -u) can write.
6873
ENV GOMODCACHE=/go/pkg/mod

deploy/environments/dev/e2e-infra/services.yaml

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,6 @@ spec:
2020
---
2121
apiVersion: v1
2222
kind: Service
23-
metadata:
24-
name: e2e-epp-health
25-
spec:
26-
selector:
27-
app: e2e-epp
28-
ports:
29-
- name: health
30-
protocol: TCP
31-
port: 9003
32-
targetPort: 9003
33-
nodePort: 30081
34-
appProtocol: http2
35-
type: NodePort
36-
---
37-
apiVersion: v1
38-
kind: Service
3923
metadata:
4024
name: e2e-epp-metrics
4125
spec:

test/coordinator/e2e/coordinator/coordinator_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,14 @@ func runCoordinatorPipeline(body []byte, expectedSteps []string, expectedImages
110110
if keepClusterOnFailure && ginkgo.CurrentSpecReport().Failed() {
111111
return
112112
}
113-
testutils.DeleteObjects(testConfig, coordinator)
114-
testutils.DeleteObjects(testConfig, modelServers)
115-
testutils.DeleteObjects(testConfig, decodeEPP)
116-
testutils.DeleteObjects(testConfig, prefillEPP)
117-
testutils.DeleteObjects(testConfig, encodeEPP)
118-
testutils.DeleteObjects(testConfig, decodePool)
119-
testutils.DeleteObjects(testConfig, prefillPool)
120-
testutils.DeleteObjects(testConfig, encodePool)
113+
testutils.DeleteObjects(testConfig, coordinator, nsName)
114+
testutils.DeleteObjects(testConfig, modelServers, nsName)
115+
testutils.DeleteObjects(testConfig, decodeEPP, nsName)
116+
testutils.DeleteObjects(testConfig, prefillEPP, nsName)
117+
testutils.DeleteObjects(testConfig, encodeEPP, nsName)
118+
testutils.DeleteObjects(testConfig, decodePool, nsName)
119+
testutils.DeleteObjects(testConfig, prefillPool, nsName)
120+
testutils.DeleteObjects(testConfig, encodePool, nsName)
121121
})
122122

123123
// Dump coordinator logs on failure, or always when E2E_PRINT_COORDINATOR_LOGS is

test/coordinator/e2e/coordinator/e2e_suite_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ var _ = ginkgo.BeforeSuite(func() {
114114
if k8sContext == "" {
115115
setupK8sCluster()
116116
}
117-
testConfig = testutils.NewTestConfig(nsName, k8sContext)
117+
testConfig = testutils.NewTestConfig(k8sContext)
118118
setupK8sClient()
119119
setupNameSpace()
120120

@@ -138,7 +138,7 @@ var _ = ginkgo.ReportAfterSuite("cleanup", func(report ginkgo.Report) {
138138
return
139139
}
140140
if len(rendererObjects) > 0 {
141-
testutils.DeleteObjects(testConfig, rendererObjects)
141+
testutils.DeleteObjects(testConfig, rendererObjects, nsName)
142142
}
143143
for _, session := range portForwardSessions {
144144
session.Terminate()

test/coordinator/e2e/coordinator/pools_check_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ func expectedPools() []string {
3939
func expectAllPoolsExist() {
4040
for _, name := range expectedPools() {
4141
pool := &inferenceapi.InferencePool{}
42-
key := types.NamespacedName{Namespace: testConfig.NsName, Name: name}
42+
key := types.NamespacedName{Namespace: nsName, Name: name}
4343
gomega.Eventually(func() error {
4444
return testConfig.K8sClient.Get(testConfig.Context, key, pool)
4545
}, readyTimeout, defaultInterval).Should(
4646
gomega.Succeed(),
47-
"InferencePool %q not found in namespace %q", name, testConfig.NsName,
47+
"InferencePool %q not found in namespace %q", name, nsName,
4848
)
4949
}
5050
}

test/coordinator/e2e/coordinator/setup_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func setupInfra() {
6565
createCRDs()
6666

6767
ginkgo.By("Applying shared Role/epp-reader from " + baseRbacManifest)
68-
_ = testutils.CreateObjsFromYaml(testConfig, testutils.ReadYaml(baseRbacManifest))
68+
_ = testutils.CreateObjsFromYaml(testConfig, testutils.ReadYaml(baseRbacManifest), nsName)
6969

7070
ginkgo.By("Applying Envoy from " + envoyManifest)
7171
applyManifest(envoyManifest, map[string]string{
@@ -79,11 +79,11 @@ func createCRDs() {
7979
ginkgo.By("Installing Gateway API CRDs from " + crdGatewayAPIPath)
8080
crds := e2eutil.RunKustomize(crdGatewayAPIPath)
8181
crds = e2eutil.FilterKinds(crds, "ValidatingAdmissionPolicy", "ValidatingAdmissionPolicyBinding")
82-
_ = testutils.CreateObjsFromYaml(testConfig, crds)
82+
_ = testutils.CreateObjsFromYaml(testConfig, crds, "")
8383

8484
ginkgo.By("Installing GIE CRDs from " + crdGIEPath)
8585
gieCRDs := e2eutil.RunKustomize(crdGIEPath)
86-
_ = testutils.CreateObjsFromYaml(testConfig, gieCRDs)
86+
_ = testutils.CreateObjsFromYaml(testConfig, gieCRDs, "")
8787
}
8888

8989
// createEndPointPicker creates the scheduling ConfigMap and EPP Deployment (plus
@@ -122,7 +122,7 @@ func createInferencePool(phase string, toDelete bool) []string {
122122

123123
docs := testutils.ReadYaml(manifest)
124124
docs = e2eutil.SubstituteMany(docs, eppSubstitutions())
125-
return testutils.CreateObjsFromYaml(testConfig, docs)
125+
return testutils.CreateObjsFromYaml(testConfig, docs, nsName)
126126
}
127127

128128
// deletePoolIfExists removes the named InferencePool when present so a rerun
@@ -131,12 +131,12 @@ func createInferencePool(phase string, toDelete bool) []string {
131131
func deletePoolIfExists(name string) {
132132
pool := &inferenceapi.InferencePool{}
133133
err := testConfig.K8sClient.Get(testConfig.Context,
134-
types.NamespacedName{Namespace: testConfig.NsName, Name: name}, pool)
134+
types.NamespacedName{Namespace: nsName, Name: name}, pool)
135135
if apierrors.IsNotFound(err) {
136136
return
137137
}
138138
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "checking InferencePool %s", name)
139-
testutils.DeleteObjects(testConfig, []string{"InferencePool/" + name})
139+
testutils.DeleteObjects(testConfig, []string{"InferencePool/" + name}, nsName)
140140
}
141141

142142
// createModelServers deploys the vLLM encode/prefill/decode workers from the
@@ -152,7 +152,7 @@ func createModelServers(encodeReplicas, prefillReplicas, decodeReplicas int) []s
152152
docs = e2eutil.SubstituteMany(docs, subs)
153153
docs = e2eutil.RemoveEmptyArgs(docs)
154154
docs = e2eutil.RemoveEmptyLabels(docs)
155-
objects := testutils.CreateObjsFromYaml(testConfig, docs)
155+
objects := testutils.CreateObjsFromYaml(testConfig, docs, nsName)
156156
podsInDeploymentsReady(objects)
157157
return objects
158158
}
@@ -180,7 +180,7 @@ func createCoordinator(config string) []string {
180180
docs = e2eutil.FilterKinds(docs, "ConfigMap")
181181
docs = e2eutil.SubstituteMany(docs, coordinatorSubstitutions())
182182
docs = e2eutil.RemoveEmptyArgs(docs)
183-
objects = append(objects, testutils.CreateObjsFromYaml(testConfig, docs)...)
183+
objects = append(objects, testutils.CreateObjsFromYaml(testConfig, docs, nsName)...)
184184

185185
podsInDeploymentsReady(objects)
186186
if k8sContext != "" {
@@ -239,7 +239,7 @@ func applyManifest(path string, subs map[string]string) []string {
239239
docs := testutils.ReadYaml(path)
240240
docs = e2eutil.SubstituteMany(docs, subs)
241241
docs = e2eutil.RemoveEmptyArgs(docs)
242-
return testutils.CreateObjsFromYaml(testConfig, docs)
242+
return testutils.CreateObjsFromYaml(testConfig, docs, nsName)
243243
}
244244

245245
func eppSubstitutions() map[string]string {
@@ -301,7 +301,7 @@ func createRenderer() []string {
301301
docs := e2eutil.RunKustomize(rendererComponentDir)
302302
docs = e2eutil.SubstituteMany(docs, rendererSubstitutions())
303303
docs = e2eutil.RemoveEmptyArgs(docs)
304-
objects := testutils.CreateObjsFromYaml(testConfig, docs)
304+
objects := testutils.CreateObjsFromYaml(testConfig, docs, nsName)
305305
podsInDeploymentsReady(objects)
306306
return objects
307307
}

test/e2e/disruption_test.go

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ var disruptionClient = &http.Client{Timeout: 10 * time.Second}
3434
// sendRawCompletion sends a completion request and returns the HTTP status code.
3535
func sendRawCompletion() (int, error) {
3636
body := fmt.Sprintf(`{"model":"%s","prompt":"%s","max_tokens":10}`, simModelName, simplePrompt)
37-
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:%s/v1/completions", port), strings.NewReader(body))
37+
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:%d/v1/completions", getPort()), strings.NewReader(body))
3838
if err != nil {
3939
return 0, err
4040
}
@@ -85,7 +85,7 @@ func eppPodReady(oldPodName string) func() bool {
8585

8686
// completionRoutedToNamespace sends one completion and reports an error on any
8787
// failure or namespace-header mismatch, for use inside Eventually blocks.
88-
func completionRoutedToNamespace() error {
88+
func completionRoutedToNamespace(nsName string) error {
8989
nsHdr, _, err := tryCompletion(simplePrompt, simModelName)
9090
if err != nil {
9191
return err
@@ -101,11 +101,13 @@ var _ = ginkgo.Describe("Disruption tests", ginkgo.Ordered, ginkgo.Label(disrupt
101101
ginkgo.It("should recover and route to surviving pods", func() {
102102
infPoolObjects = createInferencePool(1, true)
103103

104+
nsName := getNamespace()
105+
104106
modelServers := createModelServersDecode(2)
105-
ginkgo.DeferCleanup(testutils.DeleteObjects, testConfig, modelServers)
107+
ginkgo.DeferCleanup(testutils.DeleteObjects, testConfig, modelServers, nsName)
106108

107109
epp := createEndPointPicker(simpleConfig)
108-
ginkgo.DeferCleanup(testutils.DeleteObjects, testConfig, epp)
110+
ginkgo.DeferCleanup(testutils.DeleteObjects, testConfig, epp, nsName)
109111

110112
prefillPods, decodePods := getModelServerPods(podSelector, prefillSelector, decodeSelector)
111113
gomega.Expect(prefillPods).Should(gomega.BeEmpty())
@@ -145,7 +147,7 @@ var _ = ginkgo.Describe("Disruption tests", ginkgo.Ordered, ginkgo.Label(disrupt
145147
}, readyTimeout, 2*time.Second).Should(gomega.Equal(2))
146148

147149
ginkgo.By("Verifying requests succeed consistently after recovery")
148-
gomega.Eventually(completionRoutedToNamespace, eppRecoveryTimeout, 1*time.Second).
150+
gomega.Eventually(completionRoutedToNamespace, eppRecoveryTimeout, 1*time.Second).WithArguments(nsName).
149151
MustPassRepeatedly(3).Should(gomega.Succeed())
150152
})
151153
})
@@ -154,11 +156,13 @@ var _ = ginkgo.Describe("Disruption tests", ginkgo.Ordered, ginkgo.Label(disrupt
154156
ginkgo.It("should not hang and should recover routing", func() {
155157
infPoolObjects = createInferencePool(1, true)
156158

159+
nsName := getNamespace()
160+
157161
modelServers := createModelServersDecode(2)
158-
ginkgo.DeferCleanup(testutils.DeleteObjects, testConfig, modelServers)
162+
ginkgo.DeferCleanup(testutils.DeleteObjects, testConfig, modelServers, nsName)
159163

160164
epp := createEndPointPicker(simpleConfig)
161-
ginkgo.DeferCleanup(testutils.DeleteObjects, testConfig, epp)
165+
ginkgo.DeferCleanup(testutils.DeleteObjects, testConfig, epp, nsName)
162166

163167
prefillPods, decodePods := getModelServerPods(podSelector, prefillSelector, decodeSelector)
164168
gomega.Expect(prefillPods).Should(gomega.BeEmpty())
@@ -195,7 +199,7 @@ var _ = ginkgo.Describe("Disruption tests", ginkgo.Ordered, ginkgo.Label(disrupt
195199
}, readyTimeout, 2*time.Second).Should(gomega.Equal(2))
196200

197201
ginkgo.By("Verifying requests succeed consistently after recovery")
198-
gomega.Eventually(completionRoutedToNamespace, eppRecoveryTimeout, 1*time.Second).
202+
gomega.Eventually(completionRoutedToNamespace, eppRecoveryTimeout, 1*time.Second).WithArguments(nsName).
199203
MustPassRepeatedly(3).Should(gomega.Succeed())
200204
})
201205
})
@@ -204,11 +208,13 @@ var _ = ginkgo.Describe("Disruption tests", ginkgo.Ordered, ginkgo.Label(disrupt
204208
ginkgo.It("should return 503 to the client", func() {
205209
infPoolObjects = createInferencePool(1, true)
206210

211+
nsName := getNamespace()
212+
207213
modelServers := createModelServersDecode(1)
208-
ginkgo.DeferCleanup(testutils.DeleteObjects, testConfig, modelServers)
214+
ginkgo.DeferCleanup(testutils.DeleteObjects, testConfig, modelServers, nsName)
209215

210216
epp := createEndPointPicker(simpleConfig)
211-
ginkgo.DeferCleanup(testutils.DeleteObjects, testConfig, epp)
217+
ginkgo.DeferCleanup(testutils.DeleteObjects, testConfig, epp, nsName)
212218

213219
_, decodePods := getModelServerPods(podSelector, prefillSelector, decodeSelector)
214220
gomega.Expect(decodePods).Should(gomega.HaveLen(1))
@@ -218,7 +224,7 @@ var _ = ginkgo.Describe("Disruption tests", ginkgo.Ordered, ginkgo.Label(disrupt
218224
gomega.Expect(nsHdr).Should(gomega.Equal(nsName))
219225

220226
ginkgo.By("Scaling deployment to zero")
221-
scaleDeployment(modelServers, -1)
227+
scaleDeployment(nsName, modelServers, -1)
222228

223229
ginkgo.By("Waiting for all pods to be removed")
224230
gomega.Eventually(func() int {
@@ -236,7 +242,7 @@ var _ = ginkgo.Describe("Disruption tests", ginkgo.Ordered, ginkgo.Label(disrupt
236242
}, trafficProbeTimeout, 500*time.Millisecond).Should(gomega.Equal(http.StatusServiceUnavailable))
237243

238244
ginkgo.By("Scaling deployment back up")
239-
scaleDeployment(modelServers, 1)
245+
scaleDeployment(nsName, modelServers, 1)
240246

241247
ginkgo.By("Verifying requests succeed after recovery")
242248
gomega.Eventually(func() string {
@@ -250,15 +256,17 @@ var _ = ginkgo.Describe("Disruption tests", ginkgo.Ordered, ginkgo.Label(disrupt
250256
ginkgo.It("should recover and resume routing after restart", func() {
251257
infPoolObjects = createInferencePool(1, true)
252258

259+
nsName := getNamespace()
260+
253261
modelServers := createModelServersDecode(1)
254-
ginkgo.DeferCleanup(testutils.DeleteObjects, testConfig, modelServers)
262+
ginkgo.DeferCleanup(testutils.DeleteObjects, testConfig, modelServers, nsName)
255263

256264
epp := createEndPointPicker(simpleConfig)
257-
ginkgo.DeferCleanup(testutils.DeleteObjects, testConfig, epp)
265+
ginkgo.DeferCleanup(testutils.DeleteObjects, testConfig, epp, nsName)
258266

259267
ginkgo.By("Verifying requests succeed before EPP disruption")
260268
nsHdr, _, _ := runCompletion(simplePrompt, simModelName)
261-
gomega.Expect(nsHdr).Should(gomega.Equal(nsName))
269+
gomega.Expect(nsHdr).Should(gomega.Equal(getNamespace()))
262270

263271
ginkgo.By("Finding EPP pod")
264272
eppPods := getPods(map[string]string{"app": "e2e-epp"})
@@ -298,16 +306,17 @@ var _ = ginkgo.Describe("Disruption tests", ginkgo.Ordered, ginkgo.Label(disrupt
298306
ginkgo.When("Traffic is flowing during scale-to-zero and back", func() {
299307
ginkgo.It("should return 503s when empty and recover when scaled back", func() {
300308
infPoolObjects = createInferencePool(1, true)
309+
nsName := getNamespace()
301310

302311
modelServers := createModelServersDecode(1)
303-
ginkgo.DeferCleanup(testutils.DeleteObjects, testConfig, modelServers)
312+
ginkgo.DeferCleanup(testutils.DeleteObjects, testConfig, modelServers, nsName)
304313

305314
epp := createEndPointPicker(simpleConfig)
306-
ginkgo.DeferCleanup(testutils.DeleteObjects, testConfig, epp)
315+
ginkgo.DeferCleanup(testutils.DeleteObjects, testConfig, epp, nsName)
307316

308317
ginkgo.By("Verifying requests succeed before disruption")
309318
nsHdr, _, _ := runCompletion(simplePrompt, simModelName)
310-
gomega.Expect(nsHdr).Should(gomega.Equal(nsName))
319+
gomega.Expect(nsHdr).Should(gomega.Equal(getNamespace()))
311320

312321
ginkgo.By("Starting background traffic")
313322
ctx, cancel := context.WithCancel(context.Background())
@@ -319,7 +328,7 @@ var _ = ginkgo.Describe("Disruption tests", ginkgo.Ordered, ginkgo.Label(disrupt
319328
}()
320329

321330
ginkgo.By("Scaling to zero")
322-
scaleDeployment(modelServers, -1)
331+
scaleDeployment(nsName, modelServers, -1)
323332

324333
ginkgo.By("Waiting for all pods to be removed")
325334
gomega.Eventually(func() int {
@@ -331,7 +340,7 @@ var _ = ginkgo.Describe("Disruption tests", ginkgo.Ordered, ginkgo.Label(disrupt
331340
gomega.Eventually(tc.failures, trafficProbeTimeout, 500*time.Millisecond).Should(gomega.BeNumerically(">", 0))
332341

333342
ginkgo.By("Scaling back to 1")
334-
scaleDeployment(modelServers, 1)
343+
scaleDeployment(nsName, modelServers, 1)
335344

336345
ginkgo.By("Waiting for traffic to observe recovery")
337346
successBaseline := tc.successes()
@@ -351,7 +360,7 @@ var _ = ginkgo.Describe("Disruption tests", ginkgo.Ordered, ginkgo.Label(disrupt
351360
func sendStreamingCompletion(connected chan<- string) error {
352361
longPrompt := strings.Repeat("This is a longer prompt to keep the stream open. ", 20)
353362
body := fmt.Sprintf(`{"model":"%s","prompt":"%s","max_tokens":100,"stream":true}`, simModelName, longPrompt)
354-
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:%s/v1/completions", port), strings.NewReader(body))
363+
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:%d/v1/completions", getPort()), strings.NewReader(body))
355364
if err != nil {
356365
connected <- ""
357366
return err
@@ -418,7 +427,7 @@ func deletePodByName(podName string, gracePeriodSeconds int64) {
418427
pod := &corev1.Pod{
419428
ObjectMeta: metav1.ObjectMeta{
420429
Name: podName,
421-
Namespace: nsName,
430+
Namespace: getNamespace(),
422431
},
423432
}
424433
opts := &client.DeleteOptions{

0 commit comments

Comments
 (0)