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

[Backport 2.x] Fixed search monitor API to return alert counts. #984

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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 @@ -219,7 +219,7 @@ class AlertService(
return Alert(
id = id, monitor = monitor, trigger = NoOpTrigger(), startTime = currentTime,
lastNotificationTime = currentTime, state = Alert.State.ERROR, errorMessage = alertError?.message,
schemaVersion = IndexUtils.alertIndexSchemaVersion
schemaVersion = IndexUtils.alertIndexSchemaVersion, executionId = ""
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ import org.opensearch.alerting.util.isAllowed
import org.opensearch.alerting.util.isTestAction
import org.opensearch.alerting.workflow.WorkflowRunContext
import org.opensearch.client.node.NodeClient
import org.opensearch.common.Strings
import org.opensearch.commons.alerting.model.Monitor
import org.opensearch.commons.alerting.model.Table
import org.opensearch.commons.alerting.model.action.Action
import org.opensearch.commons.notifications.model.NotificationConfigInfo
import org.opensearch.core.common.Strings
import java.time.Instant

abstract class MonitorRunner {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ class AlertIndices(
clusterStateRequest,
object : ActionListener<ClusterStateResponse> {
override fun onResponse(clusterStateResponse: ClusterStateResponse) {
if (!clusterStateResponse.state.metadata.indices.isEmpty) {
if (clusterStateResponse.state.metadata.indices.isNotEmpty()) {
val indicesToDelete = getIndicesToDelete(clusterStateResponse)
logger.info("Deleting old $tag indices viz $indicesToDelete")
deleteAllOldHistoryIndices(indicesToDelete)
Expand Down Expand Up @@ -523,7 +523,7 @@ class AlertIndices(
): String? {
val creationTime = indexMetadata.creationDate
if ((Instant.now().toEpochMilli() - creationTime) > retentionPeriodMillis) {
val alias = indexMetadata.aliases.firstOrNull { writeIndex == it.value.alias }
val alias = indexMetadata.aliases.entries.firstOrNull { writeIndex == it.value.alias }
if (alias != null) {
if (historyEnabled) {
// If the index has the write alias and history is enabled, don't delete the index
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@

package org.opensearch.alerting.model.destination

import org.opensearch.common.Strings
import org.opensearch.common.io.stream.StreamInput
import org.opensearch.common.io.stream.StreamOutput
import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken
import org.opensearch.core.common.Strings
import org.opensearch.core.xcontent.ToXContent
import org.opensearch.core.xcontent.XContentBuilder
import org.opensearch.core.xcontent.XContentParser
import java.io.IOException
import java.lang.IllegalStateException

/**
* A value object that represents a Chime message. Chime message will be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@

package org.opensearch.alerting.model.destination

import org.opensearch.common.Strings
import org.opensearch.common.io.stream.StreamInput
import org.opensearch.common.io.stream.StreamOutput
import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken
import org.opensearch.core.common.Strings
import org.opensearch.core.xcontent.ToXContent
import org.opensearch.core.xcontent.XContentBuilder
import org.opensearch.core.xcontent.XContentParser
import java.io.IOException
import java.lang.IllegalStateException

/**
* A value object that represents a Custom webhook message. Webhook message will be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@

package org.opensearch.alerting.model.destination

import org.opensearch.common.Strings
import org.opensearch.common.io.stream.StreamInput
import org.opensearch.common.io.stream.StreamOutput
import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken
import org.opensearch.core.common.Strings
import org.opensearch.core.xcontent.ToXContent
import org.opensearch.core.xcontent.XContentBuilder
import org.opensearch.core.xcontent.XContentParser
import java.io.IOException
import java.lang.IllegalStateException

/**
* A value object that represents a Slack message. Slack message will be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
package org.opensearch.alerting.model.destination.email

import org.opensearch.alerting.util.isValidEmail
import org.opensearch.common.Strings
import org.opensearch.common.io.stream.StreamInput
import org.opensearch.common.io.stream.StreamOutput
import org.opensearch.common.io.stream.Writeable
import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken
import org.opensearch.commons.alerting.util.IndexUtils.Companion.NO_SCHEMA_VERSION
import org.opensearch.core.common.Strings
import org.opensearch.core.xcontent.ToXContent
import org.opensearch.core.xcontent.XContentBuilder
import org.opensearch.core.xcontent.XContentParser
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import org.opensearch.alerting.settings.AlertingSettings
import org.opensearch.alerting.util.AlertingException
import org.opensearch.client.Client
import org.opensearch.cluster.service.ClusterService
import org.opensearch.common.Strings
import org.opensearch.common.inject.Inject
import org.opensearch.common.settings.Settings
import org.opensearch.common.xcontent.LoggingDeprecationHandler
Expand All @@ -29,6 +28,7 @@ import org.opensearch.common.xcontent.XContentParserUtils
import org.opensearch.common.xcontent.XContentType
import org.opensearch.commons.alerting.model.ScheduledJob
import org.opensearch.commons.authuser.User
import org.opensearch.core.common.Strings
import org.opensearch.core.xcontent.NamedXContentRegistry
import org.opensearch.core.xcontent.XContentParser
import org.opensearch.index.query.Operator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import org.opensearch.alerting.settings.AlertingSettings
import org.opensearch.alerting.util.AlertingException
import org.opensearch.client.Client
import org.opensearch.cluster.service.ClusterService
import org.opensearch.common.Strings
import org.opensearch.common.inject.Inject
import org.opensearch.common.settings.Settings
import org.opensearch.common.xcontent.LoggingDeprecationHandler
Expand All @@ -42,6 +41,7 @@ import org.opensearch.commons.alerting.model.Finding
import org.opensearch.commons.alerting.model.FindingDocument
import org.opensearch.commons.alerting.model.FindingWithDocs
import org.opensearch.commons.utils.recreateObject
import org.opensearch.core.common.Strings
import org.opensearch.core.xcontent.NamedXContentRegistry
import org.opensearch.core.xcontent.XContentParser
import org.opensearch.index.query.Operator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.opensearch.cluster.service.ClusterService
import org.opensearch.common.inject.Inject
import org.opensearch.common.settings.Settings
import org.opensearch.commons.alerting.model.Monitor
import org.opensearch.commons.alerting.model.ScheduledJob
import org.opensearch.commons.authuser.User
import org.opensearch.index.query.BoolQueryBuilder
import org.opensearch.index.query.ExistsQueryBuilder
Expand Down Expand Up @@ -52,9 +53,18 @@ class TransportSearchMonitorAction @Inject constructor(

override fun doExecute(task: Task, searchMonitorRequest: SearchMonitorRequest, actionListener: ActionListener<SearchResponse>) {
val searchSourceBuilder = searchMonitorRequest.searchRequest.source()
.seqNoAndPrimaryTerm(true)
.version(true)
val queryBuilder = if (searchSourceBuilder.query() == null) BoolQueryBuilder()
else QueryBuilders.boolQuery().must(searchSourceBuilder.query())
queryBuilder.filter(QueryBuilders.existsQuery(Monitor.MONITOR_TYPE))

// The SearchMonitor API supports one 'index' parameter of either the SCHEDULED_JOBS_INDEX or ALL_ALERT_INDEX_PATTERN.
// When querying the ALL_ALERT_INDEX_PATTERN, we don't want to check whether the MONITOR_TYPE field exists
// because we're querying alert indexes.
if (searchMonitorRequest.searchRequest.indices().contains(ScheduledJob.SCHEDULED_JOBS_INDEX)) {
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 @@ -9,7 +9,7 @@ import org.apache.logging.log4j.LogManager
import org.opensearch.OpenSearchException
import org.opensearch.OpenSearchSecurityException
import org.opensearch.OpenSearchStatusException
import org.opensearch.common.Strings
import org.opensearch.core.common.Strings
import org.opensearch.index.IndexNotFoundException
import org.opensearch.index.engine.VersionConflictEngineException
import org.opensearch.indices.InvalidIndexNameException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
package org.opensearch.alerting.util

import org.apache.lucene.search.join.ScoreMode
import org.opensearch.common.Strings
import org.opensearch.commons.alerting.model.Monitor
import org.opensearch.commons.alerting.model.SearchInput
import org.opensearch.commons.authuser.User
import org.opensearch.core.common.Strings
import org.opensearch.index.query.BoolQueryBuilder
import org.opensearch.index.query.NestedQueryBuilder
import org.opensearch.index.query.QueryBuilders
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class IndexUtils {

@JvmStatic
fun getIndexNameWithAlias(clusterState: ClusterState, alias: String): String {
return clusterState.metadata.indices.first { it.value.aliases.containsKey(alias) }.key
return clusterState.metadata.indices.entries.first { it.value.aliases.containsKey(alias) }.key
}

@JvmStatic
Expand All @@ -127,7 +127,7 @@ class IndexUtils {
actionListener: ActionListener<AcknowledgedResponse>
) {
if (clusterState.metadata.indices.containsKey(index)) {
if (shouldUpdateIndex(clusterState.metadata.indices[index], mapping)) {
if (shouldUpdateIndex(clusterState.metadata.indices[index]!!, mapping)) {
val putMappingRequest: PutMappingRequest = PutMappingRequest(index).source(mapping, XContentType.JSON)
client.putMapping(putMappingRequest, actionListener)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
package org.opensearch.alerting.util

import org.opensearch.alerting.AlertingPlugin
import org.opensearch.common.Strings
import org.opensearch.core.common.Strings
import org.opensearch.rest.RestRequest
import org.opensearch.search.fetch.subphase.FetchSourceContext

Expand All @@ -18,7 +18,7 @@ import org.opensearch.search.fetch.subphase.FetchSourceContext
* @return FetchSourceContext
*/
fun context(request: RestRequest): FetchSourceContext? {
val userAgent = Strings.coalesceToEmpty(request.header("User-Agent"))
val userAgent = if (request.header("User-Agent") == null) "" else request.header("User-Agent")
return if (!userAgent.contains(AlertingPlugin.OPEN_SEARCH_DASHBOARDS_USER_AGENT)) {
FetchSourceContext(true, Strings.EMPTY_ARRAY, AlertingPlugin.UI_METADATA_EXCLUDE)
} else null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import org.opensearch.alerting.model.destination.Destination
import org.opensearch.alerting.model.destination.email.EmailAccount
import org.opensearch.alerting.model.destination.email.Recipient
import org.opensearch.alerting.util.DestinationType
import org.opensearch.common.Strings
import org.opensearch.commons.notifications.model.Chime
import org.opensearch.commons.notifications.model.ConfigType
import org.opensearch.commons.notifications.model.Email
Expand All @@ -22,6 +21,7 @@ import org.opensearch.commons.notifications.model.NotificationConfig
import org.opensearch.commons.notifications.model.Slack
import org.opensearch.commons.notifications.model.SmtpAccount
import org.opensearch.commons.notifications.model.Webhook
import org.opensearch.core.common.Strings
import java.net.URI
import java.net.URISyntaxException
import java.util.Locale
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import org.opensearch.alerting.util.destinationmigration.DestinationConversionUt
import org.opensearch.alerting.util.destinationmigration.DestinationConversionUtils.Companion.convertEmailGroupToNotificationConfig
import org.opensearch.alerting.util.destinationmigration.NotificationApiUtils.Companion.createNotificationConfig
import org.opensearch.client.node.NodeClient
import org.opensearch.common.Strings
import org.opensearch.common.xcontent.LoggingDeprecationHandler
import org.opensearch.common.xcontent.XContentFactory
import org.opensearch.common.xcontent.XContentParserUtils
Expand All @@ -31,6 +30,7 @@ import org.opensearch.commons.alerting.model.ScheduledJob
import org.opensearch.commons.notifications.action.CreateNotificationConfigRequest
import org.opensearch.commons.notifications.model.NotificationConfig
import org.opensearch.commons.notifications.model.NotificationConfigInfo
import org.opensearch.core.common.Strings
import org.opensearch.core.xcontent.NamedXContentRegistry
import org.opensearch.core.xcontent.XContentParser
import org.opensearch.index.query.QueryBuilders
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 @@ -1264,6 +1266,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),
NStringEntity(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
Loading