-
Notifications
You must be signed in to change notification settings - Fork 138
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement search alerts tool #1629
Merged
ylwu-amzn
merged 10 commits into
opensearch-project:feature/agent_framework_dev
from
ohltyler:search-alerts-tool-2
Nov 16, 2023
Merged
Changes from 9 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
213a095
Add search alerts tool
ohltyler b22b1be
Clean up test params; change validate()
ohltyler 23b5f98
Add kotlin dependency
ohltyler 80fb47b
remove spotless
ohltyler dbb5652
revert one dep
ohltyler da31aec
Remove clusterservice; simplify param parsing
ohltyler 49126db
Add TYPE; cast o/p to String
ohltyler 4d0622e
Support null params passed
ohltyler 455ec8e
Expose startIndex
ohltyler 8a63aeb
Bump kotlin to 1.8.21
ohltyler File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
172 changes: 172 additions & 0 deletions
172
ml-algorithms/src/main/java/org/opensearch/ml/engine/tools/SearchAlertsTool.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.ml.engine.tools; | ||
|
||
import static org.opensearch.ml.common.utils.StringUtils.gson; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import org.opensearch.client.Client; | ||
import org.opensearch.client.node.NodeClient; | ||
import org.opensearch.commons.alerting.AlertingPluginInterface; | ||
import org.opensearch.commons.alerting.action.GetAlertsRequest; | ||
import org.opensearch.commons.alerting.action.GetAlertsResponse; | ||
import org.opensearch.commons.alerting.model.Alert; | ||
import org.opensearch.commons.alerting.model.Table; | ||
import org.opensearch.core.action.ActionListener; | ||
import org.opensearch.ml.common.output.model.ModelTensors; | ||
import org.opensearch.ml.common.spi.tools.Parser; | ||
import org.opensearch.ml.common.spi.tools.Tool; | ||
import org.opensearch.ml.common.spi.tools.ToolAnnotation; | ||
|
||
import lombok.Getter; | ||
import lombok.Setter; | ||
|
||
@ToolAnnotation(SearchAlertsTool.TYPE) | ||
public class SearchAlertsTool implements Tool { | ||
public static final String TYPE = "SearchAlertsTool"; | ||
private static final String DEFAULT_DESCRIPTION = "Use this tool to search alerts."; | ||
|
||
@Setter | ||
@Getter | ||
private String name = TYPE; | ||
@Getter | ||
@Setter | ||
private String description = DEFAULT_DESCRIPTION; | ||
@Getter | ||
private String type; | ||
@Getter | ||
private String version; | ||
|
||
private Client client; | ||
@Setter | ||
private Parser<?, ?> inputParser; | ||
@Setter | ||
private Parser<?, ?> outputParser; | ||
|
||
public SearchAlertsTool(Client client) { | ||
this.client = client; | ||
|
||
// probably keep this overridden output parser. need to ensure the output matches what's expected | ||
outputParser = new Parser<>() { | ||
@Override | ||
public Object parse(Object o) { | ||
@SuppressWarnings("unchecked") | ||
List<ModelTensors> mlModelOutputs = (List<ModelTensors>) o; | ||
return mlModelOutputs.get(0).getMlModelTensors().get(0).getDataAsMap().get("response"); | ||
} | ||
}; | ||
} | ||
|
||
@Override | ||
public <T> void run(Map<String, String> parameters, ActionListener<T> listener) { | ||
final String tableSortOrder = parameters.getOrDefault("sortOrder", "asc"); | ||
final String tableSortString = parameters.getOrDefault("sortString", "monitor_name.keyword"); | ||
final int tableSize = parameters.containsKey("size") ? Integer.parseInt(parameters.get("size")) : 20; | ||
final int startIndex = parameters.containsKey("startIndex") ? Integer.parseInt(parameters.get("startIndex")) : 0; | ||
final String searchString = parameters.getOrDefault("searchString", null); | ||
|
||
// not exposing "missing" from the table, using default of null | ||
final Table table = new Table(tableSortOrder, tableSortString, null, tableSize, startIndex, searchString); | ||
|
||
final String severityLevel = parameters.getOrDefault("severityLevel", "ALL"); | ||
final String alertState = parameters.getOrDefault("alertState", "ALL"); | ||
final String monitorId = parameters.getOrDefault("monitorId", null); | ||
final String alertIndex = parameters.getOrDefault("alertIndex", null); | ||
@SuppressWarnings("unchecked") | ||
final List<String> monitorIds = parameters.containsKey("monitorIds") | ||
? gson.fromJson(parameters.get("monitorIds"), List.class) | ||
: null; | ||
@SuppressWarnings("unchecked") | ||
final List<String> workflowIds = parameters.containsKey("workflowIds") | ||
? gson.fromJson(parameters.get("workflowIds"), List.class) | ||
: null; | ||
@SuppressWarnings("unchecked") | ||
final List<String> alertIds = parameters.containsKey("alertIds") ? gson.fromJson(parameters.get("alertIds"), List.class) : null; | ||
|
||
GetAlertsRequest getAlertsRequest = new GetAlertsRequest( | ||
table, | ||
severityLevel, | ||
alertState, | ||
monitorId, | ||
alertIndex, | ||
monitorIds, | ||
workflowIds, | ||
alertIds | ||
); | ||
|
||
// create response listener | ||
// stringify the response, may change to a standard format in the future | ||
ActionListener<GetAlertsResponse> getAlertsListener = ActionListener.<GetAlertsResponse>wrap(response -> { | ||
StringBuilder sb = new StringBuilder(); | ||
sb.append("Alerts=["); | ||
for (Alert alert : response.getAlerts()) { | ||
sb.append(alert.toString()); | ||
} | ||
sb.append("]"); | ||
sb.append("TotalAlerts=").append(response.getTotalAlerts()); | ||
listener.onResponse((T) sb.toString()); | ||
}, e -> { listener.onFailure(e); }); | ||
|
||
// execute the search | ||
AlertingPluginInterface.INSTANCE.getAlerts((NodeClient) client, getAlertsRequest, getAlertsListener); | ||
} | ||
|
||
@Override | ||
public boolean validate(Map<String, String> parameters) { | ||
return true; | ||
} | ||
|
||
@Override | ||
public String getType() { | ||
return TYPE; | ||
} | ||
|
||
/** | ||
* Factory for the {@link SearchAlertsTool} | ||
*/ | ||
public static class Factory implements Tool.Factory<SearchAlertsTool> { | ||
private Client client; | ||
|
||
private static Factory INSTANCE; | ||
|
||
/** | ||
* Create or return the singleton factory instance | ||
*/ | ||
public static Factory getInstance() { | ||
if (INSTANCE != null) { | ||
return INSTANCE; | ||
} | ||
synchronized (SearchAlertsTool.class) { | ||
if (INSTANCE != null) { | ||
return INSTANCE; | ||
} | ||
INSTANCE = new Factory(); | ||
return INSTANCE; | ||
} | ||
} | ||
|
||
/** | ||
* Initialize this factory | ||
* @param client The OpenSearch client | ||
*/ | ||
public void init(Client client) { | ||
this.client = client; | ||
} | ||
|
||
@Override | ||
public SearchAlertsTool create(Map<String, Object> map) { | ||
return new SearchAlertsTool(client); | ||
} | ||
|
||
@Override | ||
public String getDefaultDescription() { | ||
return DEFAULT_DESCRIPTION; | ||
} | ||
} | ||
|
||
} |
172 changes: 172 additions & 0 deletions
172
ml-algorithms/src/test/java/org/opensearch/ml/engine/tools/SearchAlertsToolTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.ml.engine.tools; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
import static org.junit.Assert.assertFalse; | ||
import static org.junit.Assert.assertTrue; | ||
import static org.mockito.ArgumentMatchers.any; | ||
import static org.mockito.Mockito.doAnswer; | ||
import static org.mockito.Mockito.times; | ||
import static org.mockito.Mockito.verify; | ||
|
||
import java.time.Instant; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import org.junit.Before; | ||
import org.junit.Test; | ||
import org.mockito.ArgumentCaptor; | ||
import org.mockito.Mock; | ||
import org.mockito.Mockito; | ||
import org.mockito.MockitoAnnotations; | ||
import org.opensearch.action.ActionType; | ||
import org.opensearch.client.AdminClient; | ||
import org.opensearch.client.ClusterAdminClient; | ||
import org.opensearch.client.IndicesAdminClient; | ||
import org.opensearch.client.node.NodeClient; | ||
import org.opensearch.commons.alerting.action.GetAlertsResponse; | ||
import org.opensearch.commons.alerting.model.Alert; | ||
import org.opensearch.core.action.ActionListener; | ||
import org.opensearch.ml.common.spi.tools.Tool; | ||
|
||
public class SearchAlertsToolTests { | ||
@Mock | ||
private NodeClient nodeClient; | ||
@Mock | ||
private AdminClient adminClient; | ||
@Mock | ||
private IndicesAdminClient indicesAdminClient; | ||
@Mock | ||
private ClusterAdminClient clusterAdminClient; | ||
|
||
private Map<String, String> nullParams; | ||
private Map<String, String> emptyParams; | ||
private Map<String, String> nonEmptyParams; | ||
|
||
@Before | ||
public void setup() { | ||
MockitoAnnotations.openMocks(this); | ||
SearchAlertsTool.Factory.getInstance().init(nodeClient); | ||
|
||
nullParams = null; | ||
emptyParams = Collections.emptyMap(); | ||
nonEmptyParams = Map.of("searchString", "foo"); | ||
} | ||
|
||
@Test | ||
public void testRunWithNoAlerts() throws Exception { | ||
Tool tool = SearchAlertsTool.Factory.getInstance().create(Collections.emptyMap()); | ||
GetAlertsResponse getAlertsResponse = new GetAlertsResponse(Collections.emptyList(), 0); | ||
String expectedResponseStr = "Alerts=[]TotalAlerts=0"; | ||
|
||
@SuppressWarnings("unchecked") | ||
ActionListener<String> listener = Mockito.mock(ActionListener.class); | ||
|
||
doAnswer((invocation) -> { | ||
ActionListener<GetAlertsResponse> responseListener = invocation.getArgument(2); | ||
responseListener.onResponse(getAlertsResponse); | ||
return null; | ||
}).when(nodeClient).execute(any(ActionType.class), any(), any()); | ||
|
||
tool.run(nonEmptyParams, listener); | ||
ArgumentCaptor<String> responseCaptor = ArgumentCaptor.forClass(String.class); | ||
verify(listener, times(1)).onResponse(responseCaptor.capture()); | ||
assertEquals(expectedResponseStr, responseCaptor.getValue()); | ||
} | ||
|
||
@Test | ||
public void testRunWithAlerts() throws Exception { | ||
Tool tool = SearchAlertsTool.Factory.getInstance().create(Collections.emptyMap()); | ||
Alert alert1 = new Alert( | ||
"alert-id-1", | ||
1234, | ||
1, | ||
"monitor-id", | ||
"workflow-id", | ||
"workflow-name", | ||
"monitor-name", | ||
1234, | ||
null, | ||
"trigger-id", | ||
"trigger-name", | ||
Collections.emptyList(), | ||
Collections.emptyList(), | ||
Alert.State.ACKNOWLEDGED, | ||
Instant.now(), | ||
null, | ||
null, | ||
null, | ||
null, | ||
Collections.emptyList(), | ||
"test-severity", | ||
Collections.emptyList(), | ||
null, | ||
null, | ||
Collections.emptyList() | ||
); | ||
Alert alert2 = new Alert( | ||
"alert-id-2", | ||
1234, | ||
1, | ||
"monitor-id", | ||
"workflow-id", | ||
"workflow-name", | ||
"monitor-name", | ||
1234, | ||
null, | ||
"trigger-id", | ||
"trigger-name", | ||
Collections.emptyList(), | ||
Collections.emptyList(), | ||
Alert.State.ACKNOWLEDGED, | ||
Instant.now(), | ||
null, | ||
null, | ||
null, | ||
null, | ||
Collections.emptyList(), | ||
"test-severity", | ||
Collections.emptyList(), | ||
null, | ||
null, | ||
Collections.emptyList() | ||
); | ||
List<Alert> mockAlerts = List.of(alert1, alert2); | ||
|
||
GetAlertsResponse getAlertsResponse = new GetAlertsResponse(mockAlerts, mockAlerts.size()); | ||
String expectedResponseStr = new StringBuilder() | ||
.append("Alerts=[") | ||
.append(alert1.toString()) | ||
.append(alert2.toString()) | ||
.append("]TotalAlerts=2") | ||
.toString(); | ||
|
||
@SuppressWarnings("unchecked") | ||
ActionListener<String> listener = Mockito.mock(ActionListener.class); | ||
|
||
doAnswer((invocation) -> { | ||
ActionListener<GetAlertsResponse> responseListener = invocation.getArgument(2); | ||
responseListener.onResponse(getAlertsResponse); | ||
return null; | ||
}).when(nodeClient).execute(any(ActionType.class), any(), any()); | ||
|
||
tool.run(nonEmptyParams, listener); | ||
ArgumentCaptor<String> responseCaptor = ArgumentCaptor.forClass(String.class); | ||
verify(listener, times(1)).onResponse(responseCaptor.capture()); | ||
assertEquals(expectedResponseStr, responseCaptor.getValue()); | ||
} | ||
|
||
@Test | ||
public void testValidate() { | ||
Tool tool = SearchAlertsTool.Factory.getInstance().create(Collections.emptyMap()); | ||
assertEquals(SearchAlertsTool.TYPE, tool.getType()); | ||
assertTrue(tool.validate(emptyParams)); | ||
assertTrue(tool.validate(nonEmptyParams)); | ||
assertTrue(tool.validate(nullParams)); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why need to add this dependency?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For pulling in kotlin code from AlertingPluginInterface
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does just using common-utils not provide this?
api "org.opensearch:common-utils:${common_utils_version}@jar"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ohltyler Could you please update the response here?