This is an automated email from the ASF dual-hosted git repository.

dybyte pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git


The following commit(s) were added to refs/heads/dev by this push:
     new 3d55c3d763 [Fix][Connector-V2] Fix Elasticsearch connector connection 
pool leak … (#10857)
3d55c3d763 is described below

commit 3d55c3d763a0218227ce325f3703688dd0a5e872
Author: cosmosni <[email protected]>
AuthorDate: Tue May 12 17:31:13 2026 +0800

    [Fix][Connector-V2] Fix Elasticsearch connector connection pool leak … 
(#10857)
---
 .../elasticsearch/client/EsRestClient.java         |  98 ++++++-------
 .../elasticsearch/client/EsRestClientTest.java     | 155 +++++++++++++++++++++
 2 files changed, 206 insertions(+), 47 deletions(-)

diff --git 
a/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/client/EsRestClient.java
 
b/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/client/EsRestClient.java
index 21b3c0eae6..6c57433ae9 100644
--- 
a/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/client/EsRestClient.java
+++ 
b/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/client/EsRestClient.java
@@ -130,8 +130,8 @@ public class EsRestClient implements Closeable {
                         ElasticsearchConnectorErrorCode.BULK_RESPONSE_ERROR,
                         "bulk es Response is null");
             }
+            String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
             if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
-                String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
                 JsonNode json = OBJECT_MAPPER.readTree(entity);
                 int took = json.get("took").asInt();
                 boolean errors = json.get("errors").asBoolean();
@@ -140,8 +140,9 @@ public class EsRestClient implements Closeable {
                 throw new ElasticsearchConnectorException(
                         ElasticsearchConnectorErrorCode.BULK_RESPONSE_ERROR,
                         String.format(
-                                "bulk es response status=%s,request 
body(truncate)=%s",
-                                response,
+                                "bulk es response status=%s, response body=%s, 
request body(truncate)=%s",
+                                response.getStatusLine().getStatusCode(),
+                                entity,
                                 requestBody.substring(0, Math.min(1000, 
requestBody.length()))));
             }
         } catch (IOException e) {
@@ -316,16 +317,17 @@ public class EsRestClient implements Closeable {
                 log.warn("DELETE {} response null for scroll ID: {}", 
endpoint, scrollId);
                 return false;
             }
+            String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
             if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
-                String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
                 JsonNode jsonNode = JsonUtils.parseObject(entity);
                 boolean succeeded = jsonNode.get("succeeded").asBoolean();
                 return succeeded;
             } else {
                 log.warn(
-                        "DELETE {} response status code={} for scroll ID: {}",
+                        "DELETE {} response status code={}, body={} for scroll 
ID: {}",
                         endpoint,
                         response.getStatusLine().getStatusCode(),
+                        entity,
                         scrollId);
                 return false;
             }
@@ -346,16 +348,19 @@ public class EsRestClient implements Closeable {
                         ElasticsearchConnectorErrorCode.SCROLL_REQUEST_ERROR,
                         "POST " + endpoint + " response null");
             }
+            String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
             if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
