ElasticSearch + Go: ошибки индекса (нет функции для имени)

Я пытаюсь заставить ElasticSearch индексировать контент для службы автозаполнения, используя подсказки для завершения в версии 1.4x. Я следовал совету ElasticSearch - вы завершили меня, и я использую Go Client olivere/эластичный.

Мой метод индекса выглядит примерно так:

func IndexVehicle(client *elastic.Client, vehicle Vehicle) (bool, error) {
    // See if it exists already
    fetch, err := client.Get().
        Index(vehicleIndex).
        Type("vehicle").
        Id(vehicle.Id).
        Do()
    if err != nil || fetch.Found {
        return false, err
    }

    vehicleName := fmt.Sprintf("%s %s (%s) %s", vehicle.Make, vehicle.Model, vehicle.Model_year, vehicle.Primary_fuel)

    suggest := elastic.NewSuggestField()
    suggest.Input(vehicle.Make, vehicle.Model, vehicle.Primary_fuel, vehicle.Model_year).
        Output(vehicleName).
        Payload(vehicle)

    // Go forth and save
    put, err := client.Index().
        Index(vehicleIndex).
        Type("vehicle").
        Id(vehicle.Id).
        Debug(true).Pretty(true).
        BodyJson(indexBody{Name: vehicleName, Suggest: suggest}).
        Do()
    if err != nil {
        return false, err
    }
    return put.Created, nil
}

Что странно, так это то, что в некоторых моих тестах этот метод работает нормально, и элементы будут проиндексированы. В других тестах после очистки и перестроения индекса тесты не пройдут:

Более ранние тесты выглядят так в отладке:

2014/12/15 14:11:37 PUT /vehicle/vehicle/369f96459b340507c4688740da3bfe1a?pretty=true HTTP/1.1
Host: localhost:9200
User-Agent: elastic/1.3.1 (darwin-amd64)
Transfer-Encoding: chunked
Accept: application/json
Content-Type: application/json
Accept-Encoding: gzip

{"name":"American Motors Corporation Eagle 4WD (1986) regular","suggest":{"input":["American Motors Corporation","Eagle 4WD","regular","1986"],"output":"American Motors Corporation Eagle 4WD (1986) regular","payload":{"make":"American Motors Corporation","model_year":"1986","model":"Eagle 4WD","primary_fuel":"regular","vehicle_class":"Special Purpose Vehicle 4WD","transmission":"Automatic 3-spd","displacement":"4.2","drive":"4-Wheel or All-Wheel Drive","city_mpg":"15.0","highway_mpg":"19.0","comb_mpg":"17.0"}}}

2014/12/15 14:11:37 HTTP/1.1 201 Created
Content-Length: 134
Content-Type: application/json; charset=UTF-8

{
  "_index" : "vehicle",
  "_type" : "vehicle",
  "_id" : "369f96459b340507c4688740da3bfe1a",
  "_version" : 1,
  "created" : true
}

Но в более поздних тестах такое же действие приводит к ошибкам. err, возвращенный из IndexVehicle() в более поздних тестах:

