-
Notifications
You must be signed in to change notification settings - Fork 82
/
errors.go
291 lines (237 loc) · 10.7 KB
/
errors.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
// Copyright (c) 2016, 2018, 2022, Oracle and/or its affiliates. All rights reserved.
// This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
package common
import (
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"strings"
"github.com/sony/gobreaker"
)
// ServiceError models all potential errors generated the service call
type ServiceError interface {
// The http status code of the error
GetHTTPStatusCode() int
// The human-readable error string as sent by the service
GetMessage() string
// A short error code that defines the error, meant for programmatic parsing.
// See https://docs.cloud.oracle.com/Content/API/References/apierrors.htm
GetCode() string
// Unique Oracle-assigned identifier for the request.
// If you need to contact Oracle about a particular request, please provide the request ID.
GetOpcRequestID() string
}
// ServiceErrorRichInfo models all potential errors generated the service call and contains rich info for debugging purpose
type ServiceErrorRichInfo interface {
ServiceError
// The service this service call is sending to
GetTargetService() string
// The API name this service call is sending to
GetOperationName() string
// The timestamp when this request is made
GetTimestamp() SDKTime
// The endpoint and the Http method of this service call
GetRequestTarget() string
// The client version, in this case the oci go sdk version
GetClientVersion() string
// The API reference doc link for this API, optional and maybe empty
GetOperationReferenceLink() string
// Troubleshooting doc link
GetErrorTroubleshootingLink() string
}
// ServiceErrorLocalizationMessage models all potential errors generated the service call and has localized error message info
type ServiceErrorLocalizationMessage interface {
ServiceErrorRichInfo
// The original error message string as sent by the service
GetOriginalMessage() string
// The values to be substituted into the originalMessageTemplate, expressed as a string-to-string map.
GetMessageArgument() map[string]string
// Template in ICU MessageFormat for the human-readable error string in English, but without the values replaced
GetOriginalMessageTemplate() string
}
type servicefailure struct {
StatusCode int
Code string `json:"code,omitempty"`
Message string `json:"message,omitempty"`
OriginalMessage string `json:"originalMessage"`
OriginalMessageTemplate string `json:"originalMessageTemplate"`
MessageArgument map[string]string `json:"messageArguments"`
OpcRequestID string `json:"opc-request-id"`
// debugging information
TargetService string `json:"target-service"`
OperationName string `json:"operation-name"`
Timestamp SDKTime `json:"timestamp"`
RequestTarget string `json:"request-target"`
ClientVersion string `json:"client-version"`
// troubleshooting guidance
OperationReferenceLink string `json:"operation-reference-link"`
ErrorTroubleshootingLink string `json:"error-troubleshooting-link"`
}
func newServiceFailureFromResponse(response *http.Response) error {
var err error
var timestamp SDKTime
t, err := tryParsingTimeWithValidFormatsForHeaders([]byte(response.Header.Get("Date")), "Date")
if err != nil {
timestamp = *now()
} else {
timestamp = sdkTimeFromTime(t)
}
se := servicefailure{
StatusCode: response.StatusCode,
Code: "BadErrorResponse",
OpcRequestID: response.Header.Get("opc-request-id"),
Timestamp: timestamp,
ClientVersion: defaultSDKMarker + "/" + Version(),
RequestTarget: fmt.Sprintf("%s %s", response.Request.Method, response.Request.URL),
}
//If there is an error consume the body, entirely
body, err := ioutil.ReadAll(response.Body)
if err != nil {
se.Message = fmt.Sprintf("The body of the response was not readable, due to :%s", err.Error())
return se
}
err = json.Unmarshal(body, &se)
if err != nil {
Debugf("Error response could not be parsed due to: %s", err.Error())
se.Message = fmt.Sprintf("Failed to parse json from response body due to: %s. With response body %s.", err.Error(), string(body[:]))
return se
}
return se
}
// PostProcessServiceError process the service error after an error is raised and complete it with extra information
func PostProcessServiceError(err error, service string, method string, apiReferenceLink string) error {
var serviceFailure servicefailure
if _, ok := err.(servicefailure); !ok {
return err
}
serviceFailure = err.(servicefailure)
serviceFailure.OperationName = method
serviceFailure.TargetService = service
serviceFailure.ErrorTroubleshootingLink = fmt.Sprintf("https://docs.oracle.com/iaas/Content/API/References/apierrors.htm#apierrors_%v__%v_%s", serviceFailure.StatusCode, serviceFailure.StatusCode, strings.ToLower(serviceFailure.Code))
serviceFailure.OperationReferenceLink = apiReferenceLink
return serviceFailure
}
func (se servicefailure) Error() string {
return fmt.Sprintf(`Error returned by %s Service. Http Status Code: %d. Error Code: %s. Opc request id: %s. Message: %s
Operation Name: %s
Timestamp: %s
Client Version: %s
Request Endpoint: %s
Troubleshooting Tips: See %s for more information about resolving this error.%s
To get more info on the failing request, you can set OCI_GO_SDK_DEBUG env var to info or higher level to log the request/response details.
If you are unable to resolve this %s issue, please contact Oracle support and provide them this full error message.`,
se.TargetService, se.StatusCode, se.Code, se.OpcRequestID, se.Message, se.OperationName, se.Timestamp, se.ClientVersion, se.RequestTarget, se.ErrorTroubleshootingLink, se.getOperationReferenceMessage(), se.TargetService)
}
func (se servicefailure) getOperationReferenceMessage() string {
if se.OperationReferenceLink == "" {
return ""
}
return fmt.Sprintf("\nAlso see %s for details on this operation's requirements.", se.OperationReferenceLink)
}
func (se servicefailure) GetHTTPStatusCode() int {
return se.StatusCode
}
func (se servicefailure) GetMessage() string {
return se.Message
}
func (se servicefailure) GetOriginalMessage() string {
return se.OriginalMessage
}
func (se servicefailure) GetOriginalMessageTemplate() string {
return se.OriginalMessageTemplate
}
func (se servicefailure) GetMessageArgument() map[string]string {
return se.MessageArgument
}
func (se servicefailure) GetCode() string {
return se.Code
}
func (se servicefailure) GetOpcRequestID() string {
return se.OpcRequestID
}
func (se servicefailure) GetTargetService() string {
return se.TargetService
}
func (se servicefailure) GetOperationName() string {
return se.OperationName
}
func (se servicefailure) GetTimestamp() SDKTime {
return se.Timestamp
}
func (se servicefailure) GetRequestTarget() string {
return se.RequestTarget
}
func (se servicefailure) GetClientVersion() string {
return se.ClientVersion
}
func (se servicefailure) GetOperationReferenceLink() string {
return se.OperationReferenceLink
}
func (se servicefailure) GetErrorTroubleshootingLink() string {
return se.ErrorTroubleshootingLink
}
// IsServiceError returns false if the error is not service side, otherwise true
// additionally it returns an interface representing the ServiceError
func IsServiceError(err error) (failure ServiceError, ok bool) {
failure, ok = err.(ServiceError)
return
}
// IsServiceErrorRichInfo returns false if the error is not service side or is not containing rich info, otherwise true
// additionally it returns an interface representing the ServiceErrorRichInfo
func IsServiceErrorRichInfo(err error) (failure ServiceErrorRichInfo, ok bool) {
failure, ok = err.(ServiceErrorRichInfo)
return
}
// IsServiceErrorLocalizationMessage returns false if the error is not service side, otherwise true
// additionally it returns an interface representing the ServiceErrorOriginalMessage
func IsServiceErrorLocalizationMessage(err error) (failure ServiceErrorLocalizationMessage, ok bool) {
failure, ok = err.(ServiceErrorLocalizationMessage)
return
}
type deadlineExceededByBackoffError struct{}
func (deadlineExceededByBackoffError) Error() string {
return "now() + computed backoff duration exceeds request deadline"
}
// DeadlineExceededByBackoff is the error returned by Call() when GetNextDuration() returns a time.Duration that would
// force the user to wait past the request deadline before re-issuing a request. This enables us to exit early, since
// we cannot succeed based on the configured retry policy.
var DeadlineExceededByBackoff error = deadlineExceededByBackoffError{}
// NonSeekableRequestRetryFailure is the error returned when the request is with binary request body, and is configured
// retry, but the request body is not retryable
type NonSeekableRequestRetryFailure struct {
err error
}
func (ne NonSeekableRequestRetryFailure) Error() string {
if ne.err == nil {
return fmt.Sprintf("Unable to perform Retry on this request body type, which did not implement seek() interface")
}
return fmt.Sprintf("%s. Unable to perform Retry on this request body type, which did not implement seek() interface", ne.err.Error())
}
// IsNetworkError validates if an error is a net.Error and check if it's temporary or timeout
func IsNetworkError(err error) bool {
if r, ok := err.(net.Error); ok && (r.Temporary() || r.Timeout()) {
return true
}
return false
}
// IsCircuitBreakerError validates if an error's text is Open state ErrOpenState or HalfOpen state ErrTooManyRequests
func IsCircuitBreakerError(err error) bool {
if err.Error() == gobreaker.ErrOpenState.Error() || err.Error() == gobreaker.ErrTooManyRequests.Error() {
return true
}
return false
}
func getCircuitBreakerError(request *http.Request, err error, cbr *OciCircuitBreaker) error {
cbErr := fmt.Errorf("%s, so this request was not sent to the %s service.\n\n The circuit breaker was opened because the %s service failed too many times recently. "+
"Because the circuit breaker has been opened, requests within a %.2f second window of when the circuit breaker opened will not be sent to the %s service.\n\n"+
"URL which circuit breaker prevented request to - %s \n Circuit Breaker Info \n Name - %s \n State - %s \n\n Errors from %s service which opened the circuit breaker:\n\n%s \n",
err, cbr.Cbst.serviceName, cbr.Cbst.serviceName, cbr.Cbst.openStateWindow.Seconds(), cbr.Cbst.serviceName, request.URL.Host+request.URL.Path, cbr.Cbst.name, cbr.Cb.State().String(), cbr.Cbst.serviceName, cbr.GetHistory())
return cbErr
}
// StatErrCode is a type which wraps error's statusCode and errorCode from service end
type StatErrCode struct {
statusCode int
errorCode string
}