Skip to content

Commit 7d3703a

Browse files
committed
update
Signed-off-by: bitliu <[email protected]>
1 parent e554406 commit 7d3703a

File tree

4 files changed

+399
-380
lines changed

4 files changed

+399
-380
lines changed

e2e/pkg/helpers/kubernetes.go

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
package helpers
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
"os"
9+
"strings"
10+
"time"
11+
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/client-go/kubernetes"
14+
"k8s.io/client-go/rest"
15+
"k8s.io/client-go/tools/portforward"
16+
"k8s.io/client-go/transport/spdy"
17+
)
18+
19+
// CheckDeployment checks if a deployment is healthy (ready replicas > 0)
20+
func CheckDeployment(ctx context.Context, client *kubernetes.Clientset, namespace, name string, verbose bool) error {
21+
deployment, err := client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{})
22+
if err != nil {
23+
return fmt.Errorf("failed to get deployment: %w", err)
24+
}
25+
26+
if deployment.Status.ReadyReplicas == 0 {
27+
return fmt.Errorf("deployment has 0 ready replicas")
28+
}
29+
30+
if verbose {
31+
fmt.Printf("[Helper] Deployment %s/%s is healthy (%d/%d replicas ready)\n",
32+
namespace, name, deployment.Status.ReadyReplicas, deployment.Status.Replicas)
33+
}
34+
35+
return nil
36+
}
37+
38+
// GetEnvoyServiceName finds the Envoy service name in the envoy-gateway-system namespace
39+
func GetEnvoyServiceName(ctx context.Context, client *kubernetes.Clientset, verbose bool) (string, error) {
40+
services, err := client.CoreV1().Services("envoy-gateway-system").List(ctx, metav1.ListOptions{})
41+
if err != nil {
42+
return "", fmt.Errorf("failed to list services: %w", err)
43+
}
44+
45+
for _, svc := range services.Items {
46+
if strings.Contains(svc.Name, "envoy-gateway") && svc.Spec.Type == "LoadBalancer" {
47+
if verbose {
48+
fmt.Printf("[Helper] Found Envoy service: %s\n", svc.Name)
49+
}
50+
return svc.Name, nil
51+
}
52+
}
53+
54+
return "", fmt.Errorf("no Envoy LoadBalancer service found")
55+
}
56+
57+
// VerifyServicePodsRunning verifies that exactly 1 pod exists for a service and it's running with all containers ready
58+
func VerifyServicePodsRunning(ctx context.Context, client *kubernetes.Clientset, namespace, serviceName string, verbose bool) error {
59+
// Get the service
60+
svc, err := client.CoreV1().Services(namespace).Get(ctx, serviceName, metav1.GetOptions{})
61+
if err != nil {
62+
return fmt.Errorf("failed to get service: %w", err)
63+
}
64+
65+
// Build label selector from service selector
66+
var selectorParts []string
67+
for key, value := range svc.Spec.Selector {
68+
selectorParts = append(selectorParts, fmt.Sprintf("%s=%s", key, value))
69+
}
70+
labelSelector := strings.Join(selectorParts, ",")
71+
72+
// List pods matching the selector
73+
pods, err := client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
74+
LabelSelector: labelSelector,
75+
})
76+
if err != nil {
77+
return fmt.Errorf("failed to list pods: %w", err)
78+
}
79+
80+
// Verify exactly 1 pod exists
81+
if len(pods.Items) != 1 {
82+
return fmt.Errorf("expected exactly 1 pod for service %s/%s, but found %d pods", namespace, serviceName, len(pods.Items))
83+
}
84+
85+
// Check if all pods are running and ready
86+
runningCount := 0
87+
for _, pod := range pods.Items {
88+
if pod.Status.Phase == "Running" {
89+
// Also check if all containers are ready
90+
allContainersReady := true
91+
for _, containerStatus := range pod.Status.ContainerStatuses {
92+
if !containerStatus.Ready {
93+
allContainersReady = false
94+
break
95+
}
96+
}
97+
if allContainersReady {
98+
runningCount++
99+
}
100+
}
101+
}
102+
103+
// All pods must be running and ready (and we already verified count is 1)
104+
if runningCount != len(pods.Items) {
105+
return fmt.Errorf("not all pods are running for service %s/%s: %d/%d pods ready", namespace, serviceName, runningCount, len(pods.Items))
106+
}
107+
108+
if verbose {
109+
fmt.Printf("[Helper] Service %s/%s has all %d pod(s) running and ready\n",
110+
namespace, serviceName, len(pods.Items))
111+
}
112+
113+
return nil
114+
}
115+
116+
// StartPortForward starts port forwarding to a service
117+
func StartPortForward(ctx context.Context, client *kubernetes.Clientset, restConfig *rest.Config, namespace, service, ports string, verbose bool) error {
118+
// Parse ports (e.g., "8080:80" -> local=8080, remote=80)
119+
portParts := strings.Split(ports, ":")
120+
if len(portParts) != 2 {
121+
return fmt.Errorf("invalid port format: %s (expected format: localPort:remotePort)", ports)
122+
}
123+
localPort := portParts[0]
124+
remotePort := portParts[1]
125+
126+
// Get the service to find a pod
127+
svc, err := client.CoreV1().Services(namespace).Get(ctx, service, metav1.GetOptions{})
128+
if err != nil {
129+
return fmt.Errorf("failed to get service: %w", err)
130+
}
131+
132+
// Get pods matching the service selector
133+
var selectorParts []string
134+
for key, value := range svc.Spec.Selector {
135+
selectorParts = append(selectorParts, fmt.Sprintf("%s=%s", key, value))
136+
}
137+
labelSelector := strings.Join(selectorParts, ",")
138+
139+
pods, err := client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
140+
LabelSelector: labelSelector,
141+
FieldSelector: "status.phase=Running",
142+
})
143+
if err != nil {
144+
return fmt.Errorf("failed to list pods: %w", err)
145+
}
146+
147+
if len(pods.Items) == 0 {
148+
return fmt.Errorf("no running pods found for service %s", service)
149+
}
150+
151+
podName := pods.Items[0].Name
152+
153+
if verbose {
154+
fmt.Printf("[Helper] Starting port-forward to pod %s/%s (%s:%s)\n", namespace, podName, localPort, remotePort)
155+
}
156+
157+
// Create SPDY transport
158+
transport, upgrader, err := spdy.RoundTripperFor(restConfig)
159+
if err != nil {
160+
return fmt.Errorf("failed to create SPDY transport: %w", err)
161+
}
162+
163+
// Build the URL for port forwarding
164+
url := client.CoreV1().RESTClient().Post().
165+
Resource("pods").
166+
Namespace(namespace).
167+
Name(podName).
168+
SubResource("portforward").
169+
URL()
170+
171+
// Create dialer
172+
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", url)
173+
174+
// Setup channels
175+
stopChan := make(chan struct{}, 1)
176+
readyChan := make(chan struct{})
177+
out := io.Discard
178+
errOut := io.Discard
179+
if verbose {
180+
out = os.Stdout
181+
errOut = os.Stderr
182+
}
183+
184+
// Create port forwarder
185+
forwarder, err := portforward.New(dialer, []string{fmt.Sprintf("%s:%s", localPort, remotePort)}, stopChan, readyChan, out, errOut)
186+
if err != nil {
187+
return fmt.Errorf("failed to create port forwarder: %w", err)
188+
}
189+
190+
// Start port forwarding in background
191+
go func() {
192+
if err := forwarder.ForwardPorts(); err != nil {
193+
if verbose {
194+
fmt.Printf("[Helper] Port forwarding error: %v\n", err)
195+
}
196+
}
197+
}()
198+
199+
// Wait for ready or timeout
200+
select {
201+
case <-readyChan:
202+
if verbose {
203+
fmt.Printf("[Helper] Port forwarding is ready\n")
204+
}
205+
return nil
206+
case <-time.After(30 * time.Second):
207+
close(stopChan)
208+
return fmt.Errorf("timeout waiting for port forward to be ready")
209+
case <-ctx.Done():
210+
close(stopChan)
211+
return ctx.Err()
212+
}
213+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package aigateway
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"k8s.io/client-go/kubernetes"
8+
9+
"github.com/vllm-project/semantic-router/e2e/pkg/helpers"
10+
"github.com/vllm-project/semantic-router/e2e/pkg/testcases"
11+
)
12+
13+
func init() {
14+
testcases.Register("basic-health-check", testcases.TestCase{
15+
Description: "Verify that all components are deployed and healthy",
16+
Tags: []string{"ai-gateway", "health"},
17+
Fn: testBasicHealthCheck,
18+
})
19+
}
20+
21+
func testBasicHealthCheck(ctx context.Context, client *kubernetes.Clientset, opts testcases.TestCaseOptions) error {
22+
if opts.Verbose {
23+
fmt.Println("[Test] Running basic health check")
24+
}
25+
26+
// Check semantic-router deployment
27+
if err := helpers.CheckDeployment(ctx, client, "vllm-semantic-router-system", "semantic-router", opts.Verbose); err != nil {
28+
return fmt.Errorf("semantic-router deployment not healthy: %w", err)
29+
}
30+
31+
// Check envoy-gateway deployment
32+
if err := helpers.CheckDeployment(ctx, client, "envoy-gateway-system", "envoy-gateway", opts.Verbose); err != nil {
33+
return fmt.Errorf("envoy-gateway deployment not healthy: %w", err)
34+
}
35+
36+
// Check ai-gateway-controller deployment
37+
if err := helpers.CheckDeployment(ctx, client, "envoy-ai-gateway-system", "ai-gateway-controller", opts.Verbose); err != nil {
38+
return fmt.Errorf("ai-gateway-controller deployment not healthy: %w", err)
39+
}
40+
41+
if opts.Verbose {
42+
fmt.Println("[Test] ✅ All deployments are healthy")
43+
}
44+
45+
return nil
46+
}
47+

0 commit comments

Comments
 (0)