// CreateFakeServers returns the servers to use for these tests func CreateFakeServers(t *testing.T) (*tabletconntest.FakeQueryService, topo.Server, string) { cell := "local" // the FakeServer is just slightly modified f := tabletconntest.CreateFakeServer(t) f.TestingGateway = true f.StreamHealthResponse = &querypb.StreamHealthResponse{ Target: tabletconntest.TestTarget, Serving: true, TabletExternallyReparentedTimestamp: 1234589, RealtimeStats: &querypb.RealtimeStats{ SecondsBehindMaster: 1, }, } // The topo server has a single SrvKeyspace ts := zktestserver.New(t, []string{cell}) if err := ts.UpdateSrvKeyspace(context.Background(), cell, tabletconntest.TestTarget.Keyspace, &topodatapb.SrvKeyspace{ Partitions: []*topodatapb.SrvKeyspace_KeyspacePartition{ { ServedType: topodatapb.TabletType_MASTER, ShardReferences: []*topodatapb.ShardReference{ { Name: tabletconntest.TestTarget.Shard, }, }, }, }, }); err != nil { t.Fatalf("can't add srvKeyspace: %v", err) } return f, ts, cell }
// TestCreateShard tests a few cases for CreateShard func TestCreateShard(t *testing.T) { ctx := context.Background() cells := []string{"test_cell"} // Set up topology. ts := zktestserver.New(t, cells) keyspace := "test_keyspace" shard := "0" // create shard in a non-existing keyspace if err := CreateShard(ctx, ts, keyspace, shard); err == nil { t.Fatalf("CreateShard(invalid keyspace) didn't fail") } // create keyspace if err := ts.CreateKeyspace(ctx, keyspace, &topodatapb.Keyspace{}); err != nil { t.Fatalf("CreateKeyspace failed: %v", err) } // create shard should now work if err := CreateShard(ctx, ts, keyspace, shard); err != nil { t.Fatalf("CreateShard failed: %v", err) } }
// TestGetOrCreateShard will create / get 100 shards in a keyspace // for a long time in parallel, making sure the locking and everything // works correctly. func TestGetOrCreateShard(t *testing.T) { ctx := context.Background() cells := []string{"test_cell"} // Set up topology. ts := zktestserver.New(t, cells) // and do massive parallel GetOrCreateShard keyspace := "test_keyspace" wg := sync.WaitGroup{} rand.Seed(time.Now().UnixNano()) for i := 0; i < 100; i++ { wg.Add(1) go func(i int) { defer wg.Done() for j := 0; j < 100; j++ { index := rand.Intn(10) shard := fmt.Sprintf("%v", index) si, err := GetOrCreateShard(ctx, ts, keyspace, shard) if err != nil { t.Errorf("GetOrCreateShard(%v, %v) failed: %v", i, shard, err) } if si.ShardName() != shard { t.Errorf("si.ShardName() is wrong, got %v expected %v", si.ShardName(), shard) } } }(i) } wg.Wait() }
// TestInitMasterShardChecks makes sure the safety checks work func TestInitMasterShardChecks(t *testing.T) { ctx := context.Background() db := fakesqldb.Register() ts := zktestserver.New(t, []string{"cell1", "cell2"}) wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient()) master := NewFakeTablet(t, wr, "cell1", 0, topodatapb.TabletType_MASTER, db) // InitShardMaster with an unknown tablet if err := wr.InitShardMaster(ctx, master.Tablet.Keyspace, master.Tablet.Shard, &topodatapb.TabletAlias{ Cell: master.Tablet.Alias.Cell, Uid: master.Tablet.Alias.Uid + 1, }, false /*force*/, 10*time.Second); err == nil || !strings.Contains(err.Error(), "is not in the shard") { t.Errorf("InitShardMaster with unknown alias returned wrong error: %v", err) } // InitShardMaster with two masters in the shard, no force flag // (master2 needs to run InitTablet with -force, as it is the second // master in the same shard) master2 := NewFakeTablet(t, wr, "cell1", 1, topodatapb.TabletType_MASTER, db, ForceInitTablet()) if err := wr.InitShardMaster(ctx, master2.Tablet.Keyspace, master2.Tablet.Shard, master2.Tablet.Alias, false /*force*/, 10*time.Second); err == nil || !strings.Contains(err.Error(), "is not the only master in the shard") { t.Errorf("InitShardMaster with two masters returned wrong error: %v", err) } // InitShardMaster where the new master fails (use force flag // as we have 2 masters). We force the failure by making the // SQL commands executed on the master unexpected by the test fixture master.StartActionLoop(t, wr) defer master.StopActionLoop(t) master2.StartActionLoop(t, wr) defer master2.StopActionLoop(t) if err := wr.InitShardMaster(ctx, master.Tablet.Keyspace, master.Tablet.Shard, master.Tablet.Alias, true /*force*/, 10*time.Second); err == nil || !strings.Contains(err.Error(), "unexpected extra query") { t.Errorf("InitShardMaster with new master failing in new master InitMaster returned wrong error: %v", err) } }
// TestNewRestartableResultReader tests the correct error handling e.g. // if the connection to a tablet fails due to a canceled context. func TestNewRestartableResultReader(t *testing.T) { wantErr := errors.New("restartable_result_reader_test.go: context canceled") tabletconn.RegisterDialer("fake_dialer", func(tablet *topodatapb.Tablet, timeout time.Duration) (tabletconn.TabletConn, error) { return nil, wantErr }) protocol := flag.CommandLine.Lookup("tablet_protocol").Value.String() flag.Set("tablet_protocol", "fake_dialer") // Restore the previous flag value after the test. defer flag.Set("tablet_protocol", protocol) // Create dependencies e.g. a "singleTabletProvider" instance. ts := zktestserver.New(t, []string{"cell1"}) wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient()) alias := &topodatapb.TabletAlias{ Cell: "cell1", Uid: 1, } tablet := &topodatapb.Tablet{ Keyspace: "ks1", Shard: "-80", Alias: alias, } ctx := context.Background() if err := ts.CreateTablet(ctx, tablet); err != nil { t.Fatalf("CreateTablet failed: %v", err) } tp := newSingleTabletProvider(ctx, ts, alias) _, err := NewRestartableResultReader(ctx, wr.Logger(), tp, nil /* td */, chunk{}, false) if err == nil || !strings.Contains(err.Error(), wantErr.Error()) { t.Fatalf("NewRestartableResultReader() should have failed because the context is canceled: %v", err) } }
func createTestAgent(ctx context.Context, t *testing.T) *ActionAgent { ts := zktestserver.New(t, []string{"cell1"}) if err := ts.CreateKeyspace(ctx, "test_keyspace", &topodatapb.Keyspace{}); err != nil { t.Fatalf("CreateKeyspace failed: %v", err) } if err := ts.CreateShard(ctx, "test_keyspace", "0"); err != nil { t.Fatalf("CreateShard failed: %v", err) } port := int32(1234) tablet := &topodatapb.Tablet{ Alias: tabletAlias, Hostname: "host", PortMap: map[string]int32{ "vt": port, }, Ip: "1.0.0.1", Keyspace: "test_keyspace", Shard: "0", Type: topodatapb.TabletType_SPARE, } if err := ts.CreateTablet(ctx, tablet); err != nil { t.Fatalf("CreateTablet failed: %v", err) } mysqlDaemon := &mysqlctl.FakeMysqlDaemon{MysqlPort: 3306} agent := NewTestActionAgent(ctx, ts, tabletAlias, port, 0, mysqlDaemon) agent.BinlogPlayerMap = NewBinlogPlayerMap(ts, nil, nil) agent.HealthReporter = &fakeHealthCheck{} return agent }
// TestVtctlThrottlerCommands tests all vtctl commands from the // "Resharding Throttler" group. func TestVtctlThrottlerCommands(t *testing.T) { // Run a throttler server using the default process throttle manager. listener, err := net.Listen("tcp", ":0") if err != nil { t.Fatalf("Cannot listen: %v", err) } s := grpc.NewServer() go s.Serve(listener) grpcthrottlerserver.StartServer(s, throttler.GlobalManager) addr := fmt.Sprintf("localhost:%v", listener.Addr().(*net.TCPAddr).Port) ts := zktestserver.New(t, []string{"cell1", "cell2"}) vp := NewVtctlPipe(t, ts) defer vp.Close() // Get and set rate commands do not fail when no throttler is registered. { got, err := vp.RunAndOutput([]string{"ThrottlerMaxRates", "-server", addr}) if err != nil { t.Fatalf("VtctlPipe.RunAndStreamOutput() failed: %v", err) } want := "no active throttlers" if !strings.Contains(got, want) { t.Fatalf("ThrottlerMaxRates() = %v, want substring = %v", got, want) } } { got, err := vp.RunAndOutput([]string{"ThrottlerSetMaxRate", "-server", addr, "23"}) if err != nil { t.Fatalf("VtctlPipe.RunAndStreamOutput() failed: %v", err) } want := "no active throttlers" if !strings.Contains(got, want) { t.Fatalf("ThrottlerSetMaxRate(23) = %v, want substring = %v", got, want) } } // Add a throttler and check the commands again. t1, err := throttler.NewThrottler("t1", "TPS", 1 /* threadCount */, 2323, throttler.ReplicationLagModuleDisabled) if err != nil { t.Fatal(err) } defer t1.Close() // MaxRates() will return the initial rate. expectRate(t, vp, addr, "2323") // Disable the module by setting the rate to 'unlimited'. setRate(t, vp, addr, "unlimited") expectRate(t, vp, addr, "unlimited") // Re-enable it by setting a limit. setRate(t, vp, addr, "9999") expectRate(t, vp, addr, "9999") }
// TestCreateTablet tests all the logic in the topo.CreateTablet method. func TestCreateTablet(t *testing.T) { cell := "cell1" keyspace := "ks1" shard := "shard1" ctx := context.Background() ts := zktestserver.New(t, []string{cell}) // Create a tablet. alias := &topodatapb.TabletAlias{ Cell: cell, Uid: 1, } tablet := &topodatapb.Tablet{ Keyspace: keyspace, Shard: shard, Alias: alias, } if err := ts.CreateTablet(ctx, tablet); err != nil { t.Fatalf("CreateTablet failed: %v", err) } // Get the tablet, make sure it's good. Also check ShardReplication. ti, err := ts.GetTablet(ctx, alias) if err != nil || !proto.Equal(ti.Tablet, tablet) { t.Fatalf("Created Tablet doesn't match: %v %v", ti, err) } sri, err := ts.GetShardReplication(ctx, cell, keyspace, shard) if err != nil || len(sri.Nodes) != 1 || !proto.Equal(sri.Nodes[0].TabletAlias, alias) { t.Fatalf("Created ShardReplication doesn't match: %v %v", sri, err) } // Create the same tablet again, make sure it fails with ErrNodeExists. if err := ts.CreateTablet(ctx, tablet); err != topo.ErrNodeExists { t.Fatalf("CreateTablet(again) returned: %v", err) } // Remove the ShardReplication record, try to create the // tablets again, make sure it's fixed. if err := topo.RemoveShardReplicationRecord(ctx, ts, cell, keyspace, shard, alias); err != nil { t.Fatalf("RemoveShardReplicationRecord failed: %v", err) } sri, err = ts.GetShardReplication(ctx, cell, keyspace, shard) if err != nil || len(sri.Nodes) != 0 { t.Fatalf("Modifed ShardReplication doesn't match: %v %v", sri, err) } if err := ts.CreateTablet(ctx, tablet); err != topo.ErrNodeExists { t.Fatalf("CreateTablet(again and again) returned: %v", err) } sri, err = ts.GetShardReplication(ctx, cell, keyspace, shard) if err != nil || len(sri.Nodes) != 1 || !proto.Equal(sri.Nodes[0].TabletAlias, alias) { t.Fatalf("Created ShardReplication doesn't match: %v %v", sri, err) } }
func TestTabletData(t *testing.T) { db := fakesqldb.Register() ts := zktestserver.New(t, []string{"cell1", "cell2"}) wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient()) if err := ts.CreateKeyspace(context.Background(), "ks", &topodatapb.Keyspace{ ShardingColumnName: "keyspace_id", ShardingColumnType: topodatapb.KeyspaceIdType_UINT64, }); err != nil { t.Fatalf("CreateKeyspace failed: %v", err) } tablet1 := testlib.NewFakeTablet(t, wr, "cell1", 0, topodatapb.TabletType_MASTER, db, testlib.TabletKeyspaceShard(t, "ks", "-80")) tablet1.StartActionLoop(t, wr) defer tablet1.StopActionLoop(t) shsq := newStreamHealthTabletServer(t) grpcqueryservice.Register(tablet1.RPCServer, shsq) thc := newTabletHealthCache(ts) stats := &querypb.RealtimeStats{ HealthError: "testHealthError", SecondsBehindMaster: 72, CpuUsage: 1.1, } // Keep broadcasting until the first result goes through. stop := make(chan struct{}) go func() { for { select { case <-stop: return default: shsq.BroadcastHealth(42, stats) } } }() // Start streaming and wait for the first result. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) result, err := thc.Get(ctx, tablet1.Tablet.Alias) cancel() close(stop) if err != nil { t.Fatalf("thc.Get failed: %v", err) } if got, want := result.RealtimeStats, stats; !proto.Equal(got, want) { t.Errorf("RealtimeStats = %#v, want %#v", got, want) } }
func TestTabletExternallyReparentedFailedOldMaster(t *testing.T) { tabletmanager.SetReparentFlags(time.Minute /* finalizeTimeout */) ctx := context.Background() db := fakesqldb.Register() ts := zktestserver.New(t, []string{"cell1", "cell2"}) wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient()) // Create an old master, a new master, and a good slave. oldMaster := NewFakeTablet(t, wr, "cell1", 0, topodatapb.TabletType_MASTER, db) newMaster := NewFakeTablet(t, wr, "cell1", 1, topodatapb.TabletType_REPLICA, db) goodSlave := NewFakeTablet(t, wr, "cell1", 2, topodatapb.TabletType_REPLICA, db) // Reparent to a replica, and pretend the old master is not responding. // On the elected master, we will respond to // TabletActionSlaveWasPromoted newMaster.StartActionLoop(t, wr) defer newMaster.StopActionLoop(t) // On the old master, we will only get a // TabletActionSlaveWasRestarted call, let's just not // respond to it at all // On the good slave, we will respond to // TabletActionSlaveWasRestarted. goodSlave.StartActionLoop(t, wr) defer goodSlave.StopActionLoop(t) // The reparent should work as expected here t.Logf("TabletExternallyReparented(new master) expecting success") tmc := tmclient.NewTabletManagerClient() ti, err := ts.GetTablet(ctx, newMaster.Tablet.Alias) if err != nil { t.Fatalf("GetTablet failed: %v", err) } waitID := makeWaitID() if err := tmc.TabletExternallyReparented(context.Background(), ti.Tablet, waitID); err != nil { t.Fatalf("TabletExternallyReparented(replica) failed: %v", err) } waitForExternalReparent(t, waitID) // check the old master was converted to replica tablet, err := ts.GetTablet(ctx, oldMaster.Tablet.Alias) if err != nil { t.Fatalf("GetTablet(%v) failed: %v", oldMaster.Tablet.Alias, err) } if tablet.Type != topodatapb.TabletType_REPLICA { t.Fatalf("old master should be spare but is: %v", tablet.Type) } }
func TestReparentTablet(t *testing.T) { ctx := context.Background() db := fakesqldb.Register() ts := zktestserver.New(t, []string{"cell1", "cell2"}) wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient()) // create shard and tablets if err := ts.CreateShard(ctx, "test_keyspace", "0"); err != nil { t.Fatalf("CreateShard failed: %v", err) } master := NewFakeTablet(t, wr, "cell1", 1, topodatapb.TabletType_MASTER, db) slave := NewFakeTablet(t, wr, "cell1", 2, topodatapb.TabletType_REPLICA, db) // mark the master inside the shard si, err := ts.GetShard(ctx, "test_keyspace", "0") if err != nil { t.Fatalf("GetShard failed: %v", err) } si.MasterAlias = master.Tablet.Alias if err := ts.UpdateShard(ctx, si); err != nil { t.Fatalf("UpdateShard failed: %v", err) } // master action loop (to initialize host and port) master.StartActionLoop(t, wr) defer master.StopActionLoop(t) // slave loop slave.FakeMysqlDaemon.SetMasterCommandsInput = fmt.Sprintf("%v:%v", master.Tablet.Hostname, master.Tablet.PortMap["mysql"]) slave.FakeMysqlDaemon.SetMasterCommandsResult = []string{"set master cmd 1"} slave.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ "set master cmd 1", } slave.StartActionLoop(t, wr) defer slave.StopActionLoop(t) // run ReparentTablet if err := wr.ReparentTablet(ctx, slave.Tablet.Alias); err != nil { t.Fatalf("ReparentTablet failed: %v", err) } // check what was run if err := slave.FakeMysqlDaemon.CheckSuperQueryList(); err != nil { t.Fatalf("slave.FakeMysqlDaemon.CheckSuperQueryList failed: %v", err) } checkSemiSyncEnabled(t, false, true, slave) }
// TestTabletExternallyReparentedWithDifferentMysqlPort makes sure // that if mysql is restarted on the master-elect tablet and has a different // port, we pick it up correctly. func TestTabletExternallyReparentedWithDifferentMysqlPort(t *testing.T) { tabletmanager.SetReparentFlags(time.Minute /* finalizeTimeout */) ctx := context.Background() db := fakesqldb.Register() ts := zktestserver.New(t, []string{"cell1"}) wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient()) // Create an old master, a new master, two good slaves, one bad slave oldMaster := NewFakeTablet(t, wr, "cell1", 0, topodatapb.TabletType_MASTER, db) newMaster := NewFakeTablet(t, wr, "cell1", 1, topodatapb.TabletType_REPLICA, db) goodSlave := NewFakeTablet(t, wr, "cell1", 2, topodatapb.TabletType_REPLICA, db) // Now we're restarting mysql on a different port, 3301->3303 // but without updating the Tablet record in topology. // On the elected master, we will respond to // TabletActionSlaveWasPromoted, so we need a MysqlDaemon // that returns no master, and the new port (as returned by mysql) newMaster.FakeMysqlDaemon.MysqlPort = 3303 newMaster.StartActionLoop(t, wr) defer newMaster.StopActionLoop(t) // On the old master, we will only respond to // TabletActionSlaveWasRestarted and point to the new mysql port oldMaster.StartActionLoop(t, wr) defer oldMaster.StopActionLoop(t) // On the good slaves, we will respond to // TabletActionSlaveWasRestarted and point to the new mysql port goodSlave.StartActionLoop(t, wr) defer goodSlave.StopActionLoop(t) // This tests the good case, where everything works as planned t.Logf("TabletExternallyReparented(new master) expecting success") tmc := tmclient.NewTabletManagerClient() ti, err := ts.GetTablet(ctx, newMaster.Tablet.Alias) if err != nil { t.Fatalf("GetTablet failed: %v", err) } waitID := makeWaitID() if err := tmc.TabletExternallyReparented(context.Background(), ti.Tablet, waitID); err != nil { t.Fatalf("TabletExternallyReparented(replica) failed: %v", err) } waitForExternalReparent(t, waitID) }
func TestShard(t *testing.T) { cell := "cell1" keyspace := "ks1" shard := "sh1" ctx := context.Background() ts := zktestserver.New(t, []string{cell}) // Create a Keyspace / Shard if err := ts.CreateKeyspace(ctx, keyspace, &topodatapb.Keyspace{}); err != nil { t.Fatalf("CreateKeyspace failed: %v", err) } if err := ts.CreateShard(ctx, keyspace, shard); err != nil { t.Fatalf("CreateShard failed: %v", err) } // Hack the zookeeper backend to create an error for GetShard. zconn := ts.Impl.(*zktestserver.TestServer).Impl.(*zktopo.Server).GetZConn() if _, err := zconn.Set(path.Join(zktopo.GlobalKeyspacesPath, keyspace, "shards", shard), []byte{}, -1); err != nil { t.Fatalf("failed to hack the shard: %v", err) } // Create the workflow, run the validator. w := &Workflow{ logger: logutil.NewMemoryLogger(), } sv := &ShardValidator{} if err := sv.Audit(ctx, ts, w); err != nil { t.Fatalf("Audit failed: %v", err) } if len(w.fixers) != 1 { t.Fatalf("fixer not added: %v", w.fixers) } if !strings.Contains(w.fixers[0].message, "bad shard data") { t.Errorf("bad message: %v ", w.fixers[0].message) } // Run Delete, make sure the entry is removed. if err := w.fixers[0].fixer.Action(ctx, "Delete"); err != nil { t.Fatalf("Action failed: %v", err) } shards, err := ts.GetShardNames(ctx, keyspace) if err != nil || len(shards) != 0 { t.Errorf("bad GetShardNames output: %v %v ", shards, err) } }
func TestVersion(t *testing.T) { // We need to run this test with the /debug/vars version of the // plugin. wrangler.ResetDebugVarsGetVersion() // Initialize our environment db := fakesqldb.Register() ts := zktestserver.New(t, []string{"cell1", "cell2"}) wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient()) vp := NewVtctlPipe(t, ts) defer vp.Close() // couple tablets is enough sourceMaster := NewFakeTablet(t, wr, "cell1", 10, topodatapb.TabletType_MASTER, db, TabletKeyspaceShard(t, "source", "0"), StartHTTPServer()) sourceReplica := NewFakeTablet(t, wr, "cell1", 11, topodatapb.TabletType_REPLICA, db, TabletKeyspaceShard(t, "source", "0"), StartHTTPServer()) // sourceMaster loop sourceMasterGitRev := "fake git rev" sourceMaster.StartActionLoop(t, wr) sourceMaster.HTTPServer.Handler.(*http.ServeMux).HandleFunc("/debug/vars", expvarHandler(&sourceMasterGitRev)) defer sourceMaster.StopActionLoop(t) // sourceReplica loop sourceReplicaGitRev := "fake git rev" sourceReplica.StartActionLoop(t, wr) sourceReplica.HTTPServer.Handler.(*http.ServeMux).HandleFunc("/debug/vars", expvarHandler(&sourceReplicaGitRev)) defer sourceReplica.StopActionLoop(t) // test when versions are the same if err := vp.Run([]string{"ValidateVersionKeyspace", sourceMaster.Tablet.Keyspace}); err != nil { t.Fatalf("ValidateVersionKeyspace(same) failed: %v", err) } // test when versions are different sourceReplicaGitRev = "different fake git rev" if err := vp.Run([]string{"ValidateVersionKeyspace", sourceMaster.Tablet.Keyspace}); err == nil || !strings.Contains(err.Error(), "is different than slave") { t.Fatalf("ValidateVersionKeyspace(different) returned an unexpected error: %v", err) } }
func TestHandleExplorerRedirect(t *testing.T) { ctx := context.Background() ts := zktestserver.New(t, []string{"cell1"}) if err := ts.CreateTablet(ctx, &topodatapb.Tablet{ Alias: &topodatapb.TabletAlias{ Cell: "cell1", Uid: 123, }, Keyspace: "test_keyspace", Shard: "123-456", }); err != nil { t.Fatalf("CreateTablet failed: %v", err) } table := map[string]string{ "/explorers/redirect?type=keyspace&keyspace=test_keyspace": "/app/#/keyspaces/", "/explorers/redirect?type=shard&keyspace=test_keyspace&shard=-80": "/app/#/shard/test_keyspace/-80", "/explorers/redirect?type=srv_keyspace&keyspace=test_keyspace&cell=cell1": "/app/#/keyspaces/", "/explorers/redirect?type=srv_shard&keyspace=test_keyspace&shard=-80&cell=cell1": "/app/#/shard/test_keyspace/-80", "/explorers/redirect?type=srv_type&keyspace=test_keyspace&shard=-80&cell=cell1&tablet_type=replica": "/app/#/shard/test_keyspace/-80", "/explorers/redirect?type=tablet&alias=cell1-123": "/app/#/shard/test_keyspace/123-456", "/explorers/redirect?type=replication&keyspace=test_keyspace&shard=-80&cell=cell1": "/app/#/shard/test_keyspace/-80", } for input, want := range table { request, err := http.NewRequest("GET", input, nil) if err != nil { t.Fatalf("NewRequest error: %v", err) } if err := request.ParseForm(); err != nil { t.Fatalf("ParseForm error: %v", err) } got, err := handleExplorerRedirect(ctx, ts, request) if err != nil { t.Fatalf("handleExplorerRedirect error: %v", err) } if got != want { t.Errorf("handlExplorerRedirect(%#v) = %#v, want %#v", input, got, want) } } }
func newReplica(lagUpdateInterval, degrationInterval, degrationDuration time.Duration) *replica { t := &testing.T{} ts := zktestserver.New(t, []string{"cell1"}) wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient()) db := fakesqldb.Register() fakeTablet := testlib.NewFakeTablet(t, wr, "cell1", 0, topodatapb.TabletType_REPLICA, db, testlib.TabletKeyspaceShard(t, "ks", "-80")) fakeTablet.StartActionLoop(t, wr) target := querypb.Target{ Keyspace: "ks", Shard: "-80", TabletType: topodatapb.TabletType_REPLICA, } qs := fakes.NewStreamHealthQueryService(target) grpcqueryservice.Register(fakeTablet.RPCServer, qs) throttler, err := throttler.NewThrottler("replica", "TPS", 1, *rate, throttler.ReplicationLagModuleDisabled) if err != nil { log.Fatal(err) } var nextDegration time.Time if degrationInterval != time.Duration(0) { nextDegration = time.Now().Add(degrationInterval) } r := &replica{ fakeTablet: fakeTablet, qs: qs, throttler: throttler, replicationStream: make(chan time.Time, 1*1024*1024), lagUpdateInterval: lagUpdateInterval, degrationInterval: degrationInterval, degrationDuration: degrationDuration, nextDegration: nextDegration, stopChan: make(chan struct{}), } r.wg.Add(1) go r.processReplicationStream() return r }
// TestCreateShardCustomSharding checks ServedTypes is set correctly // when creating multiple custom sharding shards func TestCreateShardCustomSharding(t *testing.T) { ctx := context.Background() cells := []string{"test_cell"} // Set up topology. ts := zktestserver.New(t, cells) // create keyspace keyspace := "test_keyspace" if err := ts.CreateKeyspace(ctx, keyspace, &topodatapb.Keyspace{}); err != nil { t.Fatalf("CreateKeyspace failed: %v", err) } // create first shard in keyspace shard0 := "0" if err := CreateShard(ctx, ts, keyspace, shard0); err != nil { t.Fatalf("CreateShard(shard0) failed: %v", err) } if si, err := ts.GetShard(ctx, keyspace, shard0); err != nil { t.Fatalf("GetShard(shard0) failed: %v", err) } else { if len(si.ServedTypes) != 3 { t.Fatalf("shard0 should have all 3 served types") } } // create second shard in keyspace shard1 := "1" if err := CreateShard(ctx, ts, keyspace, shard1); err != nil { t.Fatalf("CreateShard(shard1) failed: %v", err) } if si, err := ts.GetShard(ctx, keyspace, shard1); err != nil { t.Fatalf("GetShard(shard1) failed: %v", err) } else { if len(si.ServedTypes) != 3 { t.Fatalf("shard1 should have all 3 served types") } } }
func createTestAgent(ctx context.Context, t *testing.T, preStart func(*ActionAgent)) (*ActionAgent, chan<- *binlogplayer.VtClientMock) { ts := zktestserver.New(t, []string{"cell1"}) if err := ts.CreateKeyspace(ctx, "test_keyspace", &topodatapb.Keyspace{}); err != nil { t.Fatalf("CreateKeyspace failed: %v", err) } if err := ts.CreateShard(ctx, "test_keyspace", "0"); err != nil { t.Fatalf("CreateShard failed: %v", err) } port := int32(1234) tablet := &topodatapb.Tablet{ Alias: tabletAlias, Hostname: "host", PortMap: map[string]int32{ "vt": port, }, Ip: "1.0.0.1", Keyspace: "test_keyspace", Shard: "0", Type: topodatapb.TabletType_REPLICA, } if err := ts.CreateTablet(ctx, tablet); err != nil { t.Fatalf("CreateTablet failed: %v", err) } mysqlDaemon := &mysqlctl.FakeMysqlDaemon{MysqlPort: 3306} agent := NewTestActionAgent(ctx, ts, tabletAlias, port, 0, mysqlDaemon, preStart) vtClientMocksChannel := make(chan *binlogplayer.VtClientMock, 1) agent.BinlogPlayerMap = NewBinlogPlayerMap(ts, mysqlDaemon, func() binlogplayer.VtClient { return <-vtClientMocksChannel }) agent.HealthReporter = &fakeHealthCheck{} return agent, vtClientMocksChannel }
func TestPlannedReparentShard(t *testing.T) { db := fakesqldb.Register() ts := zktestserver.New(t, []string{"cell1", "cell2"}) wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient()) vp := NewVtctlPipe(t, ts) defer vp.Close() // Create a master, a couple good slaves oldMaster := NewFakeTablet(t, wr, "cell1", 0, topodatapb.TabletType_MASTER, db) newMaster := NewFakeTablet(t, wr, "cell1", 1, topodatapb.TabletType_REPLICA, db) goodSlave1 := NewFakeTablet(t, wr, "cell1", 2, topodatapb.TabletType_REPLICA, db) goodSlave2 := NewFakeTablet(t, wr, "cell2", 3, topodatapb.TabletType_REPLICA, db) // new master newMaster.FakeMysqlDaemon.ReadOnly = true newMaster.FakeMysqlDaemon.Replicating = true newMaster.FakeMysqlDaemon.WaitMasterPosition = replication.Position{ GTIDSet: replication.MariadbGTID{ Domain: 7, Server: 123, Sequence: 990, }, } newMaster.FakeMysqlDaemon.PromoteSlaveResult = replication.Position{ GTIDSet: replication.MariadbGTID{ Domain: 7, Server: 456, Sequence: 991, }, } newMaster.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ "CREATE DATABASE IF NOT EXISTS _vt", "SUBCREATE TABLE IF NOT EXISTS _vt.reparent_journal", "SUBINSERT INTO _vt.reparent_journal (time_created_ns, action_name, master_alias, replication_position) VALUES", } newMaster.StartActionLoop(t, wr) defer newMaster.StopActionLoop(t) // old master oldMaster.FakeMysqlDaemon.ReadOnly = false oldMaster.FakeMysqlDaemon.Replicating = false oldMaster.FakeMysqlDaemon.DemoteMasterPosition = newMaster.FakeMysqlDaemon.WaitMasterPosition oldMaster.FakeMysqlDaemon.SetMasterCommandsInput = fmt.Sprintf("%v:%v", newMaster.Tablet.Hostname, newMaster.Tablet.PortMap["mysql"]) oldMaster.FakeMysqlDaemon.SetMasterCommandsResult = []string{"set master cmd 1"} oldMaster.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ "set master cmd 1", "START SLAVE", } oldMaster.StartActionLoop(t, wr) defer oldMaster.StopActionLoop(t) oldMaster.Agent.QueryServiceControl.(*tabletservermock.Controller).SetQueryServiceEnabledForTests(true) // good slave 1 is replicating goodSlave1.FakeMysqlDaemon.ReadOnly = true goodSlave1.FakeMysqlDaemon.Replicating = true goodSlave1.FakeMysqlDaemon.SetMasterCommandsInput = fmt.Sprintf("%v:%v", newMaster.Tablet.Hostname, newMaster.Tablet.PortMap["mysql"]) goodSlave1.FakeMysqlDaemon.SetMasterCommandsResult = []string{"set master cmd 1"} goodSlave1.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ "STOP SLAVE", "set master cmd 1", "START SLAVE", } goodSlave1.StartActionLoop(t, wr) defer goodSlave1.StopActionLoop(t) // good slave 2 is not replicating goodSlave2.FakeMysqlDaemon.ReadOnly = true goodSlave2.FakeMysqlDaemon.Replicating = false goodSlave2.FakeMysqlDaemon.SetMasterCommandsInput = fmt.Sprintf("%v:%v", newMaster.Tablet.Hostname, newMaster.Tablet.PortMap["mysql"]) goodSlave2.FakeMysqlDaemon.SetMasterCommandsResult = []string{"set master cmd 1"} goodSlave2.StartActionLoop(t, wr) goodSlave2.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ "set master cmd 1", } defer goodSlave2.StopActionLoop(t) // run PlannedReparentShard if err := vp.Run([]string{"PlannedReparentShard", "-wait_slave_timeout", "10s", newMaster.Tablet.Keyspace + "/" + newMaster.Tablet.Shard, topoproto.TabletAliasString(newMaster.Tablet.Alias)}); err != nil { t.Fatalf("PlannedReparentShard failed: %v", err) } // check what was run if err := newMaster.FakeMysqlDaemon.CheckSuperQueryList(); err != nil { t.Errorf("newMaster.FakeMysqlDaemon.CheckSuperQueryList failed: %v", err) } if err := oldMaster.FakeMysqlDaemon.CheckSuperQueryList(); err != nil { t.Errorf("oldMaster.FakeMysqlDaemon.CheckSuperQueryList failed: %v", err) } if err := goodSlave1.FakeMysqlDaemon.CheckSuperQueryList(); err != nil { t.Errorf("goodSlave1.FakeMysqlDaemon.CheckSuperQueryList failed: %v", err) } if err := goodSlave2.FakeMysqlDaemon.CheckSuperQueryList(); err != nil { t.Errorf("goodSlave2.FakeMysqlDaemon.CheckSuperQueryList failed: %v", err) } if newMaster.FakeMysqlDaemon.ReadOnly { t.Errorf("newMaster.FakeMysqlDaemon.ReadOnly set") } if !oldMaster.FakeMysqlDaemon.ReadOnly { t.Errorf("oldMaster.FakeMysqlDaemon.ReadOnly not set") } if !goodSlave1.FakeMysqlDaemon.ReadOnly { t.Errorf("goodSlave1.FakeMysqlDaemon.ReadOnly not set") } if !goodSlave2.FakeMysqlDaemon.ReadOnly { t.Errorf("goodSlave2.FakeMysqlDaemon.ReadOnly not set") } if !oldMaster.Agent.QueryServiceControl.IsServing() { t.Errorf("oldMaster...QueryServiceControl not serving") } // verify the old master was told to start replicating (and not // the slave that wasn't replicating in the first place) if !oldMaster.FakeMysqlDaemon.Replicating { t.Errorf("oldMaster.FakeMysqlDaemon.Replicating not set") } if !goodSlave1.FakeMysqlDaemon.Replicating { t.Errorf("goodSlave1.FakeMysqlDaemon.Replicating not set") } if goodSlave2.FakeMysqlDaemon.Replicating { t.Errorf("goodSlave2.FakeMysqlDaemon.Replicating set") } checkSemiSyncEnabled(t, true, true, newMaster) checkSemiSyncEnabled(t, false, true, goodSlave1, goodSlave2, oldMaster) }
func TestVerticalSplitDiff(t *testing.T) { db := fakesqldb.Register() ts := zktestserver.New(t, []string{"cell1", "cell2"}) ctx := context.Background() wi := NewInstance(ts, "cell1", time.Second) sourceMaster := testlib.NewFakeTablet(t, wi.wr, "cell1", 0, topodatapb.TabletType_MASTER, db, testlib.TabletKeyspaceShard(t, "source_ks", "0")) sourceRdonly1 := testlib.NewFakeTablet(t, wi.wr, "cell1", 1, topodatapb.TabletType_RDONLY, db, testlib.TabletKeyspaceShard(t, "source_ks", "0")) sourceRdonly2 := testlib.NewFakeTablet(t, wi.wr, "cell1", 2, topodatapb.TabletType_RDONLY, db, testlib.TabletKeyspaceShard(t, "source_ks", "0")) // Create the destination keyspace with the appropriate ServedFromMap ki := &topodatapb.Keyspace{ ServedFroms: []*topodatapb.Keyspace_ServedFrom{ { TabletType: topodatapb.TabletType_MASTER, Keyspace: "source_ks", }, { TabletType: topodatapb.TabletType_REPLICA, Keyspace: "source_ks", }, { TabletType: topodatapb.TabletType_RDONLY, Keyspace: "source_ks", }, }, } wi.wr.TopoServer().CreateKeyspace(ctx, "destination_ks", ki) destMaster := testlib.NewFakeTablet(t, wi.wr, "cell1", 10, topodatapb.TabletType_MASTER, db, testlib.TabletKeyspaceShard(t, "destination_ks", "0")) destRdonly1 := testlib.NewFakeTablet(t, wi.wr, "cell1", 11, topodatapb.TabletType_RDONLY, db, testlib.TabletKeyspaceShard(t, "destination_ks", "0")) destRdonly2 := testlib.NewFakeTablet(t, wi.wr, "cell1", 12, topodatapb.TabletType_RDONLY, db, testlib.TabletKeyspaceShard(t, "destination_ks", "0")) for _, ft := range []*testlib.FakeTablet{sourceMaster, sourceRdonly1, sourceRdonly2, destMaster, destRdonly1, destRdonly2} { ft.StartActionLoop(t, wi.wr) defer ft.StopActionLoop(t) } wi.wr.SetSourceShards(ctx, "destination_ks", "0", []*topodatapb.TabletAlias{sourceRdonly1.Tablet.Alias}, []string{"moving.*", "view1"}) // add the topo and schema data we'll need if err := wi.wr.RebuildKeyspaceGraph(ctx, "source_ks", nil); err != nil { t.Fatalf("RebuildKeyspaceGraph failed: %v", err) } if err := wi.wr.RebuildKeyspaceGraph(ctx, "destination_ks", nil); err != nil { t.Fatalf("RebuildKeyspaceGraph failed: %v", err) } for _, rdonly := range []*testlib.FakeTablet{sourceRdonly1, sourceRdonly2, destRdonly1, destRdonly2} { // both source and destination have the table definition for 'moving1'. // source also has "staying1" while destination has "extra1". // (Both additional tables should be ignored by the diff.) extraTable := "staying1" if rdonly == destRdonly1 || rdonly == destRdonly2 { extraTable = "extra1" } rdonly.FakeMysqlDaemon.Schema = &tabletmanagerdatapb.SchemaDefinition{ DatabaseSchema: "", TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ { Name: "moving1", Columns: []string{"id", "msg"}, PrimaryKeyColumns: []string{"id"}, Type: tmutils.TableBaseTable, }, { Name: extraTable, Columns: []string{"id", "msg"}, PrimaryKeyColumns: []string{"id"}, Type: tmutils.TableBaseTable, }, { Name: "view1", Type: tmutils.TableView, }, }, } qs := fakes.NewStreamHealthQueryService(rdonly.Target()) qs.AddDefaultHealthResponse() grpcqueryservice.Register(rdonly.RPCServer, &verticalDiffTabletServer{ t: t, StreamHealthQueryService: qs, }) } // Run the vtworker command. args := []string{"VerticalSplitDiff", "destination_ks/0"} // We need to use FakeTabletManagerClient because we don't // have a good way to fake the binlog player yet, which is // necessary for synchronizing replication. wr := wrangler.New(logutil.NewConsoleLogger(), ts, newFakeTMCTopo(ts)) if err := runCommand(t, wi, wr, args); err != nil { t.Fatal(err) } }
// CreateWorkerInstance returns a properly configured vtworker instance. func CreateWorkerInstance(t *testing.T) *worker.Instance { ts := zktestserver.New(t, []string{"cell1", "cell2"}) return worker.NewInstance(context.Background(), ts, "cell1", 1*time.Second) }
func testWaitForDrain(t *testing.T, desc, cells string, drain drainDirective, expectedErrors []string) { const keyspace = "ks" const shard = "-80" db := fakesqldb.Register() ts := zktestserver.New(t, []string{"cell1", "cell2"}) wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient()) flag.Set("vtctl_healthcheck_timeout", "0.25s") vp := NewVtctlPipe(t, ts) defer vp.Close() // Create keyspace. if err := ts.CreateKeyspace(context.Background(), keyspace, &topodatapb.Keyspace{ ShardingColumnName: "keyspace_id", ShardingColumnType: topodatapb.KeyspaceIdType_UINT64, }); err != nil { t.Fatalf("CreateKeyspace failed: %v", err) } t1 := NewFakeTablet(t, wr, "cell1", 0, topodatapb.TabletType_REPLICA, db, TabletKeyspaceShard(t, keyspace, shard)) t2 := NewFakeTablet(t, wr, "cell2", 1, topodatapb.TabletType_REPLICA, db, TabletKeyspaceShard(t, keyspace, shard)) for _, ft := range []*FakeTablet{t1, t2} { ft.StartActionLoop(t, wr) defer ft.StopActionLoop(t) } target := querypb.Target{ Keyspace: keyspace, Shard: shard, TabletType: topodatapb.TabletType_REPLICA, } fqs1 := newFakeQueryService(target) fqs2 := newFakeQueryService(target) grpcqueryservice.RegisterForTest(t1.RPCServer, fqs1) grpcqueryservice.RegisterForTest(t2.RPCServer, fqs2) // Run vtctl WaitForDrain and react depending on its output. timeout := "0.5s" if len(expectedErrors) == 0 { // Tests with a positive outcome should have a more generous timeout to // avoid flakyness. timeout = "30s" } stream, err := vp.RunAndStreamOutput( []string{"WaitForDrain", "-cells", cells, "-retry_delay", "100ms", "-timeout", timeout, keyspace + "/" + shard, topodatapb.TabletType_REPLICA.String()}) if err != nil { t.Fatalf("VtctlPipe.RunAndStreamOutput() failed: %v", err) } // QPS = 1.0. Tablets are not drained yet. fqs1.addHealthResponse(1.0) fqs2.addHealthResponse(1.0) var le *logutilpb.Event for { le, err = stream.Recv() if err != nil { break } line := logutil.EventString(le) t.Logf(line) if strings.Contains(line, "for all healthy tablets to be drained") { t.Log("Successfully waited for WaitForDrain to be blocked because tablets have a QPS rate > 0.0") break } else { t.Log("waiting for WaitForDrain to see a QPS rate > 0.0") } } if drain&DrainCell1 != 0 { fqs1.addHealthResponse(0.0) } else { fqs1.addHealthResponse(2.0) } if drain&DrainCell2 != 0 { fqs2.addHealthResponse(0.0) } else { fqs2.addHealthResponse(2.0) } // If a cell was drained, rate should go below <0.0 now. // If not all selected cells were drained, this will end after "-timeout". for { le, err = stream.Recv() if err == nil { vp.t.Logf(logutil.EventString(le)) } else { break } } if len(expectedErrors) == 0 { if err != io.EOF { t.Fatalf("TestWaitForDrain: %v: no error expected but got: %v", desc, err) } // else: Success. } else { if err == nil || err == io.EOF { t.Fatalf("TestWaitForDrain: %v: error expected but got none", desc) } for _, errString := range expectedErrors { if !strings.Contains(err.Error(), errString) { t.Fatalf("TestWaitForDrain: %v: error does not include expected string. got: %v want: %v", desc, err, errString) } } // Success. } }
func TestFindAllKeyspaceShards(t *testing.T) { ctx := context.Background() ts := zktestserver.New(t, []string{"cell1", "cell2"}) // no keyspace / shards ks, err := findAllKeyspaceShards(ctx, ts, "cell1") if err != nil { t.Errorf("unexpected error: %v", err) } if len(ks) > 0 { t.Errorf("why did I get anything? %v", ks) } // add one if err := ts.UpdateSrvKeyspace(ctx, "cell1", "test_keyspace", &topodatapb.SrvKeyspace{ Partitions: []*topodatapb.SrvKeyspace_KeyspacePartition{ { ServedType: topodatapb.TabletType_MASTER, ShardReferences: []*topodatapb.ShardReference{ { Name: "test_shard0", }, }, }, }, }); err != nil { t.Fatalf("can't add srvKeyspace: %v", err) } // get it ks, err = findAllKeyspaceShards(ctx, ts, "cell1") if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(ks, map[keyspaceShard]bool{ keyspaceShard{ keyspace: "test_keyspace", shard: "test_shard0", }: true, }) { t.Errorf("got wrong value: %v", ks) } // add another one if err := ts.UpdateSrvKeyspace(ctx, "cell1", "test_keyspace2", &topodatapb.SrvKeyspace{ Partitions: []*topodatapb.SrvKeyspace_KeyspacePartition{ { ServedType: topodatapb.TabletType_MASTER, ShardReferences: []*topodatapb.ShardReference{ { Name: "test_shard1", }, }, }, { ServedType: topodatapb.TabletType_MASTER, ShardReferences: []*topodatapb.ShardReference{ { Name: "test_shard2", }, }, }, }, }); err != nil { t.Fatalf("can't add srvKeyspace: %v", err) } // get it ks, err = findAllKeyspaceShards(ctx, ts, "cell1") if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(ks, map[keyspaceShard]bool{ keyspaceShard{ keyspace: "test_keyspace", shard: "test_shard0", }: true, keyspaceShard{ keyspace: "test_keyspace2", shard: "test_shard1", }: true, keyspaceShard{ keyspace: "test_keyspace2", shard: "test_shard2", }: true, }) { t.Errorf("got wrong value: %v", ks) } }
// TestInitMasterShard is the good scenario test, where everything // works as planned func TestInitMasterShard(t *testing.T) { ctx := context.Background() db := fakesqldb.Register() ts := zktestserver.New(t, []string{"cell1", "cell2"}) wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient()) vp := NewVtctlPipe(t, ts) defer vp.Close() db.AddQuery("CREATE DATABASE IF NOT EXISTS `vt_test_keyspace`", &sqltypes.Result{}) // Create a master, a couple good slaves master := NewFakeTablet(t, wr, "cell1", 0, topodatapb.TabletType_MASTER, db) goodSlave1 := NewFakeTablet(t, wr, "cell1", 1, topodatapb.TabletType_REPLICA, db) goodSlave2 := NewFakeTablet(t, wr, "cell2", 2, topodatapb.TabletType_REPLICA, db) // Master: set a plausible ReplicationPosition to return, // and expect to add entry in _vt.reparent_journal master.FakeMysqlDaemon.CurrentMasterPosition = replication.Position{ GTIDSet: replication.MariadbGTID{ Domain: 5, Server: 456, Sequence: 890, }, } master.FakeMysqlDaemon.ReadOnly = true master.FakeMysqlDaemon.ResetReplicationResult = []string{"reset rep 1"} master.FakeMysqlDaemon.SetSlavePositionCommandsResult = []string{"new master shouldn't use this"} master.FakeMysqlDaemon.SetMasterCommandsResult = []string{"new master shouldn't use this"} master.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ "reset rep 1", "CREATE DATABASE IF NOT EXISTS _vt", "SUBCREATE TABLE IF NOT EXISTS _vt.reparent_journal", "CREATE DATABASE IF NOT EXISTS _vt", "SUBCREATE TABLE IF NOT EXISTS _vt.reparent_journal", "SUBINSERT INTO _vt.reparent_journal (time_created_ns, action_name, master_alias, replication_position) VALUES", } master.StartActionLoop(t, wr) defer master.StopActionLoop(t) // Slave1: expect to be reset and re-parented goodSlave1.FakeMysqlDaemon.ReadOnly = true goodSlave1.FakeMysqlDaemon.ResetReplicationResult = []string{"reset rep 1"} goodSlave1.FakeMysqlDaemon.SetSlavePositionCommandsPos = master.FakeMysqlDaemon.CurrentMasterPosition goodSlave1.FakeMysqlDaemon.SetSlavePositionCommandsResult = []string{"cmd1"} goodSlave1.FakeMysqlDaemon.SetMasterCommandsInput = fmt.Sprintf("%v:%v", master.Tablet.Hostname, master.Tablet.PortMap["mysql"]) goodSlave1.FakeMysqlDaemon.SetMasterCommandsResult = []string{"set master cmd 1"} goodSlave1.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ "reset rep 1", "cmd1", "set master cmd 1", "START SLAVE", } goodSlave1.StartActionLoop(t, wr) defer goodSlave1.StopActionLoop(t) // Slave2: expect to be re-parented goodSlave2.FakeMysqlDaemon.ReadOnly = true goodSlave2.FakeMysqlDaemon.ResetReplicationResult = []string{"reset rep 2"} goodSlave2.FakeMysqlDaemon.SetSlavePositionCommandsPos = master.FakeMysqlDaemon.CurrentMasterPosition goodSlave2.FakeMysqlDaemon.SetSlavePositionCommandsResult = []string{"cmd1", "cmd2"} goodSlave2.FakeMysqlDaemon.SetMasterCommandsInput = fmt.Sprintf("%v:%v", master.Tablet.Hostname, master.Tablet.PortMap["mysql"]) goodSlave2.FakeMysqlDaemon.SetMasterCommandsResult = []string{"set master cmd 1"} goodSlave2.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ "reset rep 2", "cmd1", "cmd2", "set master cmd 1", "START SLAVE", } goodSlave2.StartActionLoop(t, wr) defer goodSlave2.StopActionLoop(t) // run InitShardMaster if err := vp.Run([]string{"InitShardMaster", "-wait_slave_timeout", "10s", master.Tablet.Keyspace + "/" + master.Tablet.Shard, topoproto.TabletAliasString(master.Tablet.Alias)}); err != nil { t.Fatalf("InitShardMaster failed: %v", err) } // check what was run if master.FakeMysqlDaemon.ReadOnly { t.Errorf("master was not turned read-write") } si, err := ts.GetShard(ctx, master.Tablet.Keyspace, master.Tablet.Shard) if err != nil { t.Fatalf("GetShard failed: %v", err) } if !topoproto.TabletAliasEqual(si.MasterAlias, master.Tablet.Alias) { t.Errorf("unexpected shard master alias, got %v expected %v", si.MasterAlias, master.Tablet.Alias) } if err := master.FakeMysqlDaemon.CheckSuperQueryList(); err != nil { t.Fatalf("master.FakeMysqlDaemon.CheckSuperQueryList failed: %v", err) } if err := goodSlave1.FakeMysqlDaemon.CheckSuperQueryList(); err != nil { t.Fatalf("goodSlave1.FakeMysqlDaemon.CheckSuperQueryList failed: %v", err) } if err := goodSlave2.FakeMysqlDaemon.CheckSuperQueryList(); err != nil { t.Fatalf("goodSlave2.FakeMysqlDaemon.CheckSuperQueryList failed: %v", err) } }
// TestInitMasterShardOneSlaveFails makes sure that if one slave fails to // proceed, the action completes anyway func TestInitMasterShardOneSlaveFails(t *testing.T) { ctx := context.Background() db := fakesqldb.Register() ts := zktestserver.New(t, []string{"cell1", "cell2"}) wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient()) // Create a master, a couple slaves master := NewFakeTablet(t, wr, "cell1", 0, topodatapb.TabletType_MASTER, db) goodSlave := NewFakeTablet(t, wr, "cell1", 1, topodatapb.TabletType_REPLICA, db) badSlave := NewFakeTablet(t, wr, "cell2", 2, topodatapb.TabletType_REPLICA, db) // Master: set a plausible ReplicationPosition to return, // and expect to add entry in _vt.reparent_journal master.FakeMysqlDaemon.CurrentMasterPosition = replication.Position{ GTIDSet: replication.MariadbGTID{ Domain: 5, Server: 456, Sequence: 890, }, } master.FakeMysqlDaemon.ReadOnly = true master.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ "CREATE DATABASE IF NOT EXISTS _vt", "SUBCREATE TABLE IF NOT EXISTS _vt.reparent_journal", "CREATE DATABASE IF NOT EXISTS _vt", "SUBCREATE TABLE IF NOT EXISTS _vt.reparent_journal", "SUBINSERT INTO _vt.reparent_journal (time_created_ns, action_name, master_alias, replication_position) VALUES", } master.StartActionLoop(t, wr) defer master.StopActionLoop(t) // goodSlave: expect to be re-parented goodSlave.FakeMysqlDaemon.ReadOnly = true goodSlave.FakeMysqlDaemon.SetSlavePositionCommandsPos = master.FakeMysqlDaemon.CurrentMasterPosition goodSlave.FakeMysqlDaemon.SetSlavePositionCommandsResult = []string{"cmd1"} goodSlave.FakeMysqlDaemon.SetMasterCommandsInput = fmt.Sprintf("%v:%v", master.Tablet.Hostname, master.Tablet.PortMap["mysql"]) goodSlave.FakeMysqlDaemon.SetMasterCommandsResult = []string{"set master cmd 1"} goodSlave.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ "cmd1", "set master cmd 1", "START SLAVE", } goodSlave.StartActionLoop(t, wr) defer goodSlave.StopActionLoop(t) // badSlave: insert an error by failing the master hostname input // on purpose badSlave.FakeMysqlDaemon.ReadOnly = true badSlave.FakeMysqlDaemon.SetSlavePositionCommandsPos = master.FakeMysqlDaemon.CurrentMasterPosition badSlave.FakeMysqlDaemon.SetMasterCommandsInput = fmt.Sprintf("%v:%v", "", master.Tablet.PortMap["mysql"]) badSlave.StartActionLoop(t, wr) defer badSlave.StopActionLoop(t) // also change the master alias in the Shard object, to make sure it // is set back. si, err := ts.GetShard(ctx, master.Tablet.Keyspace, master.Tablet.Shard) if err != nil { t.Fatalf("GetShard failed: %v", err) } si.MasterAlias.Uid++ if err := ts.UpdateShard(ctx, si); err != nil { t.Fatalf("UpdateShard failed: %v", err) } // run InitShardMaster without force, it fails because master is // changing. if err := wr.InitShardMaster(ctx, master.Tablet.Keyspace, master.Tablet.Shard, master.Tablet.Alias, false /*force*/, 10*time.Second); err == nil || !strings.Contains(err.Error(), "is not the shard master") { t.Errorf("InitShardMaster with mismatched new master returned wrong error: %v", err) } // run InitShardMaster if err := wr.InitShardMaster(ctx, master.Tablet.Keyspace, master.Tablet.Shard, master.Tablet.Alias, true /*force*/, 10*time.Second); err == nil || !strings.Contains(err.Error(), "wrong input for SetMasterCommands") { t.Errorf("InitShardMaster with one failed slave returned wrong error: %v", err) } // check what was run: master should still be good if master.FakeMysqlDaemon.ReadOnly { t.Errorf("master was not turned read-write") } si, err = ts.GetShard(ctx, master.Tablet.Keyspace, master.Tablet.Shard) if err != nil { t.Fatalf("GetShard failed: %v", err) } if !topoproto.TabletAliasEqual(si.MasterAlias, master.Tablet.Alias) { t.Errorf("unexpected shard master alias, got %v expected %v", si.MasterAlias, master.Tablet.Alias) } }
func TestAPI(t *testing.T) { ctx := context.Background() cells := []string{"cell1", "cell2"} ts := zktestserver.New(t, cells) actionRepo := NewActionRepository(ts) server := httptest.NewServer(nil) defer server.Close() // Populate topo. ts.CreateKeyspace(ctx, "ks1", &topodatapb.Keyspace{ShardingColumnName: "shardcol"}) ts.Impl.CreateShard(ctx, "ks1", "-80", &topodatapb.Shard{ Cells: cells, KeyRange: &topodatapb.KeyRange{Start: nil, End: []byte{0x80}}, }) ts.Impl.CreateShard(ctx, "ks1", "80-", &topodatapb.Shard{ Cells: cells, KeyRange: &topodatapb.KeyRange{Start: []byte{0x80}, End: nil}, }) tablet1 := topodatapb.Tablet{ Alias: &topodatapb.TabletAlias{Cell: "cell1", Uid: 100}, Keyspace: "ks1", Shard: "-80", Type: topodatapb.TabletType_REPLICA, KeyRange: &topodatapb.KeyRange{Start: nil, End: []byte{0x80}}, PortMap: map[string]int32{"vt": 100}, } ts.CreateTablet(ctx, &tablet1) tablet2 := topodatapb.Tablet{ Alias: &topodatapb.TabletAlias{Cell: "cell2", Uid: 200}, Keyspace: "ks1", Shard: "-80", Type: topodatapb.TabletType_REPLICA, KeyRange: &topodatapb.KeyRange{Start: nil, End: []byte{0x80}}, PortMap: map[string]int32{"vt": 200}, } ts.CreateTablet(ctx, &tablet2) // Populate fake actions. actionRepo.RegisterKeyspaceAction("TestKeyspaceAction", func(ctx context.Context, wr *wrangler.Wrangler, keyspace string, r *http.Request) (string, error) { return "TestKeyspaceAction Result", nil }) actionRepo.RegisterShardAction("TestShardAction", func(ctx context.Context, wr *wrangler.Wrangler, keyspace, shard string, r *http.Request) (string, error) { return "TestShardAction Result", nil }) actionRepo.RegisterTabletAction("TestTabletAction", "", func(ctx context.Context, wr *wrangler.Wrangler, tabletAlias *topodatapb.TabletAlias, r *http.Request) (string, error) { return "TestTabletAction Result", nil }) realtimeStats := newRealtimeStatsForTesting() initAPI(ctx, ts, actionRepo, realtimeStats) target := &querypb.Target{ Keyspace: "ks1", Shard: "-80", TabletType: topodatapb.TabletType_REPLICA, } stats := &querypb.RealtimeStats{ HealthError: "", SecondsBehindMaster: 2, BinlogPlayersCount: 0, CpuUsage: 12.1, Qps: 5.6, } tabletStats := &discovery.TabletStats{ Tablet: &tablet1, Target: target, Up: true, Serving: true, TabletExternallyReparentedTimestamp: 5, Stats: stats, LastError: nil, } realtimeStats.tabletStats.StatsUpdate(tabletStats) // Test cases. table := []struct { method, path, want string }{ // Cells {"GET", "cells", `["cell1","cell2"]`}, // Keyspaces {"GET", "keyspaces", `["ks1"]`}, {"GET", "keyspaces/ks1", `{ "sharding_column_name": "shardcol" }`}, {"POST", "keyspaces/ks1?action=TestKeyspaceAction", `{ "Name": "TestKeyspaceAction", "Parameters": "ks1", "Output": "TestKeyspaceAction Result", "Error": false }`}, // Shards {"GET", "shards/ks1/", `["-80","80-"]`}, {"GET", "shards/ks1/-80", `{ "key_range": {"end":"gA=="}, "cells": ["cell1", "cell2"] }`}, {"POST", "shards/ks1/-80?action=TestShardAction", `{ "Name": "TestShardAction", "Parameters": "ks1/-80", "Output": "TestShardAction Result", "Error": false }`}, // Tablets {"GET", "tablets/?shard=ks1%2F-80", `[ {"cell":"cell1","uid":100}, {"cell":"cell2","uid":200} ]`}, {"GET", "tablets/?cell=cell1", `[ {"cell":"cell1","uid":100} ]`}, {"GET", "tablets/?shard=ks1%2F-80&cell=cell2", `[ {"cell":"cell2","uid":200} ]`}, {"GET", "tablets/cell1-100", `{ "alias": {"cell": "cell1", "uid": 100}, "port_map": {"vt": 100}, "keyspace": "ks1", "shard": "-80", "key_range": {"end": "gA=="}, "type": 2 }`}, {"POST", "tablets/cell1-100?action=TestTabletAction", `{ "Name": "TestTabletAction", "Parameters": "cell1-0000000100", "Output": "TestTabletAction Result", "Error": false }`}, //Tablet Updates {"GET", "tablet_statuses/cell1/ks1/-80/REPLICA", `{"100":{"Tablet":{"alias":{"cell":"cell1","uid":100},"port_map":{"vt":100},"keyspace":"ks1","shard":"-80","key_range":{"end":"gA=="},"type":2},"Name":"","Target":{"keyspace":"ks1","shard":"-80","tablet_type":2},"Up":true,"Serving":true,"TabletExternallyReparentedTimestamp":5,"Stats":{"seconds_behind_master":2,"cpu_usage":12.1,"qps":5.6},"LastError":null}}`}, {"GET", "tablet_statuses/cell1/ks1/replica", "can't get tablet_statuses: invalid target path: \"cell1/ks1/replica\" expected path: <cell>/<keyspace>/<shard>/<type>"}, {"GET", "tablet_statuses/cell1/ks1/-80/hello", "can't get tablet_statuses: invalid tablet type: hello"}, } for _, in := range table { var resp *http.Response var err error switch in.method { case "GET": resp, err = http.Get(server.URL + apiPrefix + in.path) case "POST": resp, err = http.Post(server.URL+apiPrefix+in.path, "", nil) default: t.Errorf("[%v] unknown method: %v", in.path, in.method) continue } if err != nil { t.Errorf("[%v] http error: %v", in.path, err) continue } body, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { t.Errorf("[%v] ioutil.ReadAll(resp.Body) error: %v", in.path, err) continue } got := compactJSON(body) want := compactJSON([]byte(in.want)) if want == "" { // want is no valid JSON. Fallback to a string comparison. want = in.want // For unknown reasons errors have a trailing "\n\t\t". Remove it. got = strings.TrimSpace(string(body)) } if got != want { t.Errorf("[%v] got '%v', want '%v'", in.path, got, want) continue } } }
func TestBackupRestore(t *testing.T) { // Initialize our environment ctx := context.Background() db := fakesqldb.Register() ts := zktestserver.New(t, []string{"cell1", "cell2"}) wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient()) vp := NewVtctlPipe(t, ts) defer vp.Close() // Set up mock query results. db.AddQuery("CREATE DATABASE IF NOT EXISTS _vt", &sqltypes.Result{}) db.AddQuery("BEGIN", &sqltypes.Result{}) db.AddQuery("COMMIT", &sqltypes.Result{}) db.AddQueryPattern(`SET @@session\.sql_log_bin = .*`, &sqltypes.Result{}) db.AddQueryPattern(`CREATE TABLE IF NOT EXISTS _vt\.shard_metadata .*`, &sqltypes.Result{}) db.AddQueryPattern(`CREATE TABLE IF NOT EXISTS _vt\.local_metadata .*`, &sqltypes.Result{}) db.AddQueryPattern(`INSERT INTO _vt\.local_metadata .*`, &sqltypes.Result{}) // Initialize our temp dirs root, err := ioutil.TempDir("", "backuptest") if err != nil { t.Fatalf("os.TempDir failed: %v", err) } defer os.RemoveAll(root) // Initialize BackupStorage fbsRoot := path.Join(root, "fbs") *filebackupstorage.FileBackupStorageRoot = fbsRoot *backupstorage.BackupStorageImplementation = "file" // Initialize the fake mysql root directories sourceInnodbDataDir := path.Join(root, "source_innodb_data") sourceInnodbLogDir := path.Join(root, "source_innodb_log") sourceDataDir := path.Join(root, "source_data") sourceDataDbDir := path.Join(sourceDataDir, "vt_db") for _, s := range []string{sourceInnodbDataDir, sourceInnodbLogDir, sourceDataDbDir} { if err := os.MkdirAll(s, os.ModePerm); err != nil { t.Fatalf("failed to create directory %v: %v", s, err) } } if err := ioutil.WriteFile(path.Join(sourceInnodbDataDir, "innodb_data_1"), []byte("innodb data 1 contents"), os.ModePerm); err != nil { t.Fatalf("failed to write file innodb_data_1: %v", err) } if err := ioutil.WriteFile(path.Join(sourceInnodbLogDir, "innodb_log_1"), []byte("innodb log 1 contents"), os.ModePerm); err != nil { t.Fatalf("failed to write file innodb_log_1: %v", err) } if err := ioutil.WriteFile(path.Join(sourceDataDbDir, "db.opt"), []byte("db opt file"), os.ModePerm); err != nil { t.Fatalf("failed to write file db.opt: %v", err) } // create a master tablet, not started, just for shard health master := NewFakeTablet(t, wr, "cell1", 0, topodatapb.TabletType_MASTER, db) // create a single tablet, set it up so we can do backups sourceTablet := NewFakeTablet(t, wr, "cell1", 1, topodatapb.TabletType_REPLICA, db) sourceTablet.FakeMysqlDaemon.ReadOnly = true sourceTablet.FakeMysqlDaemon.Replicating = true sourceTablet.FakeMysqlDaemon.CurrentMasterPosition = replication.Position{ GTIDSet: replication.MariadbGTID{ Domain: 2, Server: 123, Sequence: 457, }, } sourceTablet.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ "STOP SLAVE", "START SLAVE", } sourceTablet.FakeMysqlDaemon.Mycnf = &mysqlctl.Mycnf{ DataDir: sourceDataDir, InnodbDataHomeDir: sourceInnodbDataDir, InnodbLogGroupHomeDir: sourceInnodbLogDir, } sourceTablet.StartActionLoop(t, wr) defer sourceTablet.StopActionLoop(t) // run the backup if err := vp.Run([]string{"Backup", topoproto.TabletAliasString(sourceTablet.Tablet.Alias)}); err != nil { t.Fatalf("Backup failed: %v", err) } // verify the full status if err := sourceTablet.FakeMysqlDaemon.CheckSuperQueryList(); err != nil { t.Errorf("sourceTablet.FakeMysqlDaemon.CheckSuperQueryList failed: %v", err) } if !sourceTablet.FakeMysqlDaemon.Replicating { t.Errorf("sourceTablet.FakeMysqlDaemon.Replicating not set") } if !sourceTablet.FakeMysqlDaemon.Running { t.Errorf("sourceTablet.FakeMysqlDaemon.Running not set") } // create a destination tablet, set it up so we can do restores destTablet := NewFakeTablet(t, wr, "cell1", 2, topodatapb.TabletType_REPLICA, db) destTablet.FakeMysqlDaemon.ReadOnly = true destTablet.FakeMysqlDaemon.Replicating = true destTablet.FakeMysqlDaemon.CurrentMasterPosition = replication.Position{ GTIDSet: replication.MariadbGTID{ Domain: 2, Server: 123, Sequence: 457, }, } destTablet.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ "cmd1", "set master cmd 1", "START SLAVE", } destTablet.FakeMysqlDaemon.Mycnf = &mysqlctl.Mycnf{ DataDir: sourceDataDir, InnodbDataHomeDir: sourceInnodbDataDir, InnodbLogGroupHomeDir: sourceInnodbLogDir, BinLogPath: path.Join(root, "bin-logs/filename_prefix"), RelayLogPath: path.Join(root, "relay-logs/filename_prefix"), RelayLogIndexPath: path.Join(root, "relay-log.index"), RelayLogInfoPath: path.Join(root, "relay-log.info"), } destTablet.FakeMysqlDaemon.FetchSuperQueryMap = map[string]*sqltypes.Result{ "SHOW DATABASES": {}, } destTablet.FakeMysqlDaemon.SetSlavePositionCommandsPos = sourceTablet.FakeMysqlDaemon.CurrentMasterPosition destTablet.FakeMysqlDaemon.SetSlavePositionCommandsResult = []string{"cmd1"} destTablet.FakeMysqlDaemon.SetMasterCommandsInput = fmt.Sprintf("%v:%v", master.Tablet.Hostname, master.Tablet.PortMap["mysql"]) destTablet.FakeMysqlDaemon.SetMasterCommandsResult = []string{"set master cmd 1"} destTablet.StartActionLoop(t, wr) defer destTablet.StopActionLoop(t) if err := destTablet.Agent.RestoreData(ctx, logutil.NewConsoleLogger(), false /* deleteBeforeRestore */); err != nil { t.Fatalf("RestoreData failed: %v", err) } // verify the full status if err := destTablet.FakeMysqlDaemon.CheckSuperQueryList(); err != nil { t.Errorf("destTablet.FakeMysqlDaemon.CheckSuperQueryList failed: %v", err) } if !destTablet.FakeMysqlDaemon.Replicating { t.Errorf("destTablet.FakeMysqlDaemon.Replicating not set") } if !destTablet.FakeMysqlDaemon.Running { t.Errorf("destTablet.FakeMysqlDaemon.Running not set") } }
func (tc *splitCloneTestCase) setUpWithConcurreny(v3 bool, concurrency, writeQueryMaxRows, rowsCount int) { *useV3ReshardingMode = v3 db := fakesqldb.Register() tc.ts = zktestserver.New(tc.t, []string{"cell1", "cell2"}) ctx := context.Background() tc.wi = NewInstance(tc.ts, "cell1", time.Second) if v3 { if err := tc.ts.CreateKeyspace(ctx, "ks", &topodatapb.Keyspace{}); err != nil { tc.t.Fatalf("CreateKeyspace v3 failed: %v", err) } vs := &vschemapb.Keyspace{ Sharded: true, Vindexes: map[string]*vschemapb.Vindex{ "table1_index": { Type: "numeric", }, }, Tables: map[string]*vschemapb.Table{ "table1": { ColumnVindexes: []*vschemapb.ColumnVindex{ { Column: "keyspace_id", Name: "table1_index", }, }, }, }, } if err := tc.ts.SaveVSchema(ctx, "ks", vs); err != nil { tc.t.Fatalf("SaveVSchema v3 failed: %v", err) } } else { if err := tc.ts.CreateKeyspace(ctx, "ks", &topodatapb.Keyspace{ ShardingColumnName: "keyspace_id", ShardingColumnType: topodatapb.KeyspaceIdType_UINT64, }); err != nil { tc.t.Fatalf("CreateKeyspace v2 failed: %v", err) } } sourceMaster := testlib.NewFakeTablet(tc.t, tc.wi.wr, "cell1", 0, topodatapb.TabletType_MASTER, db, testlib.TabletKeyspaceShard(tc.t, "ks", "-80")) sourceRdonly1 := testlib.NewFakeTablet(tc.t, tc.wi.wr, "cell1", 1, topodatapb.TabletType_RDONLY, db, testlib.TabletKeyspaceShard(tc.t, "ks", "-80")) sourceRdonly2 := testlib.NewFakeTablet(tc.t, tc.wi.wr, "cell1", 2, topodatapb.TabletType_RDONLY, db, testlib.TabletKeyspaceShard(tc.t, "ks", "-80")) leftMaster := testlib.NewFakeTablet(tc.t, tc.wi.wr, "cell1", 10, topodatapb.TabletType_MASTER, db, testlib.TabletKeyspaceShard(tc.t, "ks", "-40")) // leftReplica is used by the reparent test. leftReplica := testlib.NewFakeTablet(tc.t, tc.wi.wr, "cell1", 11, topodatapb.TabletType_REPLICA, db, testlib.TabletKeyspaceShard(tc.t, "ks", "-40")) tc.leftReplica = leftReplica leftRdonly1 := testlib.NewFakeTablet(tc.t, tc.wi.wr, "cell1", 12, topodatapb.TabletType_RDONLY, db, testlib.TabletKeyspaceShard(tc.t, "ks", "-40")) leftRdonly2 := testlib.NewFakeTablet(tc.t, tc.wi.wr, "cell1", 13, topodatapb.TabletType_RDONLY, db, testlib.TabletKeyspaceShard(tc.t, "ks", "-40")) rightMaster := testlib.NewFakeTablet(tc.t, tc.wi.wr, "cell1", 20, topodatapb.TabletType_MASTER, db, testlib.TabletKeyspaceShard(tc.t, "ks", "40-80")) rightRdonly1 := testlib.NewFakeTablet(tc.t, tc.wi.wr, "cell1", 22, topodatapb.TabletType_RDONLY, db, testlib.TabletKeyspaceShard(tc.t, "ks", "40-80")) rightRdonly2 := testlib.NewFakeTablet(tc.t, tc.wi.wr, "cell1", 23, topodatapb.TabletType_RDONLY, db, testlib.TabletKeyspaceShard(tc.t, "ks", "40-80")) tc.tablets = []*testlib.FakeTablet{sourceMaster, sourceRdonly1, sourceRdonly2, leftMaster, tc.leftReplica, leftRdonly1, leftRdonly2, rightMaster, rightRdonly1, rightRdonly2} for _, ft := range tc.tablets { ft.StartActionLoop(tc.t, tc.wi.wr) } // add the topo and schema data we'll need if err := tc.ts.CreateShard(ctx, "ks", "80-"); err != nil { tc.t.Fatalf("CreateShard(\"-80\") failed: %v", err) } if err := tc.wi.wr.SetKeyspaceShardingInfo(ctx, "ks", "keyspace_id", topodatapb.KeyspaceIdType_UINT64, false); err != nil { tc.t.Fatalf("SetKeyspaceShardingInfo failed: %v", err) } if err := tc.wi.wr.RebuildKeyspaceGraph(ctx, "ks", nil); err != nil { tc.t.Fatalf("RebuildKeyspaceGraph failed: %v", err) } for _, sourceRdonly := range []*testlib.FakeTablet{sourceRdonly1, sourceRdonly2} { sourceRdonly.FakeMysqlDaemon.Schema = &tabletmanagerdatapb.SchemaDefinition{ DatabaseSchema: "", TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ { Name: "table1", // "id" is the last column in the list on purpose to test for // regressions. The reconciliation code will SELECT with the primary // key columns first. The same ordering must be used throughout the // process e.g. by RowAggregator or the v2Resolver. Columns: []string{"msg", "keyspace_id", "id"}, PrimaryKeyColumns: []string{"id"}, Type: tmutils.TableBaseTable, // Set the row count to avoid that --min_rows_per_chunk reduces the // number of chunks. RowCount: uint64(rowsCount), }, }, } sourceRdonly.FakeMysqlDaemon.DbAppConnectionFactory = sourceRdonlyFactory( tc.t, "vt_ks", "table1", splitCloneTestMin, splitCloneTestMax) sourceRdonly.FakeMysqlDaemon.CurrentMasterPosition = replication.Position{ GTIDSet: replication.MariadbGTID{Domain: 12, Server: 34, Sequence: 5678}, } sourceRdonly.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ "STOP SLAVE", "START SLAVE", } shqs := fakes.NewStreamHealthQueryService(sourceRdonly.Target()) shqs.AddDefaultHealthResponse() qs := newTestQueryService(tc.t, sourceRdonly.Target(), shqs, 0, 1, topoproto.TabletAliasString(sourceRdonly.Tablet.Alias), false /* omitKeyspaceID */) qs.addGeneratedRows(100, 100+rowsCount) grpcqueryservice.Register(sourceRdonly.RPCServer, qs) tc.sourceRdonlyQs = append(tc.sourceRdonlyQs, qs) } // Set up destination rdonlys which will be used as input for the diff during the clone. for i, destRdonly := range []*testlib.FakeTablet{leftRdonly1, rightRdonly1, leftRdonly2, rightRdonly2} { shqs := fakes.NewStreamHealthQueryService(destRdonly.Target()) shqs.AddDefaultHealthResponse() qs := newTestQueryService(tc.t, destRdonly.Target(), shqs, i%2, 2, topoproto.TabletAliasString(destRdonly.Tablet.Alias), false /* omitKeyspaceID */) grpcqueryservice.Register(destRdonly.RPCServer, qs) if i%2 == 0 { tc.leftRdonlyQs = append(tc.leftRdonlyQs, qs) } else { tc.rightRdonlyQs = append(tc.rightRdonlyQs, qs) } } tc.leftMasterFakeDb = NewFakePoolConnectionQuery(tc.t, "leftMaster") tc.leftReplicaFakeDb = NewFakePoolConnectionQuery(tc.t, "leftReplica") tc.rightMasterFakeDb = NewFakePoolConnectionQuery(tc.t, "rightMaster") // In the default test case there will be 30 inserts per destination shard // because 10 writer threads will insert 5 rows on each destination shard. // (100 rowsCount / 10 writers / 2 shards = 5 rows.) // Due to --write_query_max_rows=2 there will be 3 inserts for 5 rows. rowsPerDestinationShard := rowsCount / 2 rowsPerThread := rowsPerDestinationShard / concurrency insertsPerThread := math.Ceil(float64(rowsPerThread) / float64(writeQueryMaxRows)) insertsTotal := int(insertsPerThread) * concurrency for i := 1; i <= insertsTotal; i++ { tc.leftMasterFakeDb.addExpectedQuery("INSERT INTO `vt_ks`.`table1` (`id`, `msg`, `keyspace_id`) VALUES (*", nil) // leftReplica is unused by default. tc.rightMasterFakeDb.addExpectedQuery("INSERT INTO `vt_ks`.`table1` (`id`, `msg`, `keyspace_id`) VALUES (*", nil) } expectBlpCheckpointCreationQueries(tc.leftMasterFakeDb) expectBlpCheckpointCreationQueries(tc.rightMasterFakeDb) leftMaster.FakeMysqlDaemon.DbAppConnectionFactory = tc.leftMasterFakeDb.getFactory() leftReplica.FakeMysqlDaemon.DbAppConnectionFactory = tc.leftReplicaFakeDb.getFactory() rightMaster.FakeMysqlDaemon.DbAppConnectionFactory = tc.rightMasterFakeDb.getFactory() // Fake stream health reponses because vtworker needs them to find the master. tc.leftMasterQs = fakes.NewStreamHealthQueryService(leftMaster.Target()) tc.leftMasterQs.AddDefaultHealthResponse() tc.leftReplicaQs = fakes.NewStreamHealthQueryService(leftReplica.Target()) tc.leftReplicaQs.AddDefaultHealthResponse() tc.rightMasterQs = fakes.NewStreamHealthQueryService(rightMaster.Target()) tc.rightMasterQs.AddDefaultHealthResponse() grpcqueryservice.Register(leftMaster.RPCServer, tc.leftMasterQs) grpcqueryservice.Register(leftReplica.RPCServer, tc.leftReplicaQs) grpcqueryservice.Register(rightMaster.RPCServer, tc.rightMasterQs) tc.defaultWorkerArgs = []string{ "SplitClone", "-online=false", // --max_tps is only specified to enable the throttler and ensure that the // code is executed. But the intent here is not to throttle the test, hence // the rate limit is set very high. "-max_tps", "9999", "-write_query_max_rows", strconv.Itoa(writeQueryMaxRows), "-chunk_count", strconv.Itoa(concurrency), "-min_rows_per_chunk", strconv.Itoa(rowsPerThread), "-source_reader_count", strconv.Itoa(concurrency), "-destination_writer_count", strconv.Itoa(concurrency), "ks/-80"} }
// CreateTopoServer returns the test topo server properly configured func CreateTopoServer(t *testing.T) topo.Server { return zktestserver.New(t, []string{"cell1", "cell2"}) }
// TestVerticalSplitClone will run VerticalSplitClone in the combined // online and offline mode. The online phase will copy 100 rows from the source // to the destination and the offline phase won't copy any rows as the source // has not changed in the meantime. func TestVerticalSplitClone(t *testing.T) { db := fakesqldb.Register() ts := zktestserver.New(t, []string{"cell1", "cell2"}) ctx := context.Background() wi := NewInstance(ctx, ts, "cell1", time.Second) sourceMaster := testlib.NewFakeTablet(t, wi.wr, "cell1", 0, topodatapb.TabletType_MASTER, db, testlib.TabletKeyspaceShard(t, "source_ks", "0")) sourceRdonly := testlib.NewFakeTablet(t, wi.wr, "cell1", 1, topodatapb.TabletType_RDONLY, db, testlib.TabletKeyspaceShard(t, "source_ks", "0")) // Create the destination keyspace with the appropriate ServedFromMap ki := &topodatapb.Keyspace{ ServedFroms: []*topodatapb.Keyspace_ServedFrom{ { TabletType: topodatapb.TabletType_MASTER, Keyspace: "source_ks", }, { TabletType: topodatapb.TabletType_REPLICA, Keyspace: "source_ks", }, { TabletType: topodatapb.TabletType_RDONLY, Keyspace: "source_ks", }, }, } wi.wr.TopoServer().CreateKeyspace(ctx, "destination_ks", ki) destMaster := testlib.NewFakeTablet(t, wi.wr, "cell1", 10, topodatapb.TabletType_MASTER, db, testlib.TabletKeyspaceShard(t, "destination_ks", "0")) destRdonly := testlib.NewFakeTablet(t, wi.wr, "cell1", 11, topodatapb.TabletType_RDONLY, db, testlib.TabletKeyspaceShard(t, "destination_ks", "0")) for _, ft := range []*testlib.FakeTablet{sourceMaster, sourceRdonly, destMaster, destRdonly} { ft.StartActionLoop(t, wi.wr) defer ft.StopActionLoop(t) } // add the topo and schema data we'll need if err := wi.wr.RebuildKeyspaceGraph(ctx, "source_ks", nil); err != nil { t.Fatalf("RebuildKeyspaceGraph failed: %v", err) } if err := wi.wr.RebuildKeyspaceGraph(ctx, "destination_ks", nil); err != nil { t.Fatalf("RebuildKeyspaceGraph failed: %v", err) } // Set up source rdonly which will be used as input for the diff during the clone. sourceRdonly.FakeMysqlDaemon.Schema = &tabletmanagerdatapb.SchemaDefinition{ DatabaseSchema: "", TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ { Name: "moving1", Columns: []string{"id", "msg"}, PrimaryKeyColumns: []string{"id"}, Type: tmutils.TableBaseTable, // Set the row count to avoid that --min_rows_per_chunk reduces the // number of chunks. RowCount: 100, }, { Name: "view1", Type: tmutils.TableView, }, }, } sourceRdonly.FakeMysqlDaemon.DbAppConnectionFactory = sourceRdonlyFactory( t, "vt_source_ks", "moving1", verticalSplitCloneTestMin, verticalSplitCloneTestMax) sourceRdonly.FakeMysqlDaemon.CurrentMasterPosition = replication.Position{ GTIDSet: replication.MariadbGTID{Domain: 12, Server: 34, Sequence: 5678}, } sourceRdonly.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{ "STOP SLAVE", "START SLAVE", } sourceRdonlyShqs := fakes.NewStreamHealthQueryService(sourceRdonly.Target()) sourceRdonlyShqs.AddDefaultHealthResponse() sourceRdonlyQs := newTestQueryService(t, sourceRdonly.Target(), sourceRdonlyShqs, 0, 1, topoproto.TabletAliasString(sourceRdonly.Tablet.Alias), true /* omitKeyspaceID */) sourceRdonlyQs.addGeneratedRows(verticalSplitCloneTestMin, verticalSplitCloneTestMax) grpcqueryservice.Register(sourceRdonly.RPCServer, sourceRdonlyQs) // Set up destination rdonly which will be used as input for the diff during the clone. destRdonlyShqs := fakes.NewStreamHealthQueryService(destRdonly.Target()) destRdonlyShqs.AddDefaultHealthResponse() destRdonlyQs := newTestQueryService(t, destRdonly.Target(), destRdonlyShqs, 0, 1, topoproto.TabletAliasString(destRdonly.Tablet.Alias), true /* omitKeyspaceID */) // This tablet is empty and does not return any rows. grpcqueryservice.Register(destRdonly.RPCServer, destRdonlyQs) // We read 100 source rows. sourceReaderCount is set to 10, so // we'll have 100/10=10 rows per table chunk. // destinationPackCount is set to 4, so we take 4 source rows // at once. So we'll process 4 + 4 + 2 rows to get to 10. // That means 3 insert statements on the target. So 3 * 10 // = 30 insert statements on the destination. destMasterFakeDb := createVerticalSplitCloneDestinationFakeDb(t, "destMaster", 30) defer destMasterFakeDb.verifyAllExecutedOrFail() destMaster.FakeMysqlDaemon.DbAppConnectionFactory = destMasterFakeDb.getFactory() // Fake stream health reponses because vtworker needs them to find the master. qs := fakes.NewStreamHealthQueryService(destMaster.Target()) qs.AddDefaultHealthResponse() grpcqueryservice.Register(destMaster.RPCServer, qs) // Only wait 1 ms between retries, so that the test passes faster *executeFetchRetryTime = (1 * time.Millisecond) // When the online clone inserted the last rows, modify the destination test // query service such that it will return them as well. destMasterFakeDb.getEntry(29).AfterFunc = func() { destRdonlyQs.addGeneratedRows(verticalSplitCloneTestMin, verticalSplitCloneTestMax) } // Run the vtworker command. args := []string{ "VerticalSplitClone", // --max_tps is only specified to enable the throttler and ensure that the // code is executed. But the intent here is not to throttle the test, hence // the rate limit is set very high. "-max_tps", "9999", "-tables", "moving.*,view1", "-source_reader_count", "10", // Each chunk pipeline will process 10 rows. To spread them out across 3 // write queries, set the max row count per query to 4. (10 = 4+4+2) "-write_query_max_rows", "4", "-min_rows_per_chunk", "10", "-destination_writer_count", "10", // This test uses only one healthy RDONLY tablet. "-min_healthy_rdonly_tablets", "1", "destination_ks/0", } if err := runCommand(t, wi, wi.wr, args); err != nil { t.Fatal(err) } if inserts := statsOnlineInsertsCounters.Counts()["moving1"]; inserts != 100 { t.Errorf("wrong number of rows inserted: got = %v, want = %v", inserts, 100) } if updates := statsOnlineUpdatesCounters.Counts()["moving1"]; updates != 0 { t.Errorf("wrong number of rows updated: got = %v, want = %v", updates, 0) } if deletes := statsOnlineDeletesCounters.Counts()["moving1"]; deletes != 0 { t.Errorf("wrong number of rows deleted: got = %v, want = %v", deletes, 0) } if inserts := statsOfflineInsertsCounters.Counts()["moving1"]; inserts != 0 { t.Errorf("no stats for the offline clone phase should have been modified. got inserts = %v", inserts) } if updates := statsOfflineUpdatesCounters.Counts()["moving1"]; updates != 0 { t.Errorf("no stats for the offline clone phase should have been modified. got updates = %v", updates) } if deletes := statsOfflineDeletesCounters.Counts()["moving1"]; deletes != 0 { t.Errorf("no stats for the offline clone phase should have been modified. got deletes = %v", deletes) } wantRetryCount := int64(1) if got := statsRetryCount.Get(); got != wantRetryCount { t.Errorf("Wrong statsRetryCounter: got %v, wanted %v", got, wantRetryCount) } wantRetryReadOnlyCount := int64(1) if got := statsRetryCounters.Counts()[retryCategoryReadOnly]; got != wantRetryReadOnlyCount { t.Errorf("Wrong statsRetryCounters: got %v, wanted %v", got, wantRetryReadOnlyCount) } }