Skip to content

Commit 0cc2c99

Browse files
authored
Product check: Improve retry mechanism (#309)
* Client: Improve product check Differentiate unspported/unknown products * Client: Handle http errors
1 parent 13b30bd commit 0cc2c99

File tree

3 files changed

+77
-21
lines changed

3 files changed

+77
-21
lines changed

elasticsearch.go

+27-19
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,10 @@ func init() {
4747
}
4848

4949
const (
50-
defaultURL = "http://localhost:9200"
51-
tagline = "You Know, for Search"
52-
unknownProduct = "the client noticed that the server is not Elasticsearch and we do not support this unknown product"
50+
defaultURL = "http://localhost:9200"
51+
tagline = "You Know, for Search"
52+
unknownProduct = "the client noticed that the server is not Elasticsearch and we do not support this unknown product"
53+
unsupportedProduct = "the client noticed that the server is not a supported distribution of Elasticsearch"
5354
)
5455

5556
// Version returns the package version as a string.
@@ -121,6 +122,7 @@ type info struct {
121122
Tagline string `json:"tagline"`
122123
}
123124

125+
124126
// NewDefaultClient creates a new client with default options.
125127
//
126128
// It will use http://localhost:9200 as the default address.
@@ -232,7 +234,7 @@ func NewClient(cfg Config) (*Client, error) {
232234
//
233235
func genuineCheckHeader(header http.Header) error {
234236
if header.Get("X-Elastic-Product") != "Elasticsearch" {
235-
return fmt.Errorf(unknownProduct)
237+
return errors.New(unknownProduct)
236238
}
237239
return nil
238240
}
@@ -246,18 +248,19 @@ func genuineCheckInfo(info info) error {
246248
}
247249

248250
if major < 6 {
249-
return fmt.Errorf(unknownProduct)
251+
return errors.New(unknownProduct)
250252
}
251253
if major < 7 {
252254
if info.Tagline != tagline {
253-
return fmt.Errorf(unknownProduct)
255+
return errors.New(unknownProduct)
254256
}
255257
}
256258
if major >= 7 {
257259
if minor < 14 {
258-
if info.Tagline != tagline ||
259-
info.Version.BuildFlavor != "default" {
260-
return fmt.Errorf(unknownProduct)
260+
if info.Tagline != tagline {
261+
return errors.New(unknownProduct)
262+
} else if info.Version.BuildFlavor != "default" {
263+
return errors.New(unsupportedProduct)
261264
}
262265
}
263266
}
@@ -312,18 +315,24 @@ func (c *Client) doProductCheck(f func() error) error {
312315
c.productCheckMu.RLock()
313316
productCheckSuccess := c.productCheckSuccess
314317
c.productCheckMu.RUnlock()
318+
315319
if productCheckSuccess {
316320
return nil
317321
}
322+
318323
c.productCheckMu.Lock()
319324
defer c.productCheckMu.Unlock()
325+
320326
if c.productCheckSuccess {
321327
return nil
322328
}
329+
323330
if err := f(); err != nil {
324331
return err
325332
}
333+
326334
c.productCheckSuccess = true
335+
327336
return nil
328337
}
329338

@@ -338,6 +347,10 @@ func (c *Client) productCheck() error {
338347
return err
339348
}
340349

350+
if res.IsError() {
351+
return fmt.Errorf("cannot retrieve info from Elasticsearch")
352+
}
353+
341354
contentType := res.Header.Get("Content-Type")
342355
if res.Body != nil {
343356
defer res.Body.Close()
@@ -350,19 +363,14 @@ func (c *Client) productCheck() error {
350363
}
351364
}
352365

353-
switch res.StatusCode {
354-
case http.StatusForbidden:
355-
case http.StatusUnauthorized:
356-
break
357-
default:
358-
err = genuineCheckHeader(res.Header)
366+
err = genuineCheckHeader(res.Header)
359367

360-
if err != nil {
361-
if info.Version.Number != "" {
362-
err = genuineCheckInfo(info)
363-
}
368+
if err != nil {
369+
if info.Version.Number != "" {
370+
err = genuineCheckInfo(info)
364371
}
365372
}
373+
366374
if err != nil {
367375
return err
368376
}

elasticsearch_internal_test.go

+43-1
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,7 @@ func TestGenuineCheckInfo(t *testing.T) {
471471
name string
472472
info info
473473
wantErr bool
474+
err error
474475
}{
475476
{
476477
name: "Genuine Elasticsearch 7.14.0",
@@ -482,6 +483,7 @@ func TestGenuineCheckInfo(t *testing.T) {
482483
Tagline: "You Know, for Search",
483484
},
484485
wantErr: false,
486+
err: nil,
485487
},
486488
{
487489
name: "Genuine Elasticsearch 6.15.1",
@@ -493,6 +495,7 @@ func TestGenuineCheckInfo(t *testing.T) {
493495
Tagline: "You Know, for Search",
494496
},
495497
wantErr: false,
498+
err: nil,
496499
},
497500
{
498501
name: "Not so genuine Elasticsearch 7 major",
@@ -504,6 +507,7 @@ func TestGenuineCheckInfo(t *testing.T) {
504507
Tagline: "You Know, for Search",
505508
},
506509
wantErr: true,
510+
err: errors.New(unknownProduct),
507511
},
508512
{
509513
name: "Not so genuine Elasticsearch 6 major",
@@ -515,6 +519,7 @@ func TestGenuineCheckInfo(t *testing.T) {
515519
Tagline: "You Know, for Fun",
516520
},
517521
wantErr: true,
522+
err: errors.New(unknownProduct),
518523
},
519524
{
520525
name: "Way older Elasticsearch major",
@@ -526,11 +531,24 @@ func TestGenuineCheckInfo(t *testing.T) {
526531
Tagline: "You Know, for Fun",
527532
},
528533
wantErr: true,
534+
err: errors.New(unknownProduct),
535+
},
536+
{
537+
name: "Elasticsearch oss",
538+
info: info{
539+
Version: esVersion{
540+
Number: "7.10.0",
541+
BuildFlavor: "oss",
542+
},
543+
Tagline: "You Know, for Search",
544+
},
545+
wantErr: true,
546+
err: errors.New(unsupportedProduct),
529547
},
530548
}
531549
for _, tt := range tests {
532550
t.Run(tt.name, func(t *testing.T) {
533-
if err := genuineCheckInfo(tt.info); (err != nil) != tt.wantErr {
551+
if err := genuineCheckInfo(tt.info); (err != nil) != tt.wantErr && err != tt.err {
534552
t.Errorf("genuineCheckInfo() error = %v, wantErr %v", err, tt.wantErr)
535553
}
536554
})
@@ -621,6 +639,24 @@ func TestResponseCheckOnly(t *testing.T) {
621639
requestErr: errors.New("request failed"),
622640
wantErr: true,
623641
},
642+
{
643+
name: "Valid request, 500 response",
644+
useResponseCheckOnly: false,
645+
response: &http.Response{
646+
StatusCode: http.StatusInternalServerError,
647+
},
648+
requestErr: nil,
649+
wantErr: true,
650+
},
651+
{
652+
name: "Valid request, 404 response",
653+
useResponseCheckOnly: false,
654+
response: &http.Response{
655+
StatusCode: http.StatusNotFound,
656+
},
657+
requestErr: nil,
658+
wantErr: true,
659+
},
624660
}
625661
for _, tt := range tests {
626662
t.Run(tt.name, func(t *testing.T) {
@@ -657,6 +693,9 @@ func TestProductCheckError(t *testing.T) {
657693
if _, err := c.Cat.Indices(); err == nil {
658694
t.Fatal("expected error")
659695
}
696+
if c.productCheckSuccess {
697+
t.Fatalf("product check should be invalid, got %v", c.productCheckSuccess)
698+
}
660699
if _, err := c.Cat.Indices(); err != nil {
661700
t.Fatalf("unexpected error: %s", err)
662701
}
@@ -666,4 +705,7 @@ func TestProductCheckError(t *testing.T) {
666705
if !reflect.DeepEqual(requestPaths, []string{"/", "/", "/_cat/indices"}) {
667706
t.Fatalf("unexpected request paths: %s", requestPaths)
668707
}
708+
if !c.productCheckSuccess {
709+
t.Fatalf("product check should be valid, got : %v", c.productCheckSuccess)
710+
}
669711
}

esapi/esapi_benchmark_test.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,13 @@ var (
3838
Body: ioutil.NopCloser(strings.NewReader("MOCK")),
3939
}
4040
defaultRoundTripFn = func(*http.Request) (*http.Response, error) { return defaultResponse, nil }
41-
errorRoundTripFn = func(*http.Request) (*http.Response, error) {
41+
errorRoundTripFn = func(request *http.Request) (*http.Response, error) {
42+
if request.URL.Path == "/" {
43+
return &http.Response{
44+
StatusCode: 200,
45+
Header: http.Header{"X-Elastic-Product": []string{"Elasticsearch"}},
46+
}, nil
47+
}
4248
return &http.Response{
4349
Header: http.Header{"X-Elastic-Product": []string{"Elasticsearch"}},
4450
StatusCode: 400,

0 commit comments

Comments
 (0)