1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package etcdserver
16
17 import (
18 "net/http"
19 "testing"
20 "time"
21
22 "go.uber.org/zap"
23 "go.uber.org/zap/zaptest"
24
25 pb "go.etcd.io/etcd/api/v3/etcdserverpb"
26 "go.etcd.io/etcd/client/pkg/v3/types"
27 "go.etcd.io/etcd/raft/v3/raftpb"
28 "go.etcd.io/etcd/server/v3/etcdserver/api/membership"
29 "go.etcd.io/etcd/server/v3/etcdserver/api/rafthttp"
30 "go.etcd.io/etcd/server/v3/etcdserver/api/snap"
31 )
32
33 func TestLongestConnected(t *testing.T) {
34 umap, err := types.NewURLsMap("mem1=http://10.1:2379,mem2=http://10.2:2379,mem3=http://10.3:2379")
35 if err != nil {
36 t.Fatal(err)
37 }
38 clus, err := membership.NewClusterFromURLsMap(zap.NewExample(), "test", umap)
39 if err != nil {
40 t.Fatal(err)
41 }
42 memberIDs := clus.MemberIDs()
43
44 tr := newNopTransporterWithActiveTime(memberIDs)
45 transferee, ok := longestConnected(tr, memberIDs)
46 if !ok {
47 t.Fatalf("unexpected ok %v", ok)
48 }
49 if memberIDs[0] != transferee {
50 t.Fatalf("expected first member %s to be transferee, got %s", memberIDs[0], transferee)
51 }
52
53
54 amap := make(map[types.ID]time.Time)
55 for _, id := range memberIDs {
56 amap[id] = time.Time{}
57 }
58 tr.(*nopTransporterWithActiveTime).reset(amap)
59
60 _, ok2 := longestConnected(tr, memberIDs)
61 if ok2 {
62 t.Fatalf("unexpected ok %v", ok)
63 }
64 }
65
66 type nopTransporterWithActiveTime struct {
67 activeMap map[types.ID]time.Time
68 }
69
70
71
72 func newNopTransporterWithActiveTime(memberIDs []types.ID) rafthttp.Transporter {
73 am := make(map[types.ID]time.Time)
74 for i, id := range memberIDs {
75 am[id] = time.Now().Add(time.Duration(i) * time.Second)
76 }
77 return &nopTransporterWithActiveTime{activeMap: am}
78 }
79
80 func (s *nopTransporterWithActiveTime) Start() error { return nil }
81 func (s *nopTransporterWithActiveTime) Handler() http.Handler { return nil }
82 func (s *nopTransporterWithActiveTime) Send(m []raftpb.Message) {}
83 func (s *nopTransporterWithActiveTime) SendSnapshot(m snap.Message) {}
84 func (s *nopTransporterWithActiveTime) AddRemote(id types.ID, us []string) {}
85 func (s *nopTransporterWithActiveTime) AddPeer(id types.ID, us []string) {}
86 func (s *nopTransporterWithActiveTime) RemovePeer(id types.ID) {}
87 func (s *nopTransporterWithActiveTime) RemoveAllPeers() {}
88 func (s *nopTransporterWithActiveTime) UpdatePeer(id types.ID, us []string) {}
89 func (s *nopTransporterWithActiveTime) ActiveSince(id types.ID) time.Time { return s.activeMap[id] }
90 func (s *nopTransporterWithActiveTime) ActivePeers() int { return 0 }
91 func (s *nopTransporterWithActiveTime) Stop() {}
92 func (s *nopTransporterWithActiveTime) Pause() {}
93 func (s *nopTransporterWithActiveTime) Resume() {}
94 func (s *nopTransporterWithActiveTime) reset(am map[types.ID]time.Time) { s.activeMap = am }
95
96 func TestPanicAlternativeStringer(t *testing.T) {
97 p := panicAlternativeStringer{alternative: func() string { return "alternative" }}
98
99 p.stringer = testStringerFunc(func() string { panic("here") })
100 if s := p.String(); s != "alternative" {
101 t.Fatalf("expected 'alternative', got %q", s)
102 }
103
104 p.stringer = testStringerFunc(func() string { return "test" })
105 if s := p.String(); s != "test" {
106 t.Fatalf("expected 'test', got %q", s)
107 }
108 }
109
110 type testStringerFunc func() string
111
112 func (s testStringerFunc) String() string {
113 return s()
114 }
115
116
117
118 func TestWarnOfExpensiveReadOnlyTxnRequest(t *testing.T) {
119 testCases := []struct {
120 name string
121 txnResp *pb.TxnResponse
122 }{
123 {
124 name: "all readonly responses",
125 txnResp: &pb.TxnResponse{
126 Responses: []*pb.ResponseOp{
127 {
128 Response: &pb.ResponseOp_ResponseRange{
129 ResponseRange: &pb.RangeResponse{},
130 },
131 },
132 {
133 Response: &pb.ResponseOp_ResponseRange{
134 ResponseRange: &pb.RangeResponse{},
135 },
136 },
137 },
138 },
139 },
140 {
141 name: "all readonly responses with partial nil responses",
142 txnResp: &pb.TxnResponse{
143 Responses: []*pb.ResponseOp{
144 {
145 Response: &pb.ResponseOp_ResponseRange{
146 ResponseRange: &pb.RangeResponse{},
147 },
148 },
149 {
150 Response: &pb.ResponseOp_ResponseRange{
151 ResponseRange: nil,
152 },
153 },
154 },
155 },
156 },
157 {
158 name: "all readonly responses with all nil responses",
159 txnResp: &pb.TxnResponse{
160 Responses: []*pb.ResponseOp{
161 {
162 Response: &pb.ResponseOp_ResponseRange{
163 ResponseRange: nil,
164 },
165 },
166 {
167 Response: &pb.ResponseOp_ResponseRange{
168 ResponseRange: nil,
169 },
170 },
171 },
172 },
173 },
174 {
175 name: "partial non readonly responses",
176 txnResp: &pb.TxnResponse{
177 Responses: []*pb.ResponseOp{
178 {
179 Response: &pb.ResponseOp_ResponseRange{
180 ResponseRange: nil,
181 },
182 },
183 {
184 Response: &pb.ResponseOp_ResponsePut{},
185 },
186 {
187 Response: &pb.ResponseOp_ResponseDeleteRange{},
188 },
189 },
190 },
191 },
192 {
193 name: "all non readonly responses",
194 txnResp: &pb.TxnResponse{
195 Responses: []*pb.ResponseOp{
196 {
197 Response: &pb.ResponseOp_ResponsePut{},
198 },
199 {
200 Response: &pb.ResponseOp_ResponseDeleteRange{},
201 },
202 },
203 },
204 },
205 }
206
207 for _, tc := range testCases {
208 tc := tc
209 t.Run(tc.name, func(t *testing.T) {
210 lg := zaptest.NewLogger(t)
211 start := time.Now().Add(-1 * time.Second)
212
213 warnOfExpensiveReadOnlyTxnRequest(lg, 0, start, &pb.TxnRequest{}, tc.txnResp, nil)
214 })
215 }
216 }
217
View as plain text