Skip to content

Commit 81e7ec5

Browse files
mrniranjanNiranjan M.R
and
Niranjan M.R
authored
OCPBUGS-54628: Add retry method if we hit jira rate limit (#1313)
* Add retry method if we hit jira rate limit Jira implemented rate limit due to which tests which should be skipped due to known issues do not get skipped. This PR implements retry with exponential backoff to retry the request if we hit http status code 429(too many requests) Signed-off-by: Niranjan M.R <[email protected]> * use Retry-After header first before trying exponential backoff Signed-off-by: Niranjan M.R <[email protected]> * use http api constants for status code instead of numbers Signed-off-by: Niranjan M.R <[email protected]> * log before we wait for retry time to expire log information about the time need to wait before retrying to fetch bug status Signed-off-by: Niranjan M.R <[email protected]> --------- Signed-off-by: Niranjan M.R <[email protected]> Co-authored-by: Niranjan M.R <[email protected]>
1 parent a63ae25 commit 81e7ec5

File tree

1 file changed

+75
-19
lines changed
  • test/e2e/performanceprofile/functests/utils/jira

1 file changed

+75
-19
lines changed

test/e2e/performanceprofile/functests/utils/jira/status.go

Lines changed: 75 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,21 @@ package jira
22

33
import (
44
"encoding/json"
5+
"fmt"
56
"github.com/pkg/errors"
7+
"math/rand"
68
"net/http"
79
"net/url"
10+
"strconv"
11+
"time"
12+
13+
testlog "github.com/openshift/cluster-node-tuning-operator/test/e2e/performanceprofile/functests/utils/log"
814
)
915

10-
const JIRA_BASE_URL = "https://issues.redhat.com"
16+
const (
17+
JIRA_BASE_URL = "https://issues.redhat.com"
18+
MAX_RETRIES = 3
19+
)
1120

1221
type JiraIssueStatusResponseFieldsStatus = struct {
1322
Name string `json:"name"`
@@ -26,28 +35,75 @@ type JiraIssueStatusResponse = struct {
2635
func RetrieveJiraStatus(key string) (*JiraIssueStatusResponse, error) {
2736
local_params := url.Values{}
2837
local_params.Set("fields", "summary,status")
29-
3038
fullUrl := JIRA_BASE_URL + "/rest/api/2/issue/" + key + "?" + local_params.Encode()
31-
req, err := http.NewRequest("GET", fullUrl, nil)
32-
if err != nil {
33-
return nil, errors.Wrapf(err, "failed to create request for Jira status of %s", key)
39+
jiraResponse := JiraIssueStatusResponse{}
40+
for retryCount := 0; retryCount <= MAX_RETRIES; retryCount++ {
41+
req, err := http.NewRequest("GET", fullUrl, nil)
42+
if err != nil {
43+
return nil, errors.Wrapf(err, "failed to create request for Jira status of %s", key)
44+
}
45+
resp, err := http.DefaultClient.Do(req)
46+
if err != nil {
47+
return nil, errors.Wrapf(err, "failed to create client to get jira status of %s", key)
48+
}
49+
defer resp.Body.Close()
50+
switch resp.StatusCode {
51+
case http.StatusOK:
52+
decoder := json.NewDecoder(resp.Body)
53+
err = decoder.Decode(&jiraResponse)
54+
if err != nil {
55+
return nil, errors.Wrapf(err, "failed to decode jira status of %s", key)
56+
}
57+
case http.StatusTooManyRequests:
58+
if retryAfter := resp.Header.Get("Retry-After"); retryAfter != "" {
59+
retryTime, err := convertRetryAfterTime(retryAfter)
60+
testlog.Infof("Using Retry-After header to retry after %s seconds", retryTime.String())
61+
if err != nil {
62+
return nil, err
63+
}
64+
time.Sleep(retryTime)
65+
} else {
66+
testlog.Info("No Retry-After header found using exponential backoff method to retry")
67+
delay := retryWithBackOff(retryCount)
68+
testlog.Infof("Wait for %s seconds before retry to fetch bug status", delay.String())
69+
time.Sleep(delay)
70+
}
71+
default:
72+
return nil, errors.Errorf("failed to get jira status of %s: %d %s", key, resp.StatusCode, resp.Status)
73+
}
3474
}
35-
ret, err := http.DefaultClient.Do(req)
36-
if err != nil {
37-
return nil, errors.Wrapf(err, "failed to create client to get jira status of %s", key)
38-
}
39-
defer ret.Body.Close()
75+
return &jiraResponse, nil
76+
}
4077

41-
if ret.StatusCode != http.StatusOK {
42-
return nil, errors.Errorf("failed to get jira status of %s: %d %s", key, ret.StatusCode, ret.Status)
43-
}
78+
// retryWithBackOff use exponential backoff retries when we hit rate limit
79+
func retryWithBackOff(attempts int) time.Duration {
80+
// we have a limit of 5 req/sec
81+
initialDelay := 200 * time.Millisecond
82+
maxDelay := 5 * time.Second
83+
delay := initialDelay * (1 << attempts)
84+
85+
// add jitter
86+
jitter := time.Duration(rand.Int63n(int64(delay / 2)))
87+
delay += jitter
4488

45-
response := JiraIssueStatusResponse{}
46-
decoder := json.NewDecoder(ret.Body)
47-
err = decoder.Decode(&response)
48-
if err != nil {
49-
return nil, errors.Wrapf(err, "failed to decode jira status of %s", key)
89+
if delay > maxDelay {
90+
delay = maxDelay
5091
}
92+
return delay
93+
}
5194

52-
return &response, nil
95+
// convertRetryAfterTime converts time in Retry-After in time.Duration
96+
// the format can be in the form of date or seconds
97+
func convertRetryAfterTime(retryAfter string) (time.Duration, error) {
98+
if seconds, err := strconv.Atoi(retryAfter); err == nil {
99+
return time.Duration(seconds) * time.Second, nil
100+
}
101+
if t, err := http.ParseTime(retryAfter); err != nil {
102+
wait := time.Until(t)
103+
if wait > 0 {
104+
return wait, nil
105+
}
106+
return 0, nil
107+
}
108+
return 0, fmt.Errorf("Invalid Retry-After time value %s", retryAfter)
53109
}

0 commit comments

Comments
 (0)