Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@

package org.elasticsearch.xpack.esql.plugin;

import org.elasticsearch.ElasticsearchTimeoutException;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.MatchNoneQueryBuilder;
import org.elasticsearch.transport.NoSuchRemoteClusterException;
import org.elasticsearch.xpack.esql.VerificationException;
import org.elasticsearch.xpack.esql.action.AbstractCrossClusterTestCase;
import org.elasticsearch.xpack.esql.action.EsqlExecutionInfo.Cluster.Status;
import org.elasticsearch.xpack.esql.action.EsqlQueryAction;
import org.elasticsearch.xpack.esql.action.EsqlQueryRequest;
import org.elasticsearch.xpack.esql.action.EsqlQueryResponse;
import org.elasticsearch.xpack.esql.core.expression.MetadataAttribute;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

Expand All @@ -29,9 +32,14 @@ public class RemoteIndexResolutionIT extends AbstractCrossClusterTestCase {
public void testResolvesRemoteIndex() {
indexRandom(REMOTE_CLUSTER_1, true, "index-1", 1);

try (var response = run(syncEsqlQueryRequest().query("FROM " + REMOTE_CLUSTER_1 + ":index-1 METADATA _index"))) {
try (
var response = run(
syncEsqlQueryRequest().query("FROM " + REMOTE_CLUSTER_1 + ":index-1 METADATA _index").includeCCSMetadata(true)
)
) {
assertOk(response);
assertResultConcreteIndices(response, REMOTE_CLUSTER_1 + ":index-1");
assertExecutionInfo(response, new EsqlResponseExecutionInfo(REMOTE_CLUSTER_1, "index-1", Status.SUCCESSFUL));
}
}

Expand All @@ -40,6 +48,17 @@ public void testResolveRemoteUnknownIndex() {
// This index is mixed into the resultset to test error handling of the missing concrete remote, not the empty result.
indexRandom(LOCAL_CLUSTER, true, "data", 1);

expectThrows(
VerificationException.class,
containsString("Unknown index [" + REMOTE_CLUSTER_1 + ":fake]"),
() -> run(syncEsqlQueryRequest().query("FROM data," + REMOTE_CLUSTER_1 + ":fake"))
);
expectThrows(
VerificationException.class,
containsString("Unknown index [" + REMOTE_CLUSTER_1 + ":fake]"),
() -> run(syncEsqlQueryRequest().query("FROM data," + REMOTE_CLUSTER_1 + ":fake").allowPartialResults(true))
);

setSkipUnavailable(REMOTE_CLUSTER_1, false);
expectThrows(
VerificationException.class,
Expand All @@ -48,25 +67,52 @@ public void testResolveRemoteUnknownIndex() {
);

setSkipUnavailable(REMOTE_CLUSTER_1, true);
try (var response = run(syncEsqlQueryRequest().query("FROM data," + REMOTE_CLUSTER_1 + ":fake METADATA _index"))) {
try (
var response = run(
syncEsqlQueryRequest().query("FROM data," + REMOTE_CLUSTER_1 + ":fake METADATA _index").includeCCSMetadata(true)
)
) {
assertPartial(response);
assertResultConcreteIndices(response, "data");
assertExecutionInfo(
response,
new EsqlResponseExecutionInfo(LOCAL_CLUSTER, "data", Status.SUCCESSFUL),
new EsqlResponseExecutionInfo(REMOTE_CLUSTER_1, "fake", Status.SKIPPED)
);
}

setSkipUnavailable(REMOTE_CLUSTER_1, null);
try (var response = run(syncEsqlQueryRequest().query("FROM data," + REMOTE_CLUSTER_1 + ":fake METADATA _index"))) {
try (
var response = run(
syncEsqlQueryRequest().query("FROM data," + REMOTE_CLUSTER_1 + ":fake METADATA _index").includeCCSMetadata(true)
)
) {
assertPartial(response);
assertResultConcreteIndices(response, "data");
assertExecutionInfo(
response,
new EsqlResponseExecutionInfo(LOCAL_CLUSTER, "data", Status.SUCCESSFUL),
new EsqlResponseExecutionInfo(REMOTE_CLUSTER_1, "fake", Status.SKIPPED)
);
}
}

public void testResolvesLocalAndRemoteIndex() {
indexRandom(LOCAL_CLUSTER, true, "index-1", 1);
indexRandom(REMOTE_CLUSTER_1, true, "index-1", 1);

try (var response = run(syncEsqlQueryRequest().query("FROM index-1," + REMOTE_CLUSTER_1 + ":index-1 METADATA _index"))) {
try (
var response = run(
syncEsqlQueryRequest().query("FROM index-1," + REMOTE_CLUSTER_1 + ":index-1 METADATA _index").includeCCSMetadata(true)
)
) {
assertOk(response);
assertResultConcreteIndices(response, "index-1", REMOTE_CLUSTER_1 + ":index-1");
assertExecutionInfo(
response,
new EsqlResponseExecutionInfo(LOCAL_CLUSTER, "index-1", Status.SUCCESSFUL),
new EsqlResponseExecutionInfo(REMOTE_CLUSTER_1, "index-1", Status.SUCCESSFUL)
);
}
}

Expand All @@ -75,13 +121,19 @@ public void testResolvesRemotesWithPattern() {
indexRandom(REMOTE_CLUSTER_1, true, "index-1", 1);
indexRandom(REMOTE_CLUSTER_2, true, "index-1", 1);

try (var response = run(syncEsqlQueryRequest().query("FROM *:index-1 METADATA _index"))) {
try (var response = run(syncEsqlQueryRequest().query("FROM *:index-1 METADATA _index").includeCCSMetadata(true))) {
assertOk(response);
assertResultConcreteIndices(response, REMOTE_CLUSTER_1 + ":index-1", REMOTE_CLUSTER_2 + ":index-1"); // local is not included
assertExecutionInfo(
response,
new EsqlResponseExecutionInfo(REMOTE_CLUSTER_1, "index-1", Status.SUCCESSFUL),
new EsqlResponseExecutionInfo(REMOTE_CLUSTER_2, "index-1", Status.SUCCESSFUL)
);
}
try (var response = run(syncEsqlQueryRequest().query("FROM fake*:index-1 METADATA _index"))) {
try (var response = run(syncEsqlQueryRequest().query("FROM fake*:index-1 METADATA _index").includeCCSMetadata(true))) {
assertOk(response);
assertResultConcreteIndices(response); // empty
assertExecutionInfo(response); // empty
}
}

Expand All @@ -93,14 +145,33 @@ public void testDoesNotResolvesUnknownRemote() {
);
}

private EsqlQueryResponse run(EsqlQueryRequest request) {
try {
return client(LOCAL_CLUSTER).execute(EsqlQueryAction.INSTANCE, request).actionGet(30, TimeUnit.SECONDS);
} catch (ElasticsearchTimeoutException e) {
throw new AssertionError("timeout", e);
public void testResolutionWithFilter() {
indexRandom(REMOTE_CLUSTER_1, true, "index-1", 1);

try (
var response = run(
syncEsqlQueryRequest().query("FROM " + REMOTE_CLUSTER_1 + ":index-1 METADATA _index").filter(new MatchAllQueryBuilder())
)
) {
assertOk(response);
assertResultConcreteIndices(response, REMOTE_CLUSTER_1 + ":index-1");
assertExecutionInfo(response, new EsqlResponseExecutionInfo(REMOTE_CLUSTER_1, "index-1", Status.SUCCESSFUL));
}
try (
var response = run(
syncEsqlQueryRequest().query("FROM " + REMOTE_CLUSTER_1 + ":index-1 METADATA _index").filter(new MatchNoneQueryBuilder())
)
) {
assertOk(response);
assertResultConcreteIndices(response);
assertExecutionInfo(response, new EsqlResponseExecutionInfo(REMOTE_CLUSTER_1, "index-1", Status.SUCCESSFUL));
}
}

private EsqlQueryResponse run(EsqlQueryRequest request) {
return client(LOCAL_CLUSTER).execute(EsqlQueryAction.INSTANCE, request).actionGet(30, TimeUnit.SECONDS);
}

private void indexRandom(String clusterAlias, boolean forceRefresh, String index, int numDocs) {
assert numDocs == 1;
var client = client(clusterAlias);
Expand All @@ -123,6 +194,10 @@ private static void assertResultConcreteIndices(EsqlQueryResponse response, Obje
assertThat(() -> response.column(indexColumn), containsInAnyOrder(indices));
}

private static void assertExecutionInfo(EsqlQueryResponse response, EsqlResponseExecutionInfo... infos) {
assertThat(executionInfo(response), containsInAnyOrder(infos));
}

private static int findIndexColumn(EsqlQueryResponse response) {
for (int c = 0; c < response.columns().size(); c++) {
if (Objects.equals(response.columns().get(c).name(), MetadataAttribute.INDEX)) {
Expand All @@ -131,4 +206,15 @@ private static int findIndexColumn(EsqlQueryResponse response) {
}
throw new AssertionError("no _index column found");
}

private static List<EsqlResponseExecutionInfo> executionInfo(EsqlQueryResponse response) {
return response.getExecutionInfo()
.getClusters()
.values()
.stream()
.map(cluster -> new EsqlResponseExecutionInfo(cluster.getClusterAlias(), cluster.getIndexExpression(), cluster.getStatus()))
.toList();
}

private record EsqlResponseExecutionInfo(String alias, String index, Status status) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,16 +143,18 @@ public void profile(boolean profile) {
this.profile = profile;
}

public void includeCCSMetadata(Boolean include) {
public EsqlQueryRequest includeCCSMetadata(Boolean include) {
this.includeCCSMetadata = include;
return this;
}

public Boolean includeCCSMetadata() {
return includeCCSMetadata;
}

public void includeExecutionMetadata(Boolean include) {
public EsqlQueryRequest includeExecutionMetadata(Boolean include) {
this.includeExecutionMetadata = include;
return this;
}

public Boolean includeExecutionMetadata() {
Expand Down