From 1bba7348cd73648f7c099754c3e2cd874a0872f1 Mon Sep 17 00:00:00 2001 From: Surya Sashank Nistala Date: Fri, 7 Jul 2023 14:09:01 -0700 Subject: [PATCH] apis for get workflow alerts and acknowledge chained alerts (#472) (#473) Signed-off-by: Surya Sashank Nistala --- .../alerting/AlertingPluginInterface.kt | 51 ++++ .../alerting/action/AlertingActions.kt | 11 + .../alerting/action/GetAlertsRequest.kt | 5 + .../action/GetWorkflowAlertsRequest.kt | 67 +++++ .../action/GetWorkflowAlertsResponse.kt | 51 ++++ .../commons/alerting/model/Alert.kt | 228 ++++++++++++++---- .../alerting/AlertingPluginInterfaceTests.kt | 81 ++++++- .../commons/alerting/TestHelpers.kt | 3 +- .../action/AcknowledgeAlertResponseTests.kt | 58 ++++- .../AcknowledgeChainedAlertRequestTests.kt | 2 + .../action/DeleteMonitorRequestTests.kt | 15 +- .../alerting/action/GetAlertsRequestTests.kt | 13 +- .../alerting/action/GetAlertsResponseTests.kt | 101 ++++---- .../action/GetWorkflowAlertsRequestTests.kt | 63 +++++ .../action/GetWorkflowAlertsResponseTests.kt | 96 ++++++++ .../action/PublishFindingsRequestTests.kt | 29 +++ .../commons/alerting/model/XContentTests.kt | 47 +++- 17 files changed, 793 insertions(+), 128 deletions(-) create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsResponse.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequestTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsResponseTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/PublishFindingsRequestTests.kt diff --git a/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt index eafe8e69..ea2f1391 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt @@ -11,6 +11,7 @@ import org.opensearch.common.io.stream.NamedWriteableRegistry import org.opensearch.common.io.stream.Writeable import org.opensearch.commons.alerting.action.AcknowledgeAlertRequest import org.opensearch.commons.alerting.action.AcknowledgeAlertResponse +import org.opensearch.commons.alerting.action.AcknowledgeChainedAlertRequest import org.opensearch.commons.alerting.action.AlertingActions import org.opensearch.commons.alerting.action.DeleteMonitorRequest import org.opensearch.commons.alerting.action.DeleteMonitorResponse @@ -20,6 +21,8 @@ import org.opensearch.commons.alerting.action.GetAlertsRequest import org.opensearch.commons.alerting.action.GetAlertsResponse import org.opensearch.commons.alerting.action.GetFindingsRequest import org.opensearch.commons.alerting.action.GetFindingsResponse +import org.opensearch.commons.alerting.action.GetWorkflowAlertsRequest +import org.opensearch.commons.alerting.action.GetWorkflowAlertsResponse import org.opensearch.commons.alerting.action.GetWorkflowRequest import org.opensearch.commons.alerting.action.GetWorkflowResponse import org.opensearch.commons.alerting.action.IndexMonitorRequest @@ -147,6 +150,30 @@ object AlertingPluginInterface { ) } + /** + * Get Workflow Alerts interface. + * @param client Node client for making transport action + * @param request The request object + * @param listener The listener for getting response + */ + fun getWorkflowAlerts( + client: NodeClient, + request: GetWorkflowAlertsRequest, + listener: ActionListener + ) { + client.execute( + AlertingActions.GET_WORKFLOW_ALERTS_ACTION_TYPE, + request, + wrapActionListener(listener) { response -> + recreateObject(response) { + GetWorkflowAlertsResponse( + it + ) + } + } + ) + } + /** * Get Workflow interface. * @param client Node client for making transport action @@ -237,6 +264,30 @@ object AlertingPluginInterface { ) } + /** + * Acknowledge Chained Alerts interface. + * @param client Node client for making transport action + * @param request The request object + * @param listener The listener for getting response + */ + fun acknowledgeChainedAlerts( + client: NodeClient, + request: AcknowledgeChainedAlertRequest, + listener: ActionListener + ) { + client.execute( + AlertingActions.ACKNOWLEDGE_CHAINED_ALERTS_ACTION_TYPE, + request, + wrapActionListener(listener) { response -> + recreateObject(response) { + AcknowledgeAlertResponse( + it + ) + } + } + ) + } + @Suppress("UNCHECKED_CAST") private fun wrapActionListener( listener: ActionListener, diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt index 55598a76..c2bae396 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt @@ -10,11 +10,13 @@ object AlertingActions { const val INDEX_MONITOR_ACTION_NAME = "cluster:admin/opendistro/alerting/monitor/write" const val INDEX_WORKFLOW_ACTION_NAME = "cluster:admin/opensearch/alerting/workflow/write" const val GET_ALERTS_ACTION_NAME = "cluster:admin/opendistro/alerting/alerts/get" + const val GET_WORKFLOW_ALERTS_ACTION_NAME = "cluster:admin/opensearch/alerting/workflow_alerts/get" const val GET_WORKFLOW_ACTION_NAME = "cluster:admin/opensearch/alerting/workflow/get" const val DELETE_MONITOR_ACTION_NAME = "cluster:admin/opendistro/alerting/monitor/delete" const val DELETE_WORKFLOW_ACTION_NAME = "cluster:admin/opensearch/alerting/workflow/delete" const val GET_FINDINGS_ACTION_NAME = "cluster:admin/opensearch/alerting/findings/get" const val ACKNOWLEDGE_ALERTS_ACTION_NAME = "cluster:admin/opendistro/alerting/alerts/ack" + const val ACKNOWLEDGE_CHAINED_ALERTS_ACTION_NAME = "cluster:admin/opendistro/alerting/chained_alerts/ack" const val SUBSCRIBE_FINDINGS_ACTION_NAME = "cluster:admin/opensearch/alerting/findings/subscribe" @JvmField @@ -28,6 +30,10 @@ object AlertingActions { val GET_ALERTS_ACTION_TYPE = ActionType(GET_ALERTS_ACTION_NAME, ::GetAlertsResponse) + @JvmField + val GET_WORKFLOW_ALERTS_ACTION_TYPE = + ActionType(GET_WORKFLOW_ALERTS_ACTION_NAME, ::GetWorkflowAlertsResponse) + @JvmField val GET_WORKFLOW_ACTION_TYPE = ActionType(GET_WORKFLOW_ACTION_NAME, ::GetWorkflowResponse) @@ -46,7 +52,12 @@ object AlertingActions { @JvmField val ACKNOWLEDGE_ALERTS_ACTION_TYPE = ActionType(ACKNOWLEDGE_ALERTS_ACTION_NAME, ::AcknowledgeAlertResponse) + @JvmField val SUBSCRIBE_FINDINGS_ACTION_TYPE = ActionType(SUBSCRIBE_FINDINGS_ACTION_NAME, ::SubscribeFindingsResponse) + + @JvmField + val ACKNOWLEDGE_CHAINED_ALERTS_ACTION_TYPE = + ActionType(ACKNOWLEDGE_CHAINED_ALERTS_ACTION_NAME, ::AcknowledgeAlertResponse) } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequest.kt index c0571753..bfebdb2b 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequest.kt @@ -14,6 +14,7 @@ class GetAlertsRequest : ActionRequest { val monitorId: String? val alertIndex: String? val monitorIds: List? + val workflowIds: List? val alertIds: List? constructor( @@ -23,6 +24,7 @@ class GetAlertsRequest : ActionRequest { monitorId: String?, alertIndex: String?, monitorIds: List? = null, + workflowIds: List? = null, alertIds: List? = null ) : super() { this.table = table @@ -31,6 +33,7 @@ class GetAlertsRequest : ActionRequest { this.monitorId = monitorId this.alertIndex = alertIndex this.monitorIds = monitorIds + this.workflowIds = workflowIds this.alertIds = alertIds } @@ -42,6 +45,7 @@ class GetAlertsRequest : ActionRequest { monitorId = sin.readOptionalString(), alertIndex = sin.readOptionalString(), monitorIds = sin.readOptionalStringList(), + workflowIds = sin.readOptionalStringList(), alertIds = sin.readOptionalStringList() ) @@ -57,6 +61,7 @@ class GetAlertsRequest : ActionRequest { out.writeOptionalString(monitorId) out.writeOptionalString(alertIndex) out.writeOptionalStringCollection(monitorIds) + out.writeOptionalStringCollection(workflowIds) out.writeOptionalStringCollection(alertIds) } } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequest.kt new file mode 100644 index 00000000..454372f5 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequest.kt @@ -0,0 +1,67 @@ +package org.opensearch.commons.alerting.action + +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionRequestValidationException +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.commons.alerting.model.Table +import java.io.IOException + +class GetWorkflowAlertsRequest : ActionRequest { + val table: Table + val severityLevel: String + val alertState: String + val alertIndex: String? + val monitorIds: List? + val workflowIds: List? + val alertIds: List? + val getAssociatedAlerts: Boolean + + constructor( + table: Table, + severityLevel: String, + alertState: String, + alertIndex: String?, + monitorIds: List? = null, + workflowIds: List? = null, + alertIds: List? = null, + getAssociatedAlerts: Boolean + ) : super() { + this.table = table + this.severityLevel = severityLevel + this.alertState = alertState + this.alertIndex = alertIndex + this.monitorIds = monitorIds + this.workflowIds = workflowIds + this.alertIds = alertIds + this.getAssociatedAlerts = getAssociatedAlerts + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + table = Table.readFrom(sin), + severityLevel = sin.readString(), + alertState = sin.readString(), + alertIndex = sin.readOptionalString(), + monitorIds = sin.readOptionalStringList(), + workflowIds = sin.readOptionalStringList(), + alertIds = sin.readOptionalStringList(), + getAssociatedAlerts = sin.readBoolean() + ) + + override fun validate(): ActionRequestValidationException? { + return null + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + table.writeTo(out) + out.writeString(severityLevel) + out.writeString(alertState) + out.writeOptionalString(alertIndex) + out.writeOptionalStringCollection(monitorIds) + out.writeOptionalStringCollection(workflowIds) + out.writeOptionalStringCollection(alertIds) + out.writeBoolean(getAssociatedAlerts) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsResponse.kt new file mode 100644 index 00000000..b426f7e0 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsResponse.kt @@ -0,0 +1,51 @@ +package org.opensearch.commons.alerting.action + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.commons.alerting.model.Alert +import org.opensearch.commons.notifications.action.BaseResponse +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import java.io.IOException +import java.util.Collections + +class GetWorkflowAlertsResponse : BaseResponse { + val alerts: List + val associatedAlerts: List + // totalAlerts is not the same as the size of alerts because there can be 30 alerts from the request, but + // the request only asked for 5 alerts, so totalAlerts will be 30, but alerts will only contain 5 alerts + val totalAlerts: Int? + + constructor( + alerts: List, + associatedAlerts: List, + totalAlerts: Int? + ) : super() { + this.alerts = alerts + this.associatedAlerts = associatedAlerts + this.totalAlerts = totalAlerts + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + alerts = Collections.unmodifiableList(sin.readList(::Alert)), + associatedAlerts = Collections.unmodifiableList(sin.readList(::Alert)), + totalAlerts = sin.readOptionalInt() + ) + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeCollection(alerts) + out.writeCollection(associatedAlerts) + out.writeOptionalInt(totalAlerts) + } + + @Throws(IOException::class) + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .field("alerts", alerts) + .field("associatedAlerts", associatedAlerts) + .field("totalAlerts", totalAlerts) + return builder.endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt index 7bf6b538..7bae5ee9 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt @@ -23,6 +23,7 @@ data class Alert( val schemaVersion: Int = NO_SCHEMA_VERSION, val monitorId: String, val workflowId: String, + val workflowName: String, val monitorName: String, val monitorVersion: Long, val monitorUser: User?, @@ -41,6 +42,7 @@ data class Alert( val actionExecutionResults: List, val aggregationResultBucket: AggregationResultBucket? = null, val executionId: String? = null, + val associatedAlertIds: List, ) : Writeable, ToXContent { init { @@ -49,7 +51,6 @@ data class Alert( } } - // constructor for chained alerts. constructor( startTime: Instant, lastNotificationTime: Instant?, @@ -58,14 +59,30 @@ data class Alert( schemaVersion: Int = NO_SCHEMA_VERSION, executionId: String, chainedAlertTrigger: ChainedAlertTrigger, - workflow: Workflow + workflow: Workflow, + associatedAlertIds: List, ) : this( - monitorId = NO_ID, monitorName = "", monitorVersion = NO_VERSION, monitorUser = workflow.user, - triggerId = chainedAlertTrigger.id, triggerName = chainedAlertTrigger.name, state = state, startTime = startTime, - lastNotificationTime = lastNotificationTime, errorMessage = errorMessage, errorHistory = emptyList(), - severity = chainedAlertTrigger.severity, actionExecutionResults = emptyList(), schemaVersion = schemaVersion, - aggregationResultBucket = null, findingIds = emptyList(), relatedDocIds = emptyList(), - executionId = executionId, workflowId = workflow.id + monitorId = NO_ID, + monitorName = "", + monitorVersion = NO_VERSION, + monitorUser = workflow.user, + triggerId = chainedAlertTrigger.id, + triggerName = chainedAlertTrigger.name, + state = state, + startTime = startTime, + lastNotificationTime = lastNotificationTime, + errorMessage = errorMessage, + errorHistory = emptyList(), + severity = chainedAlertTrigger.severity, + actionExecutionResults = emptyList(), + schemaVersion = schemaVersion, + aggregationResultBucket = null, + findingIds = emptyList(), + relatedDocIds = emptyList(), + executionId = executionId, + workflowId = workflow.id, + workflowName = workflow.name, + associatedAlertIds = associatedAlertIds ) constructor( @@ -81,12 +98,27 @@ data class Alert( executionId: String? = null, workflowId: String? = null, ) : this( - monitorId = monitor.id, monitorName = monitor.name, monitorVersion = monitor.version, monitorUser = monitor.user, - triggerId = trigger.id, triggerName = trigger.name, state = state, startTime = startTime, - lastNotificationTime = lastNotificationTime, errorMessage = errorMessage, errorHistory = errorHistory, - severity = trigger.severity, actionExecutionResults = actionExecutionResults, schemaVersion = schemaVersion, - aggregationResultBucket = null, findingIds = emptyList(), relatedDocIds = emptyList(), - executionId = executionId, workflowId = workflowId ?: "" + monitorId = monitor.id, + monitorName = monitor.name, + monitorVersion = monitor.version, + monitorUser = monitor.user, + triggerId = trigger.id, + triggerName = trigger.name, + state = state, + startTime = startTime, + lastNotificationTime = lastNotificationTime, + errorMessage = errorMessage, + errorHistory = errorHistory, + severity = trigger.severity, + actionExecutionResults = actionExecutionResults, + schemaVersion = schemaVersion, + aggregationResultBucket = null, + findingIds = emptyList(), + relatedDocIds = emptyList(), + executionId = executionId, + workflowId = workflowId ?: "", + workflowName = "", + associatedAlertIds = emptyList() ) constructor( @@ -103,12 +135,27 @@ data class Alert( executionId: String? = null, workflowId: String? = null, ) : this( - monitorId = monitor.id, monitorName = monitor.name, monitorVersion = monitor.version, monitorUser = monitor.user, - triggerId = trigger.id, triggerName = trigger.name, state = state, startTime = startTime, - lastNotificationTime = lastNotificationTime, errorMessage = errorMessage, errorHistory = errorHistory, - severity = trigger.severity, actionExecutionResults = actionExecutionResults, schemaVersion = schemaVersion, - aggregationResultBucket = null, findingIds = findingIds, relatedDocIds = emptyList(), - executionId = executionId, workflowId = workflowId ?: "" + monitorId = monitor.id, + monitorName = monitor.name, + monitorVersion = monitor.version, + monitorUser = monitor.user, + triggerId = trigger.id, + triggerName = trigger.name, + state = state, + startTime = startTime, + lastNotificationTime = lastNotificationTime, + errorMessage = errorMessage, + errorHistory = errorHistory, + severity = trigger.severity, + actionExecutionResults = actionExecutionResults, + schemaVersion = schemaVersion, + aggregationResultBucket = null, + findingIds = findingIds, + relatedDocIds = emptyList(), + executionId = executionId, + workflowId = workflowId ?: "", + workflowName = "", + associatedAlertIds = emptyList() ) constructor( @@ -126,12 +173,27 @@ data class Alert( executionId: String? = null, workflowId: String? = null, ) : this( - monitorId = monitor.id, monitorName = monitor.name, monitorVersion = monitor.version, monitorUser = monitor.user, - triggerId = trigger.id, triggerName = trigger.name, state = state, startTime = startTime, - lastNotificationTime = lastNotificationTime, errorMessage = errorMessage, errorHistory = errorHistory, - severity = trigger.severity, actionExecutionResults = actionExecutionResults, schemaVersion = schemaVersion, - aggregationResultBucket = aggregationResultBucket, findingIds = findingIds, relatedDocIds = emptyList(), - executionId = executionId, workflowId = workflowId ?: "" + monitorId = monitor.id, + monitorName = monitor.name, + monitorVersion = monitor.version, + monitorUser = monitor.user, + triggerId = trigger.id, + triggerName = trigger.name, + state = state, + startTime = startTime, + lastNotificationTime = lastNotificationTime, + errorMessage = errorMessage, + errorHistory = errorHistory, + severity = trigger.severity, + actionExecutionResults = actionExecutionResults, + schemaVersion = schemaVersion, + aggregationResultBucket = aggregationResultBucket, + findingIds = findingIds, + relatedDocIds = emptyList(), + executionId = executionId, + workflowId = workflowId ?: "", + workflowName = "", + associatedAlertIds = emptyList() ) constructor( @@ -150,12 +212,28 @@ data class Alert( executionId: String? = null, workflowId: String? = null, ) : this( - id = id, monitorId = monitor.id, monitorName = monitor.name, monitorVersion = monitor.version, monitorUser = monitor.user, - triggerId = trigger.id, triggerName = trigger.name, state = state, startTime = startTime, - lastNotificationTime = lastNotificationTime, errorMessage = errorMessage, errorHistory = errorHistory, - severity = trigger.severity, actionExecutionResults = actionExecutionResults, schemaVersion = schemaVersion, - aggregationResultBucket = null, findingIds = findingIds, relatedDocIds = relatedDocIds, - executionId = executionId, workflowId = workflowId ?: "" + id = id, + monitorId = monitor.id, + monitorName = monitor.name, + monitorVersion = monitor.version, + monitorUser = monitor.user, + triggerId = trigger.id, + triggerName = trigger.name, + state = state, + startTime = startTime, + lastNotificationTime = lastNotificationTime, + errorMessage = errorMessage, + errorHistory = errorHistory, + severity = trigger.severity, + actionExecutionResults = actionExecutionResults, + schemaVersion = schemaVersion, + aggregationResultBucket = null, + findingIds = findingIds, + relatedDocIds = relatedDocIds, + executionId = executionId, + workflowId = workflowId ?: "", + workflowName = "", + associatedAlertIds = emptyList() ) constructor( @@ -169,17 +247,35 @@ data class Alert( errorHistory: List = mutableListOf(), schemaVersion: Int = NO_SCHEMA_VERSION, workflowId: String? = null, + executionId: String?, ) : this( - id = id, monitorId = monitor.id, monitorName = monitor.name, monitorVersion = monitor.version, monitorUser = monitor.user, - triggerId = trigger.id, triggerName = trigger.name, state = state, startTime = startTime, - lastNotificationTime = lastNotificationTime, errorMessage = errorMessage, errorHistory = errorHistory, - severity = trigger.severity, actionExecutionResults = listOf(), schemaVersion = schemaVersion, - aggregationResultBucket = null, findingIds = listOf(), relatedDocIds = listOf(), - workflowId = workflowId ?: "" + id = id, + monitorId = monitor.id, + monitorName = monitor.name, + workflowName = "", + monitorVersion = monitor.version, + monitorUser = monitor.user, + triggerId = trigger.id, + triggerName = trigger.name, + state = state, + startTime = startTime, + lastNotificationTime = lastNotificationTime, + errorMessage = errorMessage, + errorHistory = errorHistory, + severity = trigger.severity, + actionExecutionResults = listOf(), + schemaVersion = schemaVersion, + aggregationResultBucket = null, + findingIds = listOf(), + relatedDocIds = listOf(), + workflowId = workflowId ?: "", + executionId = executionId, + associatedAlertIds = emptyList() ) enum class State { ACTIVE, ACKNOWLEDGED, COMPLETED, ERROR, DELETED, + // Alerts are created in audit state when they are generated by delegate monitors of a workflow. // since chained alerts can be configured and acknowledged, the underlying monitors' alerts are simply // for evaluating chained alert triggers and auditing purpose. @@ -194,6 +290,7 @@ data class Alert( schemaVersion = sin.readInt(), monitorId = sin.readString(), workflowId = sin.readString(), + workflowName = sin.readString(), monitorName = sin.readString(), monitorVersion = sin.readLong(), monitorUser = if (sin.readBoolean()) { @@ -213,7 +310,8 @@ data class Alert( severity = sin.readString(), actionExecutionResults = sin.readList(::ActionExecutionResult), aggregationResultBucket = if (sin.readBoolean()) AggregationResultBucket(sin) else null, - executionId = sin.readOptionalString() + executionId = sin.readOptionalString(), + associatedAlertIds = sin.readStringList() ) fun isAcknowledged(): Boolean = (state == State.ACKNOWLEDGED) @@ -225,6 +323,7 @@ data class Alert( out.writeInt(schemaVersion) out.writeString(monitorId) out.writeString(workflowId) + out.writeString(workflowName) out.writeString(monitorName) out.writeLong(monitorVersion) out.writeBoolean(monitorUser != null) @@ -249,6 +348,7 @@ data class Alert( out.writeBoolean(false) } out.writeOptionalString(executionId) + out.writeStringCollection(associatedAlertIds) } companion object { @@ -258,6 +358,7 @@ data class Alert( const val ALERT_VERSION_FIELD = "version" const val MONITOR_ID_FIELD = "monitor_id" const val WORKFLOW_ID_FIELD = "workflow_id" + const val WORKFLOW_NAME_FIELD = "workflow_name" const val MONITOR_VERSION_FIELD = "monitor_version" const val MONITOR_NAME_FIELD = "monitor_name" const val MONITOR_USER_FIELD = "monitor_user" @@ -275,6 +376,7 @@ data class Alert( const val SEVERITY_FIELD = "severity" const val ACTION_EXECUTION_RESULTS_FIELD = "action_execution_results" const val EXECUTION_ID_FIELD = "execution_id" + const val ASSOCIATED_ALERT_IDS_FIELD = "associated_alert_ids" const val BUCKET_KEYS = AggregationResultBucket.BUCKET_KEYS const val PARENTS_BUCKET_PATH = AggregationResultBucket.PARENTS_BUCKET_PATH const val NO_ID = "" @@ -286,6 +388,7 @@ data class Alert( fun parse(xcp: XContentParser, id: String = NO_ID, version: Long = NO_VERSION): Alert { lateinit var monitorId: String var workflowId = "" + var workflowName = "" var schemaVersion = NO_SCHEMA_VERSION lateinit var monitorName: String var monitorVersion: Long = Versions.NOT_FOUND @@ -305,6 +408,7 @@ data class Alert( val errorHistory: MutableList = mutableListOf() val actionExecutionResults: MutableList = mutableListOf() var aggAlertBucket: AggregationResultBucket? = null + val associatedAlertIds = mutableListOf() ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { val fieldName = xcp.currentName() @@ -313,6 +417,7 @@ data class Alert( when (fieldName) { MONITOR_ID_FIELD -> monitorId = xcp.text() WORKFLOW_ID_FIELD -> workflowId = xcp.text() + WORKFLOW_NAME_FIELD -> workflowName = xcp.text() SCHEMA_VERSION_FIELD -> schemaVersion = xcp.intValue() MONITOR_NAME_FIELD -> monitorName = xcp.text() MONITOR_VERSION_FIELD -> monitorVersion = xcp.longValue() @@ -338,6 +443,12 @@ data class Alert( ACKNOWLEDGED_TIME_FIELD -> acknowledgedTime = xcp.instant() ERROR_MESSAGE_FIELD -> errorMessage = xcp.textOrNull() EXECUTION_ID_FIELD -> executionId = xcp.textOrNull() + ASSOCIATED_ALERT_IDS_FIELD -> { + ensureExpectedToken(XContentParser.Token.START_ARRAY, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + associatedAlertIds.add(xcp.text()) + } + } ALERT_HISTORY_FIELD -> { ensureExpectedToken(XContentParser.Token.START_ARRAY, xcp.currentToken(), xcp) while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { @@ -366,14 +477,31 @@ data class Alert( } return Alert( - id = id, version = version, schemaVersion = schemaVersion, monitorId = requireNotNull(monitorId), - monitorName = requireNotNull(monitorName), monitorVersion = monitorVersion, monitorUser = monitorUser, - triggerId = requireNotNull(triggerId), triggerName = requireNotNull(triggerName), - state = requireNotNull(state), startTime = requireNotNull(startTime), endTime = endTime, - lastNotificationTime = lastNotificationTime, acknowledgedTime = acknowledgedTime, - errorMessage = errorMessage, errorHistory = errorHistory, severity = severity, - actionExecutionResults = actionExecutionResults, aggregationResultBucket = aggAlertBucket, findingIds = findingIds, - relatedDocIds = relatedDocIds, executionId = executionId, workflowId = workflowId + id = id, + version = version, + schemaVersion = schemaVersion, + monitorId = requireNotNull(monitorId), + monitorName = requireNotNull(monitorName), + monitorVersion = monitorVersion, + monitorUser = monitorUser, + triggerId = requireNotNull(triggerId), + triggerName = requireNotNull(triggerName), + state = requireNotNull(state), + startTime = requireNotNull(startTime), + endTime = endTime, + lastNotificationTime = lastNotificationTime, + acknowledgedTime = acknowledgedTime, + errorMessage = errorMessage, + errorHistory = errorHistory, + severity = severity, + actionExecutionResults = actionExecutionResults, + aggregationResultBucket = aggAlertBucket, + findingIds = findingIds, + relatedDocIds = relatedDocIds, + executionId = executionId, + workflowId = workflowId, + workflowName = workflowName, + associatedAlertIds = associatedAlertIds ) } @@ -391,12 +519,15 @@ data class Alert( fun toXContentWithUser(builder: XContentBuilder): XContentBuilder { return createXContentBuilder(builder, false) } + private fun createXContentBuilder(builder: XContentBuilder, secure: Boolean): XContentBuilder { builder.startObject() .field(ALERT_ID_FIELD, id) .field(ALERT_VERSION_FIELD, version) .field(MONITOR_ID_FIELD, monitorId) .field(WORKFLOW_ID_FIELD, workflowId) + .field(WORKFLOW_NAME_FIELD, workflowName) + .field(ASSOCIATED_ALERT_IDS_FIELD, associatedAlertIds) .field(SCHEMA_VERSION_FIELD, schemaVersion) .field(MONITOR_VERSION_FIELD, monitorVersion) .field(MONITOR_NAME_FIELD, monitorName) @@ -432,6 +563,9 @@ data class Alert( END_TIME_FIELD to endTime?.toEpochMilli(), ERROR_MESSAGE_FIELD to errorMessage, EXECUTION_ID_FIELD to executionId, + WORKFLOW_ID_FIELD to workflowId, + WORKFLOW_NAME_FIELD to workflowName, + ASSOCIATED_ALERT_IDS_FIELD to associatedAlertIds, LAST_NOTIFICATION_TIME_FIELD to lastNotificationTime?.toEpochMilli(), SEVERITY_FIELD to severity, START_TIME_FIELD to startTime.toEpochMilli(), diff --git a/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt index 69eb756c..c9e2189a 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt @@ -14,6 +14,9 @@ import org.opensearch.action.ActionType import org.opensearch.client.node.NodeClient import org.opensearch.common.io.stream.NamedWriteableRegistry import org.opensearch.common.settings.Settings +import org.opensearch.commons.alerting.action.AcknowledgeAlertRequest +import org.opensearch.commons.alerting.action.AcknowledgeAlertResponse +import org.opensearch.commons.alerting.action.AcknowledgeChainedAlertRequest import org.opensearch.commons.alerting.action.DeleteMonitorRequest import org.opensearch.commons.alerting.action.DeleteMonitorResponse import org.opensearch.commons.alerting.action.DeleteWorkflowRequest @@ -22,10 +25,14 @@ import org.opensearch.commons.alerting.action.GetAlertsRequest import org.opensearch.commons.alerting.action.GetAlertsResponse import org.opensearch.commons.alerting.action.GetFindingsRequest import org.opensearch.commons.alerting.action.GetFindingsResponse +import org.opensearch.commons.alerting.action.GetWorkflowAlertsRequest +import org.opensearch.commons.alerting.action.GetWorkflowAlertsResponse import org.opensearch.commons.alerting.action.IndexMonitorRequest import org.opensearch.commons.alerting.action.IndexMonitorResponse import org.opensearch.commons.alerting.action.IndexWorkflowRequest import org.opensearch.commons.alerting.action.IndexWorkflowResponse +import org.opensearch.commons.alerting.action.PublishFindingsRequest +import org.opensearch.commons.alerting.action.SubscribeFindingsResponse import org.opensearch.commons.alerting.model.FindingDocument import org.opensearch.commons.alerting.model.FindingWithDocs import org.opensearch.commons.alerting.model.Monitor @@ -46,7 +53,13 @@ internal class AlertingPluginInterfaceTests { val monitor = randomQueryLevelMonitor() val request = mock(IndexMonitorRequest::class.java) - val response = IndexMonitorResponse(Monitor.NO_ID, Monitor.NO_VERSION, SequenceNumbers.UNASSIGNED_SEQ_NO, SequenceNumbers.UNASSIGNED_PRIMARY_TERM, monitor) + val response = IndexMonitorResponse( + Monitor.NO_ID, + Monitor.NO_VERSION, + SequenceNumbers.UNASSIGNED_SEQ_NO, + SequenceNumbers.UNASSIGNED_PRIMARY_TERM, + monitor + ) val listener: ActionListener = mock(ActionListener::class.java) as ActionListener val namedWriteableRegistry = NamedWriteableRegistry(SearchModule(Settings.EMPTY, emptyList()).namedWriteables) @@ -89,7 +102,13 @@ internal class AlertingPluginInterfaceTests { val monitor = randomBucketLevelMonitor() val request = mock(IndexMonitorRequest::class.java) - val response = IndexMonitorResponse(Monitor.NO_ID, Monitor.NO_VERSION, SequenceNumbers.UNASSIGNED_SEQ_NO, SequenceNumbers.UNASSIGNED_PRIMARY_TERM, monitor) + val response = IndexMonitorResponse( + Monitor.NO_ID, + Monitor.NO_VERSION, + SequenceNumbers.UNASSIGNED_SEQ_NO, + SequenceNumbers.UNASSIGNED_PRIMARY_TERM, + monitor + ) val listener: ActionListener = mock(ActionListener::class.java) as ActionListener val namedWriteableRegistry = NamedWriteableRegistry(SearchModule(Settings.EMPTY, emptyList()).namedWriteables) @@ -150,6 +169,21 @@ internal class AlertingPluginInterfaceTests { Mockito.verify(listener, Mockito.times(1)).onResponse(ArgumentMatchers.eq(response)) } + @Test + fun getWorkflowAlerts() { + val request = mock(GetWorkflowAlertsRequest::class.java) + val response = GetWorkflowAlertsResponse(listOf(randomChainedAlert()), emptyList(), 1) + val listener: ActionListener = + mock(ActionListener::class.java) as ActionListener + + Mockito.doAnswer { + (it.getArgument(2) as ActionListener) + .onResponse(response) + }.whenever(client).execute(Mockito.any(ActionType::class.java), Mockito.any(), Mockito.any()) + AlertingPluginInterface.getWorkflowAlerts(client, request, listener) + Mockito.verify(listener, Mockito.times(1)).onResponse(ArgumentMatchers.eq(response)) + } + @Test fun getFindings() { val finding = randomFinding() @@ -173,4 +207,47 @@ internal class AlertingPluginInterfaceTests { AlertingPluginInterface.getFindings(client, request, listener) Mockito.verify(listener, Mockito.times(1)).onResponse(ArgumentMatchers.eq(response)) } + + @Test + fun publishFindings() { + val request = mock(PublishFindingsRequest::class.java) + val response = SubscribeFindingsResponse(status = RestStatus.OK) + val listener: ActionListener = + mock(ActionListener::class.java) as ActionListener + + Mockito.doAnswer { + (it.getArgument(2) as ActionListener) + .onResponse(response) + }.whenever(client).execute(Mockito.any(ActionType::class.java), Mockito.any(), Mockito.any()) + AlertingPluginInterface.publishFinding(client, request, listener) + Mockito.verify(listener, Mockito.times(1)).onResponse(ArgumentMatchers.eq(response)) + } + + @Test + fun acknowledgeAlerts() { + val request = mock(AcknowledgeAlertRequest::class.java) + val response = AcknowledgeAlertResponse(acknowledged = listOf(), failed = listOf(), missing = listOf()) + val listener: ActionListener = + mock(ActionListener::class.java) as ActionListener + Mockito.doAnswer { + (it.getArgument(2) as ActionListener) + .onResponse(response) + }.whenever(client).execute(Mockito.any(ActionType::class.java), Mockito.any(), Mockito.any()) + AlertingPluginInterface.acknowledgeAlerts(client, request, listener) + Mockito.verify(listener, Mockito.times(1)).onResponse(ArgumentMatchers.eq(response)) + } + + @Test + fun acknowledgeChainedAlerts() { + val request = mock(AcknowledgeChainedAlertRequest::class.java) + val response = AcknowledgeAlertResponse(acknowledged = listOf(), failed = listOf(), missing = listOf()) + val listener: ActionListener = + mock(ActionListener::class.java) as ActionListener + Mockito.doAnswer { + (it.getArgument(2) as ActionListener) + .onResponse(response) + }.whenever(client).execute(Mockito.any(ActionType::class.java), Mockito.any(), Mockito.any()) + AlertingPluginInterface.acknowledgeChainedAlerts(client, request, listener) + Mockito.verify(listener, Mockito.times(1)).onResponse(ArgumentMatchers.eq(response)) + } } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt index b6721a0f..ad92d615 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt @@ -533,7 +533,8 @@ fun randomChainedAlert( errorMessage = null, executionId = UUID.randomUUID().toString(), chainedAlertTrigger = trigger, - workflow = workflow + workflow = workflow, + associatedAlertIds = listOf("a1") ) } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponseTests.kt index 0b4fd699..7e22a1a3 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponseTests.kt @@ -18,18 +18,60 @@ class AcknowledgeAlertResponseTests { val acknowledged = mutableListOf( Alert( - "1234", 0L, 1, "monitor-1234", "", "test-monitor", 0L, randomUser(), - "trigger-14", "test-trigger", ArrayList(), ArrayList(), Alert.State.ACKNOWLEDGED, - Instant.now(), Instant.now(), Instant.now(), Instant.now(), null, ArrayList(), - "sev-2", ArrayList(), null + id = "1234", + version = 0L, + schemaVersion = 1, + monitorId = "monitor-1234", + workflowId = "", + workflowName = "", + monitorName = "test-monitor", + monitorVersion = 0L, + monitorUser = randomUser(), + triggerId = "trigger-14", + triggerName = "test-trigger", + findingIds = ArrayList(), + relatedDocIds = ArrayList(), + state = Alert.State.ACKNOWLEDGED, + startTime = Instant.now(), + endTime = Instant.now(), + lastNotificationTime = Instant.now(), + acknowledgedTime = Instant.now(), + errorMessage = null, + errorHistory = ArrayList(), + severity = "sev-2", + actionExecutionResults = ArrayList(), + aggregationResultBucket = null, + executionId = null, + associatedAlertIds = emptyList() ) ) val failed = mutableListOf( Alert( - "1234", 0L, 1, "monitor-1234", "", "test-monitor", 0L, randomUser(), - "trigger-14", "test-trigger", ArrayList(), ArrayList(), Alert.State.ERROR, Instant.now(), Instant.now(), - Instant.now(), Instant.now(), null, mutableListOf(AlertError(Instant.now(), "Error msg")), - "sev-2", mutableListOf(ActionExecutionResult("7890", null, 0)), null + id = "1234", + version = 0L, + schemaVersion = 1, + monitorId = "monitor-1234", + workflowId = "", + workflowName = "", + monitorName = "test-monitor", + monitorVersion = 0L, + monitorUser = randomUser(), + triggerId = "trigger-14", + triggerName = "test-trigger", + findingIds = ArrayList(), + relatedDocIds = ArrayList(), + state = Alert.State.ERROR, + startTime = Instant.now(), + endTime = Instant.now(), + lastNotificationTime = Instant.now(), + acknowledgedTime = Instant.now(), + errorMessage = null, + errorHistory = mutableListOf(AlertError(Instant.now(), "Error msg")), + severity = "sev-2", + actionExecutionResults = mutableListOf(ActionExecutionResult("7890", null, 0)), + aggregationResultBucket = null, + executionId = null, + associatedAlertIds = emptyList() ) ) val missing = mutableListOf("1", "2", "3", "4") diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequestTests.kt index 012e370e..3d571198 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequestTests.kt @@ -7,6 +7,7 @@ package org.opensearch.commons.alerting.action import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test import org.opensearch.common.io.stream.BytesStreamOutput import org.opensearch.common.io.stream.StreamInput @@ -23,5 +24,6 @@ class AcknowledgeChainedAlertRequestTests { val newReq = AcknowledgeChainedAlertRequest(sin) assertEquals("1234", newReq.workflowId) assertEquals(4, newReq.alertIds.size) + assertNull(newReq.validate()) } } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorRequestTests.kt index 43fa43e3..0abd9d88 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorRequestTests.kt @@ -1,7 +1,8 @@ package org.opensearch.commons.alerting.action -import org.junit.Assert -import org.junit.Test +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Test import org.opensearch.action.support.WriteRequest import org.opensearch.common.io.stream.BytesStreamOutput import org.opensearch.common.io.stream.StreamInput @@ -12,15 +13,15 @@ class DeleteMonitorRequestTests { fun `test delete monitor request`() { val req = DeleteMonitorRequest("1234", WriteRequest.RefreshPolicy.IMMEDIATE) - Assert.assertNotNull(req) - Assert.assertEquals("1234", req.monitorId) - Assert.assertEquals("true", req.refreshPolicy.value) + assertNotNull(req) + assertEquals("1234", req.monitorId) + assertEquals("true", req.refreshPolicy.value) val out = BytesStreamOutput() req.writeTo(out) val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) val newReq = DeleteMonitorRequest(sin) - Assert.assertEquals("1234", newReq.monitorId) - Assert.assertEquals("true", newReq.refreshPolicy.value) + assertEquals("1234", newReq.monitorId) + assertEquals("true", newReq.refreshPolicy.value) } } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequestTests.kt index 32ade4ba..a56a0f02 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequestTests.kt @@ -16,7 +16,16 @@ internal class GetAlertsRequestTests { val table = Table("asc", "sortString", null, 1, 0, "") - val req = GetAlertsRequest(table, "1", "active", null, null, listOf("1", "2"), listOf("alert1", "alert2")) + val req = GetAlertsRequest( + table = table, + severityLevel = "1", + alertState = "active", + monitorId = null, + alertIndex = null, + monitorIds = listOf("1", "2"), + alertIds = listOf("alert1", "alert2"), + workflowIds = listOf("w1", "w2"), + ) assertNotNull(req) val out = BytesStreamOutput() @@ -32,6 +41,8 @@ internal class GetAlertsRequestTests { assertTrue(newReq.monitorIds!!.contains("2")) assertTrue(newReq.alertIds!!.contains("alert1")) assertTrue(newReq.alertIds!!.contains("alert2")) + assertTrue(newReq.workflowIds!!.contains("w1")) + assertTrue(newReq.workflowIds!!.contains("w2")) } @Test diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponseTests.kt index 524b0aaa..dc0aac1e 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponseTests.kt @@ -32,28 +32,27 @@ class GetAlertsResponseTests { @Test fun `test get alerts response with alerts`() { val alert = Alert( - "id", - 0L, - 0, - "monitorId", - "workflowId", - "monitorName", - 0L, - randomUser(), - "triggerId", - "triggerName", - Collections.emptyList(), - Collections.emptyList(), - Alert.State.ACKNOWLEDGED, - Instant.MIN, - null, - null, - null, - null, - Collections.emptyList(), - "severity", - Collections.emptyList(), - null + monitorId = "id", + monitorName = "name", + monitorVersion = Alert.NO_VERSION, + monitorUser = randomUser(), + triggerId = "triggerId", + triggerName = "triggerNamer", + state = Alert.State.ACKNOWLEDGED, + startTime = Instant.now(), + lastNotificationTime = null, + errorMessage = null, + errorHistory = emptyList(), + severity = "high", + actionExecutionResults = emptyList(), + schemaVersion = 0, + aggregationResultBucket = null, + findingIds = emptyList(), + relatedDocIds = emptyList(), + executionId = "executionId", + workflowId = "workflowId", + workflowName = "", + associatedAlertIds = emptyList() ) val req = GetAlertsResponse(listOf(alert), 1) assertNotNull(req) @@ -71,39 +70,39 @@ class GetAlertsResponseTests { @Test fun `test toXContent for get alerts response`() { val now = Instant.now() - val alert = Alert( - "id", - 0L, - 0, - "monitorId", - "workflowId", - "monitorName", - 0L, - null, - "triggerId", - "triggerName", - Collections.emptyList(), - Collections.emptyList(), - Alert.State.ACKNOWLEDGED, - now, - null, - null, - null, - null, - Collections.emptyList(), - "severity", - Collections.emptyList(), - null + monitorId = "id", + monitorName = "name", + monitorVersion = Alert.NO_VERSION, + monitorUser = randomUser(), + triggerId = "triggerId", + triggerName = "triggerNamer", + state = Alert.State.ACKNOWLEDGED, + startTime = now, + lastNotificationTime = null, + errorMessage = null, + errorHistory = emptyList(), + severity = "high", + actionExecutionResults = emptyList(), + schemaVersion = 0, + aggregationResultBucket = null, + findingIds = emptyList(), + relatedDocIds = emptyList(), + executionId = "executionId", + workflowId = "wid", + workflowName = "", + associatedAlertIds = emptyList() ) + val req = GetAlertsResponse(listOf(alert), 1) var actualXContentString = req.toXContent(builder(), ToXContent.EMPTY_PARAMS).string() - val expectedXContentString = "{\"alerts\":[{\"id\":\"id\",\"version\":0,\"monitor_id\":\"monitorId\"," + - "\"workflow_id\":\"workflowId\",\"schema_version\":0,\"monitor_version\":0,\"monitor_name\":\"monitorName\"," + - "\"execution_id\":null,\"trigger_id\":\"triggerId\",\"trigger_name\":\"triggerName\"," + - "\"finding_ids\":[],\"related_doc_ids\":[],\"state\":\"ACKNOWLEDGED\",\"error_message\":null,\"alert_history\":[]," + - "\"severity\":\"severity\",\"action_execution_results\":[],\"start_time\":" + now.toEpochMilli() + - ",\"last_notification_time\":null,\"end_time\":null,\"acknowledged_time\":null}],\"totalAlerts\":1}" + val expectedXContentString = "{\"alerts\":[{\"id\":\"\",\"version\":-1,\"monitor_id\":\"id\",\"workflow_id\":\"wid\"," + + "\"workflow_name\":\"\",\"associated_alert_ids\":[],\"schema_version\":0,\"monitor_version\":-1," + + "\"monitor_name\":\"name\",\"execution_id\":\"executionId\",\"trigger_id\":\"triggerId\"," + + "\"trigger_name\":\"triggerNamer\",\"finding_ids\":[],\"related_doc_ids\":[],\"state\":\"ACKNOWLEDGED\"," + + "\"error_message\":null,\"alert_history\":[],\"severity\":\"high\",\"action_execution_results\":[]," + + "\"start_time\":${now.toEpochMilli()},\"last_notification_time\":null,\"end_time\":null," + + "\"acknowledged_time\":null}],\"totalAlerts\":1}" assertEquals(expectedXContentString, actualXContentString) } } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequestTests.kt new file mode 100644 index 00000000..6cead607 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequestTests.kt @@ -0,0 +1,63 @@ +package org.opensearch.commons.alerting.action + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.opensearch.common.io.stream.BytesStreamOutput +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.commons.alerting.model.Table + +internal class GetWorkflowAlertsRequestTests { + + @Test + fun `test get alerts request`() { + + val table = Table("asc", "sortString", null, 1, 0, "") + + val req = GetWorkflowAlertsRequest( + table = table, + severityLevel = "1", + alertState = "active", + getAssociatedAlerts = true, + workflowIds = listOf("w1", "w2"), + alertIds = emptyList(), + alertIndex = null, + monitorIds = emptyList() + ) + assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = GetWorkflowAlertsRequest(sin) + + assertEquals("1", newReq.severityLevel) + assertEquals("active", newReq.alertState) + assertEquals(table, newReq.table) + assertTrue(newReq.workflowIds!!.contains("w1")) + assertTrue(newReq.workflowIds!!.contains("w2")) + assertTrue(newReq.alertIds!!.isEmpty()) + assertTrue(newReq.monitorIds!!.isEmpty()) + assertNull(newReq.alertIndex) + assertTrue(newReq.getAssociatedAlerts) + } + + @Test + fun `test validate returns null`() { + val table = Table("asc", "sortString", null, 1, 0, "") + + val req = GetWorkflowAlertsRequest( + table = table, + severityLevel = "1", + alertState = "active", + getAssociatedAlerts = true, + workflowIds = listOf("w1, w2"), + alertIds = emptyList(), + alertIndex = null + ) + assertNotNull(req) + assertNull(req.validate()) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsResponseTests.kt new file mode 100644 index 00000000..fd656d67 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsResponseTests.kt @@ -0,0 +1,96 @@ +package org.opensearch.commons.alerting.action + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.opensearch.common.io.stream.BytesStreamOutput +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.commons.alerting.builder +import org.opensearch.commons.alerting.model.Alert +import org.opensearch.commons.alerting.randomAlert +import org.opensearch.commons.alerting.randomChainedAlert +import org.opensearch.commons.alerting.randomUser +import org.opensearch.commons.alerting.util.string +import org.opensearch.core.xcontent.ToXContent +import java.time.Instant +import java.util.Collections + +class GetWorkflowAlertsResponseTests { + + @Test + fun `test get alerts response with no alerts`() { + val req = GetWorkflowAlertsResponse(Collections.emptyList(), emptyList(), 0) + assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = GetWorkflowAlertsResponse(sin) + assertTrue(newReq.alerts.isEmpty()) + assertTrue(newReq.associatedAlerts.isEmpty()) + assertEquals(0, newReq.totalAlerts) + } + + @Test + fun `test get alerts response with alerts`() { + val chainedAlert1 = randomChainedAlert() + val chainedAlert2 = randomChainedAlert() + val alert1 = randomAlert() + val alert2 = randomAlert() + val req = GetWorkflowAlertsResponse(listOf(chainedAlert1, chainedAlert2), listOf(alert1, alert2), 2) + assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = GetWorkflowAlertsResponse(sin) + assertEquals(2, newReq.alerts.size) + assertEquals(2, newReq.associatedAlerts.size) + assertEquals(2, newReq.totalAlerts) + assertTrue(newReq.alerts.contains(chainedAlert1)) + assertTrue(newReq.alerts.contains(chainedAlert2)) + assertTrue(newReq.associatedAlerts.contains(alert1)) + assertTrue(newReq.associatedAlerts.contains(alert2)) + } + + @Test + fun `test toXContent for get alerts response`() { + val alert = Alert( + monitorId = "id", + monitorName = "name", + monitorVersion = Alert.NO_VERSION, + monitorUser = randomUser(), + triggerId = "triggerId", + triggerName = "triggerNamer", + state = Alert.State.ACKNOWLEDGED, + startTime = Instant.ofEpochMilli(1688591410974), + lastNotificationTime = null, + errorMessage = null, + errorHistory = emptyList(), + severity = "high", + actionExecutionResults = emptyList(), + schemaVersion = 0, + aggregationResultBucket = null, + findingIds = emptyList(), + relatedDocIds = emptyList(), + executionId = "executionId", + workflowId = "wid", + workflowName = "", + associatedAlertIds = emptyList() + ) + + val req = GetWorkflowAlertsResponse(listOf(alert), emptyList(), 1) + var actualXContentString = req.toXContent(builder(), ToXContent.EMPTY_PARAMS).string() + val expectedXContentString = + "{\"alerts\":[{\"id\":\"\",\"version\":-1,\"monitor_id\":\"id\"," + + "\"workflow_id\":\"wid\",\"workflow_name\":\"\",\"associated_alert_ids\":[]," + + "\"schema_version\":0,\"monitor_version\":-1,\"monitor_name\":\"name\",\"execution_id\":" + + "\"executionId\",\"trigger_id\":\"triggerId\",\"trigger_name\":\"triggerNamer\",\"finding_ids\":[]," + + "\"related_doc_ids\":[],\"state\":\"ACKNOWLEDGED\",\"error_message\":null,\"alert_history\":[]," + + "\"severity\":\"high\",\"action_execution_results\":[],\"start_time\":1688591410974," + + "\"last_notification_time\":null,\"end_time\":null,\"acknowledged_time\":null}]," + + "\"associatedAlerts\":[],\"totalAlerts\":1}" + assertEquals(expectedXContentString, actualXContentString) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/PublishFindingsRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/PublishFindingsRequestTests.kt new file mode 100644 index 00000000..c3d69ace --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/PublishFindingsRequestTests.kt @@ -0,0 +1,29 @@ +package org.opensearch.commons.alerting.action + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Test +import org.opensearch.common.io.stream.BytesStreamOutput +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.commons.alerting.randomFinding + +class PublishFindingsRequestTests { + + @Test + fun `test delete monitor request`() { + + val finding = randomFinding() + val monitorId = "mid" + val req = PublishFindingsRequest(monitorId, finding) + assertNotNull(req) + assertEquals(monitorId, req.monitorId) + assertEquals(finding, req.finding) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = PublishFindingsRequest(sin) + assertEquals(monitorId, newReq.monitorId) + assertEquals(finding.id, newReq.finding.id) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt index c5afed05..75d794a9 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt @@ -96,7 +96,13 @@ class XContentTests { val throttleString = throttle.toXContent(builder(), ToXContent.EMPTY_PARAMS).string() val wrongThrottleString = throttleString.replace("MINUTES", "wrongunit") - assertFailsWith("Only support MINUTES throttle unit") { Throttle.parse(parser(wrongThrottleString)) } + assertFailsWith("Only support MINUTES throttle unit") { + Throttle.parse( + parser( + wrongThrottleString + ) + ) + } } @Test @@ -104,7 +110,13 @@ class XContentTests { val throttle = randomThrottle().copy(value = -1) val throttleString = throttle.toXContent(builder(), ToXContent.EMPTY_PARAMS).string() - assertFailsWith("Can only set positive throttle period") { Throttle.parse(parser(throttleString)) } + assertFailsWith("Can only set positive throttle period") { + Throttle.parse( + parser( + throttleString + ) + ) + } } fun `test query-level monitor parsing`() { @@ -132,7 +144,13 @@ class XContentTests { } """.trimIndent() - assertFailsWith("Monitor name is null") { Monitor.parse(parser(monitorStringWithoutName)) } + assertFailsWith("Monitor name is null") { + Monitor.parse( + parser( + monitorStringWithoutName + ) + ) + } } @Test @@ -381,8 +399,14 @@ class XContentTests { fun `test alert parsing with noop trigger`() { val monitor = randomQueryLevelMonitor() val alert = Alert( - monitor = monitor, trigger = NoOpTrigger(), startTime = Instant.now().truncatedTo(ChronoUnit.MILLIS), - errorMessage = "some error", lastNotificationTime = Instant.now() + id = "", + monitor = monitor, + trigger = NoOpTrigger(), + startTime = Instant.now().truncatedTo(ChronoUnit.MILLIS), + errorMessage = "some error", + lastNotificationTime = Instant.now(), + workflowId = "", + executionId = "" ) assertEquals("Round tripping alert doesn't work", alert.triggerName, "NoOp trigger") } @@ -401,12 +425,13 @@ class XContentTests { @Test fun `test alert parsing with user as null`() { - val alertStr = "{\"id\":\"\",\"version\":-1,\"monitor_id\":\"\",\"schema_version\":0,\"monitor_version\":1,\"monitor_user\":null," + - "\"monitor_name\":\"ARahqfRaJG\",\"trigger_id\":\"fhe1-XQBySl0wQKDBkOG\",\"trigger_name\":\"ffELMuhlro\"," + - "\"state\":\"ACTIVE\",\"error_message\":null,\"alert_history\":[],\"severity\":\"1\",\"action_execution_results\"" + - ":[{\"action_id\":\"ghe1-XQBySl0wQKDBkOG\",\"last_execution_time\":1601917224583,\"throttled_count\":-1478015168}," + - "{\"action_id\":\"gxe1-XQBySl0wQKDBkOH\",\"last_execution_time\":1601917224583,\"throttled_count\":-768533744}]," + - "\"start_time\":1601917224599,\"last_notification_time\":null,\"end_time\":null,\"acknowledged_time\":null}" + val alertStr = + "{\"id\":\"\",\"version\":-1,\"monitor_id\":\"\",\"schema_version\":0,\"monitor_version\":1,\"monitor_user\":null," + + "\"monitor_name\":\"ARahqfRaJG\",\"trigger_id\":\"fhe1-XQBySl0wQKDBkOG\",\"trigger_name\":\"ffELMuhlro\"," + + "\"state\":\"ACTIVE\",\"error_message\":null,\"alert_history\":[],\"severity\":\"1\",\"action_execution_results\"" + + ":[{\"action_id\":\"ghe1-XQBySl0wQKDBkOG\",\"last_execution_time\":1601917224583,\"throttled_count\":-1478015168}," + + "{\"action_id\":\"gxe1-XQBySl0wQKDBkOH\",\"last_execution_time\":1601917224583,\"throttled_count\":-768533744}]," + + "\"start_time\":1601917224599,\"last_notification_time\":null,\"end_time\":null,\"acknowledged_time\":null}" val parsedAlert = Alert.parse(parser(alertStr)) OpenSearchTestCase.assertNull(parsedAlert.monitorUser) }