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 }