diff --git a/direct-query-core/src/main/java/org/opensearch/sql/prometheus/client/PrometheusClientImpl.java b/direct-query-core/src/main/java/org/opensearch/sql/prometheus/client/PrometheusClientImpl.java index c6594bec278..d5dffa02391 100644 --- a/direct-query-core/src/main/java/org/opensearch/sql/prometheus/client/PrometheusClientImpl.java +++ b/direct-query-core/src/main/java/org/opensearch/sql/prometheus/client/PrometheusClientImpl.java @@ -115,6 +115,9 @@ public JSONObject queryRange( logger.debug("Received Prometheus response for query_range: code={}", response); JSONObject jsonObject = readResponse(response); + if (!jsonObject.has("data")) { + return new JSONObject(); + } return jsonObject.getJSONObject("data"); } @@ -149,6 +152,9 @@ public JSONObject query(String query, Long time, Integer limit, Integer timeout) logger.info("Received Prometheus response for instant query: code={}", response); JSONObject jsonObject = readResponse(response); + if (!jsonObject.has("data")) { + return new JSONObject(); + } return jsonObject.getJSONObject("data"); } @@ -167,6 +173,9 @@ public List getLabels(Map queryParams) throws IOExceptio Request request = new Request.Builder().url(queryUrl).build(); Response response = AccessController.doPrivilegedChecked(() -> this.prometheusHttpClient.newCall(request).execute()); JSONObject jsonObject = readResponse(response); + if (!jsonObject.has("data")) { + return new ArrayList<>(); + } return toListOfLabels(jsonObject.getJSONArray("data")); } @@ -182,6 +191,9 @@ public List getLabel(String labelName, Map queryParams) Request request = new Request.Builder().url(queryUrl).build(); Response response = AccessController.doPrivilegedChecked(() -> this.prometheusHttpClient.newCall(request).execute()); JSONObject jsonObject = readResponse(response); + if (!jsonObject.has("data")) { + return new ArrayList<>(); + } return toListOfLabels(jsonObject.getJSONArray("data")); } @@ -196,6 +208,9 @@ public Map> getAllMetrics(Map query Request request = new Request.Builder().url(queryUrl).build(); Response response = AccessController.doPrivilegedChecked(() -> this.prometheusHttpClient.newCall(request).execute()); JSONObject jsonObject = readResponse(response); + if (!jsonObject.has("data")) { + return new HashMap<>(); + } TypeReference>> typeRef = new TypeReference<>() {}; return new ObjectMapper().readValue(jsonObject.getJSONObject("data").toString(), typeRef); } @@ -215,6 +230,9 @@ public List> getSeries(Map queryParams) thro Request request = new Request.Builder().url(queryUrl).build(); Response response = AccessController.doPrivilegedChecked(() -> this.prometheusHttpClient.newCall(request).execute()); JSONObject jsonObject = readResponse(response); + if (!jsonObject.has("data")) { + return new ArrayList<>(); + } JSONArray dataArray = jsonObject.getJSONArray("data"); return toListOfSeries(dataArray); } @@ -232,6 +250,9 @@ public JSONArray queryExemplars(String query, Long start, Long end) throws IOExc Request request = new Request.Builder().url(queryUrl).build(); Response response = AccessController.doPrivilegedChecked(() -> this.prometheusHttpClient.newCall(request).execute()); JSONObject jsonObject = readResponse(response); + if (!jsonObject.has("data")) { + return new JSONArray(); + } return jsonObject.getJSONArray("data"); } @@ -243,6 +264,9 @@ public JSONObject getAlerts() throws IOException { Request request = new Request.Builder().url(queryUrl).build(); Response response = AccessController.doPrivilegedChecked(() -> this.prometheusHttpClient.newCall(request).execute()); JSONObject jsonObject = readResponse(response); + if (!jsonObject.has("data")) { + return new JSONObject(); + } return jsonObject.getJSONObject("data"); } diff --git a/direct-query-core/src/test/java/org/opensearch/sql/prometheus/client/PrometheusClientImplTest.java b/direct-query-core/src/test/java/org/opensearch/sql/prometheus/client/PrometheusClientImplTest.java index c71f2852ac9..5c65ade2b18 100644 --- a/direct-query-core/src/test/java/org/opensearch/sql/prometheus/client/PrometheusClientImplTest.java +++ b/direct-query-core/src/test/java/org/opensearch/sql/prometheus/client/PrometheusClientImplTest.java @@ -1119,4 +1119,109 @@ public void testGetAlertmanagerStatusHttpErrorWithNullBody() throws IOException PrometheusClientException.class, () -> nullBodyClient.getAlertmanagerStatus()); assertTrue(exception.getMessage().contains("No response body")); } + + // Prometheus-compatible backends (e.g. Cortex) may return {"status":"success"} with no + // "data" key when there is nothing to report. Each of the following tests verifies the + // corresponding client method returns an empty result instead of throwing JSONException. + + @Test + public void testGetAllMetricsReturnsEmptyWhenDataFieldMissing() throws IOException { + String emptyResponse = "{\"status\":\"success\"}"; + mockWebServer.enqueue(new MockResponse().setBody(emptyResponse)); + + var result = client.getAllMetrics(); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void testGetAllMetricsWithParamsReturnsEmptyWhenDataFieldMissing() throws IOException { + String emptyResponse = "{\"status\":\"success\"}"; + mockWebServer.enqueue(new MockResponse().setBody(emptyResponse)); + + HashMap params = new HashMap<>(); + params.put("limit", "10"); + var result = client.getAllMetrics(params); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void testQueryRangeReturnsEmptyWhenDataFieldMissing() throws IOException { + String emptyResponse = "{\"status\":\"success\"}"; + mockWebServer.enqueue(new MockResponse().setBody(emptyResponse)); + + JSONObject result = client.queryRange("up", 1435781430L, 1435781460L, "15s"); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void testQueryReturnsEmptyWhenDataFieldMissing() throws IOException { + String emptyResponse = "{\"status\":\"success\"}"; + mockWebServer.enqueue(new MockResponse().setBody(emptyResponse)); + + JSONObject result = client.query("up", 1435781460L, 100, 30); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void testGetLabelsReturnsEmptyWhenDataFieldMissing() throws IOException { + String emptyResponse = "{\"status\":\"success\"}"; + mockWebServer.enqueue(new MockResponse().setBody(emptyResponse)); + + List result = client.getLabels(new HashMap<>()); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void testGetLabelReturnsEmptyWhenDataFieldMissing() throws IOException { + String emptyResponse = "{\"status\":\"success\"}"; + mockWebServer.enqueue(new MockResponse().setBody(emptyResponse)); + + List result = client.getLabel("job", new HashMap<>()); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void testGetSeriesReturnsEmptyWhenDataFieldMissing() throws IOException { + String emptyResponse = "{\"status\":\"success\"}"; + mockWebServer.enqueue(new MockResponse().setBody(emptyResponse)); + + var result = client.getSeries(new HashMap<>()); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void testQueryExemplarsReturnsEmptyWhenDataFieldMissing() throws IOException { + String emptyResponse = "{\"status\":\"success\"}"; + mockWebServer.enqueue(new MockResponse().setBody(emptyResponse)); + + JSONArray result = client.queryExemplars("http_request_duration_seconds_bucket", 1L, 2L); + + assertNotNull(result); + assertEquals(0, result.length()); + } + + @Test + public void testGetAlertsReturnsEmptyWhenDataFieldMissing() throws IOException { + String emptyResponse = "{\"status\":\"success\"}"; + mockWebServer.enqueue(new MockResponse().setBody(emptyResponse)); + + JSONObject result = client.getAlerts(); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } }