vitess.io/vitess@v0.16.2/go/vt/vtgate/schema/tracker_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 schema
    18  
    19  import (
    20  	"fmt"
    21  	"sync"
    22  	"testing"
    23  	"time"
    24  
    25  	"vitess.io/vitess/go/mysql"
    26  
    27  	"github.com/stretchr/testify/assert"
    28  
    29  	"github.com/stretchr/testify/require"
    30  
    31  	"vitess.io/vitess/go/sqltypes"
    32  	"vitess.io/vitess/go/test/utils"
    33  	"vitess.io/vitess/go/vt/discovery"
    34  	querypb "vitess.io/vitess/go/vt/proto/query"
    35  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    36  	"vitess.io/vitess/go/vt/sqlparser"
    37  	"vitess.io/vitess/go/vt/vtgate/vindexes"
    38  	"vitess.io/vitess/go/vt/vttablet/sandboxconn"
    39  )
    40  
    41  func TestTracking(t *testing.T) {
    42  	target := &querypb.Target{
    43  		Keyspace:   "ks",
    44  		Shard:      "-80",
    45  		TabletType: topodatapb.TabletType_PRIMARY,
    46  		Cell:       "aa",
    47  	}
    48  	tablet := &topodatapb.Tablet{
    49  		Keyspace: target.Keyspace,
    50  		Shard:    target.Shard,
    51  		Type:     target.TabletType,
    52  	}
    53  	fields := sqltypes.MakeTestFields(
    54  		"table_name|col_name|col_type|collation_name",
    55  		"varchar|varchar|varchar|varchar",
    56  	)
    57  
    58  	type delta struct {
    59  		result *sqltypes.Result
    60  		updTbl []string
    61  	}
    62  	var (
    63  		d0 = delta{
    64  			result: sqltypes.MakeTestResult(
    65  				fields,
    66  				"prior|id|int|",
    67  			),
    68  			updTbl: []string{"prior"},
    69  		}
    70  
    71  		d1 = delta{
    72  			result: sqltypes.MakeTestResult(
    73  				fields,
    74  				"t1|id|int|",
    75  				"t1|name|varchar|utf8_bin",
    76  				"t2|id|varchar|utf8_bin",
    77  			),
    78  			updTbl: []string{"t1", "t2"},
    79  		}
    80  
    81  		d2 = delta{
    82  			result: sqltypes.MakeTestResult(
    83  				fields,
    84  				"t2|id|varchar|utf8_bin",
    85  				"t2|name|varchar|utf8_bin",
    86  				"t3|id|datetime|",
    87  			),
    88  			updTbl: []string{"prior", "t1", "t2", "t3"},
    89  		}
    90  
    91  		d3 = delta{
    92  			result: sqltypes.MakeTestResult(
    93  				fields,
    94  				"t4|name|varchar|utf8_bin",
    95  			),
    96  			updTbl: []string{"t4"},
    97  		}
    98  	)
    99  
   100  	testcases := []struct {
   101  		tName  string
   102  		deltas []delta
   103  		exp    map[string][]vindexes.Column
   104  	}{{
   105  		tName:  "new tables",
   106  		deltas: []delta{d0, d1},
   107  		exp: map[string][]vindexes.Column{
   108  			"t1": {
   109  				{Name: sqlparser.NewIdentifierCI("id"), Type: querypb.Type_INT32},
   110  				{Name: sqlparser.NewIdentifierCI("name"), Type: querypb.Type_VARCHAR, CollationName: "utf8_bin"}},
   111  			"t2": {
   112  				{Name: sqlparser.NewIdentifierCI("id"), Type: querypb.Type_VARCHAR, CollationName: "utf8_bin"}},
   113  			"prior": {
   114  				{Name: sqlparser.NewIdentifierCI("id"), Type: querypb.Type_INT32}},
   115  		},
   116  	}, {
   117  		tName:  "delete t1 and prior, updated t2 and new t3",
   118  		deltas: []delta{d0, d1, d2},
   119  		exp: map[string][]vindexes.Column{
   120  			"t2": {
   121  				{Name: sqlparser.NewIdentifierCI("id"), Type: querypb.Type_VARCHAR, CollationName: "utf8_bin"},
   122  				{Name: sqlparser.NewIdentifierCI("name"), Type: querypb.Type_VARCHAR, CollationName: "utf8_bin"}},
   123  			"t3": {
   124  				{Name: sqlparser.NewIdentifierCI("id"), Type: querypb.Type_DATETIME}},
   125  		},
   126  	}, {
   127  		tName:  "new t4",
   128  		deltas: []delta{d0, d1, d2, d3},
   129  		exp: map[string][]vindexes.Column{
   130  			"t2": {
   131  				{Name: sqlparser.NewIdentifierCI("id"), Type: querypb.Type_VARCHAR, CollationName: "utf8_bin"},
   132  				{Name: sqlparser.NewIdentifierCI("name"), Type: querypb.Type_VARCHAR, CollationName: "utf8_bin"}},
   133  			"t3": {
   134  				{Name: sqlparser.NewIdentifierCI("id"), Type: querypb.Type_DATETIME}},
   135  			"t4": {
   136  				{Name: sqlparser.NewIdentifierCI("name"), Type: querypb.Type_VARCHAR, CollationName: "utf8_bin"}},
   137  		},
   138  	},
   139  	}
   140  	for i, tcase := range testcases {
   141  		t.Run(fmt.Sprintf("%d - %s", i, tcase.tName), func(t *testing.T) {
   142  			sbc := sandboxconn.NewSandboxConn(tablet)
   143  			ch := make(chan *discovery.TabletHealth)
   144  			tracker := NewTracker(ch, "", false)
   145  			tracker.consumeDelay = 1 * time.Millisecond
   146  			tracker.Start()
   147  			defer tracker.Stop()
   148  
   149  			results := []*sqltypes.Result{{}}
   150  			for _, d := range tcase.deltas {
   151  				for _, deltaRow := range d.result.Rows {
   152  					same := false
   153  					for _, row := range results[0].Rows {
   154  						if row[0].String() == deltaRow[0].String() && row[1].String() == deltaRow[1].String() {
   155  							same = true
   156  							break
   157  						}
   158  					}
   159  					if same == false {
   160  						results[0].Rows = append(results[0].Rows, deltaRow)
   161  					}
   162  				}
   163  			}
   164  
   165  			sbc.SetResults(results)
   166  			sbc.Queries = nil
   167  
   168  			wg := sync.WaitGroup{}
   169  			wg.Add(1)
   170  			tracker.RegisterSignalReceiver(func() {
   171  				wg.Done()
   172  			})
   173  
   174  			for _, d := range tcase.deltas {
   175  				ch <- &discovery.TabletHealth{
   176  					Conn:    sbc,
   177  					Tablet:  tablet,
   178  					Target:  target,
   179  					Serving: true,
   180  					Stats:   &querypb.RealtimeStats{TableSchemaChanged: d.updTbl},
   181  				}
   182  			}
   183  
   184  			require.False(t, waitTimeout(&wg, time.Second), "schema was updated but received no signal")
   185  
   186  			require.Equal(t, 1, len(sbc.StringQueries()))
   187  
   188  			_, keyspacePresent := tracker.tracked[target.Keyspace]
   189  			require.Equal(t, true, keyspacePresent)
   190  
   191  			for k, v := range tcase.exp {
   192  				utils.MustMatch(t, v, tracker.GetColumns("ks", k), "mismatch for table: ", k)
   193  			}
   194  		})
   195  	}
   196  }
   197  
   198  func TestTrackingUnHealthyTablet(t *testing.T) {
   199  	target := &querypb.Target{
   200  		Keyspace:   "ks",
   201  		Shard:      "-80",
   202  		TabletType: topodatapb.TabletType_PRIMARY,
   203  		Cell:       "aa",
   204  	}
   205  	tablet := &topodatapb.Tablet{
   206  		Keyspace: target.Keyspace,
   207  		Shard:    target.Shard,
   208  		Type:     target.TabletType,
   209  	}
   210  
   211  	sbc := sandboxconn.NewSandboxConn(tablet)
   212  	ch := make(chan *discovery.TabletHealth)
   213  	tracker := NewTracker(ch, "", false)
   214  	tracker.consumeDelay = 1 * time.Millisecond
   215  	tracker.Start()
   216  	defer tracker.Stop()
   217  
   218  	// the test are written in a way that it expects 3 signals to be sent from the tracker to the subscriber.
   219  	wg := sync.WaitGroup{}
   220  	wg.Add(3)
   221  	tracker.RegisterSignalReceiver(func() {
   222  		wg.Done()
   223  	})
   224  
   225  	tcases := []struct {
   226  		name          string
   227  		serving       bool
   228  		expectedQuery string
   229  		updatedTbls   []string
   230  	}{
   231  		{
   232  			name:    "initial load",
   233  			serving: true,
   234  		},
   235  		{
   236  			name:        "initial load",
   237  			serving:     true,
   238  			updatedTbls: []string{"a"},
   239  		},
   240  		{
   241  			name:    "non serving tablet",
   242  			serving: false,
   243  		},
   244  		{
   245  			name:    "now serving tablet",
   246  			serving: true,
   247  		},
   248  	}
   249  
   250  	sbc.SetResults([]*sqltypes.Result{{}, {}, {}})
   251  	for _, tcase := range tcases {
   252  		ch <- &discovery.TabletHealth{
   253  			Conn:    sbc,
   254  			Tablet:  tablet,
   255  			Target:  target,
   256  			Serving: tcase.serving,
   257  			Stats:   &querypb.RealtimeStats{TableSchemaChanged: tcase.updatedTbls},
   258  		}
   259  		time.Sleep(5 * time.Millisecond)
   260  	}
   261  
   262  	require.False(t, waitTimeout(&wg, 5*time.Second), "schema was updated but received no signal")
   263  	require.Equal(t, []string{mysql.FetchTables, mysql.FetchUpdatedTables, mysql.FetchTables}, sbc.StringQueries())
   264  }
   265  
   266  func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
   267  	c := make(chan struct{})
   268  	go func() {
   269  		defer close(c)
   270  		wg.Wait()
   271  	}()
   272  	select {
   273  	case <-c:
   274  		return false // completed normally
   275  	case <-time.After(timeout):
   276  		return true // timed out
   277  	}
   278  }
   279  
   280  func TestTrackerGetKeyspaceUpdateController(t *testing.T) {
   281  	ks3 := &updateController{}
   282  	tracker := Tracker{
   283  		tracked: map[keyspaceStr]*updateController{
   284  			"ks3": ks3,
   285  		},
   286  	}
   287  
   288  	th1 := &discovery.TabletHealth{
   289  		Target: &querypb.Target{Keyspace: "ks1"},
   290  	}
   291  	ks1 := tracker.getKeyspaceUpdateController(th1)
   292  
   293  	th2 := &discovery.TabletHealth{
   294  		Target: &querypb.Target{Keyspace: "ks2"},
   295  	}
   296  	ks2 := tracker.getKeyspaceUpdateController(th2)
   297  
   298  	th3 := &discovery.TabletHealth{
   299  		Target: &querypb.Target{Keyspace: "ks3"},
   300  	}
   301  
   302  	assert.NotEqual(t, ks1, ks2, "ks1 and ks2 should not be equal, belongs to different keyspace")
   303  	assert.Equal(t, ks1, tracker.getKeyspaceUpdateController(th1), "received different updateController")
   304  	assert.Equal(t, ks2, tracker.getKeyspaceUpdateController(th2), "received different updateController")
   305  	assert.Equal(t, ks3, tracker.getKeyspaceUpdateController(th3), "received different updateController")
   306  
   307  	assert.NotNil(t, ks1.reloadKeyspace, "ks1 needs to be initialized")
   308  	assert.NotNil(t, ks2.reloadKeyspace, "ks2 needs to be initialized")
   309  	assert.Nil(t, ks3.reloadKeyspace, "ks3 already initialized")
   310  }
   311  
   312  // TestViewsTracking tests that the tracker is able to track views.
   313  func TestViewsTracking(t *testing.T) {
   314  	target := &querypb.Target{Cell: "aa", Keyspace: "ks", Shard: "-80", TabletType: topodatapb.TabletType_PRIMARY}
   315  	tablet := &topodatapb.Tablet{Keyspace: target.Keyspace, Shard: target.Shard, Type: target.TabletType}
   316  
   317  	schemaDefResult := []map[string]string{{
   318  		"prior": "create view prior as select 1 from tbl",
   319  		"t1":    "create view t1 as select 1 from tbl1",
   320  		"t2":    "create view t2 as select 1 from tbl2",
   321  	}, {
   322  		"t2": "create view t2 as select 1,2 from tbl2",
   323  		"t3": "create view t3 as select 1 from tbl3",
   324  	}, {
   325  		"t4": "create view t4 as select 1 from tbl4",
   326  	}}
   327  
   328  	testcases := []struct {
   329  		testName string
   330  		updView  []string
   331  		exp      map[string]string
   332  	}{{
   333  		testName: "new views",
   334  		updView:  []string{"prior", "t1", "t2"},
   335  		exp: map[string]string{
   336  			"t1":    "select 1 from tbl1",
   337  			"t2":    "select 1 from tbl2",
   338  			"prior": "select 1 from tbl"},
   339  	}, {
   340  		testName: "delete prior, updated t2 and new t3",
   341  		updView:  []string{"prior", "t2", "t3"},
   342  		exp: map[string]string{
   343  			"t1": "select 1 from tbl1",
   344  			"t2": "select 1, 2 from tbl2",
   345  			"t3": "select 1 from tbl3"},
   346  	}, {
   347  		testName: "new t4",
   348  		updView:  []string{"t4"},
   349  		exp: map[string]string{
   350  			"t1": "select 1 from tbl1",
   351  			"t2": "select 1, 2 from tbl2",
   352  			"t3": "select 1 from tbl3",
   353  			"t4": "select 1 from tbl4"},
   354  	}}
   355  
   356  	ch := make(chan *discovery.TabletHealth)
   357  	tracker := NewTracker(ch, "", true)
   358  	tracker.tables = nil // making tables map nil - so load keyspace does not try to load the tables information.
   359  	tracker.consumeDelay = 1 * time.Millisecond
   360  	tracker.Start()
   361  	defer tracker.Stop()
   362  
   363  	wg := sync.WaitGroup{}
   364  	tracker.RegisterSignalReceiver(func() {
   365  		wg.Done()
   366  	})
   367  
   368  	sbc := sandboxconn.NewSandboxConn(tablet)
   369  	sbc.SetSchemaResult(schemaDefResult)
   370  
   371  	for count, tcase := range testcases {
   372  		t.Run(tcase.testName, func(t *testing.T) {
   373  			wg.Add(1)
   374  			ch <- &discovery.TabletHealth{
   375  				Conn:    sbc,
   376  				Tablet:  tablet,
   377  				Target:  target,
   378  				Serving: true,
   379  				Stats:   &querypb.RealtimeStats{ViewSchemaChanged: tcase.updView},
   380  			}
   381  
   382  			require.False(t, waitTimeout(&wg, time.Second), "schema was updated but received no signal")
   383  			require.EqualValues(t, count+1, sbc.GetSchemaCount.Get())
   384  
   385  			_, keyspacePresent := tracker.tracked[target.Keyspace]
   386  			require.Equal(t, true, keyspacePresent)
   387  
   388  			for k, v := range tcase.exp {
   389  				utils.MustMatch(t, v, sqlparser.String(tracker.GetViews("ks", k)), "mismatch for table: ", k)
   390  			}
   391  		})
   392  	}
   393  }