vitess.io/vitess@v0.16.2/go/vt/wrangler/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 package wrangler 18 19 import ( 20 "context" 21 "fmt" 22 "time" 23 24 "google.golang.org/protobuf/proto" 25 26 "vitess.io/vitess/go/vt/topo" 27 "vitess.io/vitess/go/vt/topo/topoproto" 28 "vitess.io/vitess/go/vt/topotools" 29 "vitess.io/vitess/go/vt/vtctl/reparentutil" 30 31 querypb "vitess.io/vitess/go/vt/proto/query" 32 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 33 vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata" 34 ) 35 36 // Tablet related methods for wrangler 37 38 // DeleteTablet removes a tablet from a shard. 39 // - if allowPrimary is set, we can Delete a primary tablet (and clear 40 // its record from the Shard record if it was the primary). 41 func (wr *Wrangler) DeleteTablet(ctx context.Context, tabletAlias *topodatapb.TabletAlias, allowPrimary bool) (err error) { 42 // load the tablet, see if we'll need to rebuild 43 ti, err := wr.ts.GetTablet(ctx, tabletAlias) 44 if err != nil { 45 return err 46 } 47 48 wasPrimary, err := wr.isPrimaryTablet(ctx, ti) 49 if err != nil && !topo.IsErrType(err, topo.NoNode) { 50 return err 51 } 52 53 if wasPrimary && !allowPrimary { 54 return fmt.Errorf("cannot delete tablet %v as it is a primary, use allow_primary flag", topoproto.TabletAliasString(tabletAlias)) 55 } 56 57 // update the Shard object if the primary was scrapped. 58 // we do this before calling DeleteTablet so that the operation can be retried in case of failure. 59 if wasPrimary { 60 // We lock the shard to not conflict with reparent operations. 61 ctx, unlock, lockErr := wr.ts.LockShard(ctx, ti.Keyspace, ti.Shard, fmt.Sprintf("DeleteTablet(%v)", topoproto.TabletAliasString(tabletAlias))) 62 if lockErr != nil { 63 return lockErr 64 } 65 defer unlock(&err) 66 67 // update the shard record's primary 68 _, err := wr.ts.UpdateShardFields(ctx, ti.Keyspace, ti.Shard, func(si *topo.ShardInfo) error { 69 if !topoproto.TabletAliasEqual(si.PrimaryAlias, tabletAlias) { 70 wr.Logger().Warningf("Deleting primary %v from shard %v/%v but primary in Shard object was %v", topoproto.TabletAliasString(tabletAlias), ti.Keyspace, ti.Shard, topoproto.TabletAliasString(si.PrimaryAlias)) 71 return topo.NewError(topo.NoUpdateNeeded, si.Keyspace()+"/"+si.ShardName()) 72 } 73 si.PrimaryAlias = nil 74 si.SetPrimaryTermStartTime(time.Now()) 75 return nil 76 }) 77 if err != nil && !topo.IsErrType(err, topo.NoNode) { 78 return err 79 } 80 } 81 82 // remove the record and its replication graph entry 83 if err := topotools.DeleteTablet(ctx, wr.ts, ti.Tablet); err != nil { 84 return err 85 } 86 87 return nil 88 } 89 90 // ChangeTabletType changes the type of tablet and recomputes all 91 // necessary derived paths in the serving graph, if necessary. 92 // 93 // Note we don't update the primary record in the Shard here, as we 94 // can't ChangeType from and out of primary anyway. 95 func (wr *Wrangler) ChangeTabletType(ctx context.Context, tabletAlias *topodatapb.TabletAlias, tabletType topodatapb.TabletType) error { 96 // Load tablet to find endpoint, and keyspace and shard assignment. 97 ti, err := wr.ts.GetTablet(ctx, tabletAlias) 98 if err != nil { 99 return err 100 } 101 102 if !topo.IsTrivialTypeChange(ti.Type, tabletType) { 103 return fmt.Errorf("tablet %v type change %v -> %v is not an allowed transition for ChangeTabletType", tabletAlias, ti.Type, tabletType) 104 } 105 106 // We should clone the tablet and change its type to the expected type before checking the durability rules 107 // Since we want to check the durability rules for the desired state and not before we make that change 108 expectedTablet := proto.Clone(ti.Tablet).(*topodatapb.Tablet) 109 expectedTablet.Type = tabletType 110 semiSync, err := wr.shouldSendSemiSyncAck(ctx, expectedTablet) 111 if err != nil { 112 return err 113 } 114 // and ask the tablet to make the change 115 return wr.tmc.ChangeType(ctx, ti.Tablet, tabletType, semiSync) 116 } 117 118 // StartReplication is used to start replication on the specified tablet 119 // It also finds out if the tablet should be sending semi-sync ACKs or not. 120 func (wr *Wrangler) StartReplication(ctx context.Context, tablet *topodatapb.Tablet) error { 121 semiSync, err := wr.shouldSendSemiSyncAck(ctx, tablet) 122 if err != nil { 123 return err 124 } 125 return wr.TabletManagerClient().StartReplication(ctx, tablet, semiSync) 126 } 127 128 // SetReplicationSource is used to set the replication source on the specified tablet to the current shard primary (if available). 129 // It also figures out if the tablet should be sending semi-sync ACKs or not and passes that to the tabletmanager RPC. 130 // It does not start the replication forcefully. If we are unable to find the shard primary of the tablet from the topo server 131 // we exit out without any error. 132 func (wr *Wrangler) SetReplicationSource(ctx context.Context, tablet *topodatapb.Tablet) error { 133 return reparentutil.SetReplicationSource(ctx, wr.ts, wr.TabletManagerClient(), tablet) 134 } 135 136 func (wr *Wrangler) shouldSendSemiSyncAck(ctx context.Context, tablet *topodatapb.Tablet) (bool, error) { 137 shardPrimary, err := wr.getShardPrimaryForTablet(ctx, tablet) 138 if err != nil { 139 return false, err 140 } 141 142 durabilityName, err := wr.ts.GetKeyspaceDurability(ctx, tablet.Keyspace) 143 if err != nil { 144 return false, err 145 } 146 durability, err := reparentutil.GetDurabilityPolicy(durabilityName) 147 if err != nil { 148 return false, err 149 } 150 151 return reparentutil.IsReplicaSemiSync(durability, shardPrimary.Tablet, tablet), nil 152 } 153 154 func (wr *Wrangler) getShardPrimaryForTablet(ctx context.Context, tablet *topodatapb.Tablet) (*topo.TabletInfo, error) { 155 return topotools.GetShardPrimaryForTablet(ctx, wr.ts, tablet) 156 } 157 158 // RefreshTabletState refreshes tablet state 159 func (wr *Wrangler) RefreshTabletState(ctx context.Context, tabletAlias *topodatapb.TabletAlias) error { 160 // Load tablet to find endpoint, and keyspace and shard assignment. 161 ti, err := wr.ts.GetTablet(ctx, tabletAlias) 162 if err != nil { 163 return err 164 } 165 166 // and ask the tablet to refresh itself 167 return wr.tmc.RefreshState(ctx, ti.Tablet) 168 } 169 170 // ExecuteFetchAsApp executes a query remotely using the App pool 171 func (wr *Wrangler) ExecuteFetchAsApp(ctx context.Context, tabletAlias *topodatapb.TabletAlias, usePool bool, query string, maxRows int) (*querypb.QueryResult, error) { 172 resp, err := wr.VtctldServer().ExecuteFetchAsApp(ctx, &vtctldatapb.ExecuteFetchAsAppRequest{ 173 TabletAlias: tabletAlias, 174 Query: query, 175 MaxRows: int64(maxRows), 176 UsePool: usePool, 177 }) 178 if err != nil { 179 return nil, err 180 } 181 182 return resp.Result, nil 183 } 184 185 // ExecuteFetchAsDba executes a query remotely using the DBA pool 186 func (wr *Wrangler) ExecuteFetchAsDba(ctx context.Context, tabletAlias *topodatapb.TabletAlias, query string, maxRows int, disableBinlogs bool, reloadSchema bool) (*querypb.QueryResult, error) { 187 resp, err := wr.VtctldServer().ExecuteFetchAsDBA(ctx, &vtctldatapb.ExecuteFetchAsDBARequest{ 188 TabletAlias: tabletAlias, 189 Query: query, 190 MaxRows: int64(maxRows), 191 DisableBinlogs: disableBinlogs, 192 ReloadSchema: reloadSchema, 193 }) 194 if err != nil { 195 return nil, err 196 } 197 198 return resp.Result, nil 199 } 200 201 // VReplicationExec executes a query remotely using the DBA pool 202 func (wr *Wrangler) VReplicationExec(ctx context.Context, tabletAlias *topodatapb.TabletAlias, query string) (*querypb.QueryResult, error) { 203 ti, err := wr.ts.GetTablet(ctx, tabletAlias) 204 if err != nil { 205 return nil, err 206 } 207 return wr.tmc.VReplicationExec(ctx, ti.Tablet, query) 208 } 209 210 // GenericVExec executes a query remotely using the DBA pool 211 func (wr *Wrangler) GenericVExec(ctx context.Context, tabletAlias *topodatapb.TabletAlias, query, workflow, keyspace string) (*querypb.QueryResult, error) { 212 ti, err := wr.ts.GetTablet(ctx, tabletAlias) 213 if err != nil { 214 return nil, err 215 } 216 return wr.tmc.VExec(ctx, ti.Tablet, query, workflow, keyspace) 217 } 218 219 // isPrimaryTablet is a shortcut way to determine whether the current tablet 220 // is a primary before we allow its tablet record to be deleted. The canonical 221 // way to determine the only true primary in a shard is to list all the tablets 222 // and find the one with the highest PrimaryTermStartTime among the ones that 223 // claim to be primary. 224 // We err on the side of caution here, i.e. we should never return false for 225 // a true primary tablet, but it is ok to return true for a tablet that isn't 226 // the true primary. This can occur if someone issues a DeleteTablet while 227 // the system is in transition (a reparenting event is in progress and parts of 228 // the topo have not yet been updated). 229 func (wr *Wrangler) isPrimaryTablet(ctx context.Context, ti *topo.TabletInfo) (bool, error) { 230 return topotools.IsPrimaryTablet(ctx, wr.TopoServer(), ti) 231 }