@@ -6,21 +6,57 @@ package es
6
6
7
7
import (
8
8
"context"
9
+ "errors"
9
10
"fmt"
10
11
"net/http"
11
12
"runtime"
13
+ "syscall"
14
+ "time"
12
15
13
16
"go.elastic.co/apm/module/apmelasticsearch/v2"
14
17
15
18
"github.com/elastic/fleet-server/v7/internal/pkg/build"
16
19
"github.com/elastic/fleet-server/v7/internal/pkg/config"
17
20
"github.com/rs/zerolog"
18
21
22
+ backoff "github.com/cenkalti/backoff/v4"
19
23
"github.com/elastic/go-elasticsearch/v8"
20
24
)
21
25
26
+ const (
27
+ initialRetryBackoff = 500 * time .Millisecond
28
+ maxRetryBackoff = 10 * time .Second
29
+ randomizationFactor = 0.5
30
+ defaultMaxRetries = 5
31
+ )
32
+
22
33
type ConfigOption func (config * elasticsearch.Config )
23
34
35
+ func applyDefaultOptions (escfg * elasticsearch.Config ) {
36
+ exp := backoff .NewExponentialBackOff ()
37
+ exp .InitialInterval = initialRetryBackoff
38
+ exp .RandomizationFactor = randomizationFactor
39
+ exp .MaxInterval = maxRetryBackoff
40
+
41
+ opts := []ConfigOption {
42
+ WithRetryOnErrs (syscall .ECONNREFUSED , syscall .ECONNRESET ), // server may be restarting
43
+
44
+ WithRetryOnStatus (http .StatusTooManyRequests ),
45
+ WithRetryOnStatus (http .StatusRequestTimeout ),
46
+ WithRetryOnStatus (http .StatusTooEarly ),
47
+ WithRetryOnStatus (http .StatusBadGateway ),
48
+ WithRetryOnStatus (http .StatusServiceUnavailable ),
49
+ WithRetryOnStatus (http .StatusGatewayTimeout ),
50
+
51
+ WithBackoff (exp ),
52
+ WithMaxRetries (defaultMaxRetries ),
53
+ }
54
+
55
+ for _ , opt := range opts {
56
+ opt (escfg )
57
+ }
58
+ }
59
+
24
60
func NewClient (ctx context.Context , cfg * config.Config , longPoll bool , opts ... ConfigOption ) (* elasticsearch.Client , error ) {
25
61
escfg , err := cfg .Output .Elasticsearch .ToESConfig (longPoll )
26
62
if err != nil {
@@ -29,6 +65,9 @@ func NewClient(ctx context.Context, cfg *config.Config, longPoll bool, opts ...C
29
65
addr := cfg .Output .Elasticsearch .Hosts
30
66
mcph := cfg .Output .Elasticsearch .MaxConnPerHost
31
67
68
+ // apply default config
69
+ applyDefaultOptions (& escfg )
70
+
32
71
// Apply configuration options
33
72
for _ , opt := range opts {
34
73
opt (& escfg )
@@ -78,6 +117,55 @@ func InstrumentRoundTripper() ConfigOption {
78
117
}
79
118
}
80
119
120
+ func WithRetryOnErrs (errs ... error ) ConfigOption {
121
+ return func (config * elasticsearch.Config ) {
122
+ config .RetryOnError = func (_ * http.Request , err error ) bool {
123
+ for _ , e := range errs {
124
+ if errors .Is (err , e ) {
125
+ return true
126
+ }
127
+ }
128
+ return false
129
+ }
130
+ }
131
+ }
132
+
133
+ func WithMaxRetries (retries int ) ConfigOption {
134
+ return func (config * elasticsearch.Config ) {
135
+ config .MaxRetries = retries
136
+ }
137
+ }
138
+
139
+ func WithRetryOnStatus (status int ) ConfigOption {
140
+ return func (config * elasticsearch.Config ) {
141
+ for _ , s := range config .RetryOnStatus {
142
+ // check for duplicities
143
+ if s == status {
144
+ return
145
+ }
146
+ }
147
+
148
+ config .RetryOnStatus = append (config .RetryOnStatus , status )
149
+ }
150
+ }
151
+
152
+ func WithBackoff (exp * backoff.ExponentialBackOff ) ConfigOption {
153
+ return func (config * elasticsearch.Config ) {
154
+ if exp == nil {
155
+ // no retry backoff
156
+ config .RetryBackoff = nil
157
+ return
158
+ }
159
+
160
+ config .RetryBackoff = func (attempt int ) time.Duration {
161
+ if attempt == 1 {
162
+ exp .Reset ()
163
+ }
164
+ return exp .NextBackOff ()
165
+ }
166
+ }
167
+ }
168
+
81
169
func userAgent (name string , bi build.Info ) string {
82
170
return fmt .Sprintf ("Elastic-%s/%s (%s; %s; %s; %s)" ,
83
171
name ,
0 commit comments