Skip to content

Commit 836ea52

Browse files
authored
chore: Added end to end tests (#310)
* Added dependencies for tests Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Updated Makefile and aded a shell script to launch the tests Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Added manifests for various K8S objects for the tests Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Added utility functions for the tests Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Added code for the tests Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Corrected lint errors Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Corrected lint errors Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Insure that make unit-test does not run the e2e-tests Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Flattened go package structure and addressed review comments Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Addressed review comments Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Removed tput commands, fails in CI Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Updated due to Go package change Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Use IGW helper function to get duration from env var Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Correctly load sidecar image using tag from env var Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Load EPP image using tag from env var Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Use ginkgo.Eventually to determine if EPP is serving Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Added extra intermediate status logging Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> * Enable specifying tag for all used images, pulling if necessary Signed-off-by: Shmuel Kallner <kallner@il.ibm.com> --------- Signed-off-by: Shmuel Kallner <kallner@il.ibm.com>
1 parent f947c78 commit 836ea52

File tree

15 files changed

+1445
-7
lines changed

15 files changed

+1445
-7
lines changed

Makefile

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,17 @@ format: ## Format Go source files
6868
@gofmt -l -w $(SRC)
6969

7070
.PHONY: test
71-
test: test-unit
71+
test: test-unit test-e2e
7272

7373
.PHONY: test-unit
7474
test-unit: download-tokenizer download-zmq
7575
@printf "\033[33;1m==== Running Unit Tests ====\033[0m\n"
76-
go test -ldflags="$(LDFLAGS)" -v ./...
76+
go test -ldflags="$(LDFLAGS)" -v $$(echo $$(go list ./... | grep -v /test/))
77+
78+
.PHONY: test-e2e
79+
test-e2e: image-build
80+
@printf "\033[33;1m==== Running End to End Tests ====\033[0m\n"
81+
./test/scripts/run_e2e.sh
7782

7883
.PHONY: test-integration
7984
test-integration: download-tokenizer download-zmq

go.mod

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@ require (
1010
github.com/google/go-cmp v0.7.0
1111
github.com/jellydator/ttlcache/v3 v3.4.0
1212
github.com/llm-d/llm-d-kv-cache-manager v0.2.1
13+
github.com/onsi/ginkgo/v2 v2.23.4
14+
github.com/onsi/gomega v1.37.0
15+
github.com/openai/openai-go v1.12.0
1316
github.com/stretchr/testify v1.11.0
17+
google.golang.org/grpc v1.73.0
18+
k8s.io/api v0.33.4
19+
k8s.io/apiextensions-apiserver v0.33.2
1420
k8s.io/apimachinery v0.33.4
1521
k8s.io/client-go v0.33.4
1622
sigs.k8s.io/controller-runtime v0.21.0
@@ -60,8 +66,6 @@ require (
6066
github.com/modern-go/reflect2 v1.0.2 // indirect
6167
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
6268
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
63-
github.com/onsi/ginkgo/v2 v2.23.4 // indirect
64-
github.com/onsi/gomega v1.37.0 // indirect
6569
github.com/pebbe/zmq4 v1.4.0 // indirect
6670
github.com/pkg/errors v0.9.1 // indirect
6771
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
@@ -74,6 +78,10 @@ require (
7478
github.com/spf13/cobra v1.9.1 // indirect
7579
github.com/spf13/pflag v1.0.6 // indirect
7680
github.com/stoewer/go-strcase v1.3.0 // indirect
81+
github.com/tidwall/gjson v1.14.4 // indirect
82+
github.com/tidwall/match v1.1.1 // indirect
83+
github.com/tidwall/pretty v1.2.1 // indirect
84+
github.com/tidwall/sjson v1.2.5 // indirect
7785
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
7886
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
7987
github.com/x448/float16 v0.8.4 // indirect
@@ -103,13 +111,10 @@ require (
103111
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
104112
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect
105113
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
106-
google.golang.org/grpc v1.73.0 // indirect
107114
google.golang.org/protobuf v1.36.6 // indirect
108115
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
109116
gopkg.in/inf.v0 v0.9.1 // indirect
110117
gopkg.in/yaml.v3 v3.0.1 // indirect
111-
k8s.io/api v0.33.4 // indirect
112-
k8s.io/apiextensions-apiserver v0.33.2 // indirect
113118
k8s.io/apiserver v0.33.2 // indirect
114119
k8s.io/component-base v0.33.2 // indirect
115120
k8s.io/klog/v2 v2.130.1 // indirect

go.sum

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus
123123
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
124124
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
125125
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
126+
github.com/openai/openai-go v1.12.0 h1:NBQCnXzqOTv5wsgNC36PrFEiskGfO5wccfCWDo9S1U0=
127+
github.com/openai/openai-go v1.12.0/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
126128
github.com/pebbe/zmq4 v1.4.0 h1:gO5P92Ayl8GXpPZdYcD62Cwbq0slSBVVQRIXwGSJ6eQ=
127129
github.com/pebbe/zmq4 v1.4.0/go.mod h1:nqnPueOapVhE2wItZ0uOErngczsJdLOGkebMxaO8r48=
128130
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -164,6 +166,16 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
164166
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
165167
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
166168
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
169+
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
170+
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
171+
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
172+
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
173+
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
174+
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
175+
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
176+
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
177+
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
178+
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
167179
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
168180
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
169181
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=

test/e2e/e2e_suite_test.go

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package e2e
2+
3+
import (
4+
"context"
5+
"io"
6+
"os/exec"
7+
"strings"
8+
"testing"
9+
"time"
10+
11+
"github.com/onsi/ginkgo/v2"
12+
"github.com/onsi/gomega"
13+
"github.com/onsi/gomega/gexec"
14+
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
15+
"k8s.io/apimachinery/pkg/runtime"
16+
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
17+
"sigs.k8s.io/controller-runtime/pkg/client"
18+
"sigs.k8s.io/controller-runtime/pkg/client/config"
19+
k8slog "sigs.k8s.io/controller-runtime/pkg/log"
20+
infextv1a2 "sigs.k8s.io/gateway-api-inference-extension/api/v1alpha2"
21+
"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/env"
22+
)
23+
24+
const (
25+
// defaultExistsTimeout is the default timeout for a resource to exist in the api server.
26+
defaultExistsTimeout = 30 * time.Second
27+
// defaultReadyTimeout is the default timeout for a resource to report a ready state.
28+
defaultReadyTimeout = 3 * time.Minute
29+
// defaultModelReadyTimeout is the default timeout for the model server deployment to report a ready state.
30+
defaultModelReadyTimeout = 10 * time.Minute
31+
// defaultInterval is the default interval to check if a resource exists or ready conditions.
32+
defaultInterval = time.Millisecond * 250
33+
// xInferPoolManifest is the manifest for the inference pool CRD with 'inference.networking.x-k8s.io' group.
34+
gieCrdsKustomize = "../../deploy/components/crds-gie"
35+
// inferExtManifest is the manifest for the inference extension test resources.
36+
inferExtManifest = "./yaml/inference-pools.yaml"
37+
// modelName is the test model name.
38+
modelName = "food-review"
39+
// kvModelName is the model name used in KV tests.
40+
kvModelName = "Qwen/Qwen2.5-1.5B-Instruct"
41+
// safeKvModelName is the safe form of the model name used in KV tests
42+
safeKvModelName = "qwen-qwen2-5-1-5b-instruct"
43+
// envoyManifest is the manifest for the envoy proxy test resources.
44+
envoyManifest = "./yaml/envoy.yaml"
45+
// eppManifest is the manifest for the deployment of the EPP
46+
eppManifest = "./yaml/deployments.yaml"
47+
// rbacManifest is the manifest for the EPP's RBAC resources.
48+
rbacManifest = "./yaml/rbac.yaml"
49+
// serviceAccountManifest is the manifest for the EPP's service account resources.
50+
serviceAccountManifest = "./yaml/service-accounts.yaml"
51+
// servicesManifest is the manifest for the EPP's service resources.
52+
servicesManifest = "./yaml/services.yaml"
53+
// nsName is the namespace in which the K8S objects will be created
54+
nsName = "default"
55+
)
56+
57+
var (
58+
ctx = context.Background()
59+
k8sClient client.Client
60+
port string
61+
scheme = runtime.NewScheme()
62+
63+
eppTag = env.GetEnvString("EPP_TAG", "dev", ginkgo.GinkgoLogr)
64+
vllmSimTag = env.GetEnvString("VLLM_SIMULATOR_TAG", "dev", ginkgo.GinkgoLogr)
65+
routingSideCarTag = env.GetEnvString("ROUTING_SIDECAR_TAG", "v0.2.0", ginkgo.GinkgoLogr)
66+
67+
existsTimeout = env.GetEnvDuration("EXISTS_TIMEOUT", defaultExistsTimeout, ginkgo.GinkgoLogr)
68+
readyTimeout = env.GetEnvDuration("READY_TIMEOUT", defaultReadyTimeout, ginkgo.GinkgoLogr)
69+
modelReadyTimeout = env.GetEnvDuration("MODEL_READY_TIMEOUT", defaultModelReadyTimeout, ginkgo.GinkgoLogr)
70+
interval = defaultInterval
71+
)
72+
73+
func TestEndToEnd(t *testing.T) {
74+
gomega.RegisterFailHandler(ginkgo.Fail)
75+
ginkgo.RunSpecs(t,
76+
"End To End Test Suite",
77+
)
78+
}
79+
80+
var _ = ginkgo.BeforeSuite(func() {
81+
port = "30080"
82+
83+
setupK8sCluster()
84+
setupK8sClient()
85+
createCRDs()
86+
createEnvoy()
87+
applyYAMLFile(rbacManifest)
88+
applyYAMLFile(serviceAccountManifest)
89+
applyYAMLFile(servicesManifest)
90+
91+
infPoolYaml := readYaml(inferExtManifest)
92+
infPoolYaml = substituteMany(infPoolYaml, map[string]string{"${POOL_NAME}": modelName + "-inference-pool"})
93+
createObjsFromYaml(infPoolYaml)
94+
})
95+
96+
var _ = ginkgo.AfterSuite(func() {
97+
command := exec.Command("kind", "delete", "cluster", "--name", "e2e-tests")
98+
session, err := gexec.Start(command, ginkgo.GinkgoWriter, ginkgo.GinkgoWriter)
99+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
100+
gomega.Eventually(session).WithTimeout(600 * time.Second).Should(gexec.Exit(0))
101+
})
102+
103+
// Create the Kubernetes cluster for the E2E tests and load the local images
104+
func setupK8sCluster() {
105+
command := exec.Command("kind", "create", "cluster", "--name", "e2e-tests", "--config", "-")
106+
stdin, err := command.StdinPipe()
107+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
108+
go func() {
109+
defer func() {
110+
err := stdin.Close()
111+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
112+
}()
113+
clusterConfig := strings.ReplaceAll(kindClusterConfig, "${PORT}", port)
114+
_, err := io.WriteString(stdin, clusterConfig)
115+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
116+
}()
117+
session, err := gexec.Start(command, ginkgo.GinkgoWriter, ginkgo.GinkgoWriter)
118+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
119+
gomega.Eventually(session).WithTimeout(600 * time.Second).Should(gexec.Exit(0))
120+
121+
command = exec.Command("kind", "--name", "e2e-tests", "load", "docker-image",
122+
"ghcr.io/llm-d/llm-d-inference-sim:"+vllmSimTag)
123+
session, err = gexec.Start(command, ginkgo.GinkgoWriter, ginkgo.GinkgoWriter)
124+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
125+
gomega.Eventually(session).WithTimeout(600 * time.Second).Should(gexec.Exit(0))
126+
127+
command = exec.Command("kind", "--name", "e2e-tests", "load", "docker-image",
128+
"ghcr.io/llm-d/llm-d-inference-scheduler:"+eppTag)
129+
session, err = gexec.Start(command, ginkgo.GinkgoWriter, ginkgo.GinkgoWriter)
130+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
131+
gomega.Eventually(session).WithTimeout(600 * time.Second).Should(gexec.Exit(0))
132+
133+
command = exec.Command("kind", "--name", "e2e-tests", "load", "docker-image",
134+
"ghcr.io/llm-d/llm-d-routing-sidecar:"+routingSideCarTag)
135+
session, err = gexec.Start(command, ginkgo.GinkgoWriter, ginkgo.GinkgoWriter)
136+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
137+
gomega.Eventually(session).WithTimeout(600 * time.Second).Should(gexec.Exit(0))
138+
}
139+
140+
func setupK8sClient() {
141+
k8sCfg := config.GetConfigOrDie()
142+
gomega.ExpectWithOffset(1, k8sCfg).NotTo(gomega.BeNil())
143+
144+
err := clientgoscheme.AddToScheme(scheme)
145+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
146+
147+
err = apiextv1.AddToScheme(scheme)
148+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
149+
150+
err = infextv1a2.Install(scheme)
151+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
152+
153+
k8sClient, err = client.New(k8sCfg, client.Options{Scheme: scheme})
154+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
155+
gomega.Expect(k8sClient).NotTo(gomega.BeNil())
156+
157+
k8slog.SetLogger(ginkgo.GinkgoLogr)
158+
}
159+
160+
// createCRDs creates the Inference Extension CRDs used for testing.
161+
func createCRDs() {
162+
crds := runKustomize(gieCrdsKustomize)
163+
createObjsFromYaml(crds)
164+
}
165+
166+
func createEnvoy() {
167+
manifests := readYaml(envoyManifest)
168+
ginkgo.By("Creating envoy proxy resources from manifest: " + envoyManifest)
169+
createObjsFromYaml(manifests)
170+
}
171+
172+
const kindClusterConfig = `
173+
kind: Cluster
174+
apiVersion: kind.x-k8s.io/v1alpha4
175+
nodes:
176+
- extraPortMappings:
177+
- containerPort: 30080
178+
hostPort: ${PORT}
179+
protocol: TCP
180+
- containerPort: 30081
181+
hostPort: 30081
182+
protocol: TCP
183+
`

0 commit comments

Comments
 (0)