Skip to content

Commit cc09b3a

Browse files
committed
Add retry Error definition
1 parent d758fc3 commit cc09b3a

File tree

3 files changed

+482
-0
lines changed

3 files changed

+482
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
2+
3+
go_library(
4+
name = "go_default_library",
5+
srcs = ["azure_error.go"],
6+
importmap = "k8s.io/kubernetes/vendor/k8s.io/legacy-cloud-providers/azure/retry",
7+
importpath = "k8s.io/legacy-cloud-providers/azure/retry",
8+
visibility = ["//visibility:public"],
9+
deps = ["//vendor/k8s.io/klog:go_default_library"],
10+
)
11+
12+
go_test(
13+
name = "go_default_test",
14+
srcs = ["azure_error_test.go"],
15+
embed = [":go_default_library"],
16+
deps = ["//vendor/github.com/stretchr/testify/assert:go_default_library"],
17+
)
18+
19+
filegroup(
20+
name = "package-srcs",
21+
srcs = glob(["**"]),
22+
tags = ["automanaged"],
23+
visibility = ["//visibility:private"],
24+
)
25+
26+
filegroup(
27+
name = "all-srcs",
28+
srcs = [":package-srcs"],
29+
tags = ["automanaged"],
30+
visibility = ["//visibility:public"],
31+
)
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
// +build !providerless
2+
3+
/*
4+
Copyright 2019 The Kubernetes Authors.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
19+
package retry
20+
21+
import (
22+
"fmt"
23+
"net/http"
24+
"strconv"
25+
"strings"
26+
"time"
27+
28+
"k8s.io/klog"
29+
)
30+
31+
var (
32+
// The function to get current time.
33+
now = time.Now
34+
)
35+
36+
// Error indicates an error returned by Azure APIs.
37+
type Error struct {
38+
// Retriable indicates whether the request is retriable.
39+
Retriable bool
40+
// HTTPStatusCode indicates the response HTTP status code.
41+
HTTPStatusCode int
42+
// RetryAfter indicates the time when the request should retry after throttling.
43+
// A throttled request is retriable.
44+
RetryAfter time.Time
45+
// RetryAfter indicates the raw error from API.
46+
RawError error
47+
}
48+
49+
// Error returns the error.
50+
// Note that Error doesn't implement error interface because (nil *Error) != (nil error).
51+
func (err *Error) Error() error {
52+
if err == nil {
53+
return nil
54+
}
55+
56+
return fmt.Errorf("Retriable: %v, RetryAfter: %s, HTTPStatusCode: %d, RawError: %v",
57+
err.Retriable, err.RetryAfter.String(), err.HTTPStatusCode, err.RawError)
58+
}
59+
60+
// NewError creates a new Error.
61+
func NewError(retriable bool, err error) *Error {
62+
return &Error{
63+
Retriable: retriable,
64+
RawError: err,
65+
}
66+
}
67+
68+
// GetRetriableError gets new retriable Error.
69+
func GetRetriableError(err error) *Error {
70+
return &Error{
71+
Retriable: true,
72+
RawError: err,
73+
}
74+
}
75+
76+
// GetError gets a new Error based on resp and error.
77+
func GetError(resp *http.Response, err error) *Error {
78+
if err == nil && resp == nil {
79+
return nil
80+
}
81+
82+
if err == nil && resp != nil && isSuccessHTTPResponse(resp) {
83+
// HTTP 2xx suggests a successful response
84+
return nil
85+
}
86+
87+
retryAfter := time.Time{}
88+
if retryAfterDuration := getRetryAfter(resp); retryAfterDuration != 0 {
89+
retryAfter = now().Add(retryAfterDuration)
90+
}
91+
rawError := err
92+
if err == nil && resp != nil {
93+
rawError = fmt.Errorf("HTTP response: %v", resp.StatusCode)
94+
}
95+
return &Error{
96+
RawError: rawError,
97+
RetryAfter: retryAfter,
98+
Retriable: shouldRetryHTTPRequest(resp, err),
99+
HTTPStatusCode: getHTTPStatusCode(resp),
100+
}
101+
}
102+
103+
// isSuccessHTTPResponse determines if the response from an HTTP request suggests success
104+
func isSuccessHTTPResponse(resp *http.Response) bool {
105+
if resp == nil {
106+
return false
107+
}
108+
109+
// HTTP 2xx suggests a successful response
110+
if 199 < resp.StatusCode && resp.StatusCode < 300 {
111+
return true
112+
}
113+
114+
return false
115+
}
116+
117+
func getHTTPStatusCode(resp *http.Response) int {
118+
if resp == nil {
119+
return -1
120+
}
121+
122+
return resp.StatusCode
123+
}
124+
125+
// shouldRetryHTTPRequest determines if the request is retriable.
126+
func shouldRetryHTTPRequest(resp *http.Response, err error) bool {
127+
if resp != nil {
128+
// HTTP 412 (StatusPreconditionFailed) means etag mismatch, hence we shouldn't retry.
129+
if resp.StatusCode == http.StatusPreconditionFailed {
130+
return false
131+
}
132+
133+
// HTTP 4xx (except 412) or 5xx suggests we should retry.
134+
if 399 < resp.StatusCode && resp.StatusCode < 600 {
135+
return true
136+
}
137+
}
138+
139+
if err != nil {
140+
return true
141+
}
142+
143+
return false
144+
}
145+
146+
// getRetryAfter gets the retryAfter from http response.
147+
// The value of Retry-After can be either the number of seconds or a date in RFC1123 format.
148+
func getRetryAfter(resp *http.Response) time.Duration {
149+
if resp == nil {
150+
return 0
151+
}
152+
153+
ra := resp.Header.Get("Retry-After")
154+
if ra == "" {
155+
return 0
156+
}
157+
158+
var dur time.Duration
159+
if retryAfter, _ := strconv.Atoi(ra); retryAfter > 0 {
160+
dur = time.Duration(retryAfter) * time.Second
161+
} else if t, err := time.Parse(time.RFC1123, ra); err == nil {
162+
dur = t.Sub(now())
163+
}
164+
return dur
165+
}
166+
167+
// GetStatusNotFoundAndForbiddenIgnoredError gets an error with StatusNotFound and StatusForbidden ignored.
168+
// It is only used in DELETE operations.
169+
func GetStatusNotFoundAndForbiddenIgnoredError(resp *http.Response, err error) *Error {
170+
rerr := GetError(resp, err)
171+
if rerr == nil {
172+
return nil
173+
}
174+
175+
// Returns nil when it is StatusNotFound error.
176+
if rerr.HTTPStatusCode == http.StatusNotFound {
177+
klog.V(3).Infof("Ignoring StatusNotFound error: %v", rerr)
178+
return nil
179+
}
180+
181+
// Returns nil if the status code is StatusForbidden.
182+
// This happens when AuthorizationFailed is reported from Azure API.
183+
if rerr.HTTPStatusCode == http.StatusForbidden {
184+
klog.V(3).Infof("Ignoring StatusForbidden error: %v", rerr)
185+
return nil
186+
}
187+
188+
return rerr
189+
}
190+
191+
// IsErrorRetriable returns true if the error is retriable.
192+
func IsErrorRetriable(err error) bool {
193+
if err == nil {
194+
return false
195+
}
196+
197+
return strings.Contains(err.Error(), "Retriable: true")
198+
}

0 commit comments

Comments
 (0)