From 6dee931a55c8558545bfbffe4fcdd6c3a0f8e5d9 Mon Sep 17 00:00:00 2001 From: Petar Dzepina Date: Tue, 2 May 2023 23:21:16 +0200 Subject: [PATCH 1/4] added noop trigger Signed-off-by: Petar Dzepina --- .../commons/alerting/model/Alert.kt | 18 +++++ .../commons/alerting/model/Monitor.kt | 3 + .../commons/alerting/model/NoOpTrigger.kt | 75 +++++++++++++++++++ .../commons/alerting/TestHelpers.kt | 4 +- .../commons/alerting/model/XContentTests.kt | 11 +++ 5 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt 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 c251e540..90c94e3c 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt @@ -125,6 +125,24 @@ data class Alert( aggregationResultBucket = null, findingIds = findingIds, relatedDocIds = relatedDocIds ) + constructor( + id: String = NO_ID, + monitor: Monitor, + trigger: NoOpTrigger, + startTime: Instant, + lastNotificationTime: Instant?, + state: State = State.ERROR, + errorMessage: String, + errorHistory: List = mutableListOf(), + schemaVersion: Int = NO_SCHEMA_VERSION + ) : 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() + ) + enum class State { ACTIVE, ACKNOWLEDGED, COMPLETED, ERROR, DELETED } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Monitor.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Monitor.kt index e32f1b67..a7e7b639 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Monitor.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Monitor.kt @@ -50,6 +50,9 @@ data class Monitor( // Ensure that trigger ids are unique within a monitor val triggerIds = mutableSetOf() triggers.forEach { trigger -> + // NoOpTrigger is only used in "Monitor Error Alerts" as a placeholder + require(trigger !is NoOpTrigger) + require(triggerIds.add(trigger.id)) { "Duplicate trigger id: ${trigger.id}. Trigger ids must be unique." } // Verify Trigger type based on Monitor type when (monitorType) { diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt new file mode 100644 index 00000000..6ffcddda --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt @@ -0,0 +1,75 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.CheckedFunction +import org.opensearch.common.UUIDs +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.alerting.model.action.Action +import org.opensearch.core.ParseField +import org.opensearch.core.xcontent.NamedXContentRegistry +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser +import java.io.IOException + +data class NoOpTrigger( + override val id: String = UUIDs.base64UUID(), + override val name: String = "NoOp trigger", + override val severity: String = "", + override val actions: List = listOf(), +) : Trigger { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this() + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .startObject(NOOP_TRIGGER_FIELD) + .endObject() + return builder + } + + override fun name(): String { + return NOOP_TRIGGER_FIELD + } + + fun asTemplateArg(): Map { + return mapOf() + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + } + + companion object { + const val NOOP_TRIGGER_FIELD = "noop_trigger" + val XCONTENT_REGISTRY = NamedXContentRegistry.Entry( + Trigger::class.java, ParseField(NOOP_TRIGGER_FIELD), + CheckedFunction { parseInner(it) } + ) + + @JvmStatic @Throws(IOException::class) + fun parseInner(xcp: XContentParser): NoOpTrigger { + if (xcp.currentToken() != XContentParser.Token.START_OBJECT && xcp.currentToken() != XContentParser.Token.FIELD_NAME) { + XContentParserUtils.throwUnknownToken(xcp.currentToken(), xcp.tokenLocation) + } + + // If the parser began on START_OBJECT, move to the next token so that the while loop enters on + // the fieldName (or END_OBJECT if it's empty). + if (xcp.currentToken() == XContentParser.Token.START_OBJECT) xcp.nextToken() + if (xcp.currentToken() != XContentParser.Token.END_OBJECT) { + XContentParserUtils.throwUnknownToken(xcp.currentToken(), xcp.tokenLocation) + } else { + xcp.nextToken() + } + return NoOpTrigger() + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): NoOpTrigger { + return NoOpTrigger(sin) + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt index a16fee63..685898ec 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt @@ -29,6 +29,7 @@ import org.opensearch.commons.alerting.model.Finding import org.opensearch.commons.alerting.model.Input import org.opensearch.commons.alerting.model.IntervalSchedule import org.opensearch.commons.alerting.model.Monitor +import org.opensearch.commons.alerting.model.NoOpTrigger import org.opensearch.commons.alerting.model.QueryLevelTrigger import org.opensearch.commons.alerting.model.Schedule import org.opensearch.commons.alerting.model.SearchInput @@ -395,7 +396,8 @@ fun xContentRegistry(): NamedXContentRegistry { DocLevelMonitorInput.XCONTENT_REGISTRY, QueryLevelTrigger.XCONTENT_REGISTRY, BucketLevelTrigger.XCONTENT_REGISTRY, - DocumentLevelTrigger.XCONTENT_REGISTRY + DocumentLevelTrigger.XCONTENT_REGISTRY, + NoOpTrigger.XCONTENT_REGISTRY ) + SearchModule(Settings.EMPTY, emptyList()).namedXContents ) } 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 fa93da5f..9481c86f 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt @@ -30,6 +30,7 @@ import org.opensearch.core.xcontent.ToXContent import org.opensearch.index.query.QueryBuilders import org.opensearch.search.builder.SearchSourceBuilder import org.opensearch.test.OpenSearchTestCase +import java.time.Instant import java.time.temporal.ChronoUnit import kotlin.test.assertFailsWith @@ -357,6 +358,16 @@ class XContentTests { assertEquals("Round tripping alert doesn't work", alert, parsedAlert) } + @Test + 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() + ) + assertEquals("Round tripping alert doesn't work", alert.triggerName, "NoOp trigger") + } + @Test fun `test alert parsing without user`() { val alertStr = "{\"id\":\"\",\"version\":-1,\"monitor_id\":\"\",\"schema_version\":0,\"monitor_version\":1," + From b5cd90128f2b6ccedd6277f534f642203502d7a2 Mon Sep 17 00:00:00 2001 From: Petar Dzepina Date: Thu, 4 May 2023 22:16:10 +0200 Subject: [PATCH 2/4] added noop trigger xcontent tests Signed-off-by: Petar Dzepina --- .../commons/alerting/model/NoOpTrigger.kt | 21 ++++++++++--------- .../commons/alerting/model/Trigger.kt | 3 ++- .../commons/alerting/model/XContentTests.kt | 10 +++++++++ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt index 6ffcddda..7b0ad8c5 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt @@ -26,7 +26,9 @@ data class NoOpTrigger( override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { builder.startObject() .startObject(NOOP_TRIGGER_FIELD) + .field(ID_FIELD, id) .endObject() + .endObject() return builder } @@ -43,6 +45,7 @@ data class NoOpTrigger( } companion object { + const val ID_FIELD = "id" const val NOOP_TRIGGER_FIELD = "noop_trigger" val XCONTENT_REGISTRY = NamedXContentRegistry.Entry( Trigger::class.java, ParseField(NOOP_TRIGGER_FIELD), @@ -51,19 +54,17 @@ data class NoOpTrigger( @JvmStatic @Throws(IOException::class) fun parseInner(xcp: XContentParser): NoOpTrigger { - if (xcp.currentToken() != XContentParser.Token.START_OBJECT && xcp.currentToken() != XContentParser.Token.FIELD_NAME) { - XContentParserUtils.throwUnknownToken(xcp.currentToken(), xcp.tokenLocation) - } - - // If the parser began on START_OBJECT, move to the next token so that the while loop enters on - // the fieldName (or END_OBJECT if it's empty). + var id = "" if (xcp.currentToken() == XContentParser.Token.START_OBJECT) xcp.nextToken() - if (xcp.currentToken() != XContentParser.Token.END_OBJECT) { - XContentParserUtils.throwUnknownToken(xcp.currentToken(), xcp.tokenLocation) - } else { + if (xcp.currentName() == ID_FIELD) { xcp.nextToken() + id = xcp.text() + xcp.nextToken() + } + if (xcp.currentToken() != XContentParser.Token.END_OBJECT || id == "") { + XContentParserUtils.throwUnknownToken(xcp.currentToken(), xcp.tokenLocation) } - return NoOpTrigger() + return NoOpTrigger(id = id) } @JvmStatic diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Trigger.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Trigger.kt index 4b83fed3..254b6401 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Trigger.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Trigger.kt @@ -12,7 +12,8 @@ interface Trigger : BaseModel { enum class Type(val value: String) { DOCUMENT_LEVEL_TRIGGER(DocumentLevelTrigger.DOCUMENT_LEVEL_TRIGGER_FIELD), QUERY_LEVEL_TRIGGER(QueryLevelTrigger.QUERY_LEVEL_TRIGGER_FIELD), - BUCKET_LEVEL_TRIGGER(BucketLevelTrigger.BUCKET_LEVEL_TRIGGER_FIELD); + BUCKET_LEVEL_TRIGGER(BucketLevelTrigger.BUCKET_LEVEL_TRIGGER_FIELD), + NOOP_TRIGGER(NoOpTrigger.NOOP_TRIGGER_FIELD); override fun toString(): String { return value 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 9481c86f..cdcb7cd9 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt @@ -180,6 +180,16 @@ class XContentTests { Assertions.assertEquals(trigger, parsedTrigger, "Round tripping BucketLevelTrigger doesn't work") } + @Test + fun `test no-op trigger parsing`() { + val trigger = NoOpTrigger() + + val triggerString = trigger.toXContent(builder(), ToXContent.EMPTY_PARAMS).string() + val parsedTrigger = Trigger.parse(parser(triggerString)) + + Assertions.assertEquals(trigger, parsedTrigger, "Round tripping BucketLevelTrigger doesn't work") + } + @Test fun `test creating a monitor with duplicate trigger ids fails`() { try { From 736468f0aac3ff29784d6a454bd78114e94dcd40 Mon Sep 17 00:00:00 2001 From: Petar Dzepina Date: Thu, 4 May 2023 23:15:46 +0200 Subject: [PATCH 3/4] small fixes Signed-off-by: Petar Dzepina --- .../org/opensearch/commons/alerting/model/NoOpTrigger.kt | 4 ++-- .../org/opensearch/commons/alerting/model/XContentTests.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt index 7b0ad8c5..045b099c 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt @@ -54,14 +54,14 @@ data class NoOpTrigger( @JvmStatic @Throws(IOException::class) fun parseInner(xcp: XContentParser): NoOpTrigger { - var id = "" + var id = UUIDs.base64UUID() if (xcp.currentToken() == XContentParser.Token.START_OBJECT) xcp.nextToken() if (xcp.currentName() == ID_FIELD) { xcp.nextToken() id = xcp.text() xcp.nextToken() } - if (xcp.currentToken() != XContentParser.Token.END_OBJECT || id == "") { + if (xcp.currentToken() != XContentParser.Token.END_OBJECT) { XContentParserUtils.throwUnknownToken(xcp.currentToken(), xcp.tokenLocation) } return NoOpTrigger(id = 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 cdcb7cd9..f8c98842 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt @@ -187,7 +187,7 @@ class XContentTests { val triggerString = trigger.toXContent(builder(), ToXContent.EMPTY_PARAMS).string() val parsedTrigger = Trigger.parse(parser(triggerString)) - Assertions.assertEquals(trigger, parsedTrigger, "Round tripping BucketLevelTrigger doesn't work") + Assertions.assertEquals(trigger, parsedTrigger, "Round tripping NoOpTrigger doesn't work") } @Test From 22f437e5cf54bcfef336d9dee3603e9e5f187693 Mon Sep 17 00:00:00 2001 From: Petar Dzepina Date: Thu, 4 May 2023 23:17:12 +0200 Subject: [PATCH 4/4] ktlint fix Signed-off-by: Petar Dzepina --- .../kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt index 045b099c..92456492 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt @@ -28,7 +28,7 @@ data class NoOpTrigger( .startObject(NOOP_TRIGGER_FIELD) .field(ID_FIELD, id) .endObject() - .endObject() + .endObject() return builder }