Skip to content

Commit fcfaba0

Browse files
bpradiptclaude
andcommitted
Add comprehensive tests for vLLM deployment with Service
Added 6 test cases for the real-world vLLM deployment manifest that includes a headless Service and complex Deployment with probes, volumes, and resource limits. These tests validate: 1. Service-before-Deployment ordering (GetPrimaryManifest correctness) 2. Named port resolution (targetPort: http -> containerPort: 8000) 3. Headless service support (clusterIP: None) 4. Complex deployment structure handling 5. Existing runtimeClassName preservation 6. Existing annotations preservation Test cases: - TestManifestSet_vLLM_ServiceBeforeDeployment - TestManifestSet_vLLM_NamedPortResolution - TestManifestSet_vLLM_HeadlessService - TestManifestSet_vLLM_ComplexDeployment - TestManifestSet_vLLM_ExistingRuntimeClass - TestManifestSet_vLLM_ExistingAnnotations Co-Authored-By: Claude <[email protected]> Signed-off-by: Pradipta Banerjee <[email protected]>
1 parent 4031432 commit fcfaba0

File tree

2 files changed

+319
-0
lines changed

2 files changed

+319
-0
lines changed

integration_test/manifest_multidoc_test.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,192 @@ func TestManifestSet_GetServiceTargetPort_InvalidNamedPort(t *testing.T) {
280280
}
281281
}
282282

