// createSourceTablet is a helper method to create the source tablet // in the given keyspace/shard. func createSourceTablet(t *testing.T, name string, ts topo.Server, keyspace, shard string) { vshard, kr, err := topo.ValidateShardName(shard) if err != nil { t.Fatalf("ValidateShardName(%v) failed: %v", shard, err) } ctx := context.Background() tablet := &topodatapb.Tablet{ Alias: &topodatapb.TabletAlias{ Cell: "cell1", Uid: 100, }, Keyspace: keyspace, Shard: vshard, Type: topodatapb.TabletType_REPLICA, KeyRange: kr, PortMap: map[string]int32{ "vt": 80, }, } if err := ts.CreateTablet(ctx, tablet); err != nil { t.Fatalf("CreateTablet failed: %v", err) } // register a tablet conn dialer that will return the instance // we want tabletconn.RegisterDialer(name, func(tablet *topodatapb.Tablet, timeout time.Duration) (tabletconn.TabletConn, error) { return &fakeTabletConn{ tablet: tablet, }, nil }) flag.Set("tablet_protocol", name) }
func newKeyRange(value string) key.KeyRange { _, result, err := topo.ValidateShardName(value) if err != nil { panic(err) } return result }
func createSourceTablet(t *testing.T, ts topo.Server, keyspace, shard string) { vshard, kr, err := topo.ValidateShardName(shard) if err != nil { t.Fatalf("ValidateShardName(%v) failed: %v", shard, err) } ctx := context.Background() tablet := &pb.Tablet{ Alias: &pb.TabletAlias{ Cell: "cell1", Uid: 100, }, Type: pb.TabletType_REPLICA, KeyRange: kr, Keyspace: keyspace, Shard: vshard, PortMap: map[string]int32{ "vt": 80, }, } if err := ts.CreateTablet(ctx, tablet); err != nil { t.Fatalf("CreateTablet failed: %v", err) } if err := topotools.UpdateTabletEndpoints(ctx, ts, tablet); err != nil { t.Fatalf("topotools.UpdateTabletEndpoints failed: %v", err) } }
// addL2VTGateConn adds a backend l2vtgate for the provided keyspace / shard. func (lg *l2VTGateGateway) addL2VTGateConn(addr, keyspace, shard string) error { lg.mu.Lock() defer lg.mu.Unlock() // extract keyrange if it's a range canonical, kr, err := topo.ValidateShardName(shard) if err != nil { return fmt.Errorf("error parsing shard name %v: %v", shard, err) } // check for duplicates for _, c := range lg.connMap[keyspace] { if c.shard == canonical { return fmt.Errorf("duplicate %v/%v entry", keyspace, shard) } } // Dial in the background conn, err := tabletconn.GetDialer()(&topodatapb.Tablet{ Hostname: addr, }, 0) if err != nil { return err } lg.connMap[keyspace] = append(lg.connMap[keyspace], &l2VTGateConn{ addr: addr, keyspace: keyspace, shard: canonical, keyRange: kr, conn: conn, }) return nil }
// TabletKeyspaceShard is the option to set the tablet keyspace and shard func TabletKeyspaceShard(t *testing.T, keyspace, shard string) TabletOption { return func(tablet *topo.Tablet) { tablet.Keyspace = keyspace var err error tablet.Shard, tablet.KeyRange, err = topo.ValidateShardName(shard) if err != nil { t.Fatalf("cannot ValidateShardName value %v", shard) } } }
// TabletKeyspaceShard is the option to set the tablet keyspace and shard func TabletKeyspaceShard(t *testing.T, keyspace, shard string) TabletOption { return func(tablet *topodatapb.Tablet) { tablet.Keyspace = keyspace shard, kr, err := topo.ValidateShardName(shard) if err != nil { t.Fatalf("cannot ValidateShardName value %v", shard) } tablet.Shard = shard tablet.KeyRange = kr } }
// isIncluded returns true iff the tablet's keyspace and shard should be // forwarded to the underlying TabletRecorder. func (fbs *FilterByShard) isIncluded(tablet *topodatapb.Tablet) bool { canonical, kr, err := topo.ValidateShardName(tablet.Shard) if err != nil { log.Errorf("Error parsing shard name %v, will ignore tablet: %v", tablet.Shard, err) return false } for _, c := range fbs.filters[tablet.Keyspace] { if canonical == c.shard { // Exact match (probably a non-sharded keyspace). return true } if kr != nil && c.keyRange != nil && key.KeyRangeIncludes(c.keyRange, kr) { // Our filter's KeyRange includes the provided KeyRange return true } } return false }
// createSourceTablet is a helper method to create the source tablet // in the given keyspace/shard. func createSourceTablet(t *testing.T, name string, ts topo.Server, keyspace, shard string) { vshard, kr, err := topo.ValidateShardName(shard) if err != nil { t.Fatalf("ValidateShardName(%v) failed: %v", shard, err) } ctx := context.Background() tablet := &pbt.Tablet{ Alias: &pbt.TabletAlias{ Cell: "cell1", Uid: 100, }, Type: pbt.TabletType_REPLICA, KeyRange: kr, Keyspace: keyspace, Shard: vshard, PortMap: map[string]int32{ "vt": 80, }, } if err := ts.CreateTablet(ctx, tablet); err != nil { t.Fatalf("CreateTablet failed: %v", err) } if err := topotools.UpdateTabletEndpoints(ctx, ts, tablet); err != nil { t.Fatalf("topotools.UpdateTabletEndpoints failed: %v", err) } // register a tablet conn dialer that will return the instance // we want tabletconn.RegisterDialer(name, func(ctx context.Context, endPoint *pbt.EndPoint, k, s string, tabletType pbt.TabletType, timeout time.Duration) (tabletconn.TabletConn, error) { return &fakeTabletConn{ endPoint: endPoint, keyspace: keyspace, shard: vshard, tabletType: pbt.TabletType_REPLICA, }, nil }) flag.Lookup("tablet_protocol").Value.Set(name) }
// getConn returns the right l2VTGateConn for a given keyspace / shard. func (lg *l2VTGateGateway) getConn(keyspace, shard string) (*l2VTGateConn, error) { lg.mu.RLock() defer lg.mu.RUnlock() canonical, kr, err := topo.ValidateShardName(shard) if err != nil { return nil, fmt.Errorf("invalid shard name: %v", shard) } for _, c := range lg.connMap[keyspace] { if canonical == c.shard { // Exact match (probably a non-sharded keyspace). return c, nil } if kr != nil && c.keyRange != nil && key.KeyRangeIncludes(c.keyRange, kr) { // The shard KeyRange is included in this destination's // KeyRange, that's the destination we want. return c, nil } } return nil, fmt.Errorf("no configured destination for %v/%v", keyspace, shard) }
// getConn returns the right l2VTGateConn for a given keyspace / shard. func (lg *l2VTGateGateway) getConn(keyspace, shard string) (*l2VTGateConn, error) { lg.mu.RLock() defer lg.mu.RUnlock() canonical, kr, err := topo.ValidateShardName(shard) if err != nil { return nil, fmt.Errorf("invalid shard name: %v", shard) } for _, c := range lg.connMap[keyspace] { if canonical == c.shard { // Exact match (probably a non-sharded keyspace). return c, nil } if key.KeyRangesIntersect(kr, c.keyRange) { // There is overlap, we can just send to the destination. // FIXME(alainjobart) if canonical is not entirely covered by l2vtgate, // this is probably an error. We probably want key.KeyRangeIncludes(), NYI. return c, nil } } return nil, fmt.Errorf("no configured destination for %v/%v", keyspace, shard) }
// NewFilterByShard creates a new FilterByShard on top of an existing // TabletRecorder. Each filter is a keyspace|shard entry, where shard // can either be a shard name, or a keyrange. All tablets that match // at least one keyspace|shard tuple will be forwarded to the // underlying TabletRecorder. func NewFilterByShard(tr TabletRecorder, filters []string) (*FilterByShard, error) { m := make(map[string][]*filterShard) for _, filter := range filters { parts := strings.Split(filter, "|") if len(parts) != 2 { return nil, fmt.Errorf("invalid FilterByShard parameter: %v", filter) } keyspace := parts[0] shard := parts[1] // extract keyrange if it's a range canonical, kr, err := topo.ValidateShardName(shard) if err != nil { return nil, fmt.Errorf("error parsing shard name %v: %v", shard, err) } // check for duplicates for _, c := range m[keyspace] { if c.shard == canonical { return nil, fmt.Errorf("duplicate %v/%v entry", keyspace, shard) } } m[keyspace] = append(m[keyspace], &filterShard{ keyspace: keyspace, shard: canonical, keyRange: kr, }) } return &FilterByShard{ tr: tr, filters: m, }, 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 }