// GetTabletsByCell implements topo.Server. func (s *Server) GetTabletsByCell(ctx context.Context, cellName string) ([]topo.TabletAlias, error) { cell, err := s.getCell(cellName) if err != nil { return nil, err } resp, err := cell.Get(tabletsDirPath, false /* sort */, false /* recursive */) if err != nil { return nil, convertError(err) } nodes, err := getNodeNames(resp) if err != nil { return nil, err } tablets := make([]topo.TabletAlias, 0, len(nodes)) for _, node := range nodes { tabletAlias, err := topo.ParseTabletAliasString(node) if err != nil { return nil, err } tablets = append(tablets, tabletAlias) } return tablets, nil }
// actionPathToTabletAlias parses an actionPath back // zkActionPath is /zk/<cell>/vt/tablets/<uid>/action/<number> func actionPathToTabletAlias(actionPath string) (topo.TabletAlias, error) { pathParts := strings.Split(actionPath, "/") if len(pathParts) != 8 || pathParts[0] != "" || pathParts[1] != "zk" || pathParts[3] != "vt" || pathParts[4] != "tablets" || pathParts[6] != "action" { return topo.TabletAlias{}, fmt.Errorf("invalid action path: %v", actionPath) } return topo.ParseTabletAliasString(pathParts[2] + "-" + pathParts[5]) }
// handleExplorerRedirect returns the redirect target URL. func handleExplorerRedirect(r *http.Request) (string, error) { keyspace := r.FormValue("keyspace") shard := r.FormValue("shard") cell := r.FormValue("cell") switch r.FormValue("type") { case "keyspace": if keyspace == "" { return "", errors.New("keyspace is required for this redirect") } return explorer.GetKeyspacePath(keyspace), nil case "shard": if keyspace == "" || shard == "" { return "", errors.New("keyspace and shard are required for this redirect") } return explorer.GetShardPath(keyspace, shard), nil case "srv_keyspace": if keyspace == "" || cell == "" { return "", errors.New("keyspace and cell are required for this redirect") } return explorer.GetSrvKeyspacePath(cell, keyspace), nil case "srv_shard": if keyspace == "" || shard == "" || cell == "" { return "", errors.New("keyspace, shard, and cell are required for this redirect") } return explorer.GetSrvShardPath(cell, keyspace, shard), nil case "srv_type": tabletType := r.FormValue("tablet_type") if keyspace == "" || shard == "" || cell == "" || tabletType == "" { return "", errors.New("keyspace, shard, cell, and tablet_type are required for this redirect") } tt, err := topo.ParseTabletType(tabletType) if err != nil { return "", fmt.Errorf("cannot parse tablet type %v: %v", tabletType, err) } return explorer.GetSrvTypePath(cell, keyspace, shard, tt), nil case "tablet": alias := r.FormValue("alias") if alias == "" { return "", errors.New("alias is required for this redirect") } tabletAlias, err := topo.ParseTabletAliasString(alias) if err != nil { return "", fmt.Errorf("bad tablet alias %q: %v", alias, err) } return explorer.GetTabletPath(tabletAlias), nil case "replication": if keyspace == "" || shard == "" || cell == "" { return "", errors.New("keyspace, shard, and cell are required for this redirect") } return explorer.GetReplicationSlaves(cell, keyspace, shard), nil default: return "", errors.New("bad redirect type") } }
// FIXME(alainjobart) remove after migration func BackfillAlias(zkPath string, alias *topo.TabletAlias) error { if alias.IsZero() && zkPath != "" { zkPathParts := strings.Split(zkPath, "/") if len(zkPathParts) != 6 || zkPathParts[0] != "" || zkPathParts[1] != "zk" || zkPathParts[3] != "vt" || zkPathParts[4] != "tablets" { return fmt.Errorf("Invalid tablet path: %v", zkPath) } a, err := topo.ParseTabletAliasString(zkPathParts[2] + "-" + zkPathParts[5]) if err != nil { return err } *alias = a } return nil }
// tabletParamToTabletAlias takes either an old style ZK tablet path or a // new style tablet alias as a string, and returns a TabletAlias. func tabletParamToTabletAlias(param string) topo.TabletAlias { if param[0] == '/' { // old zookeeper path, convert to new-style string tablet alias zkPathParts := strings.Split(param, "/") if len(zkPathParts) != 6 || zkPathParts[0] != "" || zkPathParts[1] != "zk" || zkPathParts[3] != "vt" || zkPathParts[4] != "tablets" { log.Fatalf("Invalid tablet path: %v", param) } param = zkPathParts[2] + "-" + zkPathParts[5] } result, err := topo.ParseTabletAliasString(param) if err != nil { log.Fatalf("Invalid tablet alias %v: %v", param, err) } return result }
func commandVtTabletStreamHealth(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error { count := subFlags.Int("count", 1, "number of responses to wait for") connectTimeout := subFlags.Duration("connect_timeout", 30*time.Second, "Connection timeout for vttablet client") if err := subFlags.Parse(args); err != nil { return err } if subFlags.NArg() != 1 { return fmt.Errorf("The <tablet alias> argument is required for the VtTabletStreamHealth command.") } tabletAlias, err := topo.ParseTabletAliasString(subFlags.Arg(0)) if err != nil { return err } tabletInfo, err := wr.TopoServer().GetTablet(ctx, tabletAlias) if err != nil { return err } ep, err := tabletInfo.EndPoint() if err != nil { return fmt.Errorf("cannot get EndPoint from tablet record: %v", err) } // pass in a non-UNKNOWN tablet type to not use sessionId conn, err := tabletconn.GetDialer()(ctx, ep, "", "", pb.TabletType_MASTER, *connectTimeout) if err != nil { return fmt.Errorf("cannot connect to tablet %v: %v", tabletAlias, err) } stream, errFunc, err := conn.StreamHealth(ctx) if err != nil { return err } for i := 0; i < *count; i++ { shr, ok := <-stream if !ok { return fmt.Errorf("stream ended early: %v", errFunc()) } data, err := json.Marshal(shr) if err != nil { wr.Logger().Errorf("cannot json-marshal structure: %v", err) } else { wr.Logger().Printf("%v\n", string(data)) } } return nil }
func commandReparentTablet(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error { if *disableActiveReparents { return fmt.Errorf("active reparent actions disable in this cluster") } if err := subFlags.Parse(args); err != nil { return err } if subFlags.NArg() != 1 { return fmt.Errorf("action ReparentTablet requires <tablet alias>") } tabletAlias, err := topo.ParseTabletAliasString(subFlags.Arg(0)) if err != nil { return err } return wr.ReparentTablet(ctx, tabletAlias) }
func commandVtTabletBegin(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error { keyspace := subFlags.String("keyspace", "", "keyspace the tablet belongs to") shard := subFlags.String("shard", "", "shard the tablet belongs to") tabletType := subFlags.String("tablet_type", "unknown", "tablet type we expect from the tablet (use unknown to use sessionId)") connectTimeout := subFlags.Duration("connect_timeout", 30*time.Second, "Connection timeout for vttablet client") if err := subFlags.Parse(args); err != nil { return err } if subFlags.NArg() != 1 { return fmt.Errorf("the <tablet_alias> argument is required for the VtTabletBegin command") } tt, err := topo.ParseTabletType(*tabletType) if err != nil { return err } tabletAlias, err := topo.ParseTabletAliasString(subFlags.Arg(0)) if err != nil { return err } tabletInfo, err := wr.TopoServer().GetTablet(ctx, tabletAlias) if err != nil { return err } ep, err := topo.TabletEndPoint(tabletInfo.Tablet) if err != nil { return fmt.Errorf("cannot get EndPoint from tablet record: %v", err) } conn, err := tabletconn.GetDialer()(ctx, ep, *keyspace, *shard, tt, *connectTimeout) if err != nil { return fmt.Errorf("cannot connect to tablet %v: %v", tabletAlias, err) } defer conn.Close() transactionID, err := conn.Begin(ctx) if err != nil { return fmt.Errorf("Begin failed: %v", err) } result := map[string]int64{ "transaction_id": transactionID, } return printJSON(wr, result) }
func commandVtTabletExecute(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error { transactionID := subFlags.Int("transaction_id", 0, "transaction id to use, if inside a transaction.") bindVariables := newBindvars(subFlags) keyspace := subFlags.String("keyspace", "", "keyspace the tablet belongs to") shard := subFlags.String("shard", "", "shard the tablet belongs to") tabletType := subFlags.String("tablet_type", "unknown", "tablet type we expect from the tablet (use unknown to use sessionId)") connectTimeout := subFlags.Duration("connect_timeout", 30*time.Second, "Connection timeout for vttablet client") if err := subFlags.Parse(args); err != nil { return err } if subFlags.NArg() != 2 { return fmt.Errorf("the <tablet_alis> and <sql> arguments are required for the VtTabletExecute command") } tt, err := parseTabletType3(*tabletType) if err != nil { return err } tabletAlias, err := topo.ParseTabletAliasString(subFlags.Arg(0)) if err != nil { return err } tabletInfo, err := wr.TopoServer().GetTablet(ctx, tabletAlias) if err != nil { return err } ep, err := tabletInfo.EndPoint() if err != nil { return fmt.Errorf("cannot get EndPoint from tablet record: %v", err) } conn, err := tabletconn.GetDialer()(ctx, ep, *keyspace, *shard, tt, *connectTimeout) if err != nil { return fmt.Errorf("cannot connect to tablet %v: %v", tabletAlias, err) } defer conn.Close() qr, err := conn.Execute(ctx, subFlags.Arg(1), *bindVariables, int64(*transactionID)) if err != nil { return fmt.Errorf("Execute failed: %v", err) } wr.Logger().Printf("%v\n", jscfg.ToJSON(qr)) return nil }
func commandDemoteMaster(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error { if *disableActiveReparents { return fmt.Errorf("active reparent actions disable in this cluster") } if err := subFlags.Parse(args); err != nil { return err } if subFlags.NArg() != 1 { return fmt.Errorf("action DemoteMaster requires <tablet alias>") } tabletAlias, err := topo.ParseTabletAliasString(subFlags.Arg(0)) if err != nil { return err } tabletInfo, err := wr.TopoServer().GetTablet(ctx, tabletAlias) if err != nil { return err } _, err = wr.TabletManagerClient().DemoteMaster(ctx, tabletInfo) return err }
func commandEmergencyReparentShard(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error { if *disableActiveReparents { return fmt.Errorf("active reparent actions disable in this cluster") } waitSlaveTimeout := subFlags.Duration("wait_slave_timeout", 30*time.Second, "time to wait for slaves to catch up in reparenting") if err := subFlags.Parse(args); err != nil { return err } if subFlags.NArg() != 2 { return fmt.Errorf("action EmergencyReparentShard requires <keyspace/shard> <tablet alias>") } keyspace, shard, err := topo.ParseKeyspaceShardString(subFlags.Arg(0)) if err != nil { return err } tabletAlias, err := topo.ParseTabletAliasString(subFlags.Arg(1)) if err != nil { return err } return wr.EmergencyReparentShard(ctx, keyspace, shard, tabletAlias, *waitSlaveTimeout) }
func commandInitShardMaster(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error { if *disableActiveReparents { return fmt.Errorf("active reparent actions disable in this cluster") } force := subFlags.Bool("force", false, "will force the reparent even if the provided tablet is not a master or the shard master") waitSlaveTimeout := subFlags.Duration("wait_slave_timeout", 30*time.Second, "time to wait for slaves to catch up in reparenting") if err := subFlags.Parse(args); err != nil { return err } if subFlags.NArg() != 2 { return fmt.Errorf("action InitShardMaster requires <keyspace/shard> <tablet alias>") } keyspace, shard, err := topo.ParseKeyspaceShardString(subFlags.Arg(0)) if err != nil { return err } tabletAlias, err := topo.ParseTabletAliasString(subFlags.Arg(1)) if err != nil { return err } return wr.InitShardMaster(ctx, keyspace, shard, tabletAlias, *force, *waitSlaveTimeout) }
func (zkts *Server) GetReplicationPaths(keyspace, shard, repPath string) ([]topo.TabletAlias, error) { replicationPath := path.Join(globalKeyspacesPath, keyspace, "shards", shard, repPath) children, _, err := zkts.zconn.Children(replicationPath) if err != nil { if zookeeper.IsError(err, zookeeper.ZNONODE) { err = topo.ErrNoNode } return nil, err } result := make([]topo.TabletAlias, 0, len(children)) for _, child := range children { // 'action' and 'actionlog' can only be present at the toplevel if (repPath == "" || repPath == "/") && (child == "action" || child == "actionlog") { continue } alias, err := topo.ParseTabletAliasString(child) if err != nil { return nil, err } result = append(result, alias) } return result, nil }
func main() { flag.Parse() servenv.Init() defer servenv.Close() templateLoader = NewTemplateLoader(*templateDir, dummyTemplate, *debug) ts = topo.GetServer() defer topo.CloseServers() wr := wrangler.New(logutil.NewConsoleLogger(), ts, 30*time.Second, 30*time.Second) actionRepo = NewActionRepository(ts) // keyspace actions actionRepo.RegisterKeyspaceAction("ValidateKeyspace", func(wr *wrangler.Wrangler, keyspace string, r *http.Request) (string, error) { return "", wr.ValidateKeyspace(keyspace, false) }) actionRepo.RegisterKeyspaceAction("ValidateSchemaKeyspace", func(wr *wrangler.Wrangler, keyspace string, r *http.Request) (string, error) { return "", wr.ValidateSchemaKeyspace(keyspace, nil, false) }) actionRepo.RegisterKeyspaceAction("ValidateVersionKeyspace", func(wr *wrangler.Wrangler, keyspace string, r *http.Request) (string, error) { return "", wr.ValidateVersionKeyspace(keyspace) }) actionRepo.RegisterKeyspaceAction("ValidatePermissionsKeyspace", func(wr *wrangler.Wrangler, keyspace string, r *http.Request) (string, error) { return "", wr.ValidatePermissionsKeyspace(keyspace) }) // shard actions actionRepo.RegisterShardAction("ValidateShard", func(wr *wrangler.Wrangler, keyspace, shard string, r *http.Request) (string, error) { return "", wr.ValidateShard(keyspace, shard, false) }) actionRepo.RegisterShardAction("ValidateSchemaShard", func(wr *wrangler.Wrangler, keyspace, shard string, r *http.Request) (string, error) { return "", wr.ValidateSchemaShard(keyspace, shard, nil, false) }) actionRepo.RegisterShardAction("ValidateVersionShard", func(wr *wrangler.Wrangler, keyspace, shard string, r *http.Request) (string, error) { return "", wr.ValidateVersionShard(keyspace, shard) }) actionRepo.RegisterShardAction("ValidatePermissionsShard", func(wr *wrangler.Wrangler, keyspace, shard string, r *http.Request) (string, error) { return "", wr.ValidatePermissionsShard(keyspace, shard) }) // tablet actions actionRepo.RegisterTabletAction("RpcPing", "", func(wr *wrangler.Wrangler, tabletAlias topo.TabletAlias, r *http.Request) (string, error) { return "", wr.ActionInitiator().RpcPing(tabletAlias, 10*time.Second) }) actionRepo.RegisterTabletAction("ScrapTablet", acl.ADMIN, func(wr *wrangler.Wrangler, tabletAlias topo.TabletAlias, r *http.Request) (string, error) { // refuse to scrap tablets that are not spare ti, err := wr.TopoServer().GetTablet(tabletAlias) if err != nil { return "", err } if ti.Type != topo.TYPE_SPARE { return "", fmt.Errorf("Can only scrap spare tablets") } actionPath, err := wr.Scrap(tabletAlias, false, false) if err != nil { return "", err } return "", wr.WaitForCompletion(actionPath) }) actionRepo.RegisterTabletAction("ScrapTabletForce", acl.ADMIN, func(wr *wrangler.Wrangler, tabletAlias topo.TabletAlias, r *http.Request) (string, error) { // refuse to scrap tablets that are not spare ti, err := wr.TopoServer().GetTablet(tabletAlias) if err != nil { return "", err } if ti.Type != topo.TYPE_SPARE { return "", fmt.Errorf("Can only scrap spare tablets") } _, err = wr.Scrap(tabletAlias, true, false) return "", err }) actionRepo.RegisterTabletAction("DeleteTablet", acl.ADMIN, func(wr *wrangler.Wrangler, tabletAlias topo.TabletAlias, r *http.Request) (string, error) { return "", wr.DeleteTablet(tabletAlias) }) // toplevel index http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { templateLoader.ServeTemplate("index.html", indexContent, w, r) }) // keyspace actions http.HandleFunc("/keyspace_actions", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpError(w, "cannot parse form: %s", err) return } action := r.FormValue("action") if action == "" { http.Error(w, "no action provided", http.StatusBadRequest) return } keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "no keyspace provided", http.StatusBadRequest) return } result := actionRepo.ApplyKeyspaceAction(action, keyspace, r) templateLoader.ServeTemplate("action.html", result, w, r) }) // shard actions http.HandleFunc("/shard_actions", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpError(w, "cannot parse form: %s", err) return } action := r.FormValue("action") if action == "" { http.Error(w, "no action provided", http.StatusBadRequest) return } keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "no keyspace provided", http.StatusBadRequest) return } shard := r.FormValue("shard") if shard == "" { http.Error(w, "no shard provided", http.StatusBadRequest) return } result := actionRepo.ApplyShardAction(action, keyspace, shard, r) templateLoader.ServeTemplate("action.html", result, w, r) }) // tablet actions http.HandleFunc("/tablet_actions", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpError(w, "cannot parse form: %s", err) return } action := r.FormValue("action") if action == "" { http.Error(w, "no action provided", http.StatusBadRequest) return } alias := r.FormValue("alias") if alias == "" { http.Error(w, "no alias provided", http.StatusBadRequest) return } tabletAlias, err := topo.ParseTabletAliasString(alias) if err != nil { http.Error(w, "bad alias provided", http.StatusBadRequest) return } result := actionRepo.ApplyTabletAction(action, tabletAlias, r) templateLoader.ServeTemplate("action.html", result, w, r) }) // topology server http.HandleFunc("/dbtopo", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpError(w, "cannot parse form: %s", err) return } result := DbTopologyResult{} topology, err := topotools.DbTopology(wr.TopoServer()) if err != nil { result.Error = err.Error() } else { result.Topology = topology } templateLoader.ServeTemplate("dbtopo.html", result, w, r) }) // serving graph http.HandleFunc("/serving_graph/", func(w http.ResponseWriter, r *http.Request) { parts := strings.Split(r.URL.Path, "/") cell := parts[len(parts)-1] if cell == "" { cells, err := ts.GetKnownCells() if err != nil { httpError(w, "cannot get known cells: %v", err) return } else { templateLoader.ServeTemplate("serving_graph_cells.html", cells, w, r) } return } servingGraph := topotools.DbServingGraph(wr.TopoServer(), cell) templateLoader.ServeTemplate("serving_graph.html", servingGraph, w, r) }) // redirects for explorers http.HandleFunc("/explorers/redirect", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpError(w, "cannot parse form: %s", err) return } var explorer Explorer switch len(explorers) { case 0: http.Error(w, "no explorer configured", http.StatusInternalServerError) return case 1: for _, ex := range explorers { explorer = ex } default: explorerName := r.FormValue("explorer") var ok bool explorer, ok = explorers[explorerName] if !ok { http.Error(w, "bad explorer name", http.StatusBadRequest) return } } var target string switch r.FormValue("type") { case "keyspace": keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "keyspace is obligatory for this redirect", http.StatusBadRequest) return } target = explorer.GetKeyspacePath(keyspace) case "shard": keyspace, shard := r.FormValue("keyspace"), r.FormValue("shard") if keyspace == "" || shard == "" { http.Error(w, "keyspace and shard are obligatory for this redirect", http.StatusBadRequest) return } target = explorer.GetShardPath(keyspace, shard) case "srv_keyspace": cell := r.FormValue("cell") if cell == "" { http.Error(w, "cell is obligatory for this redirect", http.StatusBadRequest) return } keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "keyspace is obligatory for this redirect", http.StatusBadRequest) return } target = explorer.GetSrvKeyspacePath(cell, keyspace) case "srv_shard": cell := r.FormValue("cell") if cell == "" { http.Error(w, "cell is obligatory for this redirect", http.StatusBadRequest) return } keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "keyspace is obligatory for this redirect", http.StatusBadRequest) return } shard := r.FormValue("shard") if shard == "" { http.Error(w, "shard is obligatory for this redirect", http.StatusBadRequest) return } target = explorer.GetSrvShardPath(cell, keyspace, shard) case "srv_type": cell := r.FormValue("cell") if cell == "" { http.Error(w, "cell is obligatory for this redirect", http.StatusBadRequest) return } keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "keyspace is obligatory for this redirect", http.StatusBadRequest) return } shard := r.FormValue("shard") if shard == "" { http.Error(w, "shard is obligatory for this redirect", http.StatusBadRequest) return } tabletType := r.FormValue("tablet_type") if tabletType == "" { http.Error(w, "tablet_type is obligatory for this redirect", http.StatusBadRequest) return } target = explorer.GetSrvTypePath(cell, keyspace, shard, topo.TabletType(tabletType)) case "tablet": aliasName := r.FormValue("alias") if aliasName == "" { http.Error(w, "keyspace is obligatory for this redirect", http.StatusBadRequest) return } alias, err := topo.ParseTabletAliasString(aliasName) if err != nil { http.Error(w, "bad tablet alias", http.StatusBadRequest) return } target = explorer.GetTabletPath(alias) case "replication": cell := r.FormValue("cell") if cell == "" { http.Error(w, "cell is obligatory for this redirect", http.StatusBadRequest) return } keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "keyspace is obligatory for this redirect", http.StatusBadRequest) return } shard := r.FormValue("shard") if shard == "" { http.Error(w, "shard is obligatory for this redirect", http.StatusBadRequest) return } target = explorer.GetReplicationSlaves(cell, keyspace, shard) default: http.Error(w, "bad redirect type", http.StatusBadRequest) return } http.Redirect(w, r, target, http.StatusFound) }) servenv.RunDefault() }
func initAPI(ctx context.Context, ts topo.Server, actions *ActionRepository) { tabletHealthCache := newTabletHealthCache(ts) // 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) }) // 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 := topo.ParseKeyspaceShardString(shardRef) if err != nil { return nil, err } if cell != "" { return topo.FindAllTabletAliasesInShardByCell(ctx, ts, keyspace, shard, []string{cell}) } return topo.FindAllTabletAliasesInShard(ctx, ts, 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 := topo.ParseTabletAliasString(parts[0]) if err != nil { return nil, err } return tabletHealthCache.Get(ctx, tabletAlias) } tabletAlias, err := topo.ParseTabletAliasString(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) }) // EndPoints handleCollection("endpoints", func(r *http.Request) (interface{}, error) { // We expect cell/keyspace/shard/tabletType. epPath := getItemPath(r.URL.Path) parts := strings.Split(epPath, "/") if len(parts) != 4 { return nil, fmt.Errorf("invalid cell/keyspace/shard/tabletType: %q", epPath) } if parts[3] == "" { // tabletType is empty, so list the tablet types. return ts.GetSrvTabletTypesPerShard(ctx, parts[0], parts[1], parts[2]) } // Get the endpoints object for a specific type. ep, _, err := ts.GetEndPoints(ctx, parts[0], parts[1], parts[2], topo.TabletType(parts[3])) return ep, err }) }
func initAPI(ctx context.Context, ts topo.Server, actions *ActionRepository) { tabletHealthCache := newTabletHealthCache(ts) // 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) }) // 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 := topo.ParseKeyspaceShardString(shardRef) if err != nil { return nil, err } if cell != "" { return topo.FindAllTabletAliasesInShardByCell(ctx, ts, keyspace, shard, []string{cell}) } return topo.FindAllTabletAliasesInShard(ctx, ts, 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 := topo.ParseTabletAliasString(parts[0]) if err != nil { return nil, err } return tabletHealthCache.Get(ctx, tabletAlias) } tabletAlias, err := topo.ParseTabletAliasString(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) }) // EndPoints handleCollection("endpoints", func(r *http.Request) (interface{}, error) { // We expect cell/keyspace/shard/tabletType. epPath := getItemPath(r.URL.Path) parts := strings.Split(epPath, "/") if len(parts) != 4 { return nil, fmt.Errorf("invalid cell/keyspace/shard/tabletType: %q", epPath) } if parts[3] == "" { // tabletType is empty, so list the tablet types. return ts.GetSrvTabletTypesPerShard(ctx, parts[0], parts[1], parts[2]) } // Get the endpoints object for a specific type. ep, _, err := ts.GetEndPoints(ctx, parts[0], parts[1], parts[2], topo.TabletType(parts[3])) return ep, err }) // Schema Change http.HandleFunc(apiPrefix+"schema/apply", func(w http.ResponseWriter, r *http.Request) { req := struct{ Keyspace, SQL string }{} if err := unmarshalRequest(r, &req); err != nil { httpErrorf(w, r, "can't unmarshal request: %v", err) return } executor := schemamanager.NewTabletExecutor( tmclient.NewTabletManagerClient(), ts) schemamanager.Run(ctx, schemamanager.NewUIController(req.SQL, req.Keyspace, w), executor) }) // VSchema http.HandleFunc(apiPrefix+"vschema/", func(w http.ResponseWriter, r *http.Request) { schemafier, ok := ts.(topo.Schemafier) if !ok { httpErrorf(w, r, "%T doesn't support schemafier API", ts) return } // Save VSchema if r.Method == "POST" { vschema, err := ioutil.ReadAll(r.Body) if err != nil { httpErrorf(w, r, "can't read request body: %v", err) return } if err := schemafier.SaveVSchema(ctx, string(vschema)); err != nil { httpErrorf(w, r, "can't save vschema: %v", err) } return } // Get VSchema vschema, err := schemafier.GetVSchema(ctx) if err != nil { httpErrorf(w, r, "can't get vschema: %v", err) return } w.Header().Set("Content-Type", jsonContentType) w.Write([]byte(vschema)) }) }
func main() { flag.Parse() servenv.Init() defer servenv.Close() templateLoader = NewTemplateLoader(*templateDir, dummyTemplate, *debug) ts := topo.GetServer() defer topo.CloseServers() wr := wrangler.New(ts, 30*time.Second, 30*time.Second) actionRepo = NewActionRepository(wr) // keyspace actions actionRepo.RegisterKeyspaceAction("ValidateKeyspace", func(wr *wrangler.Wrangler, keyspace string, r *http.Request) (string, error) { return "", wr.ValidateKeyspace(keyspace, false) }) actionRepo.RegisterKeyspaceAction("ValidateSchemaKeyspace", func(wr *wrangler.Wrangler, keyspace string, r *http.Request) (string, error) { return "", wr.ValidateSchemaKeyspace(keyspace, false) }) actionRepo.RegisterKeyspaceAction("ValidateVersionKeyspace", func(wr *wrangler.Wrangler, keyspace string, r *http.Request) (string, error) { return "", wr.ValidateVersionKeyspace(keyspace) }) actionRepo.RegisterKeyspaceAction("ValidatePermissionsKeyspace", func(wr *wrangler.Wrangler, keyspace string, r *http.Request) (string, error) { return "", wr.ValidatePermissionsKeyspace(keyspace) }) // shard actions actionRepo.RegisterShardAction("ValidateShard", func(wr *wrangler.Wrangler, keyspace, shard string, r *http.Request) (string, error) { return "", wr.ValidateShard(keyspace, shard, false) }) actionRepo.RegisterShardAction("ValidateSchemaShard", func(wr *wrangler.Wrangler, keyspace, shard string, r *http.Request) (string, error) { return "", wr.ValidateSchemaShard(keyspace, shard, false) }) actionRepo.RegisterShardAction("ValidateVersionShard", func(wr *wrangler.Wrangler, keyspace, shard string, r *http.Request) (string, error) { return "", wr.ValidateVersionShard(keyspace, shard) }) actionRepo.RegisterShardAction("ValidatePermissionsShard", func(wr *wrangler.Wrangler, keyspace, shard string, r *http.Request) (string, error) { return "", wr.ValidatePermissionsShard(keyspace, shard) }) // tablet actions actionRepo.RegisterTabletAction("RpcPing", func(wr *wrangler.Wrangler, tabletAlias topo.TabletAlias, r *http.Request) (string, error) { return "", wr.ActionInitiator().RpcPing(tabletAlias, 10*time.Second) }) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { templateLoader.ServeTemplate("index.html", indexContent, w, r) }) http.HandleFunc("/keyspace_actions", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpError(w, "cannot parse form: %s", err) return } action := r.FormValue("action") if action == "" { http.Error(w, "no action provided", http.StatusBadRequest) return } keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "no keyspace provided", http.StatusBadRequest) return } result := actionRepo.ApplyKeyspaceAction(action, keyspace, r) templateLoader.ServeTemplate("action.html", result, w, r) }) http.HandleFunc("/shard_actions", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpError(w, "cannot parse form: %s", err) return } action := r.FormValue("action") if action == "" { http.Error(w, "no action provided", http.StatusBadRequest) return } keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "no keyspace provided", http.StatusBadRequest) return } shard := r.FormValue("shard") if shard == "" { http.Error(w, "no shard provided", http.StatusBadRequest) return } result := actionRepo.ApplyShardAction(action, keyspace, shard, r) templateLoader.ServeTemplate("action.html", result, w, r) }) http.HandleFunc("/tablet_actions", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpError(w, "cannot parse form: %s", err) return } action := r.FormValue("action") if action == "" { http.Error(w, "no action provided", http.StatusBadRequest) return } alias := r.FormValue("alias") if alias == "" { http.Error(w, "no alias provided", http.StatusBadRequest) return } tabletAlias, err := topo.ParseTabletAliasString(alias) if err != nil { http.Error(w, "bad alias provided", http.StatusBadRequest) return } result := actionRepo.ApplyTabletAction(action, tabletAlias, r) templateLoader.ServeTemplate("action.html", result, w, r) }) http.HandleFunc("/dbtopo", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpError(w, "cannot parse form: %s", err) return } result := DbTopologyResult{} topology, err := wr.DbTopology() if err != nil { result.Error = err.Error() } else { result.Topology = topology } templateLoader.ServeTemplate("dbtopo.html", result, w, r) }) http.HandleFunc("/serving_graph/", func(w http.ResponseWriter, r *http.Request) { parts := strings.Split(r.URL.Path, "/") cell := parts[len(parts)-1] if cell == "" { cells, err := wr.TopoServer().GetKnownCells() if err != nil { httpError(w, "cannot get known cells: %v", err) return } else { templateLoader.ServeTemplate("serving_graph_cells.html", cells, w, r) } return } result := ServingGraphResult{} servingGraph, err := wr.ServingGraph(cell) if err != nil { result.Error = err.Error() } else { result.ServingGraph = servingGraph } templateLoader.ServeTemplate("serving_graph.html", result, w, r) }) http.HandleFunc("/explorers/redirect", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpError(w, "cannot parse form: %s", err) return } explorerName := r.FormValue("explorer") explorer, ok := explorers[explorerName] if !ok { http.Error(w, "bad explorer name", http.StatusBadRequest) return } var target string switch r.FormValue("type") { case "keyspace": keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "keyspace is obligatory for this redirect", http.StatusBadRequest) return } target = explorer.GetKeyspacePath(keyspace) case "shard": keyspace, shard := r.FormValue("keyspace"), r.FormValue("shard") if keyspace == "" || shard == "" { http.Error(w, "keyspace and shard are obligatory for this redirect", http.StatusBadRequest) return } target = explorer.GetShardPath(keyspace, shard) case "srv_keyspace": keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "keyspace is obligatory for this redirect", http.StatusBadRequest) return } cell := r.FormValue("cell") if cell == "" { http.Error(w, "cell is obligatory for this redirect", http.StatusBadRequest) return } target = explorer.GetSrvKeyspacePath(cell, keyspace) case "srv_shard": keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "keyspace is obligatory for this redirect", http.StatusBadRequest) return } cell := r.FormValue("cell") if cell == "" { http.Error(w, "cell is obligatory for this redirect", http.StatusBadRequest) return } shard := r.FormValue("shard") if shard == "" { http.Error(w, "shard is obligatory for this redirect", http.StatusBadRequest) return } target = explorer.GetSrvShardPath(cell, keyspace, shard) case "srv_type": keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "keyspace is obligatory for this redirect", http.StatusBadRequest) return } cell := r.FormValue("cell") if cell == "" { http.Error(w, "cell is obligatory for this redirect", http.StatusBadRequest) return } shard := r.FormValue("shard") if shard == "" { http.Error(w, "shard is obligatory for this redirect", http.StatusBadRequest) return } tabletType := r.FormValue("tablet_type") if tabletType == "" { http.Error(w, "tablet_type is obligatory for this redirect", http.StatusBadRequest) return } target = explorer.GetSrvTypePath(cell, keyspace, shard, topo.TabletType(tabletType)) case "tablet": aliasName := r.FormValue("alias") if aliasName == "" { http.Error(w, "keyspace is obligatory for this redirect", http.StatusBadRequest) return } alias, err := topo.ParseTabletAliasString(aliasName) if err != nil { http.Error(w, "bad tablet alias", http.StatusBadRequest) return } target = explorer.GetTabletPath(alias) case "replication": keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "keyspace is obligatory for this redirect", http.StatusBadRequest) return } cell := r.FormValue("cell") if cell == "" { http.Error(w, "cell is obligatory for this redirect", http.StatusBadRequest) return } shard := r.FormValue("shard") if shard == "" { http.Error(w, "shard is obligatory for this redirect", http.StatusBadRequest) return } target = explorer.GetReplicationSlaves(cell, keyspace, shard) default: http.Error(w, "bad redirect type", http.StatusBadRequest) return } http.Redirect(w, r, target, http.StatusFound) }) servenv.Run(*port) }
func main() { flag.Parse() servenv.Init() defer servenv.Close() templateLoader = NewTemplateLoader(*templateDir, *debug) ts = topo.GetServer() defer topo.CloseServers() actionRepo = NewActionRepository(ts) // keyspace actions actionRepo.RegisterKeyspaceAction("ValidateKeyspace", func(ctx context.Context, wr *wrangler.Wrangler, keyspace string, r *http.Request) (string, error) { return "", wr.ValidateKeyspace(ctx, keyspace, false) }) actionRepo.RegisterKeyspaceAction("ValidateSchemaKeyspace", func(ctx context.Context, wr *wrangler.Wrangler, keyspace string, r *http.Request) (string, error) { return "", wr.ValidateSchemaKeyspace(ctx, keyspace, nil, false) }) actionRepo.RegisterKeyspaceAction("ValidateVersionKeyspace", func(ctx context.Context, wr *wrangler.Wrangler, keyspace string, r *http.Request) (string, error) { return "", wr.ValidateVersionKeyspace(ctx, keyspace) }) actionRepo.RegisterKeyspaceAction("ValidatePermissionsKeyspace", func(ctx context.Context, wr *wrangler.Wrangler, keyspace string, r *http.Request) (string, error) { return "", wr.ValidatePermissionsKeyspace(ctx, keyspace) }) // shard actions actionRepo.RegisterShardAction("ValidateShard", func(ctx context.Context, wr *wrangler.Wrangler, keyspace, shard string, r *http.Request) (string, error) { return "", wr.ValidateShard(ctx, keyspace, shard, false) }) actionRepo.RegisterShardAction("ValidateSchemaShard", func(ctx context.Context, wr *wrangler.Wrangler, keyspace, shard string, r *http.Request) (string, error) { return "", wr.ValidateSchemaShard(ctx, keyspace, shard, nil, false) }) actionRepo.RegisterShardAction("ValidateVersionShard", func(ctx context.Context, wr *wrangler.Wrangler, keyspace, shard string, r *http.Request) (string, error) { return "", wr.ValidateVersionShard(ctx, keyspace, shard) }) actionRepo.RegisterShardAction("ValidatePermissionsShard", func(ctx context.Context, wr *wrangler.Wrangler, keyspace, shard string, r *http.Request) (string, error) { return "", wr.ValidatePermissionsShard(ctx, keyspace, shard) }) // tablet actions actionRepo.RegisterTabletAction("Ping", "", func(ctx context.Context, wr *wrangler.Wrangler, tabletAlias *pb.TabletAlias, r *http.Request) (string, error) { ti, err := wr.TopoServer().GetTablet(ctx, tabletAlias) if err != nil { return "", err } return "", wr.TabletManagerClient().Ping(ctx, ti) }) actionRepo.RegisterTabletAction("RefreshState", acl.ADMIN, func(ctx context.Context, wr *wrangler.Wrangler, tabletAlias *pb.TabletAlias, r *http.Request) (string, error) { ti, err := wr.TopoServer().GetTablet(ctx, tabletAlias) if err != nil { return "", err } return "", wr.TabletManagerClient().RefreshState(ctx, ti) }) actionRepo.RegisterTabletAction("ScrapTablet", acl.ADMIN, func(ctx context.Context, wr *wrangler.Wrangler, tabletAlias *pb.TabletAlias, r *http.Request) (string, error) { // refuse to scrap tablets that are not spare ti, err := wr.TopoServer().GetTablet(ctx, tabletAlias) if err != nil { return "", err } if ti.Type != pb.TabletType_SPARE { return "", fmt.Errorf("Can only scrap spare tablets") } return "", wr.Scrap(ctx, tabletAlias, false, false) }) actionRepo.RegisterTabletAction("ScrapTabletForce", acl.ADMIN, func(ctx context.Context, wr *wrangler.Wrangler, tabletAlias *pb.TabletAlias, r *http.Request) (string, error) { // refuse to scrap tablets that are not spare ti, err := wr.TopoServer().GetTablet(ctx, tabletAlias) if err != nil { return "", err } if ti.Type != pb.TabletType_SPARE { return "", fmt.Errorf("Can only scrap spare tablets") } return "", wr.Scrap(ctx, tabletAlias, true, false) }) actionRepo.RegisterTabletAction("DeleteTablet", acl.ADMIN, func(ctx context.Context, wr *wrangler.Wrangler, tabletAlias *pb.TabletAlias, r *http.Request) (string, error) { return "", wr.DeleteTablet(ctx, tabletAlias) }) actionRepo.RegisterTabletAction("ReloadSchema", acl.ADMIN, func(ctx context.Context, wr *wrangler.Wrangler, tabletAlias *pb.TabletAlias, r *http.Request) (string, error) { return "", wr.ReloadSchema(ctx, tabletAlias) }) // keyspace actions http.HandleFunc("/keyspace_actions", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpErrorf(w, r, "cannot parse form: %s", err) return } action := r.FormValue("action") if action == "" { http.Error(w, "no action provided", http.StatusBadRequest) return } keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "no keyspace provided", http.StatusBadRequest) return } ctx := context.Background() result := actionRepo.ApplyKeyspaceAction(ctx, action, keyspace, r) templateLoader.ServeTemplate("action.html", result, w, r) }) // shard actions http.HandleFunc("/shard_actions", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpErrorf(w, r, "cannot parse form: %s", err) return } action := r.FormValue("action") if action == "" { http.Error(w, "no action provided", http.StatusBadRequest) return } keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "no keyspace provided", http.StatusBadRequest) return } shard := r.FormValue("shard") if shard == "" { http.Error(w, "no shard provided", http.StatusBadRequest) return } ctx := context.Background() result := actionRepo.ApplyShardAction(ctx, action, keyspace, shard, r) templateLoader.ServeTemplate("action.html", result, w, r) }) // tablet actions http.HandleFunc("/tablet_actions", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpErrorf(w, r, "cannot parse form: %s", err) return } action := r.FormValue("action") if action == "" { http.Error(w, "no action provided", http.StatusBadRequest) return } alias := r.FormValue("alias") if alias == "" { http.Error(w, "no alias provided", http.StatusBadRequest) return } tabletAlias, err := topo.ParseTabletAliasString(alias) if err != nil { http.Error(w, "bad alias provided", http.StatusBadRequest) return } ctx := context.Background() result := actionRepo.ApplyTabletAction(ctx, action, tabletAlias, r) templateLoader.ServeTemplate("action.html", result, w, r) }) // topology server http.HandleFunc("/dbtopo", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpErrorf(w, r, "cannot parse form: %s", err) return } result := DbTopologyResult{} ctx := context.TODO() topology, err := topotools.DbTopology(ctx, ts) if err == nil && modifyDbTopology != nil { err = modifyDbTopology(ctx, ts, topology) } if err != nil { result.Error = err.Error() } else { result.Topology = topology } templateLoader.ServeTemplate("dbtopo.html", result, w, r) }) // serving graph http.HandleFunc("/serving_graph/", func(w http.ResponseWriter, r *http.Request) { ctx := context.Background() parts := strings.Split(r.URL.Path, "/") cell := parts[len(parts)-1] if cell == "" { cells, err := ts.GetKnownCells(ctx) if err != nil { httpErrorf(w, r, "cannot get known cells: %v", err) return } templateLoader.ServeTemplate("serving_graph_cells.html", cells, w, r) return } servingGraph := topotools.DbServingGraph(ctx, ts, cell) if modifyDbServingGraph != nil { modifyDbServingGraph(ctx, ts, servingGraph) } templateLoader.ServeTemplate("serving_graph.html", servingGraph, w, r) }) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/app/", http.StatusFound) }) http.HandleFunc("/content/", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, *templateDir+r.URL.Path[8:]) }) // Serve the static files for the vtctld web app. http.HandleFunc("/app/", func(w http.ResponseWriter, r *http.Request) { // Strip the prefix. parts := strings.SplitN(r.URL.Path, "/", 3) if len(parts) != 3 { http.NotFound(w, r) return } rest := parts[2] if rest == "" { rest = "index.html" } http.ServeFile(w, r, path.Join(*webDir, rest)) }) // Serve the REST API for the vtctld web app. initAPI(context.Background(), ts, actionRepo) // vschema viewer http.HandleFunc("/vschema", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpErrorf(w, r, "cannot parse form: %s", err) return } schemafier, ok := ts.(topo.Schemafier) if !ok { httpErrorf(w, r, "%s", fmt.Errorf("%T doesn't support schemafier API", ts)) } var data struct { Error error Input, Output string } ctx := context.Background() switch r.Method { case "POST": data.Input = r.FormValue("vschema") data.Error = schemafier.SaveVSchema(ctx, data.Input) } vschema, err := schemafier.GetVSchema(ctx) if err != nil { if data.Error == nil { data.Error = fmt.Errorf("Error fetching schema: %s", err) } } data.Output = vschema templateLoader.ServeTemplate("vschema.html", data, w, r) }) // redirects for explorers http.HandleFunc("/explorers/redirect", func(w http.ResponseWriter, r *http.Request) { if explorer == nil { http.Error(w, "no explorer configured", http.StatusInternalServerError) return } if err := r.ParseForm(); err != nil { httpErrorf(w, r, "cannot parse form: %s", err) return } target, err := handleExplorerRedirect(r) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } http.Redirect(w, r, target, http.StatusFound) }) // serve some data knownCellsCache := newKnownCellsCache(ts) http.HandleFunc("/json/KnownCells", func(w http.ResponseWriter, r *http.Request) { ctx := context.Background() result, err := knownCellsCache.Get(ctx) if err != nil { httpErrorf(w, r, "error getting known cells: %v", err) return } w.Write(result) }) keyspacesCache := newKeyspacesCache(ts) http.HandleFunc("/json/Keyspaces", func(w http.ResponseWriter, r *http.Request) { ctx := context.Background() result, err := keyspacesCache.Get(ctx) if err != nil { httpErrorf(w, r, "error getting keyspaces: %v", err) return } w.Write(result) }) keyspaceCache := newKeyspaceCache(ts) http.HandleFunc("/json/Keyspace", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpErrorf(w, r, "cannot parse form: %s", err) return } keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "no keyspace provided", http.StatusBadRequest) return } ctx := context.Background() result, err := keyspaceCache.Get(ctx, keyspace) if err != nil { httpErrorf(w, r, "error getting keyspace: %v", err) return } w.Write(result) }) shardNamesCache := newShardNamesCache(ts) http.HandleFunc("/json/ShardNames", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpErrorf(w, r, "cannot parse form: %s", err) return } keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "no keyspace provided", http.StatusBadRequest) return } ctx := context.Background() result, err := shardNamesCache.Get(ctx, keyspace) if err != nil { httpErrorf(w, r, "error getting shardNames: %v", err) return } w.Write(result) }) shardCache := newShardCache(ts) http.HandleFunc("/json/Shard", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpErrorf(w, r, "cannot parse form: %s", err) return } keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "no keyspace provided", http.StatusBadRequest) return } shard := r.FormValue("shard") if shard == "" { http.Error(w, "no shard provided", http.StatusBadRequest) return } ctx := context.Background() result, err := shardCache.Get(ctx, keyspace+"/"+shard) if err != nil { httpErrorf(w, r, "error getting shard: %v", err) return } w.Write(result) }) cellShardTabletsCache := newCellShardTabletsCache(ts) http.HandleFunc("/json/CellShardTablets", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpErrorf(w, r, "cannot parse form: %s", err) return } cell := r.FormValue("cell") if cell == "" { http.Error(w, "no cell provided", http.StatusBadRequest) return } keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "no keyspace provided", http.StatusBadRequest) return } shard := r.FormValue("shard") if shard == "" { http.Error(w, "no shard provided", http.StatusBadRequest) return } ctx := context.Background() result, err := cellShardTabletsCache.Get(ctx, cell+"/"+keyspace+"/"+shard) if err != nil { httpErrorf(w, r, "error getting shard: %v", err) return } w.Write(result) }) // flush all data and will force a full client reload http.HandleFunc("/json/flush", func(w http.ResponseWriter, r *http.Request) { knownCellsCache.Flush() keyspacesCache.Flush() keyspaceCache.Flush() shardNamesCache.Flush() shardCache.Flush() cellShardTabletsCache.Flush() }) http.HandleFunc("/json/schema-manager", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpErrorf(w, r, "cannot parse form: %s", err) return } sqlStr := r.FormValue("data") keyspace := r.FormValue("keyspace") executor := schemamanager.NewTabletExecutor( tmclient.NewTabletManagerClient(), ts) ctx := context.Background() schemamanager.Run( ctx, schemamanager.NewUIController(sqlStr, keyspace, w), executor, ) }) if *schemaChangeDir != "" { interval := 60 if *schemaChangeCheckInterval > 0 { interval = *schemaChangeCheckInterval } timer := timer.NewTimer(time.Duration(interval) * time.Second) controllerFactory, err := schemamanager.GetControllerFactory(*schemaChangeController) if err != nil { log.Fatalf("unable to get a controller factory, error: %v", err) } timer.Start(func() { controller, err := controllerFactory(map[string]string{ schemamanager.SchemaChangeDirName: *schemaChangeDir, schemamanager.SchemaChangeUser: *schemaChangeUser, }) if err != nil { log.Errorf("failed to get controller, error: %v", err) return } ctx := context.Background() err = schemamanager.Run( ctx, controller, schemamanager.NewTabletExecutor( tmclient.NewTabletManagerClient(), ts), ) if err != nil { log.Errorf("Schema change failed, error: %v", err) } }) servenv.OnClose(func() { timer.Stop() }) } servenv.RunDefault() }
func initAPI(ctx context.Context, ts topo.Server) { // Get Cells handleGet("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) }) // Get Keyspaces handleGet("keyspaces", func(r *http.Request) (interface{}, error) { keyspace := getItemPath(r.URL.Path) if keyspace == "" { return ts.GetKeyspaces(ctx) } return ts.GetKeyspace(ctx, keyspace) }) // Get Shards handleGet("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) if parts[1] == "" { // It's just a keyspace. List the shards. return ts.GetShardNames(ctx, parts[0]) } // It's a keyspace/shard reference. return ts.GetShard(ctx, parts[0], parts[1]) }) // Get Tablets handleGet("tablets", func(r *http.Request) (interface{}, error) { tabletPath := getItemPath(r.URL.Path) if tabletPath == "" { // List tablets based on query params. 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 := topo.ParseKeyspaceShardString(shardRef) if err != nil { return nil, err } if cell != "" { return topo.FindAllTabletAliasesInShardByCell(ctx, ts, keyspace, shard, []string{cell}) } return topo.FindAllTabletAliasesInShard(ctx, ts, keyspace, shard) } // Get all tablets in a cell. if cell == "" { return nil, errors.New("cell param required") } return ts.GetTabletsByCell(ctx, cell) } // Get a specific tablet. tabletAlias, err := topo.ParseTabletAliasString(tabletPath) if err != nil { return nil, err } return ts.GetTablet(ctx, tabletAlias) }) // Get EndPoints handleGet("endpoints", func(r *http.Request) (interface{}, error) { // We expect cell/keyspace/shard/tabletType. epPath := getItemPath(r.URL.Path) parts := strings.Split(epPath, "/") if len(parts) != 4 { return nil, fmt.Errorf("invalid cell/keyspace/shard/tabletType: %q", epPath) } if parts[3] == "" { // tabletType is empty, so list the tablet types. return ts.GetSrvTabletTypesPerShard(ctx, parts[0], parts[1], parts[2]) } // Get the endpoints object for a specific type. ep, _, err := ts.GetEndPoints(ctx, parts[0], parts[1], parts[2], topo.TabletType(parts[3])) return ep, err }) }
func main() { flag.Parse() templateLoader = NewTemplateLoader(*templateDir, dummyTemplate, *debug) ts := topo.GetServer() defer topo.CloseServers() wr := wrangler.New(ts, 30*time.Second, 30*time.Second) actionRepo = NewActionRepository(wr) // keyspace actions actionRepo.RegisterKeyspaceAction("ValidateKeyspace", func(wr *wrangler.Wrangler, keyspace string, r *http.Request) (string, error) { return "", wr.ValidateKeyspace(keyspace, false) }) actionRepo.RegisterKeyspaceAction("ValidateSchemaKeyspace", func(wr *wrangler.Wrangler, keyspace string, r *http.Request) (string, error) { return "", wr.ValidateSchemaKeyspace(keyspace, false) }) actionRepo.RegisterKeyspaceAction("ValidateVersionKeyspace", func(wr *wrangler.Wrangler, keyspace string, r *http.Request) (string, error) { return "", wr.ValidateVersionKeyspace(keyspace) }) actionRepo.RegisterKeyspaceAction("ValidatePermissionsKeyspace", func(wr *wrangler.Wrangler, keyspace string, r *http.Request) (string, error) { return "", wr.ValidatePermissionsKeyspace(keyspace) }) // shard actions actionRepo.RegisterShardAction("ValidateShard", func(wr *wrangler.Wrangler, keyspace, shard string, r *http.Request) (string, error) { return "", wr.ValidateShard(keyspace, shard, false) }) actionRepo.RegisterShardAction("ValidateSchemaShard", func(wr *wrangler.Wrangler, keyspace, shard string, r *http.Request) (string, error) { return "", wr.ValidateSchemaShard(keyspace, shard, false) }) actionRepo.RegisterShardAction("ValidateVersionShard", func(wr *wrangler.Wrangler, keyspace, shard string, r *http.Request) (string, error) { return "", wr.ValidateVersionShard(keyspace, shard) }) actionRepo.RegisterShardAction("ValidatePermissionsShard", func(wr *wrangler.Wrangler, keyspace, shard string, r *http.Request) (string, error) { return "", wr.ValidatePermissionsShard(keyspace, shard) }) // tablet actions actionRepo.RegisterTabletAction("RpcPing", func(wr *wrangler.Wrangler, tabletAlias topo.TabletAlias, r *http.Request) (string, error) { return "", wr.ActionInitiator().RpcPing(tabletAlias, 10*time.Second) }) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { templateLoader.ServeTemplate("index.html", indexContent, w, r) }) http.HandleFunc("/keyspace_actions", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpError(w, "cannot parse form: %s", err) return } action := r.FormValue("action") if action == "" { http.Error(w, "no action provided", http.StatusBadRequest) return } keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "no keyspace provided", http.StatusBadRequest) return } result := actionRepo.ApplyKeyspaceAction(action, keyspace, r) templateLoader.ServeTemplate("action.html", result, w, r) }) http.HandleFunc("/shard_actions", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpError(w, "cannot parse form: %s", err) return } action := r.FormValue("action") if action == "" { http.Error(w, "no action provided", http.StatusBadRequest) return } keyspace := r.FormValue("keyspace") if keyspace == "" { http.Error(w, "no keyspace provided", http.StatusBadRequest) return } shard := r.FormValue("shard") if shard == "" { http.Error(w, "no shard provided", http.StatusBadRequest) return } result := actionRepo.ApplyShardAction(action, keyspace, shard, r) templateLoader.ServeTemplate("action.html", result, w, r) }) http.HandleFunc("/tablet_actions", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpError(w, "cannot parse form: %s", err) return } action := r.FormValue("action") if action == "" { http.Error(w, "no action provided", http.StatusBadRequest) return } alias := r.FormValue("alias") if alias == "" { http.Error(w, "no alias provided", http.StatusBadRequest) return } tabletAlias, err := topo.ParseTabletAliasString(alias) if err != nil { http.Error(w, "bad alias provided", http.StatusBadRequest) return } result := actionRepo.ApplyTabletAction(action, tabletAlias, r) templateLoader.ServeTemplate("action.html", result, w, r) }) http.HandleFunc("/dbtopo", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpError(w, "cannot parse form: %s", err) return } result := DbTopologyResult{} topology, err := wr.DbTopology() if err != nil { result.Error = err.Error() } else { result.Topology = topology } templateLoader.ServeTemplate("dbtopo.html", result, w, r) }) log.Fatalf("%s", http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)) }
func main() { defer exit.Recover() flags := dbconfigs.AppConfig | dbconfigs.DbaConfig | dbconfigs.FilteredConfig | dbconfigs.ReplConfig dbconfigs.RegisterFlags(flags) mysqlctl.RegisterFlags() flag.Parse() if len(flag.Args()) > 0 { flag.Usage() log.Errorf("vttablet doesn't take any positional arguments") exit.Return(1) } servenv.Init() if *tabletPath == "" { log.Errorf("tabletPath required") exit.Return(1) } tabletAlias, err := topo.ParseTabletAliasString(*tabletPath) if err != nil { log.Error(err) exit.Return(1) } mycnf, err := mysqlctl.NewMycnfFromFlags(tabletAlias.Uid) if err != nil { log.Errorf("mycnf read failed: %v", err) exit.Return(1) } dbcfgs, err := dbconfigs.Init(mycnf.SocketFile, flags) if err != nil { log.Warning(err) } dbcfgs.App.EnableRowcache = *enableRowcache if *tableAclConfig != "" { tableacl.Register("simpleacl", &simpleacl.Factory{}) tableacl.Init(*tableAclConfig) } else if *enforceTableACLConfig { log.Error("table acl config has to be specified with table-acl-config flag because enforce-tableacl-config is set.") exit.Return(1) } // creates and registers the query service qsc := tabletserver.NewQueryServiceControl() tabletserver.InitQueryService(qsc) binlog.RegisterUpdateStreamService(mycnf) // Create mysqld and register the health reporter (needs to be done // before initializing the agent, so the initial health check // done by the agent has the right reporter) mysqld := mysqlctl.NewMysqld("Dba", "App", mycnf, &dbcfgs.Dba, &dbcfgs.App.ConnParams, &dbcfgs.Repl) registerHealthReporter(mysqld) // Depends on both query and updateStream. gRPCPort := int32(0) if servenv.GRPCPort != nil { gRPCPort = int32(*servenv.GRPCPort) } agent, err = tabletmanager.NewActionAgent(context.Background(), mysqld, qsc, tabletAlias, dbcfgs, mycnf, int32(*servenv.Port), gRPCPort, *overridesFile, *lockTimeout) if err != nil { log.Error(err) exit.Return(1) } servenv.OnRun(func() { addStatusParts(qsc) }) servenv.OnTerm(func() { qsc.DisallowQueries() binlog.DisableUpdateStreamService() agent.Stop() }) servenv.OnClose(func() { // We will still use the topo server during lameduck period // to update our state, so closing it in OnClose() topo.CloseServers() }) servenv.RunDefault() }
func main() { flag.Parse() templateLoader := NewTemplateLoader(*templateDir, dummyTemplate, *debug) ts := topo.GetServer() defer topo.CloseServers() wr := wrangler.New(ts, 30*time.Second, 30*time.Second) actionRepo := NewActionRepository(wr) const ( keyspacePath = "/zk/global/vt/keyspaces/*" shardPath = "/zk/global/vt/keyspaces/*/shards/*" tabletPath = "/zk/*/vt/tablets/*" ) actionRepo.Register(keyspacePath, "ValidateKeyspace", func(wr *wrangler.Wrangler, zkPath string, r *http.Request) (string, error) { return "", wr.ValidateKeyspace(path.Base(zkPath), false) }) actionRepo.Register(keyspacePath, "ValidateSchemaKeyspace", func(wr *wrangler.Wrangler, zkPath string, r *http.Request) (string, error) { return "", wr.ValidateSchemaKeyspace(path.Base(zkPath), false) }) actionRepo.Register(keyspacePath, "ValidateVersionKeyspace", func(wr *wrangler.Wrangler, zkPath string, r *http.Request) (string, error) { return "", wr.ValidateVersionKeyspace(path.Base(zkPath)) }) actionRepo.Register(keyspacePath, "ValidatePermissionsKeyspace", func(wr *wrangler.Wrangler, zkPath string, r *http.Request) (string, error) { return "", wr.ValidatePermissionsKeyspace(path.Base(zkPath)) }) actionRepo.Register(shardPath, "ValidateShard", func(wr *wrangler.Wrangler, zkPath string, r *http.Request) (string, error) { zkPathParts := strings.Split(zkPath, "/") return "", wr.ValidateShard(zkPathParts[5], zkPathParts[7], false) }) actionRepo.Register(shardPath, "ValidateSchemaShard", func(wr *wrangler.Wrangler, zkPath string, r *http.Request) (string, error) { zkPathParts := strings.Split(zkPath, "/") return "", wr.ValidateSchemaShard(zkPathParts[5], zkPathParts[7], false) }) actionRepo.Register(shardPath, "ValidateVersionShard", func(wr *wrangler.Wrangler, zkPath string, r *http.Request) (string, error) { zkPathParts := strings.Split(zkPath, "/") return "", wr.ValidateVersionShard(zkPathParts[5], zkPathParts[7]) }) actionRepo.Register(shardPath, "ValidatePermissionsShard", func(wr *wrangler.Wrangler, zkPath string, r *http.Request) (string, error) { zkPathParts := strings.Split(zkPath, "/") return "", wr.ValidatePermissionsShard(zkPathParts[5], zkPathParts[7]) }) actionRepo.Register(tabletPath, "RpcPing", func(wr *wrangler.Wrangler, zkPath string, r *http.Request) (string, error) { zkPathParts := strings.Split(zkPath, "/") alias, err := topo.ParseTabletAliasString(zkPathParts[2] + "-" + zkPathParts[5]) if err != nil { return "", err } return "", wr.ActionInitiator().RpcPing(alias, 10*time.Second) }) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { tmpl, err := templateLoader.Lookup("index.html") if err != nil { httpError(w, "error in template loader: %v", err) return } if err := tmpl.Execute(w, nil); err != nil { httpError(w, "error executing template", err) } }) http.HandleFunc("/actions", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpError(w, "cannot parse form: %s", err) return } action := r.FormValue("action") if action == "" { http.Error(w, "no action provided", http.StatusBadRequest) return } zkPath := r.FormValue("zkpath") if zkPath == "" { http.Error(w, "no zookeeper path provided", http.StatusBadRequest) return } result := actionRepo.Apply(action, zkPath, r) templateLoader.ServeTemplate("action.html", result, w, r) }) http.HandleFunc("/dbtopo", func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { httpError(w, "cannot parse form: %s", err) return } result := DbTopologyResult{} topology, err := wr.DbTopology() if err != nil { result.Error = err.Error() } else { result.Topology = topology } templateLoader.ServeTemplate("dbtopo.html", result, w, r) }) http.HandleFunc("/zk/", func(w http.ResponseWriter, r *http.Request) { zkTopoServ := topo.GetServerByName("zookeeper") if zkTopoServ == nil { http.Error(w, "can only look at zk with zktopo.Server", http.StatusInternalServerError) return } zconn := zkTopoServ.(*zktopo.Server).GetZConn() if err := r.ParseForm(); err != nil { httpError(w, "cannot parse form: %s", err) return } zkPath := r.URL.Path[strings.Index(r.URL.Path, "/zk"):] if cleanPath := path.Clean(zkPath); zkPath != cleanPath && zkPath != cleanPath+"/" { relog.Info("redirecting to %v", cleanPath) http.Redirect(w, r, cleanPath, http.StatusTemporaryRedirect) return } if strings.HasSuffix(zkPath, "/") { zkPath = zkPath[:len(zkPath)-1] } result := NewZkResult(zkPath) if zkPath == "/zk" { cells, err := zk.ResolveWildcards(zconn, []string{"/zk/*"}) if err != nil { httpError(w, "zk error: %v", err) return } for i, cell := range cells { cells[i] = cell[4:] // cut off "/zk/" } result.Children = cells } else { if data, _, err := zconn.Get(zkPath); err != nil { result.Error = err.Error() } else { actionRepo.PopulateAvailableActions(result) result.Data = data if children, _, err := zconn.Children(zkPath); err != nil { result.Error = err.Error() } else { result.Children = children } } } templateLoader.ServeTemplate("zk.html", result, w, r) }) relog.Fatal("%s", http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)) }