go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/internal/rpcquota/module.go (about)

     1  // Copyright 2022 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package rpcquota
    16  
    17  import (
    18  	"context"
    19  	"flag"
    20  
    21  	"go.chromium.org/luci/common/errors"
    22  	luciflag "go.chromium.org/luci/common/flag"
    23  	"go.chromium.org/luci/common/logging"
    24  	"go.chromium.org/luci/config"
    25  	"go.chromium.org/luci/config/server/cfgmodule"
    26  	"go.chromium.org/luci/server/module"
    27  	quota "go.chromium.org/luci/server/quotabeta"
    28  	"go.chromium.org/luci/server/quotabeta/quotaconfig"
    29  	"go.chromium.org/luci/server/quotabeta/quotaconfig/configservice"
    30  	"go.chromium.org/luci/server/redisconn"
    31  )
    32  
    33  var quotaTrackOnlyKey = "go.chromium.org/luci/resultdb/internal/rpcquota:quotaModeKey"
    34  
    35  var ModuleName = module.RegisterName("go.chromium.org/luci/resultdb/internal/rpcquota")
    36  
    37  var _ module.Module = &rpcquotaModule{}
    38  
    39  // rpcquotaModule implements module.Module.
    40  type rpcquotaModule struct {
    41  	opts *ModuleOptions
    42  }
    43  
    44  const (
    45  	// Disabled: Quota is fully off.  No quotaconfig loaded, no quota
    46  	// amounts deducted in redis, no UnaryServerInterceptor registered.
    47  	RPCQuotaModeDisabled = "disabled"
    48  
    49  	// Track only: deduct quota but don't fail requests.  Intended for dark
    50  	// launch.
    51  	RPCQuotaModeTrackOnly = "track-only"
    52  
    53  	// RPC quota fully enabled.  Requests that exceed quota will be failed.
    54  	RPCQuotaModeEnforce = "enforce"
    55  )
    56  
    57  type ModuleOptions struct {
    58  	// RPCQuotaMode determines whether quota is off, active (but not
    59  	// enforced), or active and enforced.
    60  	RPCQuotaMode string
    61  }
    62  
    63  func (o *ModuleOptions) Register(fs *flag.FlagSet) {
    64  	if o.RPCQuotaMode == "" {
    65  		o.RPCQuotaMode = RPCQuotaModeDisabled
    66  	}
    67  	fs.Var(
    68  		luciflag.NewChoice(&o.RPCQuotaMode, RPCQuotaModeDisabled, RPCQuotaModeTrackOnly, RPCQuotaModeEnforce),
    69  		"rpcquota-mode",
    70  		"RPC quota mode. Options are: disabled, track-only, enforce.")
    71  }
    72  
    73  // NewModule returns a module.Module for the rpcquota library initialized from
    74  // the given *ModuleOptions.
    75  func NewModule(opts *ModuleOptions) module.Module {
    76  	return &rpcquotaModule{
    77  		opts: opts,
    78  	}
    79  }
    80  
    81  // NewModuleFromFlags returns a module.Module for the rpcquota library which
    82  // can be initialized from command line flags.
    83  func NewModuleFromFlags() module.Module {
    84  	opts := &ModuleOptions{}
    85  	opts.Register(flag.CommandLine)
    86  	return NewModule(opts)
    87  }
    88  
    89  // Dependencies returns required and optional dependencies for this module.
    90  // Implements module.Module.
    91  func (*rpcquotaModule) Dependencies() []module.Dependency {
    92  	return []module.Dependency{
    93  		module.RequiredDependency(redisconn.ModuleName),
    94  		// NOTE: cfgmodule and rpcquota must be initialized before
    95  		// quota, so it declaring quota.ModuleName as a dependency
    96  		// doesn't work.  Instead rpcquota users must pass rpcquota
    97  		// directly to MainWithModules to ensure it gets initialized
    98  		// first.
    99  		module.RequiredDependency(cfgmodule.ModuleName),
   100  	}
   101  }
   102  
   103  // Name returns the module.Name for this module.
   104  // Implements module.Module.
   105  func (*rpcquotaModule) Name() module.Name {
   106  	return ModuleName
   107  }
   108  
   109  var ErrInvalidOptions = errors.New("Invalid options")
   110  
   111  // Initialize initializes this module by adding a quota.Use implementation to
   112  // the serving context, according to the RPCQuotaMode in the ModuleOptions.
   113  func (m *rpcquotaModule) Initialize(ctx context.Context, host module.Host, opts module.HostOptions) (context.Context, error) {
   114  	switch m.opts.RPCQuotaMode {
   115  	case RPCQuotaModeDisabled:
   116  		// Install a stub config so that luci/server/quota Initialize
   117  		// does not panic, but otherwise there's nothing to do.
   118  		cfg, err := quotaconfig.NewMemory(ctx, nil)
   119  		if err != nil {
   120  			return nil, err
   121  		}
   122  		return quota.Use(ctx, cfg), nil
   123  	case RPCQuotaModeTrackOnly:
   124  		// Leave a flag in the context that will be checked by
   125  		// UpdateUserQuota before returning ErrInsufficientQuota.
   126  		// Note that this is not the common path: we expect that
   127  		//  1. nearly all requests will be within quota
   128  		//  2. this option is only set during the "dark" launch phase.
   129  		ctx = context.WithValue(ctx, &quotaTrackOnlyKey, true)
   130  	case RPCQuotaModeEnforce:
   131  	default:
   132  		return nil, ErrInvalidOptions
   133  	}
   134  	if !opts.Prod {
   135  		logging.Warningf(ctx, "RPC quota not disabled, but not running in prod.")
   136  	}
   137  
   138  	ss, err := config.ServiceSet("luci-resultdb")
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  	cfgsvc := configservice.New(ctx, ss, "rpcquota.cfg")
   143  	if err := cfgsvc.Refresh(ctx); err != nil {
   144  		return nil, err
   145  	}
   146  	ctx = quota.Use(ctx, cfgsvc)
   147  
   148  	host.RegisterUnaryServerInterceptors(quotaCheckInterceptor())
   149  	return ctx, nil
   150  }