Skip to content
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

Fixed search monitor API to return alert counts. #978

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import org.opensearch.action.support.ActionFilters
import org.opensearch.action.support.HandledTransportAction
import org.opensearch.alerting.action.SearchMonitorAction
import org.opensearch.alerting.action.SearchMonitorRequest
import org.opensearch.alerting.alerts.AlertIndices.Companion.ALL_ALERT_INDEX_PATTERN
import org.opensearch.alerting.opensearchapi.addFilter
import org.opensearch.alerting.settings.AlertingSettings
import org.opensearch.alerting.util.AlertingException
Expand Down Expand Up @@ -52,7 +53,13 @@ class TransportSearchMonitorAction @Inject constructor(
val searchSourceBuilder = searchMonitorRequest.searchRequest.source()
val queryBuilder = if (searchSourceBuilder.query() == null) BoolQueryBuilder()
else QueryBuilders.boolQuery().must(searchSourceBuilder.query())
queryBuilder.filter(QueryBuilders.existsQuery(Monitor.MONITOR_TYPE))

if (searchMonitorRequest.searchRequest.indices().size == 1 &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is confusing.. can we add a comment or simplify this check?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment and simplified the check.

!searchMonitorRequest.searchRequest.indices().contains(ALL_ALERT_INDEX_PATTERN)
) {
queryBuilder.filter(QueryBuilders.existsQuery(Monitor.MONITOR_TYPE))
}

searchSourceBuilder.query(queryBuilder)
.seqNoAndPrimaryTerm(true)
.version(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ import org.opensearch.core.xcontent.XContentBuilder
import org.opensearch.index.query.QueryBuilders
import org.opensearch.rest.RestStatus
import org.opensearch.script.Script
import org.opensearch.search.aggregations.AggregationBuilders
import org.opensearch.search.builder.SearchSourceBuilder
import org.opensearch.search.sort.SortOrder
import org.opensearch.test.OpenSearchTestCase
import org.opensearch.test.junit.annotations.TestLogging
import org.opensearch.test.rest.OpenSearchRestTestCase
Expand Down Expand Up @@ -1263,6 +1265,118 @@ class MonitorRestApiIT : AlertingRestTestCase() {
}
}

/**
* This use case is needed by the frontend plugin for displaying alert counts on the Monitors list page.
* https://github.com/opensearch-project/alerting-dashboards-plugin/blob/main/server/services/MonitorService.js#L235
*/
fun `test get acknowledged, active, error, and ignored alerts counts`() {
putAlertMappings()
val monitorAlertCounts = hashMapOf<String, HashMap<String, Int>>()
val numMonitors = randomIntBetween(1, 10)
repeat(numMonitors) {
val monitor = createRandomMonitor(refresh = true)

val numAcknowledgedAlerts = randomIntBetween(1, 10)
val numActiveAlerts = randomIntBetween(1, 10)
var numCompletedAlerts = randomIntBetween(1, 10)
val numErrorAlerts = randomIntBetween(1, 10)
val numIgnoredAlerts = randomIntBetween(1, numCompletedAlerts)
numCompletedAlerts -= numIgnoredAlerts

val alertCounts = hashMapOf(
Alert.State.ACKNOWLEDGED.name to numAcknowledgedAlerts,
Alert.State.ACTIVE.name to numActiveAlerts,
Alert.State.COMPLETED.name to numCompletedAlerts,
Alert.State.ERROR.name to numErrorAlerts,
"IGNORED" to numIgnoredAlerts
)
monitorAlertCounts[monitor.id] = alertCounts

repeat(numAcknowledgedAlerts) {
createAlert(randomAlert(monitor).copy(acknowledgedTime = Instant.now(), state = Alert.State.ACKNOWLEDGED))
}
repeat(numActiveAlerts) {
createAlert(randomAlert(monitor).copy(state = Alert.State.ACTIVE))
}
repeat(numCompletedAlerts) {
createAlert(randomAlert(monitor).copy(acknowledgedTime = Instant.now(), state = Alert.State.COMPLETED))
}
repeat(numErrorAlerts) {
createAlert(randomAlert(monitor).copy(state = Alert.State.ERROR))
}
repeat(numIgnoredAlerts) {
createAlert(randomAlert(monitor).copy(acknowledgedTime = null, state = Alert.State.COMPLETED))
}
}

val sourceBuilder = SearchSourceBuilder()
.size(0)
.query(QueryBuilders.termsQuery("monitor_id", monitorAlertCounts.keys))
.aggregation(
AggregationBuilders
.terms("uniq_monitor_ids").field("monitor_id")
.subAggregation(AggregationBuilders.filter("active", QueryBuilders.termQuery("state", "ACTIVE")))
.subAggregation(AggregationBuilders.filter("acknowledged", QueryBuilders.termQuery("state", "ACKNOWLEDGED")))
.subAggregation(AggregationBuilders.filter("errors", QueryBuilders.termQuery("state", "ERROR")))
.subAggregation(
AggregationBuilders.filter(
"ignored",
QueryBuilders.boolQuery()
.filter(QueryBuilders.termQuery("state", "COMPLETED"))
.mustNot(QueryBuilders.existsQuery("acknowledged_time"))
)
)
.subAggregation(AggregationBuilders.max("last_notification_time").field("last_notification_time"))
.subAggregation(
AggregationBuilders.topHits("latest_alert")
.size(1)
.sort("start_time", SortOrder.DESC)
.fetchSource(arrayOf("last_notification_time", "trigger_name"), null)
)
)

val searchResponse = client().makeRequest(
"GET",
"$ALERTING_BASE_URI/_search",
hashMapOf("index" to AlertIndices.ALL_ALERT_INDEX_PATTERN),
StringEntity(sourceBuilder.toString(), ContentType.APPLICATION_JSON)
)
val xcp = createParser(XContentType.JSON.xContent(), searchResponse.entity.content).map()
val aggregations = (xcp["aggregations"]!! as Map<String, Map<String, Any>>)
val uniqMonitorIds = aggregations["uniq_monitor_ids"]!!
val buckets = uniqMonitorIds["buckets"]!! as ArrayList<Map<String, Any>>

assertEquals("Incorrect number of monitors returned", monitorAlertCounts.keys.size, buckets.size)
buckets.forEach { bucket ->
val id = bucket["key"]!!
val monitorCounts = monitorAlertCounts[id]!!

val acknowledged = (bucket["acknowledged"]!! as Map<String, Int>)["doc_count"]!!
assertEquals(
"Incorrect ${Alert.State.ACKNOWLEDGED} count returned for monitor $id",
monitorCounts[Alert.State.ACKNOWLEDGED.name], acknowledged
)

val active = (bucket["active"]!! as Map<String, Int>)["doc_count"]!!
assertEquals(
"Incorrect ${Alert.State.ACTIVE} count returned for monitor $id",
monitorCounts[Alert.State.ACTIVE.name], active
)

val errors = (bucket["errors"]!! as Map<String, Int>)["doc_count"]!!
assertEquals(
"Incorrect ${Alert.State.ERROR} count returned for monitor $id",
monitorCounts[Alert.State.ERROR.name], errors
)

val ignored = (bucket["ignored"]!! as Map<String, Int>)["doc_count"]!!
assertEquals(
"Incorrect IGNORED count returned for monitor $id",
monitorCounts["IGNORED"], ignored
)
}
}

private fun validateAlertingStatsNodeResponse(nodesResponse: Map<String, Int>) {
assertEquals("Incorrect number of nodes", numberOfNodes, nodesResponse["total"])
assertEquals("Failed nodes found during monitor stats call", 0, nodesResponse["failed"])
Expand Down