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  }