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 }