283+
func TestManifestSet_vLLM_ServiceBeforeDeployment(t *testing.T) {
284+
// Test with vLLM manifest where Service is defined BEFORE Deployment
285+
manifestSet, err := manifest.LoadMultiDocument("testdata/manifests/deployment-with-service-vllm.yaml")
286+
if err != nil {
287+
t.Fatalf("LoadMultiDocument() failed: %v", err)
288+
}
289+
290+
manifests := manifestSet.GetManifests()
291+
if len(manifests) != 2 {
292+
t.Fatalf("Expected 2 manifests, got %d", len(manifests))
293+
}
294+
295+
// Verify primary manifest is Deployment (not Service, even though Service comes first)
296+
primary := manifestSet.GetPrimaryManifest()
297+
if primary == nil {
298+
t.Fatal("GetPrimaryManifest() returned nil")
299+
}
300+
if primary.GetKind() != "Deployment" {
301+
t.Errorf("Primary manifest kind = %q, want %q", primary.GetKind(), "Deployment")
302+
}
303+
if primary.GetName() != "vllm" {
304+
t.Errorf("Primary manifest name = %q, want %q", primary.GetName(), "vllm")
305+
}
306+
307+
// Verify service manifest exists
308+
service := manifestSet.GetServiceManifest()
309+
if service == nil {
310+
t.Fatal("GetServiceManifest() returned nil")
311+
}
312+
if service.GetKind() != "Service" {
313+
t.Errorf("Service manifest kind = %q, want %q", service.GetKind(), "Service")
314+
}
315+
if service.GetName() != "vllm" {
316+
t.Errorf("Service manifest name = %q, want %q", service.GetName(), "vllm")
317+
}
318+
}
319+
320+
func TestManifestSet_vLLM_NamedPortResolution(t *testing.T) {
321+
// Test named port resolution with vLLM manifest
322+
// Service has targetPort: http
323+
// Deployment has container with port name: http, containerPort: 8000
324+
manifestSet, err := manifest.LoadMultiDocument("testdata/manifests/deployment-with-service-vllm.yaml")
325+
if err != nil {
326+
t.Fatalf("LoadMultiDocument() failed: %v", err)
327+
}
328+
329+
port, err := manifestSet.GetServiceTargetPort()
330+
if err != nil {
331+
t.Fatalf("GetServiceTargetPort() failed: %v", err)
332+
}
333+
334+
expectedPort := 8000
335+
if port != expectedPort {
336+
t.Errorf("GetServiceTargetPort() = %d, want %d (resolved from named port 'http')", port, expectedPort)
337+
}
338+
}
339+
340+
func TestManifestSet_vLLM_HeadlessService(t *testing.T) {
341+
// Test that headless service (clusterIP: None) still works for port detection
342+
manifestSet, err := manifest.LoadMultiDocument("testdata/manifests/deployment-with-service-vllm.yaml")
343+
if err != nil {
344+
t.Fatalf("LoadMultiDocument() failed: %v", err)
345+
}
346+
347+
service := manifestSet.GetServiceManifest()
348+
if service == nil {
349+
t.Fatal("GetServiceManifest() returned nil")
350+
}
351+
352+
// Verify it's a headless service
353+
spec, err := service.GetSpec()
354+
if err != nil {
355+
t.Fatalf("Failed to get service spec: %v", err)
356+
}
357+
358+
clusterIP, ok := spec["clusterIP"].(string)
359+
if !ok {
360+
t.Fatal("clusterIP not found in service spec")
361+
}
362+
if clusterIP != "None" {
363+
t.Errorf("Expected headless service (clusterIP: None), got clusterIP: %s", clusterIP)
364+
}
365+
366+
// Port detection should still work for headless services
367+
port, err := manifestSet.GetServiceTargetPort()
368+
if err != nil {
369+
t.Fatalf("GetServiceTargetPort() failed for headless service: %v", err)
370+
}
371+
if port != 8000 {
372+
t.Errorf("GetServiceTargetPort() = %d, want 8000", port)
373+
}
374+
}
375+
376+
func TestManifestSet_vLLM_ComplexDeployment(t *testing.T) {
377+
// Test with a complex deployment that has probes, volumes, resource limits
378+
manifestSet, err := manifest.LoadMultiDocument("testdata/manifests/deployment-with-service-vllm.yaml")
379+
if err != nil {
380+
t.Fatalf("LoadMultiDocument() failed: %v", err)
381+
}
382+
383+
primary := manifestSet.GetPrimaryManifest()
384+
if primary == nil {
385+
t.Fatal("GetPrimaryManifest() returned nil")
386+
}
387+
388+
// Verify the deployment has the expected structure
389+
podSpec, err := primary.GetPodSpec()
390+
if err != nil {
391+
t.Fatalf("GetPodSpec() failed: %v", err)
392+
}
393+
394+
// Check containers exist
395+
containers, ok := podSpec["containers"].([]interface{})
396+
if !ok || len(containers) == 0 {
397+
t.Fatal("No containers found in pod spec")
398+
}
399+
400+
// Verify first container has the named port
401+
firstContainer, ok := containers[0].(map[string]interface{})
402+
if !ok {
403+
t.Fatal("Invalid container structure")
404+
}
405+
406+
ports, ok := firstContainer["ports"].([]interface{})
407+
if !ok || len(ports) == 0 {
408+
t.Fatal("No ports found in container")
409+
}
410+
411+
firstPort, ok := ports[0].(map[string]interface{})
412+
if !ok {
413+
t.Fatal("Invalid port structure")
414+
}
415+
416+
portName, ok := firstPort["name"].(string)
417+
if !ok || portName != "http" {
418+
t.Errorf("Expected port name 'http', got %q", portName)
419+
}
420+
421+
// Verify port detection works despite the complexity
422+
port, err := manifestSet.GetServiceTargetPort()
423+
if err != nil {
424+
t.Fatalf("GetServiceTargetPort() failed: %v", err)
425+
}
426+
if port != 8000 {
427+
t.Errorf("GetServiceTargetPort() = %d, want 8000", port)
428+
}
429+
}
430+
431+
func TestManifestSet_vLLM_ExistingRuntimeClass(t *testing.T) {
432+
// Test that existing runtimeClassName is preserved
433+
manifestSet, err := manifest.LoadMultiDocument("testdata/manifests/deployment-with-service-vllm.yaml")
434+
if err != nil {
435+
t.Fatalf("LoadMultiDocument() failed: %v", err)
436+
}
437+
438+
primary := manifestSet.GetPrimaryManifest()
439+
if primary == nil {
440+
t.Fatal("GetPrimaryManifest() returned nil")
441+
}
442+
443+
// Check existing runtimeClassName
444+
runtimeClass := primary.GetRuntimeClass()
445+
if runtimeClass != "kata-remote" {
446+
t.Errorf("GetRuntimeClass() = %q, want %q", runtimeClass, "kata-remote")
447+
}
448+
}
449+
450+
func TestManifestSet_vLLM_ExistingAnnotations(t *testing.T) {
451+
// Test that existing annotations are preserved
452+
manifestSet, err := manifest.LoadMultiDocument("testdata/manifests/deployment-with-service-vllm.yaml")
453+
if err != nil {
454+
t.Fatalf("LoadMultiDocument() failed: %v", err)
455+
}
456+
457+
primary := manifestSet.GetPrimaryManifest()
458+
if primary == nil {
459+
t.Fatal("GetPrimaryManifest() returned nil")
460+
}
461+
462+
// Check existing annotation
463+
timeoutAnnotation := primary.GetAnnotation("io.katacontainers.config.runtime.create_container_timeout")
464+
if timeoutAnnotation != "900" {
465+
t.Errorf("Expected timeout annotation '900', got %q", timeoutAnnotation)
466+
}
467+
}
468+
283469
// Helper function to write files in tests
284470
func writeFile(path, content string) error {
285471
return os.WriteFile(path, []byte(content), 0600)
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
labels:
5+
app: vllm
6+
component: vllm
7+
name: vllm
8+
spec:
9+
clusterIP: None
10+
ipFamilies:
11+
- IPv4
12+
ipFamilyPolicy: SingleStack
13+
ports:
14+
- name: http
15+
port: 8000
16+
protocol: TCP
17+
targetPort: http
18+
selector:
19+
app: vllm
20+
component: vllm
21+
sessionAffinity: None
22+
type: ClusterIP
23+
---
24+
apiVersion: apps/v1
25+
kind: Deployment
26+
metadata:
27+
labels:
28+
app: vllm
29+
component: vllm
30+
name: vllm
31+
spec:
32+
progressDeadlineSeconds: 600
33+
replicas: 1
34+
revisionHistoryLimit: 10
35+
selector:
36+
matchLabels:
37+
app: vllm
38+
component: vllm
39+
strategy:
40+
type: Recreate
41+
template:
42+
metadata:
43+
annotations:
44+
io.katacontainers.config.runtime.create_container_timeout: "900"
45+
#io.katacontainers.config.hypervisor.machine_type: Standard_DC8es_v5
46+
#io.katacontainers.config.hypervisor.machine_type: Standard_NCC40ads_H100_v5
47+
creationTimestamp: null
48+
labels:
49+
app: vllm
50+
component: vllm
51+
spec:
52+
runtimeClassName: kata-remote
53+
affinity: {}
54+
containers:
55+
- args:
56+
- --model
57+
- TinyLlama/TinyLlama-1.1B-Chat-v1.0
58+
- --download-dir
59+
- /models-cache
60+
- --max-model-len
61+
- "2048"
62+
env:
63+
- name: HUGGING_FACE_HUB_TOKEN
64+
value: ""
65+
image: vllm/vllm-openai:latest
66+
imagePullPolicy: IfNotPresent
67+
livenessProbe:
68+
failureThreshold: 3
69+
httpGet:
70+
path: /health
71+
port: http
72+
scheme: HTTP
73+
periodSeconds: 100
74+
successThreshold: 1
75+
timeoutSeconds: 8
76+
name: server
77+
ports:
78+
- containerPort: 8000
79+
name: http
80+
protocol: TCP
81+
readinessProbe:
82+
failureThreshold: 3
83+
httpGet:
84+
path: /health
85+
port: http
86+
scheme: HTTP
87+
periodSeconds: 30
88+
successThreshold: 1
89+
timeoutSeconds: 5
90+
resources:
91+
limits:
92+
cpu: "2"
93+
memory: 8Gi
94+
requests:
95+
cpu: "1"
96+
memory: 4Gi
97+
securityContext:
98+
allowPrivilegeEscalation: false
99+
capabilities:
100+
drop:
101+
- ALL
102+
runAsNonRoot: true
103+
seccompProfile:
104+
type: RuntimeDefault
105+
startupProbe:
106+
failureThreshold: 24
107+
httpGet:
108+
path: /health
109+
port: http
110+
scheme: HTTP
111+
periodSeconds: 30
112+
successThreshold: 1
113+
timeoutSeconds: 1
114+
terminationMessagePath: /dev/termination-log
115+
terminationMessagePolicy: File
116+
volumeMounts:
117+
- mountPath: /dev/shm
118+
name: shm
119+
- mountPath: /models-cache
120+
name: models-cache
121+
dnsPolicy: ClusterFirst
122+
restartPolicy: Always
123+
schedulerName: default-scheduler
124+
securityContext: {}
125+
terminationGracePeriodSeconds: 120
126+
volumes:
127+
- emptyDir:
128+
medium: Memory
129+
sizeLimit: 1Gi
130+
name: shm
131+
- emptyDir:
132+
sizeLimit: 10Gi
133+
name: models-cache

0 commit comments

Comments
 (0)