vitess.io/vitess@v0.16.2/go/vt/vtctld/action_repository.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 vtctld 18 19 import ( 20 "context" 21 "net/http" 22 "strings" 23 24 "github.com/spf13/pflag" 25 26 "vitess.io/vitess/go/acl" 27 "vitess.io/vitess/go/vt/logutil" 28 "vitess.io/vitess/go/vt/servenv" 29 "vitess.io/vitess/go/vt/topo" 30 "vitess.io/vitess/go/vt/topo/topoproto" 31 "vitess.io/vitess/go/vt/vttablet/tmclient" 32 "vitess.io/vitess/go/vt/wrangler" 33 34 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 35 ) 36 37 var ( 38 actionTimeout = wrangler.DefaultActionTimeout 39 ) 40 41 // ActionResult contains the result of an action. If Error, the action failed. 42 type ActionResult struct { 43 Name string 44 Parameters string 45 Output string 46 Error bool 47 } 48 49 func (ar *ActionResult) error(text string) { 50 ar.Error = true 51 ar.Output = text 52 } 53 54 func init() { 55 for _, cmd := range []string{"vtcombo", "vtctld"} { 56 servenv.OnParseFor(cmd, registerActionRepositoryFlags) 57 } 58 } 59 60 func registerActionRepositoryFlags(fs *pflag.FlagSet) { 61 fs.DurationVar(&actionTimeout, "action_timeout", actionTimeout, "time to wait for an action before resorting to force") 62 } 63 64 // action{Keyspace,Shard,Tablet}Method is a function that performs 65 // some action on a Topology object. It should return a message for 66 // the user or an empty string in case there's nothing interesting to 67 // be communicated. 68 type actionKeyspaceMethod func(ctx context.Context, wr *wrangler.Wrangler, keyspace string) (output string, err error) 69 70 type actionShardMethod func(ctx context.Context, wr *wrangler.Wrangler, keyspace, shard string) (output string, err error) 71 72 type actionTabletMethod func(ctx context.Context, wr *wrangler.Wrangler, tabletAlias *topodatapb.TabletAlias) (output string, err error) 73 74 type actionTabletRecord struct { 75 role string 76 method actionTabletMethod 77 } 78 79 // ActionRepository is a repository of actions that can be performed 80 // on a {Keyspace,Shard,Tablet}. 81 type ActionRepository struct { 82 keyspaceActions map[string]actionKeyspaceMethod 83 shardActions map[string]actionShardMethod 84 tabletActions map[string]actionTabletRecord 85 ts *topo.Server 86 } 87 88 // NewActionRepository creates and returns a new ActionRepository, 89 // with no actions. 90 func NewActionRepository(ts *topo.Server) *ActionRepository { 91 return &ActionRepository{ 92 keyspaceActions: make(map[string]actionKeyspaceMethod), 93 shardActions: make(map[string]actionShardMethod), 94 tabletActions: make(map[string]actionTabletRecord), 95 ts: ts, 96 } 97 } 98 99 // RegisterKeyspaceAction registers a new action on a keyspace. 100 func (ar *ActionRepository) RegisterKeyspaceAction(name string, method actionKeyspaceMethod) { 101 ar.keyspaceActions[name] = method 102 } 103 104 // RegisterShardAction registers a new action on a shard. 105 func (ar *ActionRepository) RegisterShardAction(name string, method actionShardMethod) { 106 ar.shardActions[name] = method 107 } 108 109 // RegisterTabletAction registers a new action on a tablet. 110 func (ar *ActionRepository) RegisterTabletAction(name, role string, method actionTabletMethod) { 111 ar.tabletActions[name] = actionTabletRecord{ 112 role: role, 113 method: method, 114 } 115 } 116 117 // ApplyKeyspaceAction applies the provided action to the keyspace. 118 func (ar *ActionRepository) ApplyKeyspaceAction(ctx context.Context, actionName, keyspace string) *ActionResult { 119 result := &ActionResult{Name: actionName, Parameters: keyspace} 120 121 action, ok := ar.keyspaceActions[actionName] 122 if !ok { 123 result.error("Unknown keyspace action") 124 return result 125 } 126 127 ctx, cancel := context.WithTimeout(ctx, actionTimeout) 128 wr := wrangler.New(logutil.NewConsoleLogger(), ar.ts, tmclient.NewTabletManagerClient()) 129 output, err := action(ctx, wr, keyspace) 130 cancel() 131 if err != nil { 132 result.error(err.Error()) 133 return result 134 } 135 result.Output = output 136 return result 137 } 138 139 // ApplyShardAction applies the provided action to the shard. 140 func (ar *ActionRepository) ApplyShardAction(ctx context.Context, actionName, keyspace, shard string) *ActionResult { 141 // if the shard name contains a '-', we assume it's the 142 // name for a ranged based shard, so we lower case it. 143 if strings.Contains(shard, "-") { 144 shard = strings.ToLower(shard) 145 } 146 result := &ActionResult{Name: actionName, Parameters: keyspace + "/" + shard} 147 148 action, ok := ar.shardActions[actionName] 149 if !ok { 150 result.error("Unknown shard action") 151 return result 152 } 153 154 ctx, cancel := context.WithTimeout(ctx, actionTimeout) 155 wr := wrangler.New(logutil.NewConsoleLogger(), ar.ts, tmclient.NewTabletManagerClient()) 156 output, err := action(ctx, wr, keyspace, shard) 157 cancel() 158 if err != nil { 159 result.error(err.Error()) 160 return result 161 } 162 result.Output = output 163 return result 164 } 165 166 // ApplyTabletAction applies the provided action to the tablet. 167 func (ar *ActionRepository) ApplyTabletAction(ctx context.Context, actionName string, tabletAlias *topodatapb.TabletAlias, r *http.Request) *ActionResult { 168 result := &ActionResult{ 169 Name: actionName, 170 Parameters: topoproto.TabletAliasString(tabletAlias), 171 } 172 173 action, ok := ar.tabletActions[actionName] 174 if !ok { 175 result.error("Unknown tablet action") 176 return result 177 } 178 179 // check the role 180 if action.role != "" { 181 if err := acl.CheckAccessHTTP(r, action.role); err != nil { 182 result.error("Access denied") 183 return result 184 } 185 } 186 187 // run the action 188 ctx, cancel := context.WithTimeout(ctx, actionTimeout) 189 wr := wrangler.New(logutil.NewConsoleLogger(), ar.ts, tmclient.NewTabletManagerClient()) 190 output, err := action.method(ctx, wr, tabletAlias) 191 cancel() 192 if err != nil { 193 result.error(err.Error()) 194 return result 195 } 196 result.Output = output 197 return result 198 }