Skip to content

Commit 5e07fd2

Browse files
committed
add initial integration test for body-based routing extension
1 parent b40de04 commit 5e07fd2

File tree

2 files changed

+182
-12
lines changed

2 files changed

+182
-12
lines changed

pkg/body-based-routing/server/runserver.go

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ import (
3232

3333
// ExtProcServerRunner provides methods to manage an external process server.
3434
type ExtProcServerRunner struct {
35-
GrpcPort int
35+
GrpcPort int
36+
SecureServing bool
3637
}
3738

3839
// Default values for CLI flags in main
@@ -42,26 +43,29 @@ const (
4243

4344
func NewDefaultExtProcServerRunner() *ExtProcServerRunner {
4445
return &ExtProcServerRunner{
45-
GrpcPort: DefaultGrpcPort,
46+
GrpcPort: DefaultGrpcPort,
47+
SecureServing: true,
4648
}
4749
}
4850

4951
// AsRunnable returns a Runnable that can be used to start the ext-proc gRPC server.
5052
// The runnable implements LeaderElectionRunnable with leader election disabled.
5153
func (r *ExtProcServerRunner) AsRunnable(logger logr.Logger) manager.Runnable {
5254
return runnable.NoLeaderElection(manager.RunnableFunc(func(ctx context.Context) error {
53-
cert, err := tlsutil.CreateSelfSignedTLSCertificate(logger)
54-
if err != nil {
55-
logger.Error(err, "Failed to create self signed certificate")
56-
return err
55+
var srv *grpc.Server
56+
if r.SecureServing {
57+
cert, err := tlsutil.CreateSelfSignedTLSCertificate(logger)
58+
if err != nil {
59+
logger.Error(err, "Failed to create self signed certificate")
60+
return err
61+
}
62+
creds := credentials.NewTLS(&tls.Config{Certificates: []tls.Certificate{cert}})
63+
srv = grpc.NewServer(grpc.Creds(creds))
64+
} else {
65+
srv = grpc.NewServer()
5766
}
58-
creds := credentials.NewTLS(&tls.Config{Certificates: []tls.Certificate{cert}})
5967

60-
srv := grpc.NewServer(grpc.Creds(creds))
61-
extProcPb.RegisterExternalProcessorServer(
62-
srv,
63-
handlers.NewServer(),
64-
)
68+
extProcPb.RegisterExternalProcessorServer(srv, handlers.NewServer())
6569

6670
// Forward to the gRPC runnable.
6771
return runnable.GRPCServer("ext-proc", srv, r.GrpcPort).Start(ctx)

test/integration/bbr/hermetic_test.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package bbr contains integration tests for the body-based routing extension.
18+
package bbr
19+
20+
import (
21+
"context"
22+
"encoding/json"
23+
"fmt"
24+
"testing"
25+
"time"
26+
27+
configPb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
28+
extProcPb "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3"
29+
"github.com/go-logr/logr"
30+
"github.com/google/go-cmp/cmp"
31+
"google.golang.org/grpc"
32+
"google.golang.org/grpc/credentials/insecure"
33+
"google.golang.org/protobuf/testing/protocmp"
34+
runserver "sigs.k8s.io/gateway-api-inference-extension/pkg/body-based-routing/server"
35+
logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
36+
)
37+
38+
const port = runserver.DefaultGrpcPort
39+
40+
var (
41+
logger = logutil.NewTestLogger().V(logutil.VERBOSE)
42+
)
43+
44+
func TestBodyBasedRouting(t *testing.T) {
45+
tests := []struct {
46+
name string
47+
req *extProcPb.ProcessingRequest
48+
wantHeaders []*configPb.HeaderValueOption
49+
wantBody []byte
50+
wantErr bool
51+
}{
52+
{
53+
name: "success adding model parameter to header",
54+
req: generateRequest(logger, "llama"),
55+
wantHeaders: []*configPb.HeaderValueOption{
56+
{
57+
Header: &configPb.HeaderValue{
58+
Key: "X-Gateway-Model-Name",
59+
RawValue: []byte("llama"),
60+
},
61+
},
62+
},
63+
wantBody: []byte("{\"max_tokens\":100,\"model\":\"llama\",\"prompt\":\"test1\",\"temperature\":0}"),
64+
wantErr: false,
65+
},
66+
}
67+
68+
for _, test := range tests {
69+
t.Run(test.name, func(t *testing.T) {
70+
client, cleanup := setUpHermeticServer(t)
71+
t.Cleanup(cleanup)
72+
73+
want := &extProcPb.ProcessingResponse{
74+
Response: &extProcPb.ProcessingResponse_RequestBody{
75+
RequestBody: &extProcPb.BodyResponse{
76+
Response: &extProcPb.CommonResponse{
77+
HeaderMutation: &extProcPb.HeaderMutation{
78+
SetHeaders: test.wantHeaders,
79+
},
80+
ClearRouteCache: true,
81+
},
82+
},
83+
},
84+
}
85+
86+
res, err := sendRequest(t, client, test.req)
87+
if err != nil && !test.wantErr {
88+
t.Errorf("Unexpected error, got: %v, want error: %v", err, test.wantErr)
89+
}
90+
if diff := cmp.Diff(want, res, protocmp.Transform()); diff != "" {
91+
t.Errorf("Unexpected response, (-want +got): %v", diff)
92+
}
93+
})
94+
}
95+
}
96+
97+
func generateRequest(logger logr.Logger, model string) *extProcPb.ProcessingRequest {
98+
j := map[string]interface{}{
99+
"prompt": "test1",
100+
"max_tokens": 100,
101+
"temperature": 0,
102+
}
103+
if model != "" {
104+
j["model"] = model
105+
}
106+
107+
llmReq, err := json.Marshal(j)
108+
if err != nil {
109+
logutil.Fatal(logger, err, "Failed to unmarshal LLM request")
110+
}
111+
req := &extProcPb.ProcessingRequest{
112+
Request: &extProcPb.ProcessingRequest_RequestBody{
113+
RequestBody: &extProcPb.HttpBody{Body: llmReq},
114+
},
115+
}
116+
return req
117+
}
118+
119+
func setUpHermeticServer(t *testing.T) (client extProcPb.ExternalProcessor_ProcessClient, cleanup func()) {
120+
serverCtx, stopServer := context.WithCancel(context.Background())
121+
serverRunner := runserver.NewDefaultExtProcServerRunner()
122+
serverRunner.SecureServing = false
123+
124+
go func() {
125+
if err := serverRunner.AsRunnable(logger.WithName("ext-proc")).Start(serverCtx); err != nil {
126+
logutil.Fatal(logger, err, "Failed to start ext-proc server")
127+
}
128+
}()
129+
130+
address := fmt.Sprintf("localhost:%v", port)
131+
// Create a grpc connection
132+
conn, err := grpc.NewClient(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
133+
if err != nil {
134+
logutil.Fatal(logger, err, "Failed to connect", "address", address)
135+
}
136+
137+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
138+
client, err = extProcPb.NewExternalProcessorClient(conn).Process(ctx)
139+
if err != nil {
140+
logutil.Fatal(logger, err, "Failed to create client")
141+
}
142+
return client, func() {
143+
cancel()
144+
conn.Close()
145+
stopServer()
146+
147+
// wait a little until the goroutines actually exit
148+
time.Sleep(5 * time.Second)
149+
}
150+
}
151+
152+
func sendRequest(t *testing.T, client extProcPb.ExternalProcessor_ProcessClient, req *extProcPb.ProcessingRequest) (*extProcPb.ProcessingResponse, error) {
153+
t.Logf("Sending request: %v", req)
154+
if err := client.Send(req); err != nil {
155+
t.Logf("Failed to send request %+v: %v", req, err)
156+
return nil, err
157+
}
158+
159+
res, err := client.Recv()
160+
if err != nil {
161+
t.Logf("Failed to receive: %v", err)
162+
return nil, err
163+
}
164+
t.Logf("Received request %+v", res)
165+
return res, err
166+
}

0 commit comments

Comments
 (0)