Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ require (
github.com/aws/aws-sdk-go-v2/config v1.29.14
github.com/aws/aws-sdk-go-v2/service/ecr v1.36.2
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.27.2
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.29.3
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.45.2
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/onsi/ginkgo/v2 v2.23.0
github.com/onsi/gomega v1.36.2
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ github.com/aws/aws-sdk-go-v2/service/ecr v1.36.2 h1:VDQaVwGOokbd3VUbHF+wupiffdrb
github.com/aws/aws-sdk-go-v2/service/ecr v1.36.2/go.mod h1:lvUlMghKYmSxSfv0vU7pdU/8jSY+s0zpG8xXhaGKCw0=
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.27.2 h1:Zru9Iy2JPM5+uRnFnoqeOZzi8JIVIHJ0ua6JdeDHcyg=
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.27.2/go.mod h1:PtQC3XjutCYFCn1+i8+wtpDaXvEK+vXF2gyLIKAmh4A=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.29.3 h1:DpyV8LeDf0y7iDaGZ3h1Y+Nh5IaBOR+xj44vVgEEegY=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.29.3/go.mod h1:H232HdqVlSUoqy0cMJYW1TKjcxvGFGFZ20xQG8fOAPw=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.45.2 h1:vX70Z4lNSr7XsioU0uJq5yvxgI50sB66MvD+V/3buS4=
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.45.2/go.mod h1:xnCC3vFBfOKpU6PcsCKL2ktgBTZfOwTGxj6V8/X3IS4=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM=
Expand Down
2 changes: 1 addition & 1 deletion pkg/controllers/tagging/tagging_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ func (tc *Controller) untagEc2Instance(ctx context.Context, node *taggingControl

var err error
if tc.batchingEnabled {
err = tc.cloud.UntagResourceBatch(context.TODO(), string(instanceID), tc.tags)
err = tc.cloud.UntagResourceBatch(ctx, string(instanceID), tc.tags)
} else {
err = tc.cloud.UntagResource(ctx, string(instanceID), tc.tags)
}
Expand Down
297 changes: 153 additions & 144 deletions pkg/providers/v1/aws.go

Large diffs are not rendered by default.

130 changes: 55 additions & 75 deletions pkg/providers/v1/aws_fakes.go

Large diffs are not rendered by default.

504 changes: 272 additions & 232 deletions pkg/providers/v1/aws_loadbalancer.go

Large diffs are not rendered by default.

330 changes: 162 additions & 168 deletions pkg/providers/v1/aws_loadbalancer_test.go

Large diffs are not rendered by default.

71 changes: 40 additions & 31 deletions pkg/providers/v1/aws_sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ import (
awsConfig "github.com/aws/aws-sdk-go-v2/config"
stscredsv2 "github.com/aws/aws-sdk-go-v2/credentials/stscreds"
"github.com/aws/aws-sdk-go-v2/service/ec2"
elb "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing"
elbv2 "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/elb"
"github.com/aws/aws-sdk-go/service/elbv2"

"github.com/aws/aws-sdk-go/service/kms"

smithymiddleware "github.com/aws/smithy-go/middleware"
Expand Down Expand Up @@ -174,10 +175,9 @@ func (p *awsSDKProvider) Compute(ctx context.Context, regionName string, assumeR
o.Retryer = &customRetryer{
retry.NewStandard(),
}
})
opts = append(opts, func(o *ec2.Options) {
o.EndpointResolverV2 = p.cfg.GetCustomEC2Resolver()
})

ec2Client := ec2.NewFromConfig(cfg, opts...)

ec2 := &awsSdkEC2{
Expand All @@ -186,45 +186,54 @@ func (p *awsSDKProvider) Compute(ctx context.Context, regionName string, assumeR
return ec2, nil
}

func (p *awsSDKProvider) LoadBalancing(regionName string) (ELB, error) {
awsConfig := &aws.Config{
Region: &regionName,
Credentials: p.creds,
func (p *awsSDKProvider) LoadBalancing(ctx context.Context, regionName string, assumeRoleProvider *stscredsv2.AssumeRoleProvider) (ELB, error) {
cfg, err := awsConfig.LoadDefaultConfig(ctx, awsConfig.WithDefaultsMode(awsv2.DefaultsModeInRegion),
awsConfig.WithRegion(regionName),
)
if assumeRoleProvider != nil {
cfg.Credentials = awsv2.NewCredentialsCache(assumeRoleProvider)
}
awsConfig = awsConfig.WithCredentialsChainVerboseErrors(true).
WithEndpointResolver(p.cfg.GetResolver())
sess, err := session.NewSessionWithOptions(session.Options{
Config: *awsConfig,
SharedConfigState: session.SharedConfigEnable,
})
if err != nil {
return nil, fmt.Errorf("unable to initialize AWS session: %v", err)
return nil, fmt.Errorf("unable to initialize AWS config: %v", err)
}
elbClient := elb.New(sess)
p.AddHandlers(regionName, &elbClient.Handlers)

p.AddHandlersV2(ctx, regionName, &cfg)
var opts []func(*elb.Options) = p.cfg.GetELBEndpointOpts(regionName)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: opts := []func(*elb.Options) {... ...} instead of appending one-by-one. each append is a malloc op under the hood

Copy link
Copy Markdown
Contributor Author

@gargipanatula gargipanatula Jun 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see, do you mean something like this:

var opts []func(*ec2.Options) = p.cfg.GetEC2EndpointOpts(regionName)
	opts = append(opts, func(o *ec2.Options) {
		o.Retryer = &customRetryer{
			retry.NewStandard(),
		}
		o.EndpointResolverV2 = p.cfg.GetCustomEC2Resolver()
	})

Couldn't figure out how spread p.cfg.GetELBEndpointOpts(regionName) into a composite literal ([]func(*elb.Options){ ... }), let me know if i'm missing something

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var opts []func(*ec2.Options) = append(
         p.cfg.GetEC2EndpointOpts(regionName),
	 func(o *ec2.Options) {
		o.Retryer = &customRetryer{
			retry.NewStandard(),
		}
		o.EndpointResolverV2 = p.cfg.GetCustomEC2Resolver()
	})

i was thinking about an oneliner like this

opts = append(opts, func(o *elb.Options) {
o.Retryer = &customRetryer{
retry.NewStandard(),
}
o.EndpointResolverV2 = p.cfg.GetCustomELBResolver()
})

elbClient := elb.NewFromConfig(cfg, opts...)

return elbClient, nil
}

func (p *awsSDKProvider) LoadBalancingV2(regionName string) (ELBV2, error) {
awsConfig := &aws.Config{
Region: &regionName,
Credentials: p.creds,
func (p *awsSDKProvider) LoadBalancingV2(ctx context.Context, regionName string, assumeRoleProvider *stscredsv2.AssumeRoleProvider) (ELBV2, error) {
cfg, err := awsConfig.LoadDefaultConfig(ctx, awsConfig.WithDefaultsMode(awsv2.DefaultsModeInRegion),
awsConfig.WithRegion(regionName),
)
if assumeRoleProvider != nil {
cfg.Credentials = awsv2.NewCredentialsCache(assumeRoleProvider)
}
awsConfig = awsConfig.WithCredentialsChainVerboseErrors(true).
WithEndpointResolver(p.cfg.GetResolver())
sess, err := session.NewSessionWithOptions(session.Options{
Config: *awsConfig,
SharedConfigState: session.SharedConfigEnable,
})
if err != nil {
return nil, fmt.Errorf("unable to initialize AWS session: %v", err)
return nil, fmt.Errorf("unable to initialize AWS config: %v", err)
}
elbClient := elbv2.New(sess)

p.AddHandlers(regionName, &elbClient.Handlers)
p.AddHandlersV2(ctx, regionName, &cfg)
var opts []func(*elbv2.Options) = p.cfg.GetELBV2EndpointOpts(regionName)
opts = append(opts, func(o *elbv2.Options) {
o.Retryer = &customRetryer{
retry.NewStandard(),
}
o.EndpointResolverV2 = p.cfg.GetCustomELBV2Resolver()
})

elbv2Client := elbv2.NewFromConfig(cfg, opts...)

return elbClient, nil
return elbv2Client, nil
}

func (p *awsSDKProvider) Metadata() (config.EC2Metadata, error) {
Expand Down
129 changes: 119 additions & 10 deletions pkg/providers/v1/aws_sdk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,23 @@ import (
"testing"

"github.com/aws/aws-sdk-go-v2/service/ec2"
elb "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing"
elbv2 "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2"

"github.com/stretchr/testify/assert"
"k8s.io/cloud-provider-aws/pkg/providers/v1/config"
)

// Given an override, a custom endpoint should be used when making API requests
func TestComputeEndpointOverride(t *testing.T) {
func TestClientsEndpointOverride(t *testing.T) {
usedCustomEndpoint := false
// Dummy server that sets usedCustomEndpoint when called
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
usedCustomEndpoint = true
}))

// Pass in the test server URL through a CloudConfig, which is used by each client's custom endpoint
// resolver to override the URL for a request (see EC2Resolver.ResolveEndpoint for an example)
cfgWithServiceOverride := config.CloudConfig{
ServiceOverride: map[string]*struct {
Service string
Expand All @@ -29,7 +35,21 @@ func TestComputeEndpointOverride(t *testing.T) {
SigningName string
}{
"1": {
Service: "EC2",
Service: ec2.ServiceID,
Region: "us-west-2",
URL: testServer.URL,
SigningRegion: "signingRegion",
SigningName: "signingName",
},
"2": {
Service: elb.ServiceID,
Region: "us-west-2",
URL: testServer.URL,
SigningRegion: "signingRegion",
SigningName: "signingName",
},
"3": {
Service: elbv2.ServiceID,
Region: "us-west-2",
URL: testServer.URL,
SigningRegion: "signingRegion",
Expand All @@ -42,17 +62,35 @@ func TestComputeEndpointOverride(t *testing.T) {
regionDelayers: make(map[string]*CrossRequestRetryDelay),
}

// EC2 Client
ec2Client, err := mockProvider.Compute(context.TODO(), "us-west-2", nil)
if err != nil {
t.Errorf("error creating client, %v", err)
}
_, err = ec2Client.DescribeInstances(context.TODO(), &ec2.DescribeInstancesInput{})
assert.True(t, usedCustomEndpoint == true, "custom endpoint was not used for EC2 Client")

usedCustomEndpoint = false // reset boolean flag for next request
elbClient, err := mockProvider.LoadBalancing(context.TODO(), "us-west-2", nil)
if err != nil {
t.Errorf("error creating client, %v", err)
}
_, err = elbClient.DescribeLoadBalancers(context.TODO(), &elb.DescribeLoadBalancersInput{})
assert.True(t, usedCustomEndpoint == true, "custom endpoint was not used for ELB Client")

usedCustomEndpoint = false
elbv2Client, err := mockProvider.LoadBalancingV2(context.TODO(), "us-west-2", nil)
if err != nil {
t.Errorf("error creating client, %v", err)
}
_, err = elbv2Client.DescribeLoadBalancers(context.TODO(), &elbv2.DescribeLoadBalancersInput{})
assert.True(t, usedCustomEndpoint == true, "custom endpoint was not used for ELBV2 Client")
}

// When a nonRetryableError is thrown, an API request should not be retried
func TestComputeNoRetry(t *testing.T) {
// Test whether SDK clients refrain from retrying an API request when given a nonRetryableError.
func TestClientsNoRetry(t *testing.T) {
attemptCount := 0
// Dummy server that counts attempts and returns a nonRetryableError
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
attemptCount++
w.Header().Set("Content-Type", "text/xml")
Expand All @@ -74,6 +112,7 @@ func TestComputeNoRetry(t *testing.T) {
}))
defer testServer.Close()

// Override service endpoints with dummy server URL
cfgWithServiceOverride := config.CloudConfig{
ServiceOverride: map[string]*struct {
Service string
Expand All @@ -84,7 +123,21 @@ func TestComputeNoRetry(t *testing.T) {
SigningName string
}{
"1": {
Service: "EC2",
Service: ec2.ServiceID,
Region: "us-west-2",
URL: testServer.URL,
SigningRegion: "signingRegion",
SigningName: "signingName",
},
"2": {
Service: elb.ServiceID,
Region: "us-west-2",
URL: testServer.URL,
SigningRegion: "signingRegion",
SigningName: "signingName",
},
"3": {
Service: elbv2.ServiceID,
Region: "us-west-2",
URL: testServer.URL,
SigningRegion: "signingRegion",
Expand All @@ -97,23 +150,45 @@ func TestComputeNoRetry(t *testing.T) {
regionDelayers: make(map[string]*CrossRequestRetryDelay),
}

// EC2 Client
ec2Client, err := mockProvider.Compute(context.TODO(), "us-west-2", nil)
if err != nil {
t.Errorf("error creating client, %v", err)
}
_, err = ec2Client.DescribeInstances(context.TODO(), &ec2.DescribeInstancesInput{})
assert.True(t, attemptCount == 1, fmt.Sprintf("expected an attempt count of 1, got %d", attemptCount))
// Ensure that only 1 attempt was made, signifying no retries
assert.True(t, attemptCount == 1, fmt.Sprintf("expected an attempt count of 1 for EC2 client, got %d", attemptCount))

// ELB Client
attemptCount = 0 // reset attempt count for next request
elbClient, err := mockProvider.LoadBalancing(context.TODO(), "us-west-2", nil)
if err != nil {
t.Errorf("error creating client, %v", err)
}
_, err = elbClient.DescribeLoadBalancers(context.TODO(), &elb.DescribeLoadBalancersInput{})
assert.True(t, attemptCount == 1, fmt.Sprintf("expected an attempt count of 1 for ELB client, got %d", attemptCount))

// ELBV2 Client
attemptCount = 0
elbv2Client, err := mockProvider.LoadBalancingV2(context.TODO(), "us-west-2", nil)
if err != nil {
t.Errorf("error creating client, %v", err)
}
_, err = elbv2Client.DescribeLoadBalancers(context.TODO(), &elbv2.DescribeLoadBalancersInput{})
assert.True(t, attemptCount == 1, fmt.Sprintf("expected an attempt count of 1 for ELBV2 client, got %d", attemptCount))
}

// When a retryable error is thrown, an API request should be retried
func TestComputeWithRetry(t *testing.T) {
// Test whether SDK clients retry an API request when given a retryable error code.
func TestClientsWithRetry(t *testing.T) {
attemptCount := 0
// Dummy server that counts attempts and returns a retryable error
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
attemptCount++
// 500 status codes are retried by SDK (see https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/aws/retry)
http.Error(w, "RequestTimeout", 500)
}))

// Override service endpoints with dummy server URL
cfgWithServiceOverride := config.CloudConfig{
ServiceOverride: map[string]*struct {
Service string
Expand All @@ -124,7 +199,21 @@ func TestComputeWithRetry(t *testing.T) {
SigningName string
}{
"1": {
Service: "EC2",
Service: ec2.ServiceID,
Region: "us-west-2",
URL: testServer.URL,
SigningRegion: "signingRegion",
SigningName: "signingName",
},
"2": {
Service: elb.ServiceID,
Region: "us-west-2",
URL: testServer.URL,
SigningRegion: "signingRegion",
SigningName: "signingName",
},
"3": {
Service: elbv2.ServiceID,
Region: "us-west-2",
URL: testServer.URL,
SigningRegion: "signingRegion",
Expand All @@ -137,10 +226,30 @@ func TestComputeWithRetry(t *testing.T) {
regionDelayers: make(map[string]*CrossRequestRetryDelay),
}

// EC2 Client
ec2Client, err := mockProvider.Compute(context.TODO(), "us-west-2", nil)
if err != nil {
t.Errorf("error creating client, %v", err)
}
_, err = ec2Client.DescribeInstances(context.TODO(), &ec2.DescribeInstancesInput{})
assert.True(t, attemptCount > 1, fmt.Sprintf("expected an attempt count >1, got %d", attemptCount))
// Ensure that more than 1 attempt was made, signifying retries
assert.True(t, attemptCount > 1, fmt.Sprintf("expected an attempt count of >1 for EC2 client, got %d", attemptCount))

// ELB Client
attemptCount = 0 // Reset the attempt count before the next request
elbClient, err := mockProvider.LoadBalancing(context.TODO(), "us-west-2", nil)
if err != nil {
t.Errorf("error creating client, %v", err)
}
_, err = elbClient.DescribeLoadBalancers(context.TODO(), &elb.DescribeLoadBalancersInput{})
assert.True(t, attemptCount > 1, fmt.Sprintf("expected an attempt count of >1 for ELB client, got %d", attemptCount))

// ELBV2 Client
attemptCount = 0
elbv2Client, err := mockProvider.LoadBalancingV2(context.TODO(), "us-west-2", nil)
if err != nil {
t.Errorf("error creating client, %v", err)
}
_, err = elbv2Client.DescribeLoadBalancers(context.TODO(), &elbv2.DescribeLoadBalancersInput{})
assert.True(t, attemptCount > 1, fmt.Sprintf("expected an attempt count of >1 for ELB client, got %d", attemptCount))
}
Loading