500 lines
11 KiB
Go
500 lines
11 KiB
Go
// +build cluster
|
|
|
|
// This file is only built and run if you specify
|
|
// -tags=cluster as part of the 'go test' invocation.
|
|
// http://golang.org/pkg/go/build/#Build_Constraints
|
|
|
|
package elasticsearch_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
es "github.com/peterbourgon/elasticsearch"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func init() {
|
|
waitForCluster(15 * time.Second)
|
|
}
|
|
|
|
// Just tests es.Cluster internals; doesn't make any real connection.
|
|
func TestClusterShutdown(t *testing.T) {
|
|
endpoints := []string{"http://host1:9200", "http://host2:9200"}
|
|
pingInterval, pingTimeout := 30*time.Second, 3*time.Second
|
|
c := es.NewCluster(endpoints, pingInterval, pingTimeout)
|
|
|
|
e := make(chan error)
|
|
go func() {
|
|
c.Shutdown()
|
|
e <- nil
|
|
}()
|
|
go func() {
|
|
<-time.After(1 * time.Second)
|
|
e <- fmt.Errorf("timeout")
|
|
}()
|
|
|
|
if err := <-e; err != nil {
|
|
t.Fatalf("%s", err)
|
|
}
|
|
}
|
|
|
|
func TestClusterIndex(t *testing.T) {
|
|
c := newCluster(t, []string{"twitter"}, nil)
|
|
defer c.Shutdown()
|
|
defer deleteIndices(t, []string{"twitter"})
|
|
|
|
response, err := c.Index(es.IndexRequest{
|
|
es.IndexParams{
|
|
Index: "twitter",
|
|
Type: "tweet",
|
|
Id: "1",
|
|
Refresh: "true",
|
|
},
|
|
map[string]interface{}{
|
|
"name": "John",
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if response.Error != "" {
|
|
t.Error(response.Error)
|
|
}
|
|
|
|
if expected, got := 1, response.Version; expected != got {
|
|
t.Errorf("expected version to be %d; got %d", expected, got)
|
|
}
|
|
}
|
|
|
|
func TestClusterCreate(t *testing.T) {
|
|
c := newCluster(t, []string{"twitter"}, nil)
|
|
defer c.Shutdown()
|
|
defer deleteIndices(t, []string{"twitter"})
|
|
|
|
response, err := c.Create(es.CreateRequest{
|
|
es.IndexParams{
|
|
Index: "twitter",
|
|
Type: "tweet",
|
|
Id: "1",
|
|
Refresh: "true",
|
|
},
|
|
map[string]interface{}{
|
|
"name": "John",
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if response.Error != "" {
|
|
t.Error(response.Error)
|
|
}
|
|
|
|
if expected, got := 1, response.Version; expected != got {
|
|
t.Errorf("expected version to be %d; got %d", expected, got)
|
|
}
|
|
}
|
|
|
|
func TestClusterUpdate(t *testing.T) {
|
|
c := newCluster(t, []string{"twitter"}, map[string]interface{}{
|
|
"/twitter/tweet/1": map[string]string{
|
|
"name": "John",
|
|
},
|
|
})
|
|
defer c.Shutdown()
|
|
defer deleteIndices(t, []string{"twitter"})
|
|
|
|
response, err := c.Update(es.UpdateRequest{
|
|
es.IndexParams{
|
|
Index: "twitter",
|
|
Type: "tweet",
|
|
Id: "1",
|
|
Refresh: "true",
|
|
},
|
|
map[string]interface{}{
|
|
"script": `ctx._source.text = "some text"`,
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if response.Error != "" {
|
|
t.Error(response.Error)
|
|
}
|
|
|
|
if expected, got := 2, response.Version; expected != got {
|
|
t.Errorf("expected version to be %d; got %d", expected, got)
|
|
}
|
|
}
|
|
|
|
func TestClusterDelete(t *testing.T) {
|
|
c := newCluster(t, []string{"twitter"}, map[string]interface{}{
|
|
"/twitter/tweet/1": map[string]string{
|
|
"name": "John",
|
|
},
|
|
})
|
|
defer c.Shutdown()
|
|
defer deleteIndices(t, []string{"twitter"})
|
|
|
|
response, err := c.Delete(es.DeleteRequest{
|
|
es.IndexParams{
|
|
Index: "twitter",
|
|
Type: "tweet",
|
|
Id: "1",
|
|
Refresh: "true",
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if response.Error != "" {
|
|
t.Error(response.Error)
|
|
}
|
|
|
|
if expected, got := 2, response.Version; expected != got {
|
|
t.Errorf("expected version to be %d; got %d", expected, got)
|
|
}
|
|
}
|
|
|
|
func TestClusterBulk(t *testing.T) {
|
|
c := newCluster(t, []string{"twitter"}, map[string]interface{}{
|
|
"/twitter/tweet/1": map[string]string{
|
|
"name": "John",
|
|
},
|
|
})
|
|
defer c.Shutdown()
|
|
defer deleteIndices(t, []string{"twitter"})
|
|
|
|
response, err := c.Bulk(es.BulkRequest{
|
|
es.BulkParams{Refresh: "true"},
|
|
[]es.BulkIndexable{
|
|
es.IndexRequest{
|
|
es.IndexParams{Index: "twitter", Type: "tweet", Id: "1"},
|
|
map[string]interface{}{"name": "James"},
|
|
},
|
|
es.DeleteRequest{
|
|
es.IndexParams{Index: "twitter", Type: "tweet", Id: "2"},
|
|
},
|
|
es.CreateRequest{
|
|
es.IndexParams{Index: "twitter", Type: "tweet", Id: "3"},
|
|
map[string]interface{}{"name": "John"},
|
|
},
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(response.Items) != 3 {
|
|
t.Fatalf("expected 3 responses, got %d", len(response.Items))
|
|
}
|
|
|
|
if expected, got := 2, response.Items[0].Version; expected != got {
|
|
t.Errorf("expected version of doc to be %d; got %d", expected, got)
|
|
}
|
|
|
|
if expected, got := false, response.Items[1].Found; expected != got {
|
|
t.Errorf("expected delete op to return found = false")
|
|
}
|
|
|
|
if expected, got := 1, response.Items[2].Version; expected != got {
|
|
t.Errorf("expected version of doc to be %d; got %d", expected, got)
|
|
}
|
|
}
|
|
|
|
func TestSimpleTermQuery(t *testing.T) {
|
|
indices := []string{"twitter"}
|
|
c := newCluster(t, indices, map[string]interface{}{
|
|
"/twitter/tweet/1": map[string]string{
|
|
"user": "kimchy",
|
|
"post_date": "2009-11-15T14:12:12",
|
|
"message": "trying out Elastic Search",
|
|
},
|
|
})
|
|
defer c.Shutdown()
|
|
defer deleteIndices(t, indices) // comment out to leave data after test
|
|
|
|
q := es.QueryWrapper(es.TermQuery(es.TermQueryParams{
|
|
Query: &es.Wrapper{
|
|
Name: "user",
|
|
Wrapped: "kimchy",
|
|
},
|
|
}))
|
|
|
|
request := es.SearchRequest{
|
|
es.SearchParams{
|
|
Indices: []string{"twitter"},
|
|
Types: []string{"tweet"},
|
|
},
|
|
q,
|
|
}
|
|
|
|
response, err := c.Search(request)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
if response.Error != "" {
|
|
t.Error(response.Error)
|
|
}
|
|
if expected, got := 1, response.HitsWrapper.Total; expected != got {
|
|
t.Fatalf("expected %d, got %d", expected, got)
|
|
}
|
|
|
|
t.Logf("OK, %d hit(s), %dms", response.HitsWrapper.Total, response.Took)
|
|
}
|
|
|
|
func TestMultiSearch(t *testing.T) {
|
|
indices := []string{"index1", "index2"}
|
|
c := newCluster(t, indices, map[string]interface{}{
|
|
"/index1/foo/1": map[string]string{
|
|
"user": "alice",
|
|
"description": "index=index1 type=foo id=1 user=alice",
|
|
},
|
|
"/index2/bar/2": map[string]string{
|
|
"user": "bob",
|
|
"description": "index=index2 type=bar id=2 user=bob",
|
|
},
|
|
})
|
|
defer c.Shutdown()
|
|
defer deleteIndices(t, indices) // comment out to leave data after test
|
|
|
|
q1 := es.QueryWrapper(es.TermQuery(es.TermQueryParams{
|
|
Query: &es.Wrapper{
|
|
Name: "user",
|
|
Wrapped: "alice",
|
|
},
|
|
}))
|
|
q2 := es.QueryWrapper(es.TermQuery(es.TermQueryParams{
|
|
Query: &es.Wrapper{
|
|
Name: "user",
|
|
Wrapped: "bob",
|
|
},
|
|
}))
|
|
q3 := es.QueryWrapper(es.MatchAllQuery())
|
|
|
|
request := es.MultiSearchRequest{
|
|
Requests: []es.SearchRequest{
|
|
es.SearchRequest{
|
|
es.SearchParams{
|
|
Indices: []string{"index1"},
|
|
Types: []string{"foo"},
|
|
},
|
|
q1,
|
|
},
|
|
es.SearchRequest{
|
|
es.SearchParams{
|
|
Indices: []string{"index2"},
|
|
Types: []string{"bar"},
|
|
},
|
|
q2,
|
|
},
|
|
es.SearchRequest{
|
|
es.SearchParams{
|
|
Indices: []string{}, // "index1", "index2" is not supported (!)
|
|
Types: []string{}, // "type1", "type2" is not supported (!)
|
|
},
|
|
q3,
|
|
},
|
|
},
|
|
}
|
|
|
|
response, err := c.MultiSearch(request)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if expected, got := 3, len(response.Responses); expected != got {
|
|
t.Fatalf("expected %d response(s), got %d", expected, got)
|
|
}
|
|
|
|
r1 := response.Responses[0]
|
|
if r1.Error != "" {
|
|
t.Fatalf("response 1: %s", r1.Error)
|
|
}
|
|
if expected, got := 1, r1.HitsWrapper.Total; expected != got {
|
|
t.Fatalf("response 1: expected %d hit(s), got %d", expected, got)
|
|
}
|
|
buf, _ := json.Marshal(r1)
|
|
t.Logf("response 1 OK: %s", buf)
|
|
|
|
r2 := response.Responses[1]
|
|
if r2.Error != "" {
|
|
t.Fatalf("response 2: %s", r1.Error)
|
|
}
|
|
if expected, got := 1, r2.HitsWrapper.Total; expected != got {
|
|
t.Fatalf("response 2: expected %d hit(s), got %d", expected, got)
|
|
}
|
|
buf, _ = json.Marshal(r2)
|
|
t.Logf("response 2 OK: %s", buf)
|
|
|
|
r3 := response.Responses[2]
|
|
if r3.Error != "" {
|
|
t.Fatalf("response 3: %s", r1.Error)
|
|
}
|
|
if expected, got := 2, r3.HitsWrapper.Total; expected != got {
|
|
t.Fatalf("response 3: expected %d hit(s), got %d", expected, got)
|
|
}
|
|
buf, _ = json.Marshal(r3)
|
|
t.Logf("response 3 OK: %s", buf)
|
|
}
|
|
|
|
func TestConstantScoreNoScore(t *testing.T) {
|
|
indices := []string{"twitter"}
|
|
c := newCluster(t, indices, map[string]interface{}{
|
|
"/twitter/tweet/1": map[string]string{
|
|
"user": "kimchy",
|
|
"post_date": "2009-11-15T14:12:12",
|
|
"message": "trying out Elastic Search",
|
|
},
|
|
})
|
|
defer c.Shutdown()
|
|
defer deleteIndices(t, indices) // comment out to leave data after test
|
|
|
|
q := map[string]interface{}{
|
|
"size": 20,
|
|
"sort": []string{"post_date"},
|
|
"filter": map[string]interface{}{
|
|
"and": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"type": map[string]string{"value": "tweet"},
|
|
},
|
|
},
|
|
},
|
|
"query": map[string]interface{}{
|
|
"constant_score": map[string]interface{}{
|
|
"filter": map[string]interface{}{
|
|
"term": map[string]string{"user": "kimchy"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
request := es.SearchRequest{
|
|
es.SearchParams{
|
|
Indices: []string{"twitter"},
|
|
Types: []string{"tweet"},
|
|
},
|
|
q,
|
|
}
|
|
|
|
response, err := c.Search(request)
|
|
if err != nil {
|
|
t.Fatalf("Search: %s", err)
|
|
}
|
|
|
|
buf, _ := json.Marshal(response)
|
|
t.Logf("got response: %s", buf)
|
|
|
|
if response.Error != "" {
|
|
t.Error(response.Error)
|
|
}
|
|
if expected, got := 1, response.HitsWrapper.Total; expected != got {
|
|
t.Fatalf("expected %d, got %d", expected, got)
|
|
}
|
|
|
|
if response.HitsWrapper.Hits[0].Score != nil {
|
|
t.Fatalf("score: expected nil, got something")
|
|
}
|
|
|
|
t.Logf("OK, %d hit(s), %dms", response.HitsWrapper.Total, response.Took)
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
func waitForCluster(timeout time.Duration) {
|
|
giveUp := time.After(timeout)
|
|
delay := 100 * time.Millisecond
|
|
for {
|
|
_, err := http.Get("http://127.0.0.1:9200")
|
|
if err == nil {
|
|
fmt.Printf("ElasticSearch now available\n")
|
|
return // great
|
|
}
|
|
|
|
fmt.Printf("ElasticSearch not ready yet; waiting %s\n", delay)
|
|
select {
|
|
case <-time.After(delay):
|
|
delay *= 2
|
|
case <-giveUp:
|
|
panic("ElasticSearch didn't come up in time")
|
|
}
|
|
}
|
|
}
|
|
|
|
func newCluster(t *testing.T, indices []string, m map[string]interface{}) *es.Cluster {
|
|
deleteIndices(t, indices)
|
|
loadData(t, m)
|
|
|
|
endpoints := []string{"http://localhost:9200"}
|
|
pingInterval, pingTimeout := 10*time.Second, 3*time.Second
|
|
return es.NewCluster(endpoints, pingInterval, pingTimeout)
|
|
}
|
|
|
|
func deleteIndices(t *testing.T, indices []string) {
|
|
for _, index := range indices {
|
|
// refresh=true to make document(s) immediately deleted
|
|
url := "http://127.0.0.1:9200/" + index + "?refresh=true"
|
|
req, err := http.NewRequest("DELETE", url, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
respBuf, err := ioutil.ReadAll(resp.Body)
|
|
resp.Body.Close()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Logf("DELETE %s: %s", index, respBuf)
|
|
}
|
|
}
|
|
|
|
func loadData(t *testing.T, m map[string]interface{}) {
|
|
for path, body := range m {
|
|
reqBytes, err := json.Marshal(body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// refresh=true to make document(s) immediately searchable
|
|
url := "http://127.0.0.1:9200" + path + "?refresh=true"
|
|
req, err := http.NewRequest("PUT", url, bytes.NewBuffer(reqBytes))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
respBuf, err := ioutil.ReadAll(resp.Body)
|
|
resp.Body.Close()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Logf("PUT %s: %s", path, respBuf)
|
|
}
|
|
}
|