go-common/vendor/gopkg.in/olivere/elastic.v5/client_test.go

1331 lines
35 KiB
Go

// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"log"
"net"
"net/http"
"reflect"
"regexp"
"strings"
"sync"
"testing"
"time"
"github.com/fortytw2/leaktest"
)
func findConn(s string, slice ...*conn) (int, bool) {
for i, t := range slice {
if s == t.URL() {
return i, true
}
}
return -1, false
}
// -- NewClient --
func TestClientDefaults(t *testing.T) {
client, err := NewClient()
if err != nil {
t.Fatal(err)
}
if client.healthcheckEnabled != true {
t.Errorf("expected health checks to be enabled, got: %v", client.healthcheckEnabled)
}
if client.healthcheckTimeoutStartup != DefaultHealthcheckTimeoutStartup {
t.Errorf("expected health checks timeout on startup = %v, got: %v", DefaultHealthcheckTimeoutStartup, client.healthcheckTimeoutStartup)
}
if client.healthcheckTimeout != DefaultHealthcheckTimeout {
t.Errorf("expected health checks timeout = %v, got: %v", DefaultHealthcheckTimeout, client.healthcheckTimeout)
}
if client.healthcheckInterval != DefaultHealthcheckInterval {
t.Errorf("expected health checks interval = %v, got: %v", DefaultHealthcheckInterval, client.healthcheckInterval)
}
if client.snifferEnabled != true {
t.Errorf("expected sniffing to be enabled, got: %v", client.snifferEnabled)
}
if client.snifferTimeoutStartup != DefaultSnifferTimeoutStartup {
t.Errorf("expected sniffer timeout on startup = %v, got: %v", DefaultSnifferTimeoutStartup, client.snifferTimeoutStartup)
}
if client.snifferTimeout != DefaultSnifferTimeout {
t.Errorf("expected sniffer timeout = %v, got: %v", DefaultSnifferTimeout, client.snifferTimeout)
}
if client.snifferInterval != DefaultSnifferInterval {
t.Errorf("expected sniffer interval = %v, got: %v", DefaultSnifferInterval, client.snifferInterval)
}
if client.basicAuth != false {
t.Errorf("expected no basic auth; got: %v", client.basicAuth)
}
if client.basicAuthUsername != "" {
t.Errorf("expected no basic auth username; got: %q", client.basicAuthUsername)
}
if client.basicAuthPassword != "" {
t.Errorf("expected no basic auth password; got: %q", client.basicAuthUsername)
}
if client.sendGetBodyAs != "GET" {
t.Errorf("expected sendGetBodyAs to be GET; got: %q", client.sendGetBodyAs)
}
}
func TestClientWithoutURL(t *testing.T) {
client, err := NewClient()
if err != nil {
t.Fatal(err)
}
// Two things should happen here:
// 1. The client starts sniffing the cluster on DefaultURL
// 2. The sniffing process should find (at least) one node in the cluster, i.e. the DefaultURL
if len(client.conns) == 0 {
t.Fatalf("expected at least 1 node in the cluster, got: %d (%v)", len(client.conns), client.conns)
}
if !isTravis() {
if _, found := findConn(DefaultURL, client.conns...); !found {
t.Errorf("expected to find node with default URL of %s in %v", DefaultURL, client.conns)
}
}
}
func TestClientWithSingleURL(t *testing.T) {
client, err := NewClient(SetURL("http://127.0.0.1:9200"))
if err != nil {
t.Fatal(err)
}
// Two things should happen here:
// 1. The client starts sniffing the cluster on DefaultURL
// 2. The sniffing process should find (at least) one node in the cluster, i.e. the DefaultURL
if len(client.conns) == 0 {
t.Fatalf("expected at least 1 node in the cluster, got: %d (%v)", len(client.conns), client.conns)
}
if !isTravis() {
if _, found := findConn(DefaultURL, client.conns...); !found {
t.Errorf("expected to find node with default URL of %s in %v", DefaultURL, client.conns)
}
}
}
func TestClientWithMultipleURLs(t *testing.T) {
client, err := NewClient(SetURL("http://127.0.0.1:9200", "http://127.0.0.1:9201"))
if err != nil {
t.Fatal(err)
}
// The client should sniff both URLs, but only 127.0.0.1:9200 should return nodes.
if len(client.conns) != 1 {
t.Fatalf("expected exactly 1 node in the local cluster, got: %d (%v)", len(client.conns), client.conns)
}
if !isTravis() {
if client.conns[0].URL() != DefaultURL {
t.Errorf("expected to find node with default URL of %s in %v", DefaultURL, client.conns)
}
}
}
func TestClientWithBasicAuth(t *testing.T) {
client, err := NewClient(SetBasicAuth("user", "secret"))
if err != nil {
t.Fatal(err)
}
if client.basicAuth != true {
t.Errorf("expected basic auth; got: %v", client.basicAuth)
}
if got, want := client.basicAuthUsername, "user"; got != want {
t.Errorf("expected basic auth username %q; got: %q", want, got)
}
if got, want := client.basicAuthPassword, "secret"; got != want {
t.Errorf("expected basic auth password %q; got: %q", want, got)
}
}
func TestClientWithBasicAuthInUserInfo(t *testing.T) {
client, err := NewClient(SetURL("http://user1:secret1@localhost:9200", "http://user2:secret2@localhost:9200"))
if err != nil {
t.Fatal(err)
}
if client.basicAuth != true {
t.Errorf("expected basic auth; got: %v", client.basicAuth)
}
if got, want := client.basicAuthUsername, "user1"; got != want {
t.Errorf("expected basic auth username %q; got: %q", want, got)
}
if got, want := client.basicAuthPassword, "secret1"; got != want {
t.Errorf("expected basic auth password %q; got: %q", want, got)
}
}
func TestClientSniffSuccess(t *testing.T) {
client, err := NewClient(SetURL("http://127.0.0.1:19200", "http://127.0.0.1:9200"))
if err != nil {
t.Fatal(err)
}
// The client should sniff both URLs, but only 127.0.0.1:9200 should return nodes.
if len(client.conns) != 1 {
t.Fatalf("expected exactly 1 node in the local cluster, got: %d (%v)", len(client.conns), client.conns)
}
}
func TestClientSniffFailure(t *testing.T) {
_, err := NewClient(SetURL("http://127.0.0.1:19200", "http://127.0.0.1:19201"))
if err == nil {
t.Fatalf("expected cluster to fail with no nodes found")
}
}
func TestClientSnifferCallback(t *testing.T) {
var calls int
cb := func(node *NodesInfoNode) bool {
calls++
return false
}
_, err := NewClient(
SetURL("http://127.0.0.1:19200", "http://127.0.0.1:9200"),
SetSnifferCallback(cb))
if err == nil {
t.Fatalf("expected cluster to fail with no nodes found")
}
if calls != 1 {
t.Fatalf("expected 1 call to the sniffer callback, got %d", calls)
}
}
func TestClientSniffDisabled(t *testing.T) {
client, err := NewClient(SetSniff(false), SetURL("http://127.0.0.1:9200", "http://127.0.0.1:9201"))
if err != nil {
t.Fatal(err)
}
// The client should not sniff, so it should have two connections.
if len(client.conns) != 2 {
t.Fatalf("expected 2 nodes, got: %d (%v)", len(client.conns), client.conns)
}
// Make two requests, so that both connections are being used
for i := 0; i < len(client.conns); i++ {
client.Flush().Do(context.TODO())
}
// The first connection (127.0.0.1:9200) should now be okay.
if i, found := findConn("http://127.0.0.1:9200", client.conns...); !found {
t.Fatalf("expected connection to %q to be found", "http://127.0.0.1:9200")
} else {
if conn := client.conns[i]; conn.IsDead() {
t.Fatal("expected connection to be alive, but it is dead")
}
}
// The second connection (127.0.0.1:9201) should now be marked as dead.
if i, found := findConn("http://127.0.0.1:9201", client.conns...); !found {
t.Fatalf("expected connection to %q to be found", "http://127.0.0.1:9201")
} else {
if conn := client.conns[i]; !conn.IsDead() {
t.Fatal("expected connection to be dead, but it is alive")
}
}
}
func TestClientWillMarkConnectionsAsAliveWhenAllAreDead(t *testing.T) {
client, err := NewClient(SetURL("http://127.0.0.1:9201"),
SetSniff(false), SetHealthcheck(false), SetMaxRetries(0))
if err != nil {
t.Fatal(err)
}
// We should have a connection.
if len(client.conns) != 1 {
t.Fatalf("expected 1 node, got: %d (%v)", len(client.conns), client.conns)
}
// Make a request, so that the connections is marked as dead.
client.Flush().Do(context.TODO())
// The connection should now be marked as dead.
if i, found := findConn("http://127.0.0.1:9201", client.conns...); !found {
t.Fatalf("expected connection to %q to be found", "http://127.0.0.1:9201")
} else {
if conn := client.conns[i]; !conn.IsDead() {
t.Fatalf("expected connection to be dead, got: %v", conn)
}
}
// Now send another request and the connection should be marked as alive again.
client.Flush().Do(context.TODO())
if i, found := findConn("http://127.0.0.1:9201", client.conns...); !found {
t.Fatalf("expected connection to %q to be found", "http://127.0.0.1:9201")
} else {
if conn := client.conns[i]; conn.IsDead() {
t.Fatalf("expected connection to be alive, got: %v", conn)
}
}
}
func TestClientWithRequiredPlugins(t *testing.T) {
_, err := NewClient(SetRequiredPlugins("no-such-plugin"))
if err == nil {
t.Fatal("expected error when creating client")
}
if got, want := err.Error(), "elastic: plugin no-such-plugin not found"; got != want {
t.Fatalf("expected error %q; got: %q", want, got)
}
}
func TestClientHealthcheckStartupTimeout(t *testing.T) {
start := time.Now()
_, err := NewClient(SetURL("http://localhost:9299"), SetHealthcheckTimeoutStartup(5*time.Second))
duration := time.Since(start)
if !IsConnErr(err) {
t.Fatal(err)
}
if !strings.Contains(err.Error(), "connection refused") {
t.Fatalf("expected error to contain %q, have %q", "connection refused", err.Error())
}
if duration < 5*time.Second {
t.Fatalf("expected a timeout in more than 5 seconds; got: %v", duration)
}
}
func TestClientHealthcheckTimeoutLeak(t *testing.T) {
// This test test checks if healthcheck requests are canceled
// after timeout.
// It contains couple of hacks which won't be needed once we
// stop supporting Go1.7.
// On Go1.7 it uses server side effects to monitor if connection
// was closed,
// and on Go 1.8+ we're additionally honestly monitoring routine
// leaks via leaktest.
mux := http.NewServeMux()
var reqDoneMu sync.Mutex
var reqDone bool
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
cn, ok := w.(http.CloseNotifier)
if !ok {
t.Fatalf("Writer is not CloseNotifier, but %v", reflect.TypeOf(w).Name())
}
<-cn.CloseNotify()
reqDoneMu.Lock()
reqDone = true
reqDoneMu.Unlock()
})
lis, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("Couldn't setup listener: %v", err)
}
addr := lis.Addr().String()
srv := &http.Server{
Handler: mux,
}
go srv.Serve(lis)
cli := &Client{
c: &http.Client{},
conns: []*conn{
&conn{
url: "http://" + addr + "/",
},
},
}
type closer interface {
Shutdown(context.Context) error
}
// pre-Go1.8 Server can't Shutdown
cl, isServerCloseable := (interface{}(srv)).(closer)
// Since Go1.7 can't Shutdown() - there will be leak from server
// Monitor leaks on Go 1.8+
if isServerCloseable {
defer leaktest.CheckTimeout(t, time.Second*10)()
}
cli.healthcheck(time.Millisecond*500, true)
if isServerCloseable {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
cl.Shutdown(ctx)
}
<-time.After(time.Second)
reqDoneMu.Lock()
if !reqDone {
reqDoneMu.Unlock()
t.Fatal("Request wasn't canceled or stopped")
}
reqDoneMu.Unlock()
}
// -- NewSimpleClient --
func TestSimpleClientDefaults(t *testing.T) {
client, err := NewSimpleClient()
if err != nil {
t.Fatal(err)
}
if client.healthcheckEnabled != false {
t.Errorf("expected health checks to be disabled, got: %v", client.healthcheckEnabled)
}
if client.healthcheckTimeoutStartup != off {
t.Errorf("expected health checks timeout on startup = %v, got: %v", off, client.healthcheckTimeoutStartup)
}
if client.healthcheckTimeout != off {
t.Errorf("expected health checks timeout = %v, got: %v", off, client.healthcheckTimeout)
}
if client.healthcheckInterval != off {
t.Errorf("expected health checks interval = %v, got: %v", off, client.healthcheckInterval)
}
if client.snifferEnabled != false {
t.Errorf("expected sniffing to be disabled, got: %v", client.snifferEnabled)
}
if client.snifferTimeoutStartup != off {
t.Errorf("expected sniffer timeout on startup = %v, got: %v", off, client.snifferTimeoutStartup)
}
if client.snifferTimeout != off {
t.Errorf("expected sniffer timeout = %v, got: %v", off, client.snifferTimeout)
}
if client.snifferInterval != off {
t.Errorf("expected sniffer interval = %v, got: %v", off, client.snifferInterval)
}
if client.basicAuth != false {
t.Errorf("expected no basic auth; got: %v", client.basicAuth)
}
if client.basicAuthUsername != "" {
t.Errorf("expected no basic auth username; got: %q", client.basicAuthUsername)
}
if client.basicAuthPassword != "" {
t.Errorf("expected no basic auth password; got: %q", client.basicAuthUsername)
}
if client.sendGetBodyAs != "GET" {
t.Errorf("expected sendGetBodyAs to be GET; got: %q", client.sendGetBodyAs)
}
}
// -- Start and stop --
func TestClientStartAndStop(t *testing.T) {
client, err := NewClient()
if err != nil {
t.Fatal(err)
}
running := client.IsRunning()
if !running {
t.Fatalf("expected background processes to run; got: %v", running)
}
// Stop
client.Stop()
running = client.IsRunning()
if running {
t.Fatalf("expected background processes to be stopped; got: %v", running)
}
// Stop again => no-op
client.Stop()
running = client.IsRunning()
if running {
t.Fatalf("expected background processes to be stopped; got: %v", running)
}
// Start
client.Start()
running = client.IsRunning()
if !running {
t.Fatalf("expected background processes to run; got: %v", running)
}
// Start again => no-op
client.Start()
running = client.IsRunning()
if !running {
t.Fatalf("expected background processes to run; got: %v", running)
}
}
func TestClientStartAndStopWithSnifferAndHealthchecksDisabled(t *testing.T) {
client, err := NewClient(SetSniff(false), SetHealthcheck(false))
if err != nil {
t.Fatal(err)
}
running := client.IsRunning()
if !running {
t.Fatalf("expected background processes to run; got: %v", running)
}
// Stop
client.Stop()
running = client.IsRunning()
if running {
t.Fatalf("expected background processes to be stopped; got: %v", running)
}
// Stop again => no-op
client.Stop()
running = client.IsRunning()
if running {
t.Fatalf("expected background processes to be stopped; got: %v", running)
}
// Start
client.Start()
running = client.IsRunning()
if !running {
t.Fatalf("expected background processes to run; got: %v", running)
}
// Start again => no-op
client.Start()
running = client.IsRunning()
if !running {
t.Fatalf("expected background processes to run; got: %v", running)
}
}
// -- Sniffing --
func TestClientSniffNode(t *testing.T) {
client, err := NewClient()
if err != nil {
t.Fatal(err)
}
ch := make(chan []*conn)
go func() { ch <- client.sniffNode(context.Background(), DefaultURL) }()
select {
case nodes := <-ch:
if len(nodes) != 1 {
t.Fatalf("expected %d nodes; got: %d", 1, len(nodes))
}
pattern := `http:\/\/[\d\.]+:9200`
matched, err := regexp.MatchString(pattern, nodes[0].URL())
if err != nil {
t.Fatal(err)
}
if !matched {
t.Fatalf("expected node URL pattern %q; got: %q", pattern, nodes[0].URL())
}
case <-time.After(2 * time.Second):
t.Fatal("expected no timeout in sniff node")
break
}
}
func TestClientSniffOnDefaultURL(t *testing.T) {
client, _ := NewClient()
if client == nil {
t.Fatal("no client returned")
}
ch := make(chan error, 1)
go func() {
ch <- client.sniff(DefaultSnifferTimeoutStartup)
}()
select {
case err := <-ch:
if err != nil {
t.Fatalf("expected sniff to succeed; got: %v", err)
}
if len(client.conns) != 1 {
t.Fatalf("expected %d nodes; got: %d", 1, len(client.conns))
}
pattern := `http:\/\/[\d\.]+:9200`
matched, err := regexp.MatchString(pattern, client.conns[0].URL())
if err != nil {
t.Fatal(err)
}
if !matched {
t.Fatalf("expected node URL pattern %q; got: %q", pattern, client.conns[0].URL())
}
case <-time.After(2 * time.Second):
t.Fatal("expected no timeout in sniff")
break
}
}
func TestClientSniffTimeoutLeak(t *testing.T) {
// This test test checks if sniff requests are canceled
// after timeout.
// It contains couple of hacks which won't be needed once we
// stop supporting Go1.7.
// On Go1.7 it uses server side effects to monitor if connection
// was closed,
// and on Go 1.8+ we're additionally honestly monitoring routine
// leaks via leaktest.
mux := http.NewServeMux()
var reqDoneMu sync.Mutex
var reqDone bool
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
cn, ok := w.(http.CloseNotifier)
if !ok {
t.Fatalf("Writer is not CloseNotifier, but %v", reflect.TypeOf(w).Name())
}
<-cn.CloseNotify()
reqDoneMu.Lock()
reqDone = true
reqDoneMu.Unlock()
})
lis, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("Couldn't setup listener: %v", err)
}
addr := lis.Addr().String()
srv := &http.Server{
Handler: mux,
}
go srv.Serve(lis)
cli := &Client{
c: &http.Client{},
conns: []*conn{
&conn{
url: "http://" + addr + "/",
},
},
snifferEnabled: true,
}
type closer interface {
Shutdown(context.Context) error
}
// pre-Go1.8 Server can't Shutdown
cl, isServerCloseable := (interface{}(srv)).(closer)
// Since Go1.7 can't Shutdown() - there will be leak from server
// Monitor leaks on Go 1.8+
if isServerCloseable {
defer leaktest.CheckTimeout(t, time.Second*10)()
}
cli.sniff(time.Millisecond * 500)
if isServerCloseable {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
cl.Shutdown(ctx)
}
<-time.After(time.Second)
reqDoneMu.Lock()
if !reqDone {
reqDoneMu.Unlock()
t.Fatal("Request wasn't canceled or stopped")
}
reqDoneMu.Unlock()
}
func TestClientExtractHostname(t *testing.T) {
tests := []struct {
Scheme string
Address string
Output string
}{
{
Scheme: "http",
Address: "",
Output: "",
},
{
Scheme: "https",
Address: "abc",
Output: "",
},
{
Scheme: "http",
Address: "127.0.0.1:19200",
Output: "http://127.0.0.1:19200",
},
{
Scheme: "https",
Address: "127.0.0.1:9200",
Output: "https://127.0.0.1:9200",
},
{
Scheme: "http",
Address: "myelk.local/10.1.0.24:9200",
Output: "http://10.1.0.24:9200",
},
}
client, err := NewClient(SetSniff(false), SetHealthcheck(false))
if err != nil {
t.Fatal(err)
}
for _, test := range tests {
got := client.extractHostname(test.Scheme, test.Address)
if want := test.Output; want != got {
t.Errorf("expected %q; got: %q", want, got)
}
}
}
// -- Selector --
func TestClientSelectConnHealthy(t *testing.T) {
client, err := NewClient(
SetSniff(false),
SetHealthcheck(false),
SetURL("http://127.0.0.1:9200/node1", "http://127.0.0.1:9201/node2/"))
if err != nil {
t.Fatal(err)
}
// Both are healthy, so we should get both URLs in round-robin
client.conns[0].MarkAsHealthy()
client.conns[1].MarkAsHealthy()
// #1: Return 1st
c, err := client.next()
if err != nil {
t.Fatal(err)
}
if c.URL() != client.conns[0].URL() {
t.Fatalf("expected %s; got: %s", c.URL(), client.conns[0].URL())
}
// #2: Return 2nd
c, err = client.next()
if err != nil {
t.Fatal(err)
}
if c.URL() != client.conns[1].URL() {
t.Fatalf("expected %s; got: %s", c.URL(), client.conns[1].URL())
}
// #3: Return 1st
c, err = client.next()
if err != nil {
t.Fatal(err)
}
if c.URL() != client.conns[0].URL() {
t.Fatalf("expected %s; got: %s", c.URL(), client.conns[0].URL())
}
}
func TestClientSelectConnHealthyWithURLPrefix(t *testing.T) {
client, err := NewClient(
SetSniff(false),
SetHealthcheck(false),
SetURL("http://127.0.0.1:9200/node1", "http://127.0.0.1:9201/node2/prefix/"))
if err != nil {
t.Fatal(err)
}
// Both are healthy, so we should get both URLs in round-robin
client.conns[0].MarkAsHealthy()
client.conns[1].MarkAsHealthy()
// Check that the connection used the URLs, including its prefix
if want, have := "http://127.0.0.1:9200/node1", client.conns[0].URL(); want != have {
t.Fatalf("want Node[0] = %q, have %q", want, have)
}
// Note that it stripped the / off the suffix
if want, have := "http://127.0.0.1:9201/node2/prefix", client.conns[1].URL(); want != have {
t.Fatalf("want Node[1] = %q, have %q", want, have)
}
// #1: Return 1st
c, err := client.next()
if err != nil {
t.Fatal(err)
}
if c.URL() != client.conns[0].URL() {
t.Fatalf("expected %s; got: %s", c.URL(), client.conns[0].URL())
}
// #2: Return 2nd
c, err = client.next()
if err != nil {
t.Fatal(err)
}
if c.URL() != client.conns[1].URL() {
t.Fatalf("expected %s; got: %s", c.URL(), client.conns[1].URL())
}
// #3: Return 1st
c, err = client.next()
if err != nil {
t.Fatal(err)
}
if c.URL() != client.conns[0].URL() {
t.Fatalf("expected %s; got: %s", c.URL(), client.conns[0].URL())
}
}
func TestClientSelectConnHealthyAndDead(t *testing.T) {
client, err := NewClient(
SetSniff(false),
SetHealthcheck(false),
SetURL("http://127.0.0.1:9200", "http://127.0.0.1:9201"))
if err != nil {
t.Fatal(err)
}
// 1st is healthy, second is dead
client.conns[0].MarkAsHealthy()
client.conns[1].MarkAsDead()
// #1: Return 1st
c, err := client.next()
if err != nil {
t.Fatal(err)
}
if c.URL() != client.conns[0].URL() {
t.Fatalf("expected %s; got: %s", c.URL(), client.conns[0].URL())
}
// #2: Return 1st again
c, err = client.next()
if err != nil {
t.Fatal(err)
}
if c.URL() != client.conns[0].URL() {
t.Fatalf("expected %s; got: %s", c.URL(), client.conns[0].URL())
}
// #3: Return 1st again and again
c, err = client.next()
if err != nil {
t.Fatal(err)
}
if c.URL() != client.conns[0].URL() {
t.Fatalf("expected %s; got: %s", c.URL(), client.conns[0].URL())
}
}
func TestClientSelectConnDeadAndHealthy(t *testing.T) {
client, err := NewClient(
SetSniff(false),
SetHealthcheck(false),
SetURL("http://127.0.0.1:9200", "http://127.0.0.1:9201"))
if err != nil {
t.Fatal(err)
}
// 1st is dead, 2nd is healthy
client.conns[0].MarkAsDead()
client.conns[1].MarkAsHealthy()
// #1: Return 2nd
c, err := client.next()
if err != nil {
t.Fatal(err)
}
if c.URL() != client.conns[1].URL() {
t.Fatalf("expected %s; got: %s", c.URL(), client.conns[1].URL())
}
// #2: Return 2nd again
c, err = client.next()
if err != nil {
t.Fatal(err)
}
if c.URL() != client.conns[1].URL() {
t.Fatalf("expected %s; got: %s", c.URL(), client.conns[1].URL())
}
// #3: Return 2nd again and again
c, err = client.next()
if err != nil {
t.Fatal(err)
}
if c.URL() != client.conns[1].URL() {
t.Fatalf("expected %s; got: %s", c.URL(), client.conns[1].URL())
}
}
func TestClientSelectConnAllDead(t *testing.T) {
client, err := NewClient(
SetSniff(false),
SetHealthcheck(false),
SetURL("http://127.0.0.1:9200", "http://127.0.0.1:9201"))
if err != nil {
t.Fatal(err)
}
// Both are dead
client.conns[0].MarkAsDead()
client.conns[1].MarkAsDead()
// If all connections are dead, next should make them alive again, but
// still return an error when it first finds out.
c, err := client.next()
if !IsConnErr(err) {
t.Fatal(err)
}
if c != nil {
t.Fatalf("expected no connection; got: %v", c)
}
// Return a connection
c, err = client.next()
if err != nil {
t.Fatalf("expected no error; got: %v", err)
}
if c == nil {
t.Fatalf("expected connection; got: %v", c)
}
// Return a connection
c, err = client.next()
if err != nil {
t.Fatalf("expected no error; got: %v", err)
}
if c == nil {
t.Fatalf("expected connection; got: %v", c)
}
}
// -- ElasticsearchVersion --
func TestElasticsearchVersion(t *testing.T) {
client, err := NewClient()
if err != nil {
t.Fatal(err)
}
version, err := client.ElasticsearchVersion(DefaultURL)
if err != nil {
t.Fatal(err)
}
if version == "" {
t.Errorf("expected a version number, got: %q", version)
}
}
// -- IndexNames --
func TestIndexNames(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
names, err := client.IndexNames()
if err != nil {
t.Fatal(err)
}
if len(names) == 0 {
t.Fatalf("expected some index names, got: %d", len(names))
}
var found bool
for _, name := range names {
if name == testIndexName {
found = true
break
}
}
if !found {
t.Fatalf("expected to find index %q; got: %v", testIndexName, found)
}
}
// -- PerformRequest --
func TestPerformRequest(t *testing.T) {
client, err := NewClient()
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), "GET", "/", nil, nil)
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response to be != nil")
}
ret := new(PingResult)
if err := json.Unmarshal(res.Body, ret); err != nil {
t.Fatalf("expected no error on decode; got: %v", err)
}
if ret.ClusterName == "" {
t.Errorf("expected cluster name; got: %q", ret.ClusterName)
}
}
func TestPerformRequestWithSimpleClient(t *testing.T) {
client, err := NewSimpleClient()
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), "GET", "/", nil, nil)
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response to be != nil")
}
ret := new(PingResult)
if err := json.Unmarshal(res.Body, ret); err != nil {
t.Fatalf("expected no error on decode; got: %v", err)
}
if ret.ClusterName == "" {
t.Errorf("expected cluster name; got: %q", ret.ClusterName)
}
}
func TestPerformRequestWithLogger(t *testing.T) {
var w bytes.Buffer
out := log.New(&w, "LOGGER ", log.LstdFlags)
client, err := NewClient(SetInfoLog(out), SetSniff(false))
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), "GET", "/", nil, nil)
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response to be != nil")
}
ret := new(PingResult)
if err := json.Unmarshal(res.Body, ret); err != nil {
t.Fatalf("expected no error on decode; got: %v", err)
}
if ret.ClusterName == "" {
t.Errorf("expected cluster name; got: %q", ret.ClusterName)
}
got := w.String()
pattern := `^LOGGER \d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} GET http://.*/ \[status:200, request:\d+\.\d{3}s\]\n`
matched, err := regexp.MatchString(pattern, got)
if err != nil {
t.Fatalf("expected log line to match %q; got: %v", pattern, err)
}
if !matched {
t.Errorf("expected log line to match %q; got: %v", pattern, got)
}
}
func TestPerformRequestWithLoggerAndTracer(t *testing.T) {
var lw bytes.Buffer
lout := log.New(&lw, "LOGGER ", log.LstdFlags)
var tw bytes.Buffer
tout := log.New(&tw, "TRACER ", log.LstdFlags)
client, err := NewClient(SetInfoLog(lout), SetTraceLog(tout), SetSniff(false))
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), "GET", "/", nil, nil)
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response to be != nil")
}
ret := new(PingResult)
if err := json.Unmarshal(res.Body, ret); err != nil {
t.Fatalf("expected no error on decode; got: %v", err)
}
if ret.ClusterName == "" {
t.Errorf("expected cluster name; got: %q", ret.ClusterName)
}
lgot := lw.String()
if lgot == "" {
t.Errorf("expected logger output; got: %q", lgot)
}
tgot := tw.String()
if tgot == "" {
t.Errorf("expected tracer output; got: %q", tgot)
}
}
func TestPerformRequestWithTracerOnError(t *testing.T) {
var tw bytes.Buffer
tout := log.New(&tw, "TRACER ", log.LstdFlags)
client, err := NewClient(SetTraceLog(tout), SetSniff(false))
if err != nil {
t.Fatal(err)
}
client.PerformRequest(context.TODO(), "GET", "/no-such-index", nil, nil)
tgot := tw.String()
if tgot == "" {
t.Errorf("expected tracer output; got: %q", tgot)
}
}
type customLogger struct {
out bytes.Buffer
}
func (l *customLogger) Printf(format string, v ...interface{}) {
l.out.WriteString(fmt.Sprintf(format, v...) + "\n")
}
func TestPerformRequestWithCustomLogger(t *testing.T) {
logger := &customLogger{}
client, err := NewClient(SetInfoLog(logger), SetSniff(false))
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), "GET", "/", nil, nil)
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response to be != nil")
}
ret := new(PingResult)
if err := json.Unmarshal(res.Body, ret); err != nil {
t.Fatalf("expected no error on decode; got: %v", err)
}
if ret.ClusterName == "" {
t.Errorf("expected cluster name; got: %q", ret.ClusterName)
}
got := logger.out.String()
pattern := `^GET http://.*/ \[status:200, request:\d+\.\d{3}s\]\n`
matched, err := regexp.MatchString(pattern, got)
if err != nil {
t.Fatalf("expected log line to match %q; got: %v", pattern, err)
}
if !matched {
t.Errorf("expected log line to match %q; got: %v", pattern, got)
}
}
// failingTransport will run a fail callback if it sees a given URL path prefix.
type failingTransport struct {
path string // path prefix to look for
fail func(*http.Request) (*http.Response, error) // call when path prefix is found
next http.RoundTripper // next round-tripper (use http.DefaultTransport if nil)
}
// RoundTrip implements a failing transport.
func (tr *failingTransport) RoundTrip(r *http.Request) (*http.Response, error) {
if strings.HasPrefix(r.URL.Path, tr.path) && tr.fail != nil {
return tr.fail(r)
}
if tr.next != nil {
return tr.next.RoundTrip(r)
}
return http.DefaultTransport.RoundTrip(r)
}
func TestPerformRequestRetryOnHttpError(t *testing.T) {
var numFailedReqs int
fail := func(r *http.Request) (*http.Response, error) {
numFailedReqs += 1
//return &http.Response{Request: r, StatusCode: 400}, nil
return nil, errors.New("request failed")
}
// Run against a failing endpoint and see if PerformRequest
// retries correctly.
tr := &failingTransport{path: "/fail", fail: fail}
httpClient := &http.Client{Transport: tr}
client, err := NewClient(SetHttpClient(httpClient), SetMaxRetries(5), SetHealthcheck(false))
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), "GET", "/fail", nil, nil)
if err == nil {
t.Fatal("expected error")
}
if res != nil {
t.Fatal("expected no response")
}
// Connection should be marked as dead after it failed
if numFailedReqs != 5 {
t.Errorf("expected %d failed requests; got: %d", 5, numFailedReqs)
}
}
func TestPerformRequestNoRetryOnValidButUnsuccessfulHttpStatus(t *testing.T) {
var numFailedReqs int
fail := func(r *http.Request) (*http.Response, error) {
numFailedReqs += 1
return &http.Response{Request: r, StatusCode: 500}, nil
}
// Run against a failing endpoint and see if PerformRequest
// retries correctly.
tr := &failingTransport{path: "/fail", fail: fail}
httpClient := &http.Client{Transport: tr}
client, err := NewClient(SetHttpClient(httpClient), SetMaxRetries(5), SetHealthcheck(false))
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), "GET", "/fail", nil, nil)
if err == nil {
t.Fatal("expected error")
}
if res == nil {
t.Fatal("expected response, got nil")
}
if want, got := 500, res.StatusCode; want != got {
t.Fatalf("expected status code = %d, got %d", want, got)
}
// Retry should not have triggered additional requests because
if numFailedReqs != 1 {
t.Errorf("expected %d failed requests; got: %d", 1, numFailedReqs)
}
}
// failingBody will return an error when json.Marshal is called on it.
type failingBody struct{}
// MarshalJSON implements the json.Marshaler interface and always returns an error.
func (fb failingBody) MarshalJSON() ([]byte, error) {
return nil, errors.New("failing to marshal")
}
func TestPerformRequestWithSetBodyError(t *testing.T) {
client, err := NewClient()
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), "GET", "/", nil, failingBody{})
if err == nil {
t.Fatal("expected error")
}
if res != nil {
t.Fatal("expected no response")
}
}
// sleepingTransport will sleep before doing a request.
type sleepingTransport struct {
timeout time.Duration
}
// RoundTrip implements a "sleepy" transport.
func (tr *sleepingTransport) RoundTrip(r *http.Request) (*http.Response, error) {
time.Sleep(tr.timeout)
return http.DefaultTransport.RoundTrip(r)
}
func TestPerformRequestWithCancel(t *testing.T) {
tr := &sleepingTransport{timeout: 3 * time.Second}
httpClient := &http.Client{Transport: tr}
client, err := NewSimpleClient(SetHttpClient(httpClient), SetMaxRetries(0))
if err != nil {
t.Fatal(err)
}
type result struct {
res *Response
err error
}
ctx, cancel := context.WithCancel(context.Background())
resc := make(chan result, 1)
go func() {
res, err := client.PerformRequest(ctx, "GET", "/", nil, nil)
resc <- result{res: res, err: err}
}()
select {
case <-time.After(1 * time.Second):
cancel()
case res := <-resc:
t.Fatalf("expected response before cancel, got %v", res)
case <-ctx.Done():
t.Fatalf("expected no early termination, got ctx.Done(): %v", ctx.Err())
}
err = ctx.Err()
if err != context.Canceled {
t.Fatalf("expected error context.Canceled, got: %v", err)
}
}
func TestPerformRequestWithTimeout(t *testing.T) {
tr := &sleepingTransport{timeout: 3 * time.Second}
httpClient := &http.Client{Transport: tr}
client, err := NewSimpleClient(SetHttpClient(httpClient), SetMaxRetries(0))
if err != nil {
t.Fatal(err)
}
type result struct {
res *Response
err error
}
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
resc := make(chan result, 1)
go func() {
res, err := client.PerformRequest(ctx, "GET", "/", nil, nil)
resc <- result{res: res, err: err}
}()
select {
case res := <-resc:
t.Fatalf("expected timeout before response, got %v", res)
case <-ctx.Done():
err := ctx.Err()
if err != context.DeadlineExceeded {
t.Fatalf("expected error context.DeadlineExceeded, got: %v", err)
}
}
}
// -- Compression --
// Notice that the trace log does always print "Accept-Encoding: gzip"
// regardless of whether compression is enabled or not. This is because
// of the underlying "httputil.DumpRequestOut".
//
// Use a real HTTP proxy/recorder to convince yourself that
// "Accept-Encoding: gzip" is NOT sent when DisableCompression
// is set to true.
//
// See also:
// https://groups.google.com/forum/#!topic/golang-nuts/ms8QNCzew8Q
func TestPerformRequestWithCompressionEnabled(t *testing.T) {
testPerformRequestWithCompression(t, &http.Client{
Transport: &http.Transport{
DisableCompression: true,
},
})
}
func TestPerformRequestWithCompressionDisabled(t *testing.T) {
testPerformRequestWithCompression(t, &http.Client{
Transport: &http.Transport{
DisableCompression: false,
},
})
}
func testPerformRequestWithCompression(t *testing.T, hc *http.Client) {
client, err := NewClient(SetHttpClient(hc), SetSniff(false))
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), "GET", "/", nil, nil)
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response to be != nil")
}
ret := new(PingResult)
if err := json.Unmarshal(res.Body, ret); err != nil {
t.Fatalf("expected no error on decode; got: %v", err)
}
if ret.ClusterName == "" {
t.Errorf("expected cluster name; got: %q", ret.ClusterName)
}
}