func (agent *ActionAgent) initHealthCheck() { if !agent.IsRunningHealthCheck() { log.Infof("No target_tablet_type specified, disabling any health check") return } tt, err := topoproto.ParseTabletType(*targetTabletType) if err != nil { log.Fatalf("Invalid target tablet type %v: %v", *targetTabletType, err) } log.Infof("Starting periodic health check every %v with target_tablet_type=%v", *healthCheckInterval, *targetTabletType) t := timer.NewTimer(*healthCheckInterval) servenv.OnTermSync(func() { // When we enter lameduck mode, we want to not call // the health check any more. After this returns, we // are guaranteed to not call it. log.Info("Stopping periodic health check timer") t.Stop() // Now we can finish up and force ourselves to not healthy. agent.terminateHealthChecks(tt) }) t.Start(func() { agent.runHealthCheck(tt) }) t.Trigger() }
func main() { defer exit.Recover() flag.Parse() servenv.Init() ts := topo.GetServer() defer topo.CloseServers() resilientSrvTopoServer = vtgate.NewResilientSrvTopoServer(ts, "ResilientSrvTopoServer") healthCheck = discovery.NewHealthCheck(*healthCheckConnTimeout, *healthCheckRetryDelay, *healthCheckTimeout) healthCheck.RegisterStats() tabletTypes := make([]topodatapb.TabletType, 0, 1) if len(*tabletTypesToWait) != 0 { for _, ttStr := range strings.Split(*tabletTypesToWait, ",") { tt, err := topoproto.ParseTabletType(ttStr) if err != nil { log.Errorf("unknown tablet type: %v", ttStr) continue } tabletTypes = append(tabletTypes, tt) } } l2vtg := l2vtgate.Init(healthCheck, ts, resilientSrvTopoServer, *cell, *retryCount, tabletTypes) servenv.OnRun(func() { addStatusParts(l2vtg) }) servenv.RunDefault() }
// TabletTypeToProto turns a TabletType into a proto func TabletTypeToProto(t TabletType) pb.TabletType { if result, err := topoproto.ParseTabletType(string(t)); err != nil { panic(fmt.Errorf("unknown tablet type: %v", t)) } else { return result } }
func main() { defer exit.Recover() flag.Parse() servenv.Init() if initFakeZK != nil { initFakeZK() } ts := topo.GetServer() defer topo.CloseServers() resilientSrvTopoServer = vtgate.NewResilientSrvTopoServer(ts, "ResilientSrvTopoServer") healthCheck = discovery.NewHealthCheck(*connTimeoutTotal, *healthCheckRetryDelay, *healthCheckTimeout, "" /* statsSuffix */) tabletTypes := make([]topodatapb.TabletType, 0, 1) if len(*tabletTypesToWait) != 0 { for _, ttStr := range strings.Split(*tabletTypesToWait, ",") { tt, err := topoproto.ParseTabletType(ttStr) if err != nil { log.Errorf("unknown tablet type: %v", ttStr) continue } tabletTypes = append(tabletTypes, tt) } } vtg := vtgate.Init(context.Background(), healthCheck, ts, resilientSrvTopoServer, *cell, *retryDelay, *retryCount, *connTimeoutTotal, *connTimeoutPerConn, *connLife, tabletTypes, *maxInFlight, *testGateway) servenv.OnRun(func() { addStatusParts(vtg) }) servenv.RunDefault() }
// tabletTypesLocked returns the tablet types needed to be displayed in the heatmap based on the dropdown filters. // It returns tablet type if a specific one was chosen or returns all of them if 'all' is chosen for keyspace and/or cell. // This method is used by heatmapData to traverse over the desired tablet types. func (c *tabletStatsCache) tabletTypesLocked(keyspace, cell, tabletType string) []topodata.TabletType { if tabletType != "all" { tabletTypeObj, _ := topoproto.ParseTabletType(tabletType) return []topodata.TabletType{tabletTypeObj} } return c.typesInTopology(keyspace, cell) }
func main() { defer exit.Recover() flag.Parse() servenv.Init() if initFakeZK != nil { initFakeZK() } ts := topo.GetServer() defer topo.CloseServers() var schema *planbuilder.Schema if *schemaFile != "" { var err error if schema, err = planbuilder.LoadFile(*schemaFile); err != nil { log.Error(err) exit.Return(1) } log.Infof("v3 is enabled: loaded schema from file: %v", *schemaFile) } else { ctx := context.Background() schemaJSON, err := ts.GetVSchema(ctx) if err != nil { log.Warningf("Skipping v3 initialization: GetVSchema failed: %v", err) goto startServer } schema, err = planbuilder.NewSchema([]byte(schemaJSON)) if err != nil { log.Warningf("Skipping v3 initialization: GetVSchema failed: %v", err) goto startServer } log.Infof("v3 is enabled: loaded schema from topo") } startServer: resilientSrvTopoServer = vtgate.NewResilientSrvTopoServer(ts, "ResilientSrvTopoServer") healthCheck = discovery.NewHealthCheck(*connTimeoutTotal, *healthCheckRetryDelay, *healthCheckTimeout, "" /* statsSuffix */) tabletTypes := make([]topodatapb.TabletType, 0, 1) if len(*tabletTypesToWait) != 0 { for _, ttStr := range strings.Split(*tabletTypesToWait, ",") { tt, err := topoproto.ParseTabletType(ttStr) if err != nil { log.Errorf("unknown tablet type: %v", ttStr) continue } tabletTypes = append(tabletTypes, tt) } } vtg := vtgate.Init(healthCheck, ts, resilientSrvTopoServer, schema, *cell, *retryDelay, *retryCount, *connTimeoutTotal, *connTimeoutPerConn, *connLife, tabletTypes, *maxInFlight, *testGateway) servenv.OnRun(func() { addStatusParts(vtg) }) servenv.RunDefault() }
// 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 := topoproto.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 := topoproto.ParseTabletAlias(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") } }
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") json := subFlags.Bool("json", false, "Output JSON instead of human-readable table") if err := subFlags.Parse(args); err != nil { return err } if subFlags.NArg() != 2 { return fmt.Errorf("the <tablet_alias> and <sql> arguments are required for the VtTabletExecute command") } tt, err := topoproto.ParseTabletType(*tabletType) if err != nil { return err } tabletAlias, err := topoproto.ParseTabletAlias(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() qr, err := conn.Execute(ctx, subFlags.Arg(1), *bindVariables, int64(*transactionID)) if err != nil { return fmt.Errorf("Execute failed: %v", err) } if *json { return printJSON(wr.Logger(), qr) } printQueryResult(loggerWriter{wr.Logger()}, qr) return nil }
// Open must be called with a JSON string that looks like this: // {"protocol": "gorpc", "address": "localhost:1111", "tablet_type": "master", "timeout": 1000000000} // protocol specifies the rpc protocol to use. // address specifies the address for the VTGate to connect to. // tablet_type represents the consistency level of your operations. // For example "replica" means eventually consistent reads, while // "master" supports transactions and gives you read-after-write consistency. // timeout is specified in nanoseconds. It applies for all operations. func (d drv) Open(name string) (driver.Conn, error) { c := &conn{TabletType: "master"} err := json.Unmarshal([]byte(name), c) if err != nil { return nil, err } c.tabletType, err = topoproto.ParseTabletType(c.TabletType) if err != nil { return nil, err } err = c.dial() if err != nil { return nil, err } return c, nil }
func (agent *ActionAgent) isMasterEligible() (bool, error) { switch agent.Tablet().Type { case topodatapb.TabletType_MASTER, topodatapb.TabletType_REPLICA: return true, nil case topodatapb.TabletType_SPARE: // If we're SPARE, it could be because healthcheck is enabled. tt, err := topoproto.ParseTabletType(*targetTabletType) if err != nil { return false, fmt.Errorf("can't determine if tablet is master-eligible: currently SPARE and no -target_tablet_type flag specified") } if tt == topodatapb.TabletType_REPLICA { return true, nil } } return false, nil }
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 := topoproto.ParseTabletType(*tabletType) if err != nil { return err } tabletAlias, err := topoproto.ParseTabletAlias(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) }
// GetSrvTabletTypesPerShard is part of the topo.Server interface func (zkts *Server) GetSrvTabletTypesPerShard(ctx context.Context, cell, keyspace, shard string) ([]topodatapb.TabletType, error) { zkSgShardPath := zkPathForVtShard(cell, keyspace, shard) children, _, err := zkts.zconn.Children(zkSgShardPath) if err != nil { if zookeeper.IsError(err, zookeeper.ZNONODE) { err = topo.ErrNoNode } return nil, err } result := make([]topodatapb.TabletType, 0, len(children)) for _, tt := range children { // these two are used for locking if tt == "action" || tt == "actionlog" { continue } if ptt, err := topoproto.ParseTabletType(tt); err == nil { result = append(result, ptt) } } return result, nil }
// Open implements the database/sql/driver.Driver interface. // // For "name", the Vitess driver requires that a JSON object is passed in. // // Instead of using this call and passing in a hand-crafted JSON string, it's // recommended to use the public Vitess helper functions like // Open(), OpenShard() or OpenWithConfiguration() instead. These will generate // the required JSON string behind the scenes for you. // // Example for a JSON string: // // {"protocol": "grpc", "address": "localhost:1111", "tablet_type": "master", "timeout": 1000000000} // // For a description of the available fields, see the Configuration struct. // Note: In the JSON string, timeout has to be specified in nanoseconds. func (d drv) Open(name string) (driver.Conn, error) { c := &conn{Configuration: newDefaultConfiguration()} err := json.Unmarshal([]byte(name), c) if err != nil { return nil, err } if c.Keyspace == "" && c.Shard != "" { return nil, fmt.Errorf("the shard parameter requires a keyspace parameter. shard: %v", c.Shard) } if c.useExecuteShards() { log.Infof("Sending queries only to keyspace/shard: %v/%v", c.Keyspace, c.Shard) } c.tabletTypeProto, err = topoproto.ParseTabletType(c.TabletType) if err != nil { return nil, err } err = c.dial() if err != nil { return nil, err } return c, nil }
// Open implements the database/sql/driver.Driver interface. // // For "name", the Vitess driver requires that a JSON object is passed in. // // Instead of using this call and passing in a hand-crafted JSON string, it's // recommended to use the public Vitess helper functions like // Open(), OpenShard() or OpenWithConfiguration() instead. These will generate // the required JSON string behind the scenes for you. // // Example for a JSON string: // // {"protocol": "gorpc", "address": "localhost:1111", "tablet_type": "master", "timeout": 1000000000} // // For a description of the available fields, see the Configuration struct. // Note: In the JSON string, timeout has to be specified in nanoseconds. func (d drv) Open(name string) (driver.Conn, error) { c := &conn{Configuration: newDefaultConfiguration()} err := json.Unmarshal([]byte(name), c) if err != nil { return nil, err } if (c.Keyspace != "" && c.Shard == "") || (c.Keyspace == "" && c.Shard != "") { return nil, fmt.Errorf("Always set both keyspace and shard or leave both empty. keyspace: %v shard: %v", c.Keyspace, c.Shard) } if c.useExecuteShards() { log.Infof("Sending queries only to keyspace/shard: %v/%v", c.Keyspace, c.Shard) } c.tabletTypeProto, err = topoproto.ParseTabletType(c.TabletType) if err != nil { return nil, err } err = c.dial() if err != nil { return nil, err } return c, nil }
// GetSrvTabletTypesPerShard implements topo.Server. func (s *Server) GetSrvTabletTypesPerShard(ctx context.Context, cellName, keyspace, shard string) ([]topodatapb.TabletType, error) { cell, err := s.getCell(cellName) if err != nil { return nil, err } resp, err := cell.Get(srvShardDirPath(keyspace, shard), false /* sort */, false /* recursive */) if err != nil { return nil, convertError(err) } if resp.Node == nil { return nil, ErrBadResponse } tabletTypes := make([]topodatapb.TabletType, 0, len(resp.Node.Nodes)) for _, n := range resp.Node.Nodes { strType := path.Base(n.Key) if tt, err := topoproto.ParseTabletType(strType); err == nil { tabletTypes = append(tabletTypes, tt) } } return tabletTypes, nil }
// InitTablet initializes the tablet record if necessary. func (agent *ActionAgent) InitTablet(port, gRPCPort int32) error { // only enabled if one of init_tablet_type (when healthcheck // is disabled) or init_keyspace (when healthcheck is enabled) // is passed in, then check other parameters if *initTabletType == "" && *initKeyspace == "" { return nil } // figure out our default target type var tabletType topodatapb.TabletType if *initTabletType != "" { if *targetTabletType != "" { log.Fatalf("cannot specify both target_tablet_type and init_tablet_type parameters (as they might conflict)") } // use the type specified on the command line var err error tabletType, err = topoproto.ParseTabletType(*initTabletType) if err != nil { log.Fatalf("Invalid init tablet type %v: %v", *initTabletType, err) } if tabletType == topodatapb.TabletType_MASTER { // We disallow MASTER, so we don't have to change // shard.MasterAlias, and deal with the corner cases. log.Fatalf("init_tablet_type cannot be %v", tabletType) } } else if *targetTabletType != "" { if strings.ToUpper(*targetTabletType) == topodatapb.TabletType_name[int32(topodatapb.TabletType_MASTER)] { log.Fatalf("target_tablet_type cannot be '%v'. Use '%v' instead.", tabletType, topodatapb.TabletType_REPLICA) } // use spare, the healthcheck will turn us into what // we need to be eventually tabletType = topodatapb.TabletType_SPARE } else { log.Fatalf("if init tablet is enabled, one of init_tablet_type or target_tablet_type needs to be specified") } // create a context for this whole operation ctx, cancel := context.WithTimeout(agent.batchCtx, *initTimeout) defer cancel() // since we're assigned to a shard, make sure it exists, see if // we are its master, and update its cells list if necessary if *initKeyspace == "" || *initShard == "" { log.Fatalf("if init tablet is enabled and the target type is not idle, init_keyspace and init_shard also need to be specified") } shard, _, err := topo.ValidateShardName(*initShard) if err != nil { log.Fatalf("cannot validate shard name: %v", err) } log.Infof("Reading shard record %v/%v", *initKeyspace, shard) // read the shard, create it if necessary si, err := topotools.GetOrCreateShard(ctx, agent.TopoServer, *initKeyspace, shard) if err != nil { return fmt.Errorf("InitTablet cannot GetOrCreateShard shard: %v", err) } if si.MasterAlias != nil && topoproto.TabletAliasEqual(si.MasterAlias, agent.TabletAlias) { // we are the current master for this shard (probably // means the master tablet process was just restarted), // so InitTablet as master. tabletType = topodatapb.TabletType_MASTER } // See if we need to add the tablet's cell to the shard's cell // list. If we do, it has to be under the shard lock. if !si.HasCell(agent.TabletAlias.Cell) { actionNode := actionnode.UpdateShard() lockPath, err := actionNode.LockShard(ctx, agent.TopoServer, *initKeyspace, shard) if err != nil { return fmt.Errorf("LockShard(%v/%v) failed: %v", *initKeyspace, shard, err) } // re-read the shard with the lock si, err = agent.TopoServer.GetShard(ctx, *initKeyspace, shard) if err != nil { return actionNode.UnlockShard(ctx, agent.TopoServer, *initKeyspace, shard, lockPath, err) } // see if we really need to update it now if !si.HasCell(agent.TabletAlias.Cell) { si.Cells = append(si.Cells, agent.TabletAlias.Cell) // write it back if err := agent.TopoServer.UpdateShard(ctx, si); err != nil { return actionNode.UnlockShard(ctx, agent.TopoServer, *initKeyspace, shard, lockPath, err) } } // and unlock if err := actionNode.UnlockShard(ctx, agent.TopoServer, *initKeyspace, shard, lockPath, nil); err != nil { return err } } log.Infof("Initializing the tablet for type %v", tabletType) // figure out the hostname hostname := *tabletHostname if hostname != "" { log.Infof("Using hostname: %v from -tablet_hostname flag.", hostname) } else { hostname, err := netutil.FullyQualifiedHostname() if err != nil { return err } log.Infof("Using detected machine hostname: %v To change this, fix your machine network configuration or override it with -tablet_hostname.", hostname) } // create and populate tablet record tablet := &topodatapb.Tablet{ Alias: agent.TabletAlias, Hostname: hostname, PortMap: make(map[string]int32), Keyspace: *initKeyspace, Shard: *initShard, Type: tabletType, DbNameOverride: *initDbNameOverride, Tags: initTags, } if port != 0 { tablet.PortMap["vt"] = port } if gRPCPort != 0 { tablet.PortMap["grpc"] = gRPCPort } if err := topo.TabletComplete(tablet); err != nil { return fmt.Errorf("InitTablet TabletComplete failed: %v", err) } // now try to create the record err = agent.TopoServer.CreateTablet(ctx, tablet) switch err { case nil: // it worked, we're good, can update the replication graph if err := topo.UpdateTabletReplicationData(ctx, agent.TopoServer, tablet); err != nil { return fmt.Errorf("UpdateTabletReplicationData failed: %v", err) } case topo.ErrNodeExists: // The node already exists, will just try to update // it. So we read it first. oldTablet, err := agent.TopoServer.GetTablet(ctx, tablet.Alias) if err != nil { return fmt.Errorf("InitTablet failed to read existing tablet record: %v", err) } // Sanity check the keyspace and shard if oldTablet.Keyspace != tablet.Keyspace || oldTablet.Shard != tablet.Shard { return fmt.Errorf("InitTablet failed because existing tablet keyspace and shard %v/%v differ from the provided ones %v/%v", oldTablet.Keyspace, oldTablet.Shard, tablet.Keyspace, tablet.Shard) } // And overwrite the rest *(oldTablet.Tablet) = *tablet if err := agent.TopoServer.UpdateTablet(ctx, oldTablet); err != nil { return fmt.Errorf("UpdateTablet failed: %v", err) } // Note we don't need to UpdateTabletReplicationData // as the tablet already existed with the right data // in the replication graph default: return fmt.Errorf("CreateTablet failed: %v", err) } // and now update the serving graph. Note we do that in any case, // to clean any inaccurate record from any part of the serving graph. if err := topotools.UpdateTabletEndpoints(ctx, agent.TopoServer, tablet); err != nil { return fmt.Errorf("UpdateTabletEndpoints failed: %v", err) } return nil }
// InitTablet initializes the tablet record if necessary. func (agent *ActionAgent) InitTablet(port, gRPCPort int32) error { // it should be either we have all three of init_keyspace, // init_shard and init_tablet_type, or none. if *initKeyspace == "" && *initShard == "" && *initTabletType == "" { // not initializing the record return nil } if *initKeyspace == "" || *initShard == "" || *initTabletType == "" { return fmt.Errorf("either need all of init_keyspace, init_shard and init_tablet_type, or none") } // parse init_tablet_type tabletType, err := topoproto.ParseTabletType(*initTabletType) if err != nil { return fmt.Errorf("invalid init_tablet_type %v: %v", *initTabletType, err) } if tabletType == topodatapb.TabletType_MASTER { // We disallow MASTER, so we don't have to change // shard.MasterAlias, and deal with the corner cases. return fmt.Errorf("init_tablet_type cannot be master, use replica instead") } // parse and validate shard name shard, _, err := topo.ValidateShardName(*initShard) if err != nil { return fmt.Errorf("cannot validate shard name %v: %v", *initShard, err) } // create a context for this whole operation ctx, cancel := context.WithTimeout(agent.batchCtx, *initTimeout) defer cancel() // read the shard, create it if necessary log.Infof("Reading shard record %v/%v", *initKeyspace, shard) si, err := agent.TopoServer.GetOrCreateShard(ctx, *initKeyspace, shard) if err != nil { return fmt.Errorf("InitTablet cannot GetOrCreateShard shard: %v", err) } if si.MasterAlias != nil && topoproto.TabletAliasEqual(si.MasterAlias, agent.TabletAlias) { // We're marked as master in the shard record, which could mean the master // tablet process was just restarted. However, we need to check if a new // master is in the process of taking over. In that case, it will let us // know by forcibly updating the old master's tablet record. oldTablet, err := agent.TopoServer.GetTablet(ctx, agent.TabletAlias) switch err { case topo.ErrNoNode: // There's no existing tablet record, so we can assume // no one has left us a message to step down. tabletType = topodatapb.TabletType_MASTER case nil: if oldTablet.Type == topodatapb.TabletType_MASTER { // We're marked as master in the shard record, // and our existing tablet record agrees. tabletType = topodatapb.TabletType_MASTER } default: return fmt.Errorf("InitTablet failed to read existing tablet record: %v", err) } } // See if we need to add the tablet's cell to the shard's cell list. if !si.HasCell(agent.TabletAlias.Cell) { si, err = agent.TopoServer.UpdateShardFields(ctx, *initKeyspace, shard, func(si *topo.ShardInfo) error { if si.HasCell(agent.TabletAlias.Cell) { // Someone else already did it. return topo.ErrNoUpdateNeeded } si.Cells = append(si.Cells, agent.TabletAlias.Cell) return nil }) if err != nil { return fmt.Errorf("couldn't add tablet's cell to shard record: %v", err) } } log.Infof("Initializing the tablet for type %v", tabletType) // figure out the hostname hostname := *tabletHostname if hostname != "" { log.Infof("Using hostname: %v from -tablet_hostname flag.", hostname) } else { hostname, err := netutil.FullyQualifiedHostname() if err != nil { return err } log.Infof("Using detected machine hostname: %v To change this, fix your machine network configuration or override it with -tablet_hostname.", hostname) } // create and populate tablet record tablet := &topodatapb.Tablet{ Alias: agent.TabletAlias, Hostname: hostname, PortMap: make(map[string]int32), Keyspace: *initKeyspace, Shard: *initShard, Type: tabletType, DbNameOverride: *initDbNameOverride, Tags: initTags, } if port != 0 { tablet.PortMap["vt"] = port } if gRPCPort != 0 { tablet.PortMap["grpc"] = gRPCPort } if err := topo.TabletComplete(tablet); err != nil { return fmt.Errorf("InitTablet TabletComplete failed: %v", err) } // Now try to create the record (it will also fix up the // ShardReplication record if necessary). err = agent.TopoServer.CreateTablet(ctx, tablet) switch err { case nil: // It worked, we're good. case topo.ErrNodeExists: // The node already exists, will just try to update // it. So we read it first. oldTablet, err := agent.TopoServer.GetTablet(ctx, tablet.Alias) if err != nil { return fmt.Errorf("InitTablet failed to read existing tablet record: %v", err) } // Sanity check the keyspace and shard if oldTablet.Keyspace != tablet.Keyspace || oldTablet.Shard != tablet.Shard { return fmt.Errorf("InitTablet failed because existing tablet keyspace and shard %v/%v differ from the provided ones %v/%v", oldTablet.Keyspace, oldTablet.Shard, tablet.Keyspace, tablet.Shard) } // Then overwrite everything, ignoring version mismatch. if err := agent.TopoServer.UpdateTablet(ctx, topo.NewTabletInfo(tablet, -1)); err != nil { return fmt.Errorf("UpdateTablet failed: %v", err) } default: return fmt.Errorf("CreateTablet failed: %v", err) } return nil }
func (wr *Wrangler) exportVtnsToZkns(ctx context.Context, zconn zk.Conn, vtnsAddrPath, zknsAddrPath string) ([]string, error) { zknsPaths := make([]string, 0, 32) parts := strings.Split(vtnsAddrPath, "/") if len(parts) != 8 && len(parts) != 9 { return nil, fmt.Errorf("Invalid leaf zk path: %v", vtnsAddrPath) } cell := parts[2] keyspace := parts[5] shard := parts[6] tabletTypeStr := parts[7] if tabletTypeStr == "action" || tabletTypeStr == "actionlog" { return nil, nil } tabletType, err := topoproto.ParseTabletType(tabletTypeStr) if err != nil { return nil, err } addrs, _, err := wr.ts.GetEndPoints(ctx, cell, keyspace, shard, tabletType) if err != nil { return nil, err } // Write the individual endpoints and compute the SRV entries. vtoccAddrs := LegacyZknsAddrs{make([]string, 0, 8)} defaultAddrs := LegacyZknsAddrs{make([]string, 0, 8)} for i, entry := range addrs.Entries { zknsAddrPath := fmt.Sprintf("%v/%v", zknsAddrPath, i) zknsPaths = append(zknsPaths, zknsAddrPath) zknsAddr := zkns.ZknsAddr{ Host: entry.Host, PortMap: entry.PortMap, } err := writeAddr(zconn, zknsAddrPath, &zknsAddr) if err != nil { return nil, err } defaultAddrs.Endpoints = append(defaultAddrs.Endpoints, zknsAddrPath) vtoccAddrs.Endpoints = append(vtoccAddrs.Endpoints, zknsAddrPath+":vt") } // Prune any zkns entries that are no longer referenced by the // shard graph. deleteIdx := len(addrs.Entries) for { zknsStaleAddrPath := fmt.Sprintf("%v/%v", zknsAddrPath, deleteIdx) // A "delete" is a write of sorts - just communicate up that nothing // needs to be done to this node. zknsPaths = append(zknsPaths, zknsStaleAddrPath) err := zconn.Delete(zknsStaleAddrPath, -1) if zookeeper.IsError(err, zookeeper.ZNONODE) { break } if err != nil { return nil, err } deleteIdx++ } // Write the VDNS entries for both vtocc and mysql vtoccVdnsPath := fmt.Sprintf("%v/vt.vdns", zknsAddrPath) zknsPaths = append(zknsPaths, vtoccVdnsPath) if err = writeAddrs(zconn, vtoccVdnsPath, &vtoccAddrs); err != nil { return nil, err } defaultVdnsPath := fmt.Sprintf("%v.vdns", zknsAddrPath) zknsPaths = append(zknsPaths, defaultVdnsPath) if err = writeAddrs(zconn, defaultVdnsPath, &defaultAddrs); err != nil { return nil, err } return zknsPaths, nil }
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) // 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 := 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) }) // 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]) } tabletType, err := topoproto.ParseTabletType(parts[3]) if err != nil { return nil, fmt.Errorf("invalid tablet type %v: %v", parts[3], err) } // Get the endpoints object for a specific type. ep, _, err := ts.GetEndPoints(ctx, parts[0], parts[1], parts[2], tabletType) 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) { // 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 := ts.SaveVSchema(ctx, string(vschema)); err != nil { httpErrorf(w, r, "can't save vschema: %v", err) } return } // Get VSchema vschema, err := ts.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)) }) }