vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletmanager/vdiff/action.go (about)

     1  /*
     2  Copyright 2022 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 vdiff
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  
    24  	"github.com/google/uuid"
    25  
    26  	"vitess.io/vitess/go/vt/topo/topoproto"
    27  	"vitess.io/vitess/go/vt/vterrors"
    28  
    29  	"vitess.io/vitess/go/sqltypes"
    30  	"vitess.io/vitess/go/vt/binlog/binlogplayer"
    31  	"vitess.io/vitess/go/vt/proto/query"
    32  	tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata"
    33  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    34  )
    35  
    36  type VDiffAction string //nolint
    37  
    38  const (
    39  	CreateAction  VDiffAction = "create"
    40  	ShowAction    VDiffAction = "show"
    41  	StopAction    VDiffAction = "stop"
    42  	ResumeAction  VDiffAction = "resume"
    43  	DeleteAction  VDiffAction = "delete"
    44  	AllActionArg              = "all"
    45  	LastActionArg             = "last"
    46  )
    47  
    48  var (
    49  	Actions    = []VDiffAction{CreateAction, ShowAction, StopAction, ResumeAction, DeleteAction}
    50  	ActionArgs = []string{AllActionArg, LastActionArg}
    51  )
    52  
    53  func (vde *Engine) PerformVDiffAction(ctx context.Context, req *tabletmanagerdatapb.VDiffRequest) (*tabletmanagerdatapb.VDiffResponse, error) {
    54  	if !vde.isOpen {
    55  		return nil, vterrors.New(vtrpcpb.Code_UNAVAILABLE, "vdiff engine is closed")
    56  	}
    57  	if vde.cancelRetry != nil {
    58  		return nil, vterrors.New(vtrpcpb.Code_UNAVAILABLE, "vdiff engine is still trying to open")
    59  	}
    60  
    61  	resp := &tabletmanagerdatapb.VDiffResponse{
    62  		Id:     0,
    63  		Output: nil,
    64  	}
    65  	// We use the db_filtered user for vreplication related work
    66  	dbClient := vde.dbClientFactoryFiltered()
    67  	if err := dbClient.Connect(); err != nil {
    68  		return nil, err
    69  	}
    70  	defer dbClient.Close()
    71  
    72  	action := VDiffAction(req.Action)
    73  	switch action {
    74  	case CreateAction, ResumeAction:
    75  		if err := vde.handleCreateResumeAction(ctx, dbClient, action, req, resp); err != nil {
    76  			return nil, err
    77  		}
    78  	case ShowAction:
    79  		if err := vde.handleShowAction(ctx, dbClient, action, req, resp); err != nil {
    80  			return nil, err
    81  		}
    82  	case StopAction:
    83  		if err := vde.handleStopAction(ctx, dbClient, action, req, resp); err != nil {
    84  			return nil, err
    85  		}
    86  	case DeleteAction:
    87  		if err := vde.handleDeleteAction(ctx, dbClient, action, req, resp); err != nil {
    88  			return nil, err
    89  		}
    90  	default:
    91  		return nil, fmt.Errorf("action %s not supported", action)
    92  	}
    93  	return resp, nil
    94  }
    95  
    96  func (vde *Engine) getVDiffSummary(vdiffID int64, dbClient binlogplayer.DBClient) (*query.QueryResult, error) {
    97  	var qr *sqltypes.Result
    98  	var err error
    99  
   100  	query := fmt.Sprintf(sqlVDiffSummary, vdiffID)
   101  	if qr, err = dbClient.ExecuteFetch(query, -1); err != nil {
   102  		return nil, err
   103  	}
   104  	return sqltypes.ResultToProto3(qr), nil
   105  
   106  }
   107  
   108  // Validate vdiff options. Also setup defaults where applicable.
   109  func (vde *Engine) fixupOptions(options *tabletmanagerdatapb.VDiffOptions) (*tabletmanagerdatapb.VDiffOptions, error) {
   110  	// Assign defaults to sourceCell and targetCell if not specified.
   111  	sourceCell := options.PickerOptions.SourceCell
   112  	targetCell := options.PickerOptions.TargetCell
   113  	var defaultCell string
   114  	var err error
   115  	if sourceCell == "" || targetCell == "" {
   116  		defaultCell, err = vde.getDefaultCell()
   117  		if err != nil {
   118  			return nil, err
   119  		}
   120  	}
   121  	if sourceCell == "" {
   122  		sourceCell = defaultCell
   123  	}
   124  	if targetCell == "" {
   125  		targetCell = defaultCell
   126  	}
   127  	options.PickerOptions.SourceCell = sourceCell
   128  	options.PickerOptions.TargetCell = targetCell
   129  
   130  	return options, nil
   131  }
   132  
   133  func (vde *Engine) getDefaultCell() (string, error) {
   134  	cells, err := vde.ts.GetCellInfoNames(vde.ctx)
   135  	if err != nil {
   136  		return "", err
   137  	}
   138  	if len(cells) == 0 {
   139  		// Unreachable
   140  		return "", fmt.Errorf("there are no cells in the topo")
   141  	}
   142  	return cells[0], nil
   143  }
   144  
   145  func (vde *Engine) handleCreateResumeAction(ctx context.Context, dbClient binlogplayer.DBClient, action VDiffAction, req *tabletmanagerdatapb.VDiffRequest, resp *tabletmanagerdatapb.VDiffResponse) error {
   146  	var qr *sqltypes.Result
   147  	var err error
   148  	options := req.Options
   149  
   150  	query := fmt.Sprintf(sqlGetVDiffID, encodeString(req.VdiffUuid))
   151  	if qr, err = dbClient.ExecuteFetch(query, 1); err != nil {
   152  		return err
   153  	}
   154  	recordFound := len(qr.Rows) == 1
   155  	if recordFound && action == CreateAction {
   156  		return fmt.Errorf("vdiff with UUID %s already exists on tablet %v",
   157  			req.VdiffUuid, vde.thisTablet.Alias)
   158  	} else if action == ResumeAction {
   159  		if !recordFound {
   160  			return fmt.Errorf("vdiff with UUID %s not found on tablet %v",
   161  				req.VdiffUuid, vde.thisTablet.Alias)
   162  		}
   163  		if resp.Id, err = qr.Named().Row().ToInt64("id"); err != nil {
   164  			return fmt.Errorf("vdiff found with invalid id on tablet %v: %w",
   165  				vde.thisTablet.Alias, err)
   166  		}
   167  	}
   168  	if options, err = vde.fixupOptions(options); err != nil {
   169  		return err
   170  	}
   171  	optionsJSON, err := json.Marshal(options)
   172  	if err != nil {
   173  		return err
   174  	}
   175  	if action == CreateAction {
   176  		query := fmt.Sprintf(sqlNewVDiff,
   177  			encodeString(req.Keyspace), encodeString(req.Workflow), "pending", encodeString(string(optionsJSON)),
   178  			vde.thisTablet.Shard, topoproto.TabletDbName(vde.thisTablet), req.VdiffUuid)
   179  		if qr, err = dbClient.ExecuteFetch(query, 1); err != nil {
   180  			return err
   181  		}
   182  		if qr.InsertID == 0 {
   183  			return fmt.Errorf("unable to create vdiff for UUID %s on tablet %v (%w)",
   184  				req.VdiffUuid, vde.thisTablet.Alias, err)
   185  		}
   186  		resp.Id = int64(qr.InsertID)
   187  	} else {
   188  		query := fmt.Sprintf(sqlResumeVDiff, encodeString(string(optionsJSON)), encodeString(req.VdiffUuid))
   189  		if qr, err = dbClient.ExecuteFetch(query, 1); err != nil {
   190  			return err
   191  		}
   192  		if qr.RowsAffected == 0 {
   193  			msg := fmt.Sprintf("no completed or stopped vdiff found for UUID %s on tablet %v",
   194  				req.VdiffUuid, vde.thisTablet.Alias)
   195  			if err != nil {
   196  				msg = fmt.Sprintf("%s (%v)", msg, err)
   197  			}
   198  			return fmt.Errorf(msg)
   199  		}
   200  	}
   201  
   202  	resp.VdiffUuid = req.VdiffUuid
   203  	qr, err = vde.getVDiffByID(ctx, dbClient, resp.Id)
   204  	if err != nil {
   205  		return err
   206  	}
   207  	vde.mu.Lock()
   208  	defer vde.mu.Unlock()
   209  	if err := vde.addController(qr.Named().Row(), options); err != nil {
   210  		return err
   211  	}
   212  
   213  	return nil
   214  }
   215  
   216  func (vde *Engine) handleShowAction(ctx context.Context, dbClient binlogplayer.DBClient, action VDiffAction, req *tabletmanagerdatapb.VDiffRequest, resp *tabletmanagerdatapb.VDiffResponse) error {
   217  	var qr *sqltypes.Result
   218  	var err error
   219  	vdiffUUID := ""
   220  
   221  	if req.ActionArg == LastActionArg {
   222  		query := fmt.Sprintf(sqlGetMostRecentVDiff, encodeString(req.Keyspace), encodeString(req.Workflow))
   223  		if qr, err = dbClient.ExecuteFetch(query, 1); err != nil {
   224  			return err
   225  		}
   226  		if len(qr.Rows) == 1 {
   227  			row := qr.Named().Row()
   228  			vdiffUUID = row.AsString("vdiff_uuid", "")
   229  		}
   230  	} else {
   231  		if uuidt, err := uuid.Parse(req.ActionArg); err == nil {
   232  			vdiffUUID = uuidt.String()
   233  		}
   234  	}
   235  	if vdiffUUID != "" {
   236  		resp.VdiffUuid = vdiffUUID
   237  		query := fmt.Sprintf(sqlGetVDiffByKeyspaceWorkflowUUID, encodeString(req.Keyspace), encodeString(req.Workflow), encodeString(vdiffUUID))
   238  		if qr, err = dbClient.ExecuteFetch(query, 1); err != nil {
   239  			return err
   240  		}
   241  		switch len(qr.Rows) {
   242  		case 0:
   243  			return fmt.Errorf("no vdiff found for UUID %s keyspace %s and workflow %s on tablet %v",
   244  				vdiffUUID, req.Keyspace, req.Workflow, vde.thisTablet.Alias)
   245  		case 1:
   246  			row := qr.Named().Row()
   247  			vdiffID, _ := row["id"].ToInt64()
   248  			summary, err := vde.getVDiffSummary(vdiffID, dbClient)
   249  			resp.Output = summary
   250  			if err != nil {
   251  				return err
   252  			}
   253  		default:
   254  			return fmt.Errorf("too many vdiffs found (%d) for UUID %s keyspace %s and workflow %s on tablet %v",
   255  				len(qr.Rows), vdiffUUID, req.Keyspace, req.Workflow, vde.thisTablet.Alias)
   256  		}
   257  	}
   258  	switch req.ActionArg {
   259  	case AllActionArg:
   260  		if qr, err = dbClient.ExecuteFetch(sqlGetAllVDiffs, -1); err != nil {
   261  			return err
   262  		}
   263  		resp.Output = sqltypes.ResultToProto3(qr)
   264  	case LastActionArg:
   265  	default:
   266  		if _, err := uuid.Parse(req.ActionArg); err != nil {
   267  			return fmt.Errorf("action argument %s not supported", req.ActionArg)
   268  		}
   269  	}
   270  
   271  	return nil
   272  }
   273  
   274  func (vde *Engine) handleStopAction(ctx context.Context, dbClient binlogplayer.DBClient, action VDiffAction, req *tabletmanagerdatapb.VDiffRequest, resp *tabletmanagerdatapb.VDiffResponse) error {
   275  	vde.mu.Lock()
   276  	defer vde.mu.Unlock()
   277  	for _, controller := range vde.controllers {
   278  		if controller.uuid == req.VdiffUuid {
   279  			controller.Stop()
   280  			if err := controller.markStoppedByRequest(); err != nil {
   281  				return err
   282  			}
   283  			break
   284  		}
   285  	}
   286  	return nil
   287  }
   288  
   289  func (vde *Engine) handleDeleteAction(ctx context.Context, dbClient binlogplayer.DBClient, action VDiffAction, req *tabletmanagerdatapb.VDiffRequest, resp *tabletmanagerdatapb.VDiffResponse) error {
   290  	var err error
   291  	query := ""
   292  
   293  	switch req.ActionArg {
   294  	case AllActionArg:
   295  		query = fmt.Sprintf(sqlDeleteVDiffs, encodeString(req.Keyspace), encodeString(req.Workflow))
   296  	default:
   297  		uuid, err := uuid.Parse(req.ActionArg)
   298  		if err != nil {
   299  			return fmt.Errorf("action argument %s not supported", req.ActionArg)
   300  		}
   301  		query = fmt.Sprintf(sqlDeleteVDiffByUUID, encodeString(uuid.String()))
   302  	}
   303  	if _, err = dbClient.ExecuteFetch(query, 1); err != nil {
   304  		return err
   305  	}
   306  
   307  	return nil
   308  }