From 437eba40d6d7796b10beb4c640642ae2afdc8810 Mon Sep 17 00:00:00 2001 From: Surya Sashank Nistala Date: Mon, 10 Jul 2023 19:06:04 -0700 Subject: [PATCH] add auditDelegateMonitorAlerts flag (#476) (#477) * add auditDelegateMonitorAlerts flag * add audit state check in error alert validation * add test to verify workflow with auditDelegateMonitor flag null --------- Signed-off-by: Surya Sashank Nistala (cherry picked from commit 90bea0c58f53d2a60b91f5fba6600a5d5edd9105) --- .../action/GetWorkflowAlertsRequest.kt | 7 +- .../commons/alerting/model/Alert.kt | 6 +- .../commons/alerting/model/Workflow.kt | 18 +++-- .../commons/alerting/TestHelpers.kt | 3 +- .../action/GetWorkflowAlertsRequestTests.kt | 40 +++++++++++- .../action/GetWorkflowResponseTests.kt | 65 +++++++++++++++++++ .../action/IndexWorkflowRequestTests.kt | 3 +- .../commons/alerting/model/XContentTests.kt | 8 +++ 8 files changed, 140 insertions(+), 10 deletions(-) create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponseTests.kt diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequest.kt index 454372f5..9e2c8b2b 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequest.kt @@ -12,6 +12,7 @@ class GetWorkflowAlertsRequest : ActionRequest { val severityLevel: String val alertState: String val alertIndex: String? + val associatedAlertsIndex: String? val monitorIds: List? val workflowIds: List? val alertIds: List? @@ -22,15 +23,17 @@ class GetWorkflowAlertsRequest : ActionRequest { severityLevel: String, alertState: String, alertIndex: String?, + associatedAlertsIndex: String?, monitorIds: List? = null, workflowIds: List? = null, alertIds: List? = null, - getAssociatedAlerts: Boolean + getAssociatedAlerts: Boolean, ) : super() { this.table = table this.severityLevel = severityLevel this.alertState = alertState this.alertIndex = alertIndex + this.associatedAlertsIndex = associatedAlertsIndex this.monitorIds = monitorIds this.workflowIds = workflowIds this.alertIds = alertIds @@ -43,6 +46,7 @@ class GetWorkflowAlertsRequest : ActionRequest { severityLevel = sin.readString(), alertState = sin.readString(), alertIndex = sin.readOptionalString(), + associatedAlertsIndex = sin.readOptionalString(), monitorIds = sin.readOptionalStringList(), workflowIds = sin.readOptionalStringList(), alertIds = sin.readOptionalStringList(), @@ -59,6 +63,7 @@ class GetWorkflowAlertsRequest : ActionRequest { out.writeString(severityLevel) out.writeString(alertState) out.writeOptionalString(alertIndex) + out.writeOptionalString(associatedAlertsIndex) out.writeOptionalStringCollection(monitorIds) out.writeOptionalStringCollection(workflowIds) out.writeOptionalStringCollection(alertIds) 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 7bae5ee9..626d2133 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt @@ -46,7 +46,7 @@ data class Alert( ) : Writeable, ToXContent { init { - if (errorMessage != null) require(state == State.DELETED || state == State.ERROR) { + if (errorMessage != null) require(state == State.DELETED || state == State.ERROR || state == State.AUDIT) { "Attempt to create an alert with an error in state: $state" } } @@ -421,7 +421,9 @@ data class Alert( SCHEMA_VERSION_FIELD -> schemaVersion = xcp.intValue() MONITOR_NAME_FIELD -> monitorName = xcp.text() MONITOR_VERSION_FIELD -> monitorVersion = xcp.longValue() - MONITOR_USER_FIELD -> monitorUser = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) null else User.parse(xcp) + MONITOR_USER_FIELD -> + monitorUser = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) null + else User.parse(xcp) TRIGGER_ID_FIELD -> triggerId = xcp.text() FINDING_IDS -> { ensureExpectedToken(XContentParser.Token.START_ARRAY, xcp.currentToken(), xcp) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Workflow.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Workflow.kt index d2f2518d..a509f8d7 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Workflow.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Workflow.kt @@ -36,7 +36,8 @@ data class Workflow( val schemaVersion: Int = NO_SCHEMA_VERSION, val inputs: List, val owner: String? = DEFAULT_OWNER, - val triggers: List + val triggers: List, + val auditDelegateMonitorAlerts: Boolean? = true, ) : ScheduledJob { override val type = WORKFLOW_TYPE @@ -70,7 +71,8 @@ data class Workflow( schemaVersion = sin.readInt(), inputs = sin.readList((WorkflowInput)::readFrom), owner = sin.readOptionalString(), - triggers = sin.readList((Trigger)::readFrom) + triggers = sin.readList((Trigger)::readFrom), + auditDelegateMonitorAlerts = sin.readOptionalBoolean() ) // This enum classifies different workflows @@ -99,7 +101,7 @@ data class Workflow( private fun createXContentBuilder( builder: XContentBuilder, params: ToXContent.Params, - secure: Boolean + secure: Boolean, ): XContentBuilder { builder.startObject() if (params.paramAsBoolean("with_type", false)) builder.startObject(type) @@ -119,6 +121,9 @@ data class Workflow( .field(TRIGGERS_FIELD, triggers.toTypedArray()) .optionalTimeField(LAST_UPDATE_TIME_FIELD, lastUpdateTime) builder.field(OWNER_FIELD, owner) + if (auditDelegateMonitorAlerts != null) { + builder.field(AUDIT_DELEGATE_MONITOR_ALERTS_FIELD, auditDelegateMonitorAlerts) + } if (params.paramAsBoolean("with_type", false)) builder.endObject() return builder.endObject() } @@ -159,6 +164,7 @@ data class Workflow( } it.writeTo(out) } + out.writeOptionalBoolean(auditDelegateMonitorAlerts) } companion object { @@ -177,6 +183,7 @@ data class Workflow( const val ENABLED_TIME_FIELD = "enabled_time" const val TRIGGERS_FIELD = "triggers" const val OWNER_FIELD = "owner" + const val AUDIT_DELEGATE_MONITOR_ALERTS_FIELD = "audit_delegate_monitor_alerts" // This is defined here instead of in ScheduledJob to avoid having the ScheduledJob class know about all // the different subclasses and creating circular dependencies @@ -201,6 +208,7 @@ data class Workflow( val inputs: MutableList = mutableListOf() val triggers: MutableList = mutableListOf() var owner = DEFAULT_OWNER + var auditDelegateMonitorAlerts = true XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { @@ -245,6 +253,7 @@ data class Workflow( } ENABLED_TIME_FIELD -> enabledTime = xcp.instant() LAST_UPDATE_TIME_FIELD -> lastUpdateTime = xcp.instant() + AUDIT_DELEGATE_MONITOR_ALERTS_FIELD -> auditDelegateMonitorAlerts = xcp.booleanValue() OWNER_FIELD -> { owner = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) owner else xcp.text() } @@ -272,7 +281,8 @@ data class Workflow( schemaVersion, inputs.toList(), owner, - triggers + triggers, + auditDelegateMonitorAlerts ) } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt index ad92d615..49792cb2 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt @@ -173,6 +173,7 @@ fun randomWorkflow( enabledTime: Instant? = if (enabled) Instant.now().truncatedTo(ChronoUnit.MILLIS) else null, lastUpdateTime: Instant = Instant.now().truncatedTo(ChronoUnit.MILLIS), triggers: List = listOf(randomChainedAlertTrigger()), + auditDelegateMonitorAlerts: Boolean? = true ): Workflow { val delegates = mutableListOf() if (!monitorIds.isNullOrEmpty()) { @@ -195,7 +196,7 @@ fun randomWorkflow( return Workflow( name = name, workflowType = Workflow.WorkflowType.COMPOSITE, enabled = enabled, inputs = input, schedule = schedule, enabledTime = enabledTime, lastUpdateTime = lastUpdateTime, user = user, - triggers = triggers + triggers = triggers, auditDelegateMonitorAlerts = auditDelegateMonitorAlerts ) } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequestTests.kt index 6cead607..da480fbc 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequestTests.kt @@ -24,6 +24,7 @@ internal class GetWorkflowAlertsRequestTests { workflowIds = listOf("w1", "w2"), alertIds = emptyList(), alertIndex = null, + associatedAlertsIndex = null, monitorIds = emptyList() ) assertNotNull(req) @@ -41,6 +42,42 @@ internal class GetWorkflowAlertsRequestTests { assertTrue(newReq.alertIds!!.isEmpty()) assertTrue(newReq.monitorIds!!.isEmpty()) assertNull(newReq.alertIndex) + assertNull(newReq.associatedAlertsIndex) + assertTrue(newReq.getAssociatedAlerts) + } + + @Test + fun `test get alerts request with custom alerts and associated alerts indices`() { + + 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 = "alertIndex", + associatedAlertsIndex = "associatedAlertsIndex", + 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()) + assertEquals(newReq.alertIndex, "alertIndex") + assertEquals(newReq.associatedAlertsIndex, "associatedAlertsIndex") assertTrue(newReq.getAssociatedAlerts) } @@ -55,7 +92,8 @@ internal class GetWorkflowAlertsRequestTests { getAssociatedAlerts = true, workflowIds = listOf("w1, w2"), alertIds = emptyList(), - alertIndex = null + alertIndex = null, + associatedAlertsIndex = null ) assertNotNull(req) assertNull(req.validate()) diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponseTests.kt new file mode 100644 index 00000000..e1977357 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponseTests.kt @@ -0,0 +1,65 @@ +package org.opensearch.commons.alerting.action + +import org.junit.jupiter.api.Assertions +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.CompositeInput +import org.opensearch.commons.alerting.model.IntervalSchedule +import org.opensearch.commons.alerting.model.Workflow +import org.opensearch.commons.alerting.randomDelegate +import org.opensearch.commons.alerting.randomUser +import org.opensearch.commons.alerting.randomWorkflow +import org.opensearch.rest.RestStatus +import java.time.Instant +import java.time.temporal.ChronoUnit + +class GetWorkflowResponseTests { + + @Test + fun testGetWorkflowResponse() { + val workflow = randomWorkflow(auditDelegateMonitorAlerts = false) + val response = GetWorkflowResponse( + id = "id", version = 1, seqNo = 1, primaryTerm = 1, status = RestStatus.OK, workflow = workflow + ) + val out = BytesStreamOutput() + response.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newRes = GetWorkflowResponse(sin) + Assertions.assertEquals("id", newRes.id) + Assertions.assertFalse(newRes.workflow!!.auditDelegateMonitorAlerts!!) + Assertions.assertEquals(workflow.name, newRes.workflow!!.name) + Assertions.assertEquals(workflow.owner, newRes.workflow!!.owner) + } + + @Test + fun testGetWorkflowResponseWhereAuditDelegateMonitorAlertsFlagIsNotSet() { + val workflow = Workflow( + id = "", + version = Workflow.NO_VERSION, + name = "test", + enabled = true, + schemaVersion = 2, + schedule = IntervalSchedule(1, ChronoUnit.MINUTES), + lastUpdateTime = Instant.now(), + enabledTime = Instant.now(), + workflowType = Workflow.WorkflowType.COMPOSITE, + user = randomUser(), + inputs = listOf(CompositeInput(org.opensearch.commons.alerting.model.Sequence(listOf(randomDelegate())))), + owner = "", + triggers = listOf() + ) + val response = GetWorkflowResponse( + id = "id", version = 1, seqNo = 1, primaryTerm = 1, status = RestStatus.OK, workflow = workflow + ) + val out = BytesStreamOutput() + response.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newRes = GetWorkflowResponse(sin) + Assertions.assertEquals("id", newRes.id) + Assertions.assertTrue(newRes.workflow!!.auditDelegateMonitorAlerts!!) + Assertions.assertEquals(workflow.name, newRes.workflow!!.name) + Assertions.assertEquals(workflow.owner, newRes.workflow!!.owner) + Assertions.assertEquals(workflow.auditDelegateMonitorAlerts, newRes.workflow!!.auditDelegateMonitorAlerts) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequestTests.kt index d25cd5b2..bb4c453a 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequestTests.kt @@ -29,7 +29,7 @@ class IndexWorkflowRequestTests { val req = IndexWorkflowRequest( "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.POST, - randomWorkflow() + randomWorkflow(auditDelegateMonitorAlerts = false) ) Assertions.assertNotNull(req) @@ -42,6 +42,7 @@ class IndexWorkflowRequestTests { Assertions.assertEquals(2L, newReq.primaryTerm) Assertions.assertEquals(RestRequest.Method.POST, newReq.method) Assertions.assertNotNull(newReq.workflow) + Assertions.assertFalse(newReq.workflow.auditDelegateMonitorAlerts!!) } @Test 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 75d794a9..67e16908 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt @@ -187,6 +187,14 @@ class XContentTests { Assertions.assertEquals(workflow, parsedMonitor, "Round tripping BucketLevelMonitor doesn't work") } + @Test + fun `test composite workflow parsing with auditDelegateMonitorAlerts flag disabled`() { + val workflow = randomWorkflow(auditDelegateMonitorAlerts = false) + val monitorString = workflow.toJsonStringWithUser() + val parsedMonitor = Workflow.parse(parser(monitorString)) + Assertions.assertEquals(workflow, parsedMonitor, "Round tripping BucketLevelMonitor doesn't work") + } + @Test fun `test query-level trigger parsing`() { val trigger = randomQueryLevelTrigger()