Skip to content

Commit af8c1e4

Browse files
authored
Backport to 7.14 the latest product check update (#311)
* Product check: Improve retry mechanism (#309) * Client: Improve product check Differentiate unspported/unknown products * Client: Handle http errors * Client: Rework product check according to latest spec (#310) Handling of Info special cases
1 parent db1fad3 commit af8c1e4

File tree

3 files changed

+119
-32
lines changed

3 files changed

+119
-32
lines changed

elasticsearch.go

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
"encoding/json"
2424
"errors"
2525
"fmt"
26+
"io"
27+
"io/ioutil"
2628
"net/http"
2729
"net/url"
2830
"os"
@@ -47,9 +49,10 @@ func init() {
4749
}
4850

4951
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"
52+
defaultURL = "http://localhost:9200"
53+
tagline = "You Know, for Search"
54+
unknownProduct = "the client noticed that the server is not Elasticsearch and we do not support this unknown product"
55+
unsupportedProduct = "the client noticed that the server is not a supported distribution of Elasticsearch"
5356
)
5457

5558
// Version returns the package version as a string.
@@ -119,6 +122,7 @@ type info struct {
119122
Tagline string `json:"tagline"`
120123
}
121124

125+
122126
// NewDefaultClient creates a new client with default options.
123127
//
124128
// It will use http://localhost:9200 as the default address.
@@ -228,7 +232,7 @@ func NewClient(cfg Config) (*Client, error) {
228232
//
229233
func genuineCheckHeader(header http.Header) error {
230234
if header.Get("X-Elastic-Product") != "Elasticsearch" {
231-
return fmt.Errorf(unknownProduct)
235+
return errors.New(unknownProduct)
232236
}
233237
return nil
234238
}
@@ -242,18 +246,19 @@ func genuineCheckInfo(info info) error {
242246
}
243247

244248
if major < 6 {
245-
return fmt.Errorf(unknownProduct)
249+
return errors.New(unknownProduct)
246250
}
247251
if major < 7 {
248252
if info.Tagline != tagline {
249-
return fmt.Errorf(unknownProduct)
253+
return errors.New(unknownProduct)
250254
}
251255
}
252256
if major >= 7 {
253257
if minor < 14 {
254-
if info.Tagline != tagline ||
255-
info.Version.BuildFlavor != "default" {
256-
return fmt.Errorf(unknownProduct)
258+
if info.Tagline != tagline {
259+
return errors.New(unknownProduct)
260+
} else if info.Version.BuildFlavor != "default" {
261+
return errors.New(unsupportedProduct)
257262
}
258263
}
259264
}
@@ -308,62 +313,73 @@ func (c *Client) doProductCheck(f func() error) error {
308313
c.productCheckMu.RLock()
309314
productCheckSuccess := c.productCheckSuccess
310315
c.productCheckMu.RUnlock()
316+
311317
if productCheckSuccess {
312318
return nil
313319
}
320+
314321
c.productCheckMu.Lock()
315322
defer c.productCheckMu.Unlock()
323+
316324
if c.productCheckSuccess {
317325
return nil
318326
}
327+
319328
if err := f(); err != nil {
320329
return err
321330
}
331+
322332
c.productCheckSuccess = true
333+
323334
return nil
324335
}
325336

326337
// productCheck runs an esapi.Info query to retrieve informations of the current cluster
327338
// decodes the response and decides if the cluster is a genuine Elasticsearch product.
328339
func (c *Client) productCheck() error {
329-
var info info
330-
331340
req := esapi.InfoRequest{}
332341
res, err := req.Do(context.Background(), c.Transport)
333342
if err != nil {
334343
return err
335344
}
345+
defer res.Body.Close()
336346

337-
contentType := res.Header.Get("Content-Type")
338-
if res.Body != nil {
339-
defer res.Body.Close()
340-
341-
if strings.Contains(contentType, "json") {
342-
decoder := json.NewDecoder(res.Body)
343-
err = decoder.Decode(&info)
344-
if err != nil {
345-
return fmt.Errorf("error decoding Elasticsearch informations: %s", err)
346-
}
347+
if res.IsError() {
348+
_, err = io.Copy(ioutil.Discard, res.Body)
349+
if err != nil {
350+
return err
347351
}
348-
349352
switch res.StatusCode {
350-
case http.StatusForbidden:
351353
case http.StatusUnauthorized:
352-
break
354+
return nil
355+
case http.StatusForbidden:
356+
return nil
353357
default:
354-
err = genuineCheckHeader(res.Header)
358+
return fmt.Errorf("cannot retrieve informations from Elasticsearch")
359+
}
360+
}
355361

362+
err = genuineCheckHeader(res.Header)
363+
364+
if err != nil {
365+
var info info
366+
contentType := res.Header.Get("Content-Type")
367+
if strings.Contains(contentType, "json") {
368+
err = json.NewDecoder(res.Body).Decode(&info)
356369
if err != nil {
357-
if info.Version.Number != "" {
358-
err = genuineCheckInfo(info)
359-
}
370+
return fmt.Errorf("error decoding Elasticsearch informations: %s", err)
360371
}
361372
}
362-
if err != nil {
363-
return err
373+
374+
if info.Version.Number != "" {
375+
err = genuineCheckInfo(info)
364376
}
365377
}
366378

379+
if err != nil {
380+
return err
381+
}
382+
367383
return nil
368384
}
369385

elasticsearch_internal_test.go

Lines changed: 65 additions & 1 deletion
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,46 @@ 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+
Body: ioutil.NopCloser(strings.NewReader("")),
648+
},
649+
requestErr: nil,
650+
wantErr: true,
651+
},
652+
{
653+
name: "Valid request, 404 response",
654+
useResponseCheckOnly: false,
655+
response: &http.Response{
656+
StatusCode: http.StatusNotFound,
657+
Body: ioutil.NopCloser(strings.NewReader("")),
658+
},
659+
requestErr: nil,
660+
wantErr: true,
661+
},
662+
{
663+
name: "Valid request, 403 response",
664+
useResponseCheckOnly: false,
665+
response: &http.Response{
666+
StatusCode: http.StatusForbidden,
667+
Body: ioutil.NopCloser(strings.NewReader("")),
668+
},
669+
requestErr: nil,
670+
wantErr: false,
671+
},
672+
{
673+
name: "Valid request, 401 response",
674+
useResponseCheckOnly: false,
675+
response: &http.Response{
676+
StatusCode: http.StatusUnauthorized,
677+
Body: ioutil.NopCloser(strings.NewReader("")),
678+
},
679+
requestErr: nil,
680+
wantErr: false,
681+
},
624682
}
625683
for _, tt := range tests {
626684
t.Run(tt.name, func(t *testing.T) {
@@ -657,6 +715,9 @@ func TestProductCheckError(t *testing.T) {
657715
if _, err := c.Cat.Indices(); err == nil {
658716
t.Fatal("expected error")
659717
}
718+
if c.productCheckSuccess {
719+
t.Fatalf("product check should be invalid, got %v", c.productCheckSuccess)
720+
}
660721
if _, err := c.Cat.Indices(); err != nil {
661722
t.Fatalf("unexpected error: %s", err)
662723
}
@@ -666,4 +727,7 @@ func TestProductCheckError(t *testing.T) {
666727
if !reflect.DeepEqual(requestPaths, []string{"/", "/", "/_cat/indices"}) {
667728
t.Fatalf("unexpected request paths: %s", requestPaths)
668729
}
730+
if !c.productCheckSuccess {
731+
t.Fatalf("product check should be valid, got : %v", c.productCheckSuccess)
732+
}
669733
}

esapi/esapi_benchmark_test.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,14 @@ 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+
Body: ioutil.NopCloser(strings.NewReader("{}")),
47+
}, nil
48+
}
4249
return &http.Response{
4350
Header: http.Header{"X-Elastic-Product": []string{"Elasticsearch"}},
4451
StatusCode: 400,

0 commit comments

Comments
 (0)