diff --git a/src/test/java/org/opensearch/plugin/insights/QueryInsightsRestTestCase.java b/src/test/java/org/opensearch/plugin/insights/QueryInsightsRestTestCase.java index 0bb6194a..c4e6cd8c 100644 --- a/src/test/java/org/opensearch/plugin/insights/QueryInsightsRestTestCase.java +++ b/src/test/java/org/opensearch/plugin/insights/QueryInsightsRestTestCase.java @@ -10,11 +10,17 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.function.Supplier; +import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -37,11 +43,13 @@ import org.junit.Before; import org.opensearch.client.Request; import org.opensearch.client.Response; +import org.opensearch.client.ResponseException; import org.opensearch.client.RestClient; import org.opensearch.client.RestClientBuilder; import org.opensearch.common.settings.Settings; import org.opensearch.common.unit.TimeValue; import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.common.xcontent.json.JsonXContent; import org.opensearch.core.xcontent.DeprecationHandler; import org.opensearch.core.xcontent.MediaType; import org.opensearch.core.xcontent.NamedXContentRegistry; @@ -51,6 +59,8 @@ public abstract class QueryInsightsRestTestCase extends OpenSearchRestTestCase { protected static final String QUERY_INSIGHTS_INDICES_PREFIX = "top_queries"; + private static final Logger logger = Logger.getLogger(QueryInsightsRestTestCase.class.getName()); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ROOT).withZone(ZoneOffset.UTC); protected boolean isHttps() { return Optional.ofNullable(System.getProperty("https")).map("true"::equalsIgnoreCase).orElse(false); @@ -212,7 +222,7 @@ protected String defaultTopQueryGroupingSettings() { protected String createDocumentsBody() { return "{\n" - + " \"@timestamp\": \"2099-11-15T13:12:00\",\n" + + " \"@timestamp\": \"2024-04-01T13:12:00\",\n" + " \"message\": \"this is document 1\",\n" + " \"user\": {\n" + " \"id\": \"cyji\"\n" @@ -380,4 +390,303 @@ protected void updateClusterSettings(Supplier settingsSupplier) throws I Response response = client().performRequest(request); Assert.assertEquals(200, response.getStatusLine().getStatusCode()); } + + protected void createDocument() throws IOException { + String json = "{ \"title\": \"Test Document\", \"content\": \"This is a test document for OpenSearch\" }"; + Request req = new Request("POST", "/my-index-0/_doc/"); + req.setJsonEntity(json); + Response response = client().performRequest(req); + assertEquals(201, response.getStatusLine().getStatusCode()); + } + + protected void performSearch() throws IOException, InterruptedException { + Thread.sleep(5000); + + String searchJson = "{ \"query\": { \"match\": { \"title\": \"Test Document\" } } }"; + Request req = new Request("POST", "/my-index-0/_search?size=20"); + req.setJsonEntity(searchJson); + Response response = client().performRequest(req); + assertEquals(200, response.getStatusLine().getStatusCode()); + String content = new String(response.getEntity().getContent().readAllBytes(), StandardCharsets.UTF_8); + assertTrue("Expected search result for title", content.contains("\"Test Document\"")); + } + + protected void setLatencyWindowSize(String size) throws IOException { + String json = "{ \"persistent\": { \"search.insights.top_queries.latency.window_size\": \"" + size + "\" } }"; + Request req = new Request("PUT", "/_cluster/settings"); + req.setJsonEntity(json); + client().performRequest(req); + } + + protected void defaultExporterSettings() throws IOException { + Request request = new Request("PUT", "/_cluster/settings"); + request.setJsonEntity( + "{ \"persistent\": { " + + "\"search.insights.top_queries.exporter.type\": \"local_index\", " + + "\"search.insights.top_queries.latency.enabled\": \"true\" } }" + ); + Response response = client().performRequest(request); + Assert.assertEquals(200, response.getStatusLine().getStatusCode()); + } + + protected void cleanup() throws IOException, InterruptedException { + Thread.sleep(12000); + + try { + client().performRequest(new Request("DELETE", "/top_queries")); + } catch (ResponseException e) { + logger.warning("Cleanup: Failed to delete /top_queries: " + e.getMessage()); + } + + try { + client().performRequest(new Request("DELETE", "/my-index-0")); + } catch (ResponseException e) { + logger.warning("Cleanup: Failed to delete /my-index-0: " + e.getMessage()); + } + + String resetSettings = "{ \"persistent\": { " + + "\"search.insights.top_queries.exporter.type\": \"none\", " + + "\"search.insights.top_queries.latency.enabled\": \"false\" } }"; + Request resetReq = new Request("PUT", "/_cluster/settings"); + resetReq.setJsonEntity(resetSettings); + client().performRequest(resetReq); + } + + protected void cleanupIndextemplate() throws IOException, InterruptedException { + Thread.sleep(3000); + + try { + client().performRequest(new Request("DELETE", "/_index_template")); + } catch (ResponseException e) { + logger.warning("Failed to delete /_index_template: " + e.getMessage()); + } + } + + protected void checkLocalIndices() throws IOException { + Request indicesRequest = new Request("GET", "/_cat/indices?v"); + Response response = client().performRequest(indicesRequest); + assertEquals(200, response.getStatusLine().getStatusCode()); + + String responseContent = new String(response.getEntity().getContent().readAllBytes(), StandardCharsets.UTF_8); + assertTrue("Expected top_queries-* index to be green", responseContent.contains("green")); + + String suffix = null; + Pattern pattern = Pattern.compile("top_queries-(\\d{4}\\.\\d{2}\\.\\d{2}-\\d+)"); + Matcher matcher = pattern.matcher(responseContent); + if (matcher.find()) { + suffix = matcher.group(1); + } else { + fail("Failed to extract top_queries index suffix"); + } + + assertNotNull("Failed to extract suffix from top_queries-* index", suffix); + String fullIndexName = "top_queries-" + suffix; + assertTrue("Expected top_queries-{" + fullIndexName + "} index to be present", responseContent.contains(fullIndexName)); + + Request fetchRequest = new Request("GET", "/" + fullIndexName + "/_search?size=10"); + Response fetchResponse = client().performRequest(fetchRequest); + assertEquals(200, fetchResponse.getStatusLine().getStatusCode()); + + byte[] bytes = fetchResponse.getEntity().getContent().readAllBytes(); + + try ( + XContentParser parser = JsonXContent.jsonXContent.createParser( + NamedXContentRegistry.EMPTY, + DeprecationHandler.THROW_UNSUPPORTED_OPERATION, + bytes + ) + ) { + Map responseMap = parser.map(); + + Map hitsWrapper = (Map) responseMap.get("hits"); + List> hits = (List>) hitsWrapper.get("hits"); + + Map firstHit = hits.get(0); + Map source = (Map) firstHit.get("_source"); + + assertEquals("query_then_fetch", source.get("search_type")); + assertEquals("NONE", source.get("group_by")); + assertEquals(1, ((Number) source.get("total_shards")).intValue()); + + Map queryBlock = (Map) source.get("query"); + + if (queryBlock != null && queryBlock.containsKey("match")) { + Map match = (Map) queryBlock.get("match"); + if (match != null && match.containsKey("title")) { + Map title = (Map) match.get("title"); + if (title != null) { + assertEquals("Test Document", title.get("query")); + } + } + } + + Map measurements = (Map) source.get("measurements"); + assertNotNull("Expected measurements", measurements); + assertTrue(measurements.containsKey("cpu")); + assertTrue(measurements.containsKey("latency")); + assertTrue(measurements.containsKey("memory")); + + List> taskResourceUsages = (List>) source.get("task_resource_usages"); + assertTrue("Expected non-empty task_resource_usages", taskResourceUsages.size() > 0); + } + } + + protected void checkQueryInsightsIndexTemplate() throws IOException { + Request request = new Request("GET", "/_index_template?pretty"); + Response response = client().performRequest(request); + byte[] bytes = response.getEntity().getContent().readAllBytes(); + + try ( + XContentParser parser = JsonXContent.jsonXContent.createParser( + NamedXContentRegistry.EMPTY, + DeprecationHandler.THROW_UNSUPPORTED_OPERATION, + bytes + ) + ) { + Map parsed = parser.map(); + + List> templates = (List>) parsed.get("index_templates"); + assertNotNull("Expected index_templates to exist", templates); + assertFalse("Expected at least one index_template", templates.isEmpty()); + + Map firstTemplate = templates.get(0); + assertEquals("query_insights_top_queries_template", firstTemplate.get("name")); + + Map indexTemplate = (Map) firstTemplate.get("index_template"); + + List indexPatterns = (List) indexTemplate.get("index_patterns"); + assertTrue("Expected index_patterns to include top_queries-*", indexPatterns.contains("top_queries-*")); + + Map template = (Map) indexTemplate.get("template"); + Map settings = (Map) template.get("settings"); + Map indexSettings = (Map) settings.get("index"); + assertEquals("1", indexSettings.get("number_of_shards")); + assertEquals("0-2", indexSettings.get("auto_expand_replicas")); + + Map mappings = (Map) template.get("mappings"); + Map meta = (Map) mappings.get("_meta"); + assertEquals(1, ((Number) meta.get("schema_version")).intValue()); + assertEquals("top_n_queries", meta.get("query_insights_feature_space")); + + Map properties = (Map) mappings.get("properties"); + assertTrue("Expected 'total_shards' in mappings", properties.containsKey("total_shards")); + assertTrue("Expected 'search_type' in mappings", properties.containsKey("search_type")); + assertTrue("Expected 'task_resource_usages' in mappings", properties.containsKey("task_resource_usages")); + assertTrue("Expected 'measurements' in mappings", properties.containsKey("measurements")); + } + } + + protected void setLocalIndexToDebug() throws IOException { + String debugExporterJson = "{ \"persistent\": { \"search.insights.top_queries.exporter.type\": \"debug\" } }"; + Request debugExporterRequest = new Request("PUT", "/_cluster/settings"); + debugExporterRequest.setJsonEntity(debugExporterJson); + client().performRequest(debugExporterRequest); + } + + protected void disableLocalIndexExporter() throws IOException { + String disableExporterJson = "{ \"persistent\": { \"search.insights.top_queries.exporter.type\": \"none\" } }"; + Request disableExporterRequest = new Request("PUT", "/_cluster/settings"); + disableExporterRequest.setJsonEntity(disableExporterJson); + client().performRequest(disableExporterRequest); + } + + protected String[] invalidExporterSettings() { + return new String[] { + "{ \"persistent\" : { \"search.insights.top_queries.exporter.type\" : invalid_type } }", + "{ \"persistent\" : { \"search.insights.top_queries.exporter.type\" : local_index, \"search.insights.top_queries.exporter.config.index\" : \"1a2b\" } }" }; + } + + protected List fetchHistoricalTopQueries(String ID, String NODEID, String Type) throws IOException { + String to = formatter.format(Instant.now()); + String from = formatter.format(Instant.now().minusSeconds(9600)); // Default 160 minutes + return fetchHistoricalTopQueries(from, to, ID, NODEID, Type); + } + + protected List fetchHistoricalTopQueries(String from, String to, String filterId, String filterNodeID, String type) + throws IOException { + String endpoint = "/_insights/top_queries?from=" + from + "&to=" + to; + + if (filterId != null && !filterId.equals("null")) { + endpoint += "&id=" + filterId; + } + if (filterNodeID != null && !filterNodeID.equals("null")) { + endpoint += "&nodeId=" + filterNodeID; + } + if (type != null && !type.equals("null")) { + endpoint += "&type=" + type; + } + + Request fetchRequest = new Request("GET", endpoint); + Response fetchResponse = client().performRequest(fetchRequest); + + assertEquals(200, fetchResponse.getStatusLine().getStatusCode()); + byte[] content = fetchResponse.getEntity().getContent().readAllBytes(); + + try ( + XContentParser parser = JsonXContent.jsonXContent.createParser( + NamedXContentRegistry.EMPTY, + DeprecationHandler.THROW_UNSUPPORTED_OPERATION, + content + ) + ) { + Map root = parser.map(); + List> topQueries = (List>) root.get("top_queries"); + assertNotNull("Expected 'top_queries' field", topQueries); + assertFalse("Expected at least one top query", topQueries.isEmpty()); + + boolean matchFound = false; + List idNodePairs = new ArrayList<>(); + + for (Map query : topQueries) { + assertTrue(query.containsKey("timestamp")); + assertEquals("query_then_fetch", query.get("search_type")); + assertTrue(((List) query.get("indices")).contains("my-index-0")); + String id = (String) query.get("id"); + String nodeId = (String) query.get("node_id"); + + // Validate ID if provided + if (filterId != "null") { + assertEquals("Expected id to match filter", filterId, id); + } + if (filterNodeID != "null") { + assertEquals("Expected id to match filter", filterNodeID, nodeId); + } + + idNodePairs.add(new String[] { id, nodeId }); + + Map source = (Map) query.get("source"); + Map queryBlock = (Map) source.get("query"); + Map match = (Map) queryBlock.get("match"); + Map title = (Map) match.get("title"); + if ("Test Document".equals(title.get("query"))) { + matchFound = true; + } + + List> taskUsages = (List>) query.get("task_resource_usages"); + assertFalse("task_resource_usages should not be empty", taskUsages.isEmpty()); + for (Map task : taskUsages) { + assertTrue("Missing action", task.containsKey("action")); + Map usage = (Map) task.get("taskResourceUsage"); + assertNotNull("Missing cpu_time_in_nanos", usage.get("cpu_time_in_nanos")); + assertNotNull("Missing memory_in_bytes", usage.get("memory_in_bytes")); + } + + Map measurements = (Map) query.get("measurements"); + assertNotNull("Expected measurements", measurements); + assertTrue(measurements.containsKey("cpu")); + assertTrue(measurements.containsKey("memory")); + assertTrue(measurements.containsKey("latency")); + } + + assertTrue("Expected at least one query with title='Test Document'", matchFound); + return idNodePairs; + + } + + } + + protected List fetchHistoricalTopQueries(Instant from, Instant to, String ID, String NODEID, String Type) throws IOException { + return fetchHistoricalTopQueries(formatter.format(from), formatter.format(to), ID, NODEID, Type); + } + } diff --git a/src/test/java/org/opensearch/plugin/insights/core/exporter/QueryInsightsExporterIT.java b/src/test/java/org/opensearch/plugin/insights/core/exporter/QueryInsightsExporterIT.java index 77fd39ef..e7848302 100644 --- a/src/test/java/org/opensearch/plugin/insights/core/exporter/QueryInsightsExporterIT.java +++ b/src/test/java/org/opensearch/plugin/insights/core/exporter/QueryInsightsExporterIT.java @@ -8,22 +8,15 @@ package org.opensearch.plugin.insights.core.exporter; -import java.io.IOException; -import org.junit.Assert; import org.opensearch.client.Request; -import org.opensearch.client.Response; import org.opensearch.client.ResponseException; import org.opensearch.plugin.insights.QueryInsightsRestTestCase; -/** Rest Action tests for query */ +/** Rest Action tests for query */ public class QueryInsightsExporterIT extends QueryInsightsRestTestCase { - /** - * Test Top Queries setting endpoints - * - * @throws IOException IOException - */ - public void testQueryInsightsExporterSettings() throws IOException { - // test invalid settings + + public void testQueryInsightsExporterSettings() throws Exception { + createDocument(); for (String setting : invalidExporterSettings()) { Request request = new Request("PUT", "/_cluster/settings"); request.setJsonEntity(setting); @@ -34,30 +27,16 @@ public void testQueryInsightsExporterSettings() throws IOException { assertEquals(400, e.getResponse().getStatusLine().getStatusCode()); } } - - // Test enable Top N Queries feature - Request request = new Request("PUT", "/_cluster/settings"); - request.setJsonEntity(defaultExporterSettings()); - Response response = client().performRequest(request); - Assert.assertEquals(200, response.getStatusLine().getStatusCode()); - } - - private String defaultExporterSettings() { - return "{\n" - + " \"persistent\" : {\n" - + " \"search.insights.top_queries.exporter.type\" : \"local_index\"\n" - + " }\n" - + "}"; - } - - private String[] invalidExporterSettings() { - return new String[] { - "{\n" + " \"persistent\" : {\n" + " \"search.insights.top_queries.exporter.type\" : invalid_type\n" + " }\n" + "}", - "{\n" - + " \"persistent\" : {\n" - + " \"search.insights.top_queries.exporter.type\" : local_index,\n" - + " \"search.insights.top_queries.exporter.config.index\" : \"1a2b\"\n" - + " }\n" - + "}" }; + defaultExporterSettings();// Enabling Local index Setting + performSearch(); + setLatencyWindowSize("1m"); + Thread.sleep(70000); // Allow time for export to local index + checkLocalIndices(); + checkQueryInsightsIndexTemplate(); + cleanupIndextemplate(); + disableLocalIndexExporter(); + defaultExporterSettings();// Re-enabling the Local Index + setLocalIndexToDebug();// Ensuring it is able to toggle Local to Debug + cleanup(); } } diff --git a/src/test/java/org/opensearch/plugin/insights/core/reader/MultiIndexDateRangeIT.java b/src/test/java/org/opensearch/plugin/insights/core/reader/MultiIndexDateRangeIT.java new file mode 100644 index 00000000..262ddd0f --- /dev/null +++ b/src/test/java/org/opensearch/plugin/insights/core/reader/MultiIndexDateRangeIT.java @@ -0,0 +1,322 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ +package org.opensearch.plugin.insights.core.reader; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.junit.Assert; +import org.opensearch.client.Request; +import org.opensearch.client.Response; +import org.opensearch.common.xcontent.json.JsonXContent; +import org.opensearch.core.xcontent.DeprecationHandler; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.plugin.insights.QueryInsightsRestTestCase; + +public class MultiIndexDateRangeIT extends QueryInsightsRestTestCase { + private static final DateTimeFormatter indexPattern = DateTimeFormatter.ofPattern("yyyy.MM.dd", Locale.ROOT); + + public void testMultiIndexDateRangeRetrieval() throws IOException, ParseException, InterruptedException { + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd", Locale.ROOT); + + List inputDates = List.of("2022.06.21", "2020.10.04", "2023.02.15", "2021.12.29", "2024.03.08"); + + for (String dateStr : inputDates) { + LocalDate localDate = LocalDate.parse(dateStr, formatter); + ZonedDateTime zdt = localDate.atStartOfDay(ZoneOffset.UTC); + long timestamp = zdt.toInstant().toEpochMilli(); + String indexName = buildLocalIndexName(zdt); + createTopQueriesIndex(indexName, timestamp); + } + + Thread.sleep(10000); + + Request request = new Request("GET", "/_insights/top_queries?from=2021-04-01T00:00:00Z&to=2025-04-02T00:00:00Z"); + + try { + Response response = client().performRequest(request); + String responseBody = EntityUtils.toString(response.getEntity()); + Assert.assertEquals(200, response.getStatusLine().getStatusCode()); + Assert.assertFalse("Expected non-empty top_queries but got empty list", responseBody.contains("\"top_queries\":[]")); + byte[] bytes = response.getEntity().getContent().readAllBytes(); + + try ( + XContentParser parser = JsonXContent.jsonXContent.createParser( + NamedXContentRegistry.EMPTY, + DeprecationHandler.THROW_UNSUPPORTED_OPERATION, + bytes + ) + ) { + Map parsed = parser.map(); + List> topQueries = (List>) parsed.get("top_queries"); + + // Assert the expected count + Assert.assertEquals("Expected 4 top queries", 4, topQueries.size()); + } + + } catch (Exception e) { + + throw e; + } + cleanup(); + + } + + private void createTopQueriesIndex(String indexName, long timestamp) throws IOException, ParseException, InterruptedException { + String mapping = """ + { + "mappings": { + "dynamic": true, + "_meta": { + "schema_version": 1, + "query_insights_feature_space": "top_n_queries" + }, + "properties": { + "id": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "node_id": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "timestamp": { "type": "long" }, + "total_shards": { "type": "long" }, + "group_by": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "phase_latency_map": { + "properties": { + "expand": { "type": "long" }, + "fetch": { "type": "long" }, + "query": { "type": "long" } + } + }, + "search_type": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "task_resource_usages": { + "properties": { + "action": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "nodeId": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "parentTaskId": { "type": "long" }, + "taskId": { "type": "long" }, + "taskResourceUsage": { + "properties": { + "cpu_time_in_nanos": { "type": "long" }, + "memory_in_bytes": { "type": "long" } + } + } + } + }, + "measurements": { + "properties": { + "latency": { + "properties": { + "number": { "type": "double" }, + "count": { "type": "integer" }, + "aggregationType": { "type": "keyword" } + } + }, + "cpu": { + "properties": { + "number": { "type": "double" }, + "count": { "type": "integer" }, + "aggregationType": { "type": "keyword" } + } + }, + "memory": { + "properties": { + "number": { "type": "double" }, + "count": { "type": "integer" }, + "aggregationType": { "type": "keyword" } + } + } + } + } + } + }, + "settings": { + "index.number_of_shards": 1, + "index.auto_expand_replicas": "0-2" + } + } + """; + Request request = new Request("PUT", "/" + indexName); + request.setJsonEntity(mapping); + + Response response = client().performRequest(request); + assertEquals(200, response.getStatusLine().getStatusCode()); + + Request docrequest = new Request("POST", "/" + indexName + "/_doc"); + String docBody = createDocumentsBody(timestamp); + + docrequest.setJsonEntity(docBody); + + Response docresponse = client().performRequest(docrequest); + + Assert.assertEquals(201, docresponse.getStatusLine().getStatusCode()); + + Thread.sleep(3000); + + Request searchTest = new Request("GET", "/" + indexName + "/_search"); + searchTest.setJsonEntity("{ \"query\": { \"match_all\": {} } }"); + Response searchResp = client().performRequest(searchTest); + + } + + protected String createDocumentsBody(long timestamp) { + return String.format(Locale.ROOT, """ + { + "timestamp": %d, + "id": "6ac36175-e48e-4b90-9dbb-ee711a7ec629", + "node_id": "TL1FYh4DR36PmFp9JRCtaA", + "total_shards": 1, + "group_by": "NONE", + "search_type": "query_then_fetch", + "phase_latency_map": { + "expand": 0, + "query": 37, + "fetch": 1 + }, + "task_resource_usages": [ + { + "action": "indices:data/read/search[phase/query]", + "taskId": 41, + "parentTaskId": 40, + "nodeId": "TL1FYh4DR36PmFp9JRCtaA", + "taskResourceUsage": { + "cpu_time_in_nanos": 29965000, + "memory_in_bytes": 3723960 + } + }, + { + "action": "indices:data/read/search", + "taskId": 40, + "parentTaskId": -1, + "nodeId": "TL1FYh4DR36PmFp9JRCtaA", + "taskResourceUsage": { + "cpu_time_in_nanos": 1104000, + "memory_in_bytes": 106176 + } + } + ], + "measurements": { + "latency": { + "number": 48, + "count": 1, + "aggregationType": "NONE" + }, + "memory": { + "number": 3830136, + "count": 1, + "aggregationType": "NONE" + }, + "cpu": { + "number": 31069000, + "count": 1, + "aggregationType": "NONE" + } + } + } + """, timestamp); + } + + private String buildLocalIndexName(ZonedDateTime current) { + return "top_queries-" + current.format(indexPattern) + "-" + generateLocalIndexDateHash(current.toLocalDate()); + } + + private String buildbadLocalIndexName(ZonedDateTime current) { + return "top_queries-" + current.format(indexPattern) + "-" + "10000"; + } + + public static String generateLocalIndexDateHash(LocalDate date) { + String dateString = DateTimeFormatter.ofPattern("yyyy-MM-dd", Locale.ROOT).format(date); + return String.format(Locale.ROOT, "%05d", (dateString.hashCode() % 100000 + 100000) % 100000); + } + + public void testInvalidMultiIndexDateRangeRetrieval() throws IOException, ParseException, InterruptedException { + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd", Locale.ROOT); + + List inputDates = List.of("2022.06.21", "2020.10.04", "2023.02.15", "2021.12.29", "2024.03.08"); + + for (String dateStr : inputDates) { + LocalDate localDate = LocalDate.parse(dateStr, formatter); + ZonedDateTime zdt = localDate.atStartOfDay(ZoneOffset.UTC); + long timestamp = zdt.toInstant().toEpochMilli(); + String indexName = buildbadLocalIndexName(zdt); + createTopQueriesIndex(indexName, timestamp); + } + + Thread.sleep(10000); + + Request request = new Request("GET", "/_insights/top_queries?from=2021-04-01T00:00:00Z&to=2025-04-02T00:00:00Z"); + + try { + Response response = client().performRequest(request); + String responseBody = EntityUtils.toString(response.getEntity()); + Assert.assertEquals(200, response.getStatusLine().getStatusCode()); + Assert.assertTrue("Expected empty top_queries", responseBody.contains("\"top_queries\":[]")); + + } catch (Exception e) { + + throw e; + } + cleanup(); + + } + +} diff --git a/src/test/java/org/opensearch/plugin/insights/core/reader/QueryInsightsReaderIT.java b/src/test/java/org/opensearch/plugin/insights/core/reader/QueryInsightsReaderIT.java new file mode 100644 index 00000000..64c15db5 --- /dev/null +++ b/src/test/java/org/opensearch/plugin/insights/core/reader/QueryInsightsReaderIT.java @@ -0,0 +1,69 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ +package org.opensearch.plugin.insights.core.reader; + +import java.io.IOException; +import java.util.List; +import org.opensearch.client.Request; +import org.opensearch.client.ResponseException; +import org.opensearch.plugin.insights.QueryInsightsRestTestCase; + +public class QueryInsightsReaderIT extends QueryInsightsRestTestCase { + + public void testQueryInsightsHistoricalTopQueriesRead() throws IOException, InterruptedException { + try { + createDocument(); + defaultExporterSettings(); + setLatencyWindowSize("1m"); + performSearch(); + Thread.sleep(80000); + checkLocalIndices(); + List allPairs = fetchHistoricalTopQueries("null", "null", "null"); + assertFalse("Expected at least one top query", allPairs.isEmpty()); + String selectedId = allPairs.get(0)[0]; + String selectedNodeId = allPairs.get(0)[1]; + List filteredPairs = fetchHistoricalTopQueries(selectedId, "null", "null"); + List filteredPairs1 = fetchHistoricalTopQueries("null", selectedNodeId, "null"); + List filteredPairs2 = fetchHistoricalTopQueries(selectedId, selectedNodeId, "null"); + List filteredPairs3 = fetchHistoricalTopQueries(selectedId, selectedNodeId, "latency"); + + } catch (Exception e) { + fail("Test failed with exception: " + e.getMessage()); + } finally { + cleanup(); + } + } + + public void testInvalidDateRangeParameters() throws IOException { + String[] invalidEndpoints = new String[] { + "/_insights/top_queries?from=2024-00-01T00:00:00.000Z&to=2024-04-07T00:00:00.000Z", // Invalid month + "/_insights/top_queries?from=2024-13-01T00:00:00.000Z&to=2024-04-07T00:00:00.000Z", // Month out of range + "/_insights/top_queries?from=abcd&to=efgh", // Not a date + "/_insights/top_queries?from=&to=", // Empty values + "/_insights/top_queries?from=2024-04-10T00:00:00Z", // Missing `to` + "/_insights/top_queries?to=2024-04-10T00:00:00Z", // Missing `from` + + // Invalid metric type + "/_insights/top_queries?from=2025-04-15T17:00:00.000Z&to=2025-04-15T18:00:00.000Z&type=Latency", + "/_insights/top_queries?from=2025-04-15T17:00:00.000Z&to=2025-04-15T18:00:00.000Z&type=xyz", + + // Unexpected param + "/_insights/top_queries?from=2025-04-15T17:00:00.000Z&to=2025-04-15T18:00:00.000Z&foo=bar", + "/_insights/top_queries?from=2025-04-15T17:59:42.304Z&to=2025-04-15T20:39:42.304Zabdncmdkdkssmcmd", }; + + for (String endpoint : invalidEndpoints) { + runInvalidDateRequest(endpoint); + } + } + + private void runInvalidDateRequest(String endpoint) throws IOException { + Request request = new Request("GET", endpoint); + ResponseException e = expectThrows(ResponseException.class, () -> client().performRequest(request)); + assertEquals(400, e.getResponse().getStatusLine().getStatusCode()); + } +}