vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletmanager/shard_sync_test.go (about)

     1  /*
     2  Copyright 2021 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package tabletmanager
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"reflect"
    23  	"testing"
    24  	"time"
    25  
    26  	"vitess.io/vitess/go/protoutil"
    27  	"vitess.io/vitess/go/vt/proto/vttime"
    28  
    29  	"github.com/stretchr/testify/assert"
    30  
    31  	"vitess.io/vitess/go/vt/proto/topodata"
    32  	"vitess.io/vitess/go/vt/topo"
    33  
    34  	"github.com/stretchr/testify/require"
    35  
    36  	"vitess.io/vitess/go/vt/topo/topoproto"
    37  
    38  	"vitess.io/vitess/go/vt/topo/memorytopo"
    39  )
    40  
    41  const (
    42  	keyspace = "ks"
    43  	shard    = "0"
    44  )
    45  
    46  func TestShardSync(t *testing.T) {
    47  	ctx := context.Background()
    48  	ts := memorytopo.NewServer("cell1")
    49  	statsTabletTypeCount.ResetAll()
    50  	tm := newTestTM(t, ts, 100, keyspace, shard)
    51  	defer tm.Stop()
    52  
    53  	// update the primary info in the shard record and set it to nil
    54  	originalTime := time.Now()
    55  	updatePrimaryInfoInShardRecord(ctx, t, tm, nil, originalTime)
    56  
    57  	// now try to promote the tablet to primary
    58  	err := tm.tmState.ChangeTabletType(ctx, topodata.TabletType_PRIMARY, DBActionSetReadWrite)
    59  	require.NoError(t, err)
    60  	// verify that the tablet record has been updated
    61  	ti, err := ts.GetTablet(ctx, tm.tabletAlias)
    62  	require.NoError(t, err)
    63  	assert.Equal(t, topodata.TabletType_PRIMARY, ti.Type)
    64  	assert.NotNil(t, ti.PrimaryTermStartTime)
    65  
    66  	// wait for syncing to work correctly
    67  	// this should also have updated the shard record since it is a more recent operation
    68  	// We check here that the shard record and the tablet record are in sync
    69  	checkShardRecordWithTimeout(ctx, t, ts, ti.Alias, ti.PrimaryTermStartTime, 1*time.Second)
    70  
    71  	// even if try to update the shard record with the old timestamp, it should be reverted again
    72  	updatePrimaryInfoInShardRecord(ctx, t, tm, nil, originalTime)
    73  
    74  	// this should have also updated the shard record because of the timestamp.
    75  	checkShardRecordWithTimeout(ctx, t, ts, ti.Alias, ti.PrimaryTermStartTime, 1*time.Second)
    76  
    77  	// updating the shard record with the latest time should trigger an update in the tablet
    78  	newTime := time.Now()
    79  	updatePrimaryInfoInShardRecord(ctx, t, tm, nil, newTime)
    80  
    81  	// this should not have updated.
    82  	checkShardRecordWithTimeout(ctx, t, ts, nil, protoutil.TimeToProto(newTime), 1*time.Second)
    83  
    84  	// verify that the tablet record has been updated
    85  	checkTabletRecordWithTimeout(ctx, t, ts, tm.tabletAlias, topodata.TabletType_REPLICA, nil, 1*time.Second)
    86  }
    87  
    88  func checkShardRecordWithTimeout(ctx context.Context, t *testing.T, ts *topo.Server, tabletAlias *topodata.TabletAlias, expectedStartTime *vttime.Time, timeToWait time.Duration) {
    89  	timeOut := time.After(timeToWait)
    90  	for {
    91  		select {
    92  		case <-timeOut:
    93  			t.Fatalf("timed out: waiting for shard record to update")
    94  		default:
    95  			si, err := ts.GetShard(ctx, keyspace, shard)
    96  			require.NoError(t, err)
    97  			if reflect.DeepEqual(tabletAlias, si.PrimaryAlias) && reflect.DeepEqual(expectedStartTime, si.PrimaryTermStartTime) {
    98  				return
    99  			}
   100  			time.Sleep(100 * time.Millisecond)
   101  		}
   102  	}
   103  }
   104  
   105  func checkTabletRecordWithTimeout(ctx context.Context, t *testing.T, ts *topo.Server, tabletAlias *topodata.TabletAlias, tabletType topodata.TabletType, expectedStartTime *vttime.Time, timeToWait time.Duration) {
   106  	timeOut := time.After(timeToWait)
   107  	for {
   108  		select {
   109  		case <-timeOut:
   110  			t.Fatalf("timed out: waiting for tablet record to update")
   111  		default:
   112  			ti, err := ts.GetTablet(ctx, tabletAlias)
   113  			require.NoError(t, err)
   114  			if reflect.DeepEqual(tabletType, ti.Type) && reflect.DeepEqual(expectedStartTime, ti.PrimaryTermStartTime) {
   115  				return
   116  			}
   117  			time.Sleep(100 * time.Millisecond)
   118  		}
   119  	}
   120  }
   121  
   122  func updatePrimaryInfoInShardRecord(ctx context.Context, t *testing.T, tm *TabletManager, alias *topodata.TabletAlias, time time.Time) {
   123  	ctx, unlock, lockErr := tm.TopoServer.LockShard(ctx, keyspace, shard, fmt.Sprintf("updatePrimaryInfoInShardRecord(%v)", topoproto.TabletAliasString(tm.tabletAlias)))
   124  	require.NoError(t, lockErr)
   125  	defer unlock(&lockErr)
   126  
   127  	_, err := tm.TopoServer.UpdateShardFields(ctx, keyspace, shard, func(si *topo.ShardInfo) error {
   128  		si.PrimaryAlias = alias
   129  		si.SetPrimaryTermStartTime(time)
   130  		return nil
   131  	})
   132  	require.NoError(t, err)
   133  }