From c66e109814c7f3eee8c292c487b46dc08cfdae02 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 15:23:15 -0800 Subject: [PATCH] Improve IT coverage of all AD & Alerting tools (#165) (#167) * Add search alert IT cases * Add more IT * Turn off returning ad job * Add AD results IT; clean up constants * Use sample monitor for ingestion --------- (cherry picked from commit 21e964c61d6bfd2eeae1046d48ed62b8520f63e1) Signed-off-by: Tyler Ohlsen Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] Signed-off-by: yuye-aws --- .../tools/SearchAnomalyDetectorsTool.java | 4 +- .../agent/tools/utils/ToolConstants.java | 3 + .../integTest/BaseAgentToolsIT.java | 13 + .../integTest/SearchAlertsToolIT.java | 139 +- .../SearchAnomalyDetectorsToolIT.java | 94 +- .../integTest/SearchAnomalyResultsToolIT.java | 93 +- .../integTest/SearchMonitorsToolIT.java | 69 +- .../tools/alerting/alert_index_mappings.json | 173 +++ .../alerting_config_index_mappings.json | 1269 +++++++++++++++++ ...nt_of_search_alerts_tool_request_body.json | 0 ..._of_search_monitors_tool_request_body.json | 0 .../agent/tools/alerting/sample_alert.json | 23 + .../agent/tools/alerting/sample_monitor.json | 15 + .../detectors_index_mappings.json | 157 ++ ..._anomaly_detectors_tool_request_body.json} | 0 ...rch_anomaly_results_tool_request_body.json | 0 .../results_index_mappings.json | 161 +++ .../anomaly-detection/sample_detector.json | 48 + .../sample_index_mappings.json | 12 + .../anomaly-detection/sample_result.json | 19 + 20 files changed, 2177 insertions(+), 115 deletions(-) create mode 100644 src/test/resources/org/opensearch/agent/tools/alerting/alert_index_mappings.json create mode 100644 src/test/resources/org/opensearch/agent/tools/alerting/alerting_config_index_mappings.json rename src/test/resources/org/opensearch/agent/tools/{ => alerting}/register_flow_agent_of_search_alerts_tool_request_body.json (100%) rename src/test/resources/org/opensearch/agent/tools/{ => alerting}/register_flow_agent_of_search_monitors_tool_request_body.json (100%) create mode 100644 src/test/resources/org/opensearch/agent/tools/alerting/sample_alert.json create mode 100644 src/test/resources/org/opensearch/agent/tools/alerting/sample_monitor.json create mode 100644 src/test/resources/org/opensearch/agent/tools/anomaly-detection/detectors_index_mappings.json rename src/test/resources/org/opensearch/agent/tools/{register_flow_agent_of_search_detectors_tool_request_body.json => anomaly-detection/register_flow_agent_of_search_anomaly_detectors_tool_request_body.json} (100%) rename src/test/resources/org/opensearch/agent/tools/{ => anomaly-detection}/register_flow_agent_of_search_anomaly_results_tool_request_body.json (100%) create mode 100644 src/test/resources/org/opensearch/agent/tools/anomaly-detection/results_index_mappings.json create mode 100644 src/test/resources/org/opensearch/agent/tools/anomaly-detection/sample_detector.json create mode 100644 src/test/resources/org/opensearch/agent/tools/anomaly-detection/sample_index_mappings.json create mode 100644 src/test/resources/org/opensearch/agent/tools/anomaly-detection/sample_result.json diff --git a/src/main/java/org/opensearch/agent/tools/SearchAnomalyDetectorsTool.java b/src/main/java/org/opensearch/agent/tools/SearchAnomalyDetectorsTool.java index 26e84a4b..a94b92f6 100644 --- a/src/main/java/org/opensearch/agent/tools/SearchAnomalyDetectorsTool.java +++ b/src/main/java/org/opensearch/agent/tools/SearchAnomalyDetectorsTool.java @@ -49,7 +49,7 @@ public class SearchAnomalyDetectorsTool implements Tool { public static final String TYPE = "SearchAnomalyDetectorsTool"; private static final String DEFAULT_DESCRIPTION = - "This is a tool that searches anomaly detectors. It takes 12 optional arguments named detectorName which is the explicit name of the monitor (default is null), and detectorNamePattern which is a wildcard query to match detector name (default is null), and indices which defines the index being detected (default is null), and highCardinality which defines whether the anomaly detector is high cardinality (synonymous with multi-entity) of non-high-cardinality (synonymous with single-entity) (default is null, indicating both), and lastUpdateTime which defines the latest update time of the anomaly detector in epoch milliseconds (default is null), and sortOrder which defines the order of the results (options are asc or desc, and default is asc), and sortString which defines how to sort the results (default is name.keyword), and size which defines the size of the request to be returned (default is 20), and startIndex which defines the paginated index to start from (default is 0), and running which defines whether the anomaly detector is running (default is null, indicating both), and disabled which defines whether the anomaly detector is disabled (default is null, indicating both), and failed which defines whether the anomaly detector has failed (default is null, indicating both). The tool returns 2 values: a list of anomaly detectors (each containing the detector id, detector name, detector type indicating multi-entity or single-entity (where multi-entity also means high-cardinality), detector description, name of the configured index, last update time in epoch milliseconds), and the total number of anomaly detectors."; + "This is a tool that searches anomaly detectors. It takes 12 optional arguments named detectorName which is the explicit name of the monitor (default is null), and detectorNamePattern which is a wildcard query to match detector name (default is null), and indices which defines the index or index pattern the detector is detecting over (default is null), and highCardinality which defines whether the anomaly detector is high cardinality (synonymous with multi-entity) of non-high-cardinality (synonymous with single-entity) (default is null, indicating both), and lastUpdateTime which defines the latest update time of the anomaly detector in epoch milliseconds (default is null), and sortOrder which defines the order of the results (options are asc or desc, and default is asc), and sortString which defines how to sort the results (default is name.keyword), and size which defines the size of the request to be returned (default is 20), and startIndex which defines the paginated index to start from (default is 0), and running which defines whether the anomaly detector is running (default is null, indicating both), and disabled which defines whether the anomaly detector is disabled (default is null, indicating both), and failed which defines whether the anomaly detector has failed (default is null, indicating both). The tool returns 2 values: a list of anomaly detectors (each containing the detector id, detector name, detector type indicating multi-entity or single-entity (where multi-entity also means high-cardinality), detector description, name of the configured index, last update time in epoch milliseconds), and the total number of anomaly detectors."; @Setter @Getter @@ -160,7 +160,7 @@ public void run(Map parameters, ActionListener listener) GetAnomalyDetectorRequest profileRequest = new GetAnomalyDetectorRequest( hit.getId(), Versions.MATCH_ANY, - true, + false, true, "", "", diff --git a/src/main/java/org/opensearch/agent/tools/utils/ToolConstants.java b/src/main/java/org/opensearch/agent/tools/utils/ToolConstants.java index e6d95afe..2a90ec7e 100644 --- a/src/main/java/org/opensearch/agent/tools/utils/ToolConstants.java +++ b/src/main/java/org/opensearch/agent/tools/utils/ToolConstants.java @@ -20,7 +20,10 @@ public static enum DetectorStateString { // System indices constants are not cleanly exposed from the AD & Alerting plugins, so we persist our // own constants here. public static final String AD_RESULTS_INDEX_PATTERN = ".opendistro-anomaly-results*"; + public static final String AD_RESULTS_INDEX = ".opendistro-anomaly-results"; public static final String AD_DETECTORS_INDEX = ".opendistro-anomaly-detectors"; public static final String ALERTING_CONFIG_INDEX = ".opendistro-alerting-config"; + public static final String ALERTING_ALERTS_INDEX = ".opendistro-alerting-alerts"; + } diff --git a/src/test/java/org/opensearch/integTest/BaseAgentToolsIT.java b/src/test/java/org/opensearch/integTest/BaseAgentToolsIT.java index 38d43377..2fdbe6ab 100644 --- a/src/test/java/org/opensearch/integTest/BaseAgentToolsIT.java +++ b/src/test/java/org/opensearch/integTest/BaseAgentToolsIT.java @@ -125,6 +125,13 @@ protected String indexMonitor(String monitorAsJsonString) { return parseFieldFromResponse(response, "_id").toString(); } + protected String indexDetector(String detectorAsJsonString) { + Response response = makeRequest(client(), "POST", "_plugins/_anomaly_detection/detectors", null, detectorAsJsonString, null); + + assertEquals(RestStatus.CREATED, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + return parseFieldFromResponse(response, "_id").toString(); + } + @SneakyThrows protected Map waitResponseMeetingCondition( String method, @@ -242,6 +249,12 @@ protected void addDocToIndex(String indexName, String docId, List fieldN assertEquals(RestStatus.CREATED, RestStatus.fromCode(response.getStatusLine().getStatusCode())); } + @SneakyThrows + protected void addDocToIndex(String indexName, String docId, String contents) { + Response response = makeRequest(client(), "POST", "/" + indexName + "/_doc/" + docId + "?refresh=true", null, contents, null); + assertEquals(RestStatus.CREATED, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + } + public String createAgent(String requestBody) { Response response = makeRequest(client(), "POST", "/_plugins/_ml/agents/_register", null, requestBody, null); assertEquals(RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); diff --git a/src/test/java/org/opensearch/integTest/SearchAlertsToolIT.java b/src/test/java/org/opensearch/integTest/SearchAlertsToolIT.java index 66e8f233..95872d22 100644 --- a/src/test/java/org/opensearch/integTest/SearchAlertsToolIT.java +++ b/src/test/java/org/opensearch/integTest/SearchAlertsToolIT.java @@ -10,29 +10,43 @@ import org.junit.After; import org.junit.Before; +import org.junit.jupiter.api.BeforeEach; +import org.opensearch.agent.tools.utils.ToolConstants; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; import lombok.SneakyThrows; public class SearchAlertsToolIT extends BaseAgentToolsIT { private String registerAgentRequestBody; + private String alertsIndexMappings; + private String alertingConfigIndexMappings; + private String sampleAlert; private static final String monitorId = "foo-id"; private static final String monitorName = "foo-name"; + private static final String registerAgentFilepath = + "org/opensearch/agent/tools/alerting/register_flow_agent_of_search_alerts_tool_request_body.json"; + private static final String alertsIndexMappingsFilepath = "org/opensearch/agent/tools/alerting/alert_index_mappings.json"; + private static final String alertingConfigIndexMappingsFilepath = + "org/opensearch/agent/tools/alerting/alerting_config_index_mappings.json"; + private static final String sampleAlertFilepath = "org/opensearch/agent/tools/alerting/sample_alert.json"; @Before @SneakyThrows public void setUp() { super.setUp(); - registerAgentRequestBody = Files - .readString( - Path - .of( - this - .getClass() - .getClassLoader() - .getResource("org/opensearch/agent/tools/register_flow_agent_of_search_alerts_tool_request_body.json") - .toURI() - ) - ); + registerAgentRequestBody = Files.readString(Path.of(this.getClass().getClassLoader().getResource(registerAgentFilepath).toURI())); + alertsIndexMappings = Files.readString(Path.of(this.getClass().getClassLoader().getResource(alertsIndexMappingsFilepath).toURI())); + alertingConfigIndexMappings = Files + .readString(Path.of(this.getClass().getClassLoader().getResource(alertingConfigIndexMappingsFilepath).toURI())); + sampleAlert = Files.readString(Path.of(this.getClass().getClassLoader().getResource(sampleAlertFilepath).toURI())); + } + + @BeforeEach + @SneakyThrows + public void prepareTest() { + deleteSystemIndices(); } @After @@ -45,13 +59,108 @@ public void tearDown() { @SneakyThrows public void testSearchAlertsToolInFlowAgent_withNoSystemIndex() { - deleteSystemIndices(); String agentId = createAgent(registerAgentRequestBody); - String agentInput = "{\"parameters\":{\"monitorId\": \"" + monitorId + "\"}}"; + String agentInput = "{\"parameters\":{}}"; + String result = executeAgent(agentId, agentInput); + assertEquals("Alerts=[]TotalAlerts=0", result); + } + + @SneakyThrows + public void testSearchAlertsToolInFlowAgent_withSystemIndex() { + setupAlertingSystemIndices(); + String agentId = createAgent(registerAgentRequestBody); + String agentInput = "{\"parameters\":{}}"; String result = executeAgent(agentId, agentInput); assertEquals("Alerts=[]TotalAlerts=0", result); } - // TODO: Add IT to test against sample alerts data - // https://github.com/opensearch-project/skills/issues/136 + @SneakyThrows + public void testSearchAlertsToolInFlowAgent_singleAlert_noFilter() { + setupAlertingSystemIndices(); + ingestSampleAlert(monitorId, "1"); + String agentId = createAgent(registerAgentRequestBody); + String agentInput = "{\"parameters\":{}}"; + String result = executeAgent(agentId, agentInput); + assertTrue(result.contains("TotalAlerts=1")); + } + + @SneakyThrows + public void testSearchAlertsToolInFlowAgent_singleAlert_filter_match() { + setupAlertingSystemIndices(); + ingestSampleAlert(monitorId, "1"); + String agentId = createAgent(registerAgentRequestBody); + String agentInput = "{\"parameters\":{\"monitorId\": \"" + monitorId + "\"}}"; + String result = executeAgent(agentId, agentInput); + assertTrue(result.contains("TotalAlerts=1")); + } + + @SneakyThrows + public void testSearchAlertsToolInFlowAgent_singleAlert_filter_noMatch() { + setupAlertingSystemIndices(); + ingestSampleAlert(monitorId, "1"); + String agentId = createAgent(registerAgentRequestBody); + String agentInput = "{\"parameters\":{\"monitorId\": \"" + monitorId + "foo" + "\"}}"; + String result = executeAgent(agentId, agentInput); + assertTrue(result.contains("TotalAlerts=0")); + } + + @SneakyThrows + public void testSearchAlertsToolInFlowAgent_multipleAlerts_noFilter() { + setupAlertingSystemIndices(); + ingestSampleAlert(monitorId, "1"); + ingestSampleAlert(monitorId + "foo", "2"); + ingestSampleAlert(monitorId + "bar", "3"); + String agentId = createAgent(registerAgentRequestBody); + String agentInput = "{\"parameters\":{}}"; + String result = executeAgent(agentId, agentInput); + assertTrue(result.contains("TotalAlerts=3")); + } + + @SneakyThrows + public void testSearchAlertsToolInFlowAgent_multipleAlerts_filter() { + setupAlertingSystemIndices(); + ingestSampleAlert(monitorId, "1"); + ingestSampleAlert(monitorId + "foo", "2"); + ingestSampleAlert(monitorId + "bar", "3"); + String agentId = createAgent(registerAgentRequestBody); + String agentInput = "{\"parameters\":{\"monitorId\": \"" + monitorId + "\"}}"; + String result = executeAgent(agentId, agentInput); + assertTrue(result.contains("TotalAlerts=1")); + } + + @SneakyThrows + public void testSearchAlertsToolInFlowAgent_multipleAlerts_complexParams() { + setupAlertingSystemIndices(); + String monitorId2 = monitorId + "2"; + String monitorId3 = monitorId + "3"; + ingestSampleAlert(monitorId, "1"); + ingestSampleAlert(monitorId2, "2"); + ingestSampleAlert(monitorId3, "3"); + String agentId = createAgent(registerAgentRequestBody); + String agentInput = "{\"parameters\":{\"monitorIds\": " + + "[ \"" + + monitorId + + "\", \"" + + monitorId2 + + "\", \"" + + monitorId3 + + "\" ], " + + "\"sortOrder\": \"asc\", \"sortString\": \"monitor_name.keyword\", \"size\": 3, \"startIndex\": 0, \"severityLevel\": \"ALL\", \"alertState\": \"ALL\" } }"; + + String result = executeAgent(agentId, agentInput); + assertTrue(result.contains("TotalAlerts=3")); + } + + @SneakyThrows + private void setupAlertingSystemIndices() { + createIndexWithConfiguration(ToolConstants.ALERTING_ALERTS_INDEX, alertsIndexMappings); + createIndexWithConfiguration(ToolConstants.ALERTING_CONFIG_INDEX, alertingConfigIndexMappings); + } + + private void ingestSampleAlert(String monitorId, String docId) { + JsonObject sampleAlertJson = new Gson().fromJson(sampleAlert, JsonObject.class); + sampleAlertJson.addProperty("monitor_id", monitorId); + addDocToIndex(ToolConstants.ALERTING_ALERTS_INDEX, docId, sampleAlertJson.toString()); + } + } diff --git a/src/test/java/org/opensearch/integTest/SearchAnomalyDetectorsToolIT.java b/src/test/java/org/opensearch/integTest/SearchAnomalyDetectorsToolIT.java index aa406f8d..336d412a 100644 --- a/src/test/java/org/opensearch/integTest/SearchAnomalyDetectorsToolIT.java +++ b/src/test/java/org/opensearch/integTest/SearchAnomalyDetectorsToolIT.java @@ -11,31 +11,43 @@ import org.junit.After; import org.junit.Before; +import org.junit.jupiter.api.BeforeEach; import org.opensearch.agent.tools.utils.ToolConstants; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + import lombok.SneakyThrows; public class SearchAnomalyDetectorsToolIT extends BaseAgentToolsIT { private String registerAgentRequestBody; - private static final String detectorId = "foo-id"; + private String detectorsIndexMappings; + private String sampleDetector; + private String sampleIndexMappings; private static final String detectorName = "foo-name"; + private static final String registerAgentFilepath = + "org/opensearch/agent/tools/anomaly-detection/register_flow_agent_of_search_anomaly_detectors_tool_request_body.json"; + private static final String detectorsIndexMappingsFilepath = + "org/opensearch/agent/tools/anomaly-detection/detectors_index_mappings.json"; + private static final String sampleDetectorFilepath = "org/opensearch/agent/tools/anomaly-detection/sample_detector.json"; + private static final String sampleIndexMappingsFilepath = "org/opensearch/agent/tools/anomaly-detection/sample_index_mappings.json"; @Before @SneakyThrows public void setUp() { super.setUp(); - registerAgentRequestBody = Files - .readString( - Path - .of( - this - .getClass() - .getClassLoader() - .getResource("org/opensearch/agent/tools/register_flow_agent_of_search_detectors_tool_request_body.json") - .toURI() - ) - ); - createDetectorsSystemIndex(detectorId, detectorName); + registerAgentRequestBody = Files.readString(Path.of(this.getClass().getClassLoader().getResource(registerAgentFilepath).toURI())); + detectorsIndexMappings = Files + .readString(Path.of(this.getClass().getClassLoader().getResource(detectorsIndexMappingsFilepath).toURI())); + sampleDetector = Files.readString(Path.of(this.getClass().getClassLoader().getResource(sampleDetectorFilepath).toURI())); + sampleIndexMappings = Files.readString(Path.of(this.getClass().getClassLoader().getResource(sampleIndexMappingsFilepath).toURI())); + } + + @BeforeEach + @SneakyThrows + public void prepareTest() { + deleteSystemIndices(); } @After @@ -48,7 +60,6 @@ public void tearDown() { @SneakyThrows public void testSearchAnomalyDetectorsToolInFlowAgent_withNoSystemIndex() { - deleteSystemIndices(); String agentId = createAgent(registerAgentRequestBody); String agentInput = "{\"parameters\":{\"detectorName\": \"" + detectorName + "\"}}"; String result = executeAgent(agentId, agentInput); @@ -57,6 +68,9 @@ public void testSearchAnomalyDetectorsToolInFlowAgent_withNoSystemIndex() { @SneakyThrows public void testSearchAnomalyDetectorsToolInFlowAgent_noMatching() { + setupADSystemIndices(); + setupTestDetectionIndex("test-index"); + ingestSampleDetector(detectorName, "test-index"); String agentId = createAgent(registerAgentRequestBody); String agentInput = "{\"parameters\":{\"detectorName\": \"" + detectorName + "foo" + "\"}}"; String result = executeAgent(agentId, agentInput); @@ -65,6 +79,9 @@ public void testSearchAnomalyDetectorsToolInFlowAgent_noMatching() { @SneakyThrows public void testSearchAnomalyDetectorsToolInFlowAgent_matching() { + setupADSystemIndices(); + setupTestDetectionIndex("test-index"); + String detectorId = ingestSampleDetector(detectorName, "test-index"); String agentId = createAgent(registerAgentRequestBody); String agentInput = "{\"parameters\":{\"detectorName\": \"" + detectorName + "\"}}"; String result = executeAgent(agentId, agentInput); @@ -74,20 +91,39 @@ public void testSearchAnomalyDetectorsToolInFlowAgent_matching() { } @SneakyThrows - private void createDetectorsSystemIndex(String detectorId, String detectorName) { - createIndexWithConfiguration( - ToolConstants.AD_DETECTORS_INDEX, - "{\n" - + " \"mappings\": {\n" - + " \"properties\": {\n" - + " \"name\": {\n" - + " \"type\": \"text\",\n" - + " \"fields\": { \"keyword\": { \"type\": \"keyword\", \"ignore_above\": 256 }}" - + " }\n" - + " }\n" - + " }\n" - + "}" - ); - addDocToIndex(ToolConstants.AD_DETECTORS_INDEX, detectorId, List.of("name"), List.of(detectorName)); + public void testSearchAnomalyDetectorsToolInFlowAgent_complexParams() { + setupADSystemIndices(); + setupTestDetectionIndex("test-index"); + String detectorId = ingestSampleDetector(detectorName, "test-index"); + ingestSampleDetector(detectorName + "foo", "test-index"); + String agentId = createAgent(registerAgentRequestBody); + String agentInput = "{\"parameters\":{\"detectorName\": \"" + + detectorName + + "\", \"highCardinality\": false, \"sortOrder\": \"asc\", \"sortString\": \"name.keyword\", \"size\": 10, \"startIndex\": 0 }}"; + String result = executeAgent(agentId, agentInput); + assertTrue(result.contains(String.format("id=%s", detectorId))); + assertTrue(result.contains(String.format("name=%s", detectorName))); + assertTrue(result.contains(String.format("TotalAnomalyDetectors=%d", 1))); + } + + @SneakyThrows + private void setupADSystemIndices() { + createIndexWithConfiguration(ToolConstants.AD_DETECTORS_INDEX, detectorsIndexMappings); + } + + @SneakyThrows + private void setupTestDetectionIndex(String indexName) { + createIndexWithConfiguration(indexName, sampleIndexMappings); + addDocToIndex(indexName, "foo-id", List.of("timestamp", "value"), List.of(1234, 1)); + } + + private String ingestSampleDetector(String detectorName, String detectionIndex) { + JsonObject sampleDetectorJson = new Gson().fromJson(sampleDetector, JsonObject.class); + JsonArray arr = new JsonArray(1); + arr.add(detectionIndex); + sampleDetectorJson.addProperty("name", detectorName); + sampleDetectorJson.remove("indices"); + sampleDetectorJson.add("indices", arr); + return indexDetector(sampleDetectorJson.toString()); } } diff --git a/src/test/java/org/opensearch/integTest/SearchAnomalyResultsToolIT.java b/src/test/java/org/opensearch/integTest/SearchAnomalyResultsToolIT.java index 6454af5e..d9afb684 100644 --- a/src/test/java/org/opensearch/integTest/SearchAnomalyResultsToolIT.java +++ b/src/test/java/org/opensearch/integTest/SearchAnomalyResultsToolIT.java @@ -7,37 +7,45 @@ import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; import java.util.Locale; import org.junit.After; import org.junit.Before; +import org.junit.jupiter.api.BeforeEach; +import org.opensearch.agent.tools.utils.ToolConstants; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; import lombok.SneakyThrows; public class SearchAnomalyResultsToolIT extends BaseAgentToolsIT { private String registerAgentRequestBody; + private String resultsIndexMappings; + private String sampleResult; private static final String detectorId = "foo-id"; private static final double anomalyGrade = 0.5; private static final double confidence = 0.6; private static final String resultsSystemIndexName = ".opendistro-anomaly-results-1"; + private static final String registerAgentFilepath = + "org/opensearch/agent/tools/anomaly-detection/register_flow_agent_of_search_anomaly_results_tool_request_body.json"; + private static final String resultsIndexMappingsFilepath = "org/opensearch/agent/tools/anomaly-detection/results_index_mappings.json"; + private static final String sampleResultFilepath = "org/opensearch/agent/tools/anomaly-detection/sample_result.json"; @Before @SneakyThrows public void setUp() { super.setUp(); - registerAgentRequestBody = Files - .readString( - Path - .of( - this - .getClass() - .getClassLoader() - .getResource("org/opensearch/agent/tools/register_flow_agent_of_search_anomaly_results_tool_request_body.json") - .toURI() - ) - ); - createAnomalyResultsSystemIndex(detectorId, anomalyGrade, confidence); + registerAgentRequestBody = Files.readString(Path.of(this.getClass().getClassLoader().getResource(registerAgentFilepath).toURI())); + resultsIndexMappings = Files + .readString(Path.of(this.getClass().getClassLoader().getResource(resultsIndexMappingsFilepath).toURI())); + sampleResult = Files.readString(Path.of(this.getClass().getClassLoader().getResource(sampleResultFilepath).toURI())); + } + + @BeforeEach + @SneakyThrows + public void prepareTest() { + deleteSystemIndices(); } @After @@ -50,7 +58,6 @@ public void tearDown() { @SneakyThrows public void testSearchAnomalyResultsToolInFlowAgent_withNoSystemIndex() { - deleteSystemIndices(); String agentId = createAgent(registerAgentRequestBody); String agentInput = "{\"parameters\":{\"detectorId\": \"" + detectorId + "\"}}"; String result = executeAgent(agentId, agentInput); @@ -59,6 +66,8 @@ public void testSearchAnomalyResultsToolInFlowAgent_withNoSystemIndex() { @SneakyThrows public void testSearchAnomalyResultsToolInFlowAgent_noMatching() { + setupADSystemIndices(); + ingestSampleResult(detectorId, 0.5, 0.5, "1"); String agentId = createAgent(registerAgentRequestBody); String agentInput = "{\"parameters\":{\"detectorId\": \"" + detectorId + "foo" + "\"}}"; String result = executeAgent(agentId, agentInput); @@ -67,6 +76,8 @@ public void testSearchAnomalyResultsToolInFlowAgent_noMatching() { @SneakyThrows public void testSearchAnomalyResultsToolInFlowAgent_matching() { + setupADSystemIndices(); + ingestSampleResult(detectorId, anomalyGrade, confidence, "1"); String agentId = createAgent(registerAgentRequestBody); String agentInput = "{\"parameters\":{\"detectorId\": \"" + detectorId + "\"}}"; String result = executeAgent(agentId, agentInput); @@ -85,25 +96,41 @@ public void testSearchAnomalyResultsToolInFlowAgent_matching() { } @SneakyThrows - private void createAnomalyResultsSystemIndex(String detectorId, double anomalyGrade, double confidence) { - createIndexWithConfiguration( - resultsSystemIndexName, - "{\n" - + " \"mappings\": {\n" - + " \"properties\": {\n" - + " \"detector_id\": {\"type\": \"keyword\"}," - + " \"anomaly_grade\": {\"type\": \"double\"}," - + " \"confidence\": {\"type\": \"double\"}," - + " \"data_start_time\": {\"type\": \"date\", \"format\": \"strict_date_time||epoch_millis\"}" - + " }\n" - + " }\n" - + "}" - ); - addDocToIndex( - resultsSystemIndexName, - "foo-id", - List.of("detector_id", "anomaly_grade", "confidence"), - List.of(detectorId, anomalyGrade, confidence) + public void testSearchAnomalyResultsToolInFlowAgent_complexParams() { + setupADSystemIndices(); + ingestSampleResult(detectorId, anomalyGrade, confidence, "1"); + ingestSampleResult(detectorId + "foo", anomalyGrade, confidence, "2"); + String agentId = createAgent(registerAgentRequestBody); + String agentInput = "{\"parameters\":{\"detectorId\": \"" + + detectorId + + "\"," + + "\"realTime\": true, \"anomalyGradeThreshold\": 0, \"sortOrder\": \"asc\"," + + "\"sortString\": \"data_start_time\", \"size\": 10, \"startIndex\": 0 }}"; + String result = executeAgent(agentId, agentInput); + assertEquals( + String + .format( + Locale.ROOT, + "AnomalyResults=[{detectorId=%s,grade=%2.1f,confidence=%2.1f}]TotalAnomalyResults=%d", + detectorId, + anomalyGrade, + confidence, + 1 + ), + result ); } + + @SneakyThrows + private void setupADSystemIndices() { + createIndexWithConfiguration(ToolConstants.AD_RESULTS_INDEX, resultsIndexMappings); + } + + private void ingestSampleResult(String detectorId, double anomalyGrade, double anomalyConfidence, String docId) { + JsonObject sampleResultJson = new Gson().fromJson(sampleResult, JsonObject.class); + sampleResultJson.addProperty("detector_id", detectorId); + sampleResultJson.addProperty("anomaly_grade", anomalyGrade); + sampleResultJson.addProperty("confidence", confidence); + addDocToIndex(ToolConstants.AD_RESULTS_INDEX, docId, sampleResultJson.toString()); + } } diff --git a/src/test/java/org/opensearch/integTest/SearchMonitorsToolIT.java b/src/test/java/org/opensearch/integTest/SearchMonitorsToolIT.java index 2ed9c726..e14fbca8 100644 --- a/src/test/java/org/opensearch/integTest/SearchMonitorsToolIT.java +++ b/src/test/java/org/opensearch/integTest/SearchMonitorsToolIT.java @@ -9,39 +9,33 @@ import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import org.json.JSONObject; import org.junit.After; import org.junit.Before; import org.junit.jupiter.api.BeforeEach; +import com.google.gson.Gson; +import com.google.gson.JsonObject; + import lombok.SneakyThrows; import lombok.extern.log4j.Log4j2; @Log4j2 public class SearchMonitorsToolIT extends BaseAgentToolsIT { private String registerAgentRequestBody; + private String sampleMonitor; private static final String monitorName = "foo-name"; private static final String monitorName2 = "bar-name"; + private static final String registerAgentFilepath = + "org/opensearch/agent/tools/alerting/register_flow_agent_of_search_monitors_tool_request_body.json"; + private static final String sampleMonitorFilepath = "org/opensearch/agent/tools/alerting/sample_monitor.json"; @Before @SneakyThrows public void setUp() { super.setUp(); - registerAgentRequestBody = Files - .readString( - Path - .of( - this - .getClass() - .getClassLoader() - .getResource("org/opensearch/agent/tools/register_flow_agent_of_search_monitors_tool_request_body.json") - .toURI() - ) - ); + registerAgentRequestBody = Files.readString(Path.of(this.getClass().getClassLoader().getResource(registerAgentFilepath).toURI())); + sampleMonitor = Files.readString(Path.of(this.getClass().getClassLoader().getResource(sampleMonitorFilepath).toURI())); } @BeforeEach @@ -68,7 +62,7 @@ public void testSearchMonitorsToolInFlowAgent_withNoSystemIndex() { @SneakyThrows public void testSearchMonitorsToolInFlowAgent_searchById() { - String monitorId = indexMonitor(getMonitorJsonString(monitorName, true)); + String monitorId = ingestSampleMonitor(monitorName, true); String agentId = createAgent(registerAgentRequestBody); String agentInput = "{\"parameters\":{\"monitorId\": \"" + monitorId + "\"}}"; @@ -80,7 +74,7 @@ public void testSearchMonitorsToolInFlowAgent_searchById() { @SneakyThrows public void testSearchMonitorsToolInFlowAgent_singleMonitor_noFilter() { - indexMonitor(getMonitorJsonString(monitorName, true)); + ingestSampleMonitor(monitorName, true); String agentId = createAgent(registerAgentRequestBody); String agentInput = "{\"parameters\":{}}"; @@ -99,8 +93,8 @@ public void testSearchMonitorsToolInFlowAgent_singleMonitor_filter() { @SneakyThrows public void testSearchMonitorsToolInFlowAgent_multipleMonitors_noFilter() { - indexMonitor(getMonitorJsonString(monitorName, true)); - indexMonitor(getMonitorJsonString(monitorName2, false)); + ingestSampleMonitor(monitorName, true); + ingestSampleMonitor(monitorName2, false); String agentId = createAgent(registerAgentRequestBody); String agentInput = "{\"parameters\":{}}"; @@ -114,8 +108,8 @@ public void testSearchMonitorsToolInFlowAgent_multipleMonitors_noFilter() { @SneakyThrows public void testSearchMonitorsToolInFlowAgent_multipleMonitors_filter() { - indexMonitor(getMonitorJsonString(monitorName, true)); - indexMonitor(getMonitorJsonString(monitorName2, false)); + ingestSampleMonitor(monitorName, true); + ingestSampleMonitor(monitorName2, false); String agentId = createAgent(registerAgentRequestBody); String agentInput = "{\"parameters\":{\"monitorName\": \"" + monitorName + "\"}}"; @@ -126,20 +120,23 @@ public void testSearchMonitorsToolInFlowAgent_multipleMonitors_filter() { assertTrue(result.contains("TotalMonitors=1")); } - // Helper fn to create the JSON string to use in a REST request body when indexing a monitor - private String getMonitorJsonString(String monitorName, boolean enabled) { - JSONObject jsonObj = new JSONObject(); - jsonObj.put("type", "monitor"); - jsonObj.put("name", monitorName); - jsonObj.put("enabled", String.valueOf(enabled)); - jsonObj.put("inputs", Collections.emptyList()); - jsonObj.put("triggers", Collections.emptyList()); - Map scheduleMap = new HashMap(); - Map periodMap = new HashMap(); - periodMap.put("interval", 5); - periodMap.put("unit", "MINUTES"); - scheduleMap.put("period", periodMap); - jsonObj.put("schedule", scheduleMap); - return jsonObj.toString(); + @SneakyThrows + public void testSearchMonitorsToolInFlowAgent_multipleMonitors_complexParams() { + ingestSampleMonitor(monitorName, true); + ingestSampleMonitor(monitorName2, false); + + String agentId = createAgent(registerAgentRequestBody); + String agentInput = "{\"parameters\":{\"monitorName\": \"" + + monitorName + + "\", \"enabled\": true, \"hasTriggers\": false, \"sortOrder\": \"asc\", \"sortString\": \"monitor.name.keyword\", \"size\": 10, \"startIndex\": 0 }}"; + String result = executeAgent(agentId, agentInput); + assertTrue(result.contains("TotalMonitors=1")); + } + + private String ingestSampleMonitor(String monitorName, boolean enabled) { + JsonObject sampleMonitorJson = new Gson().fromJson(sampleMonitor, JsonObject.class); + sampleMonitorJson.addProperty("name", monitorName); + sampleMonitorJson.addProperty("enabled", String.valueOf(enabled)); + return indexMonitor(sampleMonitorJson.toString()); } } diff --git a/src/test/resources/org/opensearch/agent/tools/alerting/alert_index_mappings.json b/src/test/resources/org/opensearch/agent/tools/alerting/alert_index_mappings.json new file mode 100644 index 00000000..9d8c5ce8 --- /dev/null +++ b/src/test/resources/org/opensearch/agent/tools/alerting/alert_index_mappings.json @@ -0,0 +1,173 @@ +{ + "mappings": { + "dynamic": "strict", + "_meta": { + "schema_version": 5 + }, + "properties": { + "schema_version": { + "type": "integer" + }, + "monitor_id": { + "type": "keyword" + }, + "monitor_version": { + "type": "long" + }, + "id": { + "type": "keyword" + }, + "version": { + "type": "long" + }, + "severity": { + "type": "keyword" + }, + "monitor_name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "monitor_user": { + "properties": { + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "backend_roles": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "roles": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "custom_attribute_names": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + } + } + }, + "execution_id": { + "type": "keyword" + }, + "workflow_id": { + "type": "keyword" + }, + "workflow_name": { + "type": "keyword" + }, + "trigger_id": { + "type": "keyword" + }, + "trigger_name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "finding_ids": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "associated_alert_ids": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "related_doc_ids": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "state": { + "type": "keyword" + }, + "start_time": { + "type": "date" + }, + "last_notification_time": { + "type": "date" + }, + "acknowledged_time": { + "type": "date" + }, + "end_time": { + "type": "date" + }, + "error_message": { + "type": "text" + }, + "alert_history": { + "type": "nested", + "properties": { + "timestamp": { + "type": "date" + }, + "message": { + "type": "text" + } + } + }, + "action_execution_results": { + "type": "nested", + "properties": { + "action_id": { + "type": "keyword" + }, + "last_execution_time": { + "type": "date" + }, + "throttled_count": { + "type": "integer" + } + } + }, + "agg_alert_content": { + "dynamic": true, + "properties": { + "parent_bucket_path": { + "type": "text" + }, + "bucket_key": { + "type": "text" + } + } + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/org/opensearch/agent/tools/alerting/alerting_config_index_mappings.json b/src/test/resources/org/opensearch/agent/tools/alerting/alerting_config_index_mappings.json new file mode 100644 index 00000000..759cd448 --- /dev/null +++ b/src/test/resources/org/opensearch/agent/tools/alerting/alerting_config_index_mappings.json @@ -0,0 +1,1269 @@ +{ + "mappings": { + "_meta": { + "schema_version": 8 + }, + "properties": { + "audit_delegate_monitor_alerts": { + "type": "boolean" + }, + "data_sources": { + "properties": { + "alerts_history_index": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "alerts_history_index_pattern": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "alerts_index": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "findings_enabled": { + "type": "boolean" + }, + "findings_index": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "findings_index_pattern": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "query_index": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "query_index_mappings_by_type": { + "type": "object" + } + } + }, + "destination": { + "dynamic": "false", + "properties": { + "chime": { + "properties": { + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "custom_webhook": { + "properties": { + "header_params": { + "type": "object", + "enabled": false + }, + "host": { + "type": "text" + }, + "password": { + "type": "text" + }, + "path": { + "type": "keyword" + }, + "port": { + "type": "integer" + }, + "query_params": { + "type": "object", + "enabled": false + }, + "scheme": { + "type": "keyword" + }, + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "username": { + "type": "text" + } + } + }, + "email": { + "properties": { + "email_account_id": { + "type": "keyword" + }, + "recipients": { + "type": "nested", + "properties": { + "email": { + "type": "text" + }, + "email_group_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + } + } + }, + "last_update_time": { + "type": "date", + "format": "strict_date_time||epoch_millis" + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "schema_version": { + "type": "integer" + }, + "slack": { + "properties": { + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "type": { + "type": "keyword" + }, + "user": { + "properties": { + "backend_roles": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "custom_attribute_names": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "roles": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + } + } + } + } + }, + "email_account": { + "properties": { + "from": { + "type": "text" + }, + "host": { + "type": "text" + }, + "method": { + "type": "text" + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "port": { + "type": "integer" + } + } + }, + "email_group": { + "properties": { + "emails": { + "type": "nested", + "properties": { + "email": { + "type": "text" + } + } + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "properties": { + "composite_input": { + "properties": { + "sequence": { + "properties": { + "delegates": { + "properties": { + "monitor_id": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "order": { + "type": "long" + } + } + } + } + } + } + }, + "doc_level_input": { + "properties": { + "description": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "indices": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "queries": { + "properties": { + "id": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "query": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + } + } + }, + "search": { + "properties": { + "indices": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "query": { + "properties": { + "aggregations": { + "properties": { + "metric": { + "properties": { + "avg": { + "properties": { + "field": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + } + } + } + } + }, + "query": { + "properties": { + "bool": { + "properties": { + "adjust_pure_negative": { + "type": "boolean" + }, + "boost": { + "type": "long" + }, + "filter": { + "properties": { + "range": { + "properties": { + "dayOfWeek": { + "properties": { + "boost": { + "type": "long" + }, + "from": { + "type": "long" + }, + "include_lower": { + "type": "boolean" + }, + "include_upper": { + "type": "boolean" + }, + "to": { + "type": "long" + } + } + }, + "timestamp": { + "properties": { + "boost": { + "type": "long" + }, + "format": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "from": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "include_lower": { + "type": "boolean" + }, + "include_upper": { + "type": "boolean" + }, + "to": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + } + } + }, + "term": { + "properties": { + "dayOfWeek": { + "properties": { + "boost": { + "type": "long" + }, + "value": { + "type": "long" + } + } + } + } + } + } + } + } + } + } + }, + "size": { + "type": "long" + } + } + } + } + }, + "uri": { + "properties": { + "api_type": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "path": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "path_params": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + } + } + }, + "last_update_time": { + "type": "long" + }, + "metadata": { + "properties": { + "last_action_execution_times": { + "type": "nested", + "properties": { + "action_id": { + "type": "keyword" + }, + "execution_time": { + "type": "date", + "format": "strict_date_time||epoch_millis" + } + } + }, + "last_run_context": { + "type": "object", + "enabled": false + }, + "monitor_id": { + "type": "keyword" + }, + "source_to_query_index_mapping": { + "type": "object", + "enabled": false + } + } + }, + "monitor": { + "dynamic": "false", + "properties": { + "data_sources": { + "properties": { + "alerts_index": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "findings_index": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "query_index": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "query_index_mapping": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "enabled": { + "type": "boolean" + }, + "enabled_time": { + "type": "date", + "format": "strict_date_time||epoch_millis" + }, + "group_by_fields": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "inputs": { + "type": "nested", + "properties": { + "search": { + "properties": { + "indices": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "query": { + "type": "object", + "enabled": false + } + } + } + } + }, + "last_update_time": { + "type": "date", + "format": "strict_date_time||epoch_millis" + }, + "monitor_type": { + "type": "keyword" + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "owner": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "schedule": { + "properties": { + "cron": { + "properties": { + "expression": { + "type": "text" + }, + "timezone": { + "type": "keyword" + } + } + }, + "period": { + "properties": { + "interval": { + "type": "integer" + }, + "unit": { + "type": "keyword" + } + } + } + } + }, + "schema_version": { + "type": "integer" + }, + "triggers": { + "type": "nested", + "properties": { + "actions": { + "type": "nested", + "properties": { + "destination_id": { + "type": "keyword" + }, + "message_template": { + "type": "object", + "enabled": false + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "subject_template": { + "type": "object", + "enabled": false + }, + "throttle": { + "properties": { + "unit": { + "type": "keyword" + }, + "value": { + "type": "integer" + } + } + }, + "throttle_enabled": { + "type": "boolean" + } + } + }, + "condition": { + "type": "object", + "enabled": false + }, + "min_time_between_executions": { + "type": "integer" + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "query_level_trigger": { + "properties": { + "actions": { + "type": "nested", + "properties": { + "destination_id": { + "type": "keyword" + }, + "message_template": { + "type": "object", + "enabled": false + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "subject_template": { + "type": "object", + "enabled": false + }, + "throttle": { + "properties": { + "unit": { + "type": "keyword" + }, + "value": { + "type": "integer" + } + } + }, + "throttle_enabled": { + "type": "boolean" + } + } + }, + "condition": { + "type": "object", + "enabled": false + }, + "min_time_between_executions": { + "type": "integer" + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + } + } + }, + "type": { + "type": "keyword" + }, + "ui_metadata": { + "type": "object", + "enabled": false + }, + "user": { + "properties": { + "backend_roles": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "custom_attribute_names": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "roles": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + } + } + } + } + }, + "monitor_type": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "owner": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "schedule": { + "properties": { + "period": { + "properties": { + "interval": { + "type": "long" + }, + "unit": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + } + } + }, + "schema_version": { + "type": "long" + }, + "triggers": { + "properties": { + "chained_alert_trigger": { + "properties": { + "condition": { + "properties": { + "script": { + "properties": { + "lang": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "source": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + } + } + }, + "id": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "severity": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "document_level_trigger": { + "properties": { + "condition": { + "properties": { + "script": { + "properties": { + "lang": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "source": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + } + } + }, + "id": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "severity": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "query_level_trigger": { + "properties": { + "condition": { + "properties": { + "script": { + "properties": { + "lang": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "source": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + } + } + }, + "id": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "severity": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + } + } + }, + "type": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "workflow": { + "dynamic": "false", + "properties": { + "audit_delegate_monitor_alerts": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "enabled_time": { + "type": "date", + "format": "strict_date_time||epoch_millis" + }, + "group_by_fields": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "inputs": { + "type": "nested", + "properties": { + "composite_input": { + "type": "nested", + "properties": { + "sequence": { + "properties": { + "delegates": { + "type": "nested", + "properties": { + "chained_monitor_findings": { + "properties": { + "monitor_id": { + "type": "keyword" + } + } + }, + "monitor_id": { + "type": "keyword" + }, + "order": { + "type": "integer" + } + } + } + } + } + } + } + } + }, + "last_update_time": { + "type": "date", + "format": "strict_date_time||epoch_millis" + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "owner": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "schedule": { + "properties": { + "cron": { + "properties": { + "expression": { + "type": "text" + }, + "timezone": { + "type": "keyword" + } + } + }, + "period": { + "properties": { + "interval": { + "type": "integer" + }, + "unit": { + "type": "keyword" + } + } + } + } + }, + "schema_version": { + "type": "integer" + }, + "type": { + "type": "keyword" + }, + "user": { + "properties": { + "backend_roles": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "custom_attribute_names": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "roles": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + } + } + }, + "workflow_type": { + "type": "keyword" + } + } + }, + "workflow_metadata": { + "properties": { + "latest_execution_id": { + "type": "keyword" + }, + "latest_run_time": { + "type": "date", + "format": "strict_date_time||epoch_millis" + }, + "monitor_ids": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 1000 + } + } + }, + "workflow_id": { + "type": "keyword" + } + } + }, + "workflow_type": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/org/opensearch/agent/tools/register_flow_agent_of_search_alerts_tool_request_body.json b/src/test/resources/org/opensearch/agent/tools/alerting/register_flow_agent_of_search_alerts_tool_request_body.json similarity index 100% rename from src/test/resources/org/opensearch/agent/tools/register_flow_agent_of_search_alerts_tool_request_body.json rename to src/test/resources/org/opensearch/agent/tools/alerting/register_flow_agent_of_search_alerts_tool_request_body.json diff --git a/src/test/resources/org/opensearch/agent/tools/register_flow_agent_of_search_monitors_tool_request_body.json b/src/test/resources/org/opensearch/agent/tools/alerting/register_flow_agent_of_search_monitors_tool_request_body.json similarity index 100% rename from src/test/resources/org/opensearch/agent/tools/register_flow_agent_of_search_monitors_tool_request_body.json rename to src/test/resources/org/opensearch/agent/tools/alerting/register_flow_agent_of_search_monitors_tool_request_body.json diff --git a/src/test/resources/org/opensearch/agent/tools/alerting/sample_alert.json b/src/test/resources/org/opensearch/agent/tools/alerting/sample_alert.json new file mode 100644 index 00000000..65574189 --- /dev/null +++ b/src/test/resources/org/opensearch/agent/tools/alerting/sample_alert.json @@ -0,0 +1,23 @@ +{ + "monitor_id": "foo-monitor-id", + "workflow_id": "", + "workflow_name": "", + "associated_alert_ids": [], + "schema_version": 5, + "monitor_version": 1, + "monitor_name": "foo-monitor", + "execution_id": "foo-execution-id", + "trigger_id": "foo-trigger-id", + "trigger_name": "foo-trigger-name", + "finding_ids": [], + "related_doc_ids": [], + "state": "COMPLETED", + "error_message": null, + "alert_history": [], + "severity": "2", + "action_execution_results": [], + "start_time": 1234, + "last_notification_time": 1234, + "end_time": 1234, + "acknowledged_time": null +} \ No newline at end of file diff --git a/src/test/resources/org/opensearch/agent/tools/alerting/sample_monitor.json b/src/test/resources/org/opensearch/agent/tools/alerting/sample_monitor.json new file mode 100644 index 00000000..d7e071a4 --- /dev/null +++ b/src/test/resources/org/opensearch/agent/tools/alerting/sample_monitor.json @@ -0,0 +1,15 @@ +{ + "type": "monitor", + "schema_version": 0, + "name": "foo-monitor", + "monitor_type": "query_level_monitor", + "enabled": true, + "schedule": { + "period": { + "interval": 1, + "unit": "MINUTES" + } + }, + "inputs": [], + "triggers": [] +} \ No newline at end of file diff --git a/src/test/resources/org/opensearch/agent/tools/anomaly-detection/detectors_index_mappings.json b/src/test/resources/org/opensearch/agent/tools/anomaly-detection/detectors_index_mappings.json new file mode 100644 index 00000000..561f30ce --- /dev/null +++ b/src/test/resources/org/opensearch/agent/tools/anomaly-detection/detectors_index_mappings.json @@ -0,0 +1,157 @@ +{ + "mappings": { + "dynamic": "false", + "_meta": { + "schema_version": 5 + }, + "properties": { + "category_field": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "detection_interval": { + "properties": { + "period": { + "properties": { + "interval": { + "type": "integer" + }, + "unit": { + "type": "keyword" + } + } + } + } + }, + "detector_type": { + "type": "keyword" + }, + "feature_attributes": { + "type": "nested", + "properties": { + "aggregation_query": { + "type": "object", + "enabled": false + }, + "feature_enabled": { + "type": "boolean" + }, + "feature_id": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "feature_name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "filter_query": { + "type": "object", + "enabled": false + }, + "indices": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "last_update_time": { + "type": "date", + "format": "strict_date_time||epoch_millis" + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "result_index": { + "type": "keyword" + }, + "schema_version": { + "type": "integer" + }, + "shingle_size": { + "type": "integer" + }, + "time_field": { + "type": "keyword" + }, + "ui_metadata": { + "type": "object", + "enabled": false + }, + "user": { + "type": "nested", + "properties": { + "backend_roles": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "custom_attribute_names": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "roles": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + } + } + }, + "window_delay": { + "properties": { + "period": { + "properties": { + "interval": { + "type": "integer" + }, + "unit": { + "type": "keyword" + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/org/opensearch/agent/tools/register_flow_agent_of_search_detectors_tool_request_body.json b/src/test/resources/org/opensearch/agent/tools/anomaly-detection/register_flow_agent_of_search_anomaly_detectors_tool_request_body.json similarity index 100% rename from src/test/resources/org/opensearch/agent/tools/register_flow_agent_of_search_detectors_tool_request_body.json rename to src/test/resources/org/opensearch/agent/tools/anomaly-detection/register_flow_agent_of_search_anomaly_detectors_tool_request_body.json diff --git a/src/test/resources/org/opensearch/agent/tools/register_flow_agent_of_search_anomaly_results_tool_request_body.json b/src/test/resources/org/opensearch/agent/tools/anomaly-detection/register_flow_agent_of_search_anomaly_results_tool_request_body.json similarity index 100% rename from src/test/resources/org/opensearch/agent/tools/register_flow_agent_of_search_anomaly_results_tool_request_body.json rename to src/test/resources/org/opensearch/agent/tools/anomaly-detection/register_flow_agent_of_search_anomaly_results_tool_request_body.json diff --git a/src/test/resources/org/opensearch/agent/tools/anomaly-detection/results_index_mappings.json b/src/test/resources/org/opensearch/agent/tools/anomaly-detection/results_index_mappings.json new file mode 100644 index 00000000..ee4e5e26 --- /dev/null +++ b/src/test/resources/org/opensearch/agent/tools/anomaly-detection/results_index_mappings.json @@ -0,0 +1,161 @@ +{ + "mappings": { + "dynamic": "false", + "_meta": { + "schema_version": 5 + }, + "properties": { + "anomaly_grade": { + "type": "double" + }, + "anomaly_score": { + "type": "double" + }, + "approx_anomaly_start_time": { + "type": "date", + "format": "strict_date_time||epoch_millis" + }, + "confidence": { + "type": "double" + }, + "data_end_time": { + "type": "date", + "format": "strict_date_time||epoch_millis" + }, + "data_start_time": { + "type": "date", + "format": "strict_date_time||epoch_millis" + }, + "detector_id": { + "type": "keyword" + }, + "entity": { + "type": "nested", + "properties": { + "name": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, + "error": { + "type": "text" + }, + "execution_end_time": { + "type": "date", + "format": "strict_date_time||epoch_millis" + }, + "execution_start_time": { + "type": "date", + "format": "strict_date_time||epoch_millis" + }, + "expected_values": { + "type": "nested", + "properties": { + "likelihood": { + "type": "double" + }, + "value_list": { + "type": "nested", + "properties": { + "data": { + "type": "double" + }, + "feature_id": { + "type": "keyword" + } + } + } + } + }, + "feature_data": { + "type": "nested", + "properties": { + "data": { + "type": "double" + }, + "feature_id": { + "type": "keyword" + } + } + }, + "is_anomaly": { + "type": "boolean" + }, + "model_id": { + "type": "keyword" + }, + "past_values": { + "type": "nested", + "properties": { + "data": { + "type": "double" + }, + "feature_id": { + "type": "keyword" + } + } + }, + "relevant_attribution": { + "type": "nested", + "properties": { + "data": { + "type": "double" + }, + "feature_id": { + "type": "keyword" + } + } + }, + "schema_version": { + "type": "integer" + }, + "task_id": { + "type": "keyword" + }, + "threshold": { + "type": "double" + }, + "user": { + "type": "nested", + "properties": { + "backend_roles": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "custom_attribute_names": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "roles": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/org/opensearch/agent/tools/anomaly-detection/sample_detector.json b/src/test/resources/org/opensearch/agent/tools/anomaly-detection/sample_detector.json new file mode 100644 index 00000000..b23a3e99 --- /dev/null +++ b/src/test/resources/org/opensearch/agent/tools/anomaly-detection/sample_detector.json @@ -0,0 +1,48 @@ +{ + "name": "test-detector", + "description": "Test detector", + "time_field": "timestamp", + "indices": [ + "test-index" + ], + "feature_attributes": [ + { + "feature_name": "test", + "feature_enabled": true, + "aggregation_query": { + "test": { + "sum": { + "field": "value" + } + } + } + } + ], + "filter_query": { + "bool": { + "filter": [ + { + "range": { + "value": { + "gt": 1 + } + } + } + ], + "adjust_pure_negative": true, + "boost": 1 + } + }, + "detection_interval": { + "period": { + "interval": 1, + "unit": "Minutes" + } + }, + "window_delay": { + "period": { + "interval": 1, + "unit": "Minutes" + } + } +} \ No newline at end of file diff --git a/src/test/resources/org/opensearch/agent/tools/anomaly-detection/sample_index_mappings.json b/src/test/resources/org/opensearch/agent/tools/anomaly-detection/sample_index_mappings.json new file mode 100644 index 00000000..0697e7bf --- /dev/null +++ b/src/test/resources/org/opensearch/agent/tools/anomaly-detection/sample_index_mappings.json @@ -0,0 +1,12 @@ +{ + "mappings": { + "properties": { + "value": { + "type": "integer" + }, + "timestamp": { + "type": "date" + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/org/opensearch/agent/tools/anomaly-detection/sample_result.json b/src/test/resources/org/opensearch/agent/tools/anomaly-detection/sample_result.json new file mode 100644 index 00000000..d81a4c32 --- /dev/null +++ b/src/test/resources/org/opensearch/agent/tools/anomaly-detection/sample_result.json @@ -0,0 +1,19 @@ +{ + "detector_id": "foo-id", + "schema_version": 5, + "data_start_time": 1234, + "data_end_time": 1234, + "feature_data": [ + { + "feature_id": "foo-feature-id", + "feature_name": "foo-feature-name", + "data": 1 + } + ], + "execution_start_time": 1234, + "execution_end_time": 1234, + "anomaly_score": 0.5, + "anomaly_grade": 0.5, + "confidence": 0.5, + "threshold": 0.8 +} \ No newline at end of file