vitess.io/vitess@v0.16.2/go/vt/wrangler/keyspace.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  	"bytes"
    21  	"context"
    22  	"fmt"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  
    27  	"vitess.io/vitess/go/vt/proto/binlogdata"
    28  
    29  	"vitess.io/vitess/go/sqltypes"
    30  	"vitess.io/vitess/go/vt/concurrency"
    31  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    32  	"vitess.io/vitess/go/vt/topo"
    33  	"vitess.io/vitess/go/vt/topo/topoproto"
    34  	"vitess.io/vitess/go/vt/topotools"
    35  	"vitess.io/vitess/go/vt/vterrors"
    36  )
    37  
    38  const (
    39  	// DefaultFilteredReplicationWaitTime is the default value for argument filteredReplicationWaitTime.
    40  	DefaultFilteredReplicationWaitTime = 30 * time.Second
    41  )
    42  
    43  // keyspace related methods for Wrangler
    44  
    45  // validateNewWorkflow ensures that the specified workflow doesn't already exist
    46  // in the keyspace.
    47  func (wr *Wrangler) validateNewWorkflow(ctx context.Context, keyspace, workflow string) error {
    48  	allshards, err := wr.ts.FindAllShardsInKeyspace(ctx, keyspace)
    49  	if err != nil {
    50  		return err
    51  	}
    52  	var wg sync.WaitGroup
    53  	allErrors := &concurrency.AllErrorRecorder{}
    54  	for _, si := range allshards {
    55  		if si.PrimaryAlias == nil {
    56  			allErrors.RecordError(fmt.Errorf("shard has no primary: %v", si.ShardName()))
    57  			continue
    58  		}
    59  		wg.Add(1)
    60  		go func(si *topo.ShardInfo) {
    61  			defer wg.Done()
    62  
    63  			primary, err := wr.ts.GetTablet(ctx, si.PrimaryAlias)
    64  			if err != nil {
    65  				allErrors.RecordError(vterrors.Wrap(err, "validateWorkflowName.GetTablet"))
    66  				return
    67  			}
    68  			validations := []struct {
    69  				query string
    70  				msg   string
    71  			}{{
    72  				fmt.Sprintf("select 1 from _vt.vreplication where db_name=%s and workflow=%s", encodeString(primary.DbName()), encodeString(workflow)),
    73  				fmt.Sprintf("workflow %s already exists in keyspace %s on tablet %d", workflow, keyspace, primary.Alias.Uid),
    74  			}, {
    75  				fmt.Sprintf("select 1 from _vt.vreplication where db_name=%s and message='FROZEN' and workflow_sub_type != %d", encodeString(primary.DbName()), binlogdata.VReplicationWorkflowSubType_Partial),
    76  				fmt.Sprintf("found previous frozen workflow on tablet %d, please review and delete it first before creating a new workflow",
    77  					primary.Alias.Uid),
    78  			}}
    79  			for _, validation := range validations {
    80  				p3qr, err := wr.tmc.VReplicationExec(ctx, primary.Tablet, validation.query)
    81  				if err != nil {
    82  					allErrors.RecordError(vterrors.Wrap(err, "validateWorkflowName.VReplicationExec"))
    83  					return
    84  				}
    85  				if p3qr != nil && len(p3qr.Rows) != 0 {
    86  					allErrors.RecordError(vterrors.Wrap(fmt.Errorf(validation.msg), "validateWorkflowName.VReplicationExec"))
    87  					return
    88  				}
    89  			}
    90  		}(si)
    91  	}
    92  	wg.Wait()
    93  	return allErrors.AggrError(vterrors.Aggregate)
    94  }
    95  
    96  func (wr *Wrangler) printShards(ctx context.Context, si []*topo.ShardInfo) error {
    97  	for _, si := range si {
    98  		wr.Logger().Printf("    Shard: %v\n", si.ShardName())
    99  		if len(si.SourceShards) != 0 {
   100  			wr.Logger().Printf("      Source Shards: %v\n", si.SourceShards)
   101  		}
   102  		ti, err := wr.ts.GetTablet(ctx, si.PrimaryAlias)
   103  		if err != nil {
   104  			return err
   105  		}
   106  		qr, err := wr.tmc.VReplicationExec(ctx, ti.Tablet, fmt.Sprintf("select * from _vt.vreplication where db_name=%v", encodeString(ti.DbName())))
   107  		if err != nil {
   108  			return err
   109  		}
   110  		res := sqltypes.Proto3ToResult(qr)
   111  		if len(res.Rows) != 0 {
   112  			wr.Logger().Printf("      VReplication:\n")
   113  			for _, row := range res.Rows {
   114  				wr.Logger().Printf("        %v\n", row)
   115  			}
   116  		}
   117  		wr.Logger().Printf("      Is Primary Serving: %v\n", si.IsPrimaryServing)
   118  		if len(si.TabletControls) != 0 {
   119  			wr.Logger().Printf("      Tablet Controls: %v\n", si.TabletControls)
   120  		}
   121  	}
   122  	return nil
   123  }
   124  
   125  func (wr *Wrangler) getPrimaryPositions(ctx context.Context, shards []*topo.ShardInfo) (map[*topo.ShardInfo]string, error) {
   126  	mu := sync.Mutex{}
   127  	result := make(map[*topo.ShardInfo]string)
   128  
   129  	wg := sync.WaitGroup{}
   130  	rec := concurrency.AllErrorRecorder{}
   131  	for _, si := range shards {
   132  		wg.Add(1)
   133  		go func(si *topo.ShardInfo) {
   134  			defer wg.Done()
   135  			wr.Logger().Infof("Gathering primary position for %v", topoproto.TabletAliasString(si.PrimaryAlias))
   136  			ti, err := wr.ts.GetTablet(ctx, si.PrimaryAlias)
   137  			if err != nil {
   138  				rec.RecordError(err)
   139  				return
   140  			}
   141  
   142  			pos, err := wr.tmc.PrimaryPosition(ctx, ti.Tablet)
   143  			if err != nil {
   144  				rec.RecordError(err)
   145  				return
   146  			}
   147  
   148  			wr.Logger().Infof("Got primary position for %v", topoproto.TabletAliasString(si.PrimaryAlias))
   149  			mu.Lock()
   150  			result[si] = pos
   151  			mu.Unlock()
   152  		}(si)
   153  	}
   154  	wg.Wait()
   155  	return result, rec.Error()
   156  }
   157  
   158  func (wr *Wrangler) waitForFilteredReplication(ctx context.Context, sourcePositions map[*topo.ShardInfo]string, destinationShards []*topo.ShardInfo, waitTime time.Duration) error {
   159  	wg := sync.WaitGroup{}
   160  	rec := concurrency.AllErrorRecorder{}
   161  	for _, si := range destinationShards {
   162  		wg.Add(1)
   163  		go func(si *topo.ShardInfo) {
   164  			defer wg.Done()
   165  			ctx, cancel := context.WithTimeout(ctx, waitTime)
   166  			defer cancel()
   167  
   168  			var pos string
   169  			for _, sourceShard := range si.SourceShards {
   170  				// find the position it should be at
   171  				for s, sp := range sourcePositions {
   172  					if s.Keyspace() == sourceShard.Keyspace && s.ShardName() == sourceShard.Shard {
   173  						pos = sp
   174  						break
   175  					}
   176  				}
   177  
   178  				// and wait for it
   179  				wr.Logger().Infof("Waiting for %v to catch up", topoproto.TabletAliasString(si.PrimaryAlias))
   180  				ti, err := wr.ts.GetTablet(ctx, si.PrimaryAlias)
   181  				if err != nil {
   182  					rec.RecordError(err)
   183  					return
   184  				}
   185  
   186  				if err := wr.tmc.VReplicationWaitForPos(ctx, ti.Tablet, int(sourceShard.Uid), pos); err != nil {
   187  					if strings.Contains(err.Error(), "not found") {
   188  						wr.Logger().Infof("%v stream %d was not found. Skipping wait.", topoproto.TabletAliasString(si.PrimaryAlias), sourceShard.Uid)
   189  					} else {
   190  						rec.RecordError(err)
   191  					}
   192  				} else {
   193  					wr.Logger().Infof("%v caught up", topoproto.TabletAliasString(si.PrimaryAlias))
   194  				}
   195  			}
   196  		}(si)
   197  	}
   198  	wg.Wait()
   199  	return rec.Error()
   200  }
   201  
   202  // refreshPrimaryTablets will just RPC-ping all the primary tablets with RefreshState
   203  func (wr *Wrangler) refreshPrimaryTablets(ctx context.Context, shards []*topo.ShardInfo) error {
   204  	wg := sync.WaitGroup{}
   205  	rec := concurrency.AllErrorRecorder{}
   206  	for _, si := range shards {
   207  		wg.Add(1)
   208  		go func(si *topo.ShardInfo) {
   209  			defer wg.Done()
   210  			wr.Logger().Infof("RefreshState primary %v", topoproto.TabletAliasString(si.PrimaryAlias))
   211  			ti, err := wr.ts.GetTablet(ctx, si.PrimaryAlias)
   212  			if err != nil {
   213  				rec.RecordError(err)
   214  				return
   215  			}
   216  
   217  			if err := wr.tmc.RefreshState(ctx, ti.Tablet); err != nil {
   218  				rec.RecordError(err)
   219  			} else {
   220  				wr.Logger().Infof("%v responded", topoproto.TabletAliasString(si.PrimaryAlias))
   221  			}
   222  		}(si)
   223  	}
   224  	wg.Wait()
   225  	return rec.Error()
   226  }
   227  
   228  // updateShardRecords updates the shard records based on 'from' or 'to' direction.
   229  func (wr *Wrangler) updateShardRecords(ctx context.Context, keyspace string, shards []*topo.ShardInfo, cells []string, servedType topodatapb.TabletType, isFrom bool, clearSourceShards bool) (err error) {
   230  	return topotools.UpdateShardRecords(ctx, wr.ts, wr.tmc, keyspace, shards, cells, servedType, isFrom, clearSourceShards, wr.Logger())
   231  }
   232  
   233  // updateFrozenFlag sets or unsets the Frozen flag for primary migration. This is performed
   234  // for all primary tablet control records.
   235  func (wr *Wrangler) updateFrozenFlag(ctx context.Context, shards []*topo.ShardInfo, value bool) (err error) {
   236  	for i, si := range shards {
   237  		updatedShard, err := wr.ts.UpdateShardFields(ctx, si.Keyspace(), si.ShardName(), func(si *topo.ShardInfo) error {
   238  			tc := si.GetTabletControl(topodatapb.TabletType_PRIMARY)
   239  			if tc != nil {
   240  				tc.Frozen = value
   241  				return nil
   242  			}
   243  			// This shard does not have a tablet control record, adding one to set frozen flag
   244  			tc = &topodatapb.Shard_TabletControl{
   245  				TabletType: topodatapb.TabletType_PRIMARY,
   246  				Frozen:     value,
   247  			}
   248  			si.TabletControls = append(si.TabletControls, tc)
   249  			return nil
   250  		})
   251  		if err != nil {
   252  			return err
   253  		}
   254  
   255  		shards[i] = updatedShard
   256  	}
   257  	return nil
   258  }
   259  
   260  func encodeString(in string) string {
   261  	buf := bytes.NewBuffer(nil)
   262  	sqltypes.NewVarChar(in).EncodeSQL(buf)
   263  	return buf.String()
   264  }