vitess.io/vitess@v0.16.2/go/vt/topotools/tablet.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  /*
    18  Package topotools contains high level functions based on vt/topo and
    19  vt/actionnode. It should not depend on anything else that's higher
    20  level. In particular, it cannot depend on:
    21    - vt/wrangler: much higher level, wrangler depends on topotools.
    22    - vt/tabletmanager/initiator: we don't want the various remote
    23      protocol dependencies here.
    24  
    25  topotools is used by wrangler, so it ends up in all tools using
    26  wrangler (vtctl, vtctld, ...). It is also included by vttablet, so it contains:
    27    - most of the logic to create a shard / keyspace (tablet's init code)
    28    - some of the logic to perform a TabletExternallyReparented (RPC call
    29      to primary vttablet to let it know it's the primary).
    30  */
    31  package topotools
    32  
    33  // This file contains utility functions for tablets
    34  
    35  import (
    36  	"context"
    37  	"errors"
    38  	"fmt"
    39  
    40  	"google.golang.org/protobuf/proto"
    41  
    42  	"vitess.io/vitess/go/vt/hook"
    43  	"vitess.io/vitess/go/vt/log"
    44  	"vitess.io/vitess/go/vt/topo"
    45  	"vitess.io/vitess/go/vt/topo/topoproto"
    46  	"vitess.io/vitess/go/vt/vterrors"
    47  
    48  	querypb "vitess.io/vitess/go/vt/proto/query"
    49  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    50  	"vitess.io/vitess/go/vt/proto/vtrpc"
    51  	"vitess.io/vitess/go/vt/proto/vttime"
    52  )
    53  
    54  // ConfigureTabletHook configures the right parameters for a hook
    55  // running locally on a tablet.
    56  func ConfigureTabletHook(hk *hook.Hook, tabletAlias *topodatapb.TabletAlias) {
    57  	if hk.ExtraEnv == nil {
    58  		hk.ExtraEnv = make(map[string]string, 1)
    59  	}
    60  	hk.ExtraEnv["TABLET_ALIAS"] = topoproto.TabletAliasString(tabletAlias)
    61  }
    62  
    63  // ChangeType changes the type of the tablet. Make this external, since these
    64  // transitions need to be forced from time to time.
    65  //
    66  // If successful, the updated tablet record is returned.
    67  func ChangeType(ctx context.Context, ts *topo.Server, tabletAlias *topodatapb.TabletAlias, newType topodatapb.TabletType, PrimaryTermStartTime *vttime.Time) (*topodatapb.Tablet, error) {
    68  	var result *topodatapb.Tablet
    69  	// Always clear out the primary timestamp if not primary.
    70  	if newType != topodatapb.TabletType_PRIMARY {
    71  		PrimaryTermStartTime = nil
    72  	}
    73  	_, err := ts.UpdateTabletFields(ctx, tabletAlias, func(tablet *topodatapb.Tablet) error {
    74  		// Save the most recent tablet value so we can return it
    75  		// either if the update succeeds or if no update is needed.
    76  		result = tablet
    77  		if tablet.Type == newType && proto.Equal(tablet.PrimaryTermStartTime, PrimaryTermStartTime) {
    78  			return topo.NewError(topo.NoUpdateNeeded, topoproto.TabletAliasString(tabletAlias))
    79  		}
    80  		tablet.Type = newType
    81  		tablet.PrimaryTermStartTime = PrimaryTermStartTime
    82  		return nil
    83  	})
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	return result, nil
    88  }
    89  
    90  // CheckOwnership returns nil iff the Hostname and port match on oldTablet and
    91  // newTablet, which implies that no other tablet process has taken over the
    92  // record.
    93  func CheckOwnership(oldTablet, newTablet *topodatapb.Tablet) error {
    94  	if oldTablet == nil || newTablet == nil {
    95  		return errors.New("unable to verify ownership of tablet record")
    96  	}
    97  	if oldTablet.Hostname != newTablet.Hostname || oldTablet.PortMap["vt"] != newTablet.PortMap["vt"] {
    98  		return fmt.Errorf(
    99  			"tablet record was taken over by another process: "+
   100  				"my address is %v:%v, but record is owned by %v:%v",
   101  			oldTablet.Hostname, oldTablet.PortMap["vt"], newTablet.Hostname, newTablet.PortMap["vt"])
   102  	}
   103  	return nil
   104  }
   105  
   106  // DoCellsHaveRdonlyTablets returns true if any of the cells has at least one
   107  // tablet with type RDONLY. If the slice of cells to search over is empty, it
   108  // checks all cells in the topo.
   109  func DoCellsHaveRdonlyTablets(ctx context.Context, ts *topo.Server, cells []string) (bool, error) {
   110  	areAnyRdonly := func(tablets []*topo.TabletInfo) bool {
   111  		for _, tablet := range tablets {
   112  			if tablet.Type == topodatapb.TabletType_RDONLY {
   113  				return true
   114  			}
   115  		}
   116  
   117  		return false
   118  	}
   119  
   120  	if len(cells) == 0 {
   121  		tablets, err := GetAllTabletsAcrossCells(ctx, ts)
   122  		if err != nil {
   123  			return false, err
   124  		}
   125  
   126  		return areAnyRdonly(tablets), nil
   127  	}
   128  
   129  	for _, cell := range cells {
   130  		tablets, err := ts.GetTabletsByCell(ctx, cell)
   131  		if err != nil {
   132  			return false, err
   133  		}
   134  
   135  		if areAnyRdonly(tablets) {
   136  			return true, nil
   137  		}
   138  	}
   139  
   140  	return false, nil
   141  }
   142  
   143  // GetShardPrimaryForTablet returns the TabletInfo of the given tablet's shard's primary.
   144  //
   145  // It returns an error if:
   146  // - The shard does not exist in the topo.
   147  // - The shard has no primary in the topo.
   148  // - The shard primary does not think it is PRIMARY.
   149  // - The shard primary tablet record does not match the keyspace and shard of the replica.
   150  func GetShardPrimaryForTablet(ctx context.Context, ts *topo.Server, tablet *topodatapb.Tablet) (*topo.TabletInfo, error) {
   151  	shard, err := ts.GetShard(ctx, tablet.Keyspace, tablet.Shard)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	if !shard.HasPrimary() {
   157  		return nil, vterrors.Errorf(vtrpc.Code_FAILED_PRECONDITION, "no primary tablet for shard %v/%v", tablet.Keyspace, tablet.Shard)
   158  	}
   159  
   160  	shardPrimary, err := ts.GetTablet(ctx, shard.PrimaryAlias)
   161  	if err != nil {
   162  		return nil, fmt.Errorf("cannot lookup primary tablet %v for shard %v/%v: %w", topoproto.TabletAliasString(shard.PrimaryAlias), tablet.Keyspace, tablet.Shard, err)
   163  	}
   164  
   165  	if shardPrimary.Type != topodatapb.TabletType_PRIMARY {
   166  		return nil, vterrors.Errorf(vtrpc.Code_FAILED_PRECONDITION, "TopologyServer has inconsistent state for shard primary %v", topoproto.TabletAliasString(shard.PrimaryAlias))
   167  	}
   168  
   169  	if shardPrimary.Keyspace != tablet.Keyspace || shardPrimary.Shard != tablet.Shard {
   170  		return nil, vterrors.Errorf(vtrpc.Code_FAILED_PRECONDITION, "primary %v and potential replica %v not in same keyspace shard (%v/%v)", topoproto.TabletAliasString(shard.PrimaryAlias), topoproto.TabletAliasString(tablet.Alias), tablet.Keyspace, tablet.Shard)
   171  	}
   172  
   173  	return shardPrimary, nil
   174  }
   175  
   176  // IsPrimaryTablet is a helper function to determine whether the current tablet
   177  // is a primary before we allow its tablet record to be deleted. The canonical
   178  // way to determine the only true primary in a shard is to list all the tablets
   179  // and find the one with the highest PrimaryTermStartTime among the ones that
   180  // claim to be primary.
   181  //
   182  // We err on the side of caution here, i.e. we should never return false for
   183  // a true primary tablet, but it is okay to return true for a tablet that isn't
   184  // the true primary. This can occur if someone issues a DeleteTablet while
   185  // the system is in transition (a reparenting event is in progress and parts of
   186  // the topo have not yet been updated).
   187  func IsPrimaryTablet(ctx context.Context, ts *topo.Server, ti *topo.TabletInfo) (bool, error) {
   188  	// Tablet record claims to be non-primary, we believe it
   189  	if ti.Type != topodatapb.TabletType_PRIMARY {
   190  		return false, nil
   191  	}
   192  
   193  	si, err := ts.GetShard(ctx, ti.Keyspace, ti.Shard)
   194  	if err != nil {
   195  		// strictly speaking it isn't correct to return false here, the tablet
   196  		// status is unknown
   197  		return false, err
   198  	}
   199  
   200  	// Tablet record claims to be primary, and shard record matches
   201  	if topoproto.TabletAliasEqual(si.PrimaryAlias, ti.Tablet.Alias) {
   202  		return true, nil
   203  	}
   204  
   205  	// Shard record has another tablet as primary, so check PrimaryTermStartTime
   206  	// If tablet record's PrimaryTermStartTime is later than the one in the shard
   207  	// record, then the tablet is primary
   208  	tabletMTST := ti.GetPrimaryTermStartTime()
   209  	shardMTST := si.GetPrimaryTermStartTime()
   210  
   211  	return tabletMTST.After(shardMTST), nil
   212  }
   213  
   214  // DeleteTablet removes a tablet record from the topology:
   215  // - the replication data record if any
   216  // - the tablet record
   217  func DeleteTablet(ctx context.Context, ts *topo.Server, tablet *topodatapb.Tablet) error {
   218  	// try to remove replication data, no fatal if we fail
   219  	if err := topo.DeleteTabletReplicationData(ctx, ts, tablet); err != nil {
   220  		if topo.IsErrType(err, topo.NoNode) {
   221  			log.V(6).Infof("no ShardReplication object for cell %v", tablet.Alias.Cell)
   222  			err = nil
   223  		}
   224  		if err != nil {
   225  			log.Warningf("remove replication data for %v failed: %v", topoproto.TabletAliasString(tablet.Alias), err)
   226  		}
   227  	}
   228  
   229  	// then delete the tablet record
   230  	return ts.DeleteTablet(ctx, tablet.Alias)
   231  }
   232  
   233  // TabletIdent returns a concise string representation of this tablet.
   234  func TabletIdent(tablet *topodatapb.Tablet) string {
   235  	tagStr := ""
   236  	if tablet.Tags != nil {
   237  		for key, val := range tablet.Tags {
   238  			tagStr = tagStr + fmt.Sprintf(" %s=%s", key, val)
   239  		}
   240  	}
   241  
   242  	return fmt.Sprintf("%s-%d (%s%s)", tablet.Alias.Cell, tablet.Alias.Uid, tablet.Hostname, tagStr)
   243  }
   244  
   245  // TargetIdent returns a concise string representation of a query target
   246  func TargetIdent(target *querypb.Target) string {
   247  	return fmt.Sprintf("%s/%s (%s)", target.Keyspace, target.Shard, target.TabletType)
   248  }