github.com/lfch/etcd-io/tests/v3@v3.0.0-20221004140520-eac99acd3e9d/integration/clientv3/maintenance_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 clientv3test
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"io"
    22  	"math"
    23  	"path/filepath"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/lfch/etcd-io/server/v3/storage/mvcc/testutil"
    28  	integration2 "github.com/lfch/etcd-io/tests/v3/framework/integration"
    29  	"go.uber.org/zap/zaptest"
    30  	"google.golang.org/grpc"
    31  
    32  	"github.com/lfch/etcd-io/api/v3/v3rpc/rpctypes"
    33  	"github.com/lfch/etcd-io/api/v3/version"
    34  	"github.com/lfch/etcd-io/client/v3"
    35  	"github.com/lfch/etcd-io/server/v3/lease"
    36  	"github.com/lfch/etcd-io/server/v3/storage/backend"
    37  	"github.com/lfch/etcd-io/server/v3/storage/mvcc"
    38  )
    39  
    40  func TestMaintenanceHashKV(t *testing.T) {
    41  	integration2.BeforeTest(t)
    42  
    43  	clus := integration2.NewCluster(t, &integration2.ClusterConfig{Size: 3})
    44  	defer clus.Terminate(t)
    45  
    46  	for i := 0; i < 3; i++ {
    47  		if _, err := clus.RandClient().Put(context.Background(), "foo", "bar"); err != nil {
    48  			t.Fatal(err)
    49  		}
    50  	}
    51  
    52  	var hv uint32
    53  	for i := 0; i < 3; i++ {
    54  		cli := clus.Client(i)
    55  		// ensure writes are replicated
    56  		if _, err := cli.Get(context.TODO(), "foo"); err != nil {
    57  			t.Fatal(err)
    58  		}
    59  		hresp, err := cli.HashKV(context.Background(), clus.Members[i].GRPCURL(), 0)
    60  		if err != nil {
    61  			t.Fatal(err)
    62  		}
    63  		if hv == 0 {
    64  			hv = hresp.Hash
    65  			continue
    66  		}
    67  		if hv != hresp.Hash {
    68  			t.Fatalf("#%d: hash expected %d, got %d", i, hv, hresp.Hash)
    69  		}
    70  	}
    71  }
    72  
    73  // TODO: Change this to fuzz test
    74  func TestCompactionHash(t *testing.T) {
    75  	integration2.BeforeTest(t)
    76  
    77  	clus := integration2.NewCluster(t, &integration2.ClusterConfig{Size: 1})
    78  	defer clus.Terminate(t)
    79  
    80  	cc, err := clus.ClusterClient(t)
    81  	if err != nil {
    82  		t.Fatal(err)
    83  	}
    84  
    85  	testutil.TestCompactionHash(context.Background(), t, hashTestCase{cc, clus.Members[0].GRPCURL()}, 1000)
    86  }
    87  
    88  type hashTestCase struct {
    89  	*clientv3.Client
    90  	url string
    91  }
    92  
    93  func (tc hashTestCase) Put(ctx context.Context, key, value string) error {
    94  	_, err := tc.Client.Put(ctx, key, value)
    95  	return err
    96  }
    97  
    98  func (tc hashTestCase) Delete(ctx context.Context, key string) error {
    99  	_, err := tc.Client.Delete(ctx, key)
   100  	return err
   101  }
   102  
   103  func (tc hashTestCase) HashByRev(ctx context.Context, rev int64) (testutil.KeyValueHash, error) {
   104  	resp, err := tc.Client.HashKV(ctx, tc.url, rev)
   105  	return testutil.KeyValueHash{Hash: resp.Hash, CompactRevision: resp.CompactRevision, Revision: resp.Header.Revision}, err
   106  }
   107  
   108  func (tc hashTestCase) Defrag(ctx context.Context) error {
   109  	_, err := tc.Client.Defragment(ctx, tc.url)
   110  	return err
   111  }
   112  
   113  func (tc hashTestCase) Compact(ctx context.Context, rev int64) error {
   114  	_, err := tc.Client.Compact(ctx, rev)
   115  	// Wait for compaction to be compacted
   116  	time.Sleep(50 * time.Millisecond)
   117  	return err
   118  }
   119  
   120  func TestMaintenanceMoveLeader(t *testing.T) {
   121  	integration2.BeforeTest(t)
   122  
   123  	clus := integration2.NewCluster(t, &integration2.ClusterConfig{Size: 3})
   124  	defer clus.Terminate(t)
   125  
   126  	oldLeadIdx := clus.WaitLeader(t)
   127  	targetIdx := (oldLeadIdx + 1) % 3
   128  	target := uint64(clus.Members[targetIdx].ID())
   129  
   130  	cli := clus.Client(targetIdx)
   131  	_, err := cli.MoveLeader(context.Background(), target)
   132  	if err != rpctypes.ErrNotLeader {
   133  		t.Fatalf("error expected %v, got %v", rpctypes.ErrNotLeader, err)
   134  	}
   135  
   136  	cli = clus.Client(oldLeadIdx)
   137  	_, err = cli.MoveLeader(context.Background(), target)
   138  	if err != nil {
   139  		t.Fatal(err)
   140  	}
   141  
   142  	leadIdx := clus.WaitLeader(t)
   143  	lead := uint64(clus.Members[leadIdx].ID())
   144  	if target != lead {
   145  		t.Fatalf("new leader expected %d, got %d", target, lead)
   146  	}
   147  }
   148  
   149  // TestMaintenanceSnapshotCancel ensures that context cancel
   150  // before snapshot reading returns corresponding context errors.
   151  func TestMaintenanceSnapshotCancel(t *testing.T) {
   152  	integration2.BeforeTest(t)
   153  
   154  	clus := integration2.NewCluster(t, &integration2.ClusterConfig{Size: 1})
   155  	defer clus.Terminate(t)
   156  
   157  	// reading snapshot with canceled context should error out
   158  	ctx, cancel := context.WithCancel(context.Background())
   159  	rc1, err := clus.RandClient().Snapshot(ctx)
   160  	if err != nil {
   161  		t.Fatal(err)
   162  	}
   163  	defer rc1.Close()
   164  
   165  	cancel()
   166  	_, err = io.Copy(io.Discard, rc1)
   167  	if err != context.Canceled {
   168  		t.Errorf("expected %v, got %v", context.Canceled, err)
   169  	}
   170  }
   171  
   172  // TestMaintenanceSnapshotWithVersionTimeout ensures that SnapshotWithVersion function
   173  // returns corresponding context errors when context timeout happened before snapshot reading
   174  func TestMaintenanceSnapshotWithVersionTimeout(t *testing.T) {
   175  	testMaintenanceSnapshotTimeout(t, func(ctx context.Context, client *clientv3.Client) (io.ReadCloser, error) {
   176  		resp, err := client.SnapshotWithVersion(ctx)
   177  		if err != nil {
   178  			return nil, err
   179  		}
   180  		return resp.Snapshot, nil
   181  	})
   182  }
   183  
   184  // TestMaintenanceSnapshotTimeout ensures that Snapshot function
   185  // returns corresponding context errors when context timeout happened before snapshot reading
   186  func TestMaintenanceSnapshotTimeout(t *testing.T) {
   187  	testMaintenanceSnapshotTimeout(t, func(ctx context.Context, client *clientv3.Client) (io.ReadCloser, error) {
   188  		return client.Snapshot(ctx)
   189  	})
   190  }
   191  
   192  // testMaintenanceSnapshotTimeout given snapshot function ensures that it
   193  // returns corresponding context errors when context timeout happened before snapshot reading
   194  func testMaintenanceSnapshotTimeout(t *testing.T, snapshot func(context.Context, *clientv3.Client) (io.ReadCloser, error)) {
   195  	integration2.BeforeTest(t)
   196  
   197  	clus := integration2.NewCluster(t, &integration2.ClusterConfig{Size: 1})
   198  	defer clus.Terminate(t)
   199  
   200  	// reading snapshot with deadline exceeded should error out
   201  	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   202  	defer cancel()
   203  	rc2, err := snapshot(ctx, clus.RandClient())
   204  	if err != nil {
   205  		t.Fatal(err)
   206  	}
   207  	defer rc2.Close()
   208  
   209  	time.Sleep(2 * time.Second)
   210  
   211  	_, err = io.Copy(io.Discard, rc2)
   212  	if err != nil && !IsClientTimeout(err) {
   213  		t.Errorf("expected client timeout, got %v", err)
   214  	}
   215  }
   216  
   217  // TestMaintenanceSnapshotWithVersionErrorInflight ensures that ReaderCloser returned by SnapshotWithVersion function
   218  // will fail to read with corresponding context errors on inflight context cancel timeout.
   219  func TestMaintenanceSnapshotWithVersionErrorInflight(t *testing.T) {
   220  	testMaintenanceSnapshotErrorInflight(t, func(ctx context.Context, client *clientv3.Client) (io.ReadCloser, error) {
   221  		resp, err := client.SnapshotWithVersion(ctx)
   222  		if err != nil {
   223  			return nil, err
   224  		}
   225  		return resp.Snapshot, nil
   226  	})
   227  }
   228  
   229  // TestMaintenanceSnapshotError ensures that ReaderCloser returned by Snapshot function
   230  // will fail to read with corresponding context errors on inflight context cancel timeout.
   231  func TestMaintenanceSnapshotErrorInflight(t *testing.T) {
   232  	testMaintenanceSnapshotErrorInflight(t, func(ctx context.Context, client *clientv3.Client) (io.ReadCloser, error) {
   233  		return client.Snapshot(ctx)
   234  	})
   235  }
   236  
   237  // testMaintenanceSnapshotErrorInflight given snapshot function ensures that ReaderCloser returned by it
   238  // will fail to read with corresponding context errors on inflight context cancel timeout.
   239  func testMaintenanceSnapshotErrorInflight(t *testing.T, snapshot func(context.Context, *clientv3.Client) (io.ReadCloser, error)) {
   240  	integration2.BeforeTest(t)
   241  	lg := zaptest.NewLogger(t)
   242  
   243  	clus := integration2.NewCluster(t, &integration2.ClusterConfig{Size: 1, UseBridge: true})
   244  	defer clus.Terminate(t)
   245  
   246  	// take about 1-second to read snapshot
   247  	clus.Members[0].Stop(t)
   248  	dpath := filepath.Join(clus.Members[0].DataDir, "member", "snap", "db")
   249  	b := backend.NewDefaultBackend(lg, dpath)
   250  	s := mvcc.NewStore(lg, b, &lease.FakeLessor{}, mvcc.StoreConfig{CompactionBatchLimit: math.MaxInt32})
   251  	rev := 100000
   252  	for i := 2; i <= rev; i++ {
   253  		s.Put([]byte(fmt.Sprintf("%10d", i)), bytes.Repeat([]byte("a"), 1024), lease.NoLease)
   254  	}
   255  	s.Close()
   256  	b.Close()
   257  	clus.Members[0].Restart(t)
   258  
   259  	// reading snapshot with canceled context should error out
   260  	ctx, cancel := context.WithCancel(context.Background())
   261  	rc1, err := snapshot(ctx, clus.RandClient())
   262  	if err != nil {
   263  		t.Fatal(err)
   264  	}
   265  	defer rc1.Close()
   266  
   267  	donec := make(chan struct{})
   268  	go func() {
   269  		time.Sleep(300 * time.Millisecond)
   270  		cancel()
   271  		close(donec)
   272  	}()
   273  	_, err = io.Copy(io.Discard, rc1)
   274  	if err != nil && err != context.Canceled {
   275  		t.Errorf("expected %v, got %v", context.Canceled, err)
   276  	}
   277  	<-donec
   278  
   279  	// reading snapshot with deadline exceeded should error out
   280  	ctx, cancel = context.WithTimeout(context.Background(), time.Second)
   281  	defer cancel()
   282  	rc2, err := snapshot(ctx, clus.RandClient())
   283  	if err != nil {
   284  		t.Fatal(err)
   285  	}
   286  	defer rc2.Close()
   287  
   288  	// 300ms left and expect timeout while snapshot reading is in progress
   289  	time.Sleep(700 * time.Millisecond)
   290  	_, err = io.Copy(io.Discard, rc2)
   291  	if err != nil && !IsClientTimeout(err) {
   292  		t.Errorf("expected client timeout, got %v", err)
   293  	}
   294  }
   295  
   296  // TestMaintenanceSnapshotWithVersionVersion ensures that SnapshotWithVersion returns correct version value.
   297  func TestMaintenanceSnapshotWithVersionVersion(t *testing.T) {
   298  	integration2.BeforeTest(t)
   299  
   300  	// Set SnapshotCount to 1 to force raft snapshot to ensure that storage version is set
   301  	clus := integration2.NewCluster(t, &integration2.ClusterConfig{Size: 1, SnapshotCount: 1})
   302  	defer clus.Terminate(t)
   303  
   304  	// Put some keys to ensure that wal snapshot is triggered
   305  	for i := 0; i < 10; i++ {
   306  		clus.RandClient().Put(context.Background(), fmt.Sprintf("%d", i), "1")
   307  	}
   308  
   309  	// reading snapshot with canceled context should error out
   310  	resp, err := clus.RandClient().SnapshotWithVersion(context.Background())
   311  	if err != nil {
   312  		t.Fatal(err)
   313  	}
   314  	defer resp.Snapshot.Close()
   315  	if resp.Version != "3.6.0" {
   316  		t.Errorf("unexpected version, expected %q, got %q", version.Version, resp.Version)
   317  	}
   318  }
   319  
   320  func TestMaintenanceStatus(t *testing.T) {
   321  	integration2.BeforeTest(t)
   322  
   323  	clus := integration2.NewCluster(t, &integration2.ClusterConfig{Size: 3})
   324  	defer clus.Terminate(t)
   325  
   326  	t.Logf("Waiting for leader...")
   327  	clus.WaitLeader(t)
   328  	t.Logf("Leader established.")
   329  
   330  	eps := make([]string, 3)
   331  	for i := 0; i < 3; i++ {
   332  		eps[i] = clus.Members[i].GRPCURL()
   333  	}
   334  
   335  	t.Logf("Creating client...")
   336  	cli, err := integration2.NewClient(t, clientv3.Config{Endpoints: eps, DialOptions: []grpc.DialOption{grpc.WithBlock()}})
   337  	if err != nil {
   338  		t.Fatal(err)
   339  	}
   340  	defer cli.Close()
   341  	t.Logf("Creating client [DONE]")
   342  
   343  	prevID, leaderFound := uint64(0), false
   344  	for i := 0; i < 3; i++ {
   345  		resp, err := cli.Status(context.TODO(), eps[i])
   346  		if err != nil {
   347  			t.Fatal(err)
   348  		}
   349  		t.Logf("Response from %v: %v", i, resp)
   350  		if prevID == 0 {
   351  			prevID, leaderFound = resp.Header.MemberId, resp.Header.MemberId == resp.Leader
   352  			continue
   353  		}
   354  		if prevID == resp.Header.MemberId {
   355  			t.Errorf("#%d: status returned duplicate member ID with %016x", i, prevID)
   356  		}
   357  		if leaderFound && resp.Header.MemberId == resp.Leader {
   358  			t.Errorf("#%d: leader already found, but found another %016x", i, resp.Header.MemberId)
   359  		}
   360  		if !leaderFound {
   361  			leaderFound = resp.Header.MemberId == resp.Leader
   362  		}
   363  	}
   364  	if !leaderFound {
   365  		t.Fatal("no leader found")
   366  	}
   367  }