func main() { defer exit.Recover() flag.Parse() err := vtctlclient.RunCommandAndWait( context.Background(), *server, flag.Args(), *dialTimeout, *actionTimeout, func(e *logutilpb.Event) { switch e.Level { case logutilpb.Level_INFO: log.Info(logutil.EventString(e)) case logutilpb.Level_WARNING: log.Warning(logutil.EventString(e)) case logutilpb.Level_ERROR: log.Error(logutil.EventString(e)) case logutilpb.Level_CONSOLE: fmt.Print(logutil.EventString(e)) } }) if err != nil { log.Error(err) os.Exit(1) } }
func main() { flag.Parse() ctx, cancel := context.WithTimeout(context.Background(), *actionTimeout) defer cancel() err := vtworkerclient.RunCommandAndWait( ctx, *server, flag.Args(), func(e *logutilpb.Event) { switch e.Level { case logutilpb.Level_INFO: log.Info(logutil.EventString(e)) case logutilpb.Level_WARNING: log.Warning(logutil.EventString(e)) case logutilpb.Level_ERROR: log.Error(logutil.EventString(e)) case logutilpb.Level_CONSOLE: fmt.Print(logutil.EventString(e)) } }) if err != nil { log.Error(err) os.Exit(1) } }
func commandSucceeds(t *testing.T, client vtworkerclient.Client) { logs, errFunc, err := client.ExecuteVtworkerCommand(context.Background(), []string{"Ping", "pong"}) if err != nil { t.Fatalf("Cannot execute remote command: %v", err) } count := 0 for e := range logs { expected := "Ping command was called with message: 'pong'.\n" if logutil.EventString(e) != expected { t.Errorf("Got unexpected log line '%v' expected '%v'", e.String(), expected) } count++ } if count != 1 { t.Errorf("Didn't get expected log line only, got %v lines", count) } if err := errFunc(); err != nil { t.Fatalf("Remote error: %v", err) } logs, errFunc, err = client.ExecuteVtworkerCommand(context.Background(), []string{"Reset"}) if err != nil { t.Fatalf("Cannot execute remote command: %v", err) } for range logs { } if err := errFunc(); err != nil { t.Fatalf("Cannot execute remote command: %v", err) } }
// swapOnTablet performs the schema swap on the provided tablet and then waits for it // to become healthy and to catch up with replication. func (shardSwap *shardSchemaSwap) swapOnTablet(tablet *topodatapb.Tablet) error { log.Infof("Restoring tablet %v from backup", tablet.Alias) eventStream, err := shardSwap.parent.tabletClient.RestoreFromBackup(shardSwap.parent.ctx, tablet) if err != nil { return err } waitForRestore: for { event, err := eventStream.Recv() switch err { case nil: log.Infof("Restore process on tablet %v: %v", tablet.Alias, logutil.EventString(event)) case io.EOF: break waitForRestore default: return err } } // Check if the tablet has restored from a backup with schema change applied. swapApplied, err := shardSwap.isSwapApplied(tablet) if err != nil { return err } if !swapApplied { return fmt.Errorf("Restore from backup cannot pick up new schema. Backup from tablet with new schema needs to be taken.") } return shardSwap.waitForTabletToBeHealthy(tablet) }
func (vp *VtctlPipe) run(args []string, outputFunc func(string)) error { actionTimeout := 30 * time.Second ctx := context.Background() stream, err := vp.client.ExecuteVtctlCommand(ctx, args, actionTimeout) if err != nil { return fmt.Errorf("VtctlPipe.Run() failed: %v", err) } for { le, err := stream.Recv() switch err { case nil: outputFunc(logutil.EventString(le)) case io.EOF: return nil default: return err } } }
// takeSeedBackup takes backup on the seed tablet. The method assumes that the seed tablet is any tablet that // has info that schema swap was already applied on it. This way the function can be re-used if the seed backup // is lost somewhere half way through the schema swap process. func (shardSwap *shardSchemaSwap) takeSeedBackup() error { tabletList := shardSwap.getTabletList() sort.Sort(orderTabletsForSwap(tabletList)) var seedTablet *topodatapb.Tablet for _, tabletStats := range tabletList { swapApplied, err := shardSwap.isSwapApplied(tabletStats.Tablet) if err != nil { return err } if swapApplied && tabletStats.Tablet.Type != topodatapb.TabletType_MASTER { seedTablet = tabletStats.Tablet break } } if seedTablet == nil { return fmt.Errorf("Cannot find the seed tablet on shard %v", shardSwap.shardName) } eventStream, err := shardSwap.parent.tabletClient.Backup(shardSwap.parent.ctx, seedTablet, *backupConcurrency) if err != nil { return err } waitForBackup: for { event, err := eventStream.Recv() switch err { case nil: log.Infof("Backup process on tablet %v: %v", seedTablet.Alias, logutil.EventString(event)) case io.EOF: break waitForBackup default: return err } } return shardSwap.waitForTabletToBeHealthy(seedTablet) }
func commandSucceeds(t *testing.T, client vtworkerclient.Client) { stream, err := client.ExecuteVtworkerCommand(context.Background(), []string{"Ping", "pong"}) if err != nil { t.Fatalf("Cannot execute remote command: %v", err) } got, err := stream.Recv() if err != nil { t.Fatalf("failed to get first line: %v", err) } expected := "Ping command was called with message: 'pong'.\n" if logutil.EventString(got) != expected { t.Errorf("Got unexpected log line '%v' expected '%v'", got.String(), expected) } got, err = stream.Recv() if err != io.EOF { t.Fatalf("Didn't get EOF as expected: %v", err) } stream, err = client.ExecuteVtworkerCommand(context.Background(), []string{"Reset"}) if err != nil { t.Fatalf("Cannot execute remote command: %v", err) } for { _, err := stream.Recv() switch err { case nil: // next please! case io.EOF: // done with test return default: // unexpected error t.Fatalf("Cannot execute remote command: %v", err) } } }
func commandSucceeds(t *testing.T, client vtworkerclient.Client) { stream, err := client.ExecuteVtworkerCommand(context.Background(), []string{"Ping", "pong"}) if err != nil { t.Fatalf("Cannot execute remote command: %v", err) } got, err := stream.Recv() if err != nil { t.Fatalf("failed to get first line: %v", err) } expected := "Ping command was called with message: 'pong'.\n" if logutil.EventString(got) != expected { t.Errorf("Got unexpected log line '%v' expected '%v'", got.String(), expected) } got, err = stream.Recv() if err != io.EOF { t.Fatalf("Didn't get EOF as expected: %v", err) } // Reset vtworker for the next test function. if err := runVtworkerCommand(client, []string{"Reset"}); err != nil { t.Fatal(err) } }
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. } }
// TestSuite runs the test suite on the given topo server and client func TestSuite(t *testing.T, ts topo.Server, client vtctlclient.VtctlClient) { ctx := context.Background() // Create a fake tablet tablet := &topodatapb.Tablet{ Alias: &topodatapb.TabletAlias{Cell: "cell1", Uid: 1}, Hostname: "localhost", Ip: "10.11.12.13", PortMap: map[string]int32{ "vt": 3333, "mysql": 3334, }, Tags: map[string]string{"tag": "value"}, Keyspace: "test_keyspace", Type: topodatapb.TabletType_MASTER, } if err := ts.CreateTablet(ctx, tablet); err != nil { t.Errorf("CreateTablet: %v", err) } // run a command that's gonna return something on the log channel stream, err := client.ExecuteVtctlCommand(ctx, []string{"ListAllTablets", "cell1"}, 30*time.Second) if err != nil { t.Fatalf("Remote error: %v", err) } got, err := stream.Recv() if err != nil { t.Fatalf("failed to get first line: %v", err) } expected := "cell1-0000000001 test_keyspace <null> master localhost:3333 localhost:3334 [tag: \"value\"]\n" if logutil.EventString(got) != expected { t.Errorf("Got unexpected log line '%v' expected '%v'", got.String(), expected) } got, err = stream.Recv() if err != io.EOF { t.Errorf("Didn't get end of log stream: %v %v", got, err) } // run a command that's gonna fail stream, err = client.ExecuteVtctlCommand(ctx, []string{"ListAllTablets", "cell2"}, 30*time.Second) if err != nil { t.Fatalf("Remote error: %v", err) } got, err = stream.Recv() expected = "node doesn't exist" if err == nil || !strings.Contains(err.Error(), expected) { t.Fatalf("Unexpected remote error, got: '%v' was expecting to find '%v'", err, expected) } // run a command that's gonna panic stream, err = client.ExecuteVtctlCommand(ctx, []string{"Panic"}, 30*time.Second) if err != nil { t.Fatalf("Remote error: %v", err) } got, err = stream.Recv() expected1 := "this command panics on purpose" expected2 := "uncaught vtctl panic" if err == nil || !strings.Contains(err.Error(), expected1) || !strings.Contains(err.Error(), expected2) { t.Fatalf("Unexpected remote error, got: '%v' was expecting to find '%v' and '%v'", err, expected1, expected2) } // and clean up the tablet if err := ts.DeleteTablet(ctx, tablet.Alias); err != nil { t.Errorf("DeleteTablet: %v", err) } }
// TestSuite runs the test suite on the given topo server and client func TestSuite(t *testing.T, ts topo.Server, client vtctlclient.VtctlClient) { ctx := context.Background() // Create a fake tablet tablet := &topodatapb.Tablet{ Alias: &topodatapb.TabletAlias{Cell: "cell1", Uid: 1}, Hostname: "localhost", Ip: "10.11.12.13", PortMap: map[string]int32{ "vt": 3333, "mysql": 3334, }, Tags: map[string]string{"tag": "value"}, Keyspace: "test_keyspace", Type: topodatapb.TabletType_MASTER, } if err := ts.CreateTablet(ctx, tablet); err != nil { t.Errorf("CreateTablet: %v", err) } // run a command that's gonna return something on the log channel logs, errFunc, err := client.ExecuteVtctlCommand(ctx, []string{"ListAllTablets", "cell1"}, 30*time.Second) if err != nil { t.Fatalf("Remote error: %v", err) } count := 0 for e := range logs { expected := "cell1-0000000001 test_keyspace <null> master localhost:3333 localhost:3334 [tag: \"value\"]\n" if logutil.EventString(e) != expected { t.Errorf("Got unexpected log line '%v' expected '%v'", e.String(), expected) } count++ } if count != 1 { t.Errorf("Didn't get expected log line only, got %v lines", count) } if err := errFunc(); err != nil { t.Fatalf("Remote error: %v", err) } // run a command that's gonna fail logs, errFunc, err = client.ExecuteVtctlCommand(ctx, []string{"ListAllTablets", "cell2"}, 30*time.Second) if err != nil { t.Fatalf("Remote error: %v", err) } if e, ok := <-logs; ok { t.Errorf("Got unexpected line for logs: %v", e.String()) } expected := "node doesn't exist" if err := errFunc(); err == nil || !strings.Contains(err.Error(), expected) { t.Fatalf("Unexpected remote error, got: '%v' was expecting to find '%v'", err, expected) } // run a command that's gonna panic logs, errFunc, err = client.ExecuteVtctlCommand(ctx, []string{"Panic"}, 30*time.Second) if err != nil { t.Fatalf("Remote error: %v", err) } if e, ok := <-logs; ok { t.Errorf("Got unexpected line for logs: %v", e.String()) } expected1 := "this command panics on purpose" expected2 := "uncaught vtctl panic" if err := errFunc(); err == nil || !strings.Contains(err.Error(), expected1) || !strings.Contains(err.Error(), expected2) { t.Fatalf("Unexpected remote error, got: '%v' was expecting to find '%v' and '%v'", err, expected1, expected2) } }
func initAPI(ctx context.Context, ts topo.Server, actions *ActionRepository, realtimeStats *realtimeStats) { tabletHealthCache := newTabletHealthCache(ts) tmClient := tmclient.NewTabletManagerClient() // Cells handleCollection("cells", func(r *http.Request) (interface{}, error) { if getItemPath(r.URL.Path) != "" { return nil, errors.New("cells can only be listed, not retrieved") } return ts.GetKnownCells(ctx) }) // Keyspaces handleCollection("keyspaces", func(r *http.Request) (interface{}, error) { keyspace := getItemPath(r.URL.Path) switch r.Method { case "GET": // List all keyspaces. if keyspace == "" { return ts.GetKeyspaces(ctx) } // Get the keyspace record. k, err := ts.GetKeyspace(ctx, keyspace) // Pass the embedded proto directly or jsonpb will panic. return k.Keyspace, err // Perform an action on a keyspace. case "POST": if keyspace == "" { return nil, errors.New("A POST request needs a keyspace in the URL") } if err := r.ParseForm(); err != nil { return nil, err } action := r.FormValue("action") if action == "" { return nil, errors.New("A POST request must specify action") } return actions.ApplyKeyspaceAction(ctx, action, keyspace, r), nil default: return nil, fmt.Errorf("unsupported HTTP method: %v", r.Method) } }) // Shards handleCollection("shards", func(r *http.Request) (interface{}, error) { shardPath := getItemPath(r.URL.Path) if !strings.Contains(shardPath, "/") { return nil, fmt.Errorf("invalid shard path: %q", shardPath) } parts := strings.SplitN(shardPath, "/", 2) keyspace := parts[0] shard := parts[1] // List the shards in a keyspace. if shard == "" { return ts.GetShardNames(ctx, keyspace) } // Perform an action on a shard. if r.Method == "POST" { if err := r.ParseForm(); err != nil { return nil, err } action := r.FormValue("action") if action == "" { return nil, errors.New("must specify action") } return actions.ApplyShardAction(ctx, action, keyspace, shard, r), nil } // Get the shard record. si, err := ts.GetShard(ctx, keyspace, shard) // Pass the embedded proto directly or jsonpb will panic. return si.Shard, err }) // SrvKeyspace handleCollection("srv_keyspace", func(r *http.Request) (interface{}, error) { keyspacePath := getItemPath(r.URL.Path) parts := strings.SplitN(keyspacePath, "/", 2) // Request was incorrectly formatted. if len(parts) != 2 { return nil, fmt.Errorf("invalid srvkeyspace path: %q expected path: /srv_keyspace/<cell>/<keyspace>", keyspacePath) } cell := parts[0] keyspace := parts[1] if cell == "local" { if *localCell == "" { return nil, fmt.Errorf("local cell requested, but not specified. Please set with -cell flag") } cell = *localCell } // If a keyspace is provided then return the specified srvkeyspace. if keyspace != "" { srvKeyspace, err := ts.GetSrvKeyspace(ctx, cell, keyspace) if err != nil { return nil, fmt.Errorf("Can't get server keyspace: %v", err) } return srvKeyspace, nil } // Else return the srvKeyspace from all keyspaces. srvKeyspaces := make(map[string]interface{}) keyspaceNamesList, err := ts.GetSrvKeyspaceNames(ctx, cell) if err != nil { return nil, fmt.Errorf("can't get list of SrvKeyspaceNames for cell %q: GetSrvKeyspaceNames returned: %v", cell, err) } for _, keyspaceName := range keyspaceNamesList { err := addSrvkeyspace(ctx, ts, cell, keyspaceName, srvKeyspaces) if err != nil { return nil, err } } return srvKeyspaces, nil }) // Tablets handleCollection("tablets", func(r *http.Request) (interface{}, error) { tabletPath := getItemPath(r.URL.Path) // List tablets based on query params. if tabletPath == "" { if err := r.ParseForm(); err != nil { return nil, err } shardRef := r.FormValue("shard") cell := r.FormValue("cell") if shardRef != "" { // Look up by keyspace/shard, and optionally cell. keyspace, shard, err := topoproto.ParseKeyspaceShard(shardRef) if err != nil { return nil, err } if cell != "" { result, err := ts.FindAllTabletAliasesInShardByCell(ctx, keyspace, shard, []string{cell}) if err != nil && err != topo.ErrPartialResult { return result, err } return result, nil } result, err := ts.FindAllTabletAliasesInShard(ctx, keyspace, shard) if err != nil && err != topo.ErrPartialResult { return result, err } return result, nil } // Get all tablets in a cell. if cell == "" { return nil, errors.New("cell param required") } return ts.GetTabletsByCell(ctx, cell) } // Get tablet health. if parts := strings.Split(tabletPath, "/"); len(parts) == 2 && parts[1] == "health" { tabletAlias, err := topoproto.ParseTabletAlias(parts[0]) if err != nil { return nil, err } return tabletHealthCache.Get(ctx, tabletAlias) } tabletAlias, err := topoproto.ParseTabletAlias(tabletPath) if err != nil { return nil, err } // Perform an action on a tablet. if r.Method == "POST" { if err := r.ParseForm(); err != nil { return nil, err } action := r.FormValue("action") if action == "" { return nil, errors.New("must specify action") } return actions.ApplyTabletAction(ctx, action, tabletAlias, r), nil } // Get the tablet record. t, err := ts.GetTablet(ctx, tabletAlias) // Pass the embedded proto directly or jsonpb will panic. return t.Tablet, err }) // Healthcheck real time status per (cell, keyspace, tablet type, metric). handleCollection("tablet_statuses", func(r *http.Request) (interface{}, error) { targetPath := getItemPath(r.URL.Path) // Get the heatmap data based on query parameters. if targetPath == "" { if err := r.ParseForm(); err != nil { return nil, err } keyspace := r.FormValue("keyspace") cell := r.FormValue("cell") tabletType := r.FormValue("type") _, err := topoproto.ParseTabletType(tabletType) // Excluding the case where parse fails because all tabletTypes was chosen. if err != nil && tabletType != "all" { return nil, fmt.Errorf("invalid tablet type: %v ", err) } metric := r.FormValue("metric") // Setting default values if none was specified in the query params. if keyspace == "" { keyspace = "all" } if cell == "" { cell = "all" } if tabletType == "" { tabletType = "all" } if metric == "" { metric = "health" } if realtimeStats == nil { return nil, fmt.Errorf("realtimeStats not initialized") } heatmap, err := realtimeStats.heatmapData(keyspace, cell, tabletType, metric) if err != nil { return nil, fmt.Errorf("couldn't get heatmap data: %v", err) } return heatmap, nil } return nil, fmt.Errorf("invalid target path: %q expected path: ?keyspace=<keyspace>&cell=<cell>&type=<type>&metric=<metric>", targetPath) }) handleCollection("tablet_health", func(r *http.Request) (interface{}, error) { tabletPath := getItemPath(r.URL.Path) parts := strings.SplitN(tabletPath, "/", 2) // Request was incorrectly formatted. if len(parts) != 2 { return nil, fmt.Errorf("invalid tablet_health path: %q expected path: /tablet_health/<cell>/<uid>", tabletPath) } if realtimeStats == nil { return nil, fmt.Errorf("realtimeStats not initialized") } cell := parts[0] uidStr := parts[1] uid, err := topoproto.ParseUID(uidStr) if err != nil { return nil, fmt.Errorf("incorrect uid: %v", err) } tabletAlias := topodatapb.TabletAlias{ Cell: cell, Uid: uid, } tabletStat, err := realtimeStats.tabletStats(&tabletAlias) if err != nil { return nil, fmt.Errorf("could not get tabletStats: %v", err) } return tabletStat, nil }) handleCollection("topology_info", func(r *http.Request) (interface{}, error) { targetPath := getItemPath(r.URL.Path) // Retrieving topology information (keyspaces, cells, and types) based on query params. if targetPath == "" { if err := r.ParseForm(); err != nil { return nil, err } keyspace := r.FormValue("keyspace") cell := r.FormValue("cell") // Setting default values if none was specified in the query params. if keyspace == "" { keyspace = "all" } if cell == "" { cell = "all" } if realtimeStats == nil { return nil, fmt.Errorf("realtimeStats not initialized") } return realtimeStats.topologyInfo(keyspace, cell), nil } return nil, fmt.Errorf("invalid target path: %q expected path: ?keyspace=<keyspace>&cell=<cell>", targetPath) }) // Vtctl Command http.HandleFunc(apiPrefix+"vtctl/", func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.ADMIN); err != nil { httpErrorf(w, r, "Access denied") return } var args []string resp := struct { Error string Output string }{} if err := unmarshalRequest(r, &args); err != nil { httpErrorf(w, r, "can't unmarshal request: %v", err) return } logstream := logutil.NewMemoryLogger() wr := wrangler.New(logstream, ts, tmClient) // TODO(enisoc): Context for run command should be request-scoped. err := vtctl.RunCommand(ctx, wr, args) if err != nil { resp.Error = err.Error() } resp.Output = logstream.String() data, err := json.MarshalIndent(resp, "", " ") if err != nil { httpErrorf(w, r, "json error: %v", err) return } w.Header().Set("Content-Type", jsonContentType) w.Write(data) }) // Schema Change http.HandleFunc(apiPrefix+"schema/apply", func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.ADMIN); err != nil { httpErrorf(w, r, "Access denied") return } req := struct { Keyspace, SQL string SlaveTimeoutSeconds int }{} if err := unmarshalRequest(r, &req); err != nil { httpErrorf(w, r, "can't unmarshal request: %v", err) return } if req.SlaveTimeoutSeconds <= 0 { req.SlaveTimeoutSeconds = 10 } logger := logutil.NewCallbackLogger(func(ev *logutilpb.Event) { w.Write([]byte(logutil.EventString(ev))) }) wr := wrangler.New(logger, ts, tmClient) executor := schemamanager.NewTabletExecutor( wr, time.Duration(req.SlaveTimeoutSeconds)*time.Second) schemamanager.Run(ctx, schemamanager.NewUIController(req.SQL, req.Keyspace, w), executor) }) }
func initAPI(ctx context.Context, ts topo.Server, actions *ActionRepository) { tabletHealthCache := newTabletHealthCache(ts) tmClient := tmclient.NewTabletManagerClient() // Cells handleCollection("cells", func(r *http.Request) (interface{}, error) { if getItemPath(r.URL.Path) != "" { return nil, errors.New("cells can only be listed, not retrieved") } return ts.GetKnownCells(ctx) }) // Keyspaces handleCollection("keyspaces", func(r *http.Request) (interface{}, error) { keyspace := getItemPath(r.URL.Path) // List all keyspaces. if keyspace == "" { return ts.GetKeyspaces(ctx) } // Perform an action on a keyspace. if r.Method == "POST" { if err := r.ParseForm(); err != nil { return nil, err } action := r.FormValue("action") if action == "" { return nil, errors.New("must specify action") } return actions.ApplyKeyspaceAction(ctx, action, keyspace, r), nil } // Get the keyspace record. return ts.GetKeyspace(ctx, keyspace) }) // Shards handleCollection("shards", func(r *http.Request) (interface{}, error) { shardPath := getItemPath(r.URL.Path) if !strings.Contains(shardPath, "/") { return nil, fmt.Errorf("invalid shard path: %q", shardPath) } parts := strings.SplitN(shardPath, "/", 2) keyspace := parts[0] shard := parts[1] // List the shards in a keyspace. if shard == "" { return ts.GetShardNames(ctx, keyspace) } // Perform an action on a shard. if r.Method == "POST" { if err := r.ParseForm(); err != nil { return nil, err } action := r.FormValue("action") if action == "" { return nil, errors.New("must specify action") } return actions.ApplyShardAction(ctx, action, keyspace, shard, r), nil } // Get the shard record. return ts.GetShard(ctx, keyspace, shard) }) //SrvKeyspace handleCollection("srv_keyspace", func(r *http.Request) (interface{}, error) { keyspacePath := getItemPath(r.URL.Path) parts := strings.SplitN(keyspacePath, "/", 2) //request was incorrectly formatted if len(parts) != 2 { return nil, fmt.Errorf("invalid srvkeyspace path: %q expected path: /srv_keyspace/<cell>/<keyspace>", keyspacePath) } cell := parts[0] keyspace := parts[1] if cell == "local" { if *localCell == "" { return nil, fmt.Errorf("local cell requested, but not specified. Please set with -cell flag") } cell = *localCell } //If a keyspace is provided then return the specified srvkeyspace if keyspace != "" { srvKeyspace, err := ts.GetSrvKeyspace(ctx, cell, keyspace) if err != nil { return nil, fmt.Errorf("Can't get server keyspace: %v", err) } return srvKeyspace, nil } //Else return the srvKeyspace from all keyspaces srvKeyspaces := make(map[string]interface{}) keyspaceNamesList, err := ts.GetSrvKeyspaceNames(ctx, cell) if err != nil { return nil, fmt.Errorf("can't get list of SrvKeyspaceNames for cell %q: GetSrvKeyspaceNames returned: %v", cell, err) } for _, keyspaceName := range keyspaceNamesList { err := addSrvkeyspace(ctx, ts, cell, keyspaceName, srvKeyspaces) if err != nil { return nil, err } } return srvKeyspaces, nil }) // Tablets handleCollection("tablets", func(r *http.Request) (interface{}, error) { tabletPath := getItemPath(r.URL.Path) // List tablets based on query params. if tabletPath == "" { if err := r.ParseForm(); err != nil { return nil, err } shardRef := r.FormValue("shard") cell := r.FormValue("cell") if shardRef != "" { // Look up by keyspace/shard, and optionally cell. keyspace, shard, err := topoproto.ParseKeyspaceShard(shardRef) if err != nil { return nil, err } if cell != "" { return ts.FindAllTabletAliasesInShardByCell(ctx, keyspace, shard, []string{cell}) } return ts.FindAllTabletAliasesInShard(ctx, keyspace, shard) } // Get all tablets in a cell. if cell == "" { return nil, errors.New("cell param required") } return ts.GetTabletsByCell(ctx, cell) } // Get tablet health. if parts := strings.Split(tabletPath, "/"); len(parts) == 2 && parts[1] == "health" { tabletAlias, err := topoproto.ParseTabletAlias(parts[0]) if err != nil { return nil, err } return tabletHealthCache.Get(ctx, tabletAlias) } tabletAlias, err := topoproto.ParseTabletAlias(tabletPath) if err != nil { return nil, err } // Perform an action on a tablet. if r.Method == "POST" { if err := r.ParseForm(); err != nil { return nil, err } action := r.FormValue("action") if action == "" { return nil, errors.New("must specify action") } return actions.ApplyTabletAction(ctx, action, tabletAlias, r), nil } // Get the tablet record. return ts.GetTablet(ctx, tabletAlias) }) // Schema Change http.HandleFunc(apiPrefix+"schema/apply", func(w http.ResponseWriter, r *http.Request) { req := struct { Keyspace, SQL string SlaveTimeoutSeconds int }{} if err := unmarshalRequest(r, &req); err != nil { httpErrorf(w, r, "can't unmarshal request: %v", err) return } if req.SlaveTimeoutSeconds <= 0 { req.SlaveTimeoutSeconds = 10 } logger := logutil.NewCallbackLogger(func(ev *logutilpb.Event) { w.Write([]byte(logutil.EventString(ev))) }) wr := wrangler.New(logger, ts, tmClient) executor := schemamanager.NewTabletExecutor( wr, time.Duration(req.SlaveTimeoutSeconds)*time.Second) schemamanager.Run(ctx, schemamanager.NewUIController(req.SQL, req.Keyspace, w), executor) }) }