Skip to content

Commit 410101b

Browse files
committed
Added retry for Network errors containing i/o timeout
1 parent ba5a51c commit 410101b

File tree

5 files changed

+144
-24
lines changed

5 files changed

+144
-24
lines changed

internal/globalvar/constants.go

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,30 @@ const (
1313
ResourcePrincipal = "ResourcePrincipal"
1414
RequestHeaderOpcOboToken = "opc-obo-token"
1515
RequestHeaderOpcHostSerial = "opc-host-serial"
16-
DefaultRequestTimeout = 0
17-
DefaultConnectionTimeout = 10 * time.Second
18-
DefaultTLSHandshakeTimeout = 10 * time.Second
19-
DefaultUserAgentProviderName = "Oracle-TerraformProvider"
20-
UnknownTerraformCLIVersion = "unknown"
21-
TestTerraformCLIVersion = "test"
22-
UserAgentFormatter = "Oracle-GoSDK/%s (go/%s; %s/%s; terraform/%s; terraform-cli/%s) %s/%s"
23-
UserAgentProviderNameEnv = "USER_AGENT_PROVIDER_NAME"
24-
UserAgentTerraformNameEnv = "TF_APPEND_USER_AGENT"
25-
UserAgentSDKNameEnv = "OCI_SDK_APPEND_USER_AGENT"
26-
DomainNameOverrideEnv = "domain_name_override"
27-
HasCorrectDomainNameEnv = "has_correct_domain_name"
28-
ClientHostOverridesEnv = "CLIENT_HOST_OVERRIDES"
29-
CustomCertLocationEnv = "custom_cert_location"
30-
AcceptLocalCerts = "accept_local_certs"
31-
JobOCID = "job-ocid"
16+
17+
// HTTPRequestTimeout specifies the maximum duration for completing an HTTP request.
18+
HTTPRequestTimeOut = "HTTP_REQUEST_TIMEOUT"
19+
DefaultRequestTimeout = 0
20+
// DialContextConnectionTimeout defines the timeout for establishing a connection during a network dial operation.
21+
DialContextConnectionTimeout = "DIAL_CONTEXT_CONNECTION_TIMEOUT"
22+
DefaultConnectionTimeout = 10 * time.Second
23+
// TLSHandshakeTimeout indicates the maximum time allowed for the TLS handshake process.
24+
TLSHandshakeTimeout = "TLS_HANDSHAKE_TIMEOUT"
25+
DefaultTLSHandshakeTimeout = 10 * time.Second
26+
27+
DefaultUserAgentProviderName = "Oracle-TerraformProvider"
28+
UnknownTerraformCLIVersion = "unknown"
29+
TestTerraformCLIVersion = "test"
30+
UserAgentFormatter = "Oracle-GoSDK/%s (go/%s; %s/%s; terraform/%s; terraform-cli/%s) %s/%s"
31+
UserAgentProviderNameEnv = "USER_AGENT_PROVIDER_NAME"
32+
UserAgentTerraformNameEnv = "TF_APPEND_USER_AGENT"
33+
UserAgentSDKNameEnv = "OCI_SDK_APPEND_USER_AGENT"
34+
DomainNameOverrideEnv = "domain_name_override"
35+
HasCorrectDomainNameEnv = "has_correct_domain_name"
36+
ClientHostOverridesEnv = "CLIENT_HOST_OVERRIDES"
37+
CustomCertLocationEnv = "custom_cert_location"
38+
AcceptLocalCerts = "accept_local_certs"
39+
JobOCID = "job-ocid"
3240

3341
AuthAttrName = "auth"
3442
TenancyOcidAttrName = "tenancy_ocid"

internal/provider/provider.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -568,19 +568,27 @@ func (p ResourceDataConfigProvider) PrivateRSAKey() (key *rsa.PrivateKey, err er
568568
}
569569
func BuildHttpClient() (httpClient *http.Client) {
570570
httpClient = &http.Client{
571-
Timeout: globalvar.DefaultRequestTimeout,
571+
Timeout: getFromEnvVar(globalvar.HTTPRequestTimeOut, globalvar.DefaultRequestTimeout),
572572
Transport: &http.Transport{
573573
DialContext: (&net.Dialer{
574-
Timeout: globalvar.DefaultConnectionTimeout,
574+
Timeout: getFromEnvVar(globalvar.DialContextConnectionTimeout, globalvar.DefaultConnectionTimeout),
575575
}).DialContext,
576-
TLSHandshakeTimeout: globalvar.DefaultTLSHandshakeTimeout,
576+
TLSHandshakeTimeout: getFromEnvVar(globalvar.TLSHandshakeTimeout, globalvar.DefaultTLSHandshakeTimeout),
577577
TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12},
578578
Proxy: http.ProxyFromEnvironment,
579579
},
580580
}
581581
return
582582
}
583-
583+
func getFromEnvVar(varName string, defaultValue time.Duration) time.Duration {
584+
valueStr := utils.GetEnvSettingWithDefault(varName, fmt.Sprint(defaultValue))
585+
duration, err := time.ParseDuration(valueStr)
586+
if err != nil {
587+
utils.Debugf("ERROR while parsing env variable %s value: %v", varName, err)
588+
return defaultValue
589+
}
590+
return duration
591+
}
584592
func UserAgentFromEnv() string {
585593

586594
userAgentFromEnv, err := schemaMultiEnvDefaultFuncVar([]string{globalvar.UserAgentProviderNameEnv, globalvar.UserAgentSDKNameEnv, globalvar.UserAgentTerraformNameEnv}, globalvar.DefaultUserAgentProviderName)()

internal/provider/provider_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package provider
33
import (
44
"context"
55
"crypto/tls"
6+
"fmt"
67
"io/ioutil"
78
"log"
89
"net/http"
@@ -877,3 +878,44 @@ func TestUnitUserAgentFromEnv(t *testing.T) {
877878
})
878879
}
879880
}
881+
882+
func TestUnitGetFromEnvVar(t *testing.T) {
883+
varName := "TEST_ENV_VAR"
884+
defaultValue := 5 * time.Second
885+
886+
tests := []struct {
887+
name string
888+
envValue string
889+
expectedValue time.Duration
890+
}{
891+
{
892+
name: "Valid duration in environment variable",
893+
envValue: "10s",
894+
expectedValue: 10 * time.Second,
895+
},
896+
{
897+
name: "Invalid duration in environment variable",
898+
envValue: "invalid",
899+
expectedValue: defaultValue, // Should fall back to default value
900+
},
901+
{
902+
name: "Empty environment variable",
903+
envValue: "",
904+
expectedValue: defaultValue, // Should fall back to default value
905+
},
906+
}
907+
908+
for _, tt := range tests {
909+
t.Run(tt.name, func(t *testing.T) {
910+
// Set the environment variable
911+
os.Setenv(varName, tt.envValue)
912+
defer os.Unsetenv(varName) // Clean up after the test
913+
914+
// Call the function
915+
result := getFromEnvVar(varName, defaultValue)
916+
917+
// Assert the result
918+
assert.Equal(t, tt.expectedValue, result, fmt.Sprintf("Expected %v, but got %v", tt.expectedValue, result))
919+
})
920+
}
921+
}

