From 0b5281cad15d1524ceaa29a77198258cad15ac8f Mon Sep 17 00:00:00 2001 From: Subhobrata Dey Date: Thu, 30 May 2024 21:10:29 +0000 Subject: [PATCH] changes to support generic inputs and triggers in remote monitors (#664) Signed-off-by: Subhobrata Dey --- .../commons/alerting/model/Input.kt | 10 +- .../commons/alerting/model/Trigger.kt | 5 +- .../remote/monitors/RemoteMonitorInput.kt | 70 ++++++++++ .../remote/monitors/RemoteMonitorTrigger.kt | 126 ++++++++++++++++++ .../commons/alerting/TestHelpers.kt | 10 +- .../DocLevelMonitorFanOutRequestTests.kt | 6 +- .../DocLevelMonitorFanOutResponseTests.kt | 8 +- .../commons/alerting/model/WriteableTests.kt | 78 ++++++++++- .../commons/alerting/model/XContentTests.kt | 31 +++++ 9 files changed, 335 insertions(+), 9 deletions(-) create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/remote/monitors/RemoteMonitorInput.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/remote/monitors/RemoteMonitorTrigger.kt diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Input.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Input.kt index b3472f8a..7a5bfe59 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Input.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Input.kt @@ -3,6 +3,8 @@ package org.opensearch.commons.alerting.model import org.opensearch.commons.alerting.model.ClusterMetricsInput.Companion.URI_FIELD import org.opensearch.commons.alerting.model.DocLevelMonitorInput.Companion.DOC_LEVEL_INPUT_FIELD import org.opensearch.commons.alerting.model.SearchInput.Companion.SEARCH_FIELD +import org.opensearch.commons.alerting.model.remote.monitors.RemoteMonitorInput +import org.opensearch.commons.alerting.model.remote.monitors.RemoteMonitorInput.Companion.REMOTE_MONITOR_INPUT_FIELD import org.opensearch.commons.notifications.model.BaseModel import org.opensearch.core.common.io.stream.StreamInput import org.opensearch.core.xcontent.XContentParser @@ -14,7 +16,8 @@ interface Input : BaseModel { enum class Type(val value: String) { DOCUMENT_LEVEL_INPUT(DOC_LEVEL_INPUT_FIELD), CLUSTER_METRICS_INPUT(URI_FIELD), - SEARCH_INPUT(SEARCH_FIELD); + SEARCH_INPUT(SEARCH_FIELD), + REMOTE_MONITOR_INPUT(REMOTE_MONITOR_INPUT_FIELD); override fun toString(): String { return value @@ -32,8 +35,10 @@ interface Input : BaseModel { SearchInput.parseInner(xcp) } else if (xcp.currentName() == Type.CLUSTER_METRICS_INPUT.value) { ClusterMetricsInput.parseInner(xcp) - } else { + } else if (xcp.currentName() == Type.DOCUMENT_LEVEL_INPUT.value) { DocLevelMonitorInput.parse(xcp) + } else { + RemoteMonitorInput.parse(xcp) } XContentParserUtils.ensureExpectedToken(XContentParser.Token.END_OBJECT, xcp.nextToken(), xcp) return input @@ -46,6 +51,7 @@ interface Input : BaseModel { Type.DOCUMENT_LEVEL_INPUT -> DocLevelMonitorInput(sin) Type.CLUSTER_METRICS_INPUT -> ClusterMetricsInput(sin) Type.SEARCH_INPUT -> SearchInput(sin) + Type.REMOTE_MONITOR_INPUT -> RemoteMonitorInput(sin) // This shouldn't be reachable but ensuring exhaustiveness as Kotlin warns // enum can be null in Java else -> throw IllegalStateException("Unexpected input [$type] when reading Trigger") 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 1834f3b7..7cfb9f41 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Trigger.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Trigger.kt @@ -1,6 +1,7 @@ package org.opensearch.commons.alerting.model import org.opensearch.commons.alerting.model.action.Action +import org.opensearch.commons.alerting.model.remote.monitors.RemoteMonitorTrigger import org.opensearch.commons.notifications.model.BaseModel import org.opensearch.core.common.io.stream.StreamInput import org.opensearch.core.xcontent.XContentParser @@ -14,7 +15,8 @@ interface Trigger : BaseModel { QUERY_LEVEL_TRIGGER(QueryLevelTrigger.QUERY_LEVEL_TRIGGER_FIELD), BUCKET_LEVEL_TRIGGER(BucketLevelTrigger.BUCKET_LEVEL_TRIGGER_FIELD), NOOP_TRIGGER(NoOpTrigger.NOOP_TRIGGER_FIELD), - CHAINED_ALERT_TRIGGER(ChainedAlertTrigger.CHAINED_ALERT_TRIGGER_FIELD); + CHAINED_ALERT_TRIGGER(ChainedAlertTrigger.CHAINED_ALERT_TRIGGER_FIELD), + REMOTE_MONITOR_TRIGGER(RemoteMonitorTrigger.REMOTE_MONITOR_TRIGGER_FIELD); override fun toString(): String { return value @@ -55,6 +57,7 @@ interface Trigger : BaseModel { Type.BUCKET_LEVEL_TRIGGER -> BucketLevelTrigger(sin) Type.DOCUMENT_LEVEL_TRIGGER -> DocumentLevelTrigger(sin) Type.CHAINED_ALERT_TRIGGER -> ChainedAlertTrigger(sin) + Type.REMOTE_MONITOR_TRIGGER -> RemoteMonitorTrigger(sin) // This shouldn't be reachable but ensuring exhaustiveness as Kotlin warns // enum can be null in Java else -> throw IllegalStateException("Unexpected input [$type] when reading Trigger") diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/remote/monitors/RemoteMonitorInput.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/remote/monitors/RemoteMonitorInput.kt new file mode 100644 index 00000000..c2d3867b --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/remote/monitors/RemoteMonitorInput.kt @@ -0,0 +1,70 @@ +package org.opensearch.commons.alerting.model.remote.monitors + +import org.opensearch.commons.alerting.model.Input +import org.opensearch.core.common.bytes.BytesReference +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils +import java.io.IOException +import java.nio.ByteBuffer + +data class RemoteMonitorInput(val input: BytesReference) : Input { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readBytesReference() + ) + + fun asTemplateArg(): Map { + val bytes = input.toBytesRef().bytes + return mapOf( + INPUT_SIZE to bytes.size, + INPUT_FIELD to bytes + ) + } + + override fun name(): String { + return REMOTE_MONITOR_INPUT_FIELD + } + + override fun writeTo(out: StreamOutput) { + out.writeBytesReference(input) + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + val bytes = input.toBytesRef().bytes + return builder.startObject() + .startObject(REMOTE_MONITOR_INPUT_FIELD) + .field(INPUT_SIZE, bytes.size) + .field(INPUT_FIELD, bytes) + .endObject() + .endObject() + } + + companion object { + const val INPUT_FIELD = "input" + const val INPUT_SIZE = "size" + const val REMOTE_MONITOR_INPUT_FIELD = "remote_monitor_input" + + fun parse(xcp: XContentParser): RemoteMonitorInput { + var bytes: ByteArray? = null + var size: Int = 0 + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + when (fieldName) { + INPUT_FIELD -> bytes = xcp.binaryValue() + INPUT_SIZE -> size = xcp.intValue() + } + } + val input = BytesReference.fromByteBuffer(ByteBuffer.wrap(bytes, 0, size)) + return RemoteMonitorInput(input) + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/remote/monitors/RemoteMonitorTrigger.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/remote/monitors/RemoteMonitorTrigger.kt new file mode 100644 index 00000000..0e89e5ba --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/remote/monitors/RemoteMonitorTrigger.kt @@ -0,0 +1,126 @@ +package org.opensearch.commons.alerting.model.remote.monitors + +import org.opensearch.common.CheckedFunction +import org.opensearch.common.UUIDs +import org.opensearch.commons.alerting.model.Trigger +import org.opensearch.commons.alerting.model.action.Action +import org.opensearch.core.ParseField +import org.opensearch.core.common.bytes.BytesReference +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput +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 org.opensearch.core.xcontent.XContentParserUtils +import java.io.IOException +import java.nio.ByteBuffer + +data class RemoteMonitorTrigger( + override val id: String, + override val name: String, + override val severity: String, + override val actions: List, + val trigger: BytesReference +) : Trigger { + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readString(), + sin.readString(), + sin.readString(), + sin.readList(::Action), + sin.readBytesReference() + ) + + fun asTemplateArg(): Map { + val bytes = trigger.toBytesRef().bytes + return mapOf( + Trigger.ID_FIELD to id, + Trigger.NAME_FIELD to name, + Trigger.SEVERITY_FIELD to severity, + Trigger.ACTIONS_FIELD to actions.map { it.asTemplateArg() }, + TRIGGER_SIZE to bytes.size, + TRIGGER_FIELD to bytes + ) + } + + override fun name(): String { + return REMOTE_MONITOR_TRIGGER_FIELD + } + + override fun writeTo(out: StreamOutput) { + out.writeString(id) + out.writeString(name) + out.writeString(severity) + out.writeCollection(actions) + out.writeBytesReference(trigger) + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + val bytes = trigger.toBytesRef().bytes + return builder.startObject() + .startObject(REMOTE_MONITOR_TRIGGER_FIELD) + .field(Trigger.ID_FIELD, id) + .field(Trigger.NAME_FIELD, name) + .field(Trigger.SEVERITY_FIELD, severity) + .field(Trigger.ACTIONS_FIELD, actions.toTypedArray()) + .field(TRIGGER_SIZE, bytes.size) + .field(TRIGGER_FIELD, bytes) + .endObject() + .endObject() + } + + companion object { + const val TRIGGER_FIELD = "trigger" + const val TRIGGER_SIZE = "size" + const val REMOTE_MONITOR_TRIGGER_FIELD = "remote_monitor_trigger" + + val XCONTENT_REGISTRY = NamedXContentRegistry.Entry( + Trigger::class.java, + ParseField(REMOTE_MONITOR_TRIGGER_FIELD), + CheckedFunction { parseInner(it) } + ) + + fun parseInner(xcp: XContentParser): RemoteMonitorTrigger { + var id = UUIDs.base64UUID() // assign a default triggerId if one is not specified + lateinit var name: String + lateinit var severity: String + val actions: MutableList = mutableListOf() + var bytes: ByteArray? = null + var size: Int = 0 + + 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() + while (xcp.currentToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + when (fieldName) { + Trigger.ID_FIELD -> id = xcp.text() + Trigger.NAME_FIELD -> name = xcp.text() + Trigger.SEVERITY_FIELD -> severity = xcp.text() + Trigger.ACTIONS_FIELD -> { + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_ARRAY, + xcp.currentToken(), + xcp + ) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + actions.add(Action.parse(xcp)) + } + } + TRIGGER_FIELD -> bytes = xcp.binaryValue() + TRIGGER_SIZE -> size = xcp.intValue() + } + xcp.nextToken() + } + val trigger = BytesReference.fromByteBuffer(ByteBuffer.wrap(bytes, 0, size)) + return RemoteMonitorTrigger(id, name, severity, actions, trigger) + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt index d4f1e4f4..7ae132ef 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt @@ -56,6 +56,7 @@ import org.opensearch.commons.alerting.model.action.AlertCategory import org.opensearch.commons.alerting.model.action.PerAlertActionScope import org.opensearch.commons.alerting.model.action.PerExecutionActionScope import org.opensearch.commons.alerting.model.action.Throttle +import org.opensearch.commons.alerting.model.remote.monitors.RemoteMonitorTrigger import org.opensearch.commons.alerting.util.getBucketKeysHash import org.opensearch.commons.alerting.util.string import org.opensearch.commons.authuser.User @@ -514,6 +515,12 @@ fun parser(xc: String): XContentParser { return parser } +fun parser(xc: ByteArray): XContentParser { + val parser = XContentType.JSON.xContent().createParser(xContentRegistry(), LoggingDeprecationHandler.INSTANCE, xc) + parser.nextToken() + return parser +} + fun xContentRegistry(): NamedXContentRegistry { return NamedXContentRegistry( listOf( @@ -523,7 +530,8 @@ fun xContentRegistry(): NamedXContentRegistry { BucketLevelTrigger.XCONTENT_REGISTRY, DocumentLevelTrigger.XCONTENT_REGISTRY, ChainedAlertTrigger.XCONTENT_REGISTRY, - NoOpTrigger.XCONTENT_REGISTRY + NoOpTrigger.XCONTENT_REGISTRY, + RemoteMonitorTrigger.XCONTENT_REGISTRY ) + SearchModule(Settings.EMPTY, emptyList()).namedXContents ) } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/DocLevelMonitorFanOutRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/DocLevelMonitorFanOutRequestTests.kt index 3188c7e3..dda45483 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/DocLevelMonitorFanOutRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/DocLevelMonitorFanOutRequestTests.kt @@ -5,6 +5,8 @@ package org.opensearch.commons.alerting.action +import org.junit.Assert.assertEquals +import org.junit.jupiter.api.Test import org.opensearch.common.io.stream.BytesStreamOutput import org.opensearch.commons.alerting.model.ActionExecutionTime import org.opensearch.commons.alerting.model.DocLevelMonitorInput @@ -21,13 +23,13 @@ import org.opensearch.core.common.io.stream.StreamInput import org.opensearch.core.index.shard.ShardId import org.opensearch.index.seqno.SequenceNumbers import org.opensearch.script.Script -import org.opensearch.test.OpenSearchTestCase import java.time.Instant import java.time.temporal.ChronoUnit import java.util.UUID -class DocLevelMonitorFanOutRequestTests : OpenSearchTestCase() { +class DocLevelMonitorFanOutRequestTests { + @Test fun `test doc level monitor fan out request as stream`() { val docQuery = DocLevelQuery(query = "test_field:\"us-west-2\"", fields = listOf(), name = "3") val docLevelInput = DocLevelMonitorInput("description", listOf("test-index"), listOf(docQuery)) diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/DocLevelMonitorFanOutResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/DocLevelMonitorFanOutResponseTests.kt index 876360fd..645b7d5c 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/DocLevelMonitorFanOutResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/DocLevelMonitorFanOutResponseTests.kt @@ -5,13 +5,16 @@ package org.opensearch.commons.alerting.action +import org.junit.Assert.assertEquals +import org.junit.jupiter.api.Test import org.opensearch.common.io.stream.BytesStreamOutput import org.opensearch.commons.alerting.model.InputRunResults import org.opensearch.commons.alerting.randomDocumentLevelTriggerRunResult import org.opensearch.core.common.io.stream.StreamInput -import org.opensearch.test.OpenSearchTestCase -class DocLevelMonitorFanOutResponseTests : OpenSearchTestCase() { +class DocLevelMonitorFanOutResponseTests { + + @Test fun `test doc level monitor fan out response with errors as stream`() { val docLevelMonitorFanOutResponse = DocLevelMonitorFanOutResponse( "nodeid", @@ -33,6 +36,7 @@ class DocLevelMonitorFanOutResponseTests : OpenSearchTestCase() { assertEquals(docLevelMonitorFanOutResponse.triggerResults, newDocLevelMonitorFanOutResponse.triggerResults) } + @Test fun `test doc level monitor fan out response as stream`() { val workflow = DocLevelMonitorFanOutResponse( "nodeid", diff --git a/src/test/kotlin/org/opensearch/commons/alerting/model/WriteableTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/model/WriteableTests.kt index 4d6ab97c..58e788c4 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/WriteableTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/WriteableTests.kt @@ -8,6 +8,8 @@ import org.opensearch.common.io.stream.BytesStreamOutput import org.opensearch.commons.alerting.model.action.Action import org.opensearch.commons.alerting.model.action.ActionExecutionPolicy import org.opensearch.commons.alerting.model.action.Throttle +import org.opensearch.commons.alerting.model.remote.monitors.RemoteMonitorInput +import org.opensearch.commons.alerting.model.remote.monitors.RemoteMonitorTrigger import org.opensearch.commons.alerting.randomAction import org.opensearch.commons.alerting.randomActionExecutionPolicy import org.opensearch.commons.alerting.randomBucketLevelMonitorRunResult @@ -27,9 +29,12 @@ import org.opensearch.commons.alerting.randomUser import org.opensearch.commons.alerting.randomUserEmpty import org.opensearch.commons.authuser.User import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput +import org.opensearch.core.common.io.stream.Writeable import org.opensearch.search.builder.SearchSourceBuilder -import java.time.Instant import org.opensearch.test.OpenSearchTestCase +import java.io.IOException +import java.time.Instant import kotlin.test.assertTrue class WriteableTests { @@ -227,6 +232,7 @@ class WriteableTests { Assertions.assertEquals(user, newComment.user) } + @Test fun `test actionrunresult as stream`() { val actionRunResult = randomActionRunResult() val out = BytesStreamOutput() @@ -240,6 +246,7 @@ class WriteableTests { ) } + @Test fun `test query-level triggerrunresult as stream`() { val runResult = randomQueryLevelTriggerRunResult() val out = BytesStreamOutput() @@ -252,6 +259,7 @@ class WriteableTests { OpenSearchTestCase.assertEquals(runResult.actionResults, newRunResult.actionResults) } + @Test fun `test bucket-level triggerrunresult as stream`() { val runResult = randomBucketLevelTriggerRunResult() val out = BytesStreamOutput() @@ -261,6 +269,7 @@ class WriteableTests { OpenSearchTestCase.assertEquals("Round tripping ActionRunResult doesn't work", runResult, newRunResult) } + @Test fun `test doc-level triggerrunresult as stream`() { val runResult = randomDocumentLevelTriggerRunResult() val out = BytesStreamOutput() @@ -270,6 +279,7 @@ class WriteableTests { OpenSearchTestCase.assertEquals("Round tripping ActionRunResult doesn't work", runResult, newRunResult) } + @Test fun `test inputrunresult as stream`() { val runResult = randomInputRunResults() val out = BytesStreamOutput() @@ -279,6 +289,7 @@ class WriteableTests { OpenSearchTestCase.assertEquals("Round tripping InputRunResults doesn't work", runResult, newRunResult) } + @Test fun `test query-level monitorrunresult as stream`() { val runResult = randomQueryLevelMonitorRunResult() val out = BytesStreamOutput() @@ -288,6 +299,7 @@ class WriteableTests { OpenSearchTestCase.assertEquals("Round tripping MonitorRunResult doesn't work", runResult, newRunResult) } + @Test fun `test bucket-level monitorrunresult as stream`() { val runResult = randomBucketLevelMonitorRunResult() val out = BytesStreamOutput() @@ -317,6 +329,38 @@ class WriteableTests { Assert.assertEquals("Round tripping dltrr failed", newWorkflow, workflow) } + @Test + fun `test RemoteMonitorInput as stream`() { + val myMonitorInput = MyMonitorInput(1, "hello", MyMonitorInput(2, "world", null)) + val myObjOut = BytesStreamOutput() + myMonitorInput.writeTo(myObjOut) + val remoteMonitorInput = RemoteMonitorInput(myObjOut.bytes()) + + val out = BytesStreamOutput() + remoteMonitorInput.writeTo(out) + + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newRemoteMonitorInput = RemoteMonitorInput(sin) + val newMyMonitorInput = MyMonitorInput(StreamInput.wrap(newRemoteMonitorInput.input.toBytesRef().bytes)) + Assert.assertEquals("Round tripping RemoteMonitorInput failed", newMyMonitorInput, myMonitorInput) + } + + @Test + fun `test RemoteMonitorTrigger as stream`() { + val myMonitorTrigger = MyMonitorTrigger(1, "hello", MyMonitorTrigger(2, "world", null)) + val myObjOut = BytesStreamOutput() + myMonitorTrigger.writeTo(myObjOut) + val remoteMonitorTrigger = RemoteMonitorTrigger("id", "name", "1", listOf(), myObjOut.bytes()) + + val out = BytesStreamOutput() + remoteMonitorTrigger.writeTo(out) + + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newRemoteMonitorTrigger = RemoteMonitorTrigger(sin) + val newMyMonitorTrigger = MyMonitorTrigger(StreamInput.wrap(newRemoteMonitorTrigger.trigger.toBytesRef().bytes)) + Assert.assertEquals("Round tripping RemoteMonitorTrigger failed", newMyMonitorTrigger, myMonitorTrigger) + } + fun randomDocumentLevelTriggerRunResult(): DocumentLevelTriggerRunResult { val map = mutableMapOf() map.plus(Pair("key1", randomActionRunResult())) @@ -343,3 +387,35 @@ class WriteableTests { ) } } + +data class MyMonitorInput(val a: Int, val b: String, val c: MyMonitorInput?) : Writeable { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readInt(), + sin.readString(), + sin.readOptionalWriteable { MyMonitorInput(it) } + ) + + override fun writeTo(out: StreamOutput) { + out.writeInt(a) + out.writeString(b) + out.writeOptionalWriteable(c) + } +} + +data class MyMonitorTrigger(val a: Int, val b: String, val c: MyMonitorTrigger?) : Writeable { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readInt(), + sin.readString(), + sin.readOptionalWriteable { MyMonitorTrigger(it) } + ) + + override fun writeTo(out: StreamOutput) { + out.writeInt(a) + out.writeString(b) + out.writeOptionalWriteable(c) + } +} 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 5364116b..0b3c9cf5 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt @@ -3,12 +3,16 @@ package org.opensearch.commons.alerting.model import org.junit.Assert.assertEquals import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test +import org.opensearch.common.io.stream.BytesStreamOutput import org.opensearch.common.xcontent.XContentFactory +import org.opensearch.common.xcontent.json.JsonXContent import org.opensearch.commons.alerting.builder import org.opensearch.commons.alerting.model.action.Action import org.opensearch.commons.alerting.model.action.ActionExecutionPolicy import org.opensearch.commons.alerting.model.action.PerExecutionActionScope import org.opensearch.commons.alerting.model.action.Throttle +import org.opensearch.commons.alerting.model.remote.monitors.RemoteMonitorInput +import org.opensearch.commons.alerting.model.remote.monitors.RemoteMonitorTrigger import org.opensearch.commons.alerting.parser import org.opensearch.commons.alerting.randomAction import org.opensearch.commons.alerting.randomActionExecutionPolicy @@ -28,6 +32,7 @@ import org.opensearch.commons.alerting.toJsonString import org.opensearch.commons.alerting.toJsonStringWithUser import org.opensearch.commons.alerting.util.string import org.opensearch.commons.authuser.User +import org.opensearch.core.common.io.stream.StreamInput import org.opensearch.core.xcontent.ToXContent import org.opensearch.index.query.QueryBuilders import org.opensearch.search.builder.SearchSourceBuilder @@ -558,4 +563,30 @@ class XContentTests { val parsedMonitorMetadata = MonitorMetadata.parse(parser(monitorMetadataString)) assertEquals("Round tripping MonitorMetadata doesn't work", monitorMetadata, parsedMonitorMetadata) } + + @Test + fun `test RemoteMonitorInput`() { + val myMonitorInput = MyMonitorInput(1, "hello", MyMonitorInput(2, "world", null)) + val myObjOut = BytesStreamOutput() + myMonitorInput.writeTo(myObjOut) + val remoteMonitorInput = RemoteMonitorInput(myObjOut.bytes()) + + val xContent = remoteMonitorInput.toXContent(JsonXContent.contentBuilder(), ToXContent.EMPTY_PARAMS).string() + val parsedRemoteMonitorInput = RemoteMonitorInput.parse(parser(xContent)) + val parsedMyMonitorInput = MyMonitorInput(StreamInput.wrap(parsedRemoteMonitorInput.input.toBytesRef().bytes)) + assertEquals("Round tripping RemoteMonitorInput doesn't work", myMonitorInput, parsedMyMonitorInput) + } + + @Test + fun `test RemoteMonitorTrigger`() { + val myMonitorTrigger = MyMonitorTrigger(1, "hello", MyMonitorTrigger(2, "world", null)) + val myObjOut = BytesStreamOutput() + myMonitorTrigger.writeTo(myObjOut) + val remoteMonitorTrigger = RemoteMonitorTrigger("id", "name", "1", listOf(), myObjOut.bytes()) + + val xContent = remoteMonitorTrigger.toXContent(JsonXContent.contentBuilder(), ToXContent.EMPTY_PARAMS).string() + val parsedRemoteMonitorTrigger = Trigger.parse(parser(xContent)) as RemoteMonitorTrigger + val parsedMyMonitorTrigger = MyMonitorTrigger(StreamInput.wrap(parsedRemoteMonitorTrigger.trigger.toBytesRef().bytes)) + assertEquals("Round tripping RemoteMonitorTrigger doesn't work", myMonitorTrigger, parsedMyMonitorTrigger) + } }