Skip to content

Commit 3e440a9

Browse files
authored
Failover unit test for DNS with unavailable IP addresses (#1840)
1 parent 8e56585 commit 3e440a9

File tree

1 file changed

+96
-0
lines changed

1 file changed

+96
-0
lines changed

internal/balancer/balancer_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package balancer
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net"
7+
"sync/atomic"
8+
"testing"
9+
"time"
10+
11+
"github.com/stretchr/testify/require"
12+
"google.golang.org/grpc"
13+
"google.golang.org/grpc/resolver"
14+
"google.golang.org/grpc/test/bufconn"
15+
16+
"github.com/ydb-platform/ydb-go-sdk/v3/config"
17+
)
18+
19+
func TestBalancer_discoveryConn(t *testing.T) {
20+
// testTimeout defines the test timeout and is an example of an actual user-defined timeout.
21+
//
22+
// I couldn't find any events for synchronization, assuming that there
23+
// might be retries with different logic and their own timeouts inside `discoveryConn`.
24+
// If not now, then in the future. One second excludes false test failures in case
25+
// the test is run on very slow workers. Moreover, one second is only lost if the test fails,
26+
// and in that case, losing it is not critical. Upon successful completion of the test,
27+
// the context is canceled via `cancel()`.
28+
const testTimeout = 1 * time.Second
29+
30+
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
31+
defer cancel()
32+
33+
fakeListener := bufconn.Listen(1024 * 1024)
34+
defer fakeListener.Close()
35+
36+
fakeServer := grpc.NewServer()
37+
defer fakeServer.Stop()
38+
39+
go func() {
40+
_ = fakeServer.Serve(fakeListener)
41+
}()
42+
43+
var dialAttempt atomic.Uint32
44+
45+
balancer := &Balancer{
46+
address: "ydbmock:///mock",
47+
driverConfig: config.New(
48+
config.WithEndpoint("mock"),
49+
config.WithGrpcOptions(
50+
grpc.WithResolvers(&mockResolverBuilder{}),
51+
52+
grpc.WithContextDialer(
53+
// The first dialing is never ended, while the subsequent ones work fine.
54+
func(ctx context.Context, s string) (net.Conn, error) {
55+
if dialAttempt.Add(1) == 1 {
56+
<-ctx.Done() // dial will never complete successfully
57+
58+
return nil, fmt.Errorf("fake error for endpoint: %s: %w", s, ctx.Err())
59+
}
60+
61+
return fakeListener.DialContext(ctx)
62+
}),
63+
64+
// If you want to reproduce the issue, uncomment the line:
65+
// grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "pick_first"}`),
66+
),
67+
),
68+
}
69+
70+
_, err := balancer.discoveryConn(ctx)
71+
require.NoError(t, err)
72+
}
73+
74+
// Mock resolver
75+
//
76+
77+
type mockResolverBuilder struct{}
78+
79+
func (r *mockResolverBuilder) Build(_ resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (
80+
resolver.Resolver, error,
81+
) {
82+
state := resolver.State{Addresses: []resolver.Address{
83+
{Addr: "mockaddress1"},
84+
{Addr: "mockaddress2"},
85+
}}
86+
_ = cc.UpdateState(state)
87+
88+
return &mockResolver{}, nil
89+
}
90+
91+
func (r *mockResolverBuilder) Scheme() string { return "ydbmock" }
92+
93+
type mockResolver struct{}
94+
95+
func (r *mockResolver) ResolveNow(resolver.ResolveNowOptions) {}
96+
func (r *mockResolver) Close() {}

0 commit comments

Comments
 (0)