internal/tfresource/retry.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"log"
99
"math/rand"
10+
"net"
1011
"strconv"
1112
"strings"
1213
"time"
@@ -122,7 +123,7 @@ func getExpectedRetryDuration(response oci_common.OCIOperationResponse, disableN
122123
func GetDefaultExpectedRetryDuration(response oci_common.OCIOperationResponse, disableNotFoundRetries bool) time.Duration {
123124
defaultRetryTime := ShortRetryTime
124125

125-
if oci_common.IsNetworkError(response.Error) {
126+
if IsNetworkError(response.Error) {
126127
log.Printf("[DEBUG] Retrying for network error...")
127128
return defaultRetryTime
128129
}
@@ -215,7 +216,7 @@ func getRemainingEventualConsistencyDuration(r oci_common.OCIOperationResponse)
215216

216217
func getIdentityExpectedRetryDuration(response oci_common.OCIOperationResponse, disableNotFoundRetries bool, optionals ...interface{}) time.Duration {
217218
defaultRetryTime := GetDefaultExpectedRetryDuration(response, disableNotFoundRetries)
218-
if oci_common.IsNetworkError(response.Error) {
219+
if IsNetworkError(response.Error) {
219220
return defaultRetryTime
220221
}
221222

@@ -251,7 +252,7 @@ func getIdentityExpectedRetryDuration(response oci_common.OCIOperationResponse,
251252

252253
func getDatabaseExpectedRetryDuration(response oci_common.OCIOperationResponse, disableNotFoundRetries bool, optionals ...interface{}) time.Duration {
253254
defaultRetryTime := GetDefaultExpectedRetryDuration(response, disableNotFoundRetries)
254-
if oci_common.IsNetworkError(response.Error) {
255+
if IsNetworkError(response.Error) {
255256
return defaultRetryTime
256257
}
257258

@@ -283,7 +284,7 @@ func getDatabaseExpectedRetryDuration(response oci_common.OCIOperationResponse,
283284

284285
func getObjectstorageServiceExpectedRetryDuration(response oci_common.OCIOperationResponse, disableNotFoundRetries bool, optionals ...interface{}) time.Duration {
285286
defaultRetryTime := GetDefaultExpectedRetryDuration(response, disableNotFoundRetries)
286-
if oci_common.IsNetworkError(response.Error) {
287+
if IsNetworkError(response.Error) {
287288
return defaultRetryTime
288289
}
289290

@@ -510,3 +511,13 @@ func GetDbHomeRetryDurationFunction(retryTimeout time.Duration) expectedRetryDur
510511
return defaultRetryTime
511512
}
512513
}
514+
515+
// IsNetworkError checks if the given error is a network-related timeout error.
516+
// It returns true if the error is of type *net.OpError and indicates a timeout, or if the error message contains "i/o timeout".
517+
func IsNetworkError(err error) bool {
518+
if opErr, ok := err.(*net.OpError); ok && (opErr.Timeout() || strings.Contains(err.Error(), "i/o timeout")) {
519+
return true
520+
}
521+
// // IsNetworkError validates if an error is a net.Error and check if it's temporary or timeout
522+
return oci_common.IsNetworkError(err)
523+
}

internal/tfresource/retry_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package tfresource
55

66
import (
7+
"errors"
78
"fmt"
89
"net"
910
"net/http"
@@ -841,3 +842,53 @@ func TestUnitDefaultExpectedRetryDuration(t *testing.T) {
841842

842843
}
843844
}
845+
846+
// Unit test for IsNetworkError function.
847+
func TestUnit_IsNetworkError(t *testing.T) {
848+
// Create a timeout error using net package.
849+
timeoutErr := &net.OpError{
850+
Op: "dial",
851+
Net: "tcp",
852+
Err: &net.DNSError{
853+
IsTimeout: true,
854+
},
855+
}
856+
857+
// Create a non-timeout error using net package.
858+
nonTimeoutErr := &net.OpError{
859+
Op: "read",
860+
Net: "tcp",
861+
Err: errors.New("connection reset by peer"),
862+
}
863+
864+
tests := []struct {
865+
name string
866+
inputErr error
867+
expected bool
868+
}{
869+
{
870+
name: "Network timeout error",
871+
inputErr: timeoutErr,
872+
expected: true,
873+
},
874+
{
875+
name: "Non-timeout network error",
876+
inputErr: nonTimeoutErr,
877+
expected: false,
878+
},
879+
{
880+
name: "Nil error",
881+
inputErr: nil,
882+
expected: false,
883+
},
884+
}
885+
886+
for _, tt := range tests {
887+
t.Run(tt.name, func(t *testing.T) {
888+
result := IsNetworkError(tt.inputErr)
889+
if result != tt.expected {
890+
t.Errorf("IsNetworkTimeoutError(%v) = %v; expected %v", tt.inputErr, result, tt.expected)
891+
}
892+
})
893+
}
894+
}

0 commit comments

Comments
 (0)