golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/cloud/aws_interceptor.go (about)

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package cloud
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  
    11  	"github.com/aws/aws-sdk-go/aws"
    12  	"github.com/aws/aws-sdk-go/aws/request"
    13  	"github.com/aws/aws-sdk-go/service/ec2"
    14  	"golang.org/x/sync/errgroup"
    15  	"golang.org/x/time/rate"
    16  )
    17  
    18  // rateLimiter is an interface mainly used for testing.
    19  type rateLimiter interface {
    20  	Wait(ctx context.Context) (err error)
    21  	WaitN(ctx context.Context, n int) (err error)
    22  }
    23  
    24  // DefaultEC2LimitConfig sets limits defined in
    25  // https://docs.aws.amazon.com/AWSEC2/latest/APIReference/throttling.html
    26  var DefaultEC2LimitConfig = &EC2LimitConfig{
    27  	MutatingRate:                    5,
    28  	MutatingRateBucket:              200,
    29  	NonMutatingRate:                 20,
    30  	NonMutatingRateBucket:           100,
    31  	RunInstanceRate:                 2,
    32  	RunInstanceRateBucket:           5,
    33  	RunInstanceResource:             2,
    34  	RunInstanceResourceBucket:       1000,
    35  	TerminateInstanceResource:       20,
    36  	TerminateInstanceResourceBucket: 1000,
    37  }
    38  
    39  // EC2LimitConfig contains the desired rate and resource rate limit configurations.
    40  type EC2LimitConfig struct {
    41  	// MutatingRate sets the refill rate for mutating requests.
    42  	MutatingRate float64
    43  	// MutatingRateBucket sets the bucket size for mutating requests.
    44  	MutatingRateBucket int
    45  	// NonMutatingRate sets the refill rate for non-mutating requests.
    46  	NonMutatingRate float64
    47  	// NonMutatingRateBucket sets the bucket size for non-mutating requests.
    48  	NonMutatingRateBucket int
    49  	// RunInstanceRate sets the refill rate for run instance rate requests.
    50  	RunInstanceRate float64
    51  	// RunInstanceRateBucket sets the bucket size for run instance rate requests.
    52  	RunInstanceRateBucket int
    53  	// RunInstanceResource sets the refill rate for run instance rate resources.
    54  	RunInstanceResource float64
    55  	// RunInstanceResourceBucket sets the bucket size for run instance rate resources.
    56  	RunInstanceResourceBucket int
    57  	// TerminateInstanceResource sets the refill rate for terminate instance rate resources.
    58  	TerminateInstanceResource float64
    59  	// TerminateInstanceResourceBucket sets the bucket size for terminate instance resources.
    60  	TerminateInstanceResourceBucket int
    61  }
    62  
    63  // WithRateLimiter adds a rate limiter to the AWSClient.
    64  func WithRateLimiter(config *EC2LimitConfig) AWSOpt {
    65  	return func(c *AWSClient) {
    66  		c.ec2Client = &EC2RateLimitInterceptor{
    67  			next:                      c.ec2Client,
    68  			mutatingRate:              rate.NewLimiter(rate.Limit(config.MutatingRate), config.MutatingRateBucket),
    69  			nonMutatingRate:           rate.NewLimiter(rate.Limit(config.NonMutatingRate), config.NonMutatingRateBucket),
    70  			runInstancesRate:          rate.NewLimiter(rate.Limit(config.RunInstanceRate), config.RunInstanceRateBucket),
    71  			runInstancesResource:      rate.NewLimiter(rate.Limit(config.RunInstanceResource), config.RunInstanceResourceBucket),
    72  			terminateInstanceResource: rate.NewLimiter(rate.Limit(config.TerminateInstanceResource), config.TerminateInstanceResourceBucket),
    73  		}
    74  	}
    75  }
    76  
    77  var _ vmClient = (*EC2RateLimitInterceptor)(nil)
    78  
    79  // EC2RateLimitInterceptor implements an interceptor that will rate limit requests
    80  // to the AWS API and allow calls to the appropriate clients to proceed.
    81  type EC2RateLimitInterceptor struct {
    82  	// next is the client called after the rate limiting.
    83  	next vmClient
    84  	// mutatingRate is the rate limiter for mutating requests.
    85  	mutatingRate rateLimiter
    86  	// 	nonMutatingRate is the rate limiter for non-mutating requests.
    87  	nonMutatingRate rateLimiter
    88  	// runInstancesRate is the rate limiter for run instances requests.
    89  	runInstancesRate rateLimiter
    90  	// runInstancesResource is the rate limiter for run instance resources.
    91  	runInstancesResource rateLimiter
    92  	// terminateInstanceResource is the rate limiter for terminate instance resources.
    93  	terminateInstanceResource rateLimiter
    94  }
    95  
    96  // DescribeInstancesPagesWithContext rate limits calls. The rate limiter will return an error if the request exceeds the bucket size, the Context is canceled, or the expected wait time exceeds the Context's Deadline.
    97  func (i *EC2RateLimitInterceptor) DescribeInstancesPagesWithContext(ctx context.Context, in *ec2.DescribeInstancesInput, fn func(*ec2.DescribeInstancesOutput, bool) bool, opts ...request.Option) error {
    98  	if err := i.nonMutatingRate.Wait(ctx); err != nil {
    99  		return err
   100  	}
   101  	return i.next.DescribeInstancesPagesWithContext(ctx, in, fn, opts...)
   102  }
   103  
   104  // DescribeInstancesWithContext rate limits calls. The rate limiter will return an error if the request exceeds the bucket size, the Context is canceled, or the expected wait time exceeds the Context's Deadline.
   105  func (i *EC2RateLimitInterceptor) DescribeInstancesWithContext(ctx context.Context, in *ec2.DescribeInstancesInput, opts ...request.Option) (*ec2.DescribeInstancesOutput, error) {
   106  	if err := i.nonMutatingRate.Wait(ctx); err != nil {
   107  		return nil, err
   108  	}
   109  	return i.next.DescribeInstancesWithContext(ctx, in, opts...)
   110  }
   111  
   112  // RunInstancesWithContext rate limits calls. The rate limiter will return an error if the request exceeds the bucket size, the Context is canceled, or the expected wait time exceeds the Context's Deadline. An error is returned if either the rate or resource limiter returns an error.
   113  func (i *EC2RateLimitInterceptor) RunInstancesWithContext(ctx context.Context, in *ec2.RunInstancesInput, opts ...request.Option) (*ec2.Reservation, error) {
   114  	g := new(errgroup.Group)
   115  	g.Go(func() error {
   116  		return i.runInstancesRate.Wait(ctx)
   117  	})
   118  	g.Go(func() error {
   119  		numInst := aws.Int64Value(in.MaxCount)
   120  		c := int(numInst)
   121  		if int64(c) != numInst {
   122  			return errors.New("unable to convert max count to int")
   123  		}
   124  		return i.runInstancesResource.WaitN(ctx, c)
   125  	})
   126  	if err := g.Wait(); err != nil {
   127  		return nil, err
   128  	}
   129  	return i.next.RunInstancesWithContext(ctx, in, opts...)
   130  }
   131  
   132  // TerminateInstancesWithContext rate limits calls. The rate limiter will return an error if the request exceeds the bucket size, the Context is canceled, or the expected wait time exceeds the Context's Deadline. An error is returned if either the rate or resource limiter returns an error.
   133  func (i *EC2RateLimitInterceptor) TerminateInstancesWithContext(ctx context.Context, in *ec2.TerminateInstancesInput, opts ...request.Option) (*ec2.TerminateInstancesOutput, error) {
   134  	g := new(errgroup.Group)
   135  	g.Go(func() error {
   136  		return i.mutatingRate.Wait(ctx)
   137  	})
   138  	g.Go(func() error {
   139  		c := len(in.InstanceIds)
   140  		return i.terminateInstanceResource.WaitN(ctx, c)
   141  	})
   142  	if err := g.Wait(); err != nil {
   143  		return nil, err
   144  	}
   145  	return i.next.TerminateInstancesWithContext(ctx, in, opts...)
   146  }
   147  
   148  // WaitUntilInstanceRunningWithContext rate limits calls. The rate limiter will return an error if the request exceeds the bucket size, the Context is canceled, or the expected wait time exceeds the Context's Deadline.
   149  func (i *EC2RateLimitInterceptor) WaitUntilInstanceRunningWithContext(ctx context.Context, in *ec2.DescribeInstancesInput, opts ...request.WaiterOption) error {
   150  	if err := i.nonMutatingRate.Wait(ctx); err != nil {
   151  		return err
   152  	}
   153  	return i.next.WaitUntilInstanceRunningWithContext(ctx, in, opts...)
   154  }
   155  
   156  // DescribeInstanceTypesPagesWithContext rate limits calls. The rate limiter will return an error if the request exceeds the bucket size, the Context is canceled, or the expected wait time exceeds the Context's Deadline.
   157  func (i *EC2RateLimitInterceptor) DescribeInstanceTypesPagesWithContext(ctx context.Context, in *ec2.DescribeInstanceTypesInput, fn func(*ec2.DescribeInstanceTypesOutput, bool) bool, opts ...request.Option) error {
   158  	if err := i.nonMutatingRate.Wait(ctx); err != nil {
   159  		return err
   160  	}
   161  	return i.next.DescribeInstanceTypesPagesWithContext(ctx, in, fn, opts...)
   162  }