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 }