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 }