2323import org .elasticsearch .index .IndexNotFoundException ;
2424import org .elasticsearch .index .query .QueryBuilder ;
2525import org .elasticsearch .index .query .TermsQueryBuilder ;
26+ import org .elasticsearch .test .FailingFieldPlugin ;
2627import org .elasticsearch .test .InternalTestCluster ;
2728import org .elasticsearch .test .XContentTestUtils ;
2829import org .elasticsearch .transport .TransportService ;
30+ import org .elasticsearch .xcontent .XContentBuilder ;
31+ import org .elasticsearch .xcontent .json .JsonXContent ;
2932import org .elasticsearch .xpack .esql .VerificationException ;
3033import org .elasticsearch .xpack .esql .plugin .QueryPragmas ;
3134
@@ -433,6 +436,7 @@ public void assertExpectedClustersForMissingIndicesTests(EsqlExecutionInfo execu
433436 Set <String > expectedClusterAliases = expected .stream ().map (c -> c .clusterAlias ()).collect (Collectors .toSet ());
434437 assertThat (executionInfo .clusterAliases (), equalTo (expectedClusterAliases ));
435438
439+ boolean hasSkipped = false ;
436440 for (ExpectedCluster expectedCluster : expected ) {
437441 EsqlExecutionInfo .Cluster cluster = executionInfo .getCluster (expectedCluster .clusterAlias ());
438442 String msg = cluster .getClusterAlias ();
@@ -451,10 +455,12 @@ public void assertExpectedClustersForMissingIndicesTests(EsqlExecutionInfo execu
451455 assertThat (msg , cluster .getFailures ().get (0 ).getCause (), instanceOf (VerificationException .class ));
452456 String expectedMsg = "Unknown index [" + expectedCluster .indexExpression () + "]" ;
453457 assertThat (msg , cluster .getFailures ().get (0 ).getCause ().getMessage (), containsString (expectedMsg ));
458+ hasSkipped = true ;
454459 }
455460 // currently failed shards is always zero - change this once we start allowing partial data for individual shard failures
456461 assertThat (msg , cluster .getFailedShards (), equalTo (0 ));
457462 }
463+ assertThat (executionInfo .isPartial (), equalTo (hasSkipped ));
458464 }
459465
460466 public void testSearchesWhereNonExistentClusterIsSpecifiedWithWildcards () throws Exception {
@@ -500,6 +506,7 @@ public void testSearchesWhereNonExistentClusterIsSpecifiedWithWildcards() throws
500506 assertThat (executionInfo .isCrossClusterSearch (), is (true ));
501507 assertThat (executionInfo .overallTook ().millis (), greaterThanOrEqualTo (0L ));
502508 assertThat (executionInfo .includeCCSMetadata (), equalTo (responseExpectMeta ));
509+ assertThat (executionInfo .isPartial (), equalTo (true ));
503510
504511 assertThat (executionInfo .clusterAliases (), equalTo (Set .of (REMOTE_CLUSTER_1 , LOCAL_CLUSTER )));
505512
@@ -556,6 +563,7 @@ public void testCCSExecutionOnSearchesWithLimit0() throws Exception {
556563 long overallTookMillis = executionInfo .overallTook ().millis ();
557564 assertThat (overallTookMillis , greaterThanOrEqualTo (0L ));
558565 assertThat (executionInfo .includeCCSMetadata (), equalTo (responseExpectMeta ));
566+ assertThat (executionInfo .isPartial (), equalTo (false ));
559567 assertThat (executionInfo .clusterAliases (), equalTo (Set .of (REMOTE_CLUSTER_1 , LOCAL_CLUSTER )));
560568
561569 EsqlExecutionInfo .Cluster remoteCluster = executionInfo .getCluster (REMOTE_CLUSTER_1 );
@@ -604,6 +612,7 @@ public void testMetadataIndex() throws Exception {
604612 assertThat (executionInfo .isCrossClusterSearch (), is (true ));
605613 assertThat (executionInfo .includeCCSMetadata (), equalTo (responseExpectMeta ));
606614 assertThat (executionInfo .overallTook ().millis (), greaterThanOrEqualTo (0L ));
615+ assertThat (executionInfo .isPartial (), equalTo (false ));
607616
608617 EsqlExecutionInfo .Cluster remoteCluster = executionInfo .getCluster (REMOTE_CLUSTER_1 );
609618 assertThat (remoteCluster .getIndexExpression (), equalTo ("logs*" ));
@@ -799,6 +808,17 @@ public void testWarnings() throws Exception {
799808 assertTrue (latch .await (30 , TimeUnit .SECONDS ));
800809 }
801810
811+ // Non-disconnect remote failures still fail the request even if skip_unavailable is true
812+ public void testRemoteFailureSkipUnavailableTrue () throws IOException {
813+ Map <String , Object > testClusterInfo = setupFailClusters ();
814+ String localIndex = (String ) testClusterInfo .get ("local.index" );
815+ String remote1Index = (String ) testClusterInfo .get ("remote.index" );
816+ int localNumShards = (Integer ) testClusterInfo .get ("local.num_shards" );
817+ String q = Strings .format ("FROM %s,cluster-a:%s*" , localIndex , remote1Index );
818+ IllegalStateException e = expectThrows (IllegalStateException .class , () -> runQuery (q , false ));
819+ assertThat (e .getMessage (), containsString ("Accessing failing field" ));
820+ }
821+
802822 private static void assertClusterMetadataInResponse (EsqlQueryResponse resp , boolean responseExpectMeta ) {
803823 try {
804824 final Map <String , Object > esqlResponseAsMap = XContentTestUtils .convertToMap (resp );
@@ -925,4 +945,46 @@ Map<String, String> createEmptyIndicesWithNoMappings(int numClusters) {
925945
926946 return clusterToEmptyIndexMap ;
927947 }
948+
949+ Map <String , Object > setupFailClusters () throws IOException {
950+ int numShardsLocal = randomIntBetween (1 , 3 );
951+ populateLocalIndices (LOCAL_INDEX , numShardsLocal );
952+
953+ int numShardsRemote = randomIntBetween (1 , 3 );
954+ populateRemoteIndicesFail (REMOTE_CLUSTER_1 , REMOTE_INDEX , numShardsRemote );
955+
956+ Map <String , Object > clusterInfo = new HashMap <>();
957+ clusterInfo .put ("local.num_shards" , numShardsLocal );
958+ clusterInfo .put ("local.index" , LOCAL_INDEX );
959+ clusterInfo .put ("remote.num_shards" , numShardsRemote );
960+ clusterInfo .put ("remote.index" , REMOTE_INDEX );
961+ setSkipUnavailable (REMOTE_CLUSTER_1 , true );
962+ return clusterInfo ;
963+ }
964+
965+ void populateRemoteIndicesFail (String clusterAlias , String indexName , int numShards ) throws IOException {
966+ Client remoteClient = client (clusterAlias );
967+ XContentBuilder mapping = JsonXContent .contentBuilder ().startObject ();
968+ mapping .startObject ("runtime" );
969+ {
970+ mapping .startObject ("fail_me" );
971+ {
972+ mapping .field ("type" , "long" );
973+ mapping .startObject ("script" ).field ("source" , "" ).field ("lang" , FailingFieldPlugin .FAILING_FIELD_LANG ).endObject ();
974+ }
975+ mapping .endObject ();
976+ }
977+ mapping .endObject ();
978+ assertAcked (
979+ remoteClient .admin ()
980+ .indices ()
981+ .prepareCreate (indexName )
982+ .setSettings (Settings .builder ().put ("index.number_of_shards" , numShards ))
983+ .setMapping (mapping .endObject ())
984+ );
985+
986+ remoteClient .prepareIndex (indexName ).setSource ("id" , 0 ).get ();
987+ remoteClient .admin ().indices ().prepareRefresh (indexName ).get ();
988+ }
989+
928990}
0 commit comments