E
Errors:

  * /Users/phil/go/src/github.com/ride/autocomplete/vehicle_test.go 
  Line 79: - elastic: Error 400: ElasticsearchIllegalArgumentException[No feature for name [vehicle]] 
  goroutine 245 [running]:
  github.com/ride/autocomplete.func·033()
    /Users/phil/go/src/github.com/ride/autocomplete/vehicle_test.go:79 +0x249
  github.com/ride/autocomplete.useIndex(0x499e98)
    /Users/phil/go/src/github.com/ride/autocomplete/test_helper.go:18 +0x55
  github.com/ride/autocomplete.func·034()
    /Users/phil/go/src/github.com/ride/autocomplete/vehicle_test.go:96 +0x2a
  github.com/jtolds/gls._m(0x0, 0xc2080ae9e0)
    /Users/phil/go/src/github.com/jtolds/gls/stack_tags.go:70 +0x32
  github.com/jtolds/gls.markS(0x0, 0xc2080ae9e0)
    /Users/phil/go/src/github.com/jtolds/gls/stack_tags.go:21 +0x32
  github.com/jtolds/gls.addStackTag(0x0, 0xc2080ae9e0)
    /Users/phil/go/src/github.com/jtolds/gls/stack_tags.go:18 +0x3e
  github.com/jtolds/gls.(*ContextManager).SetValues(0xc20801e080, 0xc2080b31d0, 0xc2080ae9e0)
    /Users/phil/go/src/github.com/jtolds/gls/context.go:98 +0x503
  github.com/ride/autocomplete.TestSearchForVehicles(0xc20806a480)
    /Users/phil/go/src/github.com/ride/autocomplete/vehicle_test.go:97 +0x243
  testing.tRunner(0xc20806a480, 0x5be890)
    /opt/boxen/homebrew/Cellar/go/1.4/libexec/src/testing/testing.go:447 +0xbf
  created by testing.RunTests
    /opt/boxen/homebrew/Cellar/go/1.4/libexec/src/testing/testing.go:555 +0xa8b

  goroutine 1 [chan receive]:
  testing.RunTests(0x49a078, 0x5be800, 0x7, 0x7, 0x67c001)
    /opt/boxen/homebrew/Cellar/go/1.4/libexec/src/testing/testing.go:556 +0xad6
  testing.(*M).Run(0xc2080463c0, 0x5c9b20)
    /opt/boxen/homebrew/Cellar/go/1.4/libexec/src/testing/testing.go:485 +0x6c
  main.main()
    github.com/ride/autocomplete/_test/_testmain.go:64 +0x1d5

  goroutine 208 [chan receive]:
  github.com/olivere/elastic.(*Client).pinger(0xc208106d40)
    /Users/phil/go/src/github.com/olivere/elastic/client.go:79 +0x6b
  created by github.com/olivere/elastic.NewClient
    /Users/phil/go/src/github.com/olivere/elastic/client.go:60 +0x26e

  goroutine 248 [runnable]:
  net/http.(*persistConn).readLoop(0xc20802e4d0)
    /opt/boxen/homebrew/Cellar/go/1.4/libexec/src/net/http/transport.go:928 +0x9ce
  created by net/http.(*Transport).dialConn
    /opt/boxen/homebrew/Cellar/go/1.4/libexec/src/net/http/transport.go:660 +0xc9f

  goroutine 98 [chan receive]:
  github.com/olivere/elastic.(*Client).pinger(0xc208033e00)
    /Users/phil/go/src/github.com/olivere/elastic/client.go:79 +0x6b
  created by github.com/olivere/elastic.NewClient
    /Users/phil/go/src/github.com/olivere/elastic/client.go:60 +0x26e

  goroutine 17 [syscall, locked to thread]:
  runtime.goexit()
    /opt/boxen/homebrew/Cellar/go/1.4/libexec/src/runtime/asm_amd64.s:2232 +0x1

  goroutine 44 [chan receive]:
  github.com/olivere/elastic.(*Client).pinger(0xc2080332c0)
    /Users/phil/go/src/github.com/olivere/elastic/client.go:79 +0x6b
  created by github.com/olivere/elastic.NewClient
    /Users/phil/go/src/github.com/olivere/elastic/client.go:60 +0x26e

  goroutine 249 [select]:
  net/http.(*persistConn).writeLoop(0xc20802e4d0)
    /opt/boxen/homebrew/Cellar/go/1.4/libexec/src/net/http/transport.go:945 +0x41d
  created by net/http.(*Transport).dialConn
    /opt/boxen/homebrew/Cellar/go/1.4/libexec/src/net/http/transport.go:661 +0xcbc

  goroutine 54 [chan receive]:
  github.com/olivere/elastic.(*Client).pinger(0xc208032f80)
    /Users/phil/go/src/github.com/olivere/elastic/client.go:79 +0x6b
  created by github.com/olivere/elastic.NewClient
    /Users/phil/go/src/github.com/olivere/elastic/client.go:60 +0x26e

  goroutine 76 [chan receive]:
  github.com/olivere/elastic.(*Client).pinger(0xc208032e00)
    /Users/phil/go/src/github.com/olivere/elastic/client.go:79 +0x6b
  created by github.com/olivere/elastic.NewClient
    /Users/phil/go/src/github.com/olivere/elastic/client.go:60 +0x26e

  goroutine 250 [chan receive]:
  github.com/olivere/elastic.(*Client).pinger(0xc208106c40)
    /Users/phil/go/src/github.com/olivere/elastic/client.go:79 +0x6b
  created by github.com/olivere/elastic.NewClient
    /Users/phil/go/src/github.com/olivere/elastic/client.go:60 +0x26e

  goroutine 120 [chan receive]:
  github.com/olivere/elastic.(*Client).pinger(0xc208106b00)
    /Users/phil/go/src/github.com/olivere/elastic/client.go:79 +0x6b
  created by github.com/olivere/elastic.NewClient
    /Users/phil/go/src/github.com/olivere/elastic/client.go:60 +0x26e

  goroutine 142 [chan receive]:
  github.com/olivere/elastic.(*Client).pinger(0xc208106e00)
    /Users/phil/go/src/github.com/olivere/elastic/client.go:79 +0x6b
  created by github.com/olivere/elastic.NewClient
    /Users/phil/go/src/github.com/olivere/elastic/client.go:60 +0x26e

  goroutine 164 [chan receive]:
  github.com/olivere/elastic.(*Client).pinger(0xc208106b80)
    /Users/phil/go/src/github.com/olivere/elastic/client.go:79 +0x6b
  created by github.com/olivere/elastic.NewClient
    /Users/phil/go/src/github.com/olivere/elastic/client.go:60 +0x26e

  goroutine 186 [chan receive]:
  github.com/olivere/elastic.(*Client).pinger(0xc208106d00)
    /Users/phil/go/src/github.com/olivere/elastic/client.go:79 +0x6b
  created by github.com/olivere/elastic.NewClient
    /Users/phil/go/src/github.com/olivere/elastic/client.go:60 +0x26e

  goroutine 230 [chan receive]:
  github.com/olivere/elastic.(*Client).pinger(0xc208106dc0)
    /Users/phil/go/src/github.com/olivere/elastic/client.go:79 +0x6b
  created by github.com/olivere/elastic.NewClient
    /Users/phil/go/src/github.com/olivere/elastic/client.go:60 +0x26e

