vitess.io/vitess@v0.16.2/go/vt/wrangler/cleaner.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  	"fmt"
    21  	"sync"
    22  	"time"
    23  
    24  	"context"
    25  
    26  	"vitess.io/vitess/go/vt/concurrency"
    27  	"vitess.io/vitess/go/vt/topo"
    28  	"vitess.io/vitess/go/vt/topo/topoproto"
    29  
    30  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    31  )
    32  
    33  const (
    34  	// ChangeTabletTypeActionName is the name of the action to change a tablet type
    35  	// (can be used to find such an action by name)
    36  	ChangeTabletTypeActionName = "ChangeTabletTypeAction"
    37  
    38  	// TabletTagActionName is the name of the Tag action
    39  	TabletTagActionName = "TabletTagAction"
    40  
    41  	// StartReplicationActionName is the name of the start replication action
    42  	StartReplicationActionName = "StartReplicationAction"
    43  
    44  	// VReplicationActionName is the name of the action to execute VReplication commands
    45  	VReplicationActionName = "VReplicationAction"
    46  )
    47  
    48  // Cleaner remembers a list of cleanup steps to perform.  Just record
    49  // action cleanup steps, and execute them at the end in reverse
    50  // order, with various guarantees.
    51  type Cleaner struct {
    52  	// mu protects the following members
    53  	mu      sync.Mutex
    54  	actions []cleanerActionReference
    55  }
    56  
    57  // cleanerActionReference is the node used by Cleaner
    58  type cleanerActionReference struct {
    59  	name   string
    60  	target string
    61  	action CleanerFunction
    62  }
    63  
    64  // CleanerFunction is the interface that clean-up actions need to implement
    65  type CleanerFunction func(context.Context, *Wrangler) error
    66  
    67  // Record will add a cleaning action to the list
    68  func (cleaner *Cleaner) Record(name, target string, action CleanerFunction) {
    69  	cleaner.mu.Lock()
    70  	cleaner.actions = append(cleaner.actions, cleanerActionReference{
    71  		name:   name,
    72  		target: target,
    73  		action: action,
    74  	})
    75  	cleaner.mu.Unlock()
    76  }
    77  
    78  type cleanUpHelper struct {
    79  	err error
    80  }
    81  
    82  // CleanUp will run the recorded actions.
    83  // If an action on a target fails, it will not run the next action on
    84  // the same target.
    85  // We return the aggregate errors for all cleanups.
    86  // CleanUp uses its own context, with a timeout of 5 minutes, so that clean up action will run even if the original context times out.
    87  // TODO(alainjobart) Actions should run concurrently on a per target
    88  // basis. They are then serialized on each target.
    89  func (cleaner *Cleaner) CleanUp(wr *Wrangler) error {
    90  	// we use a background context so we're not dependent on the original context timeout
    91  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
    92  	actionMap := make(map[string]*cleanUpHelper)
    93  	rec := concurrency.AllErrorRecorder{}
    94  	cleaner.mu.Lock()
    95  	for i := len(cleaner.actions) - 1; i >= 0; i-- {
    96  		actionReference := cleaner.actions[i]
    97  		helper, ok := actionMap[actionReference.target]
    98  		if !ok {
    99  			helper = &cleanUpHelper{
   100  				err: nil,
   101  			}
   102  			actionMap[actionReference.target] = helper
   103  		}
   104  		if helper.err != nil {
   105  			wr.Logger().Warningf("previous action failed on target %v, not running %v", actionReference.target, actionReference.name)
   106  			continue
   107  		}
   108  		err := actionReference.action(ctx, wr)
   109  		if err != nil {
   110  			helper.err = err
   111  			rec.RecordError(err)
   112  			wr.Logger().Errorf2(err, "action %v failed on %v", actionReference.name, actionReference.target)
   113  		} else {
   114  			wr.Logger().Infof("action %v successful on %v", actionReference.name, actionReference.target)
   115  		}
   116  	}
   117  	cleaner.mu.Unlock()
   118  	cancel()
   119  	return rec.Error()
   120  }
   121  
   122  // RecordChangeTabletTypeAction records a new ChangeTabletTypeAction
   123  // into the specified Cleaner
   124  func RecordChangeTabletTypeAction(cleaner *Cleaner, tabletAlias *topodatapb.TabletAlias, from topodatapb.TabletType, to topodatapb.TabletType) {
   125  	cleaner.Record(ChangeTabletTypeActionName, topoproto.TabletAliasString(tabletAlias), func(ctx context.Context, wr *Wrangler) error {
   126  		ti, err := wr.ts.GetTablet(ctx, tabletAlias)
   127  		if err != nil {
   128  			return err
   129  		}
   130  		if ti.Type != from {
   131  			return fmt.Errorf("tablet %v is not of the right type (got %v expected %v), not changing it to %v", topoproto.TabletAliasString(tabletAlias), ti.Type, from, to)
   132  		}
   133  		if !topo.IsTrivialTypeChange(ti.Type, to) {
   134  			return fmt.Errorf("tablet %v type change %v -> %v is not an allowed transition for ChangeTabletType", topoproto.TabletAliasString(tabletAlias), ti.Type, to)
   135  		}
   136  
   137  		// ask the tablet to make the change
   138  		return wr.ChangeTabletType(ctx, ti.Tablet.Alias, to)
   139  	})
   140  }
   141  
   142  // RecordStartReplicationAction records a new action to restart binlog replication on a server
   143  // into the specified Cleaner
   144  func RecordStartReplicationAction(cleaner *Cleaner, tablet *topodatapb.Tablet) {
   145  	cleaner.Record(StartReplicationActionName, topoproto.TabletAliasString(tablet.Alias), func(ctx context.Context, wr *Wrangler) error {
   146  		return wr.StartReplication(ctx, tablet)
   147  	})
   148  }
   149  
   150  // RecordVReplicationAction records an action to restart binlog replication on a server
   151  // into the specified Cleaner
   152  func RecordVReplicationAction(cleaner *Cleaner, tablet *topodatapb.Tablet, query string) {
   153  	cleaner.Record(VReplicationActionName, topoproto.TabletAliasString(tablet.Alias), func(ctx context.Context, wr *Wrangler) error {
   154  		_, err := wr.TabletManagerClient().VReplicationExec(ctx, tablet, query)
   155  		return err
   156  	})
   157  }