Skip to content

Commit 2b7d14d

Browse files
committed
worked
1 parent 76e62d9 commit 2b7d14d

File tree

1 file changed

+259
-0
lines changed

1 file changed

+259
-0
lines changed

config/defaults_test.go

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
package config
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net"
7+
"sync"
8+
"testing"
9+
"time"
10+
11+
"google.golang.org/grpc"
12+
"google.golang.org/grpc/credentials/insecure"
13+
"google.golang.org/grpc/resolver"
14+
"google.golang.org/grpc/resolver/manual"
15+
"google.golang.org/protobuf/types/known/emptypb"
16+
)
17+
18+
// SimpleServiceServer - server interface
19+
type SimpleServiceServer interface {
20+
Ping(ctx context.Context, in *emptypb.Empty) (*emptypb.Empty, error)
21+
}
22+
23+
// Server implementation
24+
type simpleServer struct {
25+
delay time.Duration
26+
mu sync.Mutex
27+
dialCount int
28+
}
29+
30+
func (s *simpleServer) Ping(ctx context.Context, in *emptypb.Empty) (*emptypb.Empty, error) {
31+
if s.delay > 0 {
32+
time.Sleep(s.delay)
33+
}
34+
35+
return &emptypb.Empty{}, nil
36+
}
37+
38+
func (s *simpleServer) incrementDialCount() {
39+
s.mu.Lock()
40+
defer s.mu.Unlock()
41+
s.dialCount++
42+
}
43+
44+
func (s *simpleServer) getDialCount() int {
45+
s.mu.Lock()
46+
defer s.mu.Unlock()
47+
48+
return s.dialCount
49+
}
50+
51+
// RegisterSimpleServiceServer registers the server
52+
func RegisterSimpleServiceServer(s *grpc.Server, srv SimpleServiceServer) {
53+
s.RegisterService(&simpleServiceServiceDesc, srv)
54+
}
55+
56+
var simpleServiceServiceDesc = grpc.ServiceDesc{
57+
ServiceName: "SimpleService",
58+
HandlerType: (*SimpleServiceServer)(nil),
59+
Methods: []grpc.MethodDesc{
60+
{
61+
MethodName: "Ping",
62+
Handler: simpleServicePingHandler,
63+
},
64+
},
65+
Streams: []grpc.StreamDesc{},
66+
Metadata: "simple.proto",
67+
}
68+
69+
func simpleServicePingHandler(
70+
srv interface{},
71+
ctx context.Context,
72+
dec func(interface{}) error,
73+
interceptor grpc.UnaryServerInterceptor,
74+
) (interface{}, error) {
75+
in := new(emptypb.Empty)
76+
if err := dec(in); err != nil {
77+
return nil, err
78+
}
79+
if interceptor == nil {
80+
return srv.(SimpleServiceServer).Ping(ctx, in)
81+
}
82+
info := &grpc.UnaryServerInfo{
83+
Server: srv,
84+
FullMethod: "/SimpleService/Ping",
85+
}
86+
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
87+
return srv.(SimpleServiceServer).Ping(ctx, req.(*emptypb.Empty))
88+
}
89+
90+
return interceptor(ctx, in, info, handler)
91+
}
92+
93+
// SimpleServiceClient - client interface
94+
type SimpleServiceClient interface {
95+
Ping(
96+
ctx context.Context,
97+
in *emptypb.Empty,
98+
opts ...grpc.CallOption,
99+
) (*emptypb.Empty, error)
100+
}
101+
102+
type simpleServiceClient struct {
103+
cc grpc.ClientConnInterface
104+
}
105+
106+
func NewSimpleServiceClient(cc grpc.ClientConnInterface) SimpleServiceClient {
107+
return &simpleServiceClient{cc}
108+
}
109+
110+
func (c *simpleServiceClient) Ping(
111+
ctx context.Context,
112+
in *emptypb.Empty,
113+
opts ...grpc.CallOption,
114+
) (*emptypb.Empty, error) {
115+
out := new(emptypb.Empty)
116+
err := c.cc.Invoke(ctx, "/SimpleService/Ping", in, out, opts...)
117+
if err != nil {
118+
return nil, err
119+
}
120+
121+
return out, nil
122+
}
123+
124+
func TestGRPCLoadBalancingPolicies(t *testing.T) {
125+
// Start several real gRPC servers with different characteristics
126+
servers := make([]*simpleServer, 3)
127+
listeners := make([]net.Listener, 3)
128+
grpcServers := make([]*grpc.Server, 3)
129+
addresses := make([]string, 3)
130+
131+
// Create servers with different behaviors
132+
for i := 0; i < 3; i++ {
133+
// First server has a delay, others respond immediately
134+
delay := time.Duration(0)
135+
if i == 0 {
136+
delay = 500 * time.Millisecond
137+
}
138+
139+
servers[i] = &simpleServer{delay: delay}
140+
grpcServers[i] = grpc.NewServer()
141+
RegisterSimpleServiceServer(grpcServers[i], servers[i])
142+
143+
// Start the server on a random port
144+
lis, err := net.Listen("tcp", "localhost:0")
145+
if err != nil {
146+
t.Fatalf("Failed to create listener: %v", err)
147+
}
148+
listeners[i] = lis
149+
addresses[i] = lis.Addr().String()
150+
151+
go func(gs *grpc.Server, l net.Listener) {
152+
_ = gs.Serve(l)
153+
}(grpcServers[i], lis)
154+
}
155+
156+
// Cleanup after test
157+
defer func() {
158+
for i := 0; i < 3; i++ {
159+
if grpcServers[i] != nil {
160+
grpcServers[i].Stop()
161+
}
162+
if listeners[i] != nil {
163+
_ = listeners[i].Close()
164+
}
165+
}
166+
}()
167+
168+
// Create a manual resolver to control addresses
169+
r := manual.NewBuilderWithScheme("test")
170+
resolver.Register(r)
171+
172+
// Prepare addresses for the resolver
173+
addrs := make([]resolver.Address, 0, len(addresses))
174+
for _, addr := range addresses {
175+
addrs = append(addrs, resolver.Address{Addr: addr})
176+
}
177+
r.InitialState(resolver.State{Addresses: addrs})
178+
179+
// Test different load balancing policies
180+
tests := []struct {
181+
name string
182+
balancingPolicy string
183+
}{
184+
{"RoundRobin", "round_robin"},
185+
{"PickFirst", "pick_first"},
186+
}
187+
188+
for _, tc := range tests {
189+
t.Run(tc.name, func(t *testing.T) {
190+
// Monitor connection establishment time
191+
dialStart := time.Now()
192+
193+
// Create context with timeout for connection establishment
194+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
195+
defer cancel()
196+
197+
// #nosec G402 - Using insecure credentials is acceptable for testing
198+
// Establish connection with our balancing policy
199+
conn, err := grpc.DialContext(
200+
ctx,
201+
"test:///unused", // Address doesn't matter as we use manual resolver
202+
grpc.WithTransportCredentials(insecure.NewCredentials()),
203+
grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingPolicy": "%s"}`, tc.balancingPolicy)),
204+
grpc.WithBlock(), // Wait for connection establishment to complete
205+
)
206+
207+
dialDuration := time.Since(dialStart)
208+
209+
if err != nil {
210+
t.Fatalf("Failed to dial: %v", err)
211+
}
212+
defer conn.Close()
213+
214+
// Create client and make a request
215+
client := NewSimpleServiceClient(conn)
216+
_, err = client.Ping(context.Background(), &emptypb.Empty{})
217+
if err != nil {
218+
t.Fatalf("Ping failed: %v", err)
219+
}
220+
221+
// Analyze behavior based on balancing policy
222+
switch tc.balancingPolicy {
223+
case "round_robin":
224+
// For round_robin, we expect fast connection as it connects
225+
// to all servers in parallel and should quickly find working ones
226+
if dialDuration >= 400*time.Millisecond {
227+
t.Logf("round_robin dial took %v, expected less than 400ms", dialDuration)
228+
}
229+
230+
// Verify that requests execute successfully
231+
for i := 0; i < 10; i++ {
232+
_, err = client.Ping(context.Background(), &emptypb.Empty{})
233+
if err != nil {
234+
t.Fatalf("Ping failed: %v", err)
235+
}
236+
}
237+
238+
t.Logf("round_robin successfully established connection in %v", dialDuration)
239+
240+
case "pick_first":
241+
// For pick_first, connection time is important - if the first server is unavailable,
242+
// connection might take longer
243+
if servers[0].delay > 0 {
244+
t.Logf("pick_first dial took %v (expected to be affected by the delay)", dialDuration)
245+
}
246+
247+
// Verify that requests execute successfully
248+
for i := 0; i < 10; i++ {
249+
_, err = client.Ping(context.Background(), &emptypb.Empty{})
250+
if err != nil {
251+
t.Fatalf("Ping failed: %v", err)
252+
}
253+
}
254+
255+
t.Logf("pick_first successfully established connection in %v", dialDuration)
256+
}
257+
})
258+
}
259+
}

0 commit comments

Comments
 (0)