Description
Java API client version
8.7.1
Java version
17
Elasticsearch Version
8.12.2
Problem description
According to https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-exists.html#indices-exists-api-response-codes 404 http response code should be mapped to false result.
However, when working with ElasticsearchIndicesClient.exists(req)
method it turns out that it can return false value in cases when ES would return 400, 401, 403 or 405 http response codes.
Code analysis
RestClientHttpClient.createRestRequest()
method creates a request (that is later used in exists method) with very specific line:
// Request parameter intercepted by LLRC
clientReq.addParameter("ignore", "400,401,403,404,405");
public BooleanResponse exists(ExistsRequest request)
- ExistsRequests
indicates that it is a BooleanEndpoint
BooleanEndpoint
:
@Override
public boolean isError(int statusCode) {
return statusCode >= 500;
}
public boolean getResult(int statusCode) {
return statusCode < 400;
}
We can see that isError
implementation considers only 5xx codes as incorrect statuses, while all 4xx would return boolean value.
Such logic allows for example that forbidden 403 or unauthorized 401 errors may be mapped to false value without the actual check if index exists.
Attaching also a test written in groovy/spock/wiremock that reproduces the problem (ElasticsearchClientSpec.groovy)
ElasticsearchClientSpec.groovy
package testimport co.elastic.clients.elasticsearch.indices.ExistsRequest
import com.github.tomakehurst.wiremock.junit.WireMockRule
import groovy.util.logging.Slf4j
import org.junit.Rule
import org.springframework.data.elasticsearch.client.ClientConfiguration
import org.springframework.data.elasticsearch.client.elc.ElasticsearchClients
import spock.lang.Specification
import static com.github.tomakehurst.wiremock.client.WireMock.*
@slf4j
class ElasticsearchClientSpec extends Specification {
@Rule
public WireMockRule wireMockRule = new WireMockRule();
def setup() {
wireMockRule.start()
stubFor(get("/")
.withHeader("Content-Type", containing("application/json"))
.willReturn(ok()
.withHeader("Content-Type", "application/json")
.withBody("""
{
"name" : "test-elastic",
"cluster_name" : "test-cluster",
"cluster_uuid" : "clusterId1",
"version" : {
"number" : "8.12.2",
"build_flavor" : "default",
"build_type" : "docker",
"build_hash" : "48a287ab9497e852de30327444b0809e55d46466",
"build_date" : "2024-02-19T10:04:32.774273190Z",
"build_snapshot" : false,
"lucene_version" : "9.9.2",
"minimum_wire_compatibility_version" : "7.17.0",
"minimum_index_compatibility_version" : "7.0.0"
},
"tagline" : "You Know, for Search"
}
""")));
stubFor(head(urlEqualTo("/fake-index-name"))
.willReturn(forbidden()
.withHeader("Content-Type", "application/json")
.withBody("")))
}
def 'should add index prefix if present'() {
given:
ClientConfiguration clientConfiguration = ClientConfiguration.builder().connectedTo("127.0.0.1:8080").build()
def elasticsearchClient = ElasticsearchClients.createImperative(clientConfiguration);
when:
ExistsRequest existsRequest = ExistsRequest.of(request -> request.index("fake-index-name"));
def existsResult = elasticsearchClient.indices().exists(existsRequest).value();
log.error("existsResult: " + existsResult)
then:
def t = thrown(Throwable)
log.info("The exception is: ", t)
}
}