Skip to content

Commit f6265a5

Browse files
authored
Merge pull request #135 from atlassian-labs/issue/KUBE-11964/enable-retries
add retry logic
2 parents 336697a + 3b99382 commit f6265a5

9 files changed

Lines changed: 569 additions & 11 deletions

File tree

cmd/manager/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ func main() {
9696
metrics.Register(mgr.GetClient(), log, *namespace)
9797

9898
// Setup the cloud provider
99+
// Uses AWS SDK's built-in retry behavior
99100
cloudProvider, err := builder.BuildCloudProvider(*cloudProviderName, logger)
100101
if err != nil {
101102
log.Error(err, "Unable to build cloud provider")

pkg/cloudprovider/aws/aws.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ func verifyIfErrorOccurred(apiErr error, expectedMessage ...string) (bool, error
5959
}
6060

6161
func verifyIfErrorOccurredWithDefaults(apiErr error, expectedMessage string) (bool, error) {
62-
skip_errs := []string{
62+
skipErrs := []string{
6363
// default errors we wanted to skip
6464
"is not in correct state",
6565
expectedMessage,
6666
}
67-
return verifyIfErrorOccurred(apiErr, skip_errs...)
67+
return verifyIfErrorOccurred(apiErr, skipErrs...)
6868
}
6969

7070
type provider struct {
@@ -134,7 +134,6 @@ func (p *provider) InstancesExist(providerIDs []string) (map[string]interface{},
134134
InstanceIds: aws.StringSlice(instanceIDs),
135135
},
136136
)
137-
138137
if err != nil {
139138
return nil, err
140139
}

pkg/cloudprovider/aws/aws_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ func TestInstance_OutOfDate(t *testing.T) {
438438
MixedInstancesPolicy: &autoscaling.MixedInstancesPolicy{
439439
LaunchTemplate: &autoscaling.LaunchTemplate{
440440
LaunchTemplateSpecification: &autoscaling.LaunchTemplateSpecification{
441-
Version: aws.String(launchTemplateLatestVersion),
441+
Version: aws.String(launchTemplateLatestVersion),
442442
},
443443
},
444444
},
@@ -453,7 +453,7 @@ func TestInstance_OutOfDate(t *testing.T) {
453453
MixedInstancesPolicy: &autoscaling.MixedInstancesPolicy{
454454
LaunchTemplate: &autoscaling.LaunchTemplate{
455455
LaunchTemplateSpecification: &autoscaling.LaunchTemplateSpecification{
456-
Version: aws.String(launchTemplateLatestVersion),
456+
Version: aws.String(launchTemplateLatestVersion),
457457
},
458458
},
459459
},

pkg/cloudprovider/aws/builder.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,27 @@ import (
1313
"github.com/go-logr/logr"
1414
)
1515

16-
// NewCloudProvider returns a new AWS cloud provider
16+
// NewCloudProvider returns a new AWS cloud provider using the AWS SDK's default retry behavior
1717
func NewCloudProvider(logger logr.Logger) (cloudprovider.CloudProvider, error) {
1818
sess, err := session.NewSession()
1919
if err != nil {
2020
return nil, err
2121
}
2222

2323
var creds *credentials.Credentials
24-
config := &aws.Config{Credentials: creds}
24+
25+
// Configure AWS SDK with default retry logic
26+
// The AWS SDK v1 automatically uses client.DefaultRetryer which handles:
27+
// - Exponential backoff
28+
// - Retries for throttling errors
29+
// - Retries for transient network errors
30+
// - Retries for 5xx server errors
31+
config := &aws.Config{
32+
Credentials: creds,
33+
// Use AWS SDK's default retry behavior (3 retries with exponential backoff)
34+
// This is sufficient for most use cases
35+
MaxRetries: aws.Int(3),
36+
}
2537

2638
ec2Service := ec2.New(sess, config)
2739
autoScalingService := autoscaling.New(sess, config)
@@ -33,7 +45,7 @@ func NewCloudProvider(logger logr.Logger) (cloudprovider.CloudProvider, error) {
3345
}
3446

3547
// Log the provider we used
36-
credValue, err := autoScalingService.Client.Config.Credentials.Get()
48+
credValue, err := config.Credentials.Get()
3749
if err != nil {
3850
return nil, err
3951
}

pkg/cloudprovider/builder/builder.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
type builderFunc func(logger logr.Logger) (cloudprovider.CloudProvider, error)
1212

1313
// BuildCloudProvider returns a cloud provider based on the provided name
14+
// Uses the AWS SDK's built-in retry behavior
1415
func BuildCloudProvider(name string, logger logr.Logger) (cloudprovider.CloudProvider, error) {
1516
buildFuncs := map[string]builderFunc{
1617
aws.ProviderName: aws.NewCloudProvider,

pkg/controller/cyclenoderequest/transitioner/checks.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ func (t *CycleNodeRequestTransitioner) makeRequest(httpMethod string, httpClient
139139
return 0, nil, err
140140
}
141141

142-
defer resp.Body.Close()
142+
defer func() { _ = resp.Body.Close() }()
143143

144144
bytes, err := io.ReadAll(resp.Body)
145145
if err != nil {
@@ -242,7 +242,7 @@ func (t *CycleNodeRequestTransitioner) performInitialHealthChecks(kubeNodes map[
242242
// performCyclingHealthChecks before terminating an instance selected for termination. Cycling pauses
243243
// until all health checks pass for the new instance before terminating the old one
244244
func (t *CycleNodeRequestTransitioner) performCyclingHealthChecks(kubeNodes map[string]corev1.Node) (bool, error) {
245-
var allHealthChecksPassed bool = true
245+
allHealthChecksPassed := true
246246

247247
// Find new instsances attached to the nodegroup and perform health checks on them
248248
// before terminating the old ones they are replacing
@@ -376,7 +376,7 @@ func (t *CycleNodeRequestTransitioner) sendPreTerminationTrigger(node v1.CycleNo
376376
// It monitors the progress shutdown progress. Cyclops will wait until this endpoint returns the expected response before
377377
// proceeding to terminate the node.
378378
func (t *CycleNodeRequestTransitioner) performPreTerminationHealthChecks(node v1.CycleNodeRequestNode) (bool, error) {
379-
var allHealthChecksPassed bool = true
379+
allHealthChecksPassed := true
380380
nodeHash := getNodeHash(node)
381381

382382
// Check that the trigger has already been send to the node before performing any health checks
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package transitioner
2+
3+
import (
4+
"net"
5+
"strings"
6+
7+
"github.com/aws/aws-sdk-go/aws/awserr"
8+
)
9+
10+
// isRetryableError determines if an error should trigger a requeue instead of moving to Healing phase
11+
// Retryable errors typically include network timeouts and transient AWS service errors
12+
func isRetryableError(err error) bool {
13+
if err == nil {
14+
return false
15+
}
16+
17+
// Check for network errors (timeouts, connection refused, etc.)
18+
if netErr, ok := err.(net.Error); ok {
19+
if netErr.Timeout() {
20+
return true
21+
}
22+
}
23+
24+
// Check error string for common network patterns
25+
errStr := err.Error()
26+
networkPatterns := []string{
27+
"i/o timeout",
28+
"dial tcp",
29+
"connection reset",
30+
"connection refused",
31+
"connection timeout",
32+
"TLS handshake timeout",
33+
"broken pipe",
34+
"unexpected EOF",
35+
}
36+
37+
for _, pattern := range networkPatterns {
38+
if strings.Contains(errStr, pattern) {
39+
return true
40+
}
41+
}
42+
43+
// Check for AWS SDK transient errors
44+
if awsErr, ok := err.(awserr.Error); ok {
45+
code := awsErr.Code()
46+
// Common transient AWS error codes
47+
transientCodes := map[string]bool{
48+
"Throttling": true,
49+
"ThrottlingException": true,
50+
"TooManyRequestsException": true,
51+
"RequestLimitExceeded": true,
52+
"SlowDown": true,
53+
"ServiceUnavailable": true,
54+
"ServiceUnavailableException": true,
55+
"InternalFailure": true,
56+
"InternalError": true,
57+
"InternalServerError": true,
58+
"RequestTimeout": true,
59+
"RequestTimedOut": true,
60+
}
61+
62+
if transientCodes[code] {
63+
return true
64+
}
65+
}
66+
67+
return false
68+
}

0 commit comments

Comments
 (0)