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

     1  /*
     2  Copyright 2020 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  	"encoding/json"
    22  	"os"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/stretchr/testify/assert"
    27  	"github.com/stretchr/testify/require"
    28  
    29  	"vitess.io/vitess/go/test/utils"
    30  	"vitess.io/vitess/go/vt/key"
    31  	"vitess.io/vitess/go/vt/mysqlctl/fakemysqldaemon"
    32  	"vitess.io/vitess/go/vt/servenv"
    33  	"vitess.io/vitess/go/vt/topo"
    34  	"vitess.io/vitess/go/vt/topo/faketopo"
    35  	"vitess.io/vitess/go/vt/topo/memorytopo"
    36  	"vitess.io/vitess/go/vt/vterrors"
    37  	"vitess.io/vitess/go/vt/vttablet/tabletservermock"
    38  
    39  	tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata"
    40  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    41  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    42  )
    43  
    44  func TestStateOpenClose(t *testing.T) {
    45  	ts := memorytopo.NewServer("cell1")
    46  	tm := newTestTM(t, ts, 1, "ks", "0")
    47  
    48  	// Re-Open should be a no-op
    49  	tm.tmState.mu.Lock()
    50  	savedCtx := tm.tmState.ctx
    51  	tm.tmState.mu.Unlock()
    52  
    53  	tm.tmState.Open()
    54  
    55  	tm.tmState.mu.Lock()
    56  	assert.Equal(t, savedCtx, tm.tmState.ctx)
    57  	tm.tmState.mu.Unlock()
    58  
    59  	tm.Close()
    60  	tm.tmState.mu.Lock()
    61  	assert.False(t, tm.tmState.isOpen)
    62  	tm.tmState.mu.Unlock()
    63  }
    64  
    65  func TestStateRefreshFromTopo(t *testing.T) {
    66  	ctx := context.Background()
    67  	ts := memorytopo.NewServer("cell1")
    68  	tm := newTestTM(t, ts, 1, "ks", "0")
    69  	defer tm.Stop()
    70  
    71  	err := tm.RefreshState(ctx)
    72  	require.NoError(t, err)
    73  }
    74  
    75  func TestStateResharding(t *testing.T) {
    76  	ctx := context.Background()
    77  	ts := memorytopo.NewServer("cell1")
    78  	tm := newTestTM(t, ts, 1, "ks", "0")
    79  	defer tm.Stop()
    80  
    81  	tm.tmState.mu.Lock()
    82  	tm.tmState.tablet.Type = topodatapb.TabletType_PRIMARY
    83  	tm.tmState.mu.Unlock()
    84  
    85  	si := &topo.ShardInfo{
    86  		Shard: &topodatapb.Shard{
    87  			SourceShards: []*topodatapb.Shard_SourceShard{{
    88  				Uid: 1,
    89  			}},
    90  		},
    91  	}
    92  	tm.tmState.RefreshFromTopoInfo(ctx, si, nil)
    93  	tm.tmState.mu.Lock()
    94  	assert.True(t, tm.tmState.isResharding)
    95  	tm.tmState.mu.Unlock()
    96  
    97  	qsc := tm.QueryServiceControl.(*tabletservermock.Controller)
    98  	assert.Equal(t, topodatapb.TabletType_PRIMARY, qsc.CurrentTarget().TabletType)
    99  	assert.False(t, qsc.IsServing())
   100  }
   101  
   102  func TestStateDenyList(t *testing.T) {
   103  	ctx := context.Background()
   104  	ts := memorytopo.NewServer("cell1")
   105  	tm := newTestTM(t, ts, 1, "ks", "0")
   106  	defer tm.Stop()
   107  
   108  	fmd := tm.MysqlDaemon.(*fakemysqldaemon.FakeMysqlDaemon)
   109  	fmd.Schema = &tabletmanagerdatapb.SchemaDefinition{
   110  		TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{
   111  			Name: "t1",
   112  		}},
   113  	}
   114  	si := &topo.ShardInfo{
   115  		Shard: &topodatapb.Shard{
   116  			TabletControls: []*topodatapb.Shard_TabletControl{{
   117  				TabletType:   topodatapb.TabletType_REPLICA,
   118  				Cells:        []string{"cell1"},
   119  				DeniedTables: []string{"t1"},
   120  			}},
   121  		},
   122  	}
   123  	tm.tmState.RefreshFromTopoInfo(ctx, si, nil)
   124  	tm.tmState.mu.Lock()
   125  	assert.Equal(t, map[topodatapb.TabletType][]string{topodatapb.TabletType_REPLICA: {"t1"}}, tm.tmState.deniedTables)
   126  	tm.tmState.mu.Unlock()
   127  
   128  	qsc := tm.QueryServiceControl.(*tabletservermock.Controller)
   129  	b, _ := json.Marshal(qsc.GetQueryRules(denyListQueryList))
   130  	assert.Equal(t, `[{"Description":"enforce denied tables","Name":"denied_table","TableNames":["t1"],"Action":"FAIL_RETRY"}]`, string(b))
   131  }
   132  
   133  func TestStateTabletControls(t *testing.T) {
   134  	ctx := context.Background()
   135  	ts := memorytopo.NewServer("cell1")
   136  	tm := newTestTM(t, ts, 1, "ks", "0")
   137  	defer tm.Stop()
   138  
   139  	ks := &topodatapb.SrvKeyspace{
   140  		Partitions: []*topodatapb.SrvKeyspace_KeyspacePartition{{
   141  			ServedType: topodatapb.TabletType_REPLICA,
   142  			ShardTabletControls: []*topodatapb.ShardTabletControl{{
   143  				Name:                 "0",
   144  				QueryServiceDisabled: true,
   145  			}},
   146  		}},
   147  	}
   148  	tm.tmState.RefreshFromTopoInfo(ctx, nil, ks)
   149  	want := map[topodatapb.TabletType]bool{
   150  		topodatapb.TabletType_REPLICA: true,
   151  	}
   152  	tm.tmState.mu.Lock()
   153  	assert.Equal(t, want, tm.tmState.tabletControls)
   154  	tm.tmState.mu.Unlock()
   155  
   156  	qsc := tm.QueryServiceControl.(*tabletservermock.Controller)
   157  	assert.Equal(t, topodatapb.TabletType_REPLICA, qsc.CurrentTarget().TabletType)
   158  	assert.False(t, qsc.IsServing())
   159  }
   160  
   161  func TestStateIsShardServingisInSrvKeyspace(t *testing.T) {
   162  	ctx := context.Background()
   163  	ts := memorytopo.NewServer("cell1")
   164  	tm := newTestTM(t, ts, 1, "ks", "0")
   165  	defer tm.Stop()
   166  
   167  	tm.tmState.mu.Lock()
   168  	tm.tmState.tablet.Type = topodatapb.TabletType_PRIMARY
   169  	tm.tmState.updateLocked(ctx)
   170  	tm.tmState.mu.Unlock()
   171  
   172  	leftKeyRange, err := key.ParseShardingSpec("-80")
   173  	if err != nil || len(leftKeyRange) != 1 {
   174  		t.Fatalf("ParseShardingSpec failed. Expected non error and only one element. Got err: %v, len(%v)", err, len(leftKeyRange))
   175  	}
   176  
   177  	rightKeyRange, err := key.ParseShardingSpec("80-")
   178  	if err != nil || len(rightKeyRange) != 1 {
   179  		t.Fatalf("ParseShardingSpec failed. Expected non error and only one element. Got err: %v, len(%v)", err, len(rightKeyRange))
   180  	}
   181  
   182  	keyRange, err := key.ParseShardingSpec("0")
   183  	if err != nil || len(keyRange) != 1 {
   184  		t.Fatalf("ParseShardingSpec failed. Expected non error and only one element. Got err: %v, len(%v)", err, len(keyRange))
   185  	}
   186  
   187  	// Shard not in the SrvKeyspace, ServedType not in SrvKeyspace
   188  	ks := &topodatapb.SrvKeyspace{
   189  		Partitions: []*topodatapb.SrvKeyspace_KeyspacePartition{
   190  			{
   191  				ServedType: topodatapb.TabletType_DRAINED,
   192  				ShardReferences: []*topodatapb.ShardReference{
   193  					{
   194  						Name:     "-80",
   195  						KeyRange: leftKeyRange[0],
   196  					},
   197  					{
   198  						Name:     "80-",
   199  						KeyRange: rightKeyRange[0],
   200  					},
   201  				},
   202  			},
   203  		},
   204  	}
   205  	want := map[topodatapb.TabletType]bool{}
   206  	tm.tmState.RefreshFromTopoInfo(ctx, nil, ks)
   207  
   208  	tm.tmState.mu.Lock()
   209  	assert.False(t, tm.tmState.isInSrvKeyspace)
   210  	assert.Equal(t, want, tm.tmState.isShardServing)
   211  	tm.tmState.mu.Unlock()
   212  
   213  	assert.Equal(t, int64(0), statsIsInSrvKeyspace.Get())
   214  
   215  	// Shard not in the SrvKeyspace, ServedType in SrvKeyspace
   216  	ks = &topodatapb.SrvKeyspace{
   217  		Partitions: []*topodatapb.SrvKeyspace_KeyspacePartition{
   218  			{
   219  				ServedType: topodatapb.TabletType_PRIMARY,
   220  				ShardReferences: []*topodatapb.ShardReference{
   221  					{
   222  						Name:     "-80",
   223  						KeyRange: leftKeyRange[0],
   224  					},
   225  					{
   226  						Name:     "80-",
   227  						KeyRange: rightKeyRange[0],
   228  					},
   229  				},
   230  			},
   231  		},
   232  	}
   233  	want = map[topodatapb.TabletType]bool{}
   234  	tm.tmState.RefreshFromTopoInfo(ctx, nil, ks)
   235  
   236  	tm.tmState.mu.Lock()
   237  	assert.False(t, tm.tmState.isInSrvKeyspace)
   238  	assert.Equal(t, want, tm.tmState.isShardServing)
   239  	tm.tmState.mu.Unlock()
   240  
   241  	assert.Equal(t, int64(0), statsIsInSrvKeyspace.Get())
   242  
   243  	// Shard in the SrvKeyspace, ServedType in the SrvKeyspace
   244  	ks = &topodatapb.SrvKeyspace{
   245  		Partitions: []*topodatapb.SrvKeyspace_KeyspacePartition{
   246  			{
   247  				ServedType: topodatapb.TabletType_PRIMARY,
   248  				ShardReferences: []*topodatapb.ShardReference{
   249  					{
   250  						Name:     "0",
   251  						KeyRange: keyRange[0],
   252  					},
   253  				},
   254  			},
   255  		},
   256  	}
   257  	want = map[topodatapb.TabletType]bool{
   258  		topodatapb.TabletType_PRIMARY: true,
   259  	}
   260  	tm.tmState.RefreshFromTopoInfo(ctx, nil, ks)
   261  
   262  	tm.tmState.mu.Lock()
   263  	assert.True(t, tm.tmState.isInSrvKeyspace)
   264  	assert.Equal(t, want, tm.tmState.isShardServing)
   265  	tm.tmState.mu.Unlock()
   266  
   267  	assert.Equal(t, int64(1), statsIsInSrvKeyspace.Get())
   268  
   269  	// Shard in the SrvKeyspace, ServedType not in the SrvKeyspace
   270  	ks = &topodatapb.SrvKeyspace{
   271  		Partitions: []*topodatapb.SrvKeyspace_KeyspacePartition{
   272  			{
   273  				ServedType: topodatapb.TabletType_RDONLY,
   274  				ShardReferences: []*topodatapb.ShardReference{
   275  					{
   276  						Name:     "0",
   277  						KeyRange: keyRange[0],
   278  					},
   279  				},
   280  			},
   281  		},
   282  	}
   283  	want = map[topodatapb.TabletType]bool{
   284  		topodatapb.TabletType_RDONLY: true,
   285  	}
   286  	tm.tmState.RefreshFromTopoInfo(ctx, nil, ks)
   287  
   288  	tm.tmState.mu.Lock()
   289  	assert.False(t, tm.tmState.isInSrvKeyspace)
   290  	assert.Equal(t, want, tm.tmState.isShardServing)
   291  	tm.tmState.mu.Unlock()
   292  
   293  	assert.Equal(t, int64(0), statsIsInSrvKeyspace.Get())
   294  
   295  	// Test tablet type change - shard in the SrvKeyspace, ServedType in the SrvKeyspace
   296  	err = tm.tmState.ChangeTabletType(ctx, topodatapb.TabletType_RDONLY, DBActionNone)
   297  	require.NoError(t, err)
   298  	tm.tmState.mu.Lock()
   299  	assert.True(t, tm.tmState.isInSrvKeyspace)
   300  	tm.tmState.mu.Unlock()
   301  
   302  	assert.Equal(t, int64(1), statsIsInSrvKeyspace.Get())
   303  
   304  	// Test tablet type change - shard in the SrvKeyspace, ServedType in the SrvKeyspace
   305  	err = tm.tmState.ChangeTabletType(ctx, topodatapb.TabletType_DRAINED, DBActionNone)
   306  	require.NoError(t, err)
   307  	tm.tmState.mu.Lock()
   308  	assert.False(t, tm.tmState.isInSrvKeyspace)
   309  	tm.tmState.mu.Unlock()
   310  
   311  	assert.Equal(t, int64(0), statsIsInSrvKeyspace.Get())
   312  
   313  	// Test tablet isOpen
   314  	tm.tmState.mu.Lock()
   315  	tm.tmState.isOpen = false
   316  	tm.tmState.isInSrvKeyspace = false
   317  	tm.tmState.tablet.Type = topodatapb.TabletType_REPLICA
   318  	tm.tmState.isShardServing = map[topodatapb.TabletType]bool{
   319  		topodatapb.TabletType_REPLICA: true,
   320  	}
   321  	tm.tmState.mu.Unlock()
   322  
   323  	tm.tmState.Open()
   324  
   325  	tm.tmState.mu.Lock()
   326  	assert.True(t, tm.tmState.isInSrvKeyspace)
   327  	tm.tmState.mu.Unlock()
   328  
   329  	assert.Equal(t, int64(1), statsIsInSrvKeyspace.Get())
   330  }
   331  
   332  func TestStateNonServing(t *testing.T) {
   333  	ctx := context.Background()
   334  	ts := memorytopo.NewServer("cell1")
   335  	tm := newTestTM(t, ts, 1, "ks", "0")
   336  	defer tm.Stop()
   337  
   338  	tm.tmState.mu.Lock()
   339  	tm.tmState.tablet.Type = topodatapb.TabletType_SPARE
   340  	tm.tmState.updateLocked(ctx)
   341  	tm.tmState.mu.Unlock()
   342  
   343  	qsc := tm.QueryServiceControl.(*tabletservermock.Controller)
   344  	assert.Equal(t, topodatapb.TabletType_SPARE, qsc.CurrentTarget().TabletType)
   345  	assert.False(t, qsc.IsServing())
   346  }
   347  
   348  func TestStateChangeTabletType(t *testing.T) {
   349  	ctx := context.Background()
   350  	ts := memorytopo.NewServer("cell1")
   351  	statsTabletTypeCount.ResetAll()
   352  	tm := newTestTM(t, ts, 2, "ks", "0")
   353  	defer tm.Stop()
   354  
   355  	assert.Equal(t, 1, len(statsTabletTypeCount.Counts()))
   356  	assert.Equal(t, int64(1), statsTabletTypeCount.Counts()["replica"])
   357  
   358  	alias := &topodatapb.TabletAlias{
   359  		Cell: "cell1",
   360  		Uid:  2,
   361  	}
   362  
   363  	err := tm.tmState.ChangeTabletType(ctx, topodatapb.TabletType_PRIMARY, DBActionSetReadWrite)
   364  	require.NoError(t, err)
   365  	ti, err := ts.GetTablet(ctx, alias)
   366  	require.NoError(t, err)
   367  	assert.Equal(t, topodatapb.TabletType_PRIMARY, ti.Type)
   368  	assert.NotNil(t, ti.PrimaryTermStartTime)
   369  	assert.Equal(t, "primary", statsTabletType.Get())
   370  	assert.Equal(t, 2, len(statsTabletTypeCount.Counts()))
   371  	assert.Equal(t, int64(1), statsTabletTypeCount.Counts()["primary"])
   372  
   373  	err = tm.tmState.ChangeTabletType(ctx, topodatapb.TabletType_REPLICA, DBActionNone)
   374  	require.NoError(t, err)
   375  	ti, err = ts.GetTablet(ctx, alias)
   376  	require.NoError(t, err)
   377  	assert.Equal(t, topodatapb.TabletType_REPLICA, ti.Type)
   378  	assert.Nil(t, ti.PrimaryTermStartTime)
   379  	assert.Equal(t, "replica", statsTabletType.Get())
   380  	assert.Equal(t, 2, len(statsTabletTypeCount.Counts()))
   381  	assert.Equal(t, int64(2), statsTabletTypeCount.Counts()["replica"])
   382  }
   383  
   384  /*
   385  	This test verifies, even if SetServingType returns error we should still publish
   386  
   387  the new table type
   388  */
   389  func TestStateChangeTabletTypeWithFailure(t *testing.T) {
   390  	ctx := context.Background()
   391  	ts := memorytopo.NewServer("cell1")
   392  	statsTabletTypeCount.ResetAll()
   393  	// create TM with replica and put a hook to return error during SetServingType
   394  	tm := newTestTM(t, ts, 2, "ks", "0")
   395  	qsc := tm.QueryServiceControl.(*tabletservermock.Controller)
   396  	qsc.SetServingTypeError = vterrors.Errorf(vtrpcpb.Code_RESOURCE_EXHAUSTED, "mocking resource exhaustion error ")
   397  	defer tm.Stop()
   398  
   399  	assert.Equal(t, 1, len(statsTabletTypeCount.Counts()))
   400  	assert.Equal(t, int64(1), statsTabletTypeCount.Counts()["replica"])
   401  
   402  	alias := &topodatapb.TabletAlias{
   403  		Cell: "cell1",
   404  		Uid:  2,
   405  	}
   406  
   407  	// change table type to primary
   408  	err := tm.tmState.ChangeTabletType(ctx, topodatapb.TabletType_PRIMARY, DBActionSetReadWrite)
   409  	errMsg := "Cannot start query service: Code: RESOURCE_EXHAUSTED\nmocking resource exhaustion error \n: mocking resource exhaustion error "
   410  	require.EqualError(t, err, errMsg)
   411  
   412  	ti, err := ts.GetTablet(ctx, alias)
   413  	require.NoError(t, err)
   414  	// even though SetServingType failed. It still is expected to publish the new table type
   415  	assert.Equal(t, topodatapb.TabletType_PRIMARY, ti.Type)
   416  	assert.NotNil(t, ti.PrimaryTermStartTime)
   417  	assert.Equal(t, "primary", statsTabletType.Get())
   418  	assert.Equal(t, 2, len(statsTabletTypeCount.Counts()))
   419  	assert.Equal(t, int64(1), statsTabletTypeCount.Counts()["primary"])
   420  
   421  	err = tm.tmState.ChangeTabletType(ctx, topodatapb.TabletType_REPLICA, DBActionNone)
   422  	require.EqualError(t, err, errMsg)
   423  	ti, err = ts.GetTablet(ctx, alias)
   424  	require.NoError(t, err)
   425  	// even though SetServingType failed. It still is expected to publish the new table type
   426  	assert.Equal(t, topodatapb.TabletType_REPLICA, ti.Type)
   427  	assert.Nil(t, ti.PrimaryTermStartTime)
   428  	assert.Equal(t, "replica", statsTabletType.Get())
   429  	assert.Equal(t, 2, len(statsTabletTypeCount.Counts()))
   430  	assert.Equal(t, int64(2), statsTabletTypeCount.Counts()["replica"])
   431  
   432  	// since the table type is spare, it will exercise reason != "" in UpdateLocked and thus
   433  	// populate error object differently as compare to above scenarios
   434  	err = tm.tmState.ChangeTabletType(ctx, topodatapb.TabletType_SPARE, DBActionNone)
   435  	errMsg = "SetServingType(serving=false) failed: Code: RESOURCE_EXHAUSTED\nmocking resource exhaustion error \n: mocking resource exhaustion error "
   436  	require.EqualError(t, err, errMsg)
   437  	ti, err = ts.GetTablet(ctx, alias)
   438  	require.NoError(t, err)
   439  	// even though SetServingType failed. It still is expected to publish the new table type
   440  	assert.Equal(t, topodatapb.TabletType_SPARE, ti.Type)
   441  	assert.Nil(t, ti.PrimaryTermStartTime)
   442  	assert.Equal(t, "spare", statsTabletType.Get())
   443  	assert.Equal(t, 3, len(statsTabletTypeCount.Counts()))
   444  	assert.Equal(t, int64(1), statsTabletTypeCount.Counts()["spare"])
   445  }
   446  
   447  // TestChangeTypeErrorWhileWritingToTopo tests the case where we fail while writing to the topo-server
   448  func TestChangeTypeErrorWhileWritingToTopo(t *testing.T) {
   449  	testcases := []struct {
   450  		name               string
   451  		writePersists      bool
   452  		numberOfReadErrors int
   453  		expectedTabletType topodatapb.TabletType
   454  		expectedError      string
   455  	}{
   456  		{
   457  			name:               "Write persists even when error thrown from topo server",
   458  			writePersists:      true,
   459  			numberOfReadErrors: 5,
   460  			expectedTabletType: topodatapb.TabletType_PRIMARY,
   461  		}, {
   462  			name:               "Topo server throws error and the write also fails",
   463  			writePersists:      false,
   464  			numberOfReadErrors: 17,
   465  			expectedTabletType: topodatapb.TabletType_REPLICA,
   466  			expectedError:      "deadline exceeded: tablets/cell1-0000000002/Tablet",
   467  		},
   468  	}
   469  
   470  	for _, testcase := range testcases {
   471  		t.Run(testcase.name, func(t *testing.T) {
   472  			factory := faketopo.NewFakeTopoFactory()
   473  			// add cell1 to the factory. This returns a fake connection which we will use to set the get and update errors as we require.
   474  			fakeConn := factory.AddCell("cell1")
   475  			ts := faketopo.NewFakeTopoServer(factory)
   476  			statsTabletTypeCount.ResetAll()
   477  			tm := newTestTM(t, ts, 2, "ks", "0")
   478  			defer tm.Stop()
   479  
   480  			// ChangeTabletType calls topotools.ChangeType which in-turn issues
   481  			// a GET request and an UPDATE request to the topo server.
   482  			// We want the first GET request to pass without any failure
   483  			// We want the UPDATE request to fail
   484  			fakeConn.AddGetError(false)
   485  			fakeConn.AddUpdateError(true, testcase.writePersists)
   486  			// Since the UPDATE request failed, we will try a GET request on the
   487  			// topo server until it succeeds.
   488  			for i := 0; i < testcase.numberOfReadErrors; i++ {
   489  				fakeConn.AddGetError(true)
   490  			}
   491  			ctx := context.Background()
   492  			err := tm.tmState.ChangeTabletType(ctx, topodatapb.TabletType_PRIMARY, DBActionSetReadWrite)
   493  			if testcase.expectedError != "" {
   494  				require.EqualError(t, err, testcase.expectedError)
   495  			} else {
   496  				require.NoError(t, err)
   497  			}
   498  
   499  			alias := &topodatapb.TabletAlias{
   500  				Cell: "cell1",
   501  				Uid:  2,
   502  			}
   503  			ti, err := ts.GetTablet(ctx, alias)
   504  			require.NoError(t, err)
   505  			require.Equal(t, testcase.expectedTabletType, ti.Type)
   506  
   507  			// assert that next change type succeeds irrespective of previous failures
   508  			err = tm.tmState.ChangeTabletType(context.Background(), topodatapb.TabletType_REPLICA, DBActionNone)
   509  			require.NoError(t, err)
   510  		})
   511  	}
   512  }
   513  
   514  func TestPublishStateNew(t *testing.T) {
   515  	defer func(saved time.Duration) { publishRetryInterval = saved }(publishRetryInterval)
   516  	publishRetryInterval = 1 * time.Millisecond
   517  
   518  	// This flow doesn't test the failure scenario, which
   519  	// we can't do using memorytopo, but we do test the retry
   520  	// code path.
   521  
   522  	ctx := context.Background()
   523  	ts := memorytopo.NewServer("cell1")
   524  	tm := newTestTM(t, ts, 42, "ks", "0")
   525  	ttablet, err := tm.TopoServer.GetTablet(ctx, tm.tabletAlias)
   526  	require.NoError(t, err)
   527  	utils.MustMatch(t, tm.Tablet(), ttablet.Tablet)
   528  
   529  	tab1 := tm.Tablet()
   530  	tab1.Keyspace = "tab1"
   531  	tm.tmState.mu.Lock()
   532  	tm.tmState.tablet = tab1
   533  	tm.tmState.publishStateLocked(ctx)
   534  	tm.tmState.mu.Unlock()
   535  	ttablet, err = tm.TopoServer.GetTablet(ctx, tm.tabletAlias)
   536  	require.NoError(t, err)
   537  	utils.MustMatch(t, tab1, ttablet.Tablet)
   538  
   539  	tab2 := tm.Tablet()
   540  	tab2.Keyspace = "tab2"
   541  	tm.tmState.mu.Lock()
   542  	tm.tmState.tablet = tab2
   543  	tm.tmState.mu.Unlock()
   544  	tm.tmState.retryPublish()
   545  	ttablet, err = tm.TopoServer.GetTablet(ctx, tm.tabletAlias)
   546  	require.NoError(t, err)
   547  	utils.MustMatch(t, tab2, ttablet.Tablet)
   548  
   549  	// If hostname doesn't match, it should not update.
   550  	tab3 := tm.Tablet()
   551  	tab3.Hostname = "tab3"
   552  	tm.tmState.mu.Lock()
   553  	tm.tmState.tablet = tab3
   554  	tm.tmState.publishStateLocked(ctx)
   555  	tm.tmState.mu.Unlock()
   556  	ttablet, err = tm.TopoServer.GetTablet(ctx, tm.tabletAlias)
   557  	require.NoError(t, err)
   558  	utils.MustMatch(t, tab2, ttablet.Tablet)
   559  
   560  	// Same for retryPublish.
   561  	tm.tmState.retryPublish()
   562  	ttablet, err = tm.TopoServer.GetTablet(ctx, tm.tabletAlias)
   563  	require.NoError(t, err)
   564  	utils.MustMatch(t, tab2, ttablet.Tablet)
   565  }
   566  
   567  func TestPublishDeleted(t *testing.T) {
   568  	ctx := context.Background()
   569  	ts := memorytopo.NewServer("cell1")
   570  	tm := newTestTM(t, ts, 2, "ks", "0")
   571  	defer tm.Stop()
   572  
   573  	alias := &topodatapb.TabletAlias{
   574  		Cell: "cell1",
   575  		Uid:  2,
   576  	}
   577  
   578  	err := tm.tmState.ChangeTabletType(ctx, topodatapb.TabletType_PRIMARY, DBActionSetReadWrite)
   579  	require.NoError(t, err)
   580  
   581  	err = ts.DeleteTablet(ctx, alias)
   582  	require.NoError(t, err)
   583  
   584  	// we need to make sure to catch the signal
   585  	servenv.ExitChan = make(chan os.Signal, 1)
   586  	// Now change the tablet type and publish
   587  	err = tm.tmState.ChangeTabletType(ctx, topodatapb.TabletType_REPLICA, DBActionNone)
   588  	require.NoError(t, err)
   589  	tm.tmState.mu.Lock()
   590  	assert.False(t, tm.tmState.isPublishing)
   591  	tm.tmState.mu.Unlock()
   592  }