Возможно, более важная часть этого следа - это:

Error 400: ElasticsearchIllegalArgumentException[No feature for name [vehicle]]

Так что, не уверен, что, черт возьми, здесь не так. У меня есть 1 секунда тайм-аута для индекса, потому что этот клиент не поддерживает логику "ожидания зеленого" [пока].

func ResetVehicleIndex(client *elastic.Client) (err error) {

    if _, err = client.DeleteIndex(vehicleIndex).Do(); err != nil {
        return
    }
    if _, err = EnsureVehicleIndex(client); err != nil {
        return
    }

    // TODO: This is awful. Switch to "wait for green" when elastic client supports it
    time.Sleep(time.Second * 1)

    return nil
}

Это сработало для большинства других тестов, так какasticsearch, кажется, готовится через секунду или около того, но эти тесты постоянно дают ошибки независимо от времени ожидания в этом бите кода.

Есть идеи? Возможно, мне придется отредактировать вопрос, чтобы добавить больше кода или более качественные объяснения, но я отвечу очень быстро на любые вопросы, особенно если вы пингуете меня в Twitter @philsturgeon. Я действительно застрял на этом.

Ответ 1

Это исключение срабатывает только в одном месте, которое происходит во время вызова API Get Index. Это означает, что здесь должен быть нулевой идентификатор вашего автомобиля:

fetch, err := client.Get().
        Index(vehicleIndex).
        Type("vehicle").
        Id(vehicle.Id).       //<-- this
        Do()

Вы пытаетесь создать API Get Document, соответствующий формату GET /{index}/{type}/{id}. Однако ваш клиент не делает различий между вызовами API Get Document и Get Index... и не проверяет, что ваши параметры не равны NULL.

Таким образом, если в метод Get передается нулевое значение vehicle.Id, ваш окончательный URL будет GET /{index}/{type}/

С точки зрения Elasticsearch, это больше не вызов API Get Document... это фактически вызов Get Index, который имеет следующий формат: GET /{index}/{feature}. Элемент может быть одним из: _settings, _mappings, _aliases или _warmers.

Поскольку vehicle не является одной из этих функций, ES выбрасывает исключение и извергает. Вы можете проверить это с консоли:

curl -XGET localhost:9200/my_index/vehicle/

Ответ 2

Просто для полноты: у меня была та же ошибка, когда я инициализировал свой метод запроса с "post" вместо "POST"

вот так:

req, _ := http.NewRequest("post", url, query)
// returns: Error 400: ElasticsearchIllegalArgumentException[No feature for name [_bulk]]
req, _ := http.NewRequest("POST", url, query)
// works just fine