|
20 | 20 |
|
21 | 21 | import java.io.IOException;
|
22 | 22 | import java.util.ArrayList;
|
| 23 | +import java.util.Collection; |
23 | 24 | import java.util.List;
|
24 | 25 | import java.util.Map;
|
25 | 26 | import java.util.concurrent.atomic.AtomicBoolean;
|
@@ -72,13 +73,19 @@ public class CrossClusterEsqlRCS1MissingIndicesIT extends AbstractRemoteClusterS
|
72 | 73 | private static final String INDEX1 = "points"; // on local cluster only
|
73 | 74 | private static final String INDEX2 = "squares"; // on local and remote clusters
|
74 | 75 |
|
75 |
| - record ExpectedCluster(String clusterAlias, String indexExpression, String status, Integer totalShards) {} |
| 76 | + record ExpectedFailure(String type, Collection<String> messages) {} |
| 77 | + |
| 78 | + record ExpectedCluster(String clusterAlias, String indexExpression, String status, Integer totalShards, ExpectedFailure failure) { |
| 79 | + ExpectedCluster(String clusterAlias, String indexExpression, String status, Integer totalShards) { |
| 80 | + this(clusterAlias, indexExpression, status, totalShards, null); |
| 81 | + } |
| 82 | + } |
76 | 83 |
|
77 | 84 | @SuppressWarnings("unchecked")
|
78 | 85 | void assertExpectedClustersForMissingIndicesTests(Map<String, Object> responseMap, List<ExpectedCluster> expected) {
|
| 86 | + boolean hasPartial = false; |
79 | 87 | Map<String, ?> clusters = (Map<String, ?>) responseMap.get("_clusters");
|
80 | 88 | assertThat((int) responseMap.get("took"), greaterThan(0));
|
81 |
| - assertThat((boolean) responseMap.get("is_partial"), is(false)); |
82 | 89 |
|
83 | 90 | Map<String, ?> detailsMap = (Map<String, ?>) clusters.get("details");
|
84 | 91 | assertThat(detailsMap.size(), is(expected.size()));
|
@@ -112,24 +119,28 @@ void assertExpectedClustersForMissingIndicesTests(Map<String, Object> responseMa
|
112 | 119 | assertThat(failures.size(), is(1));
|
113 | 120 | Map<String, ?> failure1 = (Map<String, ?>) failures.get(0);
|
114 | 121 | Map<String, ?> innerReason = (Map<String, ?>) failure1.get("reason");
|
115 |
| - String expectedMsg = "Unknown index [" + expectedCluster.indexExpression() + "]"; |
116 |
| - assertThat(innerReason.get("reason").toString(), containsString(expectedMsg)); |
117 |
| - assertThat(innerReason.get("type").toString(), containsString("verification_exception")); |
118 |
| - |
| 122 | + if (expectedCluster.failure() != null) { |
| 123 | + for (var f : expectedCluster.failure().messages()) { |
| 124 | + assertThat(innerReason.get("reason").toString(), containsString(f)); |
| 125 | + } |
| 126 | + assertThat(innerReason.get("type").toString(), containsString(expectedCluster.failure().type())); |
| 127 | + } |
| 128 | + hasPartial = true; |
119 | 129 | } else {
|
120 | 130 | fail(msg + "; Unexpected status: " + expectedCluster.status());
|
121 | 131 | }
|
122 | 132 | // currently failed shards is always zero - change this once we start allowing partial data for individual shard failures
|
123 | 133 | assertThat((int) shards.get("failed"), is(0));
|
124 | 134 | }
|
| 135 | + assertThat((boolean) responseMap.get("is_partial"), is(hasPartial)); |
125 | 136 | }
|
126 | 137 |
|
127 | 138 | @SuppressWarnings("unchecked")
|
128 | 139 | public void testSearchesAgainstNonMatchingIndices() throws Exception {
|
129 | 140 | setupRolesAndPrivileges();
|
130 | 141 | setupIndex();
|
131 | 142 |
|
132 |
| - configureRemoteCluster(REMOTE_CLUSTER_ALIAS, fulfillingCluster, true, randomBoolean(), randomBoolean()); |
| 143 | + configureRemoteCluster(REMOTE_CLUSTER_ALIAS, fulfillingCluster, true, randomBoolean(), false); |
133 | 144 |
|
134 | 145 | // missing concrete local index is an error
|
135 | 146 | {
|
@@ -242,6 +253,153 @@ public void testSearchesAgainstNonMatchingIndices() throws Exception {
|
242 | 253 | }
|
243 | 254 | }
|
244 | 255 |
|
| 256 | + public void testSearchesAgainstNonMatchingIndicesSkipUnavailableTrue() throws Exception { |
| 257 | + setupRolesAndPrivileges(); |
| 258 | + setupIndex(); |
| 259 | + |
| 260 | + configureRemoteCluster(REMOTE_CLUSTER_ALIAS, fulfillingCluster, true, randomBoolean(), true); |
| 261 | + |
| 262 | + // missing concrete local index is an error |
| 263 | + // TODO: this may change soon |
| 264 | + { |
| 265 | + String q = Strings.format("FROM nomatch,%s:%s | STATS count(*)", REMOTE_CLUSTER_ALIAS, INDEX2); |
| 266 | + |
| 267 | + String limit1 = q + " | LIMIT 1"; |
| 268 | + ResponseException e = expectThrows(ResponseException.class, () -> client().performRequest(esqlRequest(limit1))); |
| 269 | + assertThat(e.getMessage(), containsString("Unknown index [nomatch]")); |
| 270 | + |
| 271 | + String limit0 = q + " | LIMIT 0"; |
| 272 | + e = expectThrows(ResponseException.class, () -> client().performRequest(esqlRequest(limit0))); |
| 273 | + assertThat(e.getMessage(), containsString("Unknown index [nomatch]")); |
| 274 | + } |
| 275 | + |
| 276 | + // missing concrete index on remote cluster is a failure if it's alone |
| 277 | + { |
| 278 | + String q = Strings.format("FROM %s:nomatch", REMOTE_CLUSTER_ALIAS); |
| 279 | + |
| 280 | + String limit1 = q + " | LIMIT 1"; |
| 281 | + ResponseException e = expectThrows(ResponseException.class, () -> client().performRequest(esqlRequest(limit1))); |
| 282 | + assertThat(e.getMessage(), containsString(Strings.format("Unknown index [%s:nomatch]", REMOTE_CLUSTER_ALIAS))); |
| 283 | + |
| 284 | + String limit0 = q + " | LIMIT 0"; |
| 285 | + e = expectThrows(ResponseException.class, () -> client().performRequest(esqlRequest(limit0))); |
| 286 | + assertThat(e.getMessage(), containsString(Strings.format("Unknown index [%s:nomatch]", REMOTE_CLUSTER_ALIAS))); |
| 287 | + } |
| 288 | + |
| 289 | + // since there is at least one matching index in the query, the missing wildcarded local index is not an error |
| 290 | + { |
| 291 | + String q = Strings.format("FROM nomatch*,%s:%s", REMOTE_CLUSTER_ALIAS, INDEX2); |
| 292 | + |
| 293 | + String limit1 = q + " | LIMIT 1"; |
| 294 | + Response response = client().performRequest(esqlRequest(limit1)); |
| 295 | + assertOK(response); |
| 296 | + |
| 297 | + Map<String, Object> map = responseAsMap(response); |
| 298 | + assertThat(((ArrayList<?>) map.get("columns")).size(), greaterThanOrEqualTo(1)); |
| 299 | + assertThat(((ArrayList<?>) map.get("values")).size(), greaterThanOrEqualTo(1)); |
| 300 | + |
| 301 | + assertExpectedClustersForMissingIndicesTests( |
| 302 | + map, |
| 303 | + List.of( |
| 304 | + // local cluster is never marked as SKIPPED even when no matching indices - just marked as 0 shards searched |
| 305 | + new ExpectedCluster("(local)", "nomatch*", "successful", 0), |
| 306 | + new ExpectedCluster(REMOTE_CLUSTER_ALIAS, INDEX2, "successful", null) |
| 307 | + ) |
| 308 | + ); |
| 309 | + |
| 310 | + String limit0 = q + " | LIMIT 0"; |
| 311 | + response = client().performRequest(esqlRequest(limit0)); |
| 312 | + assertOK(response); |
| 313 | + |
| 314 | + map = responseAsMap(response); |
| 315 | + assertThat(((ArrayList<?>) map.get("columns")).size(), greaterThanOrEqualTo(1)); |
| 316 | + assertThat(((ArrayList<?>) map.get("values")).size(), is(0)); |
| 317 | + |
| 318 | + assertExpectedClustersForMissingIndicesTests( |
| 319 | + map, |
| 320 | + List.of( |
| 321 | + // local cluster is never marked as SKIPPED even when no matching indices - just marked as 0 shards searched |
| 322 | + new ExpectedCluster("(local)", "nomatch*", "successful", 0), |
| 323 | + new ExpectedCluster(REMOTE_CLUSTER_ALIAS, INDEX2, "successful", 0) |
| 324 | + ) |
| 325 | + ); |
| 326 | + } |
| 327 | + |
| 328 | + // an error is thrown if there are no matching indices at all |
| 329 | + { |
| 330 | + String q = Strings.format("FROM %s:nomatch*", REMOTE_CLUSTER_ALIAS); |
| 331 | + |
| 332 | + String limit1 = q + " | LIMIT 1"; |
| 333 | + ResponseException e = expectThrows(ResponseException.class, () -> client().performRequest(esqlRequest(limit1))); |
| 334 | + assertThat(e.getMessage(), containsString(Strings.format("Unknown index [%s:nomatch*]", REMOTE_CLUSTER_ALIAS))); |
| 335 | + |
| 336 | + String limit0 = q + " | LIMIT 0"; |
| 337 | + e = expectThrows(ResponseException.class, () -> client().performRequest(esqlRequest(limit0))); |
| 338 | + assertThat(e.getMessage(), containsString(Strings.format("Unknown index [%s:nomatch*]", REMOTE_CLUSTER_ALIAS))); |
| 339 | + } |
| 340 | + |
| 341 | + // an error is thrown if there are no matching indices at all (2 clusters) |
| 342 | + { |
| 343 | + String localExpr = "nomatch*"; |
| 344 | + String remoteExpr = "nomatch*"; |
| 345 | + String q = Strings.format("FROM %s,%s:%s", localExpr, REMOTE_CLUSTER_ALIAS, remoteExpr); |
| 346 | + |
| 347 | + String limit1 = q + " | LIMIT 1"; |
| 348 | + ResponseException e = expectThrows(ResponseException.class, () -> client().performRequest(esqlRequest(limit1))); |
| 349 | + assertThat(e.getMessage(), containsString("Unknown index")); |
| 350 | + assertThat(e.getMessage(), containsString(Strings.format("%s:%s", REMOTE_CLUSTER_ALIAS, remoteExpr))); |
| 351 | + |
| 352 | + String limit0 = q + " | LIMIT 0"; |
| 353 | + e = expectThrows(ResponseException.class, () -> client().performRequest(esqlRequest(limit0))); |
| 354 | + assertThat(e.getMessage(), containsString("Unknown index")); |
| 355 | + assertThat(e.getMessage(), containsString(Strings.format("%s:%s", REMOTE_CLUSTER_ALIAS, remoteExpr))); |
| 356 | + } |
| 357 | + |
| 358 | + // the remote cluster specified a concrete index that is not found - skip |
| 359 | + { |
| 360 | + String q = Strings.format("FROM %s,%s:nomatch,%s:%s*", INDEX1, REMOTE_CLUSTER_ALIAS, REMOTE_CLUSTER_ALIAS, INDEX2); |
| 361 | + |
| 362 | + String limit1 = q + " | LIMIT 1"; |
| 363 | + Response response = client().performRequest(esqlRequest(limit1)); |
| 364 | + assertOK(response); |
| 365 | + |
| 366 | + Map<String, Object> map = responseAsMap(response); |
| 367 | + assertThat(((ArrayList<?>) map.get("columns")).size(), greaterThanOrEqualTo(1)); |
| 368 | + assertThat(((ArrayList<?>) map.get("values")).size(), greaterThanOrEqualTo(1)); |
| 369 | + |
| 370 | + assertExpectedClustersForMissingIndicesTests( |
| 371 | + map, |
| 372 | + List.of( |
| 373 | + new ExpectedCluster("(local)", INDEX1, "successful", null), |
| 374 | + // TODO: this will eventually be SUCCESS |
| 375 | + new ExpectedCluster( |
| 376 | + REMOTE_CLUSTER_ALIAS, |
| 377 | + "nomatch," + INDEX2, |
| 378 | + "skipped", |
| 379 | + 0, |
| 380 | + new ExpectedFailure("index_not_found_exception", List.of("no such index", "nomatch")) |
| 381 | + ) |
| 382 | + ) |
| 383 | + ); |
| 384 | + |
| 385 | + String limit0 = q + " | LIMIT 0"; |
| 386 | + response = client().performRequest(esqlRequest(limit0)); |
| 387 | + assertOK(response); |
| 388 | + |
| 389 | + map = responseAsMap(response); |
| 390 | + assertThat(((ArrayList<?>) map.get("columns")).size(), greaterThanOrEqualTo(1)); |
| 391 | + assertThat(((ArrayList<?>) map.get("values")).size(), greaterThanOrEqualTo(0)); |
| 392 | + |
| 393 | + assertExpectedClustersForMissingIndicesTests( |
| 394 | + map, |
| 395 | + List.of( |
| 396 | + new ExpectedCluster("(local)", INDEX1, "successful", 0), |
| 397 | + new ExpectedCluster(REMOTE_CLUSTER_ALIAS, "nomatch," + INDEX2, "successful", 0) |
| 398 | + ) |
| 399 | + ); |
| 400 | + } |
| 401 | + } |
| 402 | + |
245 | 403 | private void setupRolesAndPrivileges() throws IOException {
|
246 | 404 | var putUserRequest = new Request("PUT", "/_security/user/" + REMOTE_SEARCH_USER);
|
247 | 405 | putUserRequest.setJsonEntity("""
|
|
0 commit comments