-                String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
                 ObjectNode responseJson = JsonUtils.parseObject(entity);
                 return getDocsFromSqlResponse(responseJson, columnNodes);
             } else {
                 throw new ElasticsearchConnectorException(
                         ElasticsearchConnectorErrorCode.SCROLL_REQUEST_ERROR,
                         String.format(
-                                "POST %s response status code=%d,request 
body=%s",
-                                endpoint, 
response.getStatusLine().getStatusCode(), requestBody));
+                                "POST %s response status code=%d, body=%s, 
request body=%s",
+                                endpoint,
+                                response.getStatusLine().getStatusCode(),
+                                entity,
+                                requestBody));
             }
         } catch (IOException e) {
             throw new ElasticsearchConnectorException(
@@ -375,8 +380,8 @@ public class EsRestClient implements Closeable {
                         ElasticsearchConnectorErrorCode.SCROLL_REQUEST_ERROR,
                         "POST " + endpoint + " response null");
             }
+            String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
             if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
-                String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
                 ObjectNode responseJson = JsonUtils.parseObject(entity);
 
                 JsonNode shards = responseJson.get("_shards");
@@ -393,8 +398,11 @@ public class EsRestClient implements Closeable {
                 throw new ElasticsearchConnectorException(
                         ElasticsearchConnectorErrorCode.SCROLL_REQUEST_ERROR,
                         String.format(
-                                "POST %s response status code=%d,request 
body=%s",
-                                endpoint, 
response.getStatusLine().getStatusCode(), requestBody));
+                                "POST %s response status code=%d, body=%s, 
request body=%s",
+                                endpoint,
+                                response.getStatusLine().getStatusCode(),
+                                entity,
+                                requestBody));
             }
         } catch (IOException e) {
             throw new ElasticsearchConnectorException(
@@ -502,15 +510,15 @@ public class EsRestClient implements Closeable {
                         
ElasticsearchConnectorErrorCode.GET_INDEX_DOCS_COUNT_FAILED,
                         "GET " + endpoint + " response null");
             }
+            String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
             if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
-                String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
                 return JsonUtils.toList(entity, IndexDocsCount.class);
             } else {
                 throw new ElasticsearchConnectorException(
                         
ElasticsearchConnectorErrorCode.GET_INDEX_DOCS_COUNT_FAILED,
                         String.format(
-                                "GET %s response status code=%d",
-                                endpoint, 
response.getStatusLine().getStatusCode()));
+                                "GET %s response status code=%d, body=%s",
+                                endpoint, 
response.getStatusLine().getStatusCode(), entity));
             }
         } catch (IOException ex) {
             throw new ElasticsearchConnectorException(
@@ -528,8 +536,8 @@ public class EsRestClient implements Closeable {
                         ElasticsearchConnectorErrorCode.LIST_INDEX_FAILED,
                         "GET " + endpoint + " response null");
             }
+            String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
             if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
-                String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
                 return JsonUtils.toList(entity, Map.class).stream()
                         .map(map -> map.get("index").toString())
                         .collect(Collectors.toList());
@@ -537,8 +545,8 @@ public class EsRestClient implements Closeable {
                 throw new ElasticsearchConnectorException(
                         ElasticsearchConnectorErrorCode.LIST_INDEX_FAILED,
                         String.format(
-                                "GET %s response status code=%d",
-                                endpoint, 
response.getStatusLine().getStatusCode()));
+                                "GET %s response status code=%d, body=%s",
+                                endpoint, 
response.getStatusLine().getStatusCode(), entity));
             }
         } catch (IOException ex) {
             throw new ElasticsearchConnectorException(
@@ -563,12 +571,13 @@ public class EsRestClient implements Closeable {
                         ElasticsearchConnectorErrorCode.CREATE_INDEX_FAILED,
                         "PUT " + endpoint + " response null");
             }
+            String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
             if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                 throw new ElasticsearchConnectorException(
                         ElasticsearchConnectorErrorCode.CREATE_INDEX_FAILED,
                         String.format(
-                                "PUT %s response status code=%d",
-                                endpoint, 
response.getStatusLine().getStatusCode()));
+                                "PUT %s response status code=%d, body=%s",
+                                endpoint, 
response.getStatusLine().getStatusCode(), entity));
             }
         } catch (IOException ex) {
             throw new ElasticsearchConnectorException(
@@ -586,15 +595,13 @@ public class EsRestClient implements Closeable {
                         ElasticsearchConnectorErrorCode.DROP_INDEX_FAILED,
                         "DELETE " + endpoint + " response null");
             }
-            // todo: if the index doesn't exist, the response status code is 
200?
-            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
-                return;
-            } else {
+            String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
+            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                 throw new ElasticsearchConnectorException(
                         ElasticsearchConnectorErrorCode.DROP_INDEX_FAILED,
                         String.format(
-                                "DELETE %s response status code=%d",
-                                endpoint, 
response.getStatusLine().getStatusCode()));
+                                "DELETE %s response status code=%d, body=%s",
+                                endpoint, 
response.getStatusLine().getStatusCode(), entity));
             }
         } catch (IOException ex) {
             throw new ElasticsearchConnectorException(
@@ -615,15 +622,13 @@ public class EsRestClient implements Closeable {
                         
ElasticsearchConnectorErrorCode.CLEAR_INDEX_DATA_FAILED,
                         "POST " + endpoint + " response null");
             }
-            // todo: if the index doesn't exist, the response status code is 
200?
-            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
-                return;
-            } else {
+            String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
+            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                 throw new ElasticsearchConnectorException(
                         
ElasticsearchConnectorErrorCode.CLEAR_INDEX_DATA_FAILED,
                         String.format(
-                                "POST %s response status code=%d",
-                                endpoint, 
response.getStatusLine().getStatusCode()));
+                                "POST %s response status code=%d, body=%s",
+                                endpoint, 
response.getStatusLine().getStatusCode(), entity));
             }
         } catch (IOException ex) {
             throw new ElasticsearchConnectorException(
@@ -650,11 +655,12 @@ public class EsRestClient implements Closeable {
                         "GET " + endpoint + " response null");
             }
             if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
+                String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
                 throw new ElasticsearchConnectorException(
                         
ElasticsearchConnectorErrorCode.GET_INDEX_DOCS_COUNT_FAILED,
                         String.format(
-                                "GET %s response status code=%d",
-                                endpoint, 
response.getStatusLine().getStatusCode()));
+                                "GET %s response status code=%d, body=%s",
+                                endpoint, 
response.getStatusLine().getStatusCode(), entity));
             }
             String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
             log.info(String.format("GET %s respnse=%s", endpoint, entity));
