github.com/lfch/etcd-io/tests/v3@v3.0.0-20221004140520-eac99acd3e9d/integration/clientv3/connectivity/server_shutdown_test.go (about) 1 // Copyright 2017 The etcd Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package connectivity_test 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "testing" 22 "time" 23 24 "github.com/lfch/etcd-io/api/v3/v3rpc/rpctypes" 25 "github.com/lfch/etcd-io/client/v3" 26 integration2 "github.com/lfch/etcd-io/tests/v3/framework/integration" 27 "github.com/lfch/etcd-io/tests/v3/integration/clientv3" 28 ) 29 30 // TestBalancerUnderServerShutdownWatch expects that watch client 31 // switch its endpoints when the member of the pinned endpoint fails. 32 func TestBalancerUnderServerShutdownWatch(t *testing.T) { 33 integration2.BeforeTest(t) 34 35 clus := integration2.NewCluster(t, &integration2.ClusterConfig{ 36 Size: 3, 37 UseBridge: true, 38 }) 39 defer clus.Terminate(t) 40 41 eps := []string{clus.Members[0].GRPCURL(), clus.Members[1].GRPCURL(), clus.Members[2].GRPCURL()} 42 43 lead := clus.WaitLeader(t) 44 45 // pin eps[lead] 46 watchCli, err := integration2.NewClient(t, clientv3.Config{Endpoints: []string{eps[lead]}}) 47 if err != nil { 48 t.Fatal(err) 49 } 50 defer watchCli.Close() 51 52 // wait for eps[lead] to be pinned 53 clientv3test.MustWaitPinReady(t, watchCli) 54 55 // add all eps to list, so that when the original pined one fails 56 // the client can switch to other available eps 57 watchCli.SetEndpoints(eps...) 58 59 key, val := "foo", "bar" 60 wch := watchCli.Watch(context.Background(), key, clientv3.WithCreatedNotify()) 61 select { 62 case <-wch: 63 case <-time.After(integration2.RequestWaitTimeout): 64 t.Fatal("took too long to create watch") 65 } 66 67 donec := make(chan struct{}) 68 go func() { 69 defer close(donec) 70 71 // switch to others when eps[lead] is shut down 72 select { 73 case ev := <-wch: 74 if werr := ev.Err(); werr != nil { 75 t.Error(werr) 76 } 77 if len(ev.Events) != 1 { 78 t.Errorf("expected one event, got %+v", ev) 79 } 80 if !bytes.Equal(ev.Events[0].Kv.Value, []byte(val)) { 81 t.Errorf("expected %q, got %+v", val, ev.Events[0].Kv) 82 } 83 case <-time.After(7 * time.Second): 84 t.Error("took too long to receive events") 85 } 86 }() 87 88 // shut down eps[lead] 89 clus.Members[lead].Terminate(t) 90 91 // writes to eps[lead+1] 92 putCli, err := integration2.NewClient(t, clientv3.Config{Endpoints: []string{eps[(lead+1)%3]}}) 93 if err != nil { 94 t.Fatal(err) 95 } 96 defer putCli.Close() 97 for { 98 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 99 _, err = putCli.Put(ctx, key, val) 100 cancel() 101 if err == nil { 102 break 103 } 104 if clientv3test.IsClientTimeout(err) || clientv3test.IsServerCtxTimeout(err) || err == rpctypes.ErrTimeout || err == rpctypes.ErrTimeoutDueToLeaderFail { 105 continue 106 } 107 t.Fatal(err) 108 } 109 110 select { 111 case <-donec: 112 case <-time.After(5 * time.Second): // enough time for balancer switch 113 t.Fatal("took too long to receive events") 114 } 115 } 116 117 func TestBalancerUnderServerShutdownPut(t *testing.T) { 118 testBalancerUnderServerShutdownMutable(t, func(cli *clientv3.Client, ctx context.Context) error { 119 _, err := cli.Put(ctx, "foo", "bar") 120 return err 121 }) 122 } 123 124 func TestBalancerUnderServerShutdownDelete(t *testing.T) { 125 testBalancerUnderServerShutdownMutable(t, func(cli *clientv3.Client, ctx context.Context) error { 126 _, err := cli.Delete(ctx, "foo") 127 return err 128 }) 129 } 130 131 func TestBalancerUnderServerShutdownTxn(t *testing.T) { 132 testBalancerUnderServerShutdownMutable(t, func(cli *clientv3.Client, ctx context.Context) error { 133 _, err := cli.Txn(ctx). 134 If(clientv3.Compare(clientv3.Version("foo"), "=", 0)). 135 Then(clientv3.OpPut("foo", "bar")). 136 Else(clientv3.OpPut("foo", "baz")).Commit() 137 return err 138 }) 139 } 140 141 // testBalancerUnderServerShutdownMutable expects that when the member of 142 // the pinned endpoint is shut down, the balancer switches its endpoints 143 // and all subsequent put/delete/txn requests succeed with new endpoints. 144 func testBalancerUnderServerShutdownMutable(t *testing.T, op func(*clientv3.Client, context.Context) error) { 145 integration2.BeforeTest(t) 146 147 clus := integration2.NewCluster(t, &integration2.ClusterConfig{ 148 Size: 3, 149 }) 150 defer clus.Terminate(t) 151 152 eps := []string{clus.Members[0].GRPCURL(), clus.Members[1].GRPCURL(), clus.Members[2].GRPCURL()} 153 154 // pin eps[0] 155 cli, err := integration2.NewClient(t, clientv3.Config{Endpoints: []string{eps[0]}}) 156 if err != nil { 157 t.Fatal(err) 158 } 159 defer cli.Close() 160 161 // wait for eps[0] to be pinned 162 clientv3test.MustWaitPinReady(t, cli) 163 164 // add all eps to list, so that when the original pined one fails 165 // the client can switch to other available eps 166 cli.SetEndpoints(eps...) 167 168 // shut down eps[0] 169 clus.Members[0].Terminate(t) 170 171 // switched to others when eps[0] was explicitly shut down 172 // and following request should succeed 173 // TODO: remove this (expose client connection state?) 174 time.Sleep(time.Second) 175 176 cctx, ccancel := context.WithTimeout(context.Background(), time.Second) 177 err = op(cli, cctx) 178 ccancel() 179 if err != nil { 180 t.Fatal(err) 181 } 182 } 183 184 func TestBalancerUnderServerShutdownGetLinearizable(t *testing.T) { 185 testBalancerUnderServerShutdownImmutable(t, func(cli *clientv3.Client, ctx context.Context) error { 186 _, err := cli.Get(ctx, "foo") 187 return err 188 }, 7*time.Second) // give enough time for leader election, balancer switch 189 } 190 191 func TestBalancerUnderServerShutdownGetSerializable(t *testing.T) { 192 testBalancerUnderServerShutdownImmutable(t, func(cli *clientv3.Client, ctx context.Context) error { 193 _, err := cli.Get(ctx, "foo", clientv3.WithSerializable()) 194 return err 195 }, 2*time.Second) 196 } 197 198 // testBalancerUnderServerShutdownImmutable expects that when the member of 199 // the pinned endpoint is shut down, the balancer switches its endpoints 200 // and all subsequent range requests succeed with new endpoints. 201 func testBalancerUnderServerShutdownImmutable(t *testing.T, op func(*clientv3.Client, context.Context) error, timeout time.Duration) { 202 integration2.BeforeTest(t) 203 204 clus := integration2.NewCluster(t, &integration2.ClusterConfig{ 205 Size: 3, 206 }) 207 defer clus.Terminate(t) 208 209 eps := []string{clus.Members[0].GRPCURL(), clus.Members[1].GRPCURL(), clus.Members[2].GRPCURL()} 210 211 // pin eps[0] 212 cli, err := integration2.NewClient(t, clientv3.Config{Endpoints: []string{eps[0]}}) 213 if err != nil { 214 t.Errorf("failed to create client: %v", err) 215 } 216 defer cli.Close() 217 218 // wait for eps[0] to be pinned 219 clientv3test.MustWaitPinReady(t, cli) 220 221 // add all eps to list, so that when the original pined one fails 222 // the client can switch to other available eps 223 cli.SetEndpoints(eps...) 224 225 // shut down eps[0] 226 clus.Members[0].Terminate(t) 227 228 // switched to others when eps[0] was explicitly shut down 229 // and following request should succeed 230 cctx, ccancel := context.WithTimeout(context.Background(), timeout) 231 err = op(cli, cctx) 232 ccancel() 233 if err != nil { 234 t.Errorf("failed to finish range request in time %v (timeout %v)", err, timeout) 235 } 236 } 237 238 func TestBalancerUnderServerStopInflightLinearizableGetOnRestart(t *testing.T) { 239 tt := []pinTestOpt{ 240 {pinLeader: true, stopPinFirst: true}, 241 {pinLeader: true, stopPinFirst: false}, 242 {pinLeader: false, stopPinFirst: true}, 243 {pinLeader: false, stopPinFirst: false}, 244 } 245 for _, w := range tt { 246 t.Run(fmt.Sprintf("%#v", w), func(t *testing.T) { 247 testBalancerUnderServerStopInflightRangeOnRestart(t, true, w) 248 }) 249 } 250 } 251 252 func TestBalancerUnderServerStopInflightSerializableGetOnRestart(t *testing.T) { 253 tt := []pinTestOpt{ 254 {pinLeader: true, stopPinFirst: true}, 255 {pinLeader: true, stopPinFirst: false}, 256 {pinLeader: false, stopPinFirst: true}, 257 {pinLeader: false, stopPinFirst: false}, 258 } 259 for _, w := range tt { 260 t.Run(fmt.Sprintf("%#v", w), func(t *testing.T) { 261 testBalancerUnderServerStopInflightRangeOnRestart(t, false, w) 262 }) 263 } 264 } 265 266 type pinTestOpt struct { 267 pinLeader bool 268 stopPinFirst bool 269 } 270 271 // testBalancerUnderServerStopInflightRangeOnRestart expects 272 // inflight range request reconnects on server restart. 273 func testBalancerUnderServerStopInflightRangeOnRestart(t *testing.T, linearizable bool, opt pinTestOpt) { 274 integration2.BeforeTest(t) 275 276 cfg := &integration2.ClusterConfig{ 277 Size: 2, 278 UseBridge: true, 279 } 280 if linearizable { 281 cfg.Size = 3 282 } 283 284 clus := integration2.NewCluster(t, cfg) 285 defer clus.Terminate(t) 286 eps := []string{clus.Members[0].GRPCURL(), clus.Members[1].GRPCURL()} 287 if linearizable { 288 eps = append(eps, clus.Members[2].GRPCURL()) 289 } 290 291 lead := clus.WaitLeader(t) 292 293 target := lead 294 if !opt.pinLeader { 295 target = (target + 1) % 2 296 } 297 298 // pin eps[target] 299 cli, err := integration2.NewClient(t, clientv3.Config{Endpoints: []string{eps[target]}}) 300 if err != nil { 301 t.Errorf("failed to create client: %v", err) 302 } 303 defer cli.Close() 304 305 // wait for eps[target] to be pinned 306 clientv3test.MustWaitPinReady(t, cli) 307 308 // add all eps to list, so that when the original pined one fails 309 // the client can switch to other available eps 310 cli.SetEndpoints(eps...) 311 312 if opt.stopPinFirst { 313 clus.Members[target].Stop(t) 314 // give some time for balancer switch before stopping the other 315 time.Sleep(time.Second) 316 clus.Members[(target+1)%2].Stop(t) 317 } else { 318 clus.Members[(target+1)%2].Stop(t) 319 // balancer cannot pin other member since it's already stopped 320 clus.Members[target].Stop(t) 321 } 322 323 // 3-second is the minimum interval between endpoint being marked 324 // as unhealthy and being removed from unhealthy, so possibly 325 // takes >5-second to unpin and repin an endpoint 326 // TODO: decrease timeout when balancer switch rewrite 327 clientTimeout := 7 * time.Second 328 329 var gops []clientv3.OpOption 330 if !linearizable { 331 gops = append(gops, clientv3.WithSerializable()) 332 } 333 334 donec, readyc := make(chan struct{}), make(chan struct{}, 1) 335 go func() { 336 defer close(donec) 337 ctx, cancel := context.WithTimeout(context.TODO(), clientTimeout) 338 readyc <- struct{}{} 339 340 // TODO: The new grpc load balancer will not pin to an endpoint 341 // as intended by this test. But it will round robin member within 342 // two attempts. 343 // Remove retry loop once the new grpc load balancer provides retry. 344 for i := 0; i < 2; i++ { 345 _, err = cli.Get(ctx, "abc", gops...) 346 if err == nil { 347 break 348 } 349 } 350 cancel() 351 if err != nil { 352 t.Errorf("unexpected error: %v", err) 353 } 354 }() 355 356 <-readyc 357 clus.Members[target].Restart(t) 358 359 select { 360 case <-time.After(clientTimeout + integration2.RequestWaitTimeout): 361 t.Fatalf("timed out waiting for Get [linearizable: %v, opt: %+v]", linearizable, opt) 362 case <-donec: 363 } 364 }