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  }