Skip to content

Commit 80e896c

Browse files
authored
Add retry functionality for Webhook endpoints (#50)
### WHY are these changes introduced? Underlying handling of webhooks relies on RPC request. Follow similar pattern as other endpoints and use retry functionality. ### WHAT is this pull request doing? Adds retry functionality to CRUD endpoints. ### HOW can this pull request be tested? Test together with #268
1 parent 3ca2d02 commit 80e896c

File tree

1 file changed

+168
-39
lines changed

1 file changed

+168
-39
lines changed

api/webhook.go

Lines changed: 168 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,81 +4,210 @@ import (
44
"fmt"
55
"log"
66
"strconv"
7+
"strings"
8+
"time"
79
)
810

911
// CreateWebhook - create a webhook for a vhost and a specific qeueu
10-
func (api *API) CreateWebhook(instanceID int, params map[string]interface{}) (map[string]interface{}, error) {
11-
data := make(map[string]interface{})
12-
failed := make(map[string]interface{})
13-
log.Printf("[DEBUG] go-api::webhook::create params: %v", params)
14-
path := fmt.Sprintf("/api/instances/%d/webhooks", instanceID)
12+
func (api *API) CreateWebhook(instanceID int, params map[string]interface{},
13+
sleep, timeout int) (map[string]interface{}, error) {
14+
15+
return api.createWebhookWithRetry(instanceID, params, 1, sleep, timeout)
16+
}
17+
18+
// createWebhookWithRetry: create webhook with retry if backend is busy.
19+
func (api *API) createWebhookWithRetry(instanceID int, params map[string]interface{},
20+
attempt, sleep, timeout int) (map[string]interface{}, error) {
21+
22+
var (
23+
data = make(map[string]interface{})
24+
failed map[string]interface{}
25+
path = fmt.Sprintf("/api/instances/%d/webhooks", instanceID)
26+
)
27+
28+
log.Printf("[DEBUG] go-api::webhook#create path: %s, params: %v", path, params)
1529
response, err := api.sling.New().Post(path).BodyJSON(params).Receive(&data, &failed)
16-
log.Printf("[DEBUG] go-api::webhook::create response data: %v", data)
30+
log.Printf("[DEBUG] go-api::webhook#create response data: %v", data)
1731

1832
if err != nil {
1933
return nil, err
20-
}
21-
if response.StatusCode != 201 {
22-
return nil, fmt.Errorf(fmt.Sprintf("CreateWebhook failed, status: %v, message: %s", response.StatusCode, failed))
34+
} else if attempt*sleep > timeout {
35+
return nil, fmt.Errorf("create webhook reached timeout of %d seconds", timeout)
2336
}
2437

25-
if v, ok := data["id"]; ok {
26-
data["id"] = strconv.FormatFloat(v.(float64), 'f', 0, 64)
27-
} else {
28-
msg := fmt.Sprintf("go-api::webhook::create Invalid webhook identifier: %v", data["id"])
29-
log.Printf("[ERROR] %s", msg)
30-
return nil, fmt.Errorf(msg)
38+
switch response.StatusCode {
39+
case 201:
40+
if v, ok := data["id"]; ok {
41+
data["id"] = strconv.FormatFloat(v.(float64), 'f', 0, 64)
42+
} else {
43+
msg := fmt.Sprintf("go-api::webhook#create Invalid webhook identifier: %v", data["id"])
44+
log.Printf("[ERROR] %s", msg)
45+
return nil, fmt.Errorf(msg)
46+
}
47+
return data, nil
48+
case 400:
49+
if strings.Compare(failed["error"].(string), "Timeout talking to backend") == 0 {
50+
log.Printf("[INFO] go-api::webhook#create Timeout talking to backend "+
51+
"attempt: %d, until timeout: %d", attempt, (timeout - (attempt * sleep)))
52+
attempt++
53+
time.Sleep(time.Duration(sleep) * time.Second)
54+
return api.createWebhookWithRetry(instanceID, params, attempt, sleep, timeout)
55+
}
56+
return nil, fmt.Errorf("create webhook failed, status: %v, message: %s", 400, failed)
57+
default:
58+
return nil,
59+
fmt.Errorf("create webhook with retry failed, status: %v, message: %s",
60+
response.StatusCode, failed)
3161
}
32-
33-
return data, err
3462
}
3563

3664
// ReadWebhook - retrieves a specific webhook for an instance
37-
func (api *API) ReadWebhook(instanceID int, webhookID string) (map[string]interface{}, error) {
38-
data := make(map[string]interface{})
39-
failed := make(map[string]interface{})
40-
log.Printf("[DEBUG] go-api::webhook::read instance ID: %d, webhookID: %s", instanceID, webhookID)
65+
func (api *API) ReadWebhook(instanceID int, webhookID string, sleep, timeout int) (
66+
map[string]interface{}, error) {
67+
4168
path := fmt.Sprintf("/api/instances/%d/webhooks/%s", instanceID, webhookID)
42-
response, err := api.sling.New().Path(path).Receive(&data, &failed)
69+
return api.readWebhookWithRetry(path, 1, sleep, timeout)
70+
}
71+
72+
// readWebhookWithRetry: read webhook with retry if backend is busy.
73+
func (api *API) readWebhookWithRetry(path string, attempt, sleep, timeout int) (
74+
map[string]interface{}, error) {
75+
76+
var (
77+
data map[string]interface{}
78+
failed map[string]interface{}
79+
)
80+
81+
log.Printf("[DEBUG] go-api::webhook#read path: %s", path)
82+
response, err := api.sling.New().Get(path).Receive(&data, &failed)
83+
log.Printf("[DEBUG] go-api::webhook#read response data: %v", data)
4384

4485
if err != nil {
4586
return nil, err
46-
}
47-
if response.StatusCode != 200 {
48-
return nil, fmt.Errorf("ReadWebhook failed, status: %v, message: %s", response.StatusCode, failed)
87+
} else if attempt*sleep > timeout {
88+
return nil, fmt.Errorf("read webhook reached timeout of %d seconds", timeout)
4989
}
5090

51-
return data, err
91+
switch response.StatusCode {
92+
case 200:
93+
return data, nil
94+
case 400:
95+
if strings.Compare(failed["error"].(string), "Timeout talking to backend") == 0 {
96+
log.Printf("[INFO] go-api::webhook#read Timeout talking to backend "+
97+
"attempt: %d, until timeout: %d", attempt, (timeout - (attempt * sleep)))
98+
attempt++
99+
time.Sleep(time.Duration(sleep) * time.Second)
100+
return api.readWebhookWithRetry(path, attempt, sleep, timeout)
101+
}
102+
return nil, fmt.Errorf("read webhook failed, status: %v, message: %s", 400, failed)
103+
default:
104+
return nil, fmt.Errorf("read webhook with retry failed, status: %v, message: %s",
105+
response.StatusCode, failed)
106+
}
52107
}
53108

54-
// ReadWebhooks - retrieves all webhooks for an instance.
55-
func (api *API) ReadWebhooks(instanceID int) (map[string]interface{}, error) {
56-
data := make(map[string]interface{})
57-
failed := make(map[string]interface{})
58-
log.Printf("[DEBUG] go-api::webhook::read instance ID: %d", instanceID)
59-
path := fmt.Sprintf("/api/instances/%d/webhooks", instanceID)
109+
// ListWebhooks - list all webhooks for an instance.
110+
func (api *API) ListWebhooks(instanceID int) (map[string]interface{}, error) {
111+
var (
112+
data map[string]interface{}
113+
failed map[string]interface{}
114+
path = fmt.Sprintf("/api/instances/%d/webhooks", instanceID)
115+
)
116+
117+
log.Printf("[DEBUG] go-api::webhook#list path: %s", path)
60118
response, err := api.sling.New().Path(path).Receive(&data, &failed)
61119

62120
if err != nil {
63121
return nil, err
64122
}
65123
if response.StatusCode != 200 {
66-
return nil, fmt.Errorf("ReadWebhooks failed, status: %v, message: %s", response.StatusCode, failed)
124+
return nil, fmt.Errorf("list webhooks failed, status: %v, message: %s",
125+
response.StatusCode, failed)
67126
}
68127

69128
return data, err
70129
}
71130

131+
// UpdateWebhook - updates a specific webhook for an instance
132+
func (api *API) UpdateWebhook(instanceID int, webhookID string, params map[string]interface{},
133+
sleep, timeout int) error {
134+
135+
path := fmt.Sprintf("/api/instances/%d/webhooks/%s", instanceID, webhookID)
136+
return api.updateWebhookWithRetry(path, params, 1, sleep, timeout)
137+
}
138+
139+
// updateWebhookWithRetry: update webhook with retry if backend is busy.
140+
func (api *API) updateWebhookWithRetry(path string, params map[string]interface{},
141+
attempt, sleep, timeout int) error {
142+
143+
var (
144+
data = make(map[string]interface{})
145+
failed map[string]interface{}
146+
)
147+
148+
log.Printf("[DEBUG] go-api::webhook#update path: %s, params: %v", path, params)
149+
response, err := api.sling.New().Put(path).BodyJSON(params).Receive(&data, &failed)
150+
log.Printf("[DEBUG] go-api::webhook#update response data: %v", data)
151+
152+
if err != nil {
153+
return err
154+
} else if attempt*sleep > timeout {
155+
return fmt.Errorf("update webhook reached timeout of %d seconds", timeout)
156+
}
157+
158+
switch response.StatusCode {
159+
case 201:
160+
return nil
161+
case 400:
162+
if strings.Compare(failed["error"].(string), "Timeout talking to backend") == 0 {
163+
log.Printf("[INFO] go-api::webhook#update Timeout talking to backend "+
164+
"attempt: %d, until timeout: %d", attempt, (timeout - (attempt * sleep)))
165+
attempt++
166+
time.Sleep(time.Duration(sleep) * time.Second)
167+
return api.updateWebhookWithRetry(path, params, attempt, sleep, timeout)
168+
}
169+
return fmt.Errorf("update webhook failed, status: %v, message: %s", 400, failed)
170+
default:
171+
return fmt.Errorf("update webhook with retry failed, status: %v, message: %s",
172+
response.StatusCode, failed)
173+
}
174+
}
175+
72176
// DeleteWebhook - removes a specific webhook for an instance
73-
func (api *API) DeleteWebhook(instanceID int, webhookID string) error {
74-
failed := make(map[string]interface{})
75-
log.Printf("[DEBUG] go-api::webhook::delete instance ID: %d, webhookID: %s", instanceID, webhookID)
177+
func (api *API) DeleteWebhook(instanceID int, webhookID string, sleep, timeout int) error {
76178
path := fmt.Sprintf("/api/instances/%d/webhooks/%s", instanceID, webhookID)
179+
return api.deleteWebhookWithRetry(path, 1, sleep, timeout)
180+
}
181+
182+
// deleteWebhookWithRetry: delete webhook with retry if backend is busy.
183+
func (api *API) deleteWebhookWithRetry(path string, attempt, sleep, timeout int) error {
184+
var (
185+
failed map[string]interface{}
186+
)
187+
188+
log.Printf("[DEBUG] go-api::webhook#delete path: %s", path)
77189
response, err := api.sling.New().Delete(path).Receive(nil, &failed)
78190

79-
if response.StatusCode != 204 {
80-
return fmt.Errorf("DeleteWebhook failed, status: %v, message: %s", response.StatusCode, failed)
191+
if err != nil {
192+
return err
193+
} else if attempt*sleep > timeout {
194+
return fmt.Errorf("delete webhook reached timeout of %d seconds", timeout)
81195
}
82196

83-
return err
197+
switch response.StatusCode {
198+
case 204:
199+
return nil
200+
case 400:
201+
if strings.Compare(failed["error"].(string), "Timeout talking to backend") == 0 {
202+
log.Printf("[INFO] go-api::webhook#delete Timeout talking to backend "+
203+
"attempt: %d, until timeout: %d", attempt, (timeout - (attempt * sleep)))
204+
attempt++
205+
time.Sleep(time.Duration(sleep) * time.Second)
206+
return api.deleteWebhookWithRetry(path, attempt, sleep, timeout)
207+
}
208+
return fmt.Errorf("delete webhook failed, status: %v, message: %s", 400, failed)
209+
default:
210+
return fmt.Errorf("delete webhook with retry failed, status: %v, message: %s",
211+
response.StatusCode, failed)
212+
}
84213
}

0 commit comments

Comments
 (0)