func (agent *ActionAgent) initHealthCheck() { if !agent.IsRunningHealthCheck() { log.Infof("No target_tablet_type specified, disabling any health check") return } tt, err := topo.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() }
// 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") } }
// 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 = topo.ParseTabletType(c.TabletType) if err != nil { return nil, err } err = c.dial() if err != nil { return nil, err } return c, nil }
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 := 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() 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 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) }
// GetSrvTabletTypesPerShard is part of the topo.Server interface func (zkts *Server) GetSrvTabletTypesPerShard(ctx context.Context, cell, keyspace, shard string) ([]pb.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([]pb.TabletType, 0, len(children)) for _, tt := range children { // these two are used for locking if tt == "action" || tt == "actionlog" { continue } if ptt, err := topo.ParseTabletType(tt); err == nil { result = append(result, ptt) } } return result, nil }
// GetSrvTabletTypesPerShard implements topo.Server. func (s *Server) GetSrvTabletTypesPerShard(ctx context.Context, cellName, keyspace, shard string) ([]pb.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([]pb.TabletType, 0, len(resp.Node.Nodes)) for _, n := range resp.Node.Nodes { strType := path.Base(n.Key) if tt, err := topo.ParseTabletType(strType); err == nil { tabletTypes = append(tabletTypes, tt) } } return tabletTypes, 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 := topo.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 }
// 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 pb.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 = topo.ParseTabletType(*initTabletType) if err != nil { log.Fatalf("Invalid init tablet type %v: %v", *initTabletType, err) } if tabletType == pb.TabletType_MASTER || tabletType == pb.TabletType_SCRAP { // We disallow TYPE_MASTER, so we don't have to change // shard.MasterAlias, and deal with the corner cases. // We also disallow TYPE_SCRAP, obviously. log.Fatalf("init_tablet_type cannot be %v", tabletType) } } else if *targetTabletType != "" { if strings.ToUpper(*targetTabletType) == pb.TabletType_name[int32(pb.TabletType_MASTER)] { log.Fatalf("target_tablet_type cannot be '%v'. Use '%v' instead.", tabletType, pb.TabletType_REPLICA) } // use spare, the healthcheck will turn us into what // we need to be eventually tabletType = pb.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() // if we're assigned to a shard, make sure it exists, see if // we are its master, and update its cells list if necessary if tabletType != pb.TabletType_IDLE { 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 && topo.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 = pb.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 := topo.UpdateShard(ctx, agent.TopoServer, 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 == "" { var err error hostname, err = netutil.FullyQualifiedHostname() if err != nil { return err } } // create and populate tablet record tablet := &pb.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 := topo.CreateTablet(ctx, agent.TopoServer, tablet) switch err { case nil: // it worked, we're good, can update the replication graph if topo.IsInReplicationGraph(tablet.Type) { 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 { 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 := topo.UpdateTablet(ctx, agent.TopoServer, 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 tabletType != pb.TabletType_IDLE { if err := topotools.UpdateTabletEndpoints(ctx, agent.TopoServer, tablet); err != nil { return fmt.Errorf("UpdateTabletEndpoints failed: %v", err) } } return nil }