@@ -841,15 +847,13 @@ public class EsRestClient implements Closeable {
                         ElasticsearchConnectorErrorCode.ADD_FIELD_FAILED,
                         "PUT " + endpoint + " response null");
             }
+            String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
             if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                 throw new ElasticsearchConnectorException(
                         ElasticsearchConnectorErrorCode.ADD_FIELD_FAILED,
                         String.format(
-                                "PUT %s response status code=%d, response=%s",
-                                endpoint,
-                                response.getStatusLine().getStatusCode(),
-                                EntityUtils.toString(
-                                        response.getEntity(), 
StandardCharsets.UTF_8)));
+                                "PUT %s response status code=%d, body=%s",
+                                endpoint, 
response.getStatusLine().getStatusCode(), entity));
             }
         } catch (IOException ex) {
             throw new ElasticsearchConnectorException(
@@ -877,16 +881,16 @@ public class EsRestClient implements Closeable {
                         ElasticsearchConnectorErrorCode.CREATE_PIT_FAILED,
                         "POST " + endpoint + " response null");
             }
+            String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
             if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
-                String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
                 JsonNode jsonNode = JsonUtils.parseObject(entity);
                 return jsonNode.get("id").asText();
             } else {
                 throw new ElasticsearchConnectorException(
                         ElasticsearchConnectorErrorCode.CREATE_PIT_FAILED,
                         String.format(
-                                "POST %s response status code=%d",
-                                endpoint, 
response.getStatusLine().getStatusCode()));
+                                "POST %s response status code=%d, body=%s",
+                                endpoint, 
response.getStatusLine().getStatusCode(), entity));
             }
         } catch (IOException ex) {
             throw new ElasticsearchConnectorException(
@@ -913,16 +917,16 @@ public class EsRestClient implements Closeable {
                         ElasticsearchConnectorErrorCode.DELETE_PIT_FAILED,
                         "DELETE " + endpoint + " response null");
             }
+            String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
             if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
-                String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
                 JsonNode jsonNode = JsonUtils.parseObject(entity);
                 return jsonNode.get("succeeded").asBoolean();
             } else {
                 throw new ElasticsearchConnectorException(
                         ElasticsearchConnectorErrorCode.DELETE_PIT_FAILED,
                         String.format(
-                                "DELETE %s response status code=%d",
-                                endpoint, 
response.getStatusLine().getStatusCode()));
+                                "DELETE %s response status code=%d, body=%s",
+                                endpoint, 
response.getStatusLine().getStatusCode(), entity));
             }
         } catch (IOException ex) {
             throw new ElasticsearchConnectorException(
@@ -1010,15 +1014,15 @@ public class EsRestClient implements Closeable {
                         ElasticsearchConnectorErrorCode.SEARCH_WITH_PIT_FAILED,
                         "POST " + endpoint + " response null");
             }
+            String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
             if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
-                String entity = EntityUtils.toString(response.getEntity(), 
StandardCharsets.UTF_8);
                 return parsePointInTimeResponse(entity, pitId);
             } else {
                 throw new ElasticsearchConnectorException(
                         ElasticsearchConnectorErrorCode.SEARCH_WITH_PIT_FAILED,
                         String.format(
-                                "POST %s response status code=%d",
-                                endpoint, 
response.getStatusLine().getStatusCode()));
+                                "POST %s response status code=%d, body=%s",
+                                endpoint, 
response.getStatusLine().getStatusCode(), entity));
             }
         } catch (IOException ex) {
             throw new ElasticsearchConnectorException(
diff --git 
a/seatunnel-connectors-v2/connector-elasticsearch/src/test/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/client/EsRestClientTest.java
 
b/seatunnel-connectors-v2/connector-elasticsearch/src/test/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/client/EsRestClientTest.java
new file mode 100644
index 0000000000..03a4ddbb0f
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-elasticsearch/src/test/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/client/EsRestClientTest.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.connectors.seatunnel.elasticsearch.client;
+
+import 
org.apache.seatunnel.connectors.seatunnel.elasticsearch.dto.BulkResponse;
+import 
org.apache.seatunnel.connectors.seatunnel.elasticsearch.exception.ElasticsearchConnectorErrorCode;
+import 
org.apache.seatunnel.connectors.seatunnel.elasticsearch.exception.ElasticsearchConnectorException;
+
+import org.apache.http.StatusLine;
+
+import org.elasticsearch.client.Request;
+import org.elasticsearch.client.Response;
+import org.elasticsearch.client.RestClient;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.lang.reflect.Constructor;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class EsRestClientTest {
+
+    @Mock private RestClient mockRestClient;
+
+    // ---- bulk: non-200 path ----
+
+    @Test
+    void testBulkIncludesEsErrorBodyOnNon200Response() throws Exception {
+        Response mockResponse = mock(Response.class);
+        StatusLine mockStatusLine = mock(StatusLine.class);
+        String esErrorJson = 
"{\"error\":{\"root_cause\":[{\"type\":\"rate_limit_exceeded\"}]}}";
+
+        
when(mockRestClient.performRequest(any(Request.class))).thenReturn(mockResponse);
+        when(mockResponse.getStatusLine()).thenReturn(mockStatusLine);
+        when(mockStatusLine.getStatusCode()).thenReturn(429);
+        when(mockResponse.getEntity())
+                .thenReturn(
+                        new org.apache.http.entity.StringEntity(
+                                esErrorJson, 
org.apache.http.entity.ContentType.APPLICATION_JSON));
+
+        EsRestClient client = createEsRestClient(mockRestClient);
+
+        ElasticsearchConnectorException exception =
+                Assertions.assertThrows(
+                        ElasticsearchConnectorException.class,
+                        () -> client.bulk("{\"index\":{}}\n{}\n"));
+
+        Assertions.assertEquals(
+                ElasticsearchConnectorErrorCode.BULK_RESPONSE_ERROR,
+                exception.getSeaTunnelErrorCode());
+        Assertions.assertTrue(
+                exception.getMessage().contains(esErrorJson),
+                "Error message should contain ES response body for debugging");
+    }
+
+    // ---- bulk: 200 success path ----
+
+    @Test
+    void testBulkSucceedsOn200Response() throws Exception {
+        Response mockResponse = mock(Response.class);
+        StatusLine mockStatusLine = mock(StatusLine.class);
+
+        
when(mockRestClient.performRequest(any(Request.class))).thenReturn(mockResponse);
+        when(mockResponse.getStatusLine()).thenReturn(mockStatusLine);
+        when(mockStatusLine.getStatusCode()).thenReturn(200);
+        when(mockResponse.getEntity())
+                .thenReturn(
+                        new org.apache.http.entity.StringEntity(
+                                "{\"took\":1,\"errors\":false,\"items\":[]}",
+                                
org.apache.http.entity.ContentType.APPLICATION_JSON));
+
+        EsRestClient client = createEsRestClient(mockRestClient);
+
+        BulkResponse result = client.bulk("{\"index\":{}}\n{}\n");
+        Assertions.assertFalse(result.isErrors());
+    }
+
+    // ---- createIndex: 200 success path (void method, must still consume 
entity) ----
+
+    @Test
+    void testCreateIndexConsumesEntityOn200Response() throws Exception {
+        Response mockResponse = mock(Response.class);
+        StatusLine mockStatusLine = mock(StatusLine.class);
+
+        
when(mockRestClient.performRequest(any(Request.class))).thenReturn(mockResponse);
+        when(mockResponse.getStatusLine()).thenReturn(mockStatusLine);
+        when(mockStatusLine.getStatusCode()).thenReturn(200);
+        when(mockResponse.getEntity())
+                .thenReturn(
+                        new org.apache.http.entity.StringEntity(
+                                "{\"acknowledged\":true}",
+                                
org.apache.http.entity.ContentType.APPLICATION_JSON));
+
+        EsRestClient client = createEsRestClient(mockRestClient);
+
+        // Should not throw — entity is consumed internally
+        Assertions.assertDoesNotThrow(() -> client.createIndex("test_index"));
+    }
+
+    // ---- createIndex: non-200 path includes ES error body ----
+
+    @Test
+    void testCreateIndexIncludesEsErrorBodyOnNon200Response() throws Exception 
{
+        Response mockResponse = mock(Response.class);
+        StatusLine mockStatusLine = mock(StatusLine.class);
+        String esErrorJson = 
"{\"error\":{\"type\":\"resource_already_exists_exception\"}}";
+
+        
when(mockRestClient.performRequest(any(Request.class))).thenReturn(mockResponse);
+        when(mockResponse.getStatusLine()).thenReturn(mockStatusLine);
+        when(mockStatusLine.getStatusCode()).thenReturn(400);
+        when(mockResponse.getEntity())
+                .thenReturn(
+                        new org.apache.http.entity.StringEntity(
+                                esErrorJson, 
org.apache.http.entity.ContentType.APPLICATION_JSON));
+
+        EsRestClient client = createEsRestClient(mockRestClient);
+
+        ElasticsearchConnectorException exception =
+                Assertions.assertThrows(
+                        ElasticsearchConnectorException.class,
+                        () -> client.createIndex("test_index"));
+
+        Assertions.assertTrue(
+                exception.getMessage().contains(esErrorJson),
+                "Error message should contain ES response body for debugging");
+    }
+
+    private EsRestClient createEsRestClient(RestClient restClient) throws 
Exception {
+        Constructor<EsRestClient> constructor =
+                EsRestClient.class.getDeclaredConstructor(RestClient.class);
+        constructor.setAccessible(true);
+        return constructor.newInstance(restClient);
+    }
+}

Reply via email to