From d27d52c23c9d66eb2b39dcb85ad22bd868737196 Mon Sep 17 00:00:00 2001 From: Aditya Jindal <13850971+aditjind@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:47:57 -0700 Subject: [PATCH 001/149] Update Release Notes for GA (#36) * Update Release Notes for GA * Update Release Notes for GA include RC1 Changes as well. Signed-off-by: Aditya Jindal --- ...arch-common-utils.release-notes-1.0.0.0.md | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 release-notes/opensearch-common-utils.release-notes-1.0.0.0.md diff --git a/release-notes/opensearch-common-utils.release-notes-1.0.0.0.md b/release-notes/opensearch-common-utils.release-notes-1.0.0.0.md new file mode 100644 index 00000000..71d1a7db --- /dev/null +++ b/release-notes/opensearch-common-utils.release-notes-1.0.0.0.md @@ -0,0 +1,29 @@ +## Version 1.0.0.0 2021-07-01 + +Compatible with OpenSearch 1.0.0 + +### Enhancements + + * Notification plugin interface and models ([#31](https://github.com/opensearch-project/common-utils/pull/31)) + +### Infrastructure + + * Support for kotlin and JUnit5 with mockito ([#29](https://github.com/opensearch-project/common-utils/pull/29)) + * Removing Kotlin Runtime library bundled into library ([#30](https://github.com/opensearch-project/common-utils/pull/30)) + * Bump to version 1.0.0.0 #34 ([#34](https://github.com/opensearch-project/common-utils/pull/34)) + +### Documentation + + * Update OpenSearch branch to 1.0 ([#28](https://github.com/opensearch-project/common-utils/pull/28)) + * Cleanup READMEs. ([#32](https://github.com/opensearch-project/common-utils/pull/32)) + +### Maintainence + + * Update issue template with multiple labels ([#18](https://github.com/opensearch-project/common-utils/pull/18)) + * Rename namespaces from OpenDistro to OpenSearch ([#20](https://github.com/opensearch-project/common-utils/pull/20)) + * Rename classes, variables, methods to incorporate OpenSearch ([#21](https://github.com/opensearch-project/common-utils/pull/21)) + * Rename remaining identifiers to OpenSearch ([#23](https://github.com/opensearch-project/common-utils/pull/23)) + * Version changed to rc1 #24 ([#24](https://github.com/opensearch-project/common-utils/pull/24)) + * Rename consts as per changes in security plugin ([#25](https://github.com/opensearch-project/common-utils/pull/25)) + * Move workflow tags to rc1 ([#26](https://github.com/opensearch-project/common-utils/pull/26)) + * Add rc1 release notes ([#27](https://github.com/opensearch-project/common-utils/pull/27)) From 62de872df5a24b1e5e5617d7a84378b9fbf38444 Mon Sep 17 00:00:00 2001 From: Zhongnan Su Date: Fri, 16 Jul 2021 15:08:55 -0700 Subject: [PATCH 002/149] add method type in CustomWebhook data model (#39) Signed-off-by: Zhongnan Su --- .../notifications/model/HttpMethodType.kt | 36 +++++++++++++++++++ .../commons/notifications/model/Webhook.kt | 13 +++++-- .../notifications/model/WebhookTests.kt | 15 ++++++-- 3 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/HttpMethodType.kt diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/HttpMethodType.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/HttpMethodType.kt new file mode 100644 index 00000000..8485e4fd --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/HttpMethodType.kt @@ -0,0 +1,36 @@ +package org.opensearch.commons.notifications.model + +import org.opensearch.commons.utils.EnumParser + +enum class HttpMethodType(val tag: String) { + POST("POST") { + override fun toString(): String { + return tag + } + }, + PUT("PUT") { + override fun toString(): String { + return tag + } + }, + PATCH("PATCH") { + override fun toString(): String { + return tag + } + }; + + companion object { + private val tagMap = values().associateBy { it.tag } + + val enumParser = EnumParser { fromTagOrDefault(it) } + + /** + * Get HttpMethodType from tag or POST if not found + * @param tag the tag + * @return MethodType corresponding to tag. POST if invalid tag. + */ + fun fromTagOrDefault(tag: String): HttpMethodType { + return tagMap[tag] ?: POST + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt index 55a4cb3e..09dbfe9d 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt @@ -35,6 +35,7 @@ import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.HEADER_PARAMS_TAG +import org.opensearch.commons.notifications.NotificationConstants.METHOD_TAG import org.opensearch.commons.notifications.NotificationConstants.URL_TAG import org.opensearch.commons.utils.STRING_READER import org.opensearch.commons.utils.STRING_WRITER @@ -47,7 +48,8 @@ import java.io.IOException */ data class Webhook( val url: String, - val headerParams: Map = mapOf() + val headerParams: Map = mapOf(), + val method: HttpMethodType = HttpMethodType.POST ) : BaseConfigData { init { @@ -77,6 +79,7 @@ data class Webhook( fun parse(parser: XContentParser): Webhook { var url: String? = null var headerParams: Map = mapOf() + var method = HttpMethodType.POST XContentParserUtils.ensureExpectedToken( XContentParser.Token.START_OBJECT, @@ -89,6 +92,7 @@ data class Webhook( when (fieldName) { URL_TAG -> url = parser.text() HEADER_PARAMS_TAG -> headerParams = parser.mapStrings() + METHOD_TAG -> method = HttpMethodType.fromTagOrDefault(parser.text()) else -> { parser.skipChildren() log.info("Unexpected field: $fieldName, while parsing Webhook destination") @@ -96,7 +100,7 @@ data class Webhook( } } url ?: throw IllegalArgumentException("$URL_TAG field absent") - return Webhook(url, headerParams) + return Webhook(url, headerParams, method) } } @@ -108,6 +112,7 @@ data class Webhook( return builder.startObject() .field(URL_TAG, url) .field(HEADER_PARAMS_TAG, headerParams) + .field(METHOD_TAG, method.tag) .endObject() } @@ -117,7 +122,8 @@ data class Webhook( */ constructor(input: StreamInput) : this( url = input.readString(), - headerParams = input.readMap(STRING_READER, STRING_READER) + headerParams = input.readMap(STRING_READER, STRING_READER), + method = input.readEnum(HttpMethodType::class.java) ) /** @@ -126,5 +132,6 @@ data class Webhook( override fun writeTo(output: StreamOutput) { output.writeString(url) output.writeMap(headerParams, STRING_WRITER, STRING_WRITER) + output.writeEnum(method) } } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/WebhookTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/WebhookTests.kt index 9ca39c7c..48dedf49 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/WebhookTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/WebhookTests.kt @@ -46,7 +46,11 @@ internal class WebhookTests { @Test fun `Webhook serialize and deserialize using json object should be equal`() { - val sampleWebhook = Webhook("https://domain.com/sample_url#1234567890", mapOf(Pair("key", "value"))) + val sampleWebhook = Webhook( + "https://domain.com/sample_url#1234567890", + mapOf(Pair("key", "value")), + HttpMethodType.PUT + ) val jsonString = getJsonString(sampleWebhook) val recreatedObject = createObjectFromJsonString(jsonString) { Webhook.parse(it) } assertEquals(sampleWebhook, recreatedObject) @@ -54,13 +58,18 @@ internal class WebhookTests { @Test fun `Webhook should deserialize json object using parser`() { - val sampleWebhook = Webhook("https://domain.com/sample_url#1234567890", mapOf(Pair("key", "value"))) + val sampleWebhook = Webhook( + "https://domain.com/sample_url#1234567890", + mapOf(Pair("key", "value")), + HttpMethodType.PATCH + ) val jsonString = """ { "url":"${sampleWebhook.url}", "header_params":{ "key":"value" - } + }, + "method":"PATCH" } """.trimIndent() val recreatedObject = createObjectFromJsonString(jsonString) { Webhook.parse(it) } From 4ef189df7708ace7bf91867bbe6b30fd3bf98a9c Mon Sep 17 00:00:00 2001 From: Chen Dai <46505291+dai-chen@users.noreply.github.com> Date: Tue, 20 Jul 2021 09:37:48 -0700 Subject: [PATCH 003/149] Fix class loader issue for notifications response (#40) * Fix class loader issue for notifications Signed-off-by: Joshua Li * Fix formatting Signed-off-by: Joshua Li * Refactor creation of action listener object Signed-off-by: Joshua Li * Fix indentation Signed-off-by: Joshua Li * Remove unused suppresses Signed-off-by: Joshua Li * Add UT for notification API Signed-off-by: Chen Dai * Add UT for notification API Signed-off-by: Chen Dai * Add UT for send notification API Signed-off-by: Chen Dai * Fix Github workflow failure Signed-off-by: Chen Dai * Fix Github workflow failure Signed-off-by: Chen Dai * Refactor UT code Signed-off-by: Chen Dai Co-authored-by: Joshua Li --- .../NotificationsPluginInterface.kt | 43 ++- .../NotificationsPluginInterfaceTests.kt | 268 ++++++++++++++++++ .../org.mockito.plugins.MockMaker | 1 + 3 files changed, 304 insertions(+), 8 deletions(-) create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt create mode 100644 src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker diff --git a/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt b/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt index e82aaba8..2f043351 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt @@ -27,8 +27,11 @@ package org.opensearch.commons.notifications import org.opensearch.action.ActionListener +import org.opensearch.action.ActionResponse import org.opensearch.client.node.NodeClient +import org.opensearch.common.io.stream.Writeable import org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_USER_INFO_THREAD_CONTEXT +import org.opensearch.commons.notifications.action.BaseResponse import org.opensearch.commons.notifications.action.CreateNotificationConfigRequest import org.opensearch.commons.notifications.action.CreateNotificationConfigResponse import org.opensearch.commons.notifications.action.DeleteNotificationConfigRequest @@ -56,6 +59,7 @@ import org.opensearch.commons.notifications.action.UpdateNotificationConfigRespo import org.opensearch.commons.notifications.model.ChannelMessage import org.opensearch.commons.notifications.model.EventSource import org.opensearch.commons.utils.SecureClientWrapper +import org.opensearch.commons.utils.recreateObject /** * All the transport action plugin interfaces for the Notification plugin @@ -76,7 +80,7 @@ object NotificationsPluginInterface { client.execute( CREATE_NOTIFICATION_CONFIG_ACTION_TYPE, request, - listener + wrapActionListener(listener) { response -> recreateObject(response) { CreateNotificationConfigResponse(it) } } ) } @@ -94,7 +98,7 @@ object NotificationsPluginInterface { client.execute( UPDATE_NOTIFICATION_CONFIG_ACTION_TYPE, request, - listener + wrapActionListener(listener) { response -> recreateObject(response) { UpdateNotificationConfigResponse(it) } } ) } @@ -112,7 +116,7 @@ object NotificationsPluginInterface { client.execute( DELETE_NOTIFICATION_CONFIG_ACTION_TYPE, request, - listener + wrapActionListener(listener) { response -> recreateObject(response) { DeleteNotificationConfigResponse(it) } } ) } @@ -130,7 +134,7 @@ object NotificationsPluginInterface { client.execute( GET_NOTIFICATION_CONFIG_ACTION_TYPE, request, - listener + wrapActionListener(listener) { response -> recreateObject(response) { GetNotificationConfigResponse(it) } } ) } @@ -148,7 +152,7 @@ object NotificationsPluginInterface { client.execute( GET_NOTIFICATION_EVENT_ACTION_TYPE, request, - listener + wrapActionListener(listener) { response -> recreateObject(response) { GetNotificationEventResponse(it) } } ) } @@ -166,7 +170,7 @@ object NotificationsPluginInterface { client.execute( GET_PLUGIN_FEATURES_ACTION_TYPE, request, - listener + wrapActionListener(listener) { response -> recreateObject(response) { GetPluginFeaturesResponse(it) } } ) } @@ -184,7 +188,7 @@ object NotificationsPluginInterface { client.execute( GET_FEATURE_CHANNEL_LIST_ACTION_TYPE, request, - listener + wrapActionListener(listener) { response -> recreateObject(response) { GetFeatureChannelListResponse(it) } } ) } @@ -209,7 +213,30 @@ object NotificationsPluginInterface { wrapper.execute( SEND_NOTIFICATION_ACTION_TYPE, SendNotificationRequest(eventSource, channelMessage, channelIds, threadContext), - listener + wrapActionListener(listener) { response -> recreateObject(response) { SendNotificationResponse(it) } } ) } + + /** + * Wrap action listener on concrete response class by a new created one on ActionResponse. + * This is required because the response may be loaded by different classloader across plugins. + * The onResponse(ActionResponse) avoids type cast exception and give a chance to recreate + * the response object. + */ + @Suppress("UNCHECKED_CAST") + private fun wrapActionListener( + listener: ActionListener, + recreate: (Writeable) -> Response + ): ActionListener { + return object : ActionListener { + override fun onResponse(response: ActionResponse) { + val recreated = response as? Response ?: recreate(response) + listener.onResponse(recreated) + } + + override fun onFailure(exception: java.lang.Exception) { + listener.onFailure(exception) + } + } as ActionListener + } } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt new file mode 100644 index 00000000..99f558f4 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt @@ -0,0 +1,268 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ +package org.opensearch.commons.notifications + +import com.nhaarman.mockitokotlin2.whenever +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.Answers +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.doAnswer +import org.mockito.Mockito.mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.junit.jupiter.MockitoExtension +import org.opensearch.action.ActionListener +import org.opensearch.action.ActionType +import org.opensearch.client.node.NodeClient +import org.opensearch.commons.notifications.action.CreateNotificationConfigRequest +import org.opensearch.commons.notifications.action.CreateNotificationConfigResponse +import org.opensearch.commons.notifications.action.DeleteNotificationConfigRequest +import org.opensearch.commons.notifications.action.DeleteNotificationConfigResponse +import org.opensearch.commons.notifications.action.GetFeatureChannelListRequest +import org.opensearch.commons.notifications.action.GetFeatureChannelListResponse +import org.opensearch.commons.notifications.action.GetNotificationConfigRequest +import org.opensearch.commons.notifications.action.GetNotificationConfigResponse +import org.opensearch.commons.notifications.action.GetNotificationEventRequest +import org.opensearch.commons.notifications.action.GetNotificationEventResponse +import org.opensearch.commons.notifications.action.GetPluginFeaturesRequest +import org.opensearch.commons.notifications.action.GetPluginFeaturesResponse +import org.opensearch.commons.notifications.action.SendNotificationResponse +import org.opensearch.commons.notifications.action.UpdateNotificationConfigRequest +import org.opensearch.commons.notifications.action.UpdateNotificationConfigResponse +import org.opensearch.commons.notifications.model.ChannelMessage +import org.opensearch.commons.notifications.model.ConfigType +import org.opensearch.commons.notifications.model.DeliveryStatus +import org.opensearch.commons.notifications.model.EventSource +import org.opensearch.commons.notifications.model.EventStatus +import org.opensearch.commons.notifications.model.Feature +import org.opensearch.commons.notifications.model.FeatureChannel +import org.opensearch.commons.notifications.model.FeatureChannelList +import org.opensearch.commons.notifications.model.NotificationConfig +import org.opensearch.commons.notifications.model.NotificationConfigInfo +import org.opensearch.commons.notifications.model.NotificationConfigSearchResult +import org.opensearch.commons.notifications.model.NotificationEvent +import org.opensearch.commons.notifications.model.NotificationEventInfo +import org.opensearch.commons.notifications.model.NotificationEventSearchResult +import org.opensearch.commons.notifications.model.SeverityType +import org.opensearch.commons.notifications.model.Slack +import org.opensearch.rest.RestStatus +import java.time.Instant +import java.util.EnumSet + +@Suppress("UNCHECKED_CAST") +@ExtendWith(MockitoExtension::class) +internal class NotificationsPluginInterfaceTests { + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private lateinit var client: NodeClient + + @Test + fun createNotificationConfig() { + val request = mock(CreateNotificationConfigRequest::class.java) + val response = CreateNotificationConfigResponse("configId") + val listener: ActionListener = + mock(ActionListener::class.java) as ActionListener + + doAnswer { + (it.getArgument(2) as ActionListener) + .onResponse(response) + }.whenever(client).execute(any(ActionType::class.java), any(), any()) + + NotificationsPluginInterface.createNotificationConfig(client, request, listener) + verify(listener, times(1)).onResponse(eq(response)) + } + + @Test + fun updateNotificationConfig() { + val request = mock(UpdateNotificationConfigRequest::class.java) + val response = UpdateNotificationConfigResponse("configId") + val listener: ActionListener = + mock(ActionListener::class.java) as ActionListener + + doAnswer { + (it.getArgument(2) as ActionListener) + .onResponse(response) + }.whenever(client).execute(any(ActionType::class.java), any(), any()) + + NotificationsPluginInterface.updateNotificationConfig(client, request, listener) + verify(listener, times(1)).onResponse(eq(response)) + } + + @Test + fun deleteNotificationConfig() { + val request = mock(DeleteNotificationConfigRequest::class.java) + val response = DeleteNotificationConfigResponse(mapOf(Pair("sample_config_id", RestStatus.OK))) + val listener: ActionListener = + mock(ActionListener::class.java) as ActionListener + + doAnswer { + (it.getArgument(2) as ActionListener) + .onResponse(response) + }.whenever(client).execute(any(ActionType::class.java), any(), any()) + + NotificationsPluginInterface.deleteNotificationConfig(client, request, listener) + verify(listener, times(1)).onResponse(eq(response)) + } + + @Test + fun getNotificationConfig() { + val request = mock(GetNotificationConfigRequest::class.java) + val response = mockGetNotificationConfigResponse() + val listener: ActionListener = + mock(ActionListener::class.java) as ActionListener + + doAnswer { + (it.getArgument(2) as ActionListener) + .onResponse(response) + }.whenever(client).execute(any(ActionType::class.java), any(), any()) + + NotificationsPluginInterface.getNotificationConfig(client, request, listener) + verify(listener, times(1)).onResponse(eq(response)) + } + + @Test + fun getNotificationEvent() { + val request = mock(GetNotificationEventRequest::class.java) + val response = mockGetNotificationEventResponse() + val listener: ActionListener = + mock(ActionListener::class.java) as ActionListener + + doAnswer { + (it.getArgument(2) as ActionListener) + .onResponse(response) + }.whenever(client).execute(any(ActionType::class.java), any(), any()) + + NotificationsPluginInterface.getNotificationEvent(client, request, listener) + verify(listener, times(1)).onResponse(eq(response)) + } + + @Test + fun getPluginFeatures() { + val request = mock(GetPluginFeaturesRequest::class.java) + val response = GetPluginFeaturesResponse( + listOf("config_type_1", "config_type_2", "config_type_3"), + mapOf( + Pair("FeatureKey1", "FeatureValue1"), + Pair("FeatureKey2", "FeatureValue2"), + Pair("FeatureKey3", "FeatureValue3") + ) + ) + val listener: ActionListener = + mock(ActionListener::class.java) as ActionListener + + doAnswer { + (it.getArgument(2) as ActionListener) + .onResponse(response) + }.whenever(client).execute(any(ActionType::class.java), any(), any()) + + NotificationsPluginInterface.getPluginFeatures(client, request, listener) + verify(listener, times(1)).onResponse(eq(response)) + } + + @Test + fun getFeatureChannelList() { + val sampleConfig = FeatureChannel( + "config_id", + "name", + "description", + ConfigType.SLACK + ) + + val request = mock(GetFeatureChannelListRequest::class.java) + val response = GetFeatureChannelListResponse(FeatureChannelList(sampleConfig)) + val listener: ActionListener = + mock(ActionListener::class.java) as ActionListener + + doAnswer { + (it.getArgument(2) as ActionListener) + .onResponse(response) + }.whenever(client).execute(any(ActionType::class.java), any(), any()) + + NotificationsPluginInterface.getFeatureChannelList(client, request, listener) + verify(listener, times(1)).onResponse(eq(response)) + } + + @Test + fun sendNotification() { + val notificationInfo = EventSource( + "title", + "reference_id", + Feature.REPORTS, + SeverityType.HIGH, + listOf("tag1", "tag2") + ) + val channelMessage = ChannelMessage( + "text_description", + "htmlDescription", + null + ) + + val response = SendNotificationResponse("configId") + val listener: ActionListener = + mock(ActionListener::class.java) as ActionListener + + doAnswer { + (it.getArgument(2) as ActionListener) + .onResponse(response) + }.whenever(client).execute(any(ActionType::class.java), any(), any()) + + NotificationsPluginInterface.sendNotification( + client, notificationInfo, channelMessage, listOf("channelId1", "channelId2"), listener + ) + verify(listener, times(1)).onResponse(eq(response)) + } + + private fun mockGetNotificationConfigResponse(): GetNotificationConfigResponse { + val sampleSlack = Slack("https://domain.com/sample_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.REPORTS), + configData = sampleSlack + ) + val configInfo = NotificationConfigInfo( + "config_id", + Instant.now(), + Instant.now(), + "tenant", + sampleConfig + ) + return GetNotificationConfigResponse(NotificationConfigSearchResult(configInfo)) + } + + private fun mockGetNotificationEventResponse(): GetNotificationEventResponse { + val sampleEventSource = EventSource( + "title", + "reference_id", + Feature.ALERTING, + severity = SeverityType.INFO + ) + val sampleStatus = EventStatus( + "config_id", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("404", "invalid recipient") + ) + val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) + val eventInfo = NotificationEventInfo( + "event_id", + Instant.now(), + Instant.now(), + "tenant", + sampleEvent + ) + return GetNotificationEventResponse(NotificationEventSearchResult(eventInfo)) + } +} diff --git a/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 00000000..ca6ee9ce --- /dev/null +++ b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file From 0af72bffc3b4ff17d8acd588dbfe298602c7448e Mon Sep 17 00:00:00 2001 From: Zhongnan Su Date: Mon, 2 Aug 2021 10:34:23 -0700 Subject: [PATCH 004/149] remove restrictions on webhook url protocols to also include http (#43) --- .../org/opensearch/commons/utils/ValidationHelpers.kt | 6 +----- .../opensearch/commons/notifications/model/ChimeTests.kt | 6 +++--- .../opensearch/commons/notifications/model/SlackTests.kt | 6 +++--- .../commons/notifications/model/WebhookTests.kt | 8 ++++---- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt b/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt index 93bcdc77..0124179f 100644 --- a/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt +++ b/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt @@ -34,9 +34,6 @@ private val VALID_ID_CHARS: Set = (('a'..'z') + ('A'..'Z') + ('0'..'9') + fun validateUrl(urlString: String) { require(isValidUrl(urlString)) { "Invalid URL or unsupported" } - val url = URL(urlString) - require("https" == url.protocol) // Support only HTTPS. HTTP and other protocols not supported - // TODO : Add hosts deny list } fun validateEmail(email: String) { @@ -49,8 +46,7 @@ fun validateId(idString: String) { fun isValidUrl(urlString: String): Boolean { val url = URL(urlString) // throws MalformedURLException if URL is invalid - // TODO : Add hosts deny list - return ("https" == url.protocol) // Support only HTTPS. HTTP and other protocols not supported + return ("https" == url.protocol || "http" == url.protocol) // Support only http/https, other protocols not supported } /** diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/ChimeTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/ChimeTests.kt index f70dc097..da404b28 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/ChimeTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/ChimeTests.kt @@ -89,11 +89,11 @@ internal class ChimeTests { } @Test - fun `Chime should throw exception when url protocol is not https`() { + fun `Chime should throw exception when url protocol is not https or http`() { assertThrows { - Chime("http://domain.com/sample_url#1234567890") + Chime("ftp://domain.com/sample_url#1234567890") } - val jsonString = "{\"url\":\"http://domain.com/sample_url\"}" + val jsonString = "{\"url\":\"ftp://domain.com/sample_url\"}" assertThrows { createObjectFromJsonString(jsonString) { Chime.parse(it) } } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/SlackTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/SlackTests.kt index b5bf7783..98fbc0cd 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/SlackTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/SlackTests.kt @@ -89,11 +89,11 @@ internal class SlackTests { } @Test - fun `Slack should throw exception when url protocol is not https`() { + fun `Slack should throw exception when url protocol is not https or http`() { assertThrows { - Slack("http://domain.com/sample_url#1234567890") + Slack("ftp://domain.com/sample_url#1234567890") } - val jsonString = "{\"url\":\"http://domain.com/sample_url\"}" + val jsonString = "{\"url\":\"ftp://domain.com/sample_url\"}" assertThrows { createObjectFromJsonString(jsonString) { Slack.parse(it) } } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/WebhookTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/WebhookTests.kt index 48dedf49..5a717669 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/WebhookTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/WebhookTests.kt @@ -47,7 +47,7 @@ internal class WebhookTests { @Test fun `Webhook serialize and deserialize using json object should be equal`() { val sampleWebhook = Webhook( - "https://domain.com/sample_url#1234567890", + "http://domain.com/sample_url#1234567890", mapOf(Pair("key", "value")), HttpMethodType.PUT ) @@ -105,11 +105,11 @@ internal class WebhookTests { } @Test - fun `Webhook should throw exception when url protocol is not https`() { + fun `Webhook should throw exception when url protocol is not https or http`() { assertThrows { - Webhook("http://domain.com/sample_url#1234567890") + Webhook("ftp://domain.com/sample_url#1234567890") } - val jsonString = "{\"url\":\"http://domain.com/sample_url\"}" + val jsonString = "{\"url\":\"ftp://domain.com/sample_url\"}" assertThrows { createObjectFromJsonString(jsonString) { Webhook.parse(it) } } From bf2bfb312776b840acc4d3c9f92922441d996f31 Mon Sep 17 00:00:00 2001 From: Sarat Vemulapalli Date: Fri, 6 Aug 2021 09:43:06 -0700 Subject: [PATCH 005/149] Bumping common-utils to build with OpenSearch(main) 1.1.0 (#48) Signed-off-by: Sarat Vemulapalli --- .github/workflows/ci.yml | 6 +++--- build.gradle | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f49c13b9..4f0adf94 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: with: repository: 'opensearch-project/OpenSearch' path: OpenSearch - ref: '1.0' + ref: 'main' - name: Build OpenSearch working-directory: ./OpenSearch run: ./gradlew publishToMavenLocal -Dbuild.snapshot=false @@ -39,11 +39,11 @@ jobs: # common-utils - name: Build and Test run: | - ./gradlew build -Dopensearch.version=1.0.0 + ./gradlew build -Dopensearch.version=1.1.0 - name: Publish to Maven Local run: | - ./gradlew publishToMavenLocal -Dopensearch.version=1.0.0 + ./gradlew publishToMavenLocal -Dopensearch.version=1.1.0 - name: Upload Coverage Report uses: codecov/codecov-action@v1 diff --git a/build.gradle b/build.gradle index 2d955951..0f2b241a 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "1.0.0") + opensearch_version = System.getProperty("opensearch.version", "1.1.0") kotlin_version = System.getProperty("kotlin.version", "1.4.32") } @@ -146,7 +146,7 @@ task javadocJar(type: Jar) { from javadoc.destinationDir } -version '1.0.0.0' +version '1.1.0.0' publishing { publications { From 28e688b76e4a4bf99312ae051576c405e3f4ce4f Mon Sep 17 00:00:00 2001 From: Ravi <6005951+thalurur@users.noreply.github.com> Date: Fri, 6 Aug 2021 09:47:39 -0700 Subject: [PATCH 006/149] Adding an utility method that allows consumers to set custom thread context property in InjectSecurity class (#47) Signed-off-by: Ravi Thaluru --- .../opensearch/commons/InjectSecurity.java | 17 ++++++ .../commons/InjectSecurityTest.java | 54 +++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/src/main/java/org/opensearch/commons/InjectSecurity.java b/src/main/java/org/opensearch/commons/InjectSecurity.java index ab4f0323..2240ab60 100644 --- a/src/main/java/org/opensearch/commons/InjectSecurity.java +++ b/src/main/java/org/opensearch/commons/InjectSecurity.java @@ -160,6 +160,23 @@ public void injectRoles(final List roles) { } } + /** + * Allows one to set the property in threadContext if possible to the value provided. If not possible returns false. + * @param property + * @param value + * @return boolean + */ + public boolean injectProperty(final String property, final Object value) { + if (Strings.isNullOrEmpty(property) || value == null || threadContext.getTransient(property) != null) { + log.debug("{}, InjectSecurity - cannot inject property: {}", Thread.currentThread().getName(), id); + return false; + } else { + threadContext.putTransient(property, value); + log.debug("{}, InjectSecurity - inject property: {}", Thread.currentThread().getName(), id); + return true; + } + } + @Override public void close() { if (ctx != null) { diff --git a/src/test/java/org/opensearch/commons/InjectSecurityTest.java b/src/test/java/org/opensearch/commons/InjectSecurityTest.java index 0f6e0c83..a9073d93 100644 --- a/src/test/java/org/opensearch/commons/InjectSecurityTest.java +++ b/src/test/java/org/opensearch/commons/InjectSecurityTest.java @@ -27,13 +27,16 @@ package org.opensearch.commons; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opensearch.commons.ConfigConstants.INJECTED_USER; import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_INJECTED_ROLES; import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_USE_INJECTED_USER_FOR_PLUGINS; import java.util.Arrays; +import java.util.Map; import org.junit.jupiter.api.Test; import org.opensearch.common.settings.Settings; @@ -102,4 +105,55 @@ public void testInjectUser() { assertEquals("plugin", threadContext.getTransient("ctx.name")); assertNull(threadContext.getTransient(INJECTED_USER)); } + + @Test + public void testInjectProperty() { + Settings settings = Settings.builder().put(OPENSEARCH_SECURITY_USE_INJECTED_USER_FOR_PLUGINS, false).build(); + Settings headerSettings = Settings.builder().put("request.headers.default", "1").build(); + ThreadContext threadContext = new ThreadContext(headerSettings); + threadContext.putHeader("name", "opendistro"); + threadContext.putTransient("ctx.name", "plugin"); + + assertEquals("1", threadContext.getHeader("default")); + assertEquals("opendistro", threadContext.getHeader("name")); + assertEquals("plugin", threadContext.getTransient("ctx.name")); + + try (InjectSecurity helper = new InjectSecurity("test-name", settings, threadContext)) { + helper.inject("joe", Arrays.asList("ops-role", "logs-role")); + assertEquals("1", threadContext.getHeader("default")); + assertEquals("opendistro", threadContext.getHeader("name")); + assertEquals("plugin", threadContext.getTransient("ctx.name")); + assertNotNull(threadContext.getTransient(OPENSEARCH_SECURITY_INJECTED_ROLES)); + // cannot inject property that is already set + assertFalse(helper.injectProperty(OPENSEARCH_SECURITY_INJECTED_ROLES, "new value")); + assertEquals("plugin|ops-role,logs-role", threadContext.getTransient(OPENSEARCH_SECURITY_INJECTED_ROLES)); + // cannot inject invalid property/value + assertFalse(helper.injectProperty("", "new value")); + assertFalse(helper.injectProperty(null, "new value")); + assertFalse(helper.injectProperty("property", null)); + // can inject non-set valid properties + assertTrue(helper.injectProperty("property1", true)); + assertTrue(helper.injectProperty("property2", "some value")); + assertTrue(helper.injectProperty("property3", "")); + assertTrue(helper.injectProperty("property4", Map.of("key", "value"))); + // verify the set properties are not null and equal to what was set + assertNull(threadContext.getTransient("property")); + assertNotNull(threadContext.getTransient("property1")); + assertEquals(true, threadContext.getTransient("property1")); + assertNotNull(threadContext.getTransient("property2")); + assertEquals("some value", threadContext.getTransient("property2")); + assertNotNull(threadContext.getTransient("property3")); + assertEquals("", threadContext.getTransient("property3")); + assertNotNull(threadContext.getTransient("property4")); + assertEquals(Map.of("key", "value"), threadContext.getTransient("property4")); + } + assertEquals("1", threadContext.getHeader("default")); + assertEquals("opendistro", threadContext.getHeader("name")); + assertEquals("plugin", threadContext.getTransient("ctx.name")); + assertNull(threadContext.getTransient(OPENSEARCH_SECURITY_INJECTED_ROLES)); + assertNull(threadContext.getTransient("property1")); + assertNull(threadContext.getTransient("property2")); + assertNull(threadContext.getTransient("property3")); + assertNull(threadContext.getTransient("property4")); + } } From b18a489dc7c61f1e3ce6de3eef8d0b9b9482b363 Mon Sep 17 00:00:00 2001 From: Joshua Date: Mon, 9 Aug 2021 15:00:19 -0700 Subject: [PATCH 007/149] Add Notifications SNS model (#50) --- .../notifications/NotificationConstants.kt | 2 + .../commons/notifications/model/ConfigType.kt | 5 + .../notifications/model/EventStatus.kt | 1 + .../commons/notifications/model/SNS.kt | 112 ++++++++++++++++++ .../model/config/ConfigDataProperties.kt | 3 + .../commons/utils/ValidationHelpers.kt | 6 + .../commons/notifications/model/SNSTests.kt | 105 ++++++++++++++++ 7 files changed, 234 insertions(+) create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/SNSTests.kt diff --git a/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt index 344cf0b8..04f3affe 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt @@ -42,6 +42,8 @@ object NotificationConstants { const val TAGS_TAG = "tags" const val URL_TAG = "url" const val HEADER_PARAMS_TAG = "header_params" + const val TOPIC_ARN_FIELD = "topic_arn" + const val ROLE_ARN_FIELD = "role_arn" const val HOST_TAG = "host" const val PORT_TAG = "port" const val METHOD_TAG = "method" diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt index 0dbe70a6..d5400a75 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt @@ -57,6 +57,11 @@ enum class ConfigType(val tag: String) { return tag } }, + SNS("sns") { + override fun toString(): String { + return tag + } + }, SMTP_ACCOUNT("smtp_account") { override fun toString(): String { return tag diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt index 1233a997..bf72836d 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt @@ -64,6 +64,7 @@ data class EventStatus( ConfigType.WEBHOOK -> requireNotNull(deliveryStatus) ConfigType.SLACK -> requireNotNull(deliveryStatus) ConfigType.EMAIL -> require(emailRecipientStatus.isNotEmpty()) + ConfigType.SNS -> requireNotNull(deliveryStatus) ConfigType.NONE -> log.info("Some config field not recognized") else -> { log.info("non-allowed config type for Status") diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt new file mode 100644 index 00000000..12cdba4f --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt @@ -0,0 +1,112 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ +package org.opensearch.commons.notifications.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.NotificationConstants.ROLE_ARN_FIELD +import org.opensearch.commons.notifications.NotificationConstants.TOPIC_ARN_FIELD +import org.opensearch.commons.utils.fieldIfNotNull +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.validateIAMRoleArn +import java.io.IOException +import java.util.regex.Pattern + +/** + * SNS notification data model + */ +data class SNS(val topicARN: String, val roleARN: String?) : BaseConfigData { + + init { + require(SNS_ARN_REGEX.matcher(topicARN).find()) { "Invalid AWS SNS topic ARN: $topicARN" } + if (roleARN != null) { + validateIAMRoleArn(roleARN) + } + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + return builder.startObject() + .field(TOPIC_ARN_FIELD, topicARN) + .fieldIfNotNull(ROLE_ARN_FIELD, roleARN) + .endObject() + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + topicARN = input.readString(), + roleARN = input.readOptionalString() + ) + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(topicARN) + out.writeOptionalString(roleARN) + } + + companion object { + private val log by logger(SNS::class.java) + + private val SNS_ARN_REGEX = + Pattern.compile("^arn:aws(-[^:]+)?:sns:([a-zA-Z0-9-]+):([0-9]{12}):([a-zA-Z0-9-_]+)$") + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { SNS(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + @JvmStatic + @Throws(IOException::class) + fun parse(xcp: XContentParser): SNS { + var topicARN: String? = null + var roleARN: String? = null + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + when (fieldName) { + TOPIC_ARN_FIELD -> topicARN = xcp.textOrNull() + ROLE_ARN_FIELD -> roleARN = xcp.textOrNull() + else -> { + xcp.skipChildren() + log.info("Unexpected field: $fieldName, while parsing SNS destination") + } + } + } + topicARN ?: throw IllegalArgumentException("$TOPIC_ARN_FIELD field absent") + return SNS(topicARN, roleARN) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): SNS? { + return if (sin.readBoolean()) { + SNS( + topicARN = sin.readString(), + roleARN = sin.readOptionalString() + ) + } else null + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt index 1500879c..34bd2edc 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt @@ -33,6 +33,7 @@ import org.opensearch.commons.notifications.model.Chime import org.opensearch.commons.notifications.model.ConfigType import org.opensearch.commons.notifications.model.Email import org.opensearch.commons.notifications.model.EmailGroup +import org.opensearch.commons.notifications.model.SNS import org.opensearch.commons.notifications.model.Slack import org.opensearch.commons.notifications.model.SmtpAccount import org.opensearch.commons.notifications.model.Webhook @@ -53,6 +54,7 @@ internal object ConfigDataProperties { Pair(ConfigType.CHIME, ConfigProperty(Chime.reader, Chime.xParser)), Pair(ConfigType.WEBHOOK, ConfigProperty(Webhook.reader, Webhook.xParser)), Pair(ConfigType.EMAIL, ConfigProperty(Email.reader, Email.xParser)), + Pair(ConfigType.SNS, ConfigProperty(SNS.reader, SNS.xParser)), Pair(ConfigType.EMAIL_GROUP, ConfigProperty(EmailGroup.reader, EmailGroup.xParser)), Pair(ConfigType.SMTP_ACCOUNT, ConfigProperty(SmtpAccount.reader, SmtpAccount.xParser)) ) @@ -78,6 +80,7 @@ internal object ConfigDataProperties { ConfigType.EMAIL_GROUP -> configData is EmailGroup ConfigType.SMTP_ACCOUNT -> configData is SmtpAccount ConfigType.CHIME -> configData is Chime + ConfigType.SNS -> configData is SNS ConfigType.NONE -> true } } diff --git a/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt b/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt index 0124179f..fc117923 100644 --- a/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt +++ b/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt @@ -28,6 +28,7 @@ package org.opensearch.commons.utils import java.net.URL +import java.util.regex.Pattern // Valid ID characters = (All Base64 chars + "_-") to support UUID format and Base64 encoded IDs private val VALID_ID_CHARS: Set = (('a'..'z') + ('A'..'Z') + ('0'..'9') + '+' + '/' + '_' + '-').toSet() @@ -69,3 +70,8 @@ fun isValidEmail(email: String): Boolean { fun isValidId(idString: String): Boolean { return idString.isNotBlank() && idString.all { VALID_ID_CHARS.contains(it) } } + +fun validateIAMRoleArn(roleARN: String) { + val roleArnRegex = Pattern.compile("^arn:aws(-[^:]+)?:iam::([0-9]{12}):([a-zA-Z_0-9+=,.@\\-_/]+)$") + require(roleArnRegex.matcher(roleARN).find()) { "Invalid AWS role ARN: $roleARN " } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/SNSTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/SNSTests.kt new file mode 100644 index 00000000..46d7cfc8 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/SNSTests.kt @@ -0,0 +1,105 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.commons.notifications.model + +import com.fasterxml.jackson.core.JsonParseException +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test +import org.opensearch.commons.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import org.opensearch.commons.utils.recreateObject + +internal class SNSTests { + + @Test + fun `SNS should throw exception if empty topic`() { + assertThrows(IllegalArgumentException::class.java) { + SNS("", null) + } + val jsonString = "{\"topic_arn\":\"\"}" + assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { SNS.parse(it) } + } + } + + @Test + fun `SNS should throw exception if invalid topic ARN`() { + assertThrows(IllegalArgumentException::class.java) { + SNS("arn:aws:es:us-east-1:012345678989:test", null) + } + val jsonString = "{\"topic_arn\":\"arn:aws:es:us-east-1:012345678989:test\"}" + assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { SNS.parse(it) } + } + } + + @Test + fun `SNS should throw exception if invalid role ARN`() { + assertThrows(IllegalArgumentException::class.java) { + SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam:us-east-1:0123456789:role-test") + } + val jsonString = + "{\"topic_arn\":\"arn:aws:sns:us-east-1:012345678912:topic-test\",\"role_arn\":\"arn:aws:iam:us-east-1:0123456789:role-test\"}" + assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { SNS.parse(it) } + } + } + + @Test + fun `SNS serialize and deserialize transport object should be equal`() { + val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") + val recreatedObject = recreateObject(sampleSNS) { SNS(it) } + Assertions.assertEquals(sampleSNS, recreatedObject) + } + + @Test + fun `SNS serialize and deserialize using json object should be equal`() { + val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") + val jsonString = getJsonString(sampleSNS) + val recreatedObject = createObjectFromJsonString(jsonString) { SNS.parse(it) } + Assertions.assertEquals(sampleSNS, recreatedObject) + } + + @Test + fun `SNS should deserialize json object using parser`() { + val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") + val jsonString = "{\"topic_arn\":\"${sampleSNS.topicARN}\",\"role_arn\":\"${sampleSNS.roleARN}\"}" + val recreatedObject = createObjectFromJsonString(jsonString) { SNS.parse(it) } + Assertions.assertEquals(sampleSNS, recreatedObject) + } + + @Test + fun `SNS should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows(JsonParseException::class.java) { + createObjectFromJsonString(jsonString) { SNS.parse(it) } + } + } + + @Test + fun `SNS should throw exception when arn is replace with arn2 in json object`() { + val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") + val jsonString = "{\"topic_arn2\":\"${sampleSNS.topicARN}\",\"role_arn\":\"${sampleSNS.roleARN}\"}" + assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { SNS.parse(it) } + } + } + + @Test + fun `SNS should safely ignore extra field in json object`() { + val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", null) + val jsonString = "{\"topic_arn\":\"${sampleSNS.topicARN}\", \"another\":\"field\"}" + val recreatedObject = createObjectFromJsonString(jsonString) { SNS.parse(it) } + Assertions.assertEquals(sampleSNS, recreatedObject) + } +} From d79c53dbf4a49a83dc5e36e4d4653dd66c250118 Mon Sep 17 00:00:00 2001 From: Anantha Krishna Bhatta <31894175+akbhatta@users.noreply.github.com> Date: Thu, 12 Aug 2021 17:00:19 -0700 Subject: [PATCH 008/149] Added support for SesAccount (#54) Also updated Feature to be simple string for future plugin easy addition [Tests] Added unit tests for SesAccount Updated Unit tests for changes Signed-off-by: @akbhatta --- .../notifications/NotificationConstants.kt | 9 +- .../action/GetFeatureChannelListRequest.kt | 13 +- .../commons/notifications/model/ConfigType.kt | 5 + .../notifications/model/EventSource.kt | 12 +- .../commons/notifications/model/Feature.kt | 71 -------- .../notifications/model/NotificationConfig.kt | 15 +- .../commons/notifications/model/SesAccount.kt | 124 ++++++++++++++ .../notifications/model/{SNS.kt => Sns.kt} | 55 +++---- .../model/config/ConfigDataProperties.kt | 9 +- .../commons/utils/ValidationHelpers.kt | 4 +- .../NotificationsPluginInterfaceTests.kt | 10 +- .../CreateNotificationConfigRequestTests.kt | 29 ++-- .../GetFeatureChannelListRequestTests.kt | 10 +- .../GetNotificationConfigResponseTests.kt | 20 +-- .../GetNotificationEventResponseTests.kt | 19 +-- .../action/SendNotificationRequestTests.kt | 12 +- .../UpdateNotificationConfigRequestTests.kt | 29 ++-- .../notifications/model/EventSourceTests.kt | 13 +- .../notifications/model/FeatureTests.kt | 56 ------- .../model/NotificationConfigInfoTests.kt | 13 +- .../NotificationConfigSearchResultsTests.kt | 23 +-- .../model/NotificationConfigTests.kt | 34 ++-- .../model/NotificationEventInfoTests.kt | 11 +- .../NotificationEventSearchResultTests.kt | 22 +-- .../model/NotificationEventTests.kt | 8 +- .../notifications/model/SesAccountTests.kt | 151 ++++++++++++++++++ .../model/{SNSTests.kt => SnsTests.kt} | 52 +++--- 27 files changed, 496 insertions(+), 333 deletions(-) delete mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/Feature.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt rename src/main/kotlin/org/opensearch/commons/notifications/model/{SNS.kt => Sns.kt} (63%) delete mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/FeatureTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/SesAccountTests.kt rename src/test/kotlin/org/opensearch/commons/notifications/model/{SNSTests.kt => SnsTests.kt} (65%) diff --git a/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt index 04f3affe..55dc9a44 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt @@ -42,8 +42,9 @@ object NotificationConstants { const val TAGS_TAG = "tags" const val URL_TAG = "url" const val HEADER_PARAMS_TAG = "header_params" - const val TOPIC_ARN_FIELD = "topic_arn" - const val ROLE_ARN_FIELD = "role_arn" + const val TOPIC_ARN_TAG = "topic_arn" + const val ROLE_ARN_TAG = "role_arn" + const val REGION_TAG = "region" const val HOST_TAG = "host" const val PORT_TAG = "port" const val METHOD_TAG = "method" @@ -69,5 +70,9 @@ object NotificationConstants { const val CONFIG_TYPE_LIST_TAG = "config_type_list" const val PLUGIN_FEATURES_TAG = "plugin_features" + const val FEATURE_ALERTING = "alerting" + const val FEATURE_INDEX_MANAGEMENT = "index_management" + const val FEATURE_REPORTS = "reports" + const val DEFAULT_MAX_ITEMS = 1000 } diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt index 17e4f6fb..309a31bc 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt @@ -37,7 +37,6 @@ import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.FEATURE_TAG -import org.opensearch.commons.notifications.model.Feature import org.opensearch.commons.utils.logger import java.io.IOException @@ -48,7 +47,7 @@ import java.io.IOException * Hence the request also contains tenant info for space isolation. */ class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { - val feature: Feature + val feature: String companion object { private val log by logger(GetFeatureChannelListRequest::class.java) @@ -65,7 +64,7 @@ class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { @JvmStatic @Throws(IOException::class) fun parse(parser: XContentParser): GetFeatureChannelListRequest { - var feature: Feature? = null + var feature: String? = null XContentParserUtils.ensureExpectedToken( XContentParser.Token.START_OBJECT, @@ -76,7 +75,7 @@ class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { val fieldName = parser.currentName() parser.nextToken() when (fieldName) { - FEATURE_TAG -> feature = Feature.fromTagOrDefault(parser.text()) + FEATURE_TAG -> feature = parser.text() else -> { parser.skipChildren() log.info("Unexpected field: $fieldName, while parsing GetFeatureChannelListRequest") @@ -92,7 +91,7 @@ class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { * constructor for creating the class * @param feature the caller plugin feature */ - constructor(feature: Feature) { + constructor(feature: String) { this.feature = feature } @@ -101,7 +100,7 @@ class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { */ @Throws(IOException::class) constructor(input: StreamInput) : super(input) { - feature = input.readEnum(Feature::class.java) + feature = input.readString() } /** @@ -110,7 +109,7 @@ class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { @Throws(IOException::class) override fun writeTo(output: StreamOutput) { super.writeTo(output) - output.writeEnum(feature) + output.writeString(feature) } /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt index d5400a75..85c10e35 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt @@ -62,6 +62,11 @@ enum class ConfigType(val tag: String) { return tag } }, + SES_ACCOUNT("ses_account") { + override fun toString(): String { + return tag + } + }, SMTP_ACCOUNT("smtp_account") { override fun toString(): String { return tag diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt index caf86dcd..98c46d2a 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt @@ -49,7 +49,7 @@ import java.io.IOException data class EventSource( val title: String, val referenceId: String, - val feature: Feature, + val feature: String, val severity: SeverityType = SeverityType.INFO, val tags: List = listOf() ) : BaseModel { @@ -75,7 +75,7 @@ data class EventSource( fun parse(parser: XContentParser): EventSource { var title: String? = null var referenceId: String? = null - var feature: Feature? = null + var feature: String? = null var severity: SeverityType = SeverityType.INFO var tags: List = emptyList() @@ -90,7 +90,7 @@ data class EventSource( when (fieldName) { TITLE_TAG -> title = parser.text() REFERENCE_ID_TAG -> referenceId = parser.text() - FEATURE_TAG -> feature = Feature.fromTagOrDefault(parser.text()) + FEATURE_TAG -> feature = parser.text() SEVERITY_TAG -> severity = SeverityType.fromTagOrDefault(parser.text()) TAGS_TAG -> tags = parser.stringList() else -> { @@ -121,7 +121,7 @@ data class EventSource( return builder.startObject() .field(TITLE_TAG, title) .field(REFERENCE_ID_TAG, referenceId) - .field(FEATURE_TAG, feature.tag) + .field(FEATURE_TAG, feature) .field(SEVERITY_TAG, severity.tag) .field(TAGS_TAG, tags) .endObject() @@ -134,7 +134,7 @@ data class EventSource( constructor(input: StreamInput) : this( title = input.readString(), referenceId = input.readString(), - feature = input.readEnum(Feature::class.java), + feature = input.readString(), severity = input.readEnum(SeverityType::class.java), tags = input.readStringList() ) @@ -145,7 +145,7 @@ data class EventSource( override fun writeTo(output: StreamOutput) { output.writeString(title) output.writeString(referenceId) - output.writeEnum(feature) + output.writeString(feature) output.writeEnum(severity) output.writeStringCollection(tags) } diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Feature.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Feature.kt deleted file mode 100644 index 84b37139..00000000 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Feature.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * - */ - -package org.opensearch.commons.notifications.model - -import org.opensearch.commons.utils.EnumParser - -/** - * Features using notification plugin - */ -enum class Feature(val tag: String) { - NONE("none") { - override fun toString(): String { - return tag - } - }, - ALERTING("alerting") { - override fun toString(): String { - return tag - } - }, - INDEX_MANAGEMENT("index_management") { - override fun toString(): String { - return tag - } - }, - REPORTS("reports") { - override fun toString(): String { - return tag - } - }; - - companion object { - private val tagMap = values().associateBy { it.tag } - - val enumParser = EnumParser { fromTagOrDefault(it) } - - /** - * Get Feature from tag or NONE if not found - * @param tag the tag - * @return Feature corresponding to tag. NONE if invalid tag. - */ - fun fromTagOrDefault(tag: String): Feature { - return tagMap[tag] ?: NONE - } - } -} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt index 30d72147..a0f7f5ad 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt @@ -42,11 +42,12 @@ import org.opensearch.commons.notifications.NotificationConstants.NAME_TAG import org.opensearch.commons.notifications.model.config.ConfigDataProperties.createConfigData import org.opensearch.commons.notifications.model.config.ConfigDataProperties.getReaderForConfigType import org.opensearch.commons.notifications.model.config.ConfigDataProperties.validateConfigData -import org.opensearch.commons.utils.enumSet +import org.opensearch.commons.utils.STRING_READER +import org.opensearch.commons.utils.STRING_WRITER import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.stringList import java.io.IOException -import java.util.EnumSet /** * Data class representing Notification config. @@ -55,7 +56,7 @@ data class NotificationConfig( val name: String, val description: String, val configType: ConfigType, - val features: EnumSet, + val features: Set, val configData: BaseConfigData?, val isEnabled: Boolean = true ) : BaseModel { @@ -89,7 +90,7 @@ data class NotificationConfig( var name: String? = null var description = "" var configType: ConfigType? = null - var features: EnumSet? = null + var features: Set? = null var isEnabled = true var configData: BaseConfigData? = null XContentParserUtils.ensureExpectedToken( @@ -104,7 +105,7 @@ data class NotificationConfig( NAME_TAG -> name = parser.text() DESCRIPTION_TAG -> description = parser.text() CONFIG_TYPE_TAG -> configType = ConfigType.fromTagOrDefault(parser.text()) - FEATURE_LIST_TAG -> features = parser.enumSet(Feature.enumParser) + FEATURE_LIST_TAG -> features = parser.stringList().toSet() IS_ENABLED_TAG -> isEnabled = parser.booleanValue() else -> { val configTypeForTag = ConfigType.fromTagOrDefault(fieldName) @@ -154,7 +155,7 @@ data class NotificationConfig( name = input.readString(), description = input.readString(), configType = input.readEnum(ConfigType::class.java), - features = input.readEnumSet(Feature::class.java), + features = input.readSet(STRING_READER), isEnabled = input.readBoolean(), configData = input.readOptionalWriteable(getReaderForConfigType(input.readEnum(ConfigType::class.java))) ) @@ -166,7 +167,7 @@ data class NotificationConfig( output.writeString(name) output.writeString(description) output.writeEnum(configType) - output.writeEnumSet(features) + output.writeCollection(features, STRING_WRITER) output.writeBoolean(isEnabled) // Reading config types multiple times in constructor output.writeEnum(configType) diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt new file mode 100644 index 00000000..d529b407 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt @@ -0,0 +1,124 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.commons.notifications.model + +import org.opensearch.common.Strings +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.NotificationConstants.FROM_ADDRESS_TAG +import org.opensearch.commons.notifications.NotificationConstants.REGION_TAG +import org.opensearch.commons.notifications.NotificationConstants.ROLE_ARN_TAG +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.validateEmail +import org.opensearch.commons.utils.validateIamRoleArn +import java.io.IOException + +/** + * Data class representing SES account channel. + */ +data class SesAccount( + val awsRegion: String, + val roleArn: String?, + val fromAddress: String +) : BaseConfigData { + + init { + require(!Strings.isNullOrEmpty(awsRegion)) { "awsRegion is null or empty" } + validateEmail(fromAddress) + if (roleArn != null) { + validateIamRoleArn(roleArn) + } + } + + companion object { + private val log by logger(SesAccount::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { SesAccount(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): SesAccount { + var awsRegion: String? = null + var roleArn: String? = null + var fromAddress: String? = null + + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + REGION_TAG -> awsRegion = parser.text() + ROLE_ARN_TAG -> roleArn = parser.text() + FROM_ADDRESS_TAG -> fromAddress = parser.text() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing SesAccount") + } + } + } + awsRegion ?: throw IllegalArgumentException("$REGION_TAG field absent") + fromAddress ?: throw IllegalArgumentException("$FROM_ADDRESS_TAG field absent") + return SesAccount( + awsRegion, + roleArn, + fromAddress + ) + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(REGION_TAG, awsRegion) + .field(ROLE_ARN_TAG, roleArn) + .field(FROM_ADDRESS_TAG, fromAddress) + .endObject() + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + awsRegion = input.readString(), + roleArn = input.readOptionalString(), + fromAddress = input.readString() + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(out: StreamOutput) { + out.writeString(awsRegion) + out.writeOptionalString(roleArn) + out.writeString(fromAddress) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt similarity index 63% rename from src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt rename to src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt index 12cdba4f..5e723926 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt @@ -17,30 +17,30 @@ import org.opensearch.common.xcontent.ToXContent import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils -import org.opensearch.commons.notifications.NotificationConstants.ROLE_ARN_FIELD -import org.opensearch.commons.notifications.NotificationConstants.TOPIC_ARN_FIELD +import org.opensearch.commons.notifications.NotificationConstants.ROLE_ARN_TAG +import org.opensearch.commons.notifications.NotificationConstants.TOPIC_ARN_TAG import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger -import org.opensearch.commons.utils.validateIAMRoleArn +import org.opensearch.commons.utils.validateIamRoleArn import java.io.IOException import java.util.regex.Pattern /** * SNS notification data model */ -data class SNS(val topicARN: String, val roleARN: String?) : BaseConfigData { +data class Sns(val topicArn: String, val roleArn: String?) : BaseConfigData { init { - require(SNS_ARN_REGEX.matcher(topicARN).find()) { "Invalid AWS SNS topic ARN: $topicARN" } - if (roleARN != null) { - validateIAMRoleArn(roleARN) + require(SNS_ARN_REGEX.matcher(topicArn).find()) { "Invalid AWS SNS topic ARN: $topicArn" } + if (roleArn != null) { + validateIamRoleArn(roleArn) } } override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { return builder.startObject() - .field(TOPIC_ARN_FIELD, topicARN) - .fieldIfNotNull(ROLE_ARN_FIELD, roleARN) + .field(TOPIC_ARN_TAG, topicArn) + .fieldIfNotNull(ROLE_ARN_TAG, roleArn) .endObject() } @@ -49,18 +49,18 @@ data class SNS(val topicARN: String, val roleARN: String?) : BaseConfigData { * @param input StreamInput stream to deserialize data from. */ constructor(input: StreamInput) : this( - topicARN = input.readString(), - roleARN = input.readOptionalString() + topicArn = input.readString(), + roleArn = input.readOptionalString() ) @Throws(IOException::class) override fun writeTo(out: StreamOutput) { - out.writeString(topicARN) - out.writeOptionalString(roleARN) + out.writeString(topicArn) + out.writeOptionalString(roleArn) } companion object { - private val log by logger(SNS::class.java) + private val log by logger(Sns::class.java) private val SNS_ARN_REGEX = Pattern.compile("^arn:aws(-[^:]+)?:sns:([a-zA-Z0-9-]+):([0-9]{12}):([a-zA-Z0-9-_]+)$") @@ -68,7 +68,7 @@ data class SNS(val topicARN: String, val roleARN: String?) : BaseConfigData { /** * reader to create instance of class from writable. */ - val reader = Writeable.Reader { SNS(it) } + val reader = Writeable.Reader { Sns(it) } /** * Parser to parse xContent @@ -77,36 +77,25 @@ data class SNS(val topicARN: String, val roleARN: String?) : BaseConfigData { @JvmStatic @Throws(IOException::class) - fun parse(xcp: XContentParser): SNS { - var topicARN: String? = null - var roleARN: String? = null + fun parse(xcp: XContentParser): Sns { + var topicArn: String? = null + var roleArn: String? = null XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { val fieldName = xcp.currentName() xcp.nextToken() when (fieldName) { - TOPIC_ARN_FIELD -> topicARN = xcp.textOrNull() - ROLE_ARN_FIELD -> roleARN = xcp.textOrNull() + TOPIC_ARN_TAG -> topicArn = xcp.textOrNull() + ROLE_ARN_TAG -> roleArn = xcp.textOrNull() else -> { xcp.skipChildren() log.info("Unexpected field: $fieldName, while parsing SNS destination") } } } - topicARN ?: throw IllegalArgumentException("$TOPIC_ARN_FIELD field absent") - return SNS(topicARN, roleARN) - } - - @JvmStatic - @Throws(IOException::class) - fun readFrom(sin: StreamInput): SNS? { - return if (sin.readBoolean()) { - SNS( - topicARN = sin.readString(), - roleARN = sin.readOptionalString() - ) - } else null + topicArn ?: throw IllegalArgumentException("$TOPIC_ARN_TAG field absent") + return Sns(topicArn, roleArn) } } } diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt index 34bd2edc..e6844934 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt @@ -33,9 +33,10 @@ import org.opensearch.commons.notifications.model.Chime import org.opensearch.commons.notifications.model.ConfigType import org.opensearch.commons.notifications.model.Email import org.opensearch.commons.notifications.model.EmailGroup -import org.opensearch.commons.notifications.model.SNS +import org.opensearch.commons.notifications.model.SesAccount import org.opensearch.commons.notifications.model.Slack import org.opensearch.commons.notifications.model.SmtpAccount +import org.opensearch.commons.notifications.model.Sns import org.opensearch.commons.notifications.model.Webhook import org.opensearch.commons.notifications.model.XParser @@ -54,7 +55,8 @@ internal object ConfigDataProperties { Pair(ConfigType.CHIME, ConfigProperty(Chime.reader, Chime.xParser)), Pair(ConfigType.WEBHOOK, ConfigProperty(Webhook.reader, Webhook.xParser)), Pair(ConfigType.EMAIL, ConfigProperty(Email.reader, Email.xParser)), - Pair(ConfigType.SNS, ConfigProperty(SNS.reader, SNS.xParser)), + Pair(ConfigType.SNS, ConfigProperty(Sns.reader, Sns.xParser)), + Pair(ConfigType.SES_ACCOUNT, ConfigProperty(SesAccount.reader, SesAccount.xParser)), Pair(ConfigType.EMAIL_GROUP, ConfigProperty(EmailGroup.reader, EmailGroup.xParser)), Pair(ConfigType.SMTP_ACCOUNT, ConfigProperty(SmtpAccount.reader, SmtpAccount.xParser)) ) @@ -80,7 +82,8 @@ internal object ConfigDataProperties { ConfigType.EMAIL_GROUP -> configData is EmailGroup ConfigType.SMTP_ACCOUNT -> configData is SmtpAccount ConfigType.CHIME -> configData is Chime - ConfigType.SNS -> configData is SNS + ConfigType.SNS -> configData is Sns + ConfigType.SES_ACCOUNT -> configData is SesAccount ConfigType.NONE -> true } } diff --git a/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt b/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt index fc117923..9f9082c4 100644 --- a/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt +++ b/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt @@ -71,7 +71,7 @@ fun isValidId(idString: String): Boolean { return idString.isNotBlank() && idString.all { VALID_ID_CHARS.contains(it) } } -fun validateIAMRoleArn(roleARN: String) { +fun validateIamRoleArn(roleArn: String) { val roleArnRegex = Pattern.compile("^arn:aws(-[^:]+)?:iam::([0-9]{12}):([a-zA-Z_0-9+=,.@\\-_/]+)$") - require(roleArnRegex.matcher(roleARN).find()) { "Invalid AWS role ARN: $roleARN " } + require(roleArnRegex.matcher(roleArn).find()) { "Invalid AWS role ARN: $roleArn " } } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt index 99f558f4..cf4f8420 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt @@ -25,6 +25,8 @@ import org.mockito.junit.jupiter.MockitoExtension import org.opensearch.action.ActionListener import org.opensearch.action.ActionType import org.opensearch.client.node.NodeClient +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.notifications.action.CreateNotificationConfigRequest import org.opensearch.commons.notifications.action.CreateNotificationConfigResponse import org.opensearch.commons.notifications.action.DeleteNotificationConfigRequest @@ -45,7 +47,6 @@ import org.opensearch.commons.notifications.model.ConfigType import org.opensearch.commons.notifications.model.DeliveryStatus import org.opensearch.commons.notifications.model.EventSource import org.opensearch.commons.notifications.model.EventStatus -import org.opensearch.commons.notifications.model.Feature import org.opensearch.commons.notifications.model.FeatureChannel import org.opensearch.commons.notifications.model.FeatureChannelList import org.opensearch.commons.notifications.model.NotificationConfig @@ -58,7 +59,6 @@ import org.opensearch.commons.notifications.model.SeverityType import org.opensearch.commons.notifications.model.Slack import org.opensearch.rest.RestStatus import java.time.Instant -import java.util.EnumSet @Suppress("UNCHECKED_CAST") @ExtendWith(MockitoExtension::class) @@ -198,7 +198,7 @@ internal class NotificationsPluginInterfaceTests { val notificationInfo = EventSource( "title", "reference_id", - Feature.REPORTS, + FEATURE_REPORTS, SeverityType.HIGH, listOf("tag1", "tag2") ) @@ -229,7 +229,7 @@ internal class NotificationsPluginInterfaceTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = sampleSlack ) val configInfo = NotificationConfigInfo( @@ -246,7 +246,7 @@ internal class NotificationsPluginInterfaceTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt index 679ded46..f190e7f6 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt @@ -31,11 +31,11 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT import org.opensearch.commons.notifications.model.Chime import org.opensearch.commons.notifications.model.ConfigType import org.opensearch.commons.notifications.model.Email import org.opensearch.commons.notifications.model.EmailGroup -import org.opensearch.commons.notifications.model.Feature import org.opensearch.commons.notifications.model.MethodType import org.opensearch.commons.notifications.model.NotificationConfig import org.opensearch.commons.notifications.model.Slack @@ -44,7 +44,6 @@ import org.opensearch.commons.notifications.model.Webhook import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject -import java.util.EnumSet internal class CreateNotificationConfigRequestTests { @@ -54,7 +53,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.WEBHOOK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleWebhook ) @@ -66,7 +65,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) @@ -78,7 +77,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleChime ) @@ -90,7 +89,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL_GROUP, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleEmailGroup ) @@ -106,7 +105,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleEmail ) @@ -123,7 +122,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.SMTP_ACCOUNT, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSmtpAccount ) @@ -286,7 +285,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) @@ -314,7 +313,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.WEBHOOK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleWebhook ) @@ -342,7 +341,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleChime ) @@ -371,7 +370,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL_GROUP, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleEmailGroup ) @@ -404,7 +403,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleEmail ) @@ -439,7 +438,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.SMTP_ACCOUNT, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSmtpAccount ) @@ -507,7 +506,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt index c9788a80..8c5c770f 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt @@ -30,7 +30,9 @@ import com.fasterxml.jackson.core.JsonParseException import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -import org.opensearch.commons.notifications.model.Feature +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject @@ -46,14 +48,14 @@ internal class GetFeatureChannelListRequestTests { @Test fun `Get request serialize and deserialize transport object should be equal`() { - val configRequest = GetFeatureChannelListRequest(Feature.REPORTS) + val configRequest = GetFeatureChannelListRequest(FEATURE_REPORTS) val recreatedObject = recreateObject(configRequest) { GetFeatureChannelListRequest(it) } assertGetRequestEquals(configRequest, recreatedObject) } @Test fun `Get request serialize and deserialize using json object should be equal`() { - val configRequest = GetFeatureChannelListRequest(Feature.INDEX_MANAGEMENT) + val configRequest = GetFeatureChannelListRequest(FEATURE_INDEX_MANAGEMENT) val jsonString = getJsonString(configRequest) val recreatedObject = createObjectFromJsonString(jsonString) { GetFeatureChannelListRequest.parse(it) } assertGetRequestEquals(configRequest, recreatedObject) @@ -69,7 +71,7 @@ internal class GetFeatureChannelListRequestTests { @Test fun `Get request should safely ignore extra field in json object`() { - val configRequest = GetFeatureChannelListRequest(Feature.ALERTING) + val configRequest = GetFeatureChannelListRequest(FEATURE_ALERTING) val jsonString = """ { "feature":"${configRequest.feature}", diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt index a2e14b40..2ec08913 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt @@ -30,9 +30,10 @@ import org.apache.lucene.search.TotalHits import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.notifications.model.Chime import org.opensearch.commons.notifications.model.ConfigType -import org.opensearch.commons.notifications.model.Feature import org.opensearch.commons.notifications.model.NotificationConfig import org.opensearch.commons.notifications.model.NotificationConfigInfo import org.opensearch.commons.notifications.model.NotificationConfigSearchResult @@ -41,7 +42,6 @@ import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject import java.time.Instant -import java.util.EnumSet internal class GetNotificationConfigResponseTests { @@ -63,7 +63,7 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = sampleSlack ) val configInfo = NotificationConfigInfo( @@ -85,7 +85,7 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = Slack("https://domain.com/sample_url#1234567890") ) val configInfo1 = NotificationConfigInfo( @@ -99,7 +99,7 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = Chime("https://domain.com/sample_url#1234567890") ) val configInfo2 = NotificationConfigInfo( @@ -129,7 +129,7 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = sampleSlack ) val configInfo = NotificationConfigInfo( @@ -154,7 +154,7 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = Slack("https://domain.com/sample_url#1234567890") ) val configInfo1 = NotificationConfigInfo( @@ -168,7 +168,7 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = Chime("https://domain.com/sample_url#1234567890") ) val configInfo2 = NotificationConfigInfo( @@ -199,7 +199,7 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) @@ -250,7 +250,7 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt index 4f7d72d1..1895d1d3 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt @@ -30,11 +30,12 @@ import org.apache.lucene.search.TotalHits import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.notifications.model.ConfigType import org.opensearch.commons.notifications.model.DeliveryStatus import org.opensearch.commons.notifications.model.EventSource import org.opensearch.commons.notifications.model.EventStatus -import org.opensearch.commons.notifications.model.Feature import org.opensearch.commons.notifications.model.NotificationEvent import org.opensearch.commons.notifications.model.NotificationEventInfo import org.opensearch.commons.notifications.model.NotificationEventSearchResult @@ -62,7 +63,7 @@ internal class GetNotificationEventResponseTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -90,13 +91,13 @@ internal class GetNotificationEventResponseTests { val eventSource1 = EventSource( "title 1", "reference_id_1", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val eventSource2 = EventSource( "title 2", "reference_id_2", - Feature.REPORTS, + FEATURE_REPORTS, severity = SeverityType.HIGH ) val status1 = EventStatus( @@ -157,7 +158,7 @@ internal class GetNotificationEventResponseTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -188,13 +189,13 @@ internal class GetNotificationEventResponseTests { val eventSource1 = EventSource( "title 1", "reference_id_1", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val eventSource2 = EventSource( "title 2", "reference_id_2", - Feature.REPORTS, + FEATURE_REPORTS, severity = SeverityType.HIGH ) val status1 = EventStatus( @@ -242,7 +243,7 @@ internal class GetNotificationEventResponseTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -310,7 +311,7 @@ internal class GetNotificationEventResponseTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequestTests.kt index 9c8bd2b4..31f6f549 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequestTests.kt @@ -32,9 +32,11 @@ import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.notifications.model.ChannelMessage import org.opensearch.commons.notifications.model.EventSource -import org.opensearch.commons.notifications.model.Feature import org.opensearch.commons.notifications.model.SeverityType import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString @@ -58,7 +60,7 @@ internal class SendNotificationRequestTests { val notificationInfo = EventSource( "title", "reference_id", - Feature.REPORTS, + FEATURE_REPORTS, SeverityType.HIGH, listOf("tag1", "tag2") ) @@ -82,7 +84,7 @@ internal class SendNotificationRequestTests { val notificationInfo = EventSource( "title", "reference_id", - Feature.INDEX_MANAGEMENT, + FEATURE_INDEX_MANAGEMENT, SeverityType.CRITICAL, listOf("tag1", "tag2") ) @@ -115,7 +117,7 @@ internal class SendNotificationRequestTests { val notificationInfo = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, SeverityType.HIGH, listOf("tag1", "tag2") ) @@ -159,7 +161,7 @@ internal class SendNotificationRequestTests { val notificationInfo = EventSource( "title", "reference_id", - Feature.REPORTS, + FEATURE_REPORTS, SeverityType.INFO, listOf("tag1", "tag2") ) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt index 607e8077..2dfcff69 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt @@ -31,11 +31,11 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT import org.opensearch.commons.notifications.model.Chime import org.opensearch.commons.notifications.model.ConfigType import org.opensearch.commons.notifications.model.Email import org.opensearch.commons.notifications.model.EmailGroup -import org.opensearch.commons.notifications.model.Feature import org.opensearch.commons.notifications.model.MethodType import org.opensearch.commons.notifications.model.NotificationConfig import org.opensearch.commons.notifications.model.Slack @@ -44,7 +44,6 @@ import org.opensearch.commons.notifications.model.Webhook import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject -import java.util.EnumSet internal class UpdateNotificationConfigRequestTests { @@ -54,7 +53,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.WEBHOOK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleWebhook ) @@ -66,7 +65,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) @@ -78,7 +77,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleChime ) @@ -90,7 +89,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL_GROUP, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleEmailGroup ) @@ -106,7 +105,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleEmail ) @@ -123,7 +122,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.SMTP_ACCOUNT, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSmtpAccount ) @@ -250,7 +249,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) @@ -280,7 +279,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.WEBHOOK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleWebhook ) @@ -310,7 +309,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleChime ) @@ -340,7 +339,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL_GROUP, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleEmailGroup ) @@ -374,7 +373,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleEmail ) @@ -410,7 +409,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.SMTP_ACCOUNT, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSmtpAccount ) @@ -448,7 +447,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/EventSourceTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/EventSourceTests.kt index 883c5047..6a87ed3f 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/EventSourceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/EventSourceTests.kt @@ -29,6 +29,7 @@ package org.opensearch.commons.notifications.model import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject @@ -40,7 +41,7 @@ internal class EventSourceTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val recreatedObject = recreateObject(sampleEventSource) { EventSource(it) } @@ -52,7 +53,7 @@ internal class EventSourceTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) @@ -66,7 +67,7 @@ internal class EventSourceTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, tags = listOf("tag1", "tag2"), severity = SeverityType.INFO ) @@ -87,11 +88,11 @@ internal class EventSourceTests { } @Test - fun `Event source should safely ignore unknown feature type in json object`() { + fun `Event source should safely accepts unknown feature type in json object`() { val sampleEventSource = EventSource( "title", "reference_id", - Feature.NONE, + "NewFeature", tags = listOf("tag1", "tag2"), severity = SeverityType.INFO ) @@ -114,7 +115,7 @@ internal class EventSourceTests { EventSource( "", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, tags = listOf("tag1", "tag2"), severity = SeverityType.INFO ) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureTests.kt deleted file mode 100644 index 215c2c2c..00000000 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureTests.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * - */ -package org.opensearch.commons.notifications.model - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.opensearch.commons.notifications.model.Feature.Companion.enumParser -import org.opensearch.commons.notifications.model.Feature.Companion.fromTagOrDefault - -internal class FeatureTests { - - @Test - fun `toString should return tag`() { - Feature.values().forEach { - assertEquals(it.tag, it.toString()) - } - } - - @Test - fun `fromTagOrDefault should return corresponding enum`() { - Feature.values().forEach { - assertEquals(it, fromTagOrDefault(it.tag)) - } - } - - @Test - fun `EnumParser fromTagOrDefault should return corresponding enum`() { - Feature.values().forEach { - assertEquals(it, enumParser.fromTagOrDefault(it.tag)) - } - } -} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt index 79c1035d..409decd7 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt @@ -29,11 +29,12 @@ package org.opensearch.commons.notifications.model import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject import java.time.Instant -import java.util.EnumSet internal class NotificationConfigInfoTests { @@ -44,7 +45,7 @@ internal class NotificationConfigInfoTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = sampleSlack ) val configInfo = NotificationConfigInfo( @@ -67,7 +68,7 @@ internal class NotificationConfigInfoTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = sampleSlack ) val configInfo = NotificationConfigInfo( @@ -91,7 +92,7 @@ internal class NotificationConfigInfoTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) @@ -130,7 +131,7 @@ internal class NotificationConfigInfoTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) @@ -171,7 +172,7 @@ internal class NotificationConfigInfoTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = sampleSlack ) Assertions.assertThrows(IllegalArgumentException::class.java) { diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt index a9543533..6a10935f 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt @@ -30,11 +30,12 @@ import org.apache.lucene.search.TotalHits import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject import java.time.Instant -import java.util.EnumSet internal class NotificationConfigSearchResultsTests { @@ -56,7 +57,7 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = sampleSlack ) val configInfo = NotificationConfigInfo( @@ -77,7 +78,7 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = Slack("https://domain.com/sample_url#1234567890") ) val configInfo1 = NotificationConfigInfo( @@ -91,7 +92,7 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = Chime("https://domain.com/sample_url#1234567890") ) val configInfo2 = NotificationConfigInfo( @@ -118,7 +119,7 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = Slack("https://domain.com/sample_url#1234567890") ) val configInfo1 = NotificationConfigInfo( @@ -132,7 +133,7 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = Chime("https://domain.com/sample_url#1234567890") ) val configInfo2 = NotificationConfigInfo( @@ -161,7 +162,7 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = sampleSlack ) val configInfo = NotificationConfigInfo( @@ -185,7 +186,7 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = Slack("https://domain.com/sample_url#1234567890") ) val configInfo1 = NotificationConfigInfo( @@ -199,7 +200,7 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = Chime("https://domain.com/sample_url#1234567890") ) val configInfo2 = NotificationConfigInfo( @@ -229,7 +230,7 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) @@ -280,7 +281,7 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt index ab45e2fc..c7126ab0 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt @@ -28,10 +28,12 @@ package org.opensearch.commons.notifications.model import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject -import java.util.EnumSet internal class NotificationConfigTests { @@ -42,7 +44,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = sampleSlack ) val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } @@ -56,7 +58,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = sampleSlack ) val jsonString = getJsonString(sampleConfig) @@ -71,7 +73,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.ALERTING), + setOf(FEATURE_ALERTING), configData = sampleChime ) val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } @@ -85,7 +87,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.ALERTING), + setOf(FEATURE_ALERTING), configData = sampleChime ) val jsonString = getJsonString(sampleConfig) @@ -100,7 +102,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.WEBHOOK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = sampleWebhook ) val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } @@ -114,7 +116,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.WEBHOOK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = sampleWebhook ) val jsonString = getJsonString(sampleConfig) @@ -129,7 +131,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.EMAIL, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = sampleEmail ) val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } @@ -143,7 +145,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.EMAIL, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = sampleEmail ) val jsonString = getJsonString(sampleConfig) @@ -158,7 +160,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.SMTP_ACCOUNT, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = smtpAccount ) val jsonString = getJsonString(sampleConfig) @@ -173,7 +175,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.SMTP_ACCOUNT, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = sampleSmtpAccount ) val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } @@ -187,7 +189,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.EMAIL_GROUP, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = sampleEmailGroup ) val jsonString = getJsonString(sampleConfig) @@ -202,7 +204,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.EMAIL_GROUP, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = sampleEmailGroup ) val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } @@ -217,7 +219,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.NONE, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) @@ -240,13 +242,13 @@ internal class NotificationConfigTests { } @Test - fun `Config should safely ignore unknown feature type in json object`() { + fun `Config should safely accepts unknown feature type in json object`() { val sampleWebhook = Webhook("https://domain.com/sample_webhook_url#1234567890") val sampleConfig = NotificationConfig( "name", "description", ConfigType.WEBHOOK, - EnumSet.of(Feature.INDEX_MANAGEMENT, Feature.NONE), + setOf(FEATURE_INDEX_MANAGEMENT, "NewFeature1", "NewFeature2"), isEnabled = true, configData = sampleWebhook ) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt index e23ca1e0..9f45fbb0 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt @@ -29,6 +29,7 @@ package org.opensearch.commons.notifications.model import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject @@ -41,7 +42,7 @@ internal class NotificationEventInfoTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -69,7 +70,7 @@ internal class NotificationEventInfoTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -98,7 +99,7 @@ internal class NotificationEventInfoTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -155,7 +156,7 @@ internal class NotificationEventInfoTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -215,7 +216,7 @@ internal class NotificationEventInfoTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt index d09fe9d2..078d4910 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt @@ -30,6 +30,8 @@ import org.apache.lucene.search.TotalHits import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject @@ -53,7 +55,7 @@ internal class NotificationEventSearchResultTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -80,13 +82,13 @@ internal class NotificationEventSearchResultTests { val eventSource1 = EventSource( "title 1", "reference_id_1", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val eventSource2 = EventSource( "title 2", "reference_id_2", - Feature.REPORTS, + FEATURE_REPORTS, severity = SeverityType.HIGH ) val status1 = EventStatus( @@ -147,13 +149,13 @@ internal class NotificationEventSearchResultTests { val eventSource1 = EventSource( "title 1", "reference_id_1", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val eventSource2 = EventSource( "title 2", "reference_id_2", - Feature.REPORTS, + FEATURE_REPORTS, severity = SeverityType.HIGH ) val status1 = EventStatus( @@ -213,7 +215,7 @@ internal class NotificationEventSearchResultTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -243,13 +245,13 @@ internal class NotificationEventSearchResultTests { val eventSource1 = EventSource( "title 1", "reference_id_1", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val eventSource2 = EventSource( "title 2", "reference_id_2", - Feature.REPORTS, + FEATURE_REPORTS, severity = SeverityType.HIGH ) val status1 = EventStatus( @@ -296,7 +298,7 @@ internal class NotificationEventSearchResultTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -364,7 +366,7 @@ internal class NotificationEventSearchResultTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventTests.kt index 4a135d0c..92c53fd8 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventTests.kt @@ -29,6 +29,8 @@ package org.opensearch.commons.notifications.model import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject @@ -40,7 +42,7 @@ internal class NotificationEventTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -59,7 +61,7 @@ internal class NotificationEventTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.REPORTS, + FEATURE_REPORTS, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -79,7 +81,7 @@ internal class NotificationEventTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, tags = listOf("tag1", "tag2"), severity = SeverityType.INFO ) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/SesAccountTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/SesAccountTests.kt new file mode 100644 index 00000000..c99506fd --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/SesAccountTests.kt @@ -0,0 +1,151 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.commons.notifications.model + +import com.fasterxml.jackson.core.JsonParseException +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.opensearch.commons.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import org.opensearch.commons.utils.recreateObject + +internal class SesAccountTests { + + @Test + fun `SES should throw exception if empty region`() { + assertThrows { + SesAccount("", null, "from@domain.com") + } + val jsonString = """ + { + "region":"", + "from_address":"from@domain.com" + } + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { SesAccount.parse(it) } + } + } + + @Test + fun `SES should throw exception if invalid role ARN`() { + assertThrows { + SesAccount("us-east-1", "arn:aws:iam:us-east-1:0123456789:role-test", "from@domain.com") + } + val jsonString = """ + { + "region":"us-east-1", + "role_arn":"arn:aws:iam:us-east-1:0123456789:role-test", + "from_address":"from@domain.com" + } + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { SesAccount.parse(it) } + } + } + + @Test + fun `SES should throw exception when email id is invalid`() { + val jsonString = """ + { + "region":"us-east-1", + "from_address":".from@domain.com" + } + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { SesAccount.parse(it) } + } + } + + @Test + fun `SES serialize and deserialize transport object should be equal`() { + val sesAccount = SesAccount("us-east-1", "arn:aws:iam::012345678912:role/iam-test", "from@domain.com") + val recreatedObject = recreateObject(sesAccount) { SesAccount(it) } + assertEquals(sesAccount, recreatedObject) + } + + @Test + fun `SES serialize and deserialize using json object should be equal`() { + val sesAccount = SesAccount("us-east-1", "arn:aws:iam::012345678912:role/iam-test", "from@domain.com") + val jsonString = getJsonString(sesAccount) + val recreatedObject = createObjectFromJsonString(jsonString) { SesAccount.parse(it) } + assertEquals(sesAccount, recreatedObject) + } + + @Test + fun `SES should deserialize json object using parser`() { + val sesAccount = SesAccount("us-east-1", "arn:aws:iam::012345678912:role/iam-test", "from@domain.com") + val jsonString = """ + { + "region":"${sesAccount.awsRegion}", + "role_arn":"${sesAccount.roleArn}", + "from_address":"${sesAccount.fromAddress}" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { SesAccount.parse(it) } + assertEquals(sesAccount, recreatedObject) + } + + @Test + fun `SES should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { SesAccount.parse(it) } + } + } + + @Test + fun `SES should throw exception when region is replace with region2 in json object`() { + val jsonString = """ + { + "region2":"us-east-1", + "role_arn":"arn:aws:iam::012345678912:role/iam-test", + "from_address":"from@domain.com" + } + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { SesAccount.parse(it) } + } + } + + @Test + fun `SES should throw exception when from_address is replace with from_address2 in json object`() { + val jsonString = """ + { + "region":"us-east-1", + "role_arn":"arn:aws:iam::012345678912:role/iam-test", + "from_address2":"from@domain.com" + } + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { SesAccount.parse(it) } + } + } + + @Test + fun `SES should safely ignore extra field in json object`() { + val sesAccount = SesAccount("us-east-1", "arn:aws:iam::012345678912:role/iam-test", "from@domain.com") + val jsonString = """ + { + "region":"${sesAccount.awsRegion}", + "role_arn":"${sesAccount.roleArn}", + "from_address":"${sesAccount.fromAddress}", + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { SesAccount.parse(it) } + assertEquals(sesAccount, recreatedObject) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/SNSTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/SnsTests.kt similarity index 65% rename from src/test/kotlin/org/opensearch/commons/notifications/model/SNSTests.kt rename to src/test/kotlin/org/opensearch/commons/notifications/model/SnsTests.kt index 46d7cfc8..03d494d2 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/SNSTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/SnsTests.kt @@ -19,87 +19,87 @@ import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject -internal class SNSTests { +internal class SnsTests { @Test fun `SNS should throw exception if empty topic`() { assertThrows(IllegalArgumentException::class.java) { - SNS("", null) + Sns("", null) } val jsonString = "{\"topic_arn\":\"\"}" assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { SNS.parse(it) } + createObjectFromJsonString(jsonString) { Sns.parse(it) } } } @Test fun `SNS should throw exception if invalid topic ARN`() { assertThrows(IllegalArgumentException::class.java) { - SNS("arn:aws:es:us-east-1:012345678989:test", null) + Sns("arn:aws:es:us-east-1:012345678989:test", null) } val jsonString = "{\"topic_arn\":\"arn:aws:es:us-east-1:012345678989:test\"}" assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { SNS.parse(it) } + createObjectFromJsonString(jsonString) { Sns.parse(it) } } } @Test fun `SNS should throw exception if invalid role ARN`() { assertThrows(IllegalArgumentException::class.java) { - SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam:us-east-1:0123456789:role-test") + Sns("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam:us-east-1:0123456789:role-test") } val jsonString = "{\"topic_arn\":\"arn:aws:sns:us-east-1:012345678912:topic-test\",\"role_arn\":\"arn:aws:iam:us-east-1:0123456789:role-test\"}" assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { SNS.parse(it) } + createObjectFromJsonString(jsonString) { Sns.parse(it) } } } @Test fun `SNS serialize and deserialize transport object should be equal`() { - val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") - val recreatedObject = recreateObject(sampleSNS) { SNS(it) } - Assertions.assertEquals(sampleSNS, recreatedObject) + val sampleSns = Sns("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") + val recreatedObject = recreateObject(sampleSns) { Sns(it) } + Assertions.assertEquals(sampleSns, recreatedObject) } @Test fun `SNS serialize and deserialize using json object should be equal`() { - val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") - val jsonString = getJsonString(sampleSNS) - val recreatedObject = createObjectFromJsonString(jsonString) { SNS.parse(it) } - Assertions.assertEquals(sampleSNS, recreatedObject) + val sampleSns = Sns("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") + val jsonString = getJsonString(sampleSns) + val recreatedObject = createObjectFromJsonString(jsonString) { Sns.parse(it) } + Assertions.assertEquals(sampleSns, recreatedObject) } @Test fun `SNS should deserialize json object using parser`() { - val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") - val jsonString = "{\"topic_arn\":\"${sampleSNS.topicARN}\",\"role_arn\":\"${sampleSNS.roleARN}\"}" - val recreatedObject = createObjectFromJsonString(jsonString) { SNS.parse(it) } - Assertions.assertEquals(sampleSNS, recreatedObject) + val sampleSns = Sns("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") + val jsonString = "{\"topic_arn\":\"${sampleSns.topicArn}\",\"role_arn\":\"${sampleSns.roleArn}\"}" + val recreatedObject = createObjectFromJsonString(jsonString) { Sns.parse(it) } + Assertions.assertEquals(sampleSns, recreatedObject) } @Test fun `SNS should throw exception when invalid json object is passed`() { val jsonString = "sample message" assertThrows(JsonParseException::class.java) { - createObjectFromJsonString(jsonString) { SNS.parse(it) } + createObjectFromJsonString(jsonString) { Sns.parse(it) } } } @Test fun `SNS should throw exception when arn is replace with arn2 in json object`() { - val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") - val jsonString = "{\"topic_arn2\":\"${sampleSNS.topicARN}\",\"role_arn\":\"${sampleSNS.roleARN}\"}" + val sampleSns = Sns("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") + val jsonString = "{\"topic_arn2\":\"${sampleSns.topicArn}\",\"role_arn\":\"${sampleSns.roleArn}\"}" assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { SNS.parse(it) } + createObjectFromJsonString(jsonString) { Sns.parse(it) } } } @Test fun `SNS should safely ignore extra field in json object`() { - val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", null) - val jsonString = "{\"topic_arn\":\"${sampleSNS.topicARN}\", \"another\":\"field\"}" - val recreatedObject = createObjectFromJsonString(jsonString) { SNS.parse(it) } - Assertions.assertEquals(sampleSNS, recreatedObject) + val sampleSns = Sns("arn:aws:sns:us-east-1:012345678912:topic-test", null) + val jsonString = "{\"topic_arn\":\"${sampleSns.topicArn}\", \"another\":\"field\"}" + val recreatedObject = createObjectFromJsonString(jsonString) { Sns.parse(it) } + Assertions.assertEquals(sampleSns, recreatedObject) } } From 4b1c21c325967852709376142aa71cd187ae0126 Mon Sep 17 00:00:00 2001 From: Drew Baugher <46505179+dbbaughe@users.noreply.github.com> Date: Thu, 12 Aug 2021 17:47:12 -0700 Subject: [PATCH 009/149] Adds legacy chime, slack, custom webhook messages, request/response f (#53) * Adds legacy chime, slack, custom webhook messages, request/response for publishing legacy notifications, and method for executing transport action Signed-off-by: Drew Baugher <46505179+dbbaughe@users.noreply.github.com> * Fixes import and removes username/password that is not used by ISM Signed-off-by: Drew Baugher <46505179+dbbaughe@users.noreply.github.com> * Throws error for toXContent for legacy notification response Signed-off-by: Drew Baugher <46505179+dbbaughe@users.noreply.github.com> * Renames legacy destination types to have legacy prefix Signed-off-by: Drew Baugher <46505179+dbbaughe@users.noreply.github.com> * Obfuscates message to remove from logs in toString method Signed-off-by: Drew Baugher <46505179+dbbaughe@users.noreply.github.com> * Makes destinationt type private and updates places to use getter Signed-off-by: Drew Baugher <46505179+dbbaughe@users.noreply.github.com> * Inlines destination type Signed-off-by: Drew Baugher <46505179+dbbaughe@users.noreply.github.com> * Makes base message content final Signed-off-by: Drew Baugher <46505179+dbbaughe@users.noreply.github.com> * Requires url to be defined in LegacyCustomWebhookMessage for use across transport wire and only writes the full url instead of each individual part Signed-off-by: Drew Baugher <46505179+dbbaughe@users.noreply.github.com> --- .../message/LegacyBaseMessage.java | 128 ++++++++ .../message/LegacyChimeMessage.java | 91 ++++++ .../message/LegacyCustomWebhookMessage.java | 274 ++++++++++++++++++ .../message/LegacyDestinationType.java | 36 +++ .../message/LegacySlackMessage.java | 95 ++++++ .../response/LegacyBaseResponse.java | 60 ++++ .../response/LegacyDestinationResponse.java | 82 ++++++ .../NotificationsPluginInterface.kt | 28 ++ .../LegacyPublishNotificationRequest.kt | 82 ++++++ .../LegacyPublishNotificationResponse.kt | 63 ++++ .../action/NotificationsActions.kt | 13 + .../message/LegacyChimeMessageTest.java | 100 +++++++ .../LegacyCustomWebhookMessageTest.java | 177 +++++++++++ .../message/LegacySlackMessageTest.java | 110 +++++++ .../LegacyDestinationResponseTest.java | 82 ++++++ .../NotificationsPluginInterfaceTests.kt | 24 ++ .../LegacyPublishNotificationRequestTests.kt | 42 +++ .../LegacyPublishNotificationResponseTests.kt | 29 ++ 18 files changed, 1516 insertions(+) create mode 100644 src/main/java/org/opensearch/commons/destination/message/LegacyBaseMessage.java create mode 100644 src/main/java/org/opensearch/commons/destination/message/LegacyChimeMessage.java create mode 100644 src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java create mode 100644 src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java create mode 100644 src/main/java/org/opensearch/commons/destination/message/LegacySlackMessage.java create mode 100644 src/main/java/org/opensearch/commons/destination/response/LegacyBaseResponse.java create mode 100644 src/main/java/org/opensearch/commons/destination/response/LegacyDestinationResponse.java create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponse.kt create mode 100644 src/test/java/org/opensearch/commons/destination/message/LegacyChimeMessageTest.java create mode 100644 src/test/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessageTest.java create mode 100644 src/test/java/org/opensearch/commons/destination/message/LegacySlackMessageTest.java create mode 100644 src/test/java/org/opensearch/commons/destination/response/LegacyDestinationResponseTest.java create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequestTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponseTests.kt diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyBaseMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacyBaseMessage.java new file mode 100644 index 00000000..b5122f31 --- /dev/null +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyBaseMessage.java @@ -0,0 +1,128 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.message; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; + +import org.apache.http.client.utils.URIBuilder; +import org.opensearch.common.Strings; +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.common.io.stream.Writeable; + +/** + * This class holds the generic parameters required for a + * message. + */ +public abstract class LegacyBaseMessage implements Writeable { + + private final LegacyDestinationType destinationType; + protected String destinationName; + protected String url; + private final String content; + + LegacyBaseMessage(final LegacyDestinationType destinationType, final String destinationName, final String content) { + if (destinationType == null) { + throw new IllegalArgumentException("Channel type must be defined"); + } + if (!Strings.hasLength(destinationName)) { + throw new IllegalArgumentException("Channel name must be defined"); + } + this.destinationType = destinationType; + this.destinationName = destinationName; + this.content = content; + } + + LegacyBaseMessage(final LegacyDestinationType destinationType, final String destinationName, final String content, final String url) { + this(destinationType, destinationName, content); + if (url == null) { + throw new IllegalArgumentException("url is invalid or empty"); + } + this.url = url; + } + + LegacyBaseMessage(StreamInput streamInput) throws IOException { + this.destinationType = streamInput.readEnum(LegacyDestinationType.class); + this.destinationName = streamInput.readString(); + this.url = streamInput.readOptionalString(); + this.content = streamInput.readString(); + } + + public void setUrl(String url) { + this.url = url; + } + + public LegacyDestinationType getChannelType() { + return destinationType; + } + + public String getChannelName() { + return destinationName; + } + + public String getMessageContent() { + return content; + } + + public String getUrl() { + return url; + } + + public URI getUri() { + return buildUri(getUrl().trim(), null, null, -1, null, null); + } + + protected URI buildUri(String endpoint, String scheme, String host, int port, String path, Map queryParams) { + try { + if (Strings.isNullOrEmpty(endpoint)) { + if (Strings.isNullOrEmpty(scheme)) { + scheme = "https"; + } + URIBuilder uriBuilder = new URIBuilder(); + if (queryParams != null) { + for (Map.Entry e : queryParams.entrySet()) + uriBuilder.addParameter(e.getKey(), e.getValue()); + } + return uriBuilder.setScheme(scheme).setHost(host).setPort(port).setPath(path).build(); + } + return new URIBuilder(endpoint).build(); + } catch (URISyntaxException exception) { + throw new IllegalStateException("Error creating URI"); + } + } + + @Override + public void writeTo(StreamOutput streamOutput) throws IOException { + streamOutput.writeEnum(destinationType); + streamOutput.writeString(destinationName); + streamOutput.writeOptionalString(url); + streamOutput.writeString(content); + } +} diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyChimeMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacyChimeMessage.java new file mode 100644 index 00000000..b457d9aa --- /dev/null +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyChimeMessage.java @@ -0,0 +1,91 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.message; + +import java.io.IOException; + +import org.opensearch.common.Strings; +import org.opensearch.common.io.stream.StreamInput; + +/** + * This class holds the contents of an Chime message + */ +public class LegacyChimeMessage extends LegacyBaseMessage { + private final String message; + + private LegacyChimeMessage(final String destinationName, final String url, final String message) { + super(LegacyDestinationType.LEGACY_CHIME, destinationName, message, url); + + if (Strings.isNullOrEmpty(message)) { + throw new IllegalArgumentException("Message content is missing"); + } + + this.message = message; + } + + public LegacyChimeMessage(StreamInput streamInput) throws IOException { + super(streamInput); + this.message = super.getMessageContent(); + } + + @Override + public String toString() { + return "DestinationType: " + getChannelType() + ", DestinationName:" + destinationName + ", Url: " + url + ", Message: <...>"; + } + + public static class Builder { + private String message; + private final String destinationName; + private String url; + + public Builder(String destinationName) { + this.destinationName = destinationName; + } + + public LegacyChimeMessage.Builder withMessage(String message) { + this.message = message; + return this; + } + + public LegacyChimeMessage.Builder withUrl(String url) { + this.url = url; + return this; + } + + public LegacyChimeMessage build() { + return new LegacyChimeMessage(this.destinationName, this.url, this.message); + } + } + + public String getMessage() { + return message; + } + + public String getUrl() { + return url; + } +} diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java new file mode 100644 index 00000000..9645b327 --- /dev/null +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java @@ -0,0 +1,274 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.message; + +import java.io.IOException; +import java.net.URI; +import java.util.Map; + +import org.apache.http.client.methods.HttpPatch; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.opensearch.common.Strings; +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; + +/** + * This class holds the content of an CustomWebhook message + */ +public class LegacyCustomWebhookMessage extends LegacyBaseMessage { + + private final String message; + private final String url; + private final String scheme; + private final String host; + private final String method; + private final int port; + private String path; + private final Map queryParams; + private Map headerParams; + + private LegacyCustomWebhookMessage( + final String destinationName, + final String url, + final String scheme, + final String host, + final Integer port, + final String path, + final String method, + final Map queryParams, + final Map headerParams, + final String message + ) { + super(LegacyDestinationType.LEGACY_CUSTOM_WEBHOOK, destinationName, message); + + if (!Strings.isNullOrEmpty(url)) { + setUrl(url.trim()); + } + + if (Strings.isNullOrEmpty(message)) { + throw new IllegalArgumentException("Message content is missing"); + } + + this.scheme = Strings.isNullOrEmpty(scheme) ? "https" : scheme; + this.port = port == null ? -1 : port; + + if (!Strings.isNullOrEmpty(path)) { + if (!path.startsWith("/")) { + this.path = "/" + path; + } + } + + if (Strings.isNullOrEmpty(url) && Strings.isNullOrEmpty(host)) { + throw new IllegalArgumentException("Either fully qualified URL or host name should be provided"); + } + + if (Strings.isNullOrEmpty(method)) { + // Default to POST for backwards compatibility + this.method = "POST"; + } else if (!HttpPost.METHOD_NAME.equals(method) && !HttpPut.METHOD_NAME.equals(method) && !HttpPatch.METHOD_NAME.equals(method)) { + throw new IllegalArgumentException("Invalid method supplied. Only POST, PUT and PATCH are allowed"); + } else { + this.method = method; + } + + this.message = message; + this.url = url; + this.host = host; + this.queryParams = queryParams; + this.headerParams = headerParams; + } + + public LegacyCustomWebhookMessage(StreamInput streamInput) throws IOException { + super(streamInput); + this.message = super.getMessageContent(); + this.url = streamInput.readOptionalString(); + this.scheme = null; + this.host = null; + this.method = streamInput.readOptionalString(); + this.port = -1; + this.path = null; + this.queryParams = null; + if (streamInput.readBoolean()) { + @SuppressWarnings("unchecked") + Map headerParams = (Map) (Map) streamInput.readMap(); + this.headerParams = headerParams; + } + } + + @Override + public String toString() { + return "DestinationType: " + + getChannelType() + + ", DestinationName:" + + destinationName + + ", Url: " + + url + + ", scheme: " + + scheme + + ", Host: " + + host + + ", Port: " + + port + + ", Path: " + + path + + ", Method: " + + method + + ", Message: <...>"; + } + + public static class Builder { + private String message; + private final String destinationName; + private String url; + private String scheme; + private String host; + private Integer port; + private String path; + private String method; + private Map queryParams; + private Map headerParams; + + public Builder(String destinationName) { + this.destinationName = destinationName; + } + + public LegacyCustomWebhookMessage.Builder withScheme(String scheme) { + this.scheme = scheme; + return this; + } + + public LegacyCustomWebhookMessage.Builder withHost(String host) { + this.host = host; + return this; + } + + public LegacyCustomWebhookMessage.Builder withPort(Integer port) { + this.port = port; + return this; + } + + public LegacyCustomWebhookMessage.Builder withPath(String path) { + this.path = path; + return this; + } + + public LegacyCustomWebhookMessage.Builder withMethod(String method) { + this.method = method; + return this; + } + + public LegacyCustomWebhookMessage.Builder withQueryParams(Map queryParams) { + this.queryParams = queryParams; + return this; + } + + public LegacyCustomWebhookMessage.Builder withHeaderParams(Map headerParams) { + this.headerParams = headerParams; + return this; + } + + public LegacyCustomWebhookMessage.Builder withMessage(String message) { + this.message = message; + return this; + } + + public LegacyCustomWebhookMessage.Builder withUrl(String url) { + this.url = url; + return this; + } + + public LegacyCustomWebhookMessage build() { + return new LegacyCustomWebhookMessage( + this.destinationName, + this.url, + this.scheme, + this.host, + this.port, + this.path, + this.method, + this.queryParams, + this.headerParams, + this.message + ); + } + } + + public String getScheme() { + return scheme; + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + public String getPath() { + return path; + } + + public String getMethod() { + return method; + } + + public Map getQueryParams() { + return queryParams; + } + + public Map getHeaderParams() { + return headerParams; + } + + public URI getUri() { + return buildUri(getUrl(), getScheme(), getHost(), getPort(), getPath(), getQueryParams()); + } + + public String getMessage() { + return message; + } + + @Override + public void writeTo(StreamOutput streamOutput) throws IOException { + super.writeTo(streamOutput); + // Making LegacyCustomWebhookMessage streamable is purely to support the new pass through API from ISM -> Notification plugin + // and it only supports LegacyCustomWebhookMessage when the url is already constructed by ISM. + if (Strings.isNullOrEmpty(getUrl())) { + throw new IllegalStateException("Cannot use LegacyCustomWebhookMessage across transport wire without defining full url."); + } + streamOutput.writeOptionalString(url); + streamOutput.writeOptionalString(method); + streamOutput.writeBoolean(headerParams != null); + if (headerParams != null) { + @SuppressWarnings("unchecked") + Map headerParams = (Map) (Map) this.headerParams; + streamOutput.writeMap(headerParams); + } + } +} diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java b/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java new file mode 100644 index 00000000..85689510 --- /dev/null +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.message; + +/** + * Supported legacy notification destinations for Index Management + */ +public enum LegacyDestinationType { + LEGACY_CHIME, + LEGACY_SLACK, + LEGACY_CUSTOM_WEBHOOK +} diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacySlackMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacySlackMessage.java new file mode 100644 index 00000000..f426894a --- /dev/null +++ b/src/main/java/org/opensearch/commons/destination/message/LegacySlackMessage.java @@ -0,0 +1,95 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.message; + +import java.io.IOException; + +import org.opensearch.common.Strings; +import org.opensearch.common.io.stream.StreamInput; + +/** + * This class holds the content of an Slack message + */ +public class LegacySlackMessage extends LegacyBaseMessage { + private final String message; + + private LegacySlackMessage(final String destinationName, final String url, final String message) { + super(LegacyDestinationType.LEGACY_SLACK, destinationName, message, url); + + if (Strings.isNullOrEmpty(url)) { // add URL validation + throw new IllegalArgumentException("Fully qualified URL is missing/invalid: " + url); + } + + if (Strings.isNullOrEmpty(message)) { + throw new IllegalArgumentException("Message content is missing"); + } + + this.message = message; + } + + public LegacySlackMessage(StreamInput streamInput) throws IOException { + super(streamInput); + this.message = super.getMessageContent(); + } + + @Override + public String toString() { + return "DestinationType: " + getChannelType() + ", DestinationName:" + destinationName + ", Url: " + url + ", Message: <...>"; + } + + public static class Builder { + private String message; + private String destinationName; + private String url; + + public Builder(String channelName) { + this.destinationName = channelName; + } + + public LegacySlackMessage.Builder withMessage(String message) { + this.message = message; + return this; + } + + public LegacySlackMessage.Builder withUrl(String url) { + this.url = url; + return this; + } + + public LegacySlackMessage build() { + return new LegacySlackMessage(this.destinationName, this.url, this.message); + } + } + + public String getMessage() { + return message; + } + + public String getUrl() { + return url; + } +} diff --git a/src/main/java/org/opensearch/commons/destination/response/LegacyBaseResponse.java b/src/main/java/org/opensearch/commons/destination/response/LegacyBaseResponse.java new file mode 100644 index 00000000..bbdd8d70 --- /dev/null +++ b/src/main/java/org/opensearch/commons/destination/response/LegacyBaseResponse.java @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.response; + +import java.io.IOException; + +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.common.io.stream.Writeable; + +/** + * This class holds the generic response attributes + */ +public abstract class LegacyBaseResponse implements Writeable { + protected Integer statusCode; + + LegacyBaseResponse(final Integer statusCode) { + if (statusCode == null) { + throw new IllegalArgumentException("status code is invalid"); + } + this.statusCode = statusCode; + } + + public LegacyBaseResponse(StreamInput streamInput) throws IOException { + this.statusCode = streamInput.readInt(); + } + + public int getStatusCode() { + return statusCode; + } + + @Override + public void writeTo(StreamOutput streamOutput) throws IOException { + streamOutput.writeInt(statusCode); + } +} diff --git a/src/main/java/org/opensearch/commons/destination/response/LegacyDestinationResponse.java b/src/main/java/org/opensearch/commons/destination/response/LegacyDestinationResponse.java new file mode 100644 index 00000000..bb927747 --- /dev/null +++ b/src/main/java/org/opensearch/commons/destination/response/LegacyDestinationResponse.java @@ -0,0 +1,82 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.response; + +import java.io.IOException; + +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; + +/** + * This class is a place holder for destination response metadata + */ +public class LegacyDestinationResponse extends LegacyBaseResponse { + + private final String responseContent; + + private LegacyDestinationResponse(final String responseString, final Integer statusCode) { + super(statusCode); + if (responseString == null) { + throw new IllegalArgumentException("Response is missing"); + } + this.responseContent = responseString; + } + + public LegacyDestinationResponse(StreamInput streamInput) throws IOException { + super(streamInput); + this.responseContent = streamInput.readString(); + } + + public static class Builder { + private String responseContent; + private Integer statusCode; + + public LegacyDestinationResponse.Builder withResponseContent(String responseContent) { + this.responseContent = responseContent; + return this; + } + + public LegacyDestinationResponse.Builder withStatusCode(Integer statusCode) { + this.statusCode = statusCode; + return this; + } + + public LegacyDestinationResponse build() { + return new LegacyDestinationResponse(responseContent, statusCode); + } + } + + public String getResponseContent() { + return this.responseContent; + } + + @Override + public void writeTo(StreamOutput streamOutput) throws IOException { + super.writeTo(streamOutput); + streamOutput.writeString(responseContent); + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt b/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt index 2f043351..c0646d2d 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt @@ -31,6 +31,7 @@ import org.opensearch.action.ActionResponse import org.opensearch.client.node.NodeClient import org.opensearch.common.io.stream.Writeable import org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_USER_INFO_THREAD_CONTEXT +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT import org.opensearch.commons.notifications.action.BaseResponse import org.opensearch.commons.notifications.action.CreateNotificationConfigRequest import org.opensearch.commons.notifications.action.CreateNotificationConfigResponse @@ -44,12 +45,15 @@ import org.opensearch.commons.notifications.action.GetNotificationEventRequest import org.opensearch.commons.notifications.action.GetNotificationEventResponse import org.opensearch.commons.notifications.action.GetPluginFeaturesRequest import org.opensearch.commons.notifications.action.GetPluginFeaturesResponse +import org.opensearch.commons.notifications.action.LegacyPublishNotificationRequest +import org.opensearch.commons.notifications.action.LegacyPublishNotificationResponse import org.opensearch.commons.notifications.action.NotificationsActions.CREATE_NOTIFICATION_CONFIG_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.DELETE_NOTIFICATION_CONFIG_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.GET_FEATURE_CHANNEL_LIST_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.GET_NOTIFICATION_CONFIG_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.GET_NOTIFICATION_EVENT_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.GET_PLUGIN_FEATURES_ACTION_TYPE +import org.opensearch.commons.notifications.action.NotificationsActions.LEGACY_PUBLISH_NOTIFICATION_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.SEND_NOTIFICATION_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.UPDATE_NOTIFICATION_CONFIG_ACTION_TYPE import org.opensearch.commons.notifications.action.SendNotificationRequest @@ -217,6 +221,30 @@ object NotificationsPluginInterface { ) } + /** + * Publishes a notification API using the legacy notification implementation. No REST API. + * Internal API only for the Index Management plugin. + * @param client Node client for making transport action + * @param request The legacy publish notification request + * @param listener The listener for getting response + */ + fun publishLegacyNotification( + client: NodeClient, + request: LegacyPublishNotificationRequest, + listener: ActionListener + ) { + if (request.feature != FEATURE_INDEX_MANAGEMENT) { + // Do not change this; do not pass in FEATURE_INDEX_MANAGEMENT if you are not the Index Management plugin. + throw IllegalArgumentException("The publish notification method only supports the Index Management feature.") + } + + client.execute( + LEGACY_PUBLISH_NOTIFICATION_ACTION_TYPE, + request, + wrapActionListener(listener) { response -> recreateObject(response) { LegacyPublishNotificationResponse(it) } } + ) + } + /** * Wrap action listener on concrete response class by a new created one on ActionResponse. * This is required because the response may be loaded by different classloader across plugins. diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt new file mode 100644 index 00000000..e5807102 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt @@ -0,0 +1,82 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.commons.notifications.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.common.io.stream.Writeable +import org.opensearch.commons.destination.message.LegacyBaseMessage +import org.opensearch.commons.destination.message.LegacyChimeMessage +import org.opensearch.commons.destination.message.LegacyCustomWebhookMessage +import org.opensearch.commons.destination.message.LegacyDestinationType +import org.opensearch.commons.destination.message.LegacySlackMessage +import java.io.IOException + +/** + * Action Request to publish notification. This is a legacy implementation. + * This should not be used going forward, instead use [SendNotificationRequest]. + */ +class LegacyPublishNotificationRequest : ActionRequest { + val baseMessage: LegacyBaseMessage + val feature: String + + companion object { + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { LegacyPublishNotificationRequest(it) } + } + + /** + * constructor for creating the class + * @param baseMessage the base message to send + * @param feature the feature that is trying to use this request + */ + constructor( + baseMessage: LegacyBaseMessage, + feature: String + ) { + this.baseMessage = baseMessage + this.feature = feature + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + baseMessage = when (requireNotNull(input.readEnum(LegacyDestinationType::class.java)) { "Destination type cannot be null" }) { + LegacyDestinationType.LEGACY_CHIME -> LegacyChimeMessage(input) + LegacyDestinationType.LEGACY_CUSTOM_WEBHOOK -> LegacyCustomWebhookMessage(input) + LegacyDestinationType.LEGACY_SLACK -> LegacySlackMessage(input) + } + feature = input.readString() + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + super.writeTo(output) + output.writeEnum(baseMessage.channelType) + baseMessage.writeTo(output) + output.writeString(feature) + } + + /** + * {@inheritDoc} + */ + override fun validate(): ActionRequestValidationException? = null +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponse.kt new file mode 100644 index 00000000..60f68e71 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponse.kt @@ -0,0 +1,63 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.commons.notifications.action + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.commons.destination.response.LegacyDestinationResponse +import java.io.IOException + +/** + * Action Response for legacy publish notification. + */ +class LegacyPublishNotificationResponse : BaseResponse { + val destinationResponse: LegacyDestinationResponse + + companion object { + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { LegacyPublishNotificationResponse(it) } + } + + /** + * constructor for creating the class + * @param destinationResponse the response of the published notification + */ + constructor(destinationResponse: LegacyDestinationResponse) { + this.destinationResponse = destinationResponse + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + destinationResponse = LegacyDestinationResponse(input) + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + destinationResponse.writeTo(output) + } + + // This class is only used across transport wire and does not need to implement toXContent + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + throw IllegalStateException("Legacy notification response is not intended for REST or persistence and does not support XContent.") + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt index fe7457d0..d93dd29a 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt @@ -72,6 +72,12 @@ object NotificationsActions { */ const val SEND_NOTIFICATION_NAME = "cluster:admin/opensearch/notifications/feature/send" + /** + * Publish legacy notification message. Internal only - Inter plugin communication. + * Only for the Index Management plugin. + */ + const val LEGACY_PUBLISH_NOTIFICATION_NAME = "cluster:admin/opensearch/notifications/feature/publish" + /** * Create notification configuration transport action type. */ @@ -119,4 +125,11 @@ object NotificationsActions { */ val SEND_NOTIFICATION_ACTION_TYPE = ActionType(SEND_NOTIFICATION_NAME, ::SendNotificationResponse) + + /** + * Send legacy notification transport action type. Internal only - Inter plugin communication. + * Only for the Index Management plugin. + */ + val LEGACY_PUBLISH_NOTIFICATION_ACTION_TYPE = + ActionType(LEGACY_PUBLISH_NOTIFICATION_NAME, ::LegacyPublishNotificationResponse) } diff --git a/src/test/java/org/opensearch/commons/destination/message/LegacyChimeMessageTest.java b/src/test/java/org/opensearch/commons/destination/message/LegacyChimeMessageTest.java new file mode 100644 index 00000000..4477789e --- /dev/null +++ b/src/test/java/org/opensearch/commons/destination/message/LegacyChimeMessageTest.java @@ -0,0 +1,100 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.message; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.common.io.stream.StreamInput; + +public class LegacyChimeMessageTest { + + @Test + public void testBuildingLegacyChimeMessage() { + LegacyChimeMessage message = new LegacyChimeMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withUrl("https://amazon.com") + .build(); + + assertEquals("custom_webhook", message.destinationName); + assertEquals(LegacyDestinationType.LEGACY_CHIME, message.getChannelType()); + assertEquals("Hello world", message.getMessageContent()); + assertEquals("https://amazon.com", message.url); + } + + @Test + public void testRoundTrippingLegacyChimeMessage() throws IOException { + LegacyChimeMessage message = new LegacyChimeMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withUrl("https://amazon.com") + .build(); + BytesStreamOutput out = new BytesStreamOutput(); + message.writeTo(out); + + StreamInput in = StreamInput.wrap(out.bytes().toBytesRef().bytes); + LegacyChimeMessage newMessage = new LegacyChimeMessage(in); + + assertEquals(newMessage.destinationName, message.destinationName); + assertEquals(newMessage.getChannelType(), message.getChannelType()); + assertEquals(newMessage.getMessageContent(), message.getMessageContent()); + assertEquals(newMessage.url, message.url); + } + + @Test + public void testContentMissingMessage() { + try { + new LegacyChimeMessage.Builder("custom_webhook").withUrl("https://amazon.com").build(); + fail("Building legacy chime message without message should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Message content is missing", e.getMessage()); + } + } + + @Test + public void testUrlMissingMessage() { + try { + new LegacyChimeMessage.Builder("custom_webhook").withMessage("Hello world").build(); + fail("Building legacy chime message without url should fail"); + } catch (IllegalArgumentException e) { + assertEquals("url is invalid or empty", e.getMessage()); + } + } + + @Test + public void testMissingDestinationName() { + try { + new LegacyChimeMessage.Builder(null).withMessage("Hello world").withUrl("https://amazon.com").build(); + fail("Building legacy chime message with null destination name should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Channel name must be defined", e.getMessage()); + } + } +} diff --git a/src/test/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessageTest.java b/src/test/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessageTest.java new file mode 100644 index 00000000..fba614fd --- /dev/null +++ b/src/test/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessageTest.java @@ -0,0 +1,177 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.message; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.junit.jupiter.api.Test; +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.common.io.stream.StreamInput; + +public class LegacyCustomWebhookMessageTest { + + @Test + public void testBuildingLegacyCustomWebhookMessage() { + LegacyCustomWebhookMessage message = new LegacyCustomWebhookMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withUrl("https://amazon.com") + .build(); + + assertEquals("custom_webhook", message.destinationName); + assertEquals(LegacyDestinationType.LEGACY_CUSTOM_WEBHOOK, message.getChannelType()); + assertEquals("Hello world", message.getMessageContent()); + assertEquals("https://amazon.com", message.getUrl()); + } + + @Test + public void testRoundTrippingLegacyCustomWebhookMessageWithUrl() throws IOException { + LegacyCustomWebhookMessage message = new LegacyCustomWebhookMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withUrl("https://amazon.com") + .build(); + BytesStreamOutput out = new BytesStreamOutput(); + message.writeTo(out); + + StreamInput in = StreamInput.wrap(out.bytes().toBytesRef().bytes); + LegacyCustomWebhookMessage newMessage = new LegacyCustomWebhookMessage(in); + + assertEquals(newMessage.destinationName, message.destinationName); + assertEquals(newMessage.getChannelType(), message.getChannelType()); + assertEquals(newMessage.getMessageContent(), message.getMessageContent()); + assertEquals(newMessage.getUrl(), message.getUrl()); + } + + @Test + public void testRoundTrippingLegacyCustomWebhookMessageWithHostFails() throws IOException { + Map queryParams = new HashMap(); + queryParams.put("token", "sometoken"); + Map headers = new HashMap(); + headers.put("x-token", "sometoken"); + LegacyCustomWebhookMessage message = new LegacyCustomWebhookMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withHost("hooks.chime.aws") + .withPath("incomingwebhooks/abc") + .withMethod(HttpPost.METHOD_NAME) + .withQueryParams(queryParams) + .withHeaderParams(headers) + .withPort(8000) + .withScheme("https") + .build(); + BytesStreamOutput out = new BytesStreamOutput(); + try { + message.writeTo(out); + fail("Writing LegacyCustomWebhookMessage with host instead of url to stream output should fail"); + } catch (IllegalStateException e) { + assertEquals("Cannot use LegacyCustomWebhookMessage across transport wire without defining full url.", e.getMessage()); + } + } + + @Test + public void testContentMissingMessage() { + try { + new LegacyCustomWebhookMessage.Builder("custom_webhook").withUrl("https://amazon.com").build(); + fail("Building legacy custom webhook message without message should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Message content is missing", e.getMessage()); + } + } + + @Test + public void testMissingDestinationName() { + try { + new LegacyCustomWebhookMessage.Builder(null).withMessage("Hello world").withUrl("https://amazon.com").build(); + fail("Building legacy custom webhook message with null destination name should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Channel name must be defined", e.getMessage()); + } + } + + @Test + public void testUnsupportedHttpMethods() { + try { + new LegacyCustomWebhookMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withUrl("https://amazon.com") + .withMethod(HttpGet.METHOD_NAME) + .build(); + fail("Building legacy custom webhook message with unsupported http methods should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Invalid method supplied. Only POST, PUT and PATCH are allowed", e.getMessage()); + } + } + + @Test + public void testURLandHostNameMissingOrEmpty() { + try { + new LegacyCustomWebhookMessage.Builder("custom_webhook").withMessage("Hello world").withMethod(HttpGet.METHOD_NAME).build(); + fail("Building legacy custom webhook message missing or empty url and host name should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Either fully qualified URL or host name should be provided", e.getMessage()); + } + + try { + new LegacyCustomWebhookMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withUrl("") + .withMethod(HttpGet.METHOD_NAME) + .build(); + fail("Building legacy custom webhook message with missing or empty url and host name should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Either fully qualified URL or host name should be provided", e.getMessage()); + } + + try { + new LegacyCustomWebhookMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withHost("") + .withMethod(HttpGet.METHOD_NAME) + .build(); + fail("Building legacy custom webhook message with missing or empty url and host name should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Either fully qualified URL or host name should be provided", e.getMessage()); + } + + try { + new LegacyCustomWebhookMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withUrl("") + .withHost("") + .withMethod(HttpGet.METHOD_NAME) + .build(); + fail("Building legacy custom webhook message with missing or empty url and host name should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Either fully qualified URL or host name should be provided", e.getMessage()); + } + } +} diff --git a/src/test/java/org/opensearch/commons/destination/message/LegacySlackMessageTest.java b/src/test/java/org/opensearch/commons/destination/message/LegacySlackMessageTest.java new file mode 100644 index 00000000..a520fc8e --- /dev/null +++ b/src/test/java/org/opensearch/commons/destination/message/LegacySlackMessageTest.java @@ -0,0 +1,110 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.message; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.common.io.stream.StreamInput; + +public class LegacySlackMessageTest { + + @Test + public void testBuildingLegacySlackMessage() { + LegacySlackMessage message = new LegacySlackMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withUrl("https://amazon.com") + .build(); + + assertEquals("custom_webhook", message.destinationName); + assertEquals(LegacyDestinationType.LEGACY_SLACK, message.getChannelType()); + assertEquals("Hello world", message.getMessageContent()); + assertEquals("https://amazon.com", message.url); + } + + @Test + public void testRoundTrippingLegacySlackMessage() throws IOException { + LegacySlackMessage message = new LegacySlackMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withUrl("https://amazon.com") + .build(); + BytesStreamOutput out = new BytesStreamOutput(); + message.writeTo(out); + + StreamInput in = StreamInput.wrap(out.bytes().toBytesRef().bytes); + LegacySlackMessage newMessage = new LegacySlackMessage(in); + + assertEquals(newMessage.destinationName, message.destinationName); + assertEquals(newMessage.getChannelType(), message.getChannelType()); + assertEquals(newMessage.getMessageContent(), message.getMessageContent()); + assertEquals(newMessage.url, message.url); + } + + @Test + public void testContentMissingMessage() { + try { + new LegacySlackMessage.Builder("custom_webhook").withUrl("https://amazon.com").build(); + fail("Building legacy slack message without message should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Message content is missing", e.getMessage()); + } + } + + @Test + public void testUrlMissingMessage() { + try { + new LegacySlackMessage.Builder("custom_webhook").withMessage("Hello world").build(); + fail("Building legacy slack message without url should fail"); + } catch (IllegalArgumentException e) { + assertEquals("url is invalid or empty", e.getMessage()); + } + } + + @Test + public void testMissingDestinationName() { + try { + new LegacySlackMessage.Builder(null).withMessage("Hello world").withUrl("https://amazon.com").build(); + fail("Building legacy slack message with null destination name should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Channel name must be defined", e.getMessage()); + } + } + + @Test + public void testUrlEmptyMessage() { + try { + new LegacySlackMessage.Builder("custom_webhook").withMessage("Hello world").withUrl("").build(); + fail("Building legacy slack message with empty url should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Fully qualified URL is missing/invalid: ", e.getMessage()); + } + } +} diff --git a/src/test/java/org/opensearch/commons/destination/response/LegacyDestinationResponseTest.java b/src/test/java/org/opensearch/commons/destination/response/LegacyDestinationResponseTest.java new file mode 100644 index 00000000..6175827e --- /dev/null +++ b/src/test/java/org/opensearch/commons/destination/response/LegacyDestinationResponseTest.java @@ -0,0 +1,82 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.response; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.common.io.stream.StreamInput; + +public class LegacyDestinationResponseTest { + + @Test + public void testBuildingLegacyDestinationResponse() { + LegacyDestinationResponse res = new LegacyDestinationResponse.Builder() + .withStatusCode(200) + .withResponseContent("Hello world") + .build(); + + assertEquals(200, res.statusCode); + assertEquals("Hello world", res.getResponseContent()); + } + + @Test + public void testRoundTrippingLegacyDestinationResponse() throws IOException { + LegacyDestinationResponse res = new LegacyDestinationResponse.Builder() + .withStatusCode(200) + .withResponseContent("Hello world") + .build(); + BytesStreamOutput out = new BytesStreamOutput(); + res.writeTo(out); + + StreamInput in = StreamInput.wrap(out.bytes().toBytesRef().bytes); + LegacyDestinationResponse newRes = new LegacyDestinationResponse(in); + + assertEquals(res.statusCode, newRes.statusCode, "Round tripping doesn't work"); + assertEquals(res.getResponseContent(), newRes.getResponseContent(), "Round tripping doesn't work"); + } + + @Test + public void testMissingLegacyDestinationResponse() { + try { + new LegacyDestinationResponse.Builder().withStatusCode(200).build(); + fail("Creating LegacyDestinationResponse without response content should fail"); + } catch (IllegalArgumentException ignored) {} + } + + @Test + public void testMissingLegacyDestinationStatusCode() { + try { + new LegacyDestinationResponse.Builder().withResponseContent("Hello world").build(); + fail("Creating LegacyDestinationResponse without status code should fail"); + } catch (IllegalArgumentException ignored) {} + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt index cf4f8420..343fecbd 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt @@ -25,7 +25,9 @@ import org.mockito.junit.jupiter.MockitoExtension import org.opensearch.action.ActionListener import org.opensearch.action.ActionType import org.opensearch.client.node.NodeClient +import org.opensearch.commons.destination.response.LegacyDestinationResponse import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.notifications.action.CreateNotificationConfigRequest import org.opensearch.commons.notifications.action.CreateNotificationConfigResponse @@ -39,6 +41,8 @@ import org.opensearch.commons.notifications.action.GetNotificationEventRequest import org.opensearch.commons.notifications.action.GetNotificationEventResponse import org.opensearch.commons.notifications.action.GetPluginFeaturesRequest import org.opensearch.commons.notifications.action.GetPluginFeaturesResponse +import org.opensearch.commons.notifications.action.LegacyPublishNotificationRequest +import org.opensearch.commons.notifications.action.LegacyPublishNotificationResponse import org.opensearch.commons.notifications.action.SendNotificationResponse import org.opensearch.commons.notifications.action.UpdateNotificationConfigRequest import org.opensearch.commons.notifications.action.UpdateNotificationConfigResponse @@ -223,6 +227,26 @@ internal class NotificationsPluginInterfaceTests { verify(listener, times(1)).onResponse(eq(response)) } + @Test + fun publishLegacyNotification() { + val request = mock(LegacyPublishNotificationRequest::class.java) + val res = LegacyPublishNotificationResponse(LegacyDestinationResponse.Builder().withStatusCode(200).withResponseContent("Nice!").build()) + val l: ActionListener = + mock(ActionListener::class.java) as ActionListener + + doAnswer { + (it.getArgument(2) as ActionListener) + .onResponse(res) + }.whenever(client).execute(any(ActionType::class.java), any(), any()) + + doAnswer { + FEATURE_INDEX_MANAGEMENT + }.whenever(request).feature + + NotificationsPluginInterface.publishLegacyNotification(client, request, l) + verify(l, times(1)).onResponse(eq(res)) + } + private fun mockGetNotificationConfigResponse(): GetNotificationConfigResponse { val sampleSlack = Slack("https://domain.com/sample_url#1234567890") val sampleConfig = NotificationConfig( diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequestTests.kt new file mode 100644 index 00000000..349a1c14 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequestTests.kt @@ -0,0 +1,42 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.commons.notifications.action + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test +import org.opensearch.commons.destination.message.LegacyChimeMessage +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT +import org.opensearch.commons.utils.recreateObject + +internal class LegacyPublishNotificationRequestTests { + + private fun assertRequestEquals( + expected: LegacyPublishNotificationRequest, + actual: LegacyPublishNotificationRequest + ) { + assertEquals(expected.baseMessage.channelName, actual.baseMessage.channelName) + assertEquals(expected.baseMessage.channelType, actual.baseMessage.channelType) + assertEquals(expected.baseMessage.messageContent, actual.baseMessage.messageContent) + assertEquals(expected.baseMessage.url, actual.baseMessage.url) + assertEquals(expected.feature, actual.feature) + assertNull(actual.validate()) + } + + @Test + fun `publish request serialize and deserialize transport object should be equal`() { + val baseMessage = LegacyChimeMessage.Builder("chime_message").withMessage("Hello world").withUrl("https://amazon.com").build() + val request = LegacyPublishNotificationRequest(baseMessage, FEATURE_INDEX_MANAGEMENT) + val recreatedObject = recreateObject(request) { LegacyPublishNotificationRequest(it) } + assertRequestEquals(request, recreatedObject) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponseTests.kt new file mode 100644 index 00000000..4b6f8e0b --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponseTests.kt @@ -0,0 +1,29 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.commons.notifications.action + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.opensearch.commons.destination.response.LegacyDestinationResponse +import org.opensearch.commons.utils.recreateObject + +internal class LegacyPublishNotificationResponseTests { + + @Test + fun `Create response serialize and deserialize transport object should be equal`() { + val res = LegacyDestinationResponse.Builder().withStatusCode(200).withResponseContent("Hello world").build() + val configResponse = LegacyPublishNotificationResponse(res) + val recreatedObject = recreateObject(configResponse) { LegacyPublishNotificationResponse(it) } + assertEquals(configResponse.destinationResponse.statusCode, recreatedObject.destinationResponse.statusCode) + assertEquals(configResponse.destinationResponse.responseContent, recreatedObject.destinationResponse.responseContent) + } +} From 401802dddcb9d1d8c3d9700a8e4cdc6d96348814 Mon Sep 17 00:00:00 2001 From: Vacha Date: Mon, 16 Aug 2021 07:36:21 -0700 Subject: [PATCH 010/149] Using 1.1 snapshot version for OpenSearch (#57) Signed-off-by: Vacha --- .github/workflows/ci.yml | 6 +++--- build.gradle | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f0adf94..1872eb2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,16 +34,16 @@ jobs: ref: 'main' - name: Build OpenSearch working-directory: ./OpenSearch - run: ./gradlew publishToMavenLocal -Dbuild.snapshot=false + run: ./gradlew publishToMavenLocal # common-utils - name: Build and Test run: | - ./gradlew build -Dopensearch.version=1.1.0 + ./gradlew build -Dopensearch.version=1.1.0-SNAPSHOT - name: Publish to Maven Local run: | - ./gradlew publishToMavenLocal -Dopensearch.version=1.1.0 + ./gradlew publishToMavenLocal -Dopensearch.version=1.1.0-SNAPSHOT - name: Upload Coverage Report uses: codecov/codecov-action@v1 diff --git a/build.gradle b/build.gradle index 0f2b241a..4917e428 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "1.1.0") + opensearch_version = System.getProperty("opensearch.version", "1.1.0-SNAPSHOT") kotlin_version = System.getProperty("kotlin.version", "1.4.32") } From 4f30425431751239f318e89722672f81f5bf3457 Mon Sep 17 00:00:00 2001 From: Miki Date: Mon, 16 Aug 2021 07:42:06 -0700 Subject: [PATCH 011/149] Add themed logo to README (#41) Signed-off-by: Miki --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 237509c1..b2778b5d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - + - [OpenSearch Common Utils](#opensearch-common-utils) - [Contributing](#contributing) From cc02f237de3ef45c3f7dc0bf526556d09bbaba97 Mon Sep 17 00:00:00 2001 From: "Daniel Doubrovkine (dB.)" Date: Mon, 16 Aug 2021 11:25:48 -0400 Subject: [PATCH 012/149] Build snapshot build by default with the same version as OpenSearch. (#58) Signed-off-by: dblock --- .github/workflows/ci.yml | 2 +- build.gradle | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1872eb2a..53c3d9b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: with: repository: 'opensearch-project/OpenSearch' path: OpenSearch - ref: 'main' + ref: '1.x' - name: Build OpenSearch working-directory: ./OpenSearch run: ./gradlew publishToMavenLocal diff --git a/build.gradle b/build.gradle index 4917e428..d443ddf4 100644 --- a/build.gradle +++ b/build.gradle @@ -44,7 +44,17 @@ repositories { jcenter() } -group 'org.opensearch.commons' +ext { + isSnapshot = "true" == System.getProperty("build.snapshot", "true") +} + +allprojects { + group 'org.opensearch.commons' + version = opensearch_version - '-SNAPSHOT' + '.0' + if (isSnapshot) { + version += "-SNAPSHOT" + } +} sourceCompatibility = 1.8 @@ -146,8 +156,6 @@ task javadocJar(type: Jar) { from javadoc.destinationDir } -version '1.1.0.0' - publishing { publications { shadow(MavenPublication) { From 3913d7097934cbfe1fdcf919347f22a597d00b76 Mon Sep 17 00:00:00 2001 From: Anantha Krishna Bhatta <31894175+akbhatta@users.noreply.github.com> Date: Mon, 16 Aug 2021 15:49:54 -0700 Subject: [PATCH 013/149] BugFix: Allow null values for role_arn in json parsing (#60) Signed-off-by: @akbhatta --- .../commons/notifications/model/SesAccount.kt | 5 +-- .../notifications/model/SesAccountTests.kt | 35 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt index d529b407..aa9aca26 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt @@ -22,6 +22,7 @@ import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.FROM_ADDRESS_TAG import org.opensearch.commons.notifications.NotificationConstants.REGION_TAG import org.opensearch.commons.notifications.NotificationConstants.ROLE_ARN_TAG +import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateEmail import org.opensearch.commons.utils.validateIamRoleArn @@ -74,7 +75,7 @@ data class SesAccount( parser.nextToken() when (fieldName) { REGION_TAG -> awsRegion = parser.text() - ROLE_ARN_TAG -> roleArn = parser.text() + ROLE_ARN_TAG -> roleArn = parser.textOrNull() FROM_ADDRESS_TAG -> fromAddress = parser.text() else -> { parser.skipChildren() @@ -98,7 +99,7 @@ data class SesAccount( override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { return builder!!.startObject() .field(REGION_TAG, awsRegion) - .field(ROLE_ARN_TAG, roleArn) + .fieldIfNotNull(ROLE_ARN_TAG, roleArn) .field(FROM_ADDRESS_TAG, fromAddress) .endObject() } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/SesAccountTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/SesAccountTests.kt index c99506fd..e999e534 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/SesAccountTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/SesAccountTests.kt @@ -82,6 +82,14 @@ internal class SesAccountTests { assertEquals(sesAccount, recreatedObject) } + @Test + fun `SES serialize and deserialize using json object should be equal with null roleArn`() { + val sesAccount = SesAccount("us-east-1", null, "from@domain.com") + val jsonString = getJsonString(sesAccount) + val recreatedObject = createObjectFromJsonString(jsonString) { SesAccount.parse(it) } + assertEquals(sesAccount, recreatedObject) + } + @Test fun `SES should deserialize json object using parser`() { val sesAccount = SesAccount("us-east-1", "arn:aws:iam::012345678912:role/iam-test", "from@domain.com") @@ -96,6 +104,33 @@ internal class SesAccountTests { assertEquals(sesAccount, recreatedObject) } + @Test + fun `SES should deserialize json object will null role_arn using parser`() { + val sesAccount = SesAccount("us-east-1", null, "from@domain.com") + val jsonString = """ + { + "region":"${sesAccount.awsRegion}", + "role_arn":null, + "from_address":"${sesAccount.fromAddress}" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { SesAccount.parse(it) } + assertEquals(sesAccount, recreatedObject) + } + + @Test + fun `SES should deserialize json object will missing role_arn using parser`() { + val sesAccount = SesAccount("us-east-1", null, "from@domain.com") + val jsonString = """ + { + "region":"${sesAccount.awsRegion}", + "from_address":"${sesAccount.fromAddress}" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { SesAccount.parse(it) } + assertEquals(sesAccount, recreatedObject) + } + @Test fun `SES should throw exception when invalid json object is passed`() { val jsonString = "sample message" From 8fccbea9867e7d185ad46eccd7d3d2c0f5d14225 Mon Sep 17 00:00:00 2001 From: Anantha Krishna Bhatta <31894175+akbhatta@users.noreply.github.com> Date: Fri, 17 Sep 2021 11:34:55 -0700 Subject: [PATCH 014/149] Removed tenant information from the models (#73) * Removed tenant information from the models [Tests] Updated unit tests Signed-off-by: @akbhatta * Updated OpenSearch branch to 1.1 Signed-off-by: @akbhatta --- .github/workflows/ci.yml | 2 +- .../notifications/NotificationConstants.kt | 1 - .../action/GetFeatureChannelListRequest.kt | 3 - .../model/NotificationConfigInfo.kt | 9 --- .../model/NotificationEventInfo.kt | 9 --- .../NotificationsPluginInterfaceTests.kt | 2 - .../GetNotificationConfigResponseTests.kt | 13 +--- .../GetNotificationEventResponseTests.kt | 15 +---- .../model/NotificationConfigInfoTests.kt | 50 +------------- .../NotificationConfigSearchResultsTests.kt | 15 +---- .../model/NotificationEventInfoTests.kt | 67 +------------------ .../NotificationEventSearchResultTests.kt | 19 +----- 12 files changed, 7 insertions(+), 198 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 53c3d9b6..8f83153e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: with: repository: 'opensearch-project/OpenSearch' path: OpenSearch - ref: '1.x' + ref: '1.1' - name: Build OpenSearch working-directory: ./OpenSearch run: ./gradlew publishToMavenLocal diff --git a/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt index 55dc9a44..dc16a39f 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt @@ -51,7 +51,6 @@ object NotificationConstants { const val FROM_ADDRESS_TAG = "from_address" const val UPDATED_TIME_TAG = "last_updated_time_ms" const val CREATED_TIME_TAG = "created_time_ms" - const val TENANT_TAG = "tenant" const val CONFIG_LIST_TAG = "config_list" const val EVENT_LIST_TAG = "event_list" const val FEATURE_CONFIG_LIST_TAG = "feature_channel_list" diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt index 309a31bc..ba6e3b19 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt @@ -42,9 +42,6 @@ import java.io.IOException /** * This request is plugin-only call. i.e. REST interface is not exposed. - * Also the library will remove the user context while making this call - * so that user making this call need not have to set permission to this API. - * Hence the request also contains tenant info for space isolation. */ class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { val feature: String diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt index 7a949bbb..3c17a28c 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt @@ -37,7 +37,6 @@ import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_TAG import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TAG import org.opensearch.commons.notifications.NotificationConstants.CREATED_TIME_TAG -import org.opensearch.commons.notifications.NotificationConstants.TENANT_TAG import org.opensearch.commons.notifications.NotificationConstants.UPDATED_TIME_TAG import org.opensearch.commons.utils.logger import java.io.IOException @@ -50,7 +49,6 @@ data class NotificationConfigInfo( val configId: String, val lastUpdatedTime: Instant, val createdTime: Instant, - val tenant: String, val notificationConfig: NotificationConfig ) : BaseModel { @@ -76,7 +74,6 @@ data class NotificationConfigInfo( var configId: String? = null var lastUpdatedTime: Instant? = null var createdTime: Instant? = null - var tenant: String? = null var notificationConfig: NotificationConfig? = null XContentParserUtils.ensureExpectedToken( @@ -91,7 +88,6 @@ data class NotificationConfigInfo( CONFIG_ID_TAG -> configId = parser.text() UPDATED_TIME_TAG -> lastUpdatedTime = Instant.ofEpochMilli(parser.longValue()) CREATED_TIME_TAG -> createdTime = Instant.ofEpochMilli(parser.longValue()) - TENANT_TAG -> tenant = parser.text() CONFIG_TAG -> notificationConfig = NotificationConfig.parse(parser) else -> { parser.skipChildren() @@ -102,13 +98,11 @@ data class NotificationConfigInfo( configId ?: throw IllegalArgumentException("$CONFIG_ID_TAG field absent") lastUpdatedTime ?: throw IllegalArgumentException("$UPDATED_TIME_TAG field absent") createdTime ?: throw IllegalArgumentException("$CREATED_TIME_TAG field absent") - tenant = tenant ?: "" notificationConfig ?: throw IllegalArgumentException("$CONFIG_TAG field absent") return NotificationConfigInfo( configId, lastUpdatedTime, createdTime, - tenant, notificationConfig ) } @@ -122,7 +116,6 @@ data class NotificationConfigInfo( configId = input.readString(), lastUpdatedTime = input.readInstant(), createdTime = input.readInstant(), - tenant = input.readString(), notificationConfig = NotificationConfig.reader.read(input) ) @@ -133,7 +126,6 @@ data class NotificationConfigInfo( output.writeString(configId) output.writeInstant(lastUpdatedTime) output.writeInstant(createdTime) - output.writeString(tenant) notificationConfig.writeTo(output) } @@ -146,7 +138,6 @@ data class NotificationConfigInfo( .field(CONFIG_ID_TAG, configId) .field(UPDATED_TIME_TAG, lastUpdatedTime.toEpochMilli()) .field(CREATED_TIME_TAG, createdTime.toEpochMilli()) - .field(TENANT_TAG, tenant) .field(CONFIG_TAG, notificationConfig) .endObject() } diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfo.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfo.kt index 27f9d0ad..3e4ff552 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfo.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfo.kt @@ -37,7 +37,6 @@ import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CREATED_TIME_TAG import org.opensearch.commons.notifications.NotificationConstants.EVENT_ID_TAG import org.opensearch.commons.notifications.NotificationConstants.EVENT_TAG -import org.opensearch.commons.notifications.NotificationConstants.TENANT_TAG import org.opensearch.commons.notifications.NotificationConstants.UPDATED_TIME_TAG import org.opensearch.commons.utils.logger import java.io.IOException @@ -50,7 +49,6 @@ data class NotificationEventInfo( val eventId: String, val lastUpdatedTime: Instant, val createdTime: Instant, - val tenant: String, val notificationEvent: NotificationEvent ) : BaseModel { @@ -76,7 +74,6 @@ data class NotificationEventInfo( var eventId: String? = null var lastUpdatedTime: Instant? = null var createdTime: Instant? = null - var tenant: String? = null var notificationEvent: NotificationEvent? = null XContentParserUtils.ensureExpectedToken( @@ -91,7 +88,6 @@ data class NotificationEventInfo( EVENT_ID_TAG -> eventId = parser.text() UPDATED_TIME_TAG -> lastUpdatedTime = Instant.ofEpochMilli(parser.longValue()) CREATED_TIME_TAG -> createdTime = Instant.ofEpochMilli(parser.longValue()) - TENANT_TAG -> tenant = parser.text() EVENT_TAG -> notificationEvent = NotificationEvent.parse(parser) else -> { parser.skipChildren() @@ -102,13 +98,11 @@ data class NotificationEventInfo( eventId ?: throw IllegalArgumentException("$EVENT_ID_TAG field absent") lastUpdatedTime ?: throw IllegalArgumentException("$UPDATED_TIME_TAG field absent") createdTime ?: throw IllegalArgumentException("$CREATED_TIME_TAG field absent") - tenant = tenant ?: "" notificationEvent ?: throw IllegalArgumentException("$EVENT_TAG field absent") return NotificationEventInfo( eventId, lastUpdatedTime, createdTime, - tenant, notificationEvent ) } @@ -123,7 +117,6 @@ data class NotificationEventInfo( .field(EVENT_ID_TAG, eventId) .field(UPDATED_TIME_TAG, lastUpdatedTime.toEpochMilli()) .field(CREATED_TIME_TAG, createdTime.toEpochMilli()) - .field(TENANT_TAG, tenant) .field(EVENT_TAG, notificationEvent) .endObject() } @@ -136,7 +129,6 @@ data class NotificationEventInfo( eventId = input.readString(), lastUpdatedTime = input.readInstant(), createdTime = input.readInstant(), - tenant = input.readString(), notificationEvent = NotificationEvent.reader.read(input) ) @@ -147,7 +139,6 @@ data class NotificationEventInfo( output.writeString(eventId) output.writeInstant(lastUpdatedTime) output.writeInstant(createdTime) - output.writeString(tenant) notificationEvent.writeTo(output) } } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt index 343fecbd..77d12ff2 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt @@ -260,7 +260,6 @@ internal class NotificationsPluginInterfaceTests { "config_id", Instant.now(), Instant.now(), - "tenant", sampleConfig ) return GetNotificationConfigResponse(NotificationConfigSearchResult(configInfo)) @@ -284,7 +283,6 @@ internal class NotificationsPluginInterfaceTests { "event_id", Instant.now(), Instant.now(), - "tenant", sampleEvent ) return GetNotificationEventResponse(NotificationEventSearchResult(eventInfo)) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt index 2ec08913..8cc66d80 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt @@ -70,7 +70,6 @@ internal class GetNotificationConfigResponseTests { "config_id", Instant.now(), Instant.now(), - "tenant", sampleConfig ) val searchResult = NotificationConfigSearchResult(configInfo) @@ -92,7 +91,6 @@ internal class GetNotificationConfigResponseTests { "config_id1", Instant.now(), Instant.now(), - "tenant", sampleConfig1 ) val sampleConfig2 = NotificationConfig( @@ -106,7 +104,6 @@ internal class GetNotificationConfigResponseTests { "config_id2", Instant.now(), Instant.now(), - "tenant", sampleConfig2 ) val searchResult = NotificationConfigSearchResult( @@ -136,7 +133,6 @@ internal class GetNotificationConfigResponseTests { "config_id", lastUpdatedTimeMs, createdTimeMs, - "tenant", sampleConfig ) val searchResult = NotificationConfigSearchResult(configInfo) @@ -161,7 +157,6 @@ internal class GetNotificationConfigResponseTests { "config_id1", lastUpdatedTimeMs, createdTimeMs, - "tenant", sampleConfig1 ) val sampleConfig2 = NotificationConfig( @@ -175,7 +170,6 @@ internal class GetNotificationConfigResponseTests { "config_id2", lastUpdatedTimeMs, createdTimeMs, - "tenant", sampleConfig2 ) val searchResult = NotificationConfigSearchResult( @@ -207,7 +201,6 @@ internal class GetNotificationConfigResponseTests { "config-Id", lastUpdatedTimeMs, createdTimeMs, - "selectedTenant", sampleConfig ) val searchResult = NotificationConfigSearchResult(configInfo) @@ -221,7 +214,6 @@ internal class GetNotificationConfigResponseTests { "config_id":"config-Id", "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "tenant":"selectedTenant", "config":{ "name":"name", "description":"description", @@ -258,7 +250,6 @@ internal class GetNotificationConfigResponseTests { "config-Id", lastUpdatedTimeMs, createdTimeMs, - "selectedTenant", sampleConfig ) val searchResult = NotificationConfigSearchResult(configInfo) @@ -269,7 +260,6 @@ internal class GetNotificationConfigResponseTests { "config_id":"config-Id", "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "tenant":"selectedTenant", "config":{ "name":"name", "description":"description", @@ -299,8 +289,7 @@ internal class GetNotificationConfigResponseTests { { "config_id":"config-Id", "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", - "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "tenant":"selectedTenant" + "created_time_ms":"${createdTimeMs.toEpochMilli()}" } ] } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt index 1895d1d3..be6af960 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt @@ -77,7 +77,6 @@ internal class GetNotificationEventResponseTests { "event_id", Instant.now(), Instant.now(), - "tenant", sampleEvent ) val searchResult = NotificationEventSearchResult(eventInfo) @@ -116,28 +115,24 @@ internal class GetNotificationEventResponseTests { "event_id1", Instant.now(), Instant.now(), - "tenant", NotificationEvent(eventSource1, listOf(status1)) ) val eventInfo2 = NotificationEventInfo( "event_id2", Instant.now(), Instant.now(), - "tenant", NotificationEvent(eventSource2, listOf(status2)) ) val eventInfo3 = NotificationEventInfo( "event_id3", Instant.now(), Instant.now(), - "tenant", NotificationEvent(eventSource1, listOf(status1, status2)) ) val eventInfo4 = NotificationEventInfo( "event_id4", Instant.now(), Instant.now(), - "tenant", NotificationEvent(eventSource2, listOf(status1, status2)) ) val searchResult = NotificationEventSearchResult( @@ -172,7 +167,6 @@ internal class GetNotificationEventResponseTests { "event_id", lastUpdatedTimeMs, createdTimeMs, - "tenant", sampleEvent ) val searchResult = NotificationEventSearchResult(eventInfo) @@ -214,14 +208,12 @@ internal class GetNotificationEventResponseTests { "event_id1", lastUpdatedTimeMs, createdTimeMs, - "tenant", NotificationEvent(eventSource1, listOf(status1)) ) val eventInfo2 = NotificationEventInfo( "event_id2", lastUpdatedTimeMs, createdTimeMs, - "tenant", NotificationEvent(eventSource2, listOf(status2)) ) val searchResult = NotificationEventSearchResult( @@ -257,7 +249,6 @@ internal class GetNotificationEventResponseTests { "event_id", lastUpdatedTimeMs, createdTimeMs, - "selectedTenant", sampleEvent ) val searchResult = NotificationEventSearchResult(eventInfo) @@ -271,7 +262,6 @@ internal class GetNotificationEventResponseTests { "event_id":"event_id", "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "tenant":"selectedTenant", "event":{ "event_source":{ "title":"title", @@ -325,7 +315,6 @@ internal class GetNotificationEventResponseTests { "event_id", lastUpdatedTimeMs, createdTimeMs, - "selectedTenant", sampleEvent ) val searchResult = NotificationEventSearchResult(eventInfo) @@ -336,7 +325,6 @@ internal class GetNotificationEventResponseTests { "event_id":"event_id", "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "tenant":"selectedTenant", "event":{ "event_source":{ "title":"title", @@ -379,8 +367,7 @@ internal class GetNotificationEventResponseTests { { "event_id":"event_id", "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", - "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "tenant":"selectedTenant" + "created_time_ms":"${createdTimeMs.toEpochMilli()}" } ] } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt index 409decd7..6f532fb0 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt @@ -52,7 +52,6 @@ internal class NotificationConfigInfoTests { "config_id", Instant.now(), Instant.now(), - "tenant", sampleConfig ) val recreatedObject = recreateObject(configInfo) { NotificationConfigInfo(it) } @@ -75,7 +74,6 @@ internal class NotificationConfigInfoTests { "config_id", lastUpdatedTimeMs, createdTimeMs, - "tenant", sampleConfig ) val jsonString = getJsonString(configInfo) @@ -83,45 +81,6 @@ internal class NotificationConfigInfoTests { assertEquals(configInfo, recreatedObject) } - @Test - fun `Config info should take default tenant when field is absent in json object`() { - val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) - val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) - val sampleSlack = Slack("https://domain.com/sample_slack_url#1234567890") - val sampleConfig = NotificationConfig( - "name", - "description", - ConfigType.SLACK, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleSlack - ) - val configInfo = NotificationConfigInfo( - "config-Id", - lastUpdatedTimeMs, - createdTimeMs, - "", // Default tenant - sampleConfig - ) - val jsonString = """ - { - "config_id":"config-Id", - "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", - "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "config":{ - "name":"name", - "description":"description", - "config_type":"slack", - "feature_list":["index_management"], - "is_enabled":true, - "slack":{"url":"https://domain.com/sample_slack_url#1234567890"} - } - } - """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { NotificationConfigInfo.parse(it) } - assertEquals(configInfo, recreatedObject) - } - @Test fun `Config info should safely ignore extra field in json object`() { val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) @@ -139,7 +98,6 @@ internal class NotificationConfigInfoTests { "config-Id", lastUpdatedTimeMs, createdTimeMs, - "selectedTenant", sampleConfig ) val jsonString = """ @@ -147,7 +105,6 @@ internal class NotificationConfigInfoTests { "config_id":"config-Id", "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "tenant":"selectedTenant", "config":{ "name":"name", "description":"description", @@ -180,7 +137,6 @@ internal class NotificationConfigInfoTests { "", Instant.now(), Instant.now(), - "tenant", sampleConfig ) } @@ -194,7 +150,6 @@ internal class NotificationConfigInfoTests { { "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "tenant":"selectedTenant", "config":{ "name":"name", "description":"description", @@ -218,7 +173,6 @@ internal class NotificationConfigInfoTests { { "config_id":"config-Id", "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "tenant":"selectedTenant", "config":{ "name":"name", "description":"description", @@ -241,7 +195,6 @@ internal class NotificationConfigInfoTests { { "config_id":"config-Id", "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", - "tenant":"selectedTenant", "config":{ "name":"name", "description":"description", @@ -265,8 +218,7 @@ internal class NotificationConfigInfoTests { { "config_id":"config-Id", "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", - "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "tenant":"selectedTenant" + "created_time_ms":"${createdTimeMs.toEpochMilli()}" } """.trimIndent() Assertions.assertThrows(IllegalArgumentException::class.java) { diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt index 6a10935f..dab33090 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt @@ -64,7 +64,6 @@ internal class NotificationConfigSearchResultsTests { "config_id", Instant.now(), Instant.now(), - "tenant", sampleConfig ) val searchResult = NotificationConfigSearchResult(configInfo) @@ -85,7 +84,6 @@ internal class NotificationConfigSearchResultsTests { "config_id1", Instant.now(), Instant.now(), - "tenant", sampleConfig1 ) val sampleConfig2 = NotificationConfig( @@ -99,7 +97,6 @@ internal class NotificationConfigSearchResultsTests { "config_id2", Instant.now(), Instant.now(), - "tenant", sampleConfig2 ) val searchResult = NotificationConfigSearchResult(listOf(configInfo1, configInfo2)) @@ -126,7 +123,6 @@ internal class NotificationConfigSearchResultsTests { "config_id1", Instant.now(), Instant.now(), - "tenant", sampleConfig1 ) val sampleConfig2 = NotificationConfig( @@ -140,7 +136,6 @@ internal class NotificationConfigSearchResultsTests { "config_id2", Instant.now(), Instant.now(), - "tenant", sampleConfig2 ) val searchResult = NotificationConfigSearchResult( @@ -169,7 +164,6 @@ internal class NotificationConfigSearchResultsTests { "config_id", lastUpdatedTimeMs, createdTimeMs, - "tenant", sampleConfig ) val searchResult = NotificationConfigSearchResult(configInfo) @@ -193,7 +187,6 @@ internal class NotificationConfigSearchResultsTests { "config_id1", lastUpdatedTimeMs, createdTimeMs, - "tenant", sampleConfig1 ) val sampleConfig2 = NotificationConfig( @@ -207,7 +200,6 @@ internal class NotificationConfigSearchResultsTests { "config_id2", lastUpdatedTimeMs, createdTimeMs, - "tenant", sampleConfig2 ) val searchResult = NotificationConfigSearchResult( @@ -238,7 +230,6 @@ internal class NotificationConfigSearchResultsTests { "config-Id", lastUpdatedTimeMs, createdTimeMs, - "selectedTenant", sampleConfig ) val searchResult = NotificationConfigSearchResult(configInfo) @@ -252,7 +243,6 @@ internal class NotificationConfigSearchResultsTests { "config_id":"config-Id", "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "tenant":"selectedTenant", "config":{ "name":"name", "description":"description", @@ -289,7 +279,6 @@ internal class NotificationConfigSearchResultsTests { "config-Id", lastUpdatedTimeMs, createdTimeMs, - "selectedTenant", sampleConfig ) val searchResult = NotificationConfigSearchResult(configInfo) @@ -300,7 +289,6 @@ internal class NotificationConfigSearchResultsTests { "config_id":"config-Id", "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "tenant":"selectedTenant", "config":{ "name":"name", "description":"description", @@ -330,8 +318,7 @@ internal class NotificationConfigSearchResultsTests { { "config_id":"config-Id", "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", - "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "tenant":"selectedTenant" + "created_time_ms":"${createdTimeMs.toEpochMilli()}" } ] } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt index 9f45fbb0..3937c252 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt @@ -56,7 +56,6 @@ internal class NotificationEventInfoTests { "event_id", Instant.now(), Instant.now(), - "tenant", sampleEvent ) val recreatedObject = recreateObject(eventInfo) { NotificationEventInfo(it) } @@ -84,7 +83,6 @@ internal class NotificationEventInfoTests { "event_id", lastUpdatedTimeMs, createdTimeMs, - "tenant", sampleEvent ) val jsonString = getJsonString(eventInfo) @@ -92,63 +90,6 @@ internal class NotificationEventInfoTests { assertEquals(eventInfo, recreatedObject) } - @Test - fun `Event info should take default tenant when field is absent in json object`() { - val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) - val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) - val sampleEventSource = EventSource( - "title", - "reference_id", - FEATURE_ALERTING, - severity = SeverityType.INFO - ) - val sampleStatus = EventStatus( - "config_id", - "name", - ConfigType.SLACK, - deliveryStatus = DeliveryStatus("200", "success") - ) - val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) - val eventInfo = NotificationEventInfo( - "event_id", - lastUpdatedTimeMs, - createdTimeMs, - "tenant", - sampleEvent - ) - val jsonString = """ - { - "event_id":"event_id", - "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", - "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "tenant":"tenant", - "event":{ - "event_source":{ - "title":"title", - "reference_id":"reference_id", - "feature":"alerting", - "severity":"info", - "tags":[] - }, - "status_list":[ - { - "config_id":"config_id", - "config_type":"slack", - "config_name":"name", - "delivery_status": - { - "status_code":"200", - "status_text":"success" - } - } - ] - } - } - """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { NotificationEventInfo.parse(it) } - assertEquals(eventInfo, recreatedObject) - } - @Test fun `Event info should safely ignore extra field in json object`() { val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) @@ -170,7 +111,6 @@ internal class NotificationEventInfoTests { "event_id", lastUpdatedTimeMs, createdTimeMs, - "tenant", sampleEvent ) val jsonString = """ @@ -178,7 +118,6 @@ internal class NotificationEventInfoTests { "event_id":"event_id", "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "tenant":"tenant", "event":{ "event_source":{ "title":"title", @@ -231,7 +170,6 @@ internal class NotificationEventInfoTests { "", lastUpdatedTimeMs, createdTimeMs, - "tenant", sampleEvent ) } @@ -281,7 +219,6 @@ internal class NotificationEventInfoTests { { "event_id":"event_id", "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "tenant":"selectedTenant", "event":{ "event_source":{ "title":"title", @@ -317,7 +254,6 @@ internal class NotificationEventInfoTests { { "event_id":"event_id", "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", - "tenant":"selectedTenant", "event":{ "event_source":{ "title":"title", @@ -354,8 +290,7 @@ internal class NotificationEventInfoTests { { "event_id":"event_id", "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", - "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "tenant":"selectedTenant" + "created_time_ms":"${createdTimeMs.toEpochMilli()}" } """.trimIndent() Assertions.assertThrows(IllegalArgumentException::class.java) { diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt index 078d4910..c0e9786d 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt @@ -69,7 +69,6 @@ internal class NotificationEventSearchResultTests { "event_id", Instant.now(), Instant.now(), - "tenant", sampleEvent ) val searchResult = NotificationEventSearchResult(eventInfo) @@ -107,28 +106,24 @@ internal class NotificationEventSearchResultTests { "event_id1", Instant.now(), Instant.now(), - "tenant", NotificationEvent(eventSource1, listOf(status1)) ) val eventInfo2 = NotificationEventInfo( "event_id2", Instant.now(), Instant.now(), - "tenant", NotificationEvent(eventSource2, listOf(status2)) ) val eventInfo3 = NotificationEventInfo( "event_id3", Instant.now(), Instant.now(), - "tenant", NotificationEvent(eventSource1, listOf(status1, status2)) ) val eventInfo4 = NotificationEventInfo( "event_id4", Instant.now(), Instant.now(), - "tenant", NotificationEvent(eventSource2, listOf(status1, status2)) ) val searchResult = NotificationEventSearchResult( @@ -174,28 +169,24 @@ internal class NotificationEventSearchResultTests { "event_id1", Instant.now(), Instant.now(), - "tenant", NotificationEvent(eventSource1, listOf(status1)) ) val eventInfo2 = NotificationEventInfo( "event_id2", Instant.now(), Instant.now(), - "tenant", NotificationEvent(eventSource2, listOf(status2)) ) val eventInfo3 = NotificationEventInfo( "event_id3", Instant.now(), Instant.now(), - "tenant", NotificationEvent(eventSource1, listOf(status1, status2)) ) val eventInfo4 = NotificationEventInfo( "event_id4", Instant.now(), Instant.now(), - "tenant", NotificationEvent(eventSource2, listOf(status1, status2)) ) val searchResult = NotificationEventSearchResult( @@ -229,7 +220,6 @@ internal class NotificationEventSearchResultTests { "event_id", lastUpdatedTimeMs, createdTimeMs, - "tenant", sampleEvent ) val searchResult = NotificationEventSearchResult(eventInfo) @@ -270,14 +260,12 @@ internal class NotificationEventSearchResultTests { "event_id1", lastUpdatedTimeMs, createdTimeMs, - "tenant", NotificationEvent(eventSource1, listOf(status1)) ) val eventInfo2 = NotificationEventInfo( "event_id2", lastUpdatedTimeMs, createdTimeMs, - "tenant", NotificationEvent(eventSource2, listOf(status2)) ) val searchResult = NotificationEventSearchResult( @@ -312,7 +300,6 @@ internal class NotificationEventSearchResultTests { "event_id", lastUpdatedTimeMs, createdTimeMs, - "selectedTenant", sampleEvent ) val searchResult = NotificationEventSearchResult(eventInfo) @@ -326,7 +313,6 @@ internal class NotificationEventSearchResultTests { "event_id":"event_id", "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "tenant":"selectedTenant", "event":{ "event_source":{ "title":"title", @@ -380,7 +366,6 @@ internal class NotificationEventSearchResultTests { "event_id", lastUpdatedTimeMs, createdTimeMs, - "selectedTenant", sampleEvent ) val searchResult = NotificationEventSearchResult(eventInfo) @@ -391,7 +376,6 @@ internal class NotificationEventSearchResultTests { "event_id":"event_id", "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "tenant":"selectedTenant", "event":{ "event_source":{ "title":"title", @@ -434,8 +418,7 @@ internal class NotificationEventSearchResultTests { { "event_id":"event_id", "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", - "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "tenant":"selectedTenant" + "created_time_ms":"${createdTimeMs.toEpochMilli()}" } ] } From 21b951def468ac06bef87145822f5ec6927f5863 Mon Sep 17 00:00:00 2001 From: Anantha Krishna Bhatta <31894175+akbhatta@users.noreply.github.com> Date: Thu, 23 Sep 2021 15:48:16 -0700 Subject: [PATCH 015/149] Updated the plugin feature response with allowed config features (#74) [Tests] Updated the unit tests Signed-off-by: @akbhatta --- .../notifications/NotificationConstants.kt | 3 +- .../action/GetPluginFeaturesResponse.kt | 38 +++++++++++++------ .../NotificationsPluginInterfaceTests.kt | 1 + .../action/GetPluginFeaturesResponseTests.kt | 31 +++++++++++++-- 4 files changed, 56 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt index dc16a39f..7a958154 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt @@ -66,7 +66,8 @@ object NotificationConstants { const val TOTAL_HIT_RELATION_TAG = "total_hit_relation" const val QUERY_TAG = "query" const val COMPACT_TAG = "compact" - const val CONFIG_TYPE_LIST_TAG = "config_type_list" + const val ALLOWED_CONFIG_TYPE_LIST_TAG = "allowed_config_type_list" + const val ALLOWED_CONFIG_FEATURE_LIST_TAG = "allowed_config_feature_list" const val PLUGIN_FEATURES_TAG = "plugin_features" const val FEATURE_ALERTING = "alerting" diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt index 681b943e..dde25709 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt @@ -33,7 +33,8 @@ import org.opensearch.common.xcontent.ToXContent import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils -import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TYPE_LIST_TAG +import org.opensearch.commons.notifications.NotificationConstants.ALLOWED_CONFIG_FEATURE_LIST_TAG +import org.opensearch.commons.notifications.NotificationConstants.ALLOWED_CONFIG_TYPE_LIST_TAG import org.opensearch.commons.notifications.NotificationConstants.PLUGIN_FEATURES_TAG import org.opensearch.commons.utils.STRING_READER import org.opensearch.commons.utils.STRING_WRITER @@ -45,7 +46,8 @@ import java.io.IOException * Action Response for getting notification plugin features. */ class GetPluginFeaturesResponse : BaseResponse { - val configTypeList: List + val allowedConfigTypeList: List + val allowedConfigFeatureList: List val pluginFeatures: Map companion object { @@ -63,7 +65,8 @@ class GetPluginFeaturesResponse : BaseResponse { @JvmStatic @Throws(IOException::class) fun parse(parser: XContentParser): GetPluginFeaturesResponse { - var configTypeList: List? = null + var allowedConfigTypeList: List? = null + var allowedConfigFeatureList: List? = null var pluginFeatures: Map? = null XContentParserUtils.ensureExpectedToken( @@ -75,7 +78,8 @@ class GetPluginFeaturesResponse : BaseResponse { val fieldName = parser.currentName() parser.nextToken() when (fieldName) { - CONFIG_TYPE_LIST_TAG -> configTypeList = parser.stringList() + ALLOWED_CONFIG_TYPE_LIST_TAG -> allowedConfigTypeList = parser.stringList() + ALLOWED_CONFIG_FEATURE_LIST_TAG -> allowedConfigFeatureList = parser.stringList() PLUGIN_FEATURES_TAG -> pluginFeatures = parser.mapStrings() else -> { parser.skipChildren() @@ -83,9 +87,10 @@ class GetPluginFeaturesResponse : BaseResponse { } } } - configTypeList ?: throw IllegalArgumentException("$CONFIG_TYPE_LIST_TAG field absent") + allowedConfigTypeList ?: throw IllegalArgumentException("$ALLOWED_CONFIG_TYPE_LIST_TAG field absent") + allowedConfigFeatureList ?: throw IllegalArgumentException("$ALLOWED_CONFIG_TYPE_LIST_TAG field absent") pluginFeatures ?: throw IllegalArgumentException("$PLUGIN_FEATURES_TAG field absent") - return GetPluginFeaturesResponse(configTypeList, pluginFeatures) + return GetPluginFeaturesResponse(allowedConfigTypeList, allowedConfigFeatureList, pluginFeatures) } } @@ -94,18 +99,25 @@ class GetPluginFeaturesResponse : BaseResponse { */ override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { return builder!!.startObject() - .field(CONFIG_TYPE_LIST_TAG, configTypeList) + .field(ALLOWED_CONFIG_TYPE_LIST_TAG, allowedConfigTypeList) + .field(ALLOWED_CONFIG_FEATURE_LIST_TAG, allowedConfigFeatureList) .field(PLUGIN_FEATURES_TAG, pluginFeatures) .endObject() } /** * constructor for creating the class - * @param configTypeList the list of config types supported by plugin + * @param allowedConfigTypeList the list of config types supported by plugin + * @param allowedConfigFeatureList the list of config features supported by plugin * @param pluginFeatures the map of plugin features supported to its value */ - constructor(configTypeList: List, pluginFeatures: Map) { - this.configTypeList = configTypeList + constructor( + allowedConfigTypeList: List, + allowedConfigFeatureList: List, + pluginFeatures: Map + ) { + this.allowedConfigTypeList = allowedConfigTypeList + this.allowedConfigFeatureList = allowedConfigFeatureList this.pluginFeatures = pluginFeatures } @@ -114,7 +126,8 @@ class GetPluginFeaturesResponse : BaseResponse { */ @Throws(IOException::class) constructor(input: StreamInput) : super(input) { - configTypeList = input.readStringList() + allowedConfigTypeList = input.readStringList() + allowedConfigFeatureList = input.readStringList() pluginFeatures = input.readMap(STRING_READER, STRING_READER) } @@ -123,7 +136,8 @@ class GetPluginFeaturesResponse : BaseResponse { */ @Throws(IOException::class) override fun writeTo(output: StreamOutput) { - output.writeStringCollection(configTypeList) + output.writeStringCollection(allowedConfigTypeList) + output.writeStringCollection(allowedConfigFeatureList) output.writeMap(pluginFeatures, STRING_WRITER, STRING_WRITER) } } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt index 77d12ff2..31d47bba 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt @@ -156,6 +156,7 @@ internal class NotificationsPluginInterfaceTests { val request = mock(GetPluginFeaturesRequest::class.java) val response = GetPluginFeaturesResponse( listOf("config_type_1", "config_type_2", "config_type_3"), + listOf("config_feature_1", "config_feature_2", "config_feature_3"), mapOf( Pair("FeatureKey1", "FeatureValue1"), Pair("FeatureKey2", "FeatureValue2"), diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponseTests.kt index 3103825f..930d430f 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponseTests.kt @@ -39,7 +39,7 @@ internal class GetPluginFeaturesResponseTests { expected: GetPluginFeaturesResponse, actual: GetPluginFeaturesResponse ) { - assertEquals(expected.configTypeList, actual.configTypeList) + assertEquals(expected.allowedConfigTypeList, actual.allowedConfigTypeList) assertEquals(expected.pluginFeatures, actual.pluginFeatures) } @@ -47,6 +47,7 @@ internal class GetPluginFeaturesResponseTests { fun `Get Response serialize and deserialize transport object should be equal`() { val response = GetPluginFeaturesResponse( listOf("config_type_1", "config_type_2", "config_type_3"), + listOf("config_feature_1", "config_feature_2", "config_feature_3"), mapOf( Pair("FeatureKey1", "FeatureValue1"), Pair("FeatureKey2", "FeatureValue2"), @@ -61,6 +62,7 @@ internal class GetPluginFeaturesResponseTests { fun `Get Response serialize and deserialize using json config object should be equal`() { val response = GetPluginFeaturesResponse( listOf("config_type_1", "config_type_2", "config_type_3"), + listOf("config_feature_1", "config_feature_2", "config_feature_3"), mapOf( Pair("FeatureKey1", "FeatureValue1"), Pair("FeatureKey2", "FeatureValue2"), @@ -76,6 +78,7 @@ internal class GetPluginFeaturesResponseTests { fun `Get Response should safely ignore extra field in json object`() { val response = GetPluginFeaturesResponse( listOf("config_type_1", "config_type_2", "config_type_3"), + listOf("config_feature_1", "config_feature_2", "config_feature_3"), mapOf( Pair("FeatureKey1", "FeatureValue1"), Pair("FeatureKey2", "FeatureValue2"), @@ -84,7 +87,8 @@ internal class GetPluginFeaturesResponseTests { ) val jsonString = """ { - "config_type_list":["config_type_1", "config_type_2", "config_type_3"], + "allowed_config_type_list":["config_type_1", "config_type_2", "config_type_3"], + "allowed_config_feature_list":["config_feature_1", "config_feature_2", "config_feature_3"], "plugin_features":{ "FeatureKey1":"FeatureValue1", "FeatureKey2":"FeatureValue2", @@ -100,9 +104,27 @@ internal class GetPluginFeaturesResponseTests { } @Test - fun `Get Response should throw exception if config_type_list is absent in json`() { + fun `Get Response should throw exception if allowed_config_type_list is absent in json`() { val jsonString = """ { + "allowed_config_feature_list":["config_feature_1", "config_feature_2", "config_feature_3"], + "plugin_features":{ + "FeatureKey1":"FeatureValue1", + "FeatureKey2":"FeatureValue2", + "FeatureKey3":"FeatureValue3" + } + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { GetPluginFeaturesResponse.parse(it) } + } + } + + @Test + fun `Get Response should throw exception if allowed_config_feature_list is absent in json`() { + val jsonString = """ + { + "allowed_config_type_list":["config_type_1", "config_type_2", "config_type_3"], "plugin_features":{ "FeatureKey1":"FeatureValue1", "FeatureKey2":"FeatureValue2", @@ -119,7 +141,8 @@ internal class GetPluginFeaturesResponseTests { fun `Get Response should throw exception if plugin_features is absent in json`() { val jsonString = """ { - "config_type_list":["config_type_1", "config_type_2", "config_type_3"] + "config_type_list":["config_type_1", "config_type_2", "config_type_3"], + "allowed_config_feature_list":["config_feature_1", "config_feature_2", "config_feature_3"] } """.trimIndent() Assertions.assertThrows(IllegalArgumentException::class.java) { From 486c2e194a145d3719274650cf3cac3537eae3e9 Mon Sep 17 00:00:00 2001 From: Clay Downs <89109232+downsrob@users.noreply.github.com> Date: Thu, 30 Sep 2021 17:41:03 -0700 Subject: [PATCH 016/149] Updates common-utils version to 1.2 (#77) * Updates common-utils version to 1.2 and Uses Maven for 1.2 dependencies Signed-off-by: Clay Downs --- .github/workflows/ci.yml | 15 ++------------- build.gradle | 4 +++- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f83153e..52eea2f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,25 +25,14 @@ jobs: with: java-version: ${{ matrix.java }} - # dependencies: OpenSearch - - name: Checkout OpenSearch - uses: actions/checkout@v2 - with: - repository: 'opensearch-project/OpenSearch' - path: OpenSearch - ref: '1.1' - - name: Build OpenSearch - working-directory: ./OpenSearch - run: ./gradlew publishToMavenLocal - # common-utils - name: Build and Test run: | - ./gradlew build -Dopensearch.version=1.1.0-SNAPSHOT + ./gradlew build -Dopensearch.version=1.2.0-SNAPSHOT - name: Publish to Maven Local run: | - ./gradlew publishToMavenLocal -Dopensearch.version=1.1.0-SNAPSHOT + ./gradlew publishToMavenLocal -Dopensearch.version=1.2.0-SNAPSHOT - name: Upload Coverage Report uses: codecov/codecov-action@v1 diff --git a/build.gradle b/build.gradle index d443ddf4..dc33b6d9 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "1.1.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "1.2.0-SNAPSHOT") kotlin_version = System.getProperty("kotlin.version", "1.4.32") } @@ -21,6 +21,7 @@ buildscript { mavenCentral() maven { url "https://plugins.gradle.org/m2/" } jcenter() + maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } } dependencies { @@ -42,6 +43,7 @@ repositories { mavenCentral() maven { url "https://plugins.gradle.org/m2/" } jcenter() + maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } } ext { From 151646fb017ff0b64713290b786b10046a3aea3a Mon Sep 17 00:00:00 2001 From: "Daniel Doubrovkine (dB.)" Date: Wed, 6 Oct 2021 13:43:20 -0400 Subject: [PATCH 017/149] Publish .md5 and .sha1 signatures. (#79) (#80) * Publish .md5 and .sha1 signatures. Signed-off-by: dblock * Use OpenSearch 1.1. Signed-off-by: dblock --- .github/workflows/push-common-utils-jar.yml | 44 --------------- build.gradle | 10 ++-- scripts/build.sh | 61 +++++++++++++++++++++ 3 files changed, 66 insertions(+), 49 deletions(-) delete mode 100644 .github/workflows/push-common-utils-jar.yml create mode 100755 scripts/build.sh diff --git a/.github/workflows/push-common-utils-jar.yml b/.github/workflows/push-common-utils-jar.yml deleted file mode 100644 index 6a4bc703..00000000 --- a/.github/workflows/push-common-utils-jar.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Upload Common-Utils Jar to Maven - -on: - push: - tags: - - v* -jobs: - upload-common-utils-jar: - runs-on: [ubuntu-16.04] - name: Upload common-utils Jar to Maven - steps: - - name: Checkout Repo - uses: actions/checkout@v2 - - - name: Configure AWS CLI - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: us-east-1 - - - name: Setup Java - uses: actions/setup-java@v1 - with: - java-version: '14' - - - name: Upload common-utils Jar to Maven - env: - passphrase: ${{ secrets.PASSPHRASE }} - run: | - cd .. - export JAVA14_HOME=$JAVA_HOME - aws s3 cp s3://opendistro-docs/github-actions/pgp-public-key . - aws s3 cp s3://opendistro-docs/github-actions/pgp-private-key . - - gpg --import pgp-public-key - gpg --allow-secret-key-import --import pgp-private-key - - mkdir /home/runner/.gradle - aws s3 cp s3://opendistro-docs/github-actions/gradle.properties /home/runner/.gradle/ - - cd common-utils - - ./gradlew publishShadowPublicationToSonatype-stagingRepository -Dcompiler.java=14 -Dbuild.snapshot=false -Djavax.net.ssl.trustStore=$JAVA_HOME/lib/security/cacerts diff --git a/build.gradle b/build.gradle index dc33b6d9..b0a720cf 100644 --- a/build.gradle +++ b/build.gradle @@ -148,6 +148,11 @@ shadowJar { classifier = null } +shadowJar.doLast { + ant.checksum algorithm: 'md5', file: it.archivePath + ant.checksum algorithm: 'sha1', file: it.archivePath +} + task sourcesJar(type: Jar) { classifier = 'sources' from sourceSets.main.allJava @@ -197,9 +202,4 @@ publishing { gradle.startParameter.setShowStacktrace(ShowStacktrace.ALWAYS) gradle.startParameter.setLogLevel(LogLevel.DEBUG) - - signing { - required { gradle.taskGraph.hasTask("publishShadowPublicationToSonatype-stagingRepository") } - sign publishing.publications.shadow - } } diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 00000000..fdc9be57 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# Copyright OpenSearch Contributors. +# SPDX-License-Identifier: Apache-2.0 + +set -ex + +function usage() { + echo "Usage: $0 [args]" + echo "" + echo "Arguments:" + echo -e "-v VERSION\t[Required] OpenSearch version." + echo -e "-s SNAPSHOT\t[Optional] Build a snapshot, default is 'false'." + echo -e "-a ARCHITECTURE\t[Optional] Build architecture, ignored." + echo -e "-o OUTPUT\t[Optional] Output path, default is 'artifacts'." + echo -e "-h help" +} + +while getopts ":h:v:s:o:a:" arg; do + case $arg in + h) + usage + exit 1 + ;; + v) + VERSION=$OPTARG + ;; + s) + SNAPSHOT=$OPTARG + ;; + o) + OUTPUT=$OPTARG + ;; + a) + ARCHITECTURE=$OPTARG + ;; + :) + echo "Error: -${OPTARG} requires an argument" + usage + exit 1 + ;; + ?) + echo "Invalid option: -${arg}" + exit 1 + ;; + esac +done + +if [ -z "$VERSION" ]; then + echo "Error: You must specify the OpenSearch version" + usage + exit 1 +fi + +[[ "$SNAPSHOT" == "true" ]] && VERSION=$VERSION-SNAPSHOT +[ -z "$OUTPUT" ] && OUTPUT=artifacts + +./gradlew build -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT +./gradlew publishShadowPublicationToMavenLocal -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT +mkdir -p $OUTPUT/maven/org/opensearch +cp -r ./build/libs $OUTPUT/maven/org/opensearch/common-utils From cbeba12d5a67e4f0079b93f37b3ce7b51961f69b Mon Sep 17 00:00:00 2001 From: "Daniel Doubrovkine (dB.)" Date: Wed, 6 Oct 2021 17:10:21 -0400 Subject: [PATCH 018/149] Publish source and javadoc checksums. (#81) Signed-off-by: dblock --- build.gradle | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index b0a720cf..92f71cc5 100644 --- a/build.gradle +++ b/build.gradle @@ -148,11 +148,6 @@ shadowJar { classifier = null } -shadowJar.doLast { - ant.checksum algorithm: 'md5', file: it.archivePath - ant.checksum algorithm: 'sha1', file: it.archivePath -} - task sourcesJar(type: Jar) { classifier = 'sources' from sourceSets.main.allJava @@ -163,6 +158,15 @@ task javadocJar(type: Jar) { from javadoc.destinationDir } +tasks.withType(Jar) { task -> + task.doLast { + ant.checksum algorithm: 'md5', file: it.archivePath + ant.checksum algorithm: 'sha1', file: it.archivePath + ant.checksum algorithm: 'sha-256', file: it.archivePath, fileext: '.sha256' + ant.checksum algorithm: 'sha-512', file: it.archivePath, fileext: '.sha512' + } +} + publishing { publications { shadow(MavenPublication) { From e51a67ae47e3382c68d56c7fb05a3bcf01733776 Mon Sep 17 00:00:00 2001 From: Anantha Krishna Bhatta <31894175+akbhatta@users.noreply.github.com> Date: Wed, 13 Oct 2021 12:28:34 -0700 Subject: [PATCH 019/149] Email recipients made nested for backward compatibility with Alerting (#83) [Tests] Unit tests added/updated Signed-off-by: @akbhatta --- .../commons/notifications/model/Email.kt | 15 +- .../commons/notifications/model/EmailGroup.kt | 19 +-- .../notifications/model/EmailRecipient.kt | 119 +++++++++++++++ .../CreateNotificationConfigRequestTests.kt | 18 ++- .../action/GetPluginFeaturesResponseTests.kt | 1 + .../UpdateNotificationConfigRequestTests.kt | 18 ++- .../notifications/model/EmailGroupTests.kt | 90 +++++------ .../model/EmailRecipientTests.kt | 142 ++++++++++++++++++ .../commons/notifications/model/EmailTests.kt | 69 +++------ .../model/NotificationConfigTests.kt | 8 +- .../model/config/ConfigPropertiesTests.kt | 3 +- 11 files changed, 361 insertions(+), 141 deletions(-) create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipient.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/EmailRecipientTests.kt diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt index c8da0f85..86d5f67a 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt @@ -38,8 +38,8 @@ import org.opensearch.commons.notifications.NotificationConstants.EMAIL_ACCOUNT_ import org.opensearch.commons.notifications.NotificationConstants.EMAIL_GROUP_ID_LIST_TAG import org.opensearch.commons.notifications.NotificationConstants.RECIPIENT_LIST_TAG import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.objectList import org.opensearch.commons.utils.stringList -import org.opensearch.commons.utils.validateEmail import java.io.IOException /** @@ -47,15 +47,12 @@ import java.io.IOException */ data class Email( val emailAccountID: String, - val recipients: List, + val recipients: List, val emailGroupIds: List ) : BaseConfigData { init { require(!Strings.isNullOrEmpty(emailAccountID)) { "emailAccountID is null or empty" } - recipients.forEach { - validateEmail(it) - } } companion object { @@ -79,7 +76,7 @@ data class Email( @Throws(IOException::class) fun parse(parser: XContentParser): Email { var emailAccountID: String? = null - var recipients: List = listOf() + var recipients: List = listOf() var emailGroupIds: List = listOf() XContentParserUtils.ensureExpectedToken( @@ -92,7 +89,7 @@ data class Email( parser.nextToken() when (fieldName) { EMAIL_ACCOUNT_ID_TAG -> emailAccountID = parser.text() - RECIPIENT_LIST_TAG -> recipients = parser.stringList() + RECIPIENT_LIST_TAG -> recipients = parser.objectList { EmailRecipient.parse(it) } EMAIL_GROUP_ID_LIST_TAG -> emailGroupIds = parser.stringList() else -> { parser.skipChildren() @@ -111,7 +108,7 @@ data class Email( */ constructor(input: StreamInput) : this( emailAccountID = input.readString(), - recipients = input.readStringList(), + recipients = input.readList(EmailRecipient.reader), emailGroupIds = input.readStringList() ) @@ -120,7 +117,7 @@ data class Email( */ override fun writeTo(output: StreamOutput) { output.writeString(emailAccountID) - output.writeStringCollection(recipients) + output.writeList(recipients) output.writeStringCollection(emailGroupIds) } diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailGroup.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailGroup.kt index f3beecd8..4940cc39 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailGroup.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailGroup.kt @@ -35,23 +35,16 @@ import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.RECIPIENT_LIST_TAG import org.opensearch.commons.utils.logger -import org.opensearch.commons.utils.stringList -import org.opensearch.commons.utils.validateEmail +import org.opensearch.commons.utils.objectList import java.io.IOException /** * Data class representing Email group. */ data class EmailGroup( - val recipients: List + val recipients: List ) : BaseConfigData { - init { - recipients.forEach { - validateEmail(it) - } - } - companion object { private val log by logger(EmailGroup::class.java) @@ -72,7 +65,7 @@ data class EmailGroup( @JvmStatic @Throws(IOException::class) fun parse(parser: XContentParser): EmailGroup { - var recipients: List? = null + var recipients: List? = null XContentParserUtils.ensureExpectedToken( XContentParser.Token.START_OBJECT, @@ -83,7 +76,7 @@ data class EmailGroup( val fieldName = parser.currentName() parser.nextToken() when (fieldName) { - RECIPIENT_LIST_TAG -> recipients = parser.stringList() + RECIPIENT_LIST_TAG -> recipients = parser.objectList { EmailRecipient.parse(it) } else -> { parser.skipChildren() log.info("Unexpected field: $fieldName, while parsing EmailGroup") @@ -100,14 +93,14 @@ data class EmailGroup( * @param input StreamInput stream to deserialize data from. */ constructor(input: StreamInput) : this( - recipients = input.readStringList() + recipients = input.readList(EmailRecipient.reader) ) /** * {@inheritDoc} */ override fun writeTo(output: StreamOutput) { - output.writeStringCollection(recipients) + output.writeList(recipients) } /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipient.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipient.kt new file mode 100644 index 00000000..78c4ae59 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipient.kt @@ -0,0 +1,119 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ +package org.opensearch.commons.notifications.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.NotificationConstants.RECIPIENT_TAG +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.validateEmail +import java.io.IOException + +/** + * Data class representing Email recipient. + */ +data class EmailRecipient( + val recipient: String +) : BaseConfigData { + + init { + validateEmail(recipient) + } + + companion object { + private val log by logger(EmailRecipient::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { EmailRecipient(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): EmailRecipient { + var recipient: String? = null + + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + RECIPIENT_TAG -> recipient = parser.text() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing EmailRecipient") + } + } + } + recipient ?: throw IllegalArgumentException("$RECIPIENT_TAG field absent") + return EmailRecipient(recipient) + } + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + recipient = input.readString() + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(recipient) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(RECIPIENT_TAG, recipient) + .endObject() + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt index f190e7f6..28f337b8 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt @@ -36,6 +36,7 @@ import org.opensearch.commons.notifications.model.Chime import org.opensearch.commons.notifications.model.ConfigType import org.opensearch.commons.notifications.model.Email import org.opensearch.commons.notifications.model.EmailGroup +import org.opensearch.commons.notifications.model.EmailRecipient import org.opensearch.commons.notifications.model.MethodType import org.opensearch.commons.notifications.model.NotificationConfig import org.opensearch.commons.notifications.model.Slack @@ -84,7 +85,7 @@ internal class CreateNotificationConfigRequestTests { } private fun createEmailGroupContentConfigObject(): NotificationConfig { - val sampleEmailGroup = EmailGroup(listOf("dummy@company.com")) + val sampleEmailGroup = EmailGroup(listOf(EmailRecipient("dummy@company.com"))) return NotificationConfig( "name", "description", @@ -98,7 +99,7 @@ internal class CreateNotificationConfigRequestTests { private fun createEmailContentConfigObject(): NotificationConfig { val sampleEmail = Email( emailAccountID = "sample_1@dummy.com", - recipients = listOf("sample_2@dummy.com"), + recipients = listOf(EmailRecipient("sample_2@dummy.com")), emailGroupIds = listOf("sample_3@dummy.com") ) return NotificationConfig( @@ -365,7 +366,7 @@ internal class CreateNotificationConfigRequestTests { @Test fun `Create config should deserialize json object using parser Email Group`() { - val sampleEmailGroup = EmailGroup(listOf("dummy@company.com")) + val sampleEmailGroup = EmailGroup(listOf(EmailRecipient("dummy@company.com"))) val config = NotificationConfig( "name", "description", @@ -384,7 +385,7 @@ internal class CreateNotificationConfigRequestTests { "config_type":"email_group", "feature_list":["index_management"], "is_enabled":true, - "email_group":{"recipient_list":["dummy@company.com"]} + "email_group":{"recipient_list":[{"recipient":"dummy@company.com"}]} } } """.trimIndent() @@ -396,7 +397,7 @@ internal class CreateNotificationConfigRequestTests { fun `Update config should deserialize json object using parser Email`() { val sampleEmail = Email( emailAccountID = "sample_1@dummy.com", - recipients = listOf("sample_2@dummy.com"), + recipients = listOf(EmailRecipient("sample_2@dummy.com")), emailGroupIds = listOf("sample_3@dummy.com") ) val config = NotificationConfig( @@ -417,8 +418,11 @@ internal class CreateNotificationConfigRequestTests { "config_type":"email", "feature_list":["index_management"], "is_enabled":true, - "email":{"email_account_id":"sample_1@dummy.com","recipient_list":["sample_2@dummy.com"], - "email_group_id_list":["sample_3@dummy.com"] } + "email":{ + "email_account_id":"sample_1@dummy.com", + "recipient_list":[{"recipient":"sample_2@dummy.com"}], + "email_group_id_list":["sample_3@dummy.com"] + } } } """.trimIndent() diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponseTests.kt index 930d430f..c9924269 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponseTests.kt @@ -40,6 +40,7 @@ internal class GetPluginFeaturesResponseTests { actual: GetPluginFeaturesResponse ) { assertEquals(expected.allowedConfigTypeList, actual.allowedConfigTypeList) + assertEquals(expected.allowedConfigFeatureList, actual.allowedConfigFeatureList) assertEquals(expected.pluginFeatures, actual.pluginFeatures) } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt index 2dfcff69..9599d60e 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt @@ -36,6 +36,7 @@ import org.opensearch.commons.notifications.model.Chime import org.opensearch.commons.notifications.model.ConfigType import org.opensearch.commons.notifications.model.Email import org.opensearch.commons.notifications.model.EmailGroup +import org.opensearch.commons.notifications.model.EmailRecipient import org.opensearch.commons.notifications.model.MethodType import org.opensearch.commons.notifications.model.NotificationConfig import org.opensearch.commons.notifications.model.Slack @@ -84,7 +85,7 @@ internal class UpdateNotificationConfigRequestTests { } private fun createEmailGroupContentConfigObject(): NotificationConfig { - val sampleEmailGroup = EmailGroup(listOf("dummy@company.com")) + val sampleEmailGroup = EmailGroup(listOf(EmailRecipient("dummy@company.com"))) return NotificationConfig( "name", "description", @@ -98,7 +99,7 @@ internal class UpdateNotificationConfigRequestTests { private fun createEmailContentConfigObject(): NotificationConfig { val sampleEmail = Email( emailAccountID = "sample_1@dummy.com", - recipients = listOf("sample_2@dummy.com"), + recipients = listOf(EmailRecipient("sample_2@dummy.com")), emailGroupIds = listOf("sample_3@dummy.com") ) return NotificationConfig( @@ -334,7 +335,7 @@ internal class UpdateNotificationConfigRequestTests { @Test fun `Update config should deserialize json object using parser Email Group`() { - val sampleEmailGroup = EmailGroup(listOf("dummy@company.com")) + val sampleEmailGroup = EmailGroup(listOf(EmailRecipient("dummy@company.com"))) val config = NotificationConfig( "name", "description", @@ -353,7 +354,7 @@ internal class UpdateNotificationConfigRequestTests { "config_type":"email_group", "feature_list":["index_management"], "is_enabled":true, - "email_group":{"recipient_list":["dummy@company.com"]} + "email_group":{"recipient_list":[{"recipient":"dummy@company.com"}]} } } """.trimIndent() @@ -366,7 +367,7 @@ internal class UpdateNotificationConfigRequestTests { fun `Update config should deserialize json object using parser Email`() { val sampleEmail = Email( emailAccountID = "sample_1@dummy.com", - recipients = listOf("sample_2@dummy.com"), + recipients = listOf(EmailRecipient("sample_2@dummy.com")), emailGroupIds = listOf("sample_3@dummy.com") ) val config = NotificationConfig( @@ -387,8 +388,11 @@ internal class UpdateNotificationConfigRequestTests { "config_type":"email", "feature_list":["index_management"], "is_enabled":true, - "email":{"email_account_id":"sample_1@dummy.com","recipient_list":["sample_2@dummy.com"], - "email_group_id_list":["sample_3@dummy.com"] } + "email":{ + "email_account_id":"sample_1@dummy.com", + "recipient_list":[{"recipient":"sample_2@dummy.com"}], + "email_group_id_list":["sample_3@dummy.com"] + } } } """.trimIndent() diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/EmailGroupTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/EmailGroupTests.kt index 5146d785..dbf2f77b 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/EmailGroupTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/EmailGroupTests.kt @@ -29,7 +29,6 @@ package org.opensearch.commons.notifications.model import com.fasterxml.jackson.core.JsonParseException import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString @@ -37,56 +36,26 @@ import org.opensearch.commons.utils.recreateObject internal class EmailGroupTests { - private fun checkValidEmailAddress(emailAddress: String) { - assertDoesNotThrow("should accept $emailAddress") { - EmailGroup(listOf(emailAddress)) - } - } - - private fun checkInvalidEmailAddress(emailAddress: String) { - assertThrows("Should throw an Exception for invalid email $emailAddress") { - EmailGroup(listOf(emailAddress)) - } - } - - @Test - fun `EmailGroup should accept valid email address`() { - checkValidEmailAddress("email1234@email.com") - checkValidEmailAddress("email+1234@email.com") - checkValidEmailAddress("email-1234@email.com") - checkValidEmailAddress("email_1234@email.com") - checkValidEmailAddress("email.1234@email.com") - checkValidEmailAddress("e.ma_il-1+2@test-email-domain.co.uk") - checkValidEmailAddress("email-.+_=#|@domain.com") - checkValidEmailAddress("e@mail.com") - } - - @Test - fun `EmailGroup should throw exception for invalid email address`() { - checkInvalidEmailAddress("email") - checkInvalidEmailAddress("email@") - checkInvalidEmailAddress("email@1234@email.com") - checkInvalidEmailAddress(".email@email.com") - checkInvalidEmailAddress("email.@email.com") - checkInvalidEmailAddress("email..1234@email.com") - checkInvalidEmailAddress("email@email..com") - checkInvalidEmailAddress("email@.com") - checkInvalidEmailAddress("email@email.com.") - checkInvalidEmailAddress("email@.email.com") - checkInvalidEmailAddress("email@email.com-") - checkInvalidEmailAddress("email@email_domain.com") - } - @Test fun `EmailGroup serialize and deserialize transport object should be equal`() { - val sampleEmailGroup = EmailGroup(listOf("email1@email.com", "email2@email.com")) + val sampleEmailGroup = EmailGroup( + listOf( + EmailRecipient("email1@email.com"), + EmailRecipient("email2@email.com") + ) + ) val recreatedObject = recreateObject(sampleEmailGroup) { EmailGroup(it) } assertEquals(sampleEmailGroup, recreatedObject) } @Test fun `EmailGroup serialize and deserialize using json object should be equal`() { - val sampleEmailGroup = EmailGroup(listOf("email1@email.com", "email2@email.com")) + val sampleEmailGroup = EmailGroup( + listOf( + EmailRecipient("email1@email.com"), + EmailRecipient("email2@email.com") + ) + ) val jsonString = getJsonString(sampleEmailGroup) val recreatedObject = createObjectFromJsonString(jsonString) { EmailGroup.parse(it) } assertEquals(sampleEmailGroup, recreatedObject) @@ -94,12 +63,17 @@ internal class EmailGroupTests { @Test fun `EmailGroup should deserialize json object using parser`() { - val sampleEmailGroup = EmailGroup(listOf("email1@email.com", "email2@email.com")) + val sampleEmailGroup = EmailGroup( + listOf( + EmailRecipient("email1@email.com"), + EmailRecipient("email2@email.com") + ) + ) val jsonString = """ { "recipient_list":[ - "${sampleEmailGroup.recipients[0]}", - "${sampleEmailGroup.recipients[1]}" + {"recipient":"${sampleEmailGroup.recipients[0].recipient}"}, + {"recipient":"${sampleEmailGroup.recipients[1].recipient}"} ] }" """.trimIndent() @@ -117,12 +91,17 @@ internal class EmailGroupTests { @Test fun `EmailGroup should throw exception when recipients is replaced with recipients2 in json object`() { - val sampleEmailGroup = EmailGroup(listOf("email1@email.com", "email2@email.com")) + val sampleEmailGroup = EmailGroup( + listOf( + EmailRecipient("email1@email.com"), + EmailRecipient("email2@email.com") + ) + ) val jsonString = """ { "recipient_list2":[ - "${sampleEmailGroup.recipients[0]}", - "${sampleEmailGroup.recipients[1]}" + {"recipient":"${sampleEmailGroup.recipients[0]}"}, + {"recipient":"${sampleEmailGroup.recipients[1]}"} ] }" """.trimIndent() @@ -133,8 +112,17 @@ internal class EmailGroupTests { @Test fun `EmailGroup should safely ignore extra field in json object`() { - val sampleEmailGroup = EmailGroup(listOf("email@email.com")) - val jsonString = "{\"recipient_list\":[\"${sampleEmailGroup.recipients[0]}\"], \"another\":\"field\"}" + val sampleEmailGroup = EmailGroup(listOf(EmailRecipient("email1@email.com"))) + val jsonString = """ + { + "recipient_list":[ + {"recipient":"${sampleEmailGroup.recipients[0].recipient}"} + ], + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + }" + """.trimIndent() val recreatedObject = createObjectFromJsonString(jsonString) { EmailGroup.parse(it) } assertEquals(sampleEmailGroup, recreatedObject) } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/EmailRecipientTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/EmailRecipientTests.kt new file mode 100644 index 00000000..c2f2d36d --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/EmailRecipientTests.kt @@ -0,0 +1,142 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ +package org.opensearch.commons.notifications.model + +import com.fasterxml.jackson.core.JsonParseException +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows +import org.opensearch.commons.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import org.opensearch.commons.utils.recreateObject + +internal class EmailRecipientTests { + + private fun checkValidEmailAddress(emailAddress: String) { + assertDoesNotThrow("should accept $emailAddress") { + EmailRecipient(emailAddress) + } + } + + private fun checkInvalidEmailAddress(emailAddress: String) { + assertThrows("Should throw an Exception for invalid email $emailAddress") { + EmailRecipient(emailAddress) + } + } + + @Test + fun `EmailRecipient should accept valid email address`() { + checkValidEmailAddress("email1234@email.com") + checkValidEmailAddress("email+1234@email.com") + checkValidEmailAddress("email-1234@email.com") + checkValidEmailAddress("email_1234@email.com") + checkValidEmailAddress("email.1234@email.com") + checkValidEmailAddress("e.ma_il-1+2@test-email-domain.co.uk") + checkValidEmailAddress("email-.+_=#|@domain.com") + checkValidEmailAddress("e@mail.com") + } + + @Test + fun `EmailRecipient should throw exception for invalid email address`() { + checkInvalidEmailAddress("email") + checkInvalidEmailAddress("email@") + checkInvalidEmailAddress("email@1234@email.com") + checkInvalidEmailAddress(".email@email.com") + checkInvalidEmailAddress("email.@email.com") + checkInvalidEmailAddress("email..1234@email.com") + checkInvalidEmailAddress("email@email..com") + checkInvalidEmailAddress("email@.com") + checkInvalidEmailAddress("email@email.com.") + checkInvalidEmailAddress("email@.email.com") + checkInvalidEmailAddress("email@email.com-") + checkInvalidEmailAddress("email@email_domain.com") + } + + @Test + fun `EmailRecipient serialize and deserialize transport object should be equal`() { + val sampleEmailRecipient = EmailRecipient("email1@email.com") + val recreatedObject = recreateObject(sampleEmailRecipient) { EmailRecipient(it) } + assertEquals(sampleEmailRecipient, recreatedObject) + } + + @Test + fun `EmailRecipient serialize and deserialize using json object should be equal`() { + val sampleEmailRecipient = EmailRecipient("email1@email.com") + val jsonString = getJsonString(sampleEmailRecipient) + val recreatedObject = createObjectFromJsonString(jsonString) { EmailRecipient.parse(it) } + assertEquals(sampleEmailRecipient, recreatedObject) + } + + @Test + fun `EmailRecipient should deserialize json object using parser`() { + val sampleEmailRecipient = EmailRecipient("email1@email.com") + val jsonString = """ + { + "recipient": "${sampleEmailRecipient.recipient}" + }" + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { EmailRecipient.parse(it) } + assertEquals(sampleEmailRecipient, recreatedObject) + } + + @Test + fun `EmailRecipient should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { EmailRecipient.parse(it) } + } + } + + @Test + fun `EmailRecipient should throw exception when recipients is replaced with recipients2 in json object`() { + val sampleEmailRecipient = EmailRecipient("email1@email.com") + val jsonString = """ + { + "recipient2": "${sampleEmailRecipient.recipient}" + }" + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { EmailRecipient.parse(it) } + } + } + + @Test + fun `EmailRecipient should safely ignore extra field in json object`() { + val sampleEmailRecipient = EmailRecipient("email@email.com") + val jsonString = """ + { + "recipient": "${sampleEmailRecipient.recipient}", + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + }" + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { EmailRecipient.parse(it) } + assertEquals(sampleEmailRecipient, recreatedObject) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/EmailTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/EmailTests.kt index 6590f08e..8e4772f5 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/EmailTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/EmailTests.kt @@ -29,7 +29,6 @@ package org.opensearch.commons.notifications.model import com.fasterxml.jackson.core.JsonParseException import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString @@ -37,51 +36,14 @@ import org.opensearch.commons.utils.recreateObject internal class EmailTests { - private fun checkValidEmailAddress(emailAddress: String) { - assertDoesNotThrow("should accept $emailAddress") { - Email("sampleId", listOf(emailAddress), listOf()) - } - } - - private fun checkInvalidEmailAddress(emailAddress: String) { - assertThrows("Should throw an Exception for invalid email $emailAddress") { - Email("sampleId", listOf(emailAddress), listOf()) - } - } - - @Test - fun `Email should accept valid email address`() { - checkValidEmailAddress("email1234@email.com") - checkValidEmailAddress("email+1234@email.com") - checkValidEmailAddress("email-1234@email.com") - checkValidEmailAddress("email_1234@email.com") - checkValidEmailAddress("email.1234@email.com") - checkValidEmailAddress("e.ma_il-1+2@test-email-domain.co.uk") - checkValidEmailAddress("email-.+_=#|@domain.com") - checkValidEmailAddress("e@mail.com") - } - - @Test - fun `Email should throw exception for invalid email address`() { - checkInvalidEmailAddress("email") - checkInvalidEmailAddress("email@") - checkInvalidEmailAddress("email@1234@email.com") - checkInvalidEmailAddress(".email@email.com") - checkInvalidEmailAddress("email.@email.com") - checkInvalidEmailAddress("email..1234@email.com") - checkInvalidEmailAddress("email@email..com") - checkInvalidEmailAddress("email@.com") - checkInvalidEmailAddress("email@email.com.") - checkInvalidEmailAddress("email@.email.com") - checkInvalidEmailAddress("email@email.com-") - checkInvalidEmailAddress("email@email_domain.com") - } - @Test fun `Email serialize and deserialize transport object should be equal`() { val sampleEmail = Email( "sampleAccountId", - listOf("email1@email.com", "email2@email.com"), + listOf( + EmailRecipient("email1@email.com"), + EmailRecipient("email2@email.com") + ), listOf("sample_group_id_1", "sample_group_id_2") ) val recreatedObject = recreateObject(sampleEmail) { Email(it) } @@ -92,7 +54,10 @@ internal class EmailTests { fun `Email serialize and deserialize using json object should be equal`() { val sampleEmail = Email( "sampleAccountId", - listOf("email1@email.com", "email2@email.com"), + listOf( + EmailRecipient("email1@email.com"), + EmailRecipient("email2@email.com") + ), listOf("sample_group_id_1", "sample_group_id_2") ) val jsonString = getJsonString(sampleEmail) @@ -104,15 +69,18 @@ internal class EmailTests { fun `Email should deserialize json object using parser`() { val sampleEmail = Email( "sampleAccountId", - listOf("email1@email.com", "email2@email.com"), + listOf( + EmailRecipient("email1@email.com"), + EmailRecipient("email2@email.com") + ), listOf("sample_group_id_1", "sample_group_id_2") ) val jsonString = """ { "email_account_id":"${sampleEmail.emailAccountID}", "recipient_list":[ - "${sampleEmail.recipients[0]}", - "${sampleEmail.recipients[1]}" + {"recipient":"${sampleEmail.recipients[0].recipient}"}, + {"recipient":"${sampleEmail.recipients[1].recipient}"} ], "email_group_id_list":[ "${sampleEmail.emailGroupIds[0]}", @@ -136,15 +104,18 @@ internal class EmailTests { fun `Email should throw exception when emailAccountID is replaced with emailAccountID2 in json object`() { val sampleEmail = Email( "sampleAccountId", - listOf("email1@email.com", "email2@email.com"), + listOf( + EmailRecipient("email1@email.com"), + EmailRecipient("email2@email.com") + ), listOf("sample_group_id_1", "sample_group_id_2") ) val jsonString = """ { "email_account_id2":"${sampleEmail.emailAccountID}", "recipient_list":[ - "${sampleEmail.recipients[0]}", - "${sampleEmail.recipients[1]}" + {"recipient":"${sampleEmail.recipients[0]}"}, + {"recipient":"${sampleEmail.recipients[1]}"} ], "email_group_id_list":[ "${sampleEmail.emailGroupIds[0]}", diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt index c7126ab0..0a96c8ed 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt @@ -126,7 +126,7 @@ internal class NotificationConfigTests { @Test fun `Config serialize and deserialize with email object should be equal`() { - val sampleEmail = Email("id_1234567890", listOf("email@domain.com"), listOf("groupId")) + val sampleEmail = Email("id_1234567890", listOf(EmailRecipient("email@domain.com")), listOf("groupId")) val sampleConfig = NotificationConfig( "name", "description", @@ -140,7 +140,7 @@ internal class NotificationConfigTests { @Test fun `Config serialize and deserialize with json email object should be equal`() { - val sampleEmail = Email("id_1234567890", listOf("email@domain.com"), listOf("groupId")) + val sampleEmail = Email("id_1234567890", listOf(EmailRecipient("email@domain.com")), listOf("groupId")) val sampleConfig = NotificationConfig( "name", "description", @@ -184,7 +184,7 @@ internal class NotificationConfigTests { @Test fun `Config serialize and deserialize with json emailGroup object should be equal`() { - val sampleEmailGroup = EmailGroup(listOf("email@domain.com")) + val sampleEmailGroup = EmailGroup(listOf(EmailRecipient("email@domain.com"))) val sampleConfig = NotificationConfig( "name", "description", @@ -199,7 +199,7 @@ internal class NotificationConfigTests { @Test fun `Config serialize and deserialize with emailGroup object should be equal`() { - val sampleEmailGroup = EmailGroup(listOf("email@domain.com")) + val sampleEmailGroup = EmailGroup(listOf(EmailRecipient("email@domain.com"))) val sampleConfig = NotificationConfig( "name", "description", diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/config/ConfigPropertiesTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/config/ConfigPropertiesTests.kt index 0217c53f..80a20b88 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/config/ConfigPropertiesTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/config/ConfigPropertiesTests.kt @@ -5,6 +5,7 @@ import org.opensearch.commons.notifications.model.Chime import org.opensearch.commons.notifications.model.ConfigType import org.opensearch.commons.notifications.model.Email import org.opensearch.commons.notifications.model.EmailGroup +import org.opensearch.commons.notifications.model.EmailRecipient import org.opensearch.commons.notifications.model.MethodType import org.opensearch.commons.notifications.model.Slack import org.opensearch.commons.notifications.model.SmtpAccount @@ -72,7 +73,7 @@ internal class ConfigPropertiesTests { @Test fun `Validate config data parse EmailGroup`() { - val sampleEmailGroup = EmailGroup(listOf("email1@email.com", "email2@email.com")) + val sampleEmailGroup = EmailGroup(listOf(EmailRecipient("email1@email.com"), EmailRecipient("email2@email.com"))) val jsonString = getJsonString(sampleEmailGroup) val recreatedObject = createObjectFromJsonString(jsonString) { createConfigData(ConfigType.EMAIL_GROUP, it) } assertEquals(sampleEmailGroup, recreatedObject) From 32f0c6febebefb72d12fc1ff51c25a2c573c13b3 Mon Sep 17 00:00:00 2001 From: Mohammad Qureshi <47198598+qreshi@users.noreply.github.com> Date: Tue, 2 Nov 2021 15:13:11 -0700 Subject: [PATCH 020/149] Update copyright notice (#90) Signed-off-by: Mohammad Qureshi --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b2778b5d..fc95f76e 100644 --- a/README.md +++ b/README.md @@ -44,4 +44,4 @@ This project is licensed under the [Apache v2.0 License](LICENSE.txt). ## Copyright -Copyright 2020-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +Copyright OpenSearch Contributors. See [NOTICE](NOTICE.txt) for details. From 225333160429e50b37ca4c8f6bb1065b3b78457d Mon Sep 17 00:00:00 2001 From: Marc Handalian Date: Thu, 4 Nov 2021 07:16:42 -0700 Subject: [PATCH 021/149] Update maven publication to include cksums. (#91) This change adds a local staging repo task that will include cksums. It will also update build.sh to use this new task and copy the contents of the staging repo to the output directory. The maven publish plugin will not include these cksums when publishing to maven local but will when published to a separate folder. Signed-off-by: Marc Handalian --- build.gradle | 16 ++++++---------- scripts/build.sh | 3 ++- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/build.gradle b/build.gradle index 92f71cc5..92e442ca 100644 --- a/build.gradle +++ b/build.gradle @@ -157,17 +157,13 @@ task javadocJar(type: Jar) { classifier = 'javadoc' from javadoc.destinationDir } - -tasks.withType(Jar) { task -> - task.doLast { - ant.checksum algorithm: 'md5', file: it.archivePath - ant.checksum algorithm: 'sha1', file: it.archivePath - ant.checksum algorithm: 'sha-256', file: it.archivePath, fileext: '.sha256' - ant.checksum algorithm: 'sha-512', file: it.archivePath, fileext: '.sha512' - } -} - publishing { + repositories { + maven { + name = 'staging' + url = "${rootProject.buildDir}/local-staging-repo" + } + } publications { shadow(MavenPublication) { project.shadow.component(it) diff --git a/scripts/build.sh b/scripts/build.sh index fdc9be57..891e27fc 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -57,5 +57,6 @@ fi ./gradlew build -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT ./gradlew publishShadowPublicationToMavenLocal -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT +./gradlew publishShadowPublicationToStagingRepository -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT mkdir -p $OUTPUT/maven/org/opensearch -cp -r ./build/libs $OUTPUT/maven/org/opensearch/common-utils +cp -r ./build/local-staging-repo/org/opensearch/common-utils $OUTPUT/maven/org/opensearch/common-utils From 2679a01d39a1f0daf87201ff9fe24a6f04b77ea1 Mon Sep 17 00:00:00 2001 From: Ashish Agrawal Date: Fri, 5 Nov 2021 16:24:52 -0700 Subject: [PATCH 022/149] Add release notes for version 1.2.0.0 (#92) * Add release notes for version 1.2.0.0 Signed-off-by: Ashish Agrawal --- ...pensearch-common-utils.release-notes-1.2.0.0.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 release-notes/opensearch-common-utils.release-notes-1.2.0.0.md diff --git a/release-notes/opensearch-common-utils.release-notes-1.2.0.0.md b/release-notes/opensearch-common-utils.release-notes-1.2.0.0.md new file mode 100644 index 00000000..afb92873 --- /dev/null +++ b/release-notes/opensearch-common-utils.release-notes-1.2.0.0.md @@ -0,0 +1,14 @@ +## Version 1.2.0.0 2021-11-05 + +Compatible with OpenSearch 1.2.0 + +### Infrastructure + + * Updates common-utils version to 1.2 ([#77](https://github.com/opensearch-project/common-utils/pull/77)) + * Update maven publication to include cksums. ([#91](https://github.com/opensearch-project/common-utils/pull/91)) + +### Documentation + + * Add themed logo to README ([#41](https://github.com/opensearch-project/common-utils/pull/41)) + * Update copyright notice ([#90](https://github.com/opensearch-project/common-utils/pull/90)) + * Add release notes for version 1.2.0.0 ([#92](https://github.com/opensearch-project/common-utils/pull/92)) \ No newline at end of file From 84bb45720fcb8570aad203547cd17354541bebf6 Mon Sep 17 00:00:00 2001 From: Ashish Agrawal Date: Mon, 8 Nov 2021 16:06:09 -0800 Subject: [PATCH 023/149] Fix copyright notice and add DCO check workflow (#94) Signed-off-by: Ashish Agrawal --- .github/workflows/dco.yml | 18 ++++++++++++++ CONTRIBUTING.md | 49 ++++++++++++++++++++++++++++++++++++++- NOTICE | 4 ++-- 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/dco.yml diff --git a/.github/workflows/dco.yml b/.github/workflows/dco.yml new file mode 100644 index 00000000..53ed5304 --- /dev/null +++ b/.github/workflows/dco.yml @@ -0,0 +1,18 @@ +name: Developer Certificate of Origin Check + +on: [pull_request] + +jobs: + check: + runs-on: ubuntu-latest + + steps: + - name: Get PR Commits + id: 'get-pr-commits' + uses: tim-actions/get-pr-commits@v1.1.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + - name: DCO Check + uses: tim-actions/dco@v1.1.0 + with: + commits: ${{ steps.get-pr-commits.outputs.commits }} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c970f9d3..c25787ad 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,51 @@ ## Contributing to this Project OpenSearch is a community project that is built and maintained by people just like **you**. -[This document](https://github.com/opensearch-project/.github/blob/main/CONTRIBUTING.md) explains how you can contribute to this and related projects. \ No newline at end of file +[This document](https://github.com/opensearch-project/.github/blob/main/CONTRIBUTING.md) explains how you can contribute to this and related projects. + +## Developer Certificate of Origin + +OpenSearch is an open source product released under the Apache 2.0 license (see either [the Apache site](https://www.apache.org/licenses/LICENSE-2.0) or the [LICENSE.txt file](LICENSE.txt)). The Apache 2.0 license allows you to freely use, modify, distribute, and sell your own products that include Apache 2.0 licensed software. + +We respect intellectual property rights of others and we want to make sure all incoming contributions are correctly attributed and licensed. A Developer Certificate of Origin (DCO) is a lightweight mechanism to do that. + +The DCO is a declaration attached to every contribution made by every developer. In the commit message of the contribution, the developer simply adds a `Signed-off-by` statement and thereby agrees to the DCO, which you can find below or at [DeveloperCertificate.org](http://developercertificate.org/). + +``` +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the + best of my knowledge, is covered under an appropriate open + source license and I have the right under that license to + submit that work with modifications, whether created in whole + or in part by me, under the same open source license (unless + I am permitted to submit under a different license), as + Indicated in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including + all personal information I submit with it, including my + sign-off) is maintained indefinitely and may be redistributed + consistent with this project or the open source license(s) + involved. + ``` + +We require that every contribution to OpenSearch is signed with a Developer Certificate of Origin. Additionally, please use your real name. We do not accept anonymous contributors nor those utilizing pseudonyms. + +Each commit must include a DCO which looks like this + +``` +Signed-off-by: Jane Smith +``` + +You may type this line on your own when writing your commit messages. However, if your user.name and user.email are set in your git configs, you can use `-s` or `– – signoff` to add the `Signed-off-by` line to the end of the commit message. \ No newline at end of file diff --git a/NOTICE b/NOTICE index be83767d..6c7dc983 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ -OpenSearch -Copyright 2021 OpenSearch Contributors +OpenSearch (https://opensearch.org/) +Copyright OpenSearch Contributors This product includes software developed by Elasticsearch (http://www.elastic.co). From eb79baeac41e75fa09ee8b9c3c5778ab40fb5937 Mon Sep 17 00:00:00 2001 From: Marc Handalian Date: Fri, 12 Nov 2021 06:29:39 -0800 Subject: [PATCH 024/149] Update build.sh script to include optional platform param. (#95) Signed-off-by: Marc Handalian --- scripts/build.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/build.sh b/scripts/build.sh index 891e27fc..f68dc52e 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -11,12 +11,13 @@ function usage() { echo "Arguments:" echo -e "-v VERSION\t[Required] OpenSearch version." echo -e "-s SNAPSHOT\t[Optional] Build a snapshot, default is 'false'." + echo -e "-p PLATFORM\t[Optional] Platform, ignored." echo -e "-a ARCHITECTURE\t[Optional] Build architecture, ignored." echo -e "-o OUTPUT\t[Optional] Output path, default is 'artifacts'." echo -e "-h help" } -while getopts ":h:v:s:o:a:" arg; do +while getopts ":h:v:s:o:p:a:" arg; do case $arg in h) usage @@ -31,6 +32,9 @@ while getopts ":h:v:s:o:a:" arg; do o) OUTPUT=$OPTARG ;; + p) + PLATFORM=$OPTARG + ;; a) ARCHITECTURE=$OPTARG ;; From 2ebb5e66bdc1ce44b25daabe5dbb73517046e0de Mon Sep 17 00:00:00 2001 From: Ryan Bogan <10944539+ryanbogan@users.noreply.github.com> Date: Fri, 12 Nov 2021 17:37:12 -0500 Subject: [PATCH 025/149] Add codeowners support for repo (#96) Signed-off-by: Ryan Bogan --- .github/CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..b57ed5c8 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# This should match the owning team set up in https://github.com/orgs/opensearch-project/teams +* @opensearch-project/common-utils \ No newline at end of file From 19ca2c084c7e306dde88246cd0f1c749c6fa47b4 Mon Sep 17 00:00:00 2001 From: Ashish Agrawal Date: Wed, 1 Dec 2021 16:48:53 -0800 Subject: [PATCH 026/149] Bump version to 1.3 (#99) Signed-off-by: Ashish Agrawal --- .github/workflows/ci.yml | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 52eea2f1..badeafda 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,11 +28,11 @@ jobs: # common-utils - name: Build and Test run: | - ./gradlew build -Dopensearch.version=1.2.0-SNAPSHOT + ./gradlew build -Dopensearch.version=1.3.0-SNAPSHOT - name: Publish to Maven Local run: | - ./gradlew publishToMavenLocal -Dopensearch.version=1.2.0-SNAPSHOT + ./gradlew publishToMavenLocal -Dopensearch.version=1.3.0-SNAPSHOT - name: Upload Coverage Report uses: codecov/codecov-action@v1 diff --git a/build.gradle b/build.gradle index 92e442ca..9b97f0ac 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "1.2.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "1.3.0-SNAPSHOT") kotlin_version = System.getProperty("kotlin.version", "1.4.32") } From 643157d7e21c462cba4fbc3c64b36f6efa43fef6 Mon Sep 17 00:00:00 2001 From: "Daniel Doubrovkine (dB.)" Date: Tue, 4 Jan 2022 19:26:01 +0000 Subject: [PATCH 027/149] Auto-increment version on new release tags. (#106) Signed-off-by: dblock --- .github/workflows/ci.yml | 4 ++-- .github/workflows/version.yml | 42 +++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/version.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index badeafda..c35e0c0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,11 +28,11 @@ jobs: # common-utils - name: Build and Test run: | - ./gradlew build -Dopensearch.version=1.3.0-SNAPSHOT + ./gradlew build - name: Publish to Maven Local run: | - ./gradlew publishToMavenLocal -Dopensearch.version=1.3.0-SNAPSHOT + ./gradlew publishToMavenLocal - name: Upload Coverage Report uses: codecov/codecov-action@v1 diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml new file mode 100644 index 00000000..196a690d --- /dev/null +++ b/.github/workflows/version.yml @@ -0,0 +1,42 @@ +name: Increment Version + +on: + push: + tags: + - '*.*.*.*' + +jobs: + build: + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v2 + - name: Fetch Tag and Version Information + run: | + TAG=$(echo "${GITHUB_REF#refs/*/}") + CURRENT_VERSION_ARRAY=($(echo "$TAG" | tr . '\n')) + BASE=$(IFS=. ; echo "${CURRENT_VERSION_ARRAY[*]:0:2}") + CURRENT_VERSION=$(IFS=. ; echo "${CURRENT_VERSION_ARRAY[*]:0:3}") + CURRENT_VERSION_ARRAY[2]=$((CURRENT_VERSION_ARRAY[2]+1)) + NEXT_VERSION=$(IFS=. ; echo "${CURRENT_VERSION_ARRAY[*]:0:3}") + echo "TAG=$TAG" >> $GITHUB_ENV + echo "BASE=$BASE" >> $GITHUB_ENV + echo "CURRENT_VERSION=$CURRENT_VERSION" >> $GITHUB_ENV + echo "NEXT_VERSION=$NEXT_VERSION" >> $GITHUB_ENV + - uses: actions/checkout@v2 + with: + ref: ${{ env.BASE }} + - name: Increment Version + run: | + echo Incrementing $CURRENT_VERSION to $NEXT_VERSION + sed -i "s/$CURRENT_VERSION-SNAPSHOT/$NEXT_VERSION-SNAPSHOT/g" build.gradle + - name: Create Pull Request + uses: peter-evans/create-pull-request@v3 + with: + base: ${{ env.BASE }} + commit-message: Incremented version to ${{ env.NEXT_VERSION }} + delete-branch: true + title: '[AUTO] Incremented version to ${{ env.NEXT_VERSION }}.' + body: | + I've noticed that a new tag ${{ env.TAG }} was pushed, and incremented the version from ${{ env.CURRENT_VERSION }} to ${{ env.NEXT_VERSION }}. From 7c0460dba284d7122e9e45117b6ad8a954bc19f2 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Thu, 17 Feb 2022 15:53:56 -0600 Subject: [PATCH 028/149] Remove jcenter repository (#115) --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9b97f0ac..43974b0b 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,6 @@ buildscript { mavenLocal() mavenCentral() maven { url "https://plugins.gradle.org/m2/" } - jcenter() maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } } From 31fd4112b162fcded3be378ba7e788fa4f44287b Mon Sep 17 00:00:00 2001 From: Vacha Shah Date: Fri, 18 Feb 2022 17:30:12 -0800 Subject: [PATCH 029/149] Using Github App token to trigger CI for version increment PRs (#116) Signed-off-by: Vacha Shah --- .github/workflows/version.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index 196a690d..8ca31d8e 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -8,9 +8,15 @@ on: jobs: build: runs-on: ubuntu-latest - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: + - name: GitHub App token + id: github_app_token + uses: tibdex/github-app-token@v1.5.0 + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + installation_id: 22958780 + - uses: actions/checkout@v2 - name: Fetch Tag and Version Information run: | @@ -27,13 +33,17 @@ jobs: - uses: actions/checkout@v2 with: ref: ${{ env.BASE }} + token: ${{ steps.github_app_token.outputs.token }} + - name: Increment Version run: | echo Incrementing $CURRENT_VERSION to $NEXT_VERSION sed -i "s/$CURRENT_VERSION-SNAPSHOT/$NEXT_VERSION-SNAPSHOT/g" build.gradle + - name: Create Pull Request uses: peter-evans/create-pull-request@v3 with: + token: ${{ steps.github_app_token.outputs.token }} base: ${{ env.BASE }} commit-message: Incremented version to ${{ env.NEXT_VERSION }} delete-branch: true From bccb1bb329517c3c550df9b8fd9df81bbf05e4f1 Mon Sep 17 00:00:00 2001 From: Drew Baugher <46505179+dbbaughe@users.noreply.github.com> Date: Mon, 21 Feb 2022 11:08:03 -0800 Subject: [PATCH 030/149] Fixes copyright headers (#117) Signed-off-by: Drew Baugher <46505179+dbbaughe@users.noreply.github.com> --- build.gradle | 8 +------ detekt.yml | 8 +------ settings.gradle | 8 +------ spotless.license.java | 23 +----------------- .../opensearch/commons/ConfigConstants.java | 23 +----------------- .../opensearch/commons/InjectSecurity.java | 23 +----------------- .../authuser/AuthUserRequestBuilder.java | 23 +----------------- .../org/opensearch/commons/authuser/User.java | 23 +----------------- .../message/LegacyBaseMessage.java | 23 +----------------- .../message/LegacyChimeMessage.java | 23 +----------------- .../message/LegacyCustomWebhookMessage.java | 23 +----------------- .../message/LegacyDestinationType.java | 23 +----------------- .../message/LegacySlackMessage.java | 23 +----------------- .../response/LegacyBaseResponse.java | 23 +----------------- .../response/LegacyDestinationResponse.java | 23 +----------------- .../commons/rest/SecureRestClientBuilder.java | 23 +----------------- .../opensearch/commons/rest/TrustStore.java | 23 +----------------- .../NotificationsPluginInterface.kt | 24 +------------------ .../notifications/action/BaseResponse.kt | 24 +------------------ .../action/CreateNotificationConfigRequest.kt | 24 +------------------ .../CreateNotificationConfigResponse.kt | 24 +------------------ .../action/DeleteNotificationConfigRequest.kt | 24 +------------------ .../DeleteNotificationConfigResponse.kt | 24 +------------------ .../action/GetFeatureChannelListRequest.kt | 24 +------------------ .../action/GetFeatureChannelListResponse.kt | 24 +------------------ .../action/GetNotificationConfigRequest.kt | 24 +------------------ .../action/GetNotificationConfigResponse.kt | 24 +------------------ .../action/GetNotificationEventRequest.kt | 24 +------------------ .../action/GetNotificationEventResponse.kt | 24 +------------------ .../action/GetPluginFeaturesRequest.kt | 24 +------------------ .../action/GetPluginFeaturesResponse.kt | 24 +------------------ .../LegacyPublishNotificationRequest.kt | 8 +------ .../LegacyPublishNotificationResponse.kt | 8 +------ .../action/NotificationsActions.kt | 24 +------------------ .../action/SendNotificationRequest.kt | 24 +------------------ .../action/SendNotificationResponse.kt | 24 +------------------ .../action/UpdateNotificationConfigRequest.kt | 24 +------------------ .../UpdateNotificationConfigResponse.kt | 24 +------------------ .../commons/notifications/model/Attachment.kt | 24 +------------------ .../notifications/model/BaseConfigData.kt | 24 +------------------ .../commons/notifications/model/BaseModel.kt | 24 +------------------ .../notifications/model/ChannelMessage.kt | 24 +------------------ .../commons/notifications/model/Chime.kt | 24 +------------------ .../commons/notifications/model/ConfigType.kt | 24 +------------------ .../notifications/model/DeliveryStatus.kt | 24 +------------------ .../commons/notifications/model/Email.kt | 24 +------------------ .../commons/notifications/model/EmailGroup.kt | 24 +------------------ .../notifications/model/EmailRecipient.kt | 24 +------------------ .../model/EmailRecipientStatus.kt | 24 +------------------ .../notifications/model/EventSource.kt | 24 +------------------ .../notifications/model/EventStatus.kt | 24 +------------------ .../notifications/model/FeatureChannel.kt | 24 +------------------ .../notifications/model/FeatureChannelList.kt | 24 +------------------ .../notifications/model/NotificationConfig.kt | 24 +------------------ .../model/NotificationConfigInfo.kt | 23 +----------------- .../model/NotificationConfigSearchResult.kt | 24 +------------------ .../notifications/model/NotificationEvent.kt | 24 +------------------ .../model/NotificationEventInfo.kt | 24 +------------------ .../model/NotificationEventSearchResult.kt | 24 +------------------ .../notifications/model/SearchResults.kt | 24 +------------------ .../commons/notifications/model/SesAccount.kt | 8 +------ .../notifications/model/SeverityType.kt | 24 +------------------ .../commons/notifications/model/Slack.kt | 24 +------------------ .../notifications/model/SmtpAccount.kt | 24 +------------------ .../commons/notifications/model/Sns.kt | 9 ++----- .../commons/notifications/model/Webhook.kt | 24 +------------------ .../commons/notifications/model/XParser.kt | 24 +------------------ .../model/config/ConfigDataProperties.kt | 24 +------------------ .../opensearch/commons/utils/EnumHelpers.kt | 24 +------------------ .../opensearch/commons/utils/EnumParser.kt | 24 +------------------ .../org/opensearch/commons/utils/Helpers.kt | 24 +------------------ .../commons/utils/OpenForTesting.kt | 24 +------------------ .../commons/utils/SecureClientWrapper.kt | 24 +------------------ .../commons/utils/TransportHelpers.kt | 24 +------------------ .../commons/utils/ValidationHelpers.kt | 24 +------------------ .../commons/utils/XContentHelpers.kt | 24 +------------------ .../commons/InjectSecurityTest.java | 23 +----------------- .../opensearch/commons/authuser/UserTest.java | 23 +----------------- .../message/LegacyChimeMessageTest.java | 23 +----------------- .../LegacyCustomWebhookMessageTest.java | 23 +----------------- .../message/LegacySlackMessageTest.java | 23 +----------------- .../LegacyDestinationResponseTest.java | 23 +----------------- .../commons/rest/IntegrationTests.java | 23 +----------------- .../rest/SecureRestClientBuilderTest.java | 23 +----------------- .../commons/rest/TrustStoreTest.java | 23 +----------------- .../NotificationsPluginInterfaceTests.kt | 9 ++----- .../CreateNotificationConfigRequestTests.kt | 24 +------------------ .../CreateNotificationConfigResponseTests.kt | 24 +------------------ .../DeleteNotificationConfigRequestTests.kt | 24 +------------------ .../DeleteNotificationConfigResponseTests.kt | 24 +------------------ .../GetFeatureChannelListRequestTests.kt | 24 +------------------ .../GetFeatureChannelListResponseTests.kt | 24 +------------------ .../GetNotificationConfigRequestTests.kt | 24 +------------------ .../GetNotificationConfigResponseTests.kt | 24 +------------------ .../GetNotificationEventRequestTests.kt | 24 +------------------ .../GetNotificationEventResponseTests.kt | 24 +------------------ .../action/GetPluginFeaturesRequestTests.kt | 24 +------------------ .../action/GetPluginFeaturesResponseTests.kt | 24 +------------------ .../LegacyPublishNotificationRequestTests.kt | 8 +------ .../LegacyPublishNotificationResponseTests.kt | 8 +------ .../action/SendNotificationRequestTests.kt | 24 +------------------ .../action/SendNotificationResponseTests.kt | 24 +------------------ .../UpdateNotificationConfigRequestTests.kt | 24 +------------------ .../UpdateNotificationConfigResponseTests.kt | 24 +------------------ .../notifications/model/AttachmentTests.kt | 24 +------------------ .../model/ChannelMessageTests.kt | 24 +------------------ .../commons/notifications/model/ChimeTests.kt | 24 +------------------ .../notifications/model/ConfigTypeTests.kt | 24 +------------------ .../model/DeliveryStatusTests.kt | 24 +------------------ .../notifications/model/EmailGroupTests.kt | 24 +------------------ .../model/EmailRecipientStatusTests.kt | 24 +------------------ .../model/EmailRecipientTests.kt | 24 +------------------ .../commons/notifications/model/EmailTests.kt | 24 +------------------ .../notifications/model/EventSourceTests.kt | 24 +------------------ .../notifications/model/EventStatusTests.kt | 24 +------------------ .../model/FeatureChannelListTests.kt | 24 +------------------ .../model/FeatureChannelTests.kt | 24 +------------------ .../model/FilterConfigListTests.kt | 24 +------------------ .../notifications/model/FilterConfigTests.kt | 24 +------------------ .../notifications/model/MethodTypeTests.kt | 24 +------------------ .../model/NotificationConfigInfoTests.kt | 24 +------------------ .../NotificationConfigSearchResultsTests.kt | 24 +------------------ .../model/NotificationConfigTests.kt | 24 +------------------ .../model/NotificationEventInfoTests.kt | 24 +------------------ .../NotificationEventSearchResultTests.kt | 24 +------------------ .../model/NotificationEventTests.kt | 24 +------------------ .../notifications/model/SesAccountTests.kt | 8 +------ .../notifications/model/SeverityTypeTests.kt | 24 +------------------ .../commons/notifications/model/SlackTests.kt | 24 +------------------ .../notifications/model/SmtpAccountTests.kt | 24 +------------------ .../commons/notifications/model/SnsTests.kt | 8 +------ .../notifications/model/WebhookTests.kt | 24 +------------------ .../opensearch/commons/utils/TestHelpers.kt | 24 +------------------ 133 files changed, 135 insertions(+), 2843 deletions(-) diff --git a/build.gradle b/build.gradle index 43974b0b..07768d30 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. */ buildscript { diff --git a/detekt.yml b/detekt.yml index 34ed3c1e..251da688 100644 --- a/detekt.yml +++ b/detekt.yml @@ -1,14 +1,8 @@ --- # +# Copyright OpenSearch Contributors # SPDX-License-Identifier: Apache-2.0 # -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. -# -# Modifications Copyright OpenSearch Contributors. See -# GitHub history for details. -# style: ForbiddenComment: diff --git a/settings.gradle b/settings.gradle index 7fa821ef..59d3dd1c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,12 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. */ rootProject.name = 'common-utils' \ No newline at end of file diff --git a/spotless.license.java b/spotless.license.java index 01b9ea6c..9e182dcd 100644 --- a/spotless.license.java +++ b/spotless.license.java @@ -1,26 +1,5 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ diff --git a/src/main/java/org/opensearch/commons/ConfigConstants.java b/src/main/java/org/opensearch/commons/ConfigConstants.java index d7516dbc..6fc6e362 100644 --- a/src/main/java/org/opensearch/commons/ConfigConstants.java +++ b/src/main/java/org/opensearch/commons/ConfigConstants.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons; diff --git a/src/main/java/org/opensearch/commons/InjectSecurity.java b/src/main/java/org/opensearch/commons/InjectSecurity.java index 2240ab60..f2644c29 100644 --- a/src/main/java/org/opensearch/commons/InjectSecurity.java +++ b/src/main/java/org/opensearch/commons/InjectSecurity.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons; diff --git a/src/main/java/org/opensearch/commons/authuser/AuthUserRequestBuilder.java b/src/main/java/org/opensearch/commons/authuser/AuthUserRequestBuilder.java index c4b32e5f..32602744 100644 --- a/src/main/java/org/opensearch/commons/authuser/AuthUserRequestBuilder.java +++ b/src/main/java/org/opensearch/commons/authuser/AuthUserRequestBuilder.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons.authuser; diff --git a/src/main/java/org/opensearch/commons/authuser/User.java b/src/main/java/org/opensearch/commons/authuser/User.java index 017405e9..f698d36b 100644 --- a/src/main/java/org/opensearch/commons/authuser/User.java +++ b/src/main/java/org/opensearch/commons/authuser/User.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons.authuser; diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyBaseMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacyBaseMessage.java index b5122f31..96bd06ba 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacyBaseMessage.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyBaseMessage.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons.destination.message; diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyChimeMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacyChimeMessage.java index b457d9aa..31b45d1f 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacyChimeMessage.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyChimeMessage.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons.destination.message; diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java index 9645b327..d0421ca9 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons.destination.message; diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java b/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java index 85689510..ab50649b 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons.destination.message; diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacySlackMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacySlackMessage.java index f426894a..02d30bb4 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacySlackMessage.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacySlackMessage.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons.destination.message; diff --git a/src/main/java/org/opensearch/commons/destination/response/LegacyBaseResponse.java b/src/main/java/org/opensearch/commons/destination/response/LegacyBaseResponse.java index bbdd8d70..4d34b67f 100644 --- a/src/main/java/org/opensearch/commons/destination/response/LegacyBaseResponse.java +++ b/src/main/java/org/opensearch/commons/destination/response/LegacyBaseResponse.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons.destination.response; diff --git a/src/main/java/org/opensearch/commons/destination/response/LegacyDestinationResponse.java b/src/main/java/org/opensearch/commons/destination/response/LegacyDestinationResponse.java index bb927747..d55f5f3d 100644 --- a/src/main/java/org/opensearch/commons/destination/response/LegacyDestinationResponse.java +++ b/src/main/java/org/opensearch/commons/destination/response/LegacyDestinationResponse.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons.destination.response; diff --git a/src/main/java/org/opensearch/commons/rest/SecureRestClientBuilder.java b/src/main/java/org/opensearch/commons/rest/SecureRestClientBuilder.java index 109809a8..96af1540 100644 --- a/src/main/java/org/opensearch/commons/rest/SecureRestClientBuilder.java +++ b/src/main/java/org/opensearch/commons/rest/SecureRestClientBuilder.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons.rest; diff --git a/src/main/java/org/opensearch/commons/rest/TrustStore.java b/src/main/java/org/opensearch/commons/rest/TrustStore.java index 95f23796..52832b32 100644 --- a/src/main/java/org/opensearch/commons/rest/TrustStore.java +++ b/src/main/java/org/opensearch/commons/rest/TrustStore.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons.rest; diff --git a/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt b/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt index c0646d2d..d406bb00 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/BaseResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/BaseResponse.kt index 4bfa2271..e0659e8f 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/BaseResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/BaseResponse.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequest.kt index e62e92dd..dfbd1b95 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequest.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponse.kt index 742e7be5..e8bce5af 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponse.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequest.kt index 3699369f..1bcd355a 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequest.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponse.kt index e701daa2..efb68005 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponse.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt index ba6e3b19..901b4040 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponse.kt index 455109fc..79bf4435 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponse.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequest.kt index a410fe7e..a78d0d51 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequest.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponse.kt index a810759b..1825ccca 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponse.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequest.kt index 27e20554..66bfba6a 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequest.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponse.kt index 176d95ea..c512aa48 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponse.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequest.kt index 8b9b81b4..9437f376 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequest.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt index dde25709..1e6fa2b9 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt index e5807102..eb5080cf 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt @@ -1,12 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. */ package org.opensearch.commons.notifications.action diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponse.kt index 60f68e71..f7311da2 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponse.kt @@ -1,12 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. */ package org.opensearch.commons.notifications.action diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt index d93dd29a..10605cac 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequest.kt index 9943ed57..9f4d3159 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequest.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponse.kt index 38338fc5..96599b3a 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponse.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequest.kt index dcba7b71..765533be 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequest.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponse.kt index 0e15a69b..7de2e58c 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponse.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Attachment.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Attachment.kt index 282781f0..7768273d 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Attachment.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Attachment.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/BaseConfigData.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/BaseConfigData.kt index a766d3e0..1b8e59bf 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/BaseConfigData.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/BaseConfigData.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/BaseModel.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/BaseModel.kt index bdc13931..5ead5594 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/BaseModel.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/BaseModel.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelMessage.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelMessage.kt index 5f70efa7..18a65d39 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelMessage.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelMessage.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt index 2ffb0fd0..f39a3f7a 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt index 85c10e35..e9888e2b 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/DeliveryStatus.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/DeliveryStatus.kt index e824242b..41009ba7 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/DeliveryStatus.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/DeliveryStatus.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt index 86d5f67a..07c055d6 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailGroup.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailGroup.kt index 4940cc39..c286b66e 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailGroup.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailGroup.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipient.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipient.kt index 78c4ae59..aa523346 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipient.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipient.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatus.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatus.kt index a65224de..12774764 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatus.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatus.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt index 98c46d2a..06b28bee 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt index bf72836d..b322e401 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/FeatureChannel.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/FeatureChannel.kt index a108d2ff..c0e74c56 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/FeatureChannel.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/FeatureChannel.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/FeatureChannelList.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/FeatureChannelList.kt index 6fc57b22..d5df20a1 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/FeatureChannelList.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/FeatureChannelList.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt index a0f7f5ad..e80a5497 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt index 3c17a28c..7867a2d2 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt @@ -1,29 +1,8 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. */ -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * - */ package org.opensearch.commons.notifications.model import org.opensearch.common.Strings diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResult.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResult.kt index 48952335..579a16cb 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResult.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResult.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt index c6bf6928..a76f552d 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfo.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfo.kt index 3e4ff552..51921fc8 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfo.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfo.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResult.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResult.kt index 71ed1ba5..e9b68406 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResult.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResult.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SearchResults.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SearchResults.kt index 868cfebe..226f8d5d 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/SearchResults.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SearchResults.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt index aa9aca26..4aea8eb4 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt @@ -1,12 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SeverityType.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SeverityType.kt index 6f84f07b..0fa8a427 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/SeverityType.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SeverityType.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt index e5d3c3f6..f022470b 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SmtpAccount.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SmtpAccount.kt index 21e2155e..6022a913 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/SmtpAccount.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SmtpAccount.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt index 5e723926..583aec49 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt @@ -1,13 +1,8 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. */ + package org.opensearch.commons.notifications.model import org.opensearch.common.io.stream.StreamInput diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt index 09dbfe9d..710a9594 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/XParser.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/XParser.kt index 894b21a9..e019acd7 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/XParser.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/XParser.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt index e6844934..7fb4327f 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model.config diff --git a/src/main/kotlin/org/opensearch/commons/utils/EnumHelpers.kt b/src/main/kotlin/org/opensearch/commons/utils/EnumHelpers.kt index 3a18407e..39db7949 100644 --- a/src/main/kotlin/org/opensearch/commons/utils/EnumHelpers.kt +++ b/src/main/kotlin/org/opensearch/commons/utils/EnumHelpers.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.utils diff --git a/src/main/kotlin/org/opensearch/commons/utils/EnumParser.kt b/src/main/kotlin/org/opensearch/commons/utils/EnumParser.kt index 2a678f1f..b327d53c 100644 --- a/src/main/kotlin/org/opensearch/commons/utils/EnumParser.kt +++ b/src/main/kotlin/org/opensearch/commons/utils/EnumParser.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.utils diff --git a/src/main/kotlin/org/opensearch/commons/utils/Helpers.kt b/src/main/kotlin/org/opensearch/commons/utils/Helpers.kt index ae08c1d0..c31f0cab 100644 --- a/src/main/kotlin/org/opensearch/commons/utils/Helpers.kt +++ b/src/main/kotlin/org/opensearch/commons/utils/Helpers.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.utils diff --git a/src/main/kotlin/org/opensearch/commons/utils/OpenForTesting.kt b/src/main/kotlin/org/opensearch/commons/utils/OpenForTesting.kt index ccbd1536..1549e2e5 100644 --- a/src/main/kotlin/org/opensearch/commons/utils/OpenForTesting.kt +++ b/src/main/kotlin/org/opensearch/commons/utils/OpenForTesting.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.utils diff --git a/src/main/kotlin/org/opensearch/commons/utils/SecureClientWrapper.kt b/src/main/kotlin/org/opensearch/commons/utils/SecureClientWrapper.kt index 45fa29bd..bc0f0596 100644 --- a/src/main/kotlin/org/opensearch/commons/utils/SecureClientWrapper.kt +++ b/src/main/kotlin/org/opensearch/commons/utils/SecureClientWrapper.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.utils diff --git a/src/main/kotlin/org/opensearch/commons/utils/TransportHelpers.kt b/src/main/kotlin/org/opensearch/commons/utils/TransportHelpers.kt index 4763a48a..049dabdc 100644 --- a/src/main/kotlin/org/opensearch/commons/utils/TransportHelpers.kt +++ b/src/main/kotlin/org/opensearch/commons/utils/TransportHelpers.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.utils diff --git a/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt b/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt index 9f9082c4..ab9f7409 100644 --- a/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt +++ b/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.utils diff --git a/src/main/kotlin/org/opensearch/commons/utils/XContentHelpers.kt b/src/main/kotlin/org/opensearch/commons/utils/XContentHelpers.kt index 92dee033..e4af6007 100644 --- a/src/main/kotlin/org/opensearch/commons/utils/XContentHelpers.kt +++ b/src/main/kotlin/org/opensearch/commons/utils/XContentHelpers.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.utils diff --git a/src/test/java/org/opensearch/commons/InjectSecurityTest.java b/src/test/java/org/opensearch/commons/InjectSecurityTest.java index a9073d93..5a69de07 100644 --- a/src/test/java/org/opensearch/commons/InjectSecurityTest.java +++ b/src/test/java/org/opensearch/commons/InjectSecurityTest.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons; diff --git a/src/test/java/org/opensearch/commons/authuser/UserTest.java b/src/test/java/org/opensearch/commons/authuser/UserTest.java index ca727e6d..4df30a44 100644 --- a/src/test/java/org/opensearch/commons/authuser/UserTest.java +++ b/src/test/java/org/opensearch/commons/authuser/UserTest.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons.authuser; diff --git a/src/test/java/org/opensearch/commons/destination/message/LegacyChimeMessageTest.java b/src/test/java/org/opensearch/commons/destination/message/LegacyChimeMessageTest.java index 4477789e..191fef30 100644 --- a/src/test/java/org/opensearch/commons/destination/message/LegacyChimeMessageTest.java +++ b/src/test/java/org/opensearch/commons/destination/message/LegacyChimeMessageTest.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons.destination.message; diff --git a/src/test/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessageTest.java b/src/test/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessageTest.java index fba614fd..7443d060 100644 --- a/src/test/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessageTest.java +++ b/src/test/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessageTest.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons.destination.message; diff --git a/src/test/java/org/opensearch/commons/destination/message/LegacySlackMessageTest.java b/src/test/java/org/opensearch/commons/destination/message/LegacySlackMessageTest.java index a520fc8e..6b04d651 100644 --- a/src/test/java/org/opensearch/commons/destination/message/LegacySlackMessageTest.java +++ b/src/test/java/org/opensearch/commons/destination/message/LegacySlackMessageTest.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons.destination.message; diff --git a/src/test/java/org/opensearch/commons/destination/response/LegacyDestinationResponseTest.java b/src/test/java/org/opensearch/commons/destination/response/LegacyDestinationResponseTest.java index 6175827e..e9189d24 100644 --- a/src/test/java/org/opensearch/commons/destination/response/LegacyDestinationResponseTest.java +++ b/src/test/java/org/opensearch/commons/destination/response/LegacyDestinationResponseTest.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons.destination.response; diff --git a/src/test/java/org/opensearch/commons/rest/IntegrationTests.java b/src/test/java/org/opensearch/commons/rest/IntegrationTests.java index ee465086..0c6b7cfe 100644 --- a/src/test/java/org/opensearch/commons/rest/IntegrationTests.java +++ b/src/test/java/org/opensearch/commons/rest/IntegrationTests.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons.rest; diff --git a/src/test/java/org/opensearch/commons/rest/SecureRestClientBuilderTest.java b/src/test/java/org/opensearch/commons/rest/SecureRestClientBuilderTest.java index 1fe5a51e..4a034f7e 100644 --- a/src/test/java/org/opensearch/commons/rest/SecureRestClientBuilderTest.java +++ b/src/test/java/org/opensearch/commons/rest/SecureRestClientBuilderTest.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons.rest; diff --git a/src/test/java/org/opensearch/commons/rest/TrustStoreTest.java b/src/test/java/org/opensearch/commons/rest/TrustStoreTest.java index bea767d0..108a9e8d 100644 --- a/src/test/java/org/opensearch/commons/rest/TrustStoreTest.java +++ b/src/test/java/org/opensearch/commons/rest/TrustStoreTest.java @@ -1,27 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. */ package org.opensearch.commons.rest; diff --git a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt index 31d47bba..8147650f 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt @@ -1,13 +1,8 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. */ + package org.opensearch.commons.notifications import com.nhaarman.mockitokotlin2.whenever diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt index 28f337b8..9eb1de1f 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponseTests.kt index b9f0f4e1..0f580fcd 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponseTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequestTests.kt index 4c0c016d..c44d1458 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequestTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponseTests.kt index 5c479b47..fb574114 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponseTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt index 8c5c770f..98d577de 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponseTests.kt index 149e966f..805f651e 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponseTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequestTests.kt index cf892209..8873202d 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequestTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt index 8cc66d80..7541acb2 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequestTests.kt index 5e12d5f1..53bb825a 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequestTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt index be6af960..16139bb1 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequestTests.kt index 3861fdd9..1c85ed70 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequestTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponseTests.kt index c9924269..72b9a49f 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponseTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequestTests.kt index 349a1c14..c323ec85 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequestTests.kt @@ -1,12 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. */ package org.opensearch.commons.notifications.action diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponseTests.kt index 4b6f8e0b..4a75a82e 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponseTests.kt @@ -1,12 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. */ package org.opensearch.commons.notifications.action diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequestTests.kt index 31f6f549..3874190c 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequestTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponseTests.kt index 511c5760..ca7f789c 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponseTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt index 9599d60e..725e3c59 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponseTests.kt index aa2839c3..ca0b18cc 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponseTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.action diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/AttachmentTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/AttachmentTests.kt index b0873cfe..5aa127fe 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/AttachmentTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/AttachmentTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/ChannelMessageTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/ChannelMessageTests.kt index 7cda9d66..0a9f3f87 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/ChannelMessageTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/ChannelMessageTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/ChimeTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/ChimeTests.kt index da404b28..8e7f434e 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/ChimeTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/ChimeTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/ConfigTypeTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/ConfigTypeTests.kt index 28b1ec7e..6be8d68b 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/ConfigTypeTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/ConfigTypeTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/DeliveryStatusTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/DeliveryStatusTests.kt index 377d2be9..33c42e99 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/DeliveryStatusTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/DeliveryStatusTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/EmailGroupTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/EmailGroupTests.kt index dbf2f77b..15ab3ade 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/EmailGroupTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/EmailGroupTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatusTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatusTests.kt index 31bb80bd..c9bd89af 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatusTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatusTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/EmailRecipientTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/EmailRecipientTests.kt index c2f2d36d..56a5f752 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/EmailRecipientTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/EmailRecipientTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/EmailTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/EmailTests.kt index 8e4772f5..8624b138 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/EmailTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/EmailTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/EventSourceTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/EventSourceTests.kt index 6a87ed3f..2244d0c4 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/EventSourceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/EventSourceTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/EventStatusTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/EventStatusTests.kt index e9a1634f..65173cfe 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/EventStatusTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/EventStatusTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelListTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelListTests.kt index 5bd3db62..8a40aadd 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelListTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelListTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelTests.kt index d2b8f009..3fa2e4ab 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigListTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigListTests.kt index f4ceee7f..9ac569f6 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigListTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigListTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigTests.kt index b5dd66b6..234548a8 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/MethodTypeTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/MethodTypeTests.kt index 75685d53..f9982d7c 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/MethodTypeTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/MethodTypeTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt index 6f532fb0..d14b9f34 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt index dab33090..835af358 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt index 0a96c8ed..47614986 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt index 3937c252..a87e334a 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt index c0e9786d..ae3f01a9 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventTests.kt index 92c53fd8..313ffca9 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/SesAccountTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/SesAccountTests.kt index e999e534..e5a52837 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/SesAccountTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/SesAccountTests.kt @@ -1,12 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/SeverityTypeTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/SeverityTypeTests.kt index 71fe1460..35f7b218 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/SeverityTypeTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/SeverityTypeTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/SlackTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/SlackTests.kt index 98fbc0cd..5c64c6ae 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/SlackTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/SlackTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/SmtpAccountTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/SmtpAccountTests.kt index bc924681..ffdf26c7 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/SmtpAccountTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/SmtpAccountTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/SnsTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/SnsTests.kt index 03d494d2..494f09e9 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/SnsTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/SnsTests.kt @@ -1,12 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/WebhookTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/WebhookTests.kt index 5a717669..3272203d 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/WebhookTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/WebhookTests.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.notifications.model diff --git a/src/test/kotlin/org/opensearch/commons/utils/TestHelpers.kt b/src/test/kotlin/org/opensearch/commons/utils/TestHelpers.kt index 35f5d3b9..81dc33e0 100644 --- a/src/test/kotlin/org/opensearch/commons/utils/TestHelpers.kt +++ b/src/test/kotlin/org/opensearch/commons/utils/TestHelpers.kt @@ -1,28 +1,6 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * */ package org.opensearch.commons.utils From 1da8807b7db32a997e7a9da5e7239b20fae00b80 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Thu, 24 Feb 2022 16:07:40 -0600 Subject: [PATCH 031/149] Remove jcenter repository missed on first pass (#118) Signed-off-by: Peter Nied --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index 07768d30..76a5c34e 100644 --- a/build.gradle +++ b/build.gradle @@ -35,7 +35,6 @@ repositories { mavenLocal() mavenCentral() maven { url "https://plugins.gradle.org/m2/" } - jcenter() maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } } From 3919543651049284a877fdc0980aec76ced72a21 Mon Sep 17 00:00:00 2001 From: "Daniel Doubrovkine (dB.)" Date: Tue, 1 Mar 2022 17:09:12 -0500 Subject: [PATCH 032/149] Run CI/CD on Java 8, 11, 14 and 17. (#121) * Run CI/CD on Java 8, 11, 14 and 17. Signed-off-by: dblock * Add JDK 17. Signed-off-by: dblock --- .github/workflows/ci-17.yml | 40 +++++++++++++++++++ .github/workflows/ci.yml | 6 ++- DEVELOPER_GUIDE.md | 6 +-- .../commons/InjectSecurityTest.java | 14 +++++-- 4 files changed, 58 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/ci-17.yml diff --git a/.github/workflows/ci-17.yml b/.github/workflows/ci-17.yml new file mode 100644 index 00000000..a6a8a1e2 --- /dev/null +++ b/.github/workflows/ci-17.yml @@ -0,0 +1,40 @@ +name: Build and Test +on: + push: + branches: + - "*" + pull_request: + branches: + - "*" + +jobs: + build: + + name: Build and Test (17) + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup Java 11 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: 11 + + - name: Build + run: | + ./gradlew build --build-cache + + - name: Setup Java 17 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: 17 + + - name: Test + run: | + ./gradlew test --build-cache + + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c35e0c0a..e3e495c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,10 @@ jobs: build: strategy: matrix: - java: [14] + java: + - 8 + - 11 + - 14 name: Build and Test runs-on: ubuntu-latest @@ -25,7 +28,6 @@ jobs: with: java-version: ${{ matrix.java }} - # common-utils - name: Build and Test run: | ./gradlew build diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index d162eccb..27fc15b3 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -1,7 +1,7 @@ - [Developer Guide](#developer-guide) - [Forking and Cloning](#forking-and-cloning) - [Install Prerequisites](#install-prerequisites) - - [JDK 14](#jdk-14) + - [JDK 11](#jdk-11) - [Building](#building) - [Using IntelliJ IDEA](#using-intellij-idea) - [Submitting Changes](#submitting-changes) @@ -16,9 +16,9 @@ Fork this repository on GitHub, and clone locally with `git clone`. ### Install Prerequisites -#### JDK 14 +#### JDK 11 -OpenSearch components build using Java 14 at a minimum. This means you must have a JDK 14 installed with the environment variable `JAVA_HOME` referencing the path to Java home for your JDK 14 installation, e.g. `JAVA_HOME=/usr/lib/jvm/jdk-14`. +OpenSearch components build using Java 11 at a minimum. This means you must have a JDK 11 installed with the environment variable `JAVA_HOME` referencing the path to Java home for your JDK 11 installation, e.g. `JAVA_HOME=/usr/lib/jvm/jdk-11`. ### Building diff --git a/src/test/java/org/opensearch/commons/InjectSecurityTest.java b/src/test/java/org/opensearch/commons/InjectSecurityTest.java index 5a69de07..818aa9c7 100644 --- a/src/test/java/org/opensearch/commons/InjectSecurityTest.java +++ b/src/test/java/org/opensearch/commons/InjectSecurityTest.java @@ -15,7 +15,7 @@ import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_USE_INJECTED_USER_FOR_PLUGINS; import java.util.Arrays; -import java.util.Map; +import java.util.HashMap; import org.junit.jupiter.api.Test; import org.opensearch.common.settings.Settings; @@ -114,7 +114,11 @@ public void testInjectProperty() { assertTrue(helper.injectProperty("property1", true)); assertTrue(helper.injectProperty("property2", "some value")); assertTrue(helper.injectProperty("property3", "")); - assertTrue(helper.injectProperty("property4", Map.of("key", "value"))); + assertTrue(helper.injectProperty("property4", new HashMap() { + { + put("key", "value"); + } + })); // verify the set properties are not null and equal to what was set assertNull(threadContext.getTransient("property")); assertNotNull(threadContext.getTransient("property1")); @@ -124,7 +128,11 @@ public void testInjectProperty() { assertNotNull(threadContext.getTransient("property3")); assertEquals("", threadContext.getTransient("property3")); assertNotNull(threadContext.getTransient("property4")); - assertEquals(Map.of("key", "value"), threadContext.getTransient("property4")); + assertEquals(new HashMap() { + { + put("key", "value"); + } + }, threadContext.getTransient("property4")); } assertEquals("1", threadContext.getHeader("default")); assertEquals("opendistro", threadContext.getHeader("name")); From 710b1f9e337a3a07d65f88b1ec823f1cc4f8cbf4 Mon Sep 17 00:00:00 2001 From: "whitesource-for-github-com[bot]" <50673670+whitesource-for-github-com[bot]@users.noreply.github.com> Date: Tue, 1 Mar 2022 16:55:00 -0800 Subject: [PATCH 033/149] Add .whitesource configuration file (#109) Co-authored-by: whitesource-for-github-com[bot] <50673670+whitesource-for-github-com[bot]@users.noreply.github.com> --- .whitesource | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .whitesource diff --git a/.whitesource b/.whitesource new file mode 100644 index 00000000..db4b0fec --- /dev/null +++ b/.whitesource @@ -0,0 +1,15 @@ +{ + "scanSettings": { + "configMode": "AUTO", + "configExternalURL": "", + "projectToken": "", + "baseBranches": [] + }, + "checkRunSettings": { + "vulnerableCheckRunConclusionLevel": "failure", + "displayMode": "diff" + }, + "issueSettings": { + "minSeverityLevel": "LOW" + } +} \ No newline at end of file From 361db4ce3933fe902773df1266fc32cf6b0098bb Mon Sep 17 00:00:00 2001 From: Ashish Agrawal Date: Wed, 2 Mar 2022 17:09:20 -0800 Subject: [PATCH 034/149] Upgrade to 2.0 (#122) Signed-off-by: Ashish Agrawal --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 76a5c34e..d92e0acf 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "1.3.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.0.0-SNAPSHOT") kotlin_version = System.getProperty("kotlin.version", "1.4.32") } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 21a8fe40..f338a880 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip From 1be69e5326c0434df08f37903bda3d782fa6bbca Mon Sep 17 00:00:00 2001 From: "Daniel Doubrovkine (dB.)" Date: Wed, 9 Mar 2022 14:32:56 -0500 Subject: [PATCH 035/149] Remove JDK8 support and fix JDK17 build. (#129) --- .github/workflows/ci-17.yml | 40 ------------------------------------- .github/workflows/ci.yml | 2 +- build.gradle | 19 +++++++++--------- 3 files changed, 11 insertions(+), 50 deletions(-) delete mode 100644 .github/workflows/ci-17.yml diff --git a/.github/workflows/ci-17.yml b/.github/workflows/ci-17.yml deleted file mode 100644 index a6a8a1e2..00000000 --- a/.github/workflows/ci-17.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Build and Test -on: - push: - branches: - - "*" - pull_request: - branches: - - "*" - -jobs: - build: - - name: Build and Test (17) - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup Java 11 - uses: actions/setup-java@v2 - with: - distribution: 'temurin' - java-version: 11 - - - name: Build - run: | - ./gradlew build --build-cache - - - name: Setup Java 17 - uses: actions/setup-java@v2 - with: - distribution: 'temurin' - java-version: 17 - - - name: Test - run: | - ./gradlew test --build-cache - - diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3e495c4..69e7573d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,9 +12,9 @@ jobs: strategy: matrix: java: - - 8 - 11 - 14 + - 17 name: Build and Test runs-on: ubuntu-latest diff --git a/build.gradle b/build.gradle index d92e0acf..5ab47cb4 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { ext { opensearch_group = "org.opensearch" opensearch_version = System.getProperty("opensearch.version", "2.0.0-SNAPSHOT") - kotlin_version = System.getProperty("kotlin.version", "1.4.32") + kotlin_version = System.getProperty("kotlin.version", "1.6.10") } repositories { @@ -21,7 +21,7 @@ buildscript { classpath "${opensearch_group}.gradle:build-tools:${opensearch_version}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlin_version}" classpath "org.jetbrains.kotlin:kotlin-allopen:${kotlin_version}" - classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.17.1" + classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.20.0-RC1" } } @@ -50,7 +50,8 @@ allprojects { } } -sourceCompatibility = 1.8 +targetCompatibility = JavaVersion.VERSION_11 +sourceCompatibility = JavaVersion.VERSION_11 apply plugin: 'java' apply plugin: 'jacoco' @@ -70,15 +71,15 @@ dependencies { compileOnly "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}" compileOnly "org.jetbrains.kotlin:kotlin-stdlib-common:${kotlin_version}" compileOnly "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3" // ${kotlin_version} does not work for coroutines - testCompile "org.opensearch.test:framework:${opensearch_version}" - testCompile "org.jetbrains.kotlin:kotlin-test:${kotlin_version}" - testCompile "org.mockito:mockito-core:3.10.0" + testImplementation "org.opensearch.test:framework:${opensearch_version}" + testImplementation "org.jetbrains.kotlin:kotlin-test:${kotlin_version}" + testImplementation "org.mockito:mockito-core:3.10.0" testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2' testImplementation 'org.mockito:mockito-junit-jupiter:3.10.0' testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2' - ktlint "com.pinterest:ktlint:0.41.0" + ktlint "com.pinterest:ktlint:0.44.0" } test { @@ -126,13 +127,13 @@ task ktlintFormat(type: JavaExec, group: "formatting") { compileKotlin { kotlinOptions { freeCompilerArgs = ['-Xjsr305=strict'] - jvmTarget = "1.8" + jvmTarget = "11" } } compileTestKotlin { kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "11" } } From fb43b2206fd869db075862afb93e5f62e68a29df Mon Sep 17 00:00:00 2001 From: "Daniel Doubrovkine (dB.)" Date: Wed, 9 Mar 2022 20:24:57 -0500 Subject: [PATCH 036/149] Add support for build version qualifier. (#128) Signed-off-by: dblock --- build.gradle | 6 +++++- scripts/build.sh | 12 ++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 5ab47cb4..16c28922 100644 --- a/build.gradle +++ b/build.gradle @@ -40,11 +40,15 @@ repositories { ext { isSnapshot = "true" == System.getProperty("build.snapshot", "true") + buildVersionQualifier = System.getProperty("build.version_qualifier") } allprojects { group 'org.opensearch.commons' - version = opensearch_version - '-SNAPSHOT' + '.0' + version = opensearch_version.tokenize('-')[0] + '.0' + if (buildVersionQualifier) { + version += "-${buildVersionQualifier}" + } if (isSnapshot) { version += "-SNAPSHOT" } diff --git a/scripts/build.sh b/scripts/build.sh index f68dc52e..1f8b6d4d 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -10,6 +10,7 @@ function usage() { echo "" echo "Arguments:" echo -e "-v VERSION\t[Required] OpenSearch version." + echo -e "-q QUALIFIER\t[Optional] Build qualifier." echo -e "-s SNAPSHOT\t[Optional] Build a snapshot, default is 'false'." echo -e "-p PLATFORM\t[Optional] Platform, ignored." echo -e "-a ARCHITECTURE\t[Optional] Build architecture, ignored." @@ -17,7 +18,7 @@ function usage() { echo -e "-h help" } -while getopts ":h:v:s:o:p:a:" arg; do +while getopts ":h:v:q:s:o:p:a:" arg; do case $arg in h) usage @@ -26,6 +27,9 @@ while getopts ":h:v:s:o:p:a:" arg; do v) VERSION=$OPTARG ;; + q) + QUALIFIER=$OPTARG + ;; s) SNAPSHOT=$OPTARG ;; @@ -59,8 +63,8 @@ fi [[ "$SNAPSHOT" == "true" ]] && VERSION=$VERSION-SNAPSHOT [ -z "$OUTPUT" ] && OUTPUT=artifacts -./gradlew build -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -./gradlew publishShadowPublicationToMavenLocal -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -./gradlew publishShadowPublicationToStagingRepository -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT +./gradlew build -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -Dbuild.version_qualifier=$QUALIFIER +./gradlew publishShadowPublicationToMavenLocal -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -Dbuild.version_qualifier=$QUALIFIER +./gradlew publishShadowPublicationToStagingRepository -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -Dbuild.version_qualifier=$QUALIFIER mkdir -p $OUTPUT/maven/org/opensearch cp -r ./build/local-staging-repo/org/opensearch/common-utils $OUTPUT/maven/org/opensearch/common-utils From bdd8a8135c86a34ae49f5153a17fd0cfb7ec4745 Mon Sep 17 00:00:00 2001 From: Annie Lee Date: Thu, 10 Mar 2022 10:50:04 -0800 Subject: [PATCH 037/149] Add backport and auto delete workflow (#130) Signed-off-by: Annie Lee --- .github/workflows/backport.yml | 29 ++++++++++++++++++++ .github/workflows/delete_backport_branch.yml | 15 ++++++++++ DEVELOPER_GUIDE.md | 6 +++- 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/backport.yml create mode 100644 .github/workflows/delete_backport_branch.yml diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml new file mode 100644 index 00000000..e3f96a44 --- /dev/null +++ b/.github/workflows/backport.yml @@ -0,0 +1,29 @@ + +name: Backport +on: + pull_request_target: + types: + - closed + - labeled + +jobs: + backport: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + name: Backport + steps: + - name: GitHub App token + id: github_app_token + uses: tibdex/github-app-token@v1.5.0 + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + installation_id: 22958780 + + - name: Backport + uses: VachaShah/backport@v1.1.4 + with: + github_token: ${{ steps.github_app_token.outputs.token }} + branch_name: backport/backport-${{ github.event.number }} diff --git a/.github/workflows/delete_backport_branch.yml b/.github/workflows/delete_backport_branch.yml new file mode 100644 index 00000000..f24f022b --- /dev/null +++ b/.github/workflows/delete_backport_branch.yml @@ -0,0 +1,15 @@ +name: Delete merged branch of the backport PRs +on: + pull_request: + types: + - closed + +jobs: + delete-branch: + runs-on: ubuntu-latest + if: startsWith(github.event.pull_request.head.ref,'backport/') + steps: + - name: Delete merged branch + uses: SvanBoxel/delete-merged-branch@main + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index 27fc15b3..d4a44c46 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -36,4 +36,8 @@ Launch Intellij IDEA, choose **Import Project**, and select the `settings.gradle ### Submitting Changes -See [CONTRIBUTING](CONTRIBUTING.md). \ No newline at end of file +See [CONTRIBUTING](CONTRIBUTING.md). + +### Backport + +- [Link to backport documentation](https://github.com/opensearch-project/opensearch-plugins/blob/main/BACKPORT.md) From be72763aa9f94e594500c799dacbeb0e8dd3365d Mon Sep 17 00:00:00 2001 From: Saurabh Singh Date: Fri, 11 Mar 2022 14:40:13 -0800 Subject: [PATCH 038/149] Add release notes for version 1.3.0.0 (#132) Signed-off-by: Saurabh Singh Co-authored-by: Saurabh Singh --- ...pensearch-common-utils.release-notes-1.3.0.0.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 release-notes/opensearch-common-utils.release-notes-1.3.0.0.md diff --git a/release-notes/opensearch-common-utils.release-notes-1.3.0.0.md b/release-notes/opensearch-common-utils.release-notes-1.3.0.0.md new file mode 100644 index 00000000..4f210951 --- /dev/null +++ b/release-notes/opensearch-common-utils.release-notes-1.3.0.0.md @@ -0,0 +1,14 @@ +## Version 1.3.0.0 2022-03-11 + +Compatible with OpenSearch 1.3.0 + +### Infrastructure + + * Updates common-utils version to 1.3 ([#99](https://github.com/opensearch-project/common-utils/pull/99)) + * Update build.sh script to include optional platform param. ([#95](https://github.com/opensearch-project/common-utils/pull/95)) + * Update copyright notice and add DCO check workflow. ([#94](https://github.com/opensearch-project/common-utils/pull/94)) + +### Documentation + + * Update copyright headers ([#117](https://github.com/opensearch-project/common-utils/pull/117)) + * Add release notes for version 1.3.0.0 ([#132](https://github.com/opensearch-project/common-utils/pull/132)) \ No newline at end of file From fe3abb9aaac36f43b5721d4b167e9a535f5e5eee Mon Sep 17 00:00:00 2001 From: Ashish Agrawal Date: Mon, 14 Mar 2022 16:54:51 -0700 Subject: [PATCH 039/149] Upgrade gradle artifacts to 7.3.3 (#135) Signed-off-by: Ashish Agrawal --- gradle/wrapper/gradle-wrapper.jar | Bin 56172 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 282 ++++++++++++++--------- gradlew.bat | 43 ++-- 4 files changed, 197 insertions(+), 130 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 28861d273a5d270fd8f65dd74570c17c9c507736..7454180f2ae8848c63b8b4dea2cb829da983f2fa 100644 GIT binary patch delta 51437 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%0DCB$;CIxFT%Ws?oirIo$=Lq=nDPZE9YO&<7lE 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 BrV#)D delta 48172 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к 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 cc>h-)Mo|U| \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -81,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -89,84 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index e95643d6..ac1b06f9 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -35,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -45,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell From 05931c9cc2a88656aa9323b0319a5b7bc9b1a611 Mon Sep 17 00:00:00 2001 From: Mohammad Qureshi <47198598+qreshi@users.noreply.github.com> Date: Thu, 17 Mar 2022 12:44:15 -0700 Subject: [PATCH 040/149] Remove feature and feature_list usage for Notifications (#136) * Remove feature_list from NotificationConfig Signed-off-by: Mohammad Qureshi <47198598+qreshi@users.noreply.github.com> * Remove feature from FeatueChannelsListRequest Signed-off-by: Mohammad Qureshi <47198598+qreshi@users.noreply.github.com> * Remove 'feature' from EventSource Signed-off-by: Mohammad Qureshi <47198598+qreshi@users.noreply.github.com> * Remove feature from LegacyPublishNotification Signed-off-by: Mohammad Qureshi <47198598+qreshi@users.noreply.github.com> * Updated descriptions related to publishLegacyNotification Signed-off-by: Mohammad Qureshi <47198598+qreshi@users.noreply.github.com> --- .../message/LegacyDestinationType.java | 2 +- .../notifications/NotificationConstants.kt | 5 -- .../NotificationsPluginInterface.kt | 8 +- .../action/GetFeatureChannelListRequest.kt | 40 +++++----- .../LegacyPublishNotificationRequest.kt | 8 +- .../action/NotificationsActions.kt | 4 +- .../notifications/model/EventSource.kt | 9 --- .../notifications/model/NotificationConfig.kt | 12 --- .../NotificationsPluginInterfaceTests.kt | 10 --- .../CreateNotificationConfigRequestTests.kt | 74 +++++++------------ .../GetFeatureChannelListRequestTests.kt | 23 +----- .../GetNotificationConfigResponseTests.kt | 20 +---- .../GetNotificationEventResponseTests.kt | 12 --- .../LegacyPublishNotificationRequestTests.kt | 4 +- .../action/SendNotificationRequestTests.kt | 12 --- .../UpdateNotificationConfigRequestTests.kt | 66 +++++++---------- .../notifications/model/EventSourceTests.kt | 18 ++--- .../model/NotificationConfigInfoTests.kt | 10 +-- .../NotificationConfigSearchResultsTests.kt | 20 +---- .../model/NotificationConfigTests.kt | 25 +------ .../model/NotificationEventInfoTests.kt | 5 -- .../NotificationEventSearchResultTests.kt | 12 --- .../model/NotificationEventTests.kt | 9 +-- 23 files changed, 103 insertions(+), 305 deletions(-) diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java b/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java index ab50649b..1bad1029 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java @@ -6,7 +6,7 @@ package org.opensearch.commons.destination.message; /** - * Supported legacy notification destinations for Index Management + * Supported legacy notification destinations for Alerting and Index Management */ public enum LegacyDestinationType { LEGACY_CHIME, diff --git a/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt index 7a958154..6ebf7889 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt @@ -36,7 +36,6 @@ object NotificationConstants { const val NAME_TAG = "name" const val DESCRIPTION_TAG = "description" const val IS_ENABLED_TAG = "is_enabled" - const val FEATURE_LIST_TAG = "feature_list" const val TITLE_TAG = "title" const val SEVERITY_TAG = "severity" const val TAGS_TAG = "tags" @@ -70,9 +69,5 @@ object NotificationConstants { const val ALLOWED_CONFIG_FEATURE_LIST_TAG = "allowed_config_feature_list" const val PLUGIN_FEATURES_TAG = "plugin_features" - const val FEATURE_ALERTING = "alerting" - const val FEATURE_INDEX_MANAGEMENT = "index_management" - const val FEATURE_REPORTS = "reports" - const val DEFAULT_MAX_ITEMS = 1000 } diff --git a/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt b/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt index d406bb00..b67ec282 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt @@ -9,7 +9,6 @@ import org.opensearch.action.ActionResponse import org.opensearch.client.node.NodeClient import org.opensearch.common.io.stream.Writeable import org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_USER_INFO_THREAD_CONTEXT -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT import org.opensearch.commons.notifications.action.BaseResponse import org.opensearch.commons.notifications.action.CreateNotificationConfigRequest import org.opensearch.commons.notifications.action.CreateNotificationConfigResponse @@ -201,7 +200,7 @@ object NotificationsPluginInterface { /** * Publishes a notification API using the legacy notification implementation. No REST API. - * Internal API only for the Index Management plugin. + * Internal API only for the Alerting and Index Management plugin, other consumers should use [sendNotification]. * @param client Node client for making transport action * @param request The legacy publish notification request * @param listener The listener for getting response @@ -211,11 +210,6 @@ object NotificationsPluginInterface { request: LegacyPublishNotificationRequest, listener: ActionListener ) { - if (request.feature != FEATURE_INDEX_MANAGEMENT) { - // Do not change this; do not pass in FEATURE_INDEX_MANAGEMENT if you are not the Index Management plugin. - throw IllegalArgumentException("The publish notification method only supports the Index Management feature.") - } - client.execute( LEGACY_PUBLISH_NOTIFICATION_ACTION_TYPE, request, diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt index 901b4040..56a0b0fa 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt @@ -14,7 +14,7 @@ import org.opensearch.common.xcontent.ToXContentObject import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_TAG +import org.opensearch.commons.notifications.NotificationConstants.COMPACT_TAG import org.opensearch.commons.utils.logger import java.io.IOException @@ -22,7 +22,7 @@ import java.io.IOException * This request is plugin-only call. i.e. REST interface is not exposed. */ class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { - val feature: String + val compact: Boolean // Dummy request parameter for transport request companion object { private val log by logger(GetFeatureChannelListRequest::class.java) @@ -39,7 +39,7 @@ class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { @JvmStatic @Throws(IOException::class) fun parse(parser: XContentParser): GetFeatureChannelListRequest { - var feature: String? = null + var compact = false XContentParserUtils.ensureExpectedToken( XContentParser.Token.START_OBJECT, @@ -50,24 +50,32 @@ class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { val fieldName = parser.currentName() parser.nextToken() when (fieldName) { - FEATURE_TAG -> feature = parser.text() + COMPACT_TAG -> compact = parser.booleanValue() else -> { parser.skipChildren() log.info("Unexpected field: $fieldName, while parsing GetFeatureChannelListRequest") } } } - feature ?: throw IllegalArgumentException("$FEATURE_TAG field absent") - return GetFeatureChannelListRequest(feature) + return GetFeatureChannelListRequest() } } + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(COMPACT_TAG, compact) + .endObject() + } + /** * constructor for creating the class - * @param feature the caller plugin feature + * @param compact Dummy request parameter for transport request */ - constructor(feature: String) { - this.feature = feature + constructor(compact: Boolean = false) { + this.compact = compact } /** @@ -75,7 +83,7 @@ class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { */ @Throws(IOException::class) constructor(input: StreamInput) : super(input) { - feature = input.readString() + compact = input.readBoolean() } /** @@ -84,17 +92,7 @@ class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { @Throws(IOException::class) override fun writeTo(output: StreamOutput) { super.writeTo(output) - output.writeString(feature) - } - - /** - * {@inheritDoc} - */ - override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { - builder!! - return builder.startObject() - .field(FEATURE_TAG, feature) - .endObject() + output.writeBoolean(compact) } /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt index eb5080cf..508815a8 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt @@ -23,7 +23,6 @@ import java.io.IOException */ class LegacyPublishNotificationRequest : ActionRequest { val baseMessage: LegacyBaseMessage - val feature: String companion object { /** @@ -35,14 +34,11 @@ class LegacyPublishNotificationRequest : ActionRequest { /** * constructor for creating the class * @param baseMessage the base message to send - * @param feature the feature that is trying to use this request */ constructor( - baseMessage: LegacyBaseMessage, - feature: String + baseMessage: LegacyBaseMessage ) { this.baseMessage = baseMessage - this.feature = feature } /** @@ -55,7 +51,6 @@ class LegacyPublishNotificationRequest : ActionRequest { LegacyDestinationType.LEGACY_CUSTOM_WEBHOOK -> LegacyCustomWebhookMessage(input) LegacyDestinationType.LEGACY_SLACK -> LegacySlackMessage(input) } - feature = input.readString() } /** @@ -66,7 +61,6 @@ class LegacyPublishNotificationRequest : ActionRequest { super.writeTo(output) output.writeEnum(baseMessage.channelType) baseMessage.writeTo(output) - output.writeString(feature) } /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt index 10605cac..ca8dd2d7 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt @@ -52,7 +52,7 @@ object NotificationsActions { /** * Publish legacy notification message. Internal only - Inter plugin communication. - * Only for the Index Management plugin. + * Only for the Alerting and Index Management plugins. */ const val LEGACY_PUBLISH_NOTIFICATION_NAME = "cluster:admin/opensearch/notifications/feature/publish" @@ -106,7 +106,7 @@ object NotificationsActions { /** * Send legacy notification transport action type. Internal only - Inter plugin communication. - * Only for the Index Management plugin. + * Only for the Alerting and Index Management plugins. */ val LEGACY_PUBLISH_NOTIFICATION_ACTION_TYPE = ActionType(LEGACY_PUBLISH_NOTIFICATION_NAME, ::LegacyPublishNotificationResponse) diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt index 06b28bee..10bf04e3 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt @@ -12,7 +12,6 @@ import org.opensearch.common.xcontent.ToXContent import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_TAG import org.opensearch.commons.notifications.NotificationConstants.REFERENCE_ID_TAG import org.opensearch.commons.notifications.NotificationConstants.SEVERITY_TAG import org.opensearch.commons.notifications.NotificationConstants.TAGS_TAG @@ -27,7 +26,6 @@ import java.io.IOException data class EventSource( val title: String, val referenceId: String, - val feature: String, val severity: SeverityType = SeverityType.INFO, val tags: List = listOf() ) : BaseModel { @@ -53,7 +51,6 @@ data class EventSource( fun parse(parser: XContentParser): EventSource { var title: String? = null var referenceId: String? = null - var feature: String? = null var severity: SeverityType = SeverityType.INFO var tags: List = emptyList() @@ -68,7 +65,6 @@ data class EventSource( when (fieldName) { TITLE_TAG -> title = parser.text() REFERENCE_ID_TAG -> referenceId = parser.text() - FEATURE_TAG -> feature = parser.text() SEVERITY_TAG -> severity = SeverityType.fromTagOrDefault(parser.text()) TAGS_TAG -> tags = parser.stringList() else -> { @@ -79,12 +75,10 @@ data class EventSource( } title ?: throw IllegalArgumentException("$TITLE_TAG field absent") referenceId ?: throw IllegalArgumentException("$REFERENCE_ID_TAG field absent") - feature ?: throw IllegalArgumentException("$FEATURE_TAG field absent") return EventSource( title, referenceId, - feature, severity, tags ) @@ -99,7 +93,6 @@ data class EventSource( return builder.startObject() .field(TITLE_TAG, title) .field(REFERENCE_ID_TAG, referenceId) - .field(FEATURE_TAG, feature) .field(SEVERITY_TAG, severity.tag) .field(TAGS_TAG, tags) .endObject() @@ -112,7 +105,6 @@ data class EventSource( constructor(input: StreamInput) : this( title = input.readString(), referenceId = input.readString(), - feature = input.readString(), severity = input.readEnum(SeverityType::class.java), tags = input.readStringList() ) @@ -123,7 +115,6 @@ data class EventSource( override fun writeTo(output: StreamOutput) { output.writeString(title) output.writeString(referenceId) - output.writeString(feature) output.writeEnum(severity) output.writeStringCollection(tags) } diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt index e80a5497..f490e8b1 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt @@ -14,17 +14,13 @@ import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TYPE_TAG import org.opensearch.commons.notifications.NotificationConstants.DESCRIPTION_TAG -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_LIST_TAG import org.opensearch.commons.notifications.NotificationConstants.IS_ENABLED_TAG import org.opensearch.commons.notifications.NotificationConstants.NAME_TAG import org.opensearch.commons.notifications.model.config.ConfigDataProperties.createConfigData import org.opensearch.commons.notifications.model.config.ConfigDataProperties.getReaderForConfigType import org.opensearch.commons.notifications.model.config.ConfigDataProperties.validateConfigData -import org.opensearch.commons.utils.STRING_READER -import org.opensearch.commons.utils.STRING_WRITER import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger -import org.opensearch.commons.utils.stringList import java.io.IOException /** @@ -34,7 +30,6 @@ data class NotificationConfig( val name: String, val description: String, val configType: ConfigType, - val features: Set, val configData: BaseConfigData?, val isEnabled: Boolean = true ) : BaseModel { @@ -68,7 +63,6 @@ data class NotificationConfig( var name: String? = null var description = "" var configType: ConfigType? = null - var features: Set? = null var isEnabled = true var configData: BaseConfigData? = null XContentParserUtils.ensureExpectedToken( @@ -83,7 +77,6 @@ data class NotificationConfig( NAME_TAG -> name = parser.text() DESCRIPTION_TAG -> description = parser.text() CONFIG_TYPE_TAG -> configType = ConfigType.fromTagOrDefault(parser.text()) - FEATURE_LIST_TAG -> features = parser.stringList().toSet() IS_ENABLED_TAG -> isEnabled = parser.booleanValue() else -> { val configTypeForTag = ConfigType.fromTagOrDefault(fieldName) @@ -98,12 +91,10 @@ data class NotificationConfig( } name ?: throw IllegalArgumentException("$NAME_TAG field absent") configType ?: throw IllegalArgumentException("$CONFIG_TYPE_TAG field absent") - features ?: throw IllegalArgumentException("$FEATURE_LIST_TAG field absent") return NotificationConfig( name, description, configType, - features, configData, isEnabled ) @@ -119,7 +110,6 @@ data class NotificationConfig( .field(NAME_TAG, name) .field(DESCRIPTION_TAG, description) .field(CONFIG_TYPE_TAG, configType.tag) - .field(FEATURE_LIST_TAG, features) .field(IS_ENABLED_TAG, isEnabled) .fieldIfNotNull(configType.tag, configData) .endObject() @@ -133,7 +123,6 @@ data class NotificationConfig( name = input.readString(), description = input.readString(), configType = input.readEnum(ConfigType::class.java), - features = input.readSet(STRING_READER), isEnabled = input.readBoolean(), configData = input.readOptionalWriteable(getReaderForConfigType(input.readEnum(ConfigType::class.java))) ) @@ -145,7 +134,6 @@ data class NotificationConfig( output.writeString(name) output.writeString(description) output.writeEnum(configType) - output.writeCollection(features, STRING_WRITER) output.writeBoolean(isEnabled) // Reading config types multiple times in constructor output.writeEnum(configType) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt index 8147650f..f6d3f9c1 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt @@ -21,9 +21,6 @@ import org.opensearch.action.ActionListener import org.opensearch.action.ActionType import org.opensearch.client.node.NodeClient import org.opensearch.commons.destination.response.LegacyDestinationResponse -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.notifications.action.CreateNotificationConfigRequest import org.opensearch.commons.notifications.action.CreateNotificationConfigResponse import org.opensearch.commons.notifications.action.DeleteNotificationConfigRequest @@ -198,7 +195,6 @@ internal class NotificationsPluginInterfaceTests { val notificationInfo = EventSource( "title", "reference_id", - FEATURE_REPORTS, SeverityType.HIGH, listOf("tag1", "tag2") ) @@ -235,10 +231,6 @@ internal class NotificationsPluginInterfaceTests { .onResponse(res) }.whenever(client).execute(any(ActionType::class.java), any(), any()) - doAnswer { - FEATURE_INDEX_MANAGEMENT - }.whenever(request).feature - NotificationsPluginInterface.publishLegacyNotification(client, request, l) verify(l, times(1)).onResponse(eq(res)) } @@ -249,7 +241,6 @@ internal class NotificationsPluginInterfaceTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_REPORTS), configData = sampleSlack ) val configInfo = NotificationConfigInfo( @@ -265,7 +256,6 @@ internal class NotificationsPluginInterfaceTests { val sampleEventSource = EventSource( "title", "reference_id", - FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt index 9eb1de1f..24f389e2 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt @@ -9,7 +9,6 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT import org.opensearch.commons.notifications.model.Chime import org.opensearch.commons.notifications.model.ConfigType import org.opensearch.commons.notifications.model.Email @@ -32,9 +31,8 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.WEBHOOK, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleWebhook + configData = sampleWebhook, + isEnabled = true ) } @@ -44,9 +42,8 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleSlack + configData = sampleSlack, + isEnabled = true ) } @@ -56,9 +53,8 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.CHIME, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleChime + configData = sampleChime, + isEnabled = true ) } @@ -68,9 +64,8 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL_GROUP, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleEmailGroup + configData = sampleEmailGroup, + isEnabled = true ) } @@ -84,9 +79,8 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleEmail + configData = sampleEmail, + isEnabled = true ) } @@ -101,9 +95,8 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.SMTP_ACCOUNT, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleSmtpAccount + configData = sampleSmtpAccount, + isEnabled = true ) } @@ -264,9 +257,8 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleSlack + configData = sampleSlack, + isEnabled = true ) val jsonString = """ @@ -275,7 +267,6 @@ internal class CreateNotificationConfigRequestTests { "name":"name", "description":"description", "config_type":"slack", - "feature_list":["index_management"], "is_enabled":true, "slack":{"url":"https://domain.com/sample_slack_url#1234567890"} } @@ -292,9 +283,8 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.WEBHOOK, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleWebhook + configData = sampleWebhook, + isEnabled = true ) val jsonString = """ @@ -303,7 +293,6 @@ internal class CreateNotificationConfigRequestTests { "name":"name", "description":"description", "config_type":"webhook", - "feature_list":["index_management"], "is_enabled":true, "webhook":{"url":"https://domain.com/sample_webhook_url#1234567890"} } @@ -320,9 +309,8 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.CHIME, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleChime + configData = sampleChime, + isEnabled = true ) val jsonString = """ @@ -332,7 +320,6 @@ internal class CreateNotificationConfigRequestTests { "name":"name", "description":"description", "config_type":"chime", - "feature_list":["index_management"], "is_enabled":true, "chime":{"url":"https://domain.com/sample_chime_url#1234567890"} } @@ -349,9 +336,8 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL_GROUP, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleEmailGroup + configData = sampleEmailGroup, + isEnabled = true ) val jsonString = """ @@ -361,7 +347,6 @@ internal class CreateNotificationConfigRequestTests { "name":"name", "description":"description", "config_type":"email_group", - "feature_list":["index_management"], "is_enabled":true, "email_group":{"recipient_list":[{"recipient":"dummy@company.com"}]} } @@ -382,9 +367,8 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleEmail + configData = sampleEmail, + isEnabled = true ) val jsonString = """ @@ -394,7 +378,6 @@ internal class CreateNotificationConfigRequestTests { "name":"name", "description":"description", "config_type":"email", - "feature_list":["index_management"], "is_enabled":true, "email":{ "email_account_id":"sample_1@dummy.com", @@ -420,9 +403,8 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.SMTP_ACCOUNT, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleSmtpAccount + configData = sampleSmtpAccount, + isEnabled = true ) val jsonString = """ @@ -432,7 +414,6 @@ internal class CreateNotificationConfigRequestTests { "name":"name", "description":"description", "config_type":"smtp_account", - "feature_list":["index_management"], "is_enabled":true, "smtp_account":{"host":"http://dummy.com", "port":11,"method": "ssl", "from_address": "sample@dummy.com" } } @@ -463,7 +444,6 @@ internal class CreateNotificationConfigRequestTests { "name":"name", "description":"description", "config_type":"chime", - "feature_list":["index_management"], "is_enabled":true, "chime":{"url":"https://domain.com/sample_chime_url#1234567890"} } @@ -488,9 +468,8 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleSlack + configData = sampleSlack, + isEnabled = true ) val jsonString = """ @@ -499,7 +478,6 @@ internal class CreateNotificationConfigRequestTests { "name":"name", "description":"description", "config_type":"slack", - "feature_list":["index_management"], "is_enabled":true, "slack":{"url":"https://domain.com/sample_slack_url#1234567890"}, "extra_field_1":["extra", "value"], diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt index 98d577de..b9913ae2 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt @@ -8,9 +8,6 @@ import com.fasterxml.jackson.core.JsonParseException import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject @@ -21,19 +18,19 @@ internal class GetFeatureChannelListRequestTests { expected: GetFeatureChannelListRequest, actual: GetFeatureChannelListRequest ) { - assertEquals(expected.feature, actual.feature) + assertEquals(expected.compact, actual.compact) } @Test fun `Get request serialize and deserialize transport object should be equal`() { - val configRequest = GetFeatureChannelListRequest(FEATURE_REPORTS) + val configRequest = GetFeatureChannelListRequest() val recreatedObject = recreateObject(configRequest) { GetFeatureChannelListRequest(it) } assertGetRequestEquals(configRequest, recreatedObject) } @Test fun `Get request serialize and deserialize using json object should be equal`() { - val configRequest = GetFeatureChannelListRequest(FEATURE_INDEX_MANAGEMENT) + val configRequest = GetFeatureChannelListRequest() val jsonString = getJsonString(configRequest) val recreatedObject = createObjectFromJsonString(jsonString) { GetFeatureChannelListRequest.parse(it) } assertGetRequestEquals(configRequest, recreatedObject) @@ -49,10 +46,9 @@ internal class GetFeatureChannelListRequestTests { @Test fun `Get request should safely ignore extra field in json object`() { - val configRequest = GetFeatureChannelListRequest(FEATURE_ALERTING) + val configRequest = GetFeatureChannelListRequest() val jsonString = """ { - "feature":"${configRequest.feature}", "extra_field_1":["extra", "value"], "extra_field_2":{"extra":"value"}, "extra_field_3":"extra value 3" @@ -61,15 +57,4 @@ internal class GetFeatureChannelListRequestTests { val recreatedObject = createObjectFromJsonString(jsonString) { GetFeatureChannelListRequest.parse(it) } assertGetRequestEquals(configRequest, recreatedObject) } - - @Test - fun `Get request should throw exception if feature field is absent in json object`() { - val jsonString = """ - { - } - """.trimIndent() - assertThrows { - createObjectFromJsonString(jsonString) { GetFeatureChannelListRequest.parse(it) } - } - } } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt index 7541acb2..3cfccfcb 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt @@ -8,8 +8,6 @@ import org.apache.lucene.search.TotalHits import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.notifications.model.Chime import org.opensearch.commons.notifications.model.ConfigType import org.opensearch.commons.notifications.model.NotificationConfig @@ -41,7 +39,6 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_REPORTS), configData = sampleSlack ) val configInfo = NotificationConfigInfo( @@ -62,7 +59,6 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_REPORTS), configData = Slack("https://domain.com/sample_url#1234567890") ) val configInfo1 = NotificationConfigInfo( @@ -75,7 +71,6 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.CHIME, - setOf(FEATURE_INDEX_MANAGEMENT), configData = Chime("https://domain.com/sample_url#1234567890") ) val configInfo2 = NotificationConfigInfo( @@ -104,7 +99,6 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_REPORTS), configData = sampleSlack ) val configInfo = NotificationConfigInfo( @@ -128,7 +122,6 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_REPORTS), configData = Slack("https://domain.com/sample_url#1234567890") ) val configInfo1 = NotificationConfigInfo( @@ -141,7 +134,6 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.CHIME, - setOf(FEATURE_INDEX_MANAGEMENT), configData = Chime("https://domain.com/sample_url#1234567890") ) val configInfo2 = NotificationConfigInfo( @@ -171,9 +163,8 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleSlack + configData = sampleSlack, + isEnabled = true ) val configInfo = NotificationConfigInfo( "config-Id", @@ -196,7 +187,6 @@ internal class GetNotificationConfigResponseTests { "name":"name", "description":"description", "config_type":"slack", - "feature_list":["index_management"], "is_enabled":true, "slack":{"url":"https://domain.com/sample_slack_url#1234567890"} } @@ -220,9 +210,8 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleSlack + configData = sampleSlack, + isEnabled = true ) val configInfo = NotificationConfigInfo( "config-Id", @@ -242,7 +231,6 @@ internal class GetNotificationConfigResponseTests { "name":"name", "description":"description", "config_type":"slack", - "feature_list":["index_management"], "is_enabled":true, "slack":{"url":"https://domain.com/sample_slack_url#1234567890"} } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt index 16139bb1..0de1c78b 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt @@ -8,8 +8,6 @@ import org.apache.lucene.search.TotalHits import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.notifications.model.ConfigType import org.opensearch.commons.notifications.model.DeliveryStatus import org.opensearch.commons.notifications.model.EventSource @@ -41,7 +39,6 @@ internal class GetNotificationEventResponseTests { val sampleEventSource = EventSource( "title", "reference_id", - FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -68,13 +65,11 @@ internal class GetNotificationEventResponseTests { val eventSource1 = EventSource( "title 1", "reference_id_1", - FEATURE_ALERTING, severity = SeverityType.INFO ) val eventSource2 = EventSource( "title 2", "reference_id_2", - FEATURE_REPORTS, severity = SeverityType.HIGH ) val status1 = EventStatus( @@ -131,7 +126,6 @@ internal class GetNotificationEventResponseTests { val sampleEventSource = EventSource( "title", "reference_id", - FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -161,13 +155,11 @@ internal class GetNotificationEventResponseTests { val eventSource1 = EventSource( "title 1", "reference_id_1", - FEATURE_ALERTING, severity = SeverityType.INFO ) val eventSource2 = EventSource( "title 2", "reference_id_2", - FEATURE_REPORTS, severity = SeverityType.HIGH ) val status1 = EventStatus( @@ -213,7 +205,6 @@ internal class GetNotificationEventResponseTests { val sampleEventSource = EventSource( "title", "reference_id", - FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -244,7 +235,6 @@ internal class GetNotificationEventResponseTests { "event_source":{ "title":"title", "reference_id":"reference_id", - "feature":"alerting", "severity":"info", "tags":[] }, @@ -279,7 +269,6 @@ internal class GetNotificationEventResponseTests { val sampleEventSource = EventSource( "title", "reference_id", - FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -307,7 +296,6 @@ internal class GetNotificationEventResponseTests { "event_source":{ "title":"title", "reference_id":"reference_id", - "feature":"alerting", "severity":"info", "tags":[] }, diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequestTests.kt index c323ec85..e4b990a5 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequestTests.kt @@ -9,7 +9,6 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test import org.opensearch.commons.destination.message.LegacyChimeMessage -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT import org.opensearch.commons.utils.recreateObject internal class LegacyPublishNotificationRequestTests { @@ -22,14 +21,13 @@ internal class LegacyPublishNotificationRequestTests { assertEquals(expected.baseMessage.channelType, actual.baseMessage.channelType) assertEquals(expected.baseMessage.messageContent, actual.baseMessage.messageContent) assertEquals(expected.baseMessage.url, actual.baseMessage.url) - assertEquals(expected.feature, actual.feature) assertNull(actual.validate()) } @Test fun `publish request serialize and deserialize transport object should be equal`() { val baseMessage = LegacyChimeMessage.Builder("chime_message").withMessage("Hello world").withUrl("https://amazon.com").build() - val request = LegacyPublishNotificationRequest(baseMessage, FEATURE_INDEX_MANAGEMENT) + val request = LegacyPublishNotificationRequest(baseMessage) val recreatedObject = recreateObject(request) { LegacyPublishNotificationRequest(it) } assertRequestEquals(request, recreatedObject) } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequestTests.kt index 3874190c..70e0bd6c 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequestTests.kt @@ -10,9 +10,6 @@ import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.notifications.model.ChannelMessage import org.opensearch.commons.notifications.model.EventSource import org.opensearch.commons.notifications.model.SeverityType @@ -38,7 +35,6 @@ internal class SendNotificationRequestTests { val notificationInfo = EventSource( "title", "reference_id", - FEATURE_REPORTS, SeverityType.HIGH, listOf("tag1", "tag2") ) @@ -62,7 +58,6 @@ internal class SendNotificationRequestTests { val notificationInfo = EventSource( "title", "reference_id", - FEATURE_INDEX_MANAGEMENT, SeverityType.CRITICAL, listOf("tag1", "tag2") ) @@ -95,7 +90,6 @@ internal class SendNotificationRequestTests { val notificationInfo = EventSource( "title", "reference_id", - FEATURE_ALERTING, SeverityType.HIGH, listOf("tag1", "tag2") ) @@ -115,7 +109,6 @@ internal class SendNotificationRequestTests { "event_source":{ "title":"${notificationInfo.title}", "reference_id":"${notificationInfo.referenceId}", - "feature":"${notificationInfo.feature}", "severity":"${notificationInfo.severity}", "tags":["tag1", "tag2"] }, @@ -139,7 +132,6 @@ internal class SendNotificationRequestTests { val notificationInfo = EventSource( "title", "reference_id", - FEATURE_REPORTS, SeverityType.INFO, listOf("tag1", "tag2") ) @@ -159,7 +151,6 @@ internal class SendNotificationRequestTests { "event_source":{ "title":"${notificationInfo.title}", "reference_id":"${notificationInfo.referenceId}", - "feature":"${notificationInfo.feature}", "severity":"${notificationInfo.severity}", "tags":["tag1", "tag2"] }, @@ -196,7 +187,6 @@ internal class SendNotificationRequestTests { "event_source":{ "title":"title", "reference_id":"reference_id", - "feature":"feature", "severity":"High", "tags":["tag1", "tag2"] }, @@ -215,7 +205,6 @@ internal class SendNotificationRequestTests { "event_source":{ "title":"title", "reference_id":"reference_id", - "feature":"feature", "severity":"High", "tags":["tag1", "tag2"] }, @@ -236,7 +225,6 @@ internal class SendNotificationRequestTests { "event_source":{ "title":"title", "reference_id":"reference_id", - "feature":"feature", "severity":"High", "tags":["tag1", "tag2"] }, diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt index 725e3c59..a6e0077a 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt @@ -9,7 +9,6 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT import org.opensearch.commons.notifications.model.Chime import org.opensearch.commons.notifications.model.ConfigType import org.opensearch.commons.notifications.model.Email @@ -32,9 +31,8 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.WEBHOOK, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleWebhook + configData = sampleWebhook, + isEnabled = true ) } @@ -44,9 +42,8 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleSlack + configData = sampleSlack, + isEnabled = true ) } @@ -56,9 +53,8 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.CHIME, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleChime + configData = sampleChime, + isEnabled = true ) } @@ -68,9 +64,8 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL_GROUP, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleEmailGroup + configData = sampleEmailGroup, + isEnabled = true ) } @@ -84,9 +79,8 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleEmail + configData = sampleEmail, + isEnabled = true ) } @@ -101,9 +95,8 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.SMTP_ACCOUNT, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleSmtpAccount + configData = sampleSmtpAccount, + isEnabled = true ) } @@ -228,9 +221,8 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleSlack + configData = sampleSlack, + isEnabled = true ) val jsonString = """ @@ -258,9 +250,8 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.WEBHOOK, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleWebhook + configData = sampleWebhook, + isEnabled = true ) val jsonString = """ @@ -288,9 +279,8 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.CHIME, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleChime + configData = sampleChime, + isEnabled = true ) val jsonString = """ @@ -318,9 +308,8 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL_GROUP, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleEmailGroup + configData = sampleEmailGroup, + isEnabled = true ) val jsonString = """ @@ -352,9 +341,8 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleEmail + configData = sampleEmail, + isEnabled = true ) val jsonString = """ @@ -391,9 +379,8 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.SMTP_ACCOUNT, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleSmtpAccount + configData = sampleSmtpAccount, + isEnabled = true ) val jsonString = """ @@ -429,9 +416,8 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleSlack + configData = sampleSlack, + isEnabled = true ) val jsonString = """ diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/EventSourceTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/EventSourceTests.kt index 2244d0c4..b36a2fc0 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/EventSourceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/EventSourceTests.kt @@ -7,7 +7,6 @@ package org.opensearch.commons.notifications.model import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject @@ -19,7 +18,6 @@ internal class EventSourceTests { val sampleEventSource = EventSource( "title", "reference_id", - FEATURE_ALERTING, severity = SeverityType.INFO ) val recreatedObject = recreateObject(sampleEventSource) { EventSource(it) } @@ -31,7 +29,6 @@ internal class EventSourceTests { val sampleEventSource = EventSource( "title", "reference_id", - FEATURE_ALERTING, severity = SeverityType.INFO ) @@ -45,9 +42,8 @@ internal class EventSourceTests { val sampleEventSource = EventSource( "title", "reference_id", - FEATURE_ALERTING, - tags = listOf("tag1", "tag2"), - severity = SeverityType.INFO + severity = SeverityType.INFO, + tags = listOf("tag1", "tag2") ) val jsonString = """ { @@ -70,9 +66,8 @@ internal class EventSourceTests { val sampleEventSource = EventSource( "title", "reference_id", - "NewFeature", - tags = listOf("tag1", "tag2"), - severity = SeverityType.INFO + severity = SeverityType.INFO, + tags = listOf("tag1", "tag2") ) val jsonString = """ { @@ -93,9 +88,8 @@ internal class EventSourceTests { EventSource( "", "reference_id", - FEATURE_ALERTING, - tags = listOf("tag1", "tag2"), - severity = SeverityType.INFO + severity = SeverityType.INFO, + tags = listOf("tag1", "tag2") ) } } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt index d14b9f34..c4e28f33 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt @@ -7,8 +7,6 @@ package org.opensearch.commons.notifications.model import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject @@ -23,7 +21,6 @@ internal class NotificationConfigInfoTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_REPORTS), configData = sampleSlack ) val configInfo = NotificationConfigInfo( @@ -45,7 +42,6 @@ internal class NotificationConfigInfoTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_REPORTS), configData = sampleSlack ) val configInfo = NotificationConfigInfo( @@ -68,9 +64,8 @@ internal class NotificationConfigInfoTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleSlack + configData = sampleSlack, + isEnabled = true ) val configInfo = NotificationConfigInfo( "config-Id", @@ -107,7 +102,6 @@ internal class NotificationConfigInfoTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_REPORTS), configData = sampleSlack ) Assertions.assertThrows(IllegalArgumentException::class.java) { diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt index 835af358..06bb3557 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt @@ -8,8 +8,6 @@ import org.apache.lucene.search.TotalHits import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject @@ -35,7 +33,6 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_REPORTS), configData = sampleSlack ) val configInfo = NotificationConfigInfo( @@ -55,7 +52,6 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_REPORTS), configData = Slack("https://domain.com/sample_url#1234567890") ) val configInfo1 = NotificationConfigInfo( @@ -68,7 +64,6 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.CHIME, - setOf(FEATURE_INDEX_MANAGEMENT), configData = Chime("https://domain.com/sample_url#1234567890") ) val configInfo2 = NotificationConfigInfo( @@ -94,7 +89,6 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_REPORTS), configData = Slack("https://domain.com/sample_url#1234567890") ) val configInfo1 = NotificationConfigInfo( @@ -107,7 +101,6 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.CHIME, - setOf(FEATURE_INDEX_MANAGEMENT), configData = Chime("https://domain.com/sample_url#1234567890") ) val configInfo2 = NotificationConfigInfo( @@ -135,7 +128,6 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_REPORTS), configData = sampleSlack ) val configInfo = NotificationConfigInfo( @@ -158,7 +150,6 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_REPORTS), configData = Slack("https://domain.com/sample_url#1234567890") ) val configInfo1 = NotificationConfigInfo( @@ -171,7 +162,6 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.CHIME, - setOf(FEATURE_INDEX_MANAGEMENT), configData = Chime("https://domain.com/sample_url#1234567890") ) val configInfo2 = NotificationConfigInfo( @@ -200,9 +190,8 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleSlack + configData = sampleSlack, + isEnabled = true ) val configInfo = NotificationConfigInfo( "config-Id", @@ -249,9 +238,8 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleSlack + configData = sampleSlack, + isEnabled = true ) val configInfo = NotificationConfigInfo( "config-Id", diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt index 47614986..f8aa9e9d 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt @@ -6,9 +6,6 @@ package org.opensearch.commons.notifications.model import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject @@ -22,7 +19,6 @@ internal class NotificationConfigTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_REPORTS), configData = sampleSlack ) val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } @@ -36,7 +32,6 @@ internal class NotificationConfigTests { "name", "description", ConfigType.SLACK, - setOf(FEATURE_REPORTS), configData = sampleSlack ) val jsonString = getJsonString(sampleConfig) @@ -51,7 +46,6 @@ internal class NotificationConfigTests { "name", "description", ConfigType.CHIME, - setOf(FEATURE_ALERTING), configData = sampleChime ) val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } @@ -65,7 +59,6 @@ internal class NotificationConfigTests { "name", "description", ConfigType.CHIME, - setOf(FEATURE_ALERTING), configData = sampleChime ) val jsonString = getJsonString(sampleConfig) @@ -80,7 +73,6 @@ internal class NotificationConfigTests { "name", "description", ConfigType.WEBHOOK, - setOf(FEATURE_INDEX_MANAGEMENT), configData = sampleWebhook ) val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } @@ -94,7 +86,6 @@ internal class NotificationConfigTests { "name", "description", ConfigType.WEBHOOK, - setOf(FEATURE_INDEX_MANAGEMENT), configData = sampleWebhook ) val jsonString = getJsonString(sampleConfig) @@ -109,7 +100,6 @@ internal class NotificationConfigTests { "name", "description", ConfigType.EMAIL, - setOf(FEATURE_INDEX_MANAGEMENT), configData = sampleEmail ) val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } @@ -123,7 +113,6 @@ internal class NotificationConfigTests { "name", "description", ConfigType.EMAIL, - setOf(FEATURE_INDEX_MANAGEMENT), configData = sampleEmail ) val jsonString = getJsonString(sampleConfig) @@ -138,7 +127,6 @@ internal class NotificationConfigTests { "name", "description", ConfigType.SMTP_ACCOUNT, - setOf(FEATURE_INDEX_MANAGEMENT), configData = smtpAccount ) val jsonString = getJsonString(sampleConfig) @@ -153,7 +141,6 @@ internal class NotificationConfigTests { "name", "description", ConfigType.SMTP_ACCOUNT, - setOf(FEATURE_INDEX_MANAGEMENT), configData = sampleSmtpAccount ) val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } @@ -167,7 +154,6 @@ internal class NotificationConfigTests { "name", "description", ConfigType.EMAIL_GROUP, - setOf(FEATURE_INDEX_MANAGEMENT), configData = sampleEmailGroup ) val jsonString = getJsonString(sampleConfig) @@ -182,7 +168,6 @@ internal class NotificationConfigTests { "name", "description", ConfigType.EMAIL_GROUP, - setOf(FEATURE_INDEX_MANAGEMENT), configData = sampleEmailGroup ) val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } @@ -197,9 +182,8 @@ internal class NotificationConfigTests { "name", "description", ConfigType.NONE, - setOf(FEATURE_INDEX_MANAGEMENT), - isEnabled = true, - configData = sampleSlack + configData = sampleSlack, + isEnabled = true ) val jsonString = """ { @@ -226,9 +210,8 @@ internal class NotificationConfigTests { "name", "description", ConfigType.WEBHOOK, - setOf(FEATURE_INDEX_MANAGEMENT, "NewFeature1", "NewFeature2"), - isEnabled = true, - configData = sampleWebhook + configData = sampleWebhook, + isEnabled = true ) val jsonString = """ { diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt index a87e334a..85d2dee9 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt @@ -7,7 +7,6 @@ package org.opensearch.commons.notifications.model import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject @@ -20,7 +19,6 @@ internal class NotificationEventInfoTests { val sampleEventSource = EventSource( "title", "reference_id", - FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -47,7 +45,6 @@ internal class NotificationEventInfoTests { val sampleEventSource = EventSource( "title", "reference_id", - FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -75,7 +72,6 @@ internal class NotificationEventInfoTests { val sampleEventSource = EventSource( "title", "reference_id", - FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -133,7 +129,6 @@ internal class NotificationEventInfoTests { val sampleEventSource = EventSource( "title", "reference_id", - FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt index ae3f01a9..b65a0783 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt @@ -8,8 +8,6 @@ import org.apache.lucene.search.TotalHits import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject @@ -33,7 +31,6 @@ internal class NotificationEventSearchResultTests { val sampleEventSource = EventSource( "title", "reference_id", - FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -59,13 +56,11 @@ internal class NotificationEventSearchResultTests { val eventSource1 = EventSource( "title 1", "reference_id_1", - FEATURE_ALERTING, severity = SeverityType.INFO ) val eventSource2 = EventSource( "title 2", "reference_id_2", - FEATURE_REPORTS, severity = SeverityType.HIGH ) val status1 = EventStatus( @@ -122,13 +117,11 @@ internal class NotificationEventSearchResultTests { val eventSource1 = EventSource( "title 1", "reference_id_1", - FEATURE_ALERTING, severity = SeverityType.INFO ) val eventSource2 = EventSource( "title 2", "reference_id_2", - FEATURE_REPORTS, severity = SeverityType.HIGH ) val status1 = EventStatus( @@ -184,7 +177,6 @@ internal class NotificationEventSearchResultTests { val sampleEventSource = EventSource( "title", "reference_id", - FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -213,13 +205,11 @@ internal class NotificationEventSearchResultTests { val eventSource1 = EventSource( "title 1", "reference_id_1", - FEATURE_ALERTING, severity = SeverityType.INFO ) val eventSource2 = EventSource( "title 2", "reference_id_2", - FEATURE_REPORTS, severity = SeverityType.HIGH ) val status1 = EventStatus( @@ -264,7 +254,6 @@ internal class NotificationEventSearchResultTests { val sampleEventSource = EventSource( "title", "reference_id", - FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -330,7 +319,6 @@ internal class NotificationEventSearchResultTests { val sampleEventSource = EventSource( "title", "reference_id", - FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventTests.kt index 313ffca9..57df6066 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventTests.kt @@ -7,8 +7,6 @@ package org.opensearch.commons.notifications.model import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject @@ -20,7 +18,6 @@ internal class NotificationEventTests { val sampleEventSource = EventSource( "title", "reference_id", - FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -39,7 +36,6 @@ internal class NotificationEventTests { val sampleEventSource = EventSource( "title", "reference_id", - FEATURE_REPORTS, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -59,9 +55,8 @@ internal class NotificationEventTests { val sampleEventSource = EventSource( "title", "reference_id", - FEATURE_ALERTING, - tags = listOf("tag1", "tag2"), - severity = SeverityType.INFO + severity = SeverityType.INFO, + tags = listOf("tag1", "tag2") ) val status1 = EventStatus( "config_id1", From 7ab297e5f99be5a8cf1b3225f570587b10d301bc Mon Sep 17 00:00:00 2001 From: Marc Handalian Date: Fri, 18 Mar 2022 12:14:33 -0700 Subject: [PATCH 041/149] Add opensearch repositories plugin to resolve lucene snapshot dependencies. (#137) Signed-off-by: Marc Handalian --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 16c28922..bc142d75 100644 --- a/build.gradle +++ b/build.gradle @@ -65,6 +65,7 @@ apply plugin: 'com.github.johnrengelman.shadow' apply plugin: 'io.gitlab.arturbosch.detekt' apply plugin: 'org.jetbrains.kotlin.jvm' apply plugin: 'org.jetbrains.kotlin.plugin.allopen' +apply plugin: 'opensearch.repositories' configurations { ktlint From a0868058fac2dbd1f109e0213ec030b569d02941 Mon Sep 17 00:00:00 2001 From: Saurabh Singh Date: Mon, 21 Mar 2022 15:22:54 -0700 Subject: [PATCH 042/149] Update MAINTAINERS.m to add new maintainer. (#127) Signed-off-by: Saurabh Singh Co-authored-by: Saurabh Singh --- MAINTAINERS.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/MAINTAINERS.md b/MAINTAINERS.md index c50082af..3802e32b 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -1,13 +1,14 @@ ## Maintainers -| Maintainer | GitHub ID | Affiliation | -| --------------- | --------- | ----------- | -| Ashish Agrawal | [lezzago](https://github.com/lezzago) | Amazon | -| Mohammad Qureshi | [qreshi](https://github.com/qreshi) | Amazon | -| Sriram Kosuri | [skkosuri-amzn](https://github.com/skkosuri-amzn) | Amazon | -| Bowen Lan | [bowenlan-amzn](https://github.com/bowenlan-amzn) | Amazon | -| Rishabh Maurya | [rishabhmaurya](https://github.com/rishabhmaurya) | Amazon | -| Tianli Feng | [tlfeng](https://github.com/tlfeng) | Amazon | -| Annie Lee | [leeyun-amzn](https://github.com/leeyun-amzn) | Amazon | +| Maintainer | GitHub ID | Affiliation | +|------------------|---------------------------------------------------| ----------- | +| Ashish Agrawal | [lezzago](https://github.com/lezzago) | Amazon | +| Mohammad Qureshi | [qreshi](https://github.com/qreshi) | Amazon | +| Sriram Kosuri | [skkosuri-amzn](https://github.com/skkosuri-amzn) | Amazon | +| Bowen Lan | [bowenlan-amzn](https://github.com/bowenlan-amzn) | Amazon | +| Rishabh Maurya | [rishabhmaurya](https://github.com/rishabhmaurya) | Amazon | +| Tianli Feng | [tlfeng](https://github.com/tlfeng) | Amazon | +| Annie Lee | [leeyun-amzn](https://github.com/leeyun-amzn) | Amazon | +| Saurabh Singh | [getsaurabh02](https://github.com/getsaurabh02) | Amazon | [This document](https://github.com/opensearch-project/.github/blob/main/MAINTAINERS.md) explains what maintainers do in this repo, and how they should be doing it. If you're interested in contributing, see [CONTRIBUTING](CONTRIBUTING.md). From d77dec5a69e9f99c2948a06d088b505e5fa1dd7e Mon Sep 17 00:00:00 2001 From: Mohammad Qureshi <47198598+qreshi@users.noreply.github.com> Date: Tue, 22 Mar 2022 10:19:41 -0700 Subject: [PATCH 043/149] Rename references for Get Channels API (#140) Signed-off-by: Mohammad Qureshi <47198598+qreshi@users.noreply.github.com> --- .../notifications/NotificationConstants.kt | 2 +- .../NotificationsPluginInterface.kt | 18 ++-- ...istRequest.kt => GetChannelListRequest.kt} | 12 +-- ...tResponse.kt => GetChannelListResponse.kt} | 16 ++-- .../action/NotificationsActions.kt | 8 +- .../model/{FeatureChannel.kt => Channel.kt} | 14 +-- .../{FeatureChannelList.kt => ChannelList.kt} | 26 +++--- .../NotificationsPluginInterfaceTests.kt | 24 +++--- ...Tests.kt => GetChannelListRequestTests.kt} | 20 ++--- ...ests.kt => GetChannelListResponseTests.kt} | 78 ++++++++--------- ...hannelListTests.kt => ChannelListTests.kt} | 86 +++++++++---------- ...FeatureChannelTests.kt => ChannelTests.kt} | 72 ++++++++-------- .../model/FilterConfigListTests.kt | 64 +++++++------- .../notifications/model/FilterConfigTests.kt | 28 +++--- 14 files changed, 234 insertions(+), 234 deletions(-) rename src/main/kotlin/org/opensearch/commons/notifications/action/{GetFeatureChannelListRequest.kt => GetChannelListRequest.kt} (88%) rename src/main/kotlin/org/opensearch/commons/notifications/action/{GetFeatureChannelListResponse.kt => GetChannelListResponse.kt} (75%) rename src/main/kotlin/org/opensearch/commons/notifications/model/{FeatureChannel.kt => Channel.kt} (92%) rename src/main/kotlin/org/opensearch/commons/notifications/model/{FeatureChannelList.kt => ChannelList.kt} (62%) rename src/test/kotlin/org/opensearch/commons/notifications/action/{GetFeatureChannelListRequestTests.kt => GetChannelListRequestTests.kt} (76%) rename src/test/kotlin/org/opensearch/commons/notifications/action/{GetFeatureChannelListResponseTests.kt => GetChannelListResponseTests.kt} (73%) rename src/test/kotlin/org/opensearch/commons/notifications/model/{FeatureChannelListTests.kt => ChannelListTests.kt} (66%) rename src/test/kotlin/org/opensearch/commons/notifications/model/{FeatureChannelTests.kt => ChannelTests.kt} (65%) diff --git a/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt index 6ebf7889..146c48de 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt @@ -52,7 +52,7 @@ object NotificationConstants { const val CREATED_TIME_TAG = "created_time_ms" const val CONFIG_LIST_TAG = "config_list" const val EVENT_LIST_TAG = "event_list" - const val FEATURE_CONFIG_LIST_TAG = "feature_channel_list" + const val CHANNEL_LIST_TAG = "channel_list" const val DELETE_RESPONSE_LIST_TAG = "delete_response_list" const val FROM_INDEX_TAG = "from_index" const val MAX_ITEMS_TAG = "max_items" diff --git a/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt b/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt index b67ec282..2da0562d 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt @@ -14,8 +14,8 @@ import org.opensearch.commons.notifications.action.CreateNotificationConfigReque import org.opensearch.commons.notifications.action.CreateNotificationConfigResponse import org.opensearch.commons.notifications.action.DeleteNotificationConfigRequest import org.opensearch.commons.notifications.action.DeleteNotificationConfigResponse -import org.opensearch.commons.notifications.action.GetFeatureChannelListRequest -import org.opensearch.commons.notifications.action.GetFeatureChannelListResponse +import org.opensearch.commons.notifications.action.GetChannelListRequest +import org.opensearch.commons.notifications.action.GetChannelListResponse import org.opensearch.commons.notifications.action.GetNotificationConfigRequest import org.opensearch.commons.notifications.action.GetNotificationConfigResponse import org.opensearch.commons.notifications.action.GetNotificationEventRequest @@ -26,7 +26,7 @@ import org.opensearch.commons.notifications.action.LegacyPublishNotificationRequ import org.opensearch.commons.notifications.action.LegacyPublishNotificationResponse import org.opensearch.commons.notifications.action.NotificationsActions.CREATE_NOTIFICATION_CONFIG_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.DELETE_NOTIFICATION_CONFIG_ACTION_TYPE -import org.opensearch.commons.notifications.action.NotificationsActions.GET_FEATURE_CHANNEL_LIST_ACTION_TYPE +import org.opensearch.commons.notifications.action.NotificationsActions.GET_CHANNEL_LIST_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.GET_NOTIFICATION_CONFIG_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.GET_NOTIFICATION_EVENT_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.GET_PLUGIN_FEATURES_ACTION_TYPE @@ -156,20 +156,20 @@ object NotificationsPluginInterface { } /** - * Get notification channel configuration enabled for a feature. + * Get notification channel configuration. * @param client Node client for making transport action * @param request The request object * @param listener The listener for getting response */ - fun getFeatureChannelList( + fun getChannelList( client: NodeClient, - request: GetFeatureChannelListRequest, - listener: ActionListener + request: GetChannelListRequest, + listener: ActionListener ) { client.execute( - GET_FEATURE_CHANNEL_LIST_ACTION_TYPE, + GET_CHANNEL_LIST_ACTION_TYPE, request, - wrapActionListener(listener) { response -> recreateObject(response) { GetFeatureChannelListResponse(it) } } + wrapActionListener(listener) { response -> recreateObject(response) { GetChannelListResponse(it) } } ) } diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListRequest.kt similarity index 88% rename from src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt rename to src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListRequest.kt index 56a0b0fa..96a54eb2 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListRequest.kt @@ -21,16 +21,16 @@ import java.io.IOException /** * This request is plugin-only call. i.e. REST interface is not exposed. */ -class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { +class GetChannelListRequest : ActionRequest, ToXContentObject { val compact: Boolean // Dummy request parameter for transport request companion object { - private val log by logger(GetFeatureChannelListRequest::class.java) + private val log by logger(GetChannelListRequest::class.java) /** * reader to create instance of class from writable. */ - val reader = Writeable.Reader { GetFeatureChannelListRequest(it) } + val reader = Writeable.Reader { GetChannelListRequest(it) } /** * Creator used in REST communication. @@ -38,7 +38,7 @@ class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { */ @JvmStatic @Throws(IOException::class) - fun parse(parser: XContentParser): GetFeatureChannelListRequest { + fun parse(parser: XContentParser): GetChannelListRequest { var compact = false XContentParserUtils.ensureExpectedToken( @@ -53,11 +53,11 @@ class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { COMPACT_TAG -> compact = parser.booleanValue() else -> { parser.skipChildren() - log.info("Unexpected field: $fieldName, while parsing GetFeatureChannelListRequest") + log.info("Unexpected field: $fieldName, while parsing GetChannelListRequest") } } } - return GetFeatureChannelListRequest() + return GetChannelListRequest() } } diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListResponse.kt similarity index 75% rename from src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponse.kt rename to src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListResponse.kt index 79bf4435..89623ed0 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListResponse.kt @@ -10,21 +10,21 @@ import org.opensearch.common.io.stream.Writeable import org.opensearch.common.xcontent.ToXContent import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.common.xcontent.XContentParser -import org.opensearch.commons.notifications.model.FeatureChannelList +import org.opensearch.commons.notifications.model.ChannelList import java.io.IOException /** * Action Response for creating new configuration. */ -class GetFeatureChannelListResponse : BaseResponse { - val searchResult: FeatureChannelList +class GetChannelListResponse : BaseResponse { + val searchResult: ChannelList companion object { /** * reader to create instance of class from writable. */ - val reader = Writeable.Reader { GetFeatureChannelListResponse(it) } + val reader = Writeable.Reader { GetChannelListResponse(it) } /** * Creator used in REST communication. @@ -32,8 +32,8 @@ class GetFeatureChannelListResponse : BaseResponse { */ @JvmStatic @Throws(IOException::class) - fun parse(parser: XContentParser): GetFeatureChannelListResponse { - return GetFeatureChannelListResponse(FeatureChannelList(parser)) + fun parse(parser: XContentParser): GetChannelListResponse { + return GetChannelListResponse(ChannelList(parser)) } } @@ -41,7 +41,7 @@ class GetFeatureChannelListResponse : BaseResponse { * constructor for creating the class * @param searchResult the notification configuration list */ - constructor(searchResult: FeatureChannelList) { + constructor(searchResult: ChannelList) { this.searchResult = searchResult } @@ -50,7 +50,7 @@ class GetFeatureChannelListResponse : BaseResponse { */ @Throws(IOException::class) constructor(input: StreamInput) : super(input) { - searchResult = FeatureChannelList(input) + searchResult = ChannelList(input) } /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt index ca8dd2d7..204d9326 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt @@ -43,7 +43,7 @@ object NotificationsActions { /** * Get Config List for feature. Internal only - Inter plugin communication. */ - const val GET_FEATURE_CHANNEL_LIST_NAME = "cluster:admin/opensearch/notifications/feature/channels/get" + const val GET_CHANNEL_LIST_NAME = "cluster:admin/opensearch/notifications/channels/get" /** * Send notification message. Internal only - Inter plugin communication. @@ -93,10 +93,10 @@ object NotificationsActions { ActionType(GET_PLUGIN_FEATURES_NAME, ::GetPluginFeaturesResponse) /** - * Get Config List for feature transport action type. + * Get notification channel List transport action type. */ - val GET_FEATURE_CHANNEL_LIST_ACTION_TYPE = - ActionType(GET_FEATURE_CHANNEL_LIST_NAME, ::GetFeatureChannelListResponse) + val GET_CHANNEL_LIST_ACTION_TYPE = + ActionType(GET_CHANNEL_LIST_NAME, ::GetChannelListResponse) /** * Send notification transport action type. Internal only - Inter plugin communication. diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/FeatureChannel.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Channel.kt similarity index 92% rename from src/main/kotlin/org/opensearch/commons/notifications/model/FeatureChannel.kt rename to src/main/kotlin/org/opensearch/commons/notifications/model/Channel.kt index c0e74c56..a235d974 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/FeatureChannel.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Channel.kt @@ -21,9 +21,9 @@ import org.opensearch.commons.utils.logger import java.io.IOException /** - * Data class representing Notification config for exposed for other plugins. + * Data class representing Notification config exposed for other plugins. */ -data class FeatureChannel( +data class Channel( val configId: String, val name: String, val description: String, @@ -37,12 +37,12 @@ data class FeatureChannel( } companion object { - private val log by logger(FeatureChannel::class.java) + private val log by logger(Channel::class.java) /** * reader to create instance of class from writable. */ - val reader = Writeable.Reader { FeatureChannel(it) } + val reader = Writeable.Reader { Channel(it) } /** * Creator used in REST communication. @@ -51,7 +51,7 @@ data class FeatureChannel( @Suppress("ComplexMethod") @JvmStatic @Throws(IOException::class) - fun parse(parser: XContentParser): FeatureChannel { + fun parse(parser: XContentParser): Channel { var configId: String? = null var name: String? = null var description = "" @@ -74,14 +74,14 @@ data class FeatureChannel( IS_ENABLED_TAG -> isEnabled = parser.booleanValue() else -> { parser.skipChildren() - log.info("Unexpected field: $fieldName, while parsing FeatureChannel") + log.info("Unexpected field: $fieldName, while parsing Channel") } } } configId ?: throw IllegalArgumentException("$CONFIG_ID_TAG field absent") name ?: throw IllegalArgumentException("$NAME_TAG field absent") configType ?: throw IllegalArgumentException("$CONFIG_TYPE_TAG field absent") - return FeatureChannel( + return Channel( configId, name, description, diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/FeatureChannelList.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelList.kt similarity index 62% rename from src/main/kotlin/org/opensearch/commons/notifications/model/FeatureChannelList.kt rename to src/main/kotlin/org/opensearch/commons/notifications/model/ChannelList.kt index d5df20a1..7cb25c3a 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/FeatureChannelList.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelList.kt @@ -9,22 +9,22 @@ import org.apache.lucene.search.TotalHits import org.opensearch.action.search.SearchResponse import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.xcontent.XContentParser -import org.opensearch.commons.notifications.NotificationConstants.FEATURE_CONFIG_LIST_TAG +import org.opensearch.commons.notifications.NotificationConstants.CHANNEL_LIST_TAG /** - * FeatureChannel search results + * Channel search results */ -class FeatureChannelList : SearchResults { +class ChannelList : SearchResults { /** * single item result constructor */ - constructor(objectItem: FeatureChannel) : super(FEATURE_CONFIG_LIST_TAG, objectItem) + constructor(objectItem: Channel) : super(CHANNEL_LIST_TAG, objectItem) /** * multiple items result constructor */ - constructor(objectList: List) : this( + constructor(objectList: List) : this( 0, objectList.size.toLong(), TotalHits.Relation.EQUAL_TO, @@ -38,34 +38,34 @@ class FeatureChannelList : SearchResults { startIndex: Long, totalHits: Long, totalHitRelation: TotalHits.Relation, - objectList: List - ) : super(startIndex, totalHits, totalHitRelation, FEATURE_CONFIG_LIST_TAG, objectList) + objectList: List + ) : super(startIndex, totalHits, totalHitRelation, CHANNEL_LIST_TAG, objectList) /** * Constructor used in transport action communication. * @param input StreamInput stream to deserialize data from. */ - constructor(input: StreamInput) : super(input, FeatureChannel.reader) + constructor(input: StreamInput) : super(input, Channel.reader) /** * Construct object from XContentParser */ - constructor(parser: XContentParser) : super(parser, FEATURE_CONFIG_LIST_TAG) + constructor(parser: XContentParser) : super(parser, CHANNEL_LIST_TAG) /** * Construct object from SearchResponse */ - constructor(from: Long, response: SearchResponse, searchHitParser: SearchHitParser) : super( + constructor(from: Long, response: SearchResponse, searchHitParser: SearchHitParser) : super( from, response, searchHitParser, - FEATURE_CONFIG_LIST_TAG + CHANNEL_LIST_TAG ) /** * {@inheritDoc} */ - override fun parseItem(parser: XContentParser): FeatureChannel { - return FeatureChannel.parse(parser) + override fun parseItem(parser: XContentParser): Channel { + return Channel.parse(parser) } } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt index f6d3f9c1..c71836eb 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt @@ -25,8 +25,8 @@ import org.opensearch.commons.notifications.action.CreateNotificationConfigReque import org.opensearch.commons.notifications.action.CreateNotificationConfigResponse import org.opensearch.commons.notifications.action.DeleteNotificationConfigRequest import org.opensearch.commons.notifications.action.DeleteNotificationConfigResponse -import org.opensearch.commons.notifications.action.GetFeatureChannelListRequest -import org.opensearch.commons.notifications.action.GetFeatureChannelListResponse +import org.opensearch.commons.notifications.action.GetChannelListRequest +import org.opensearch.commons.notifications.action.GetChannelListResponse import org.opensearch.commons.notifications.action.GetNotificationConfigRequest import org.opensearch.commons.notifications.action.GetNotificationConfigResponse import org.opensearch.commons.notifications.action.GetNotificationEventRequest @@ -38,13 +38,13 @@ import org.opensearch.commons.notifications.action.LegacyPublishNotificationResp import org.opensearch.commons.notifications.action.SendNotificationResponse import org.opensearch.commons.notifications.action.UpdateNotificationConfigRequest import org.opensearch.commons.notifications.action.UpdateNotificationConfigResponse +import org.opensearch.commons.notifications.model.Channel +import org.opensearch.commons.notifications.model.ChannelList import org.opensearch.commons.notifications.model.ChannelMessage import org.opensearch.commons.notifications.model.ConfigType import org.opensearch.commons.notifications.model.DeliveryStatus import org.opensearch.commons.notifications.model.EventSource import org.opensearch.commons.notifications.model.EventStatus -import org.opensearch.commons.notifications.model.FeatureChannel -import org.opensearch.commons.notifications.model.FeatureChannelList import org.opensearch.commons.notifications.model.NotificationConfig import org.opensearch.commons.notifications.model.NotificationConfigInfo import org.opensearch.commons.notifications.model.NotificationConfigSearchResult @@ -168,25 +168,25 @@ internal class NotificationsPluginInterfaceTests { } @Test - fun getFeatureChannelList() { - val sampleConfig = FeatureChannel( + fun getChannelList() { + val sampleConfig = Channel( "config_id", "name", "description", ConfigType.SLACK ) - val request = mock(GetFeatureChannelListRequest::class.java) - val response = GetFeatureChannelListResponse(FeatureChannelList(sampleConfig)) - val listener: ActionListener = - mock(ActionListener::class.java) as ActionListener + val request = mock(GetChannelListRequest::class.java) + val response = GetChannelListResponse(ChannelList(sampleConfig)) + val listener: ActionListener = + mock(ActionListener::class.java) as ActionListener doAnswer { - (it.getArgument(2) as ActionListener) + (it.getArgument(2) as ActionListener) .onResponse(response) }.whenever(client).execute(any(ActionType::class.java), any(), any()) - NotificationsPluginInterface.getFeatureChannelList(client, request, listener) + NotificationsPluginInterface.getChannelList(client, request, listener) verify(listener, times(1)).onResponse(eq(response)) } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetChannelListRequestTests.kt similarity index 76% rename from src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt rename to src/test/kotlin/org/opensearch/commons/notifications/action/GetChannelListRequestTests.kt index b9913ae2..aaa29e21 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetChannelListRequestTests.kt @@ -12,27 +12,27 @@ import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject -internal class GetFeatureChannelListRequestTests { +internal class GetChannelListRequestTests { private fun assertGetRequestEquals( - expected: GetFeatureChannelListRequest, - actual: GetFeatureChannelListRequest + expected: GetChannelListRequest, + actual: GetChannelListRequest ) { assertEquals(expected.compact, actual.compact) } @Test fun `Get request serialize and deserialize transport object should be equal`() { - val configRequest = GetFeatureChannelListRequest() - val recreatedObject = recreateObject(configRequest) { GetFeatureChannelListRequest(it) } + val configRequest = GetChannelListRequest() + val recreatedObject = recreateObject(configRequest) { GetChannelListRequest(it) } assertGetRequestEquals(configRequest, recreatedObject) } @Test fun `Get request serialize and deserialize using json object should be equal`() { - val configRequest = GetFeatureChannelListRequest() + val configRequest = GetChannelListRequest() val jsonString = getJsonString(configRequest) - val recreatedObject = createObjectFromJsonString(jsonString) { GetFeatureChannelListRequest.parse(it) } + val recreatedObject = createObjectFromJsonString(jsonString) { GetChannelListRequest.parse(it) } assertGetRequestEquals(configRequest, recreatedObject) } @@ -40,13 +40,13 @@ internal class GetFeatureChannelListRequestTests { fun `Get request should throw exception when invalid json object is passed`() { val jsonString = "sample message" assertThrows { - createObjectFromJsonString(jsonString) { GetFeatureChannelListRequest.parse(it) } + createObjectFromJsonString(jsonString) { GetChannelListRequest.parse(it) } } } @Test fun `Get request should safely ignore extra field in json object`() { - val configRequest = GetFeatureChannelListRequest() + val configRequest = GetChannelListRequest() val jsonString = """ { "extra_field_1":["extra", "value"], @@ -54,7 +54,7 @@ internal class GetFeatureChannelListRequestTests { "extra_field_3":"extra value 3" } """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { GetFeatureChannelListRequest.parse(it) } + val recreatedObject = createObjectFromJsonString(jsonString) { GetChannelListRequest.parse(it) } assertGetRequestEquals(configRequest, recreatedObject) } } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetChannelListResponseTests.kt similarity index 73% rename from src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponseTests.kt rename to src/test/kotlin/org/opensearch/commons/notifications/action/GetChannelListResponseTests.kt index 805f651e..eb4023dd 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetChannelListResponseTests.kt @@ -8,18 +8,18 @@ import org.apache.lucene.search.TotalHits import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.opensearch.commons.notifications.model.Channel +import org.opensearch.commons.notifications.model.ChannelList import org.opensearch.commons.notifications.model.ConfigType -import org.opensearch.commons.notifications.model.FeatureChannel -import org.opensearch.commons.notifications.model.FeatureChannelList import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject -internal class GetFeatureChannelListResponseTests { +internal class GetChannelListResponseTests { private fun assertSearchResultEquals( - expected: FeatureChannelList, - actual: FeatureChannelList + expected: ChannelList, + actual: ChannelList ) { assertEquals(expected.startIndex, actual.startIndex) assertEquals(expected.totalHits, actual.totalHits) @@ -30,112 +30,112 @@ internal class GetFeatureChannelListResponseTests { @Test fun `Get Response serialize and deserialize with config object should be equal`() { - val sampleConfig = FeatureChannel( + val sampleConfig = Channel( "config_id", "name", "description", ConfigType.SLACK ) - val searchResult = FeatureChannelList(sampleConfig) - val getResponse = GetFeatureChannelListResponse(searchResult) - val recreatedObject = recreateObject(getResponse) { GetFeatureChannelListResponse(it) } + val searchResult = ChannelList(sampleConfig) + val getResponse = GetChannelListResponse(searchResult) + val recreatedObject = recreateObject(getResponse) { GetChannelListResponse(it) } assertSearchResultEquals(searchResult, recreatedObject.searchResult) } @Test fun `Get Response serialize and deserialize with multiple config object should be equal`() { - val sampleConfig1 = FeatureChannel( + val sampleConfig1 = Channel( "config_id1", "name1", "description1", ConfigType.SLACK ) - val sampleConfig2 = FeatureChannel( + val sampleConfig2 = Channel( "config_id2", "name2", "description2", ConfigType.CHIME ) - val sampleConfig3 = FeatureChannel( + val sampleConfig3 = Channel( "config_id3", "name3", "description3", ConfigType.WEBHOOK ) - val searchResult = FeatureChannelList( + val searchResult = ChannelList( 100, 1000, TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, listOf(sampleConfig1, sampleConfig2, sampleConfig3) ) - val getResponse = GetFeatureChannelListResponse(searchResult) - val recreatedObject = recreateObject(getResponse) { GetFeatureChannelListResponse(it) } + val getResponse = GetChannelListResponse(searchResult) + val recreatedObject = recreateObject(getResponse) { GetChannelListResponse(it) } assertSearchResultEquals(searchResult, recreatedObject.searchResult) } @Test fun `Get Response serialize and deserialize using json config object should be equal`() { - val sampleConfig = FeatureChannel( + val sampleConfig = Channel( "config_id", "name", "description", ConfigType.EMAIL_GROUP ) - val searchResult = FeatureChannelList(sampleConfig) - val getResponse = GetFeatureChannelListResponse(searchResult) + val searchResult = ChannelList(sampleConfig) + val getResponse = GetChannelListResponse(searchResult) val jsonString = getJsonString(getResponse) - val recreatedObject = createObjectFromJsonString(jsonString) { GetFeatureChannelListResponse.parse(it) } + val recreatedObject = createObjectFromJsonString(jsonString) { GetChannelListResponse.parse(it) } assertSearchResultEquals(searchResult, recreatedObject.searchResult) } @Test fun `Get Response serialize and deserialize using json with multiple config object should be equal`() { - val sampleConfig1 = FeatureChannel( + val sampleConfig1 = Channel( "config_id1", "name1", "description1", ConfigType.SLACK ) - val sampleConfig2 = FeatureChannel( + val sampleConfig2 = Channel( "config_id2", "name2", "description2", ConfigType.CHIME ) - val sampleConfig3 = FeatureChannel( + val sampleConfig3 = Channel( "config_id3", "name3", "description3", ConfigType.WEBHOOK ) - val searchResult = FeatureChannelList( + val searchResult = ChannelList( 100, 1000, TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, listOf(sampleConfig1, sampleConfig2, sampleConfig3) ) - val getResponse = GetFeatureChannelListResponse(searchResult) + val getResponse = GetChannelListResponse(searchResult) val jsonString = getJsonString(getResponse) - val recreatedObject = createObjectFromJsonString(jsonString) { GetFeatureChannelListResponse.parse(it) } + val recreatedObject = createObjectFromJsonString(jsonString) { GetChannelListResponse.parse(it) } assertSearchResultEquals(searchResult, recreatedObject.searchResult) } @Test fun `Get Response should use isEnabled=true if absent in json object`() { - val sampleConfig = FeatureChannel( + val sampleConfig = Channel( "config_id", "name", "description", ConfigType.EMAIL, true ) - val searchResult = FeatureChannelList(sampleConfig) + val searchResult = ChannelList(sampleConfig) val jsonString = """ { "start_index":"0", "total_hits":"1", "total_hit_relation":"eq", - "feature_channel_list":[ + "channel_list":[ { "config_id":"config_id", "name":"name", @@ -145,25 +145,25 @@ internal class GetFeatureChannelListResponseTests { ] } """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { GetFeatureChannelListResponse.parse(it) } + val recreatedObject = createObjectFromJsonString(jsonString) { GetChannelListResponse.parse(it) } assertSearchResultEquals(searchResult, recreatedObject.searchResult) } @Test fun `Get Response should safely ignore extra field in json object`() { - val sampleConfig = FeatureChannel( + val sampleConfig = Channel( "config_id", "name", "description", ConfigType.EMAIL ) - val searchResult = FeatureChannelList(sampleConfig) + val searchResult = ChannelList(sampleConfig) val jsonString = """ { "start_index":"0", "total_hits":"1", "total_hit_relation":"eq", - "feature_channel_list":[ + "channel_list":[ { "config_id":"config_id", "name":"name", @@ -177,22 +177,22 @@ internal class GetFeatureChannelListResponseTests { "extra_field_3":"extra value 3" } """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { GetFeatureChannelListResponse.parse(it) } + val recreatedObject = createObjectFromJsonString(jsonString) { GetChannelListResponse.parse(it) } assertSearchResultEquals(searchResult, recreatedObject.searchResult) } @Test fun `Get Response should safely fallback to default if startIndex, totalHits or totalHitRelation field absent in json object`() { - val sampleConfig = FeatureChannel( + val sampleConfig = Channel( "config_id", "name", "description", ConfigType.EMAIL ) - val searchResult = FeatureChannelList(sampleConfig) + val searchResult = ChannelList(sampleConfig) val jsonString = """ { - "feature_channel_list":[ + "channel_list":[ { "config_id":"config_id", "name":"name", @@ -203,12 +203,12 @@ internal class GetFeatureChannelListResponseTests { ] } """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { GetFeatureChannelListResponse.parse(it) } + val recreatedObject = createObjectFromJsonString(jsonString) { GetChannelListResponse.parse(it) } assertSearchResultEquals(searchResult, recreatedObject.searchResult) } @Test - fun `Get Response should throw exception if featureChannelList is absent in json`() { + fun `Get Response should throw exception if channelList is absent in json`() { val jsonString = """ { "start_index":"0", @@ -217,7 +217,7 @@ internal class GetFeatureChannelListResponseTests { } """.trimIndent() Assertions.assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { GetFeatureChannelListResponse.parse(it) } + createObjectFromJsonString(jsonString) { GetChannelListResponse.parse(it) } } } } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelListTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/ChannelListTests.kt similarity index 66% rename from src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelListTests.kt rename to src/test/kotlin/org/opensearch/commons/notifications/model/ChannelListTests.kt index 8a40aadd..06387d37 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelListTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/ChannelListTests.kt @@ -12,11 +12,11 @@ import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject -internal class FeatureChannelListTests { +internal class ChannelListTests { private fun assertSearchResultEquals( - expected: FeatureChannelList, - actual: FeatureChannelList + expected: ChannelList, + actual: ChannelList ) { assertEquals(expected.startIndex, actual.startIndex) assertEquals(expected.totalHits, actual.totalHits) @@ -27,129 +27,129 @@ internal class FeatureChannelListTests { @Test fun `Feature Channel List serialize and deserialize using transport should be equal`() { - val featureChannel = FeatureChannel( + val channel = Channel( "configId", "name", "description", ConfigType.SLACK, true ) - val featureChannelList = FeatureChannelList(featureChannel) - val recreatedObject = recreateObject(featureChannelList) { FeatureChannelList(it) } - assertSearchResultEquals(featureChannelList, recreatedObject) + val channelList = ChannelList(channel) + val recreatedObject = recreateObject(channelList) { ChannelList(it) } + assertSearchResultEquals(channelList, recreatedObject) } @Test fun `Feature Channel List serialize and deserialize multiple object with default values should be equal`() { - val featureChannel1 = FeatureChannel( + val channel1 = Channel( "configId1", "name1", "description1", ConfigType.SLACK, true ) - val featureChannel2 = FeatureChannel( + val channel2 = Channel( "configId2", "name2", "description2", ConfigType.CHIME, true ) - val featureChannelList = FeatureChannelList(listOf(featureChannel1, featureChannel2)) - val expectedResult = FeatureChannelList( + val channelList = ChannelList(listOf(channel1, channel2)) + val expectedResult = ChannelList( 0, 2, TotalHits.Relation.EQUAL_TO, - listOf(featureChannel1, featureChannel2) + listOf(channel1, channel2) ) - val recreatedObject = recreateObject(featureChannelList) { FeatureChannelList(it) } + val recreatedObject = recreateObject(channelList) { ChannelList(it) } assertSearchResultEquals(expectedResult, recreatedObject) } @Test fun `Feature Channel List serialize and deserialize with multiple object should be equal`() { - val featureChannel1 = FeatureChannel( + val channel1 = Channel( "configId1", "name1", "description1", ConfigType.SLACK, true ) - val featureChannel2 = FeatureChannel( + val channel2 = Channel( "configId2", "name2", "description2", ConfigType.CHIME, true ) - val featureChannelList = FeatureChannelList( + val channelList = ChannelList( 100, 1000, TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, - listOf(featureChannel1, featureChannel2) + listOf(channel1, channel2) ) - val recreatedObject = recreateObject(featureChannelList) { FeatureChannelList(it) } - assertSearchResultEquals(featureChannelList, recreatedObject) + val recreatedObject = recreateObject(channelList) { ChannelList(it) } + assertSearchResultEquals(channelList, recreatedObject) } @Test fun `Feature Channel List serialize and deserialize using json should be equal`() { - val featureChannel = FeatureChannel( + val channel = Channel( "configId", "name", "description", ConfigType.SLACK, true ) - val featureChannelList = FeatureChannelList(featureChannel) - val jsonString = getJsonString(featureChannelList) - val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannelList(it) } - assertSearchResultEquals(featureChannelList, recreatedObject) + val channelList = ChannelList(channel) + val jsonString = getJsonString(channelList) + val recreatedObject = createObjectFromJsonString(jsonString) { ChannelList(it) } + assertSearchResultEquals(channelList, recreatedObject) } @Test fun `Feature Channel List serialize and deserialize using json with multiple object should be equal`() { - val featureChannel1 = FeatureChannel( + val channel1 = Channel( "configId1", "name1", "description1", ConfigType.SLACK, true ) - val featureChannel2 = FeatureChannel( + val channel2 = Channel( "configId2", "name2", "description2", ConfigType.CHIME, true ) - val featureChannelList = FeatureChannelList( + val channelList = ChannelList( 100, 1000, TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, - listOf(featureChannel1, featureChannel2) + listOf(channel1, channel2) ) - val jsonString = getJsonString(featureChannelList) - val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannelList(it) } - assertSearchResultEquals(featureChannelList, recreatedObject) + val jsonString = getJsonString(channelList) + val recreatedObject = createObjectFromJsonString(jsonString) { ChannelList(it) } + assertSearchResultEquals(channelList, recreatedObject) } @Test fun `Feature Channel List should safely ignore extra field in json object`() { - val featureChannel = FeatureChannel( + val channel = Channel( "configId", "name", "description", ConfigType.SLACK, true ) - val featureChannelList = FeatureChannelList(featureChannel) + val channelList = ChannelList(channel) val jsonString = """ { "start_index":"0", "total_hits":"1", "total_hit_relation":"eq", - "feature_channel_list":[ + "channel_list":[ { "config_id":"configId", "name":"name", @@ -163,23 +163,23 @@ internal class FeatureChannelListTests { "extra_field_3":"extra value 3" } """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannelList(it) } - assertSearchResultEquals(featureChannelList, recreatedObject) + val recreatedObject = createObjectFromJsonString(jsonString) { ChannelList(it) } + assertSearchResultEquals(channelList, recreatedObject) } @Test fun `Feature Channel List should safely fallback to default if startIndex, totalHits or totalHitRelation field absent in json object`() { - val featureChannel = FeatureChannel( + val channel = Channel( "configId", "name", "description", ConfigType.SLACK, true ) - val featureChannelList = FeatureChannelList(featureChannel) + val channelList = ChannelList(channel) val jsonString = """ { - "feature_channel_list":[ + "channel_list":[ { "config_id":"configId", "name":"name", @@ -190,12 +190,12 @@ internal class FeatureChannelListTests { ] } """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannelList(it) } - assertSearchResultEquals(featureChannelList, recreatedObject) + val recreatedObject = createObjectFromJsonString(jsonString) { ChannelList(it) } + assertSearchResultEquals(channelList, recreatedObject) } @Test - fun `Feature Channel List should throw exception if feature_channel_list is absent in json`() { + fun `Channel List should throw exception if channel_list is absent in json`() { val jsonString = """ { "start_index":"0", @@ -204,7 +204,7 @@ internal class FeatureChannelListTests { } """.trimIndent() Assertions.assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { FeatureChannelList(it) } + createObjectFromJsonString(jsonString) { ChannelList(it) } } } } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/ChannelTests.kt similarity index 65% rename from src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelTests.kt rename to src/test/kotlin/org/opensearch/commons/notifications/model/ChannelTests.kt index 3fa2e4ab..3f64cc5a 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/ChannelTests.kt @@ -11,38 +11,38 @@ import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject -internal class FeatureChannelTests { +internal class ChannelTests { @Test - fun `FeatureChannel Object serialize and deserialize using transport should be equal`() { - val featureChannel = FeatureChannel( + fun `Channel Object serialize and deserialize using transport should be equal`() { + val channel = Channel( "configId", "name", "description", ConfigType.SLACK, true ) - val recreatedObject = recreateObject(featureChannel) { FeatureChannel(it) } - assertEquals(featureChannel, recreatedObject) + val recreatedObject = recreateObject(channel) { Channel(it) } + assertEquals(channel, recreatedObject) } @Test - fun `FeatureChannel Object serialize and deserialize using json should be equal`() { - val featureChannel = FeatureChannel( + fun `Channel Object serialize and deserialize using json should be equal`() { + val channel = Channel( "configId", "name", "description", ConfigType.CHIME, false ) - val jsonString = getJsonString(featureChannel) - val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } - assertEquals(featureChannel, recreatedObject) + val jsonString = getJsonString(channel) + val recreatedObject = createObjectFromJsonString(jsonString) { Channel.parse(it) } + assertEquals(channel, recreatedObject) } @Test - fun `FeatureChannel Json parsing should safely ignore extra fields`() { - val featureChannel = FeatureChannel( + fun `Channel Json parsing should safely ignore extra fields`() { + val channel = Channel( "configId", "name", "description", @@ -61,13 +61,13 @@ internal class FeatureChannelTests { "extra_field_3":"extra value 3" } """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } - assertEquals(featureChannel, recreatedObject) + val recreatedObject = createObjectFromJsonString(jsonString) { Channel.parse(it) } + assertEquals(channel, recreatedObject) } @Test - fun `FeatureChannel Json parsing should safely ignore unknown config type`() { - val featureChannel = FeatureChannel( + fun `Channel Json parsing should safely ignore unknown config type`() { + val channel = Channel( "configId", "name", "description", @@ -83,13 +83,13 @@ internal class FeatureChannelTests { "is_enabled":true } """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } - assertEquals(featureChannel, recreatedObject) + val recreatedObject = createObjectFromJsonString(jsonString) { Channel.parse(it) } + assertEquals(channel, recreatedObject) } @Test - fun `FeatureChannel Json parsing should safely parse if description is absent`() { - val featureChannel = FeatureChannel( + fun `Channel Json parsing should safely parse if description is absent`() { + val channel = Channel( "configId", "name", "", @@ -104,13 +104,13 @@ internal class FeatureChannelTests { "is_enabled":true } """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } - assertEquals(featureChannel, recreatedObject) + val recreatedObject = createObjectFromJsonString(jsonString) { Channel.parse(it) } + assertEquals(channel, recreatedObject) } @Test - fun `FeatureChannel Json parsing should safely parse if is_enabled is absent`() { - val featureChannel = FeatureChannel( + fun `Channel Json parsing should safely parse if is_enabled is absent`() { + val channel = Channel( "configId", "name", "description", @@ -125,12 +125,12 @@ internal class FeatureChannelTests { "config_type":"slack" } """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } - assertEquals(featureChannel, recreatedObject) + val recreatedObject = createObjectFromJsonString(jsonString) { Channel.parse(it) } + assertEquals(channel, recreatedObject) } @Test - fun `FeatureChannel Json parsing should throw exception if config_id is absent`() { + fun `Channel Json parsing should throw exception if config_id is absent`() { val jsonString = """ { "name":"name", @@ -140,12 +140,12 @@ internal class FeatureChannelTests { } """.trimIndent() Assertions.assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + createObjectFromJsonString(jsonString) { Channel.parse(it) } } } @Test - fun `FeatureChannel Json parsing should throw exception if config_id is empty`() { + fun `Channel Json parsing should throw exception if config_id is empty`() { val jsonString = """ { "config_id":"", @@ -156,12 +156,12 @@ internal class FeatureChannelTests { } """.trimIndent() Assertions.assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + createObjectFromJsonString(jsonString) { Channel.parse(it) } } } @Test - fun `FeatureChannel Json parsing should throw exception if name is absent`() { + fun `Channel Json parsing should throw exception if name is absent`() { val jsonString = """ { "config_id":"configId", @@ -171,12 +171,12 @@ internal class FeatureChannelTests { } """.trimIndent() Assertions.assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + createObjectFromJsonString(jsonString) { Channel.parse(it) } } } @Test - fun `FeatureChannel Json parsing should throw exception if name is empty`() { + fun `Channel Json parsing should throw exception if name is empty`() { val jsonString = """ { "config_id":"configId", @@ -187,12 +187,12 @@ internal class FeatureChannelTests { } """.trimIndent() Assertions.assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + createObjectFromJsonString(jsonString) { Channel.parse(it) } } } @Test - fun `FeatureChannel Json parsing should throw exception if config_type is absent`() { + fun `Channel Json parsing should throw exception if config_type is absent`() { val jsonString = """ { "config_id":"configId", @@ -202,7 +202,7 @@ internal class FeatureChannelTests { } """.trimIndent() Assertions.assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + createObjectFromJsonString(jsonString) { Channel.parse(it) } } } } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigListTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigListTests.kt index 9ac569f6..de648bcd 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigListTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigListTests.kt @@ -15,8 +15,8 @@ import org.opensearch.commons.utils.recreateObject internal class FilterConfigListTests { private fun assertSearchResultEquals( - expected: FeatureChannelList, - actual: FeatureChannelList + expected: ChannelList, + actual: ChannelList ) { assertEquals(expected.startIndex, actual.startIndex) assertEquals(expected.totalHits, actual.totalHits) @@ -27,108 +27,108 @@ internal class FilterConfigListTests { @Test fun `Search result serialize and deserialize with config object should be equal`() { - val sampleConfig = FeatureChannel( + val sampleConfig = Channel( "config_id", "name", "description", ConfigType.SLACK ) - val searchResult = FeatureChannelList(sampleConfig) - val recreatedObject = recreateObject(searchResult) { FeatureChannelList(it) } + val searchResult = ChannelList(sampleConfig) + val recreatedObject = recreateObject(searchResult) { ChannelList(it) } assertSearchResultEquals(searchResult, recreatedObject) } @Test fun `Search result serialize and deserialize with multiple config object should be equal`() { - val sampleConfig1 = FeatureChannel( + val sampleConfig1 = Channel( "config_id1", "name1", "description1", ConfigType.SLACK ) - val sampleConfig2 = FeatureChannel( + val sampleConfig2 = Channel( "config_id2", "name2", "description2", ConfigType.CHIME ) - val sampleConfig3 = FeatureChannel( + val sampleConfig3 = Channel( "config_id3", "name3", "description3", ConfigType.WEBHOOK ) - val searchResult = FeatureChannelList( + val searchResult = ChannelList( 100, 1000, TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, listOf(sampleConfig1, sampleConfig2, sampleConfig3) ) - val recreatedObject = recreateObject(searchResult) { FeatureChannelList(it) } + val recreatedObject = recreateObject(searchResult) { ChannelList(it) } assertSearchResultEquals(searchResult, recreatedObject) } @Test fun `Search result serialize and deserialize using json config object should be equal`() { - val sampleConfig = FeatureChannel( + val sampleConfig = Channel( "config_id", "name", "description", ConfigType.EMAIL_GROUP ) - val searchResult = FeatureChannelList(sampleConfig) + val searchResult = ChannelList(sampleConfig) val jsonString = getJsonString(searchResult) - val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannelList(it) } + val recreatedObject = createObjectFromJsonString(jsonString) { ChannelList(it) } assertSearchResultEquals(searchResult, recreatedObject) } @Test fun `Search result serialize and deserialize using json with multiple config object should be equal`() { - val sampleConfig1 = FeatureChannel( + val sampleConfig1 = Channel( "config_id1", "name1", "description1", ConfigType.SLACK ) - val sampleConfig2 = FeatureChannel( + val sampleConfig2 = Channel( "config_id2", "name2", "description2", ConfigType.CHIME ) - val sampleConfig3 = FeatureChannel( + val sampleConfig3 = Channel( "config_id3", "name3", "description3", ConfigType.WEBHOOK ) - val searchResult = FeatureChannelList( + val searchResult = ChannelList( 100, 1000, TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, listOf(sampleConfig1, sampleConfig2, sampleConfig3) ) val jsonString = getJsonString(searchResult) - val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannelList(it) } + val recreatedObject = createObjectFromJsonString(jsonString) { ChannelList(it) } assertSearchResultEquals(searchResult, recreatedObject) } @Test fun `Search result should use isEnabled=true if absent in json object`() { - val sampleConfig = FeatureChannel( + val sampleConfig = Channel( "config_id", "name", "description", ConfigType.EMAIL, true ) - val searchResult = FeatureChannelList(sampleConfig) + val searchResult = ChannelList(sampleConfig) val jsonString = """ { "start_index":"0", "total_hits":"1", "total_hit_relation":"eq", - "feature_channel_list":[ + "channel_list":[ { "config_id":"config_id", "name":"name", @@ -138,25 +138,25 @@ internal class FilterConfigListTests { ] } """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannelList(it) } + val recreatedObject = createObjectFromJsonString(jsonString) { ChannelList(it) } assertSearchResultEquals(searchResult, recreatedObject) } @Test fun `Search result should safely ignore extra field in json object`() { - val sampleConfig = FeatureChannel( + val sampleConfig = Channel( "config_id", "name", "description", ConfigType.EMAIL ) - val searchResult = FeatureChannelList(sampleConfig) + val searchResult = ChannelList(sampleConfig) val jsonString = """ { "start_index":"0", "total_hits":"1", "total_hit_relation":"eq", - "feature_channel_list":[ + "channel_list":[ { "config_id":"config_id", "name":"name", @@ -170,22 +170,22 @@ internal class FilterConfigListTests { "extra_field_3":"extra value 3" } """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannelList(it) } + val recreatedObject = createObjectFromJsonString(jsonString) { ChannelList(it) } assertSearchResultEquals(searchResult, recreatedObject) } @Test fun `Search result should safely fallback to default if startIndex, totalHits or totalHitRelation field absent in json object`() { - val sampleConfig = FeatureChannel( + val sampleConfig = Channel( "config_id", "name", "description", ConfigType.EMAIL ) - val searchResult = FeatureChannelList(sampleConfig) + val searchResult = ChannelList(sampleConfig) val jsonString = """ { - "feature_channel_list":[ + "channel_list":[ { "config_id":"config_id", "name":"name", @@ -196,12 +196,12 @@ internal class FilterConfigListTests { ] } """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannelList(it) } + val recreatedObject = createObjectFromJsonString(jsonString) { ChannelList(it) } assertSearchResultEquals(searchResult, recreatedObject) } @Test - fun `Search result should throw exception if featureChannelList is absent in json`() { + fun `Search result should throw exception if channelList is absent in json`() { val jsonString = """ { "start_index":"0", @@ -210,7 +210,7 @@ internal class FilterConfigListTests { } """.trimIndent() Assertions.assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { FeatureChannelList(it) } + createObjectFromJsonString(jsonString) { ChannelList(it) } } } } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigTests.kt index 234548a8..c3557e89 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigTests.kt @@ -15,45 +15,45 @@ internal class FilterConfigTests { @Test fun `Config serialize and deserialize with default isEnabled flag should be equal`() { - val sampleConfig = FeatureChannel( + val sampleConfig = Channel( "config_id", "name", "description", ConfigType.SLACK ) - val recreatedObject = recreateObject(sampleConfig) { FeatureChannel(it) } + val recreatedObject = recreateObject(sampleConfig) { Channel(it) } assertEquals(sampleConfig, recreatedObject) } @Test fun `Config serialize and deserialize with isEnabled=false should be equal`() { - val sampleConfig = FeatureChannel( + val sampleConfig = Channel( "config_id", "name", "description", ConfigType.CHIME, false ) - val recreatedObject = recreateObject(sampleConfig) { FeatureChannel(it) } + val recreatedObject = recreateObject(sampleConfig) { Channel(it) } assertEquals(sampleConfig, recreatedObject) } @Test fun `Config serialize and deserialize using json object with default isEnabled flag should be equal`() { - val sampleConfig = FeatureChannel( + val sampleConfig = Channel( "config_id", "name", "description", ConfigType.WEBHOOK ) val jsonString = getJsonString(sampleConfig) - val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + val recreatedObject = createObjectFromJsonString(jsonString) { Channel.parse(it) } assertEquals(sampleConfig, recreatedObject) } @Test fun `Config serialize and deserialize using json object with isEnabled=false should be equal`() { - val sampleConfig = FeatureChannel( + val sampleConfig = Channel( "config_id", "name", "description", @@ -61,13 +61,13 @@ internal class FilterConfigTests { false ) val jsonString = getJsonString(sampleConfig) - val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + val recreatedObject = createObjectFromJsonString(jsonString) { Channel.parse(it) } assertEquals(sampleConfig, recreatedObject) } @Test fun `Config should safely ignore extra field in json object`() { - val sampleConfig = FeatureChannel( + val sampleConfig = Channel( "config_id", "name", "description", @@ -85,13 +85,13 @@ internal class FilterConfigTests { "extra_field_3":"extra value 3" } """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + val recreatedObject = createObjectFromJsonString(jsonString) { Channel.parse(it) } assertEquals(sampleConfig, recreatedObject) } @Test fun `Config should safely ignore unknown config type in json object`() { - val sampleConfig = FeatureChannel( + val sampleConfig = Channel( "config_id", "name", "description", @@ -105,14 +105,14 @@ internal class FilterConfigTests { "config_type":"NewConfig" } """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + val recreatedObject = createObjectFromJsonString(jsonString) { Channel.parse(it) } assertEquals(sampleConfig, recreatedObject) } @Test fun `Config throw exception if configId is empty`() { Assertions.assertThrows(IllegalArgumentException::class.java) { - FeatureChannel( + Channel( "", "name", "description", @@ -124,7 +124,7 @@ internal class FilterConfigTests { @Test fun `Config throw exception if name is empty`() { Assertions.assertThrows(IllegalArgumentException::class.java) { - FeatureChannel( + Channel( "config_id", "", "description", From 6e78f6995bfa3303a9aceb45af28530fcf0de34a Mon Sep 17 00:00:00 2001 From: Vacha Shah Date: Wed, 23 Mar 2022 12:54:51 -0700 Subject: [PATCH 044/149] Adding signoff option for version workflow PR (#143) Signed-off-by: Vacha Shah --- .github/workflows/version.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index 8ca31d8e..6c96199a 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -46,6 +46,7 @@ jobs: token: ${{ steps.github_app_token.outputs.token }} base: ${{ env.BASE }} commit-message: Incremented version to ${{ env.NEXT_VERSION }} + signoff: true delete-branch: true title: '[AUTO] Incremented version to ${{ env.NEXT_VERSION }}.' body: | From 43d6e6da45f15b9b8db21294362c24d0d1b8dfbd Mon Sep 17 00:00:00 2001 From: Mohammad Qureshi <47198598+qreshi@users.noreply.github.com> Date: Mon, 28 Mar 2022 10:28:01 -0700 Subject: [PATCH 045/149] Remove allowedConfigFeatureList from GetPluginFeaturesResponse (#144) Signed-off-by: Mohammad Qureshi <47198598+qreshi@users.noreply.github.com> --- .../notifications/NotificationConstants.kt | 1 - .../action/GetPluginFeaturesResponse.kt | 13 +--------- .../NotificationsPluginInterfaceTests.kt | 1 - .../action/GetPluginFeaturesResponseTests.kt | 26 +------------------ 4 files changed, 2 insertions(+), 39 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt index 146c48de..74fcc600 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt @@ -66,7 +66,6 @@ object NotificationConstants { const val QUERY_TAG = "query" const val COMPACT_TAG = "compact" const val ALLOWED_CONFIG_TYPE_LIST_TAG = "allowed_config_type_list" - const val ALLOWED_CONFIG_FEATURE_LIST_TAG = "allowed_config_feature_list" const val PLUGIN_FEATURES_TAG = "plugin_features" const val DEFAULT_MAX_ITEMS = 1000 diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt index 1e6fa2b9..73db57a0 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt @@ -11,7 +11,6 @@ import org.opensearch.common.xcontent.ToXContent import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils -import org.opensearch.commons.notifications.NotificationConstants.ALLOWED_CONFIG_FEATURE_LIST_TAG import org.opensearch.commons.notifications.NotificationConstants.ALLOWED_CONFIG_TYPE_LIST_TAG import org.opensearch.commons.notifications.NotificationConstants.PLUGIN_FEATURES_TAG import org.opensearch.commons.utils.STRING_READER @@ -25,7 +24,6 @@ import java.io.IOException */ class GetPluginFeaturesResponse : BaseResponse { val allowedConfigTypeList: List - val allowedConfigFeatureList: List val pluginFeatures: Map companion object { @@ -44,7 +42,6 @@ class GetPluginFeaturesResponse : BaseResponse { @Throws(IOException::class) fun parse(parser: XContentParser): GetPluginFeaturesResponse { var allowedConfigTypeList: List? = null - var allowedConfigFeatureList: List? = null var pluginFeatures: Map? = null XContentParserUtils.ensureExpectedToken( @@ -57,7 +54,6 @@ class GetPluginFeaturesResponse : BaseResponse { parser.nextToken() when (fieldName) { ALLOWED_CONFIG_TYPE_LIST_TAG -> allowedConfigTypeList = parser.stringList() - ALLOWED_CONFIG_FEATURE_LIST_TAG -> allowedConfigFeatureList = parser.stringList() PLUGIN_FEATURES_TAG -> pluginFeatures = parser.mapStrings() else -> { parser.skipChildren() @@ -66,9 +62,8 @@ class GetPluginFeaturesResponse : BaseResponse { } } allowedConfigTypeList ?: throw IllegalArgumentException("$ALLOWED_CONFIG_TYPE_LIST_TAG field absent") - allowedConfigFeatureList ?: throw IllegalArgumentException("$ALLOWED_CONFIG_TYPE_LIST_TAG field absent") pluginFeatures ?: throw IllegalArgumentException("$PLUGIN_FEATURES_TAG field absent") - return GetPluginFeaturesResponse(allowedConfigTypeList, allowedConfigFeatureList, pluginFeatures) + return GetPluginFeaturesResponse(allowedConfigTypeList, pluginFeatures) } } @@ -78,7 +73,6 @@ class GetPluginFeaturesResponse : BaseResponse { override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { return builder!!.startObject() .field(ALLOWED_CONFIG_TYPE_LIST_TAG, allowedConfigTypeList) - .field(ALLOWED_CONFIG_FEATURE_LIST_TAG, allowedConfigFeatureList) .field(PLUGIN_FEATURES_TAG, pluginFeatures) .endObject() } @@ -86,16 +80,13 @@ class GetPluginFeaturesResponse : BaseResponse { /** * constructor for creating the class * @param allowedConfigTypeList the list of config types supported by plugin - * @param allowedConfigFeatureList the list of config features supported by plugin * @param pluginFeatures the map of plugin features supported to its value */ constructor( allowedConfigTypeList: List, - allowedConfigFeatureList: List, pluginFeatures: Map ) { this.allowedConfigTypeList = allowedConfigTypeList - this.allowedConfigFeatureList = allowedConfigFeatureList this.pluginFeatures = pluginFeatures } @@ -105,7 +96,6 @@ class GetPluginFeaturesResponse : BaseResponse { @Throws(IOException::class) constructor(input: StreamInput) : super(input) { allowedConfigTypeList = input.readStringList() - allowedConfigFeatureList = input.readStringList() pluginFeatures = input.readMap(STRING_READER, STRING_READER) } @@ -115,7 +105,6 @@ class GetPluginFeaturesResponse : BaseResponse { @Throws(IOException::class) override fun writeTo(output: StreamOutput) { output.writeStringCollection(allowedConfigTypeList) - output.writeStringCollection(allowedConfigFeatureList) output.writeMap(pluginFeatures, STRING_WRITER, STRING_WRITER) } } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt index c71836eb..aaa831d1 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt @@ -148,7 +148,6 @@ internal class NotificationsPluginInterfaceTests { val request = mock(GetPluginFeaturesRequest::class.java) val response = GetPluginFeaturesResponse( listOf("config_type_1", "config_type_2", "config_type_3"), - listOf("config_feature_1", "config_feature_2", "config_feature_3"), mapOf( Pair("FeatureKey1", "FeatureValue1"), Pair("FeatureKey2", "FeatureValue2"), diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponseTests.kt index 72b9a49f..1aa065a2 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponseTests.kt @@ -18,7 +18,6 @@ internal class GetPluginFeaturesResponseTests { actual: GetPluginFeaturesResponse ) { assertEquals(expected.allowedConfigTypeList, actual.allowedConfigTypeList) - assertEquals(expected.allowedConfigFeatureList, actual.allowedConfigFeatureList) assertEquals(expected.pluginFeatures, actual.pluginFeatures) } @@ -26,7 +25,6 @@ internal class GetPluginFeaturesResponseTests { fun `Get Response serialize and deserialize transport object should be equal`() { val response = GetPluginFeaturesResponse( listOf("config_type_1", "config_type_2", "config_type_3"), - listOf("config_feature_1", "config_feature_2", "config_feature_3"), mapOf( Pair("FeatureKey1", "FeatureValue1"), Pair("FeatureKey2", "FeatureValue2"), @@ -41,7 +39,6 @@ internal class GetPluginFeaturesResponseTests { fun `Get Response serialize and deserialize using json config object should be equal`() { val response = GetPluginFeaturesResponse( listOf("config_type_1", "config_type_2", "config_type_3"), - listOf("config_feature_1", "config_feature_2", "config_feature_3"), mapOf( Pair("FeatureKey1", "FeatureValue1"), Pair("FeatureKey2", "FeatureValue2"), @@ -57,7 +54,6 @@ internal class GetPluginFeaturesResponseTests { fun `Get Response should safely ignore extra field in json object`() { val response = GetPluginFeaturesResponse( listOf("config_type_1", "config_type_2", "config_type_3"), - listOf("config_feature_1", "config_feature_2", "config_feature_3"), mapOf( Pair("FeatureKey1", "FeatureValue1"), Pair("FeatureKey2", "FeatureValue2"), @@ -67,7 +63,6 @@ internal class GetPluginFeaturesResponseTests { val jsonString = """ { "allowed_config_type_list":["config_type_1", "config_type_2", "config_type_3"], - "allowed_config_feature_list":["config_feature_1", "config_feature_2", "config_feature_3"], "plugin_features":{ "FeatureKey1":"FeatureValue1", "FeatureKey2":"FeatureValue2", @@ -86,24 +81,6 @@ internal class GetPluginFeaturesResponseTests { fun `Get Response should throw exception if allowed_config_type_list is absent in json`() { val jsonString = """ { - "allowed_config_feature_list":["config_feature_1", "config_feature_2", "config_feature_3"], - "plugin_features":{ - "FeatureKey1":"FeatureValue1", - "FeatureKey2":"FeatureValue2", - "FeatureKey3":"FeatureValue3" - } - } - """.trimIndent() - Assertions.assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { GetPluginFeaturesResponse.parse(it) } - } - } - - @Test - fun `Get Response should throw exception if allowed_config_feature_list is absent in json`() { - val jsonString = """ - { - "allowed_config_type_list":["config_type_1", "config_type_2", "config_type_3"], "plugin_features":{ "FeatureKey1":"FeatureValue1", "FeatureKey2":"FeatureValue2", @@ -120,8 +97,7 @@ internal class GetPluginFeaturesResponseTests { fun `Get Response should throw exception if plugin_features is absent in json`() { val jsonString = """ { - "config_type_list":["config_type_1", "config_type_2", "config_type_3"], - "allowed_config_feature_list":["config_feature_1", "config_feature_2", "config_feature_3"] + "config_type_list":["config_type_1", "config_type_2", "config_type_3"] } """.trimIndent() Assertions.assertThrows(IllegalArgumentException::class.java) { From f2f57e59351973916d6fcb5a8ec5e958060c6593 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 28 Mar 2022 16:56:39 -0400 Subject: [PATCH 046/149] change default opensearch version to 2.0.0-alpha1-SNAPSHOT (#146) Signed-off-by: Peter Zhu --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index bc142d75..f11f252e 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "2.0.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.0.0-alpha1-SNAPSHOT") kotlin_version = System.getProperty("kotlin.version", "1.6.10") } From a6c2da895cfd585fa48aeb1454f5584cbb331876 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 31 Mar 2022 16:44:08 -0400 Subject: [PATCH 047/149] Add qualifier default to alpha1 in build.gradle (#151) Signed-off-by: Peter Zhu --- build.gradle | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index f11f252e..cf3ca5b9 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,8 @@ buildscript { ext { opensearch_group = "org.opensearch" opensearch_version = System.getProperty("opensearch.version", "2.0.0-alpha1-SNAPSHOT") + isSnapshot = "true" == System.getProperty("build.snapshot", "true") + buildVersionQualifier = System.getProperty("build.version_qualifier", "alpha1") kotlin_version = System.getProperty("kotlin.version", "1.6.10") } @@ -38,11 +40,6 @@ repositories { maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } } -ext { - isSnapshot = "true" == System.getProperty("build.snapshot", "true") - buildVersionQualifier = System.getProperty("build.version_qualifier") -} - allprojects { group 'org.opensearch.commons' version = opensearch_version.tokenize('-')[0] + '.0' From 58cd7ccf3179a3003f60486d061ece08b81e7b08 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 31 Mar 2022 22:18:55 -0400 Subject: [PATCH 048/149] Make CU hosts its own script before delete the one on build repo (#152) Signed-off-by: Peter Zhu --- scripts/build.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/build.sh b/scripts/build.sh index 1f8b6d4d..e0495d4a 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -10,7 +10,7 @@ function usage() { echo "" echo "Arguments:" echo -e "-v VERSION\t[Required] OpenSearch version." - echo -e "-q QUALIFIER\t[Optional] Build qualifier." + echo -e "-q QUALIFIER\t[Optional] Version qualifier." echo -e "-s SNAPSHOT\t[Optional] Build a snapshot, default is 'false'." echo -e "-p PLATFORM\t[Optional] Platform, ignored." echo -e "-a ARCHITECTURE\t[Optional] Build architecture, ignored." @@ -60,11 +60,12 @@ if [ -z "$VERSION" ]; then exit 1 fi +[[ ! -z "$QUALIFIER" ]] && VERSION=$VERSION-$QUALIFIER [[ "$SNAPSHOT" == "true" ]] && VERSION=$VERSION-SNAPSHOT [ -z "$OUTPUT" ] && OUTPUT=artifacts -./gradlew build -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -Dbuild.version_qualifier=$QUALIFIER +./gradlew build -x test -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -Dbuild.version_qualifier=$QUALIFIER ./gradlew publishShadowPublicationToMavenLocal -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -Dbuild.version_qualifier=$QUALIFIER ./gradlew publishShadowPublicationToStagingRepository -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -Dbuild.version_qualifier=$QUALIFIER mkdir -p $OUTPUT/maven/org/opensearch -cp -r ./build/local-staging-repo/org/opensearch/common-utils $OUTPUT/maven/org/opensearch/common-utils +cp -r ./build/local-staging-repo/org/opensearch/. $OUTPUT/maven/org/opensearch From f62e734c323bd5148600c040f919634eb00e485e Mon Sep 17 00:00:00 2001 From: Aditya Jindal <13850971+aditjind@users.noreply.github.com> Date: Tue, 5 Apr 2022 13:43:56 -0700 Subject: [PATCH 049/149] Removal of NotificationEvent Request, Response and SearchResults (#153) * Removal of NotificationEvent Request, Response , Search Results Addition of NotificationEventDoc and Removal of NotificationEventInfo Addition of NotificationEventDocTests Signed-off-by: Jindal * Removing NotificationEventDoc and NotificationEventDocTests Signed-off-by: Jindal Co-authored-by: Jindal --- .../NotificationsPluginInterface.kt | 21 - .../action/GetNotificationEventRequest.kt | 171 -------- .../action/GetNotificationEventResponse.kt | 70 ---- .../action/NotificationsActions.kt | 11 - .../model/NotificationEventInfo.kt | 122 ------ .../model/NotificationEventSearchResult.kt | 71 ---- .../NotificationsPluginInterfaceTests.kt | 45 -- .../GetNotificationEventRequestTests.kt | 258 ------------ .../GetNotificationEventResponseTests.kt | 345 --------------- .../model/NotificationEventInfoTests.kt | 273 ------------ .../NotificationEventSearchResultTests.kt | 396 ------------------ 11 files changed, 1783 deletions(-) delete mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequest.kt delete mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponse.kt delete mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfo.kt delete mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResult.kt delete mode 100644 src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequestTests.kt delete mode 100644 src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt delete mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt delete mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt diff --git a/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt b/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt index 2da0562d..c3baa938 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt @@ -18,8 +18,6 @@ import org.opensearch.commons.notifications.action.GetChannelListRequest import org.opensearch.commons.notifications.action.GetChannelListResponse import org.opensearch.commons.notifications.action.GetNotificationConfigRequest import org.opensearch.commons.notifications.action.GetNotificationConfigResponse -import org.opensearch.commons.notifications.action.GetNotificationEventRequest -import org.opensearch.commons.notifications.action.GetNotificationEventResponse import org.opensearch.commons.notifications.action.GetPluginFeaturesRequest import org.opensearch.commons.notifications.action.GetPluginFeaturesResponse import org.opensearch.commons.notifications.action.LegacyPublishNotificationRequest @@ -28,7 +26,6 @@ import org.opensearch.commons.notifications.action.NotificationsActions.CREATE_N import org.opensearch.commons.notifications.action.NotificationsActions.DELETE_NOTIFICATION_CONFIG_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.GET_CHANNEL_LIST_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.GET_NOTIFICATION_CONFIG_ACTION_TYPE -import org.opensearch.commons.notifications.action.NotificationsActions.GET_NOTIFICATION_EVENT_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.GET_PLUGIN_FEATURES_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.LEGACY_PUBLISH_NOTIFICATION_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.SEND_NOTIFICATION_ACTION_TYPE @@ -119,24 +116,6 @@ object NotificationsPluginInterface { ) } - /** - * Get notification events. - * @param client Node client for making transport action - * @param request The request object - * @param listener The listener for getting response - */ - fun getNotificationEvent( - client: NodeClient, - request: GetNotificationEventRequest, - listener: ActionListener - ) { - client.execute( - GET_NOTIFICATION_EVENT_ACTION_TYPE, - request, - wrapActionListener(listener) { response -> recreateObject(response) { GetNotificationEventResponse(it) } } - ) - } - /** * Get notification plugin features. * @param client Node client for making transport action diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequest.kt deleted file mode 100644 index 66bfba6a..00000000 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequest.kt +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ -package org.opensearch.commons.notifications.action - -import org.opensearch.action.ActionRequest -import org.opensearch.action.ActionRequestValidationException -import org.opensearch.action.ValidateActions -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.ToXContentObject -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser -import org.opensearch.common.xcontent.XContentParserUtils -import org.opensearch.commons.notifications.NotificationConstants.DEFAULT_MAX_ITEMS -import org.opensearch.commons.notifications.NotificationConstants.EVENT_ID_LIST_TAG -import org.opensearch.commons.notifications.NotificationConstants.FILTER_PARAM_LIST_TAG -import org.opensearch.commons.notifications.NotificationConstants.FROM_INDEX_TAG -import org.opensearch.commons.notifications.NotificationConstants.MAX_ITEMS_TAG -import org.opensearch.commons.notifications.NotificationConstants.SORT_FIELD_TAG -import org.opensearch.commons.notifications.NotificationConstants.SORT_ORDER_TAG -import org.opensearch.commons.utils.STRING_READER -import org.opensearch.commons.utils.STRING_WRITER -import org.opensearch.commons.utils.enumReader -import org.opensearch.commons.utils.fieldIfNotNull -import org.opensearch.commons.utils.logger -import org.opensearch.commons.utils.stringList -import org.opensearch.search.sort.SortOrder -import java.io.IOException - -/** - * Action Request for getting notification event. - */ -class GetNotificationEventRequest : ActionRequest, ToXContentObject { - val eventIds: Set - val fromIndex: Int - val maxItems: Int - val sortField: String? - val sortOrder: SortOrder? - val filterParams: Map - - companion object { - private val log by logger(GetNotificationEventRequest::class.java) - - /** - * reader to create instance of class from writable. - */ - val reader = Writeable.Reader { GetNotificationEventRequest(it) } - - /** - * Creator used in REST communication. - * @param parser XContentParser to deserialize data from. - */ - @JvmStatic - @Throws(IOException::class) - fun parse(parser: XContentParser): GetNotificationEventRequest { - var eventIds: Set = setOf() - var fromIndex = 0 - var maxItems = DEFAULT_MAX_ITEMS - var sortField: String? = null - var sortOrder: SortOrder? = null - var filterParams: Map = mapOf() - - XContentParserUtils.ensureExpectedToken( - XContentParser.Token.START_OBJECT, - parser.currentToken(), - parser - ) - while (parser.nextToken() != XContentParser.Token.END_OBJECT) { - val fieldName = parser.currentName() - parser.nextToken() - when (fieldName) { - EVENT_ID_LIST_TAG -> eventIds = parser.stringList().toSet() - FROM_INDEX_TAG -> fromIndex = parser.intValue() - MAX_ITEMS_TAG -> maxItems = parser.intValue() - SORT_FIELD_TAG -> sortField = parser.textOrNull() - SORT_ORDER_TAG -> sortOrder = SortOrder.fromString(parser.text()) - FILTER_PARAM_LIST_TAG -> filterParams = parser.mapStrings() - else -> { - parser.skipChildren() - log.info("Unexpected field: $fieldName, while parsing GetNotificationEventRequest") - } - } - } - return GetNotificationEventRequest(eventIds, fromIndex, maxItems, sortField, sortOrder, filterParams) - } - } - - /** - * {@inheritDoc} - */ - override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { - return builder!!.startObject() - .field(EVENT_ID_LIST_TAG, eventIds) - .field(FROM_INDEX_TAG, fromIndex) - .field(MAX_ITEMS_TAG, maxItems) - .fieldIfNotNull(SORT_FIELD_TAG, sortField) - .fieldIfNotNull(SORT_ORDER_TAG, sortOrder) - .field(FILTER_PARAM_LIST_TAG, filterParams) - .endObject() - } - - /** - * constructor for creating the class - * @param eventIds the ids of the notification events (other parameters are not relevant if ids are present) - * @param fromIndex the starting index for paginated response - * @param maxItems the maximum number of items to return for paginated response - * @param sortField the sort field if response has many items - * @param sortOrder the sort order if response has many items - * @param filterParams the filter parameters - */ - constructor( - eventIds: Set = setOf(), - fromIndex: Int = 0, - maxItems: Int = DEFAULT_MAX_ITEMS, - sortField: String? = null, - sortOrder: SortOrder? = null, - filterParams: Map = mapOf() - ) { - this.eventIds = eventIds - this.fromIndex = fromIndex - this.maxItems = maxItems - this.sortField = sortField - this.sortOrder = sortOrder - this.filterParams = filterParams - } - - /** - * {@inheritDoc} - */ - @Throws(IOException::class) - constructor(input: StreamInput) : super(input) { - eventIds = input.readStringList().toSet() - fromIndex = input.readInt() - maxItems = input.readInt() - sortField = input.readOptionalString() - sortOrder = input.readOptionalWriteable(enumReader(SortOrder::class.java)) - filterParams = input.readMap(STRING_READER, STRING_READER) - } - - /** - * {@inheritDoc} - */ - @Throws(IOException::class) - override fun writeTo(output: StreamOutput) { - super.writeTo(output) - output.writeStringCollection(eventIds) - output.writeInt(fromIndex) - output.writeInt(maxItems) - output.writeOptionalString(sortField) - output.writeOptionalWriteable(sortOrder) - output.writeMap(filterParams, STRING_WRITER, STRING_WRITER) - } - - /** - * {@inheritDoc} - */ - override fun validate(): ActionRequestValidationException? { - var validationException: ActionRequestValidationException? = null - if (fromIndex < 0) { - validationException = ValidateActions.addValidationError("fromIndex is -ve", validationException) - } - if (maxItems <= 0) { - validationException = ValidateActions.addValidationError("maxItems is not +ve", validationException) - } - return validationException - } -} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponse.kt deleted file mode 100644 index c512aa48..00000000 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponse.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ -package org.opensearch.commons.notifications.action - -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser -import org.opensearch.commons.notifications.model.NotificationEventSearchResult -import java.io.IOException - -/** - * Action Response for getting notification event. - */ -class GetNotificationEventResponse : BaseResponse { - val searchResult: NotificationEventSearchResult - - companion object { - - /** - * reader to create instance of class from writable. - */ - val reader = Writeable.Reader { GetNotificationEventResponse(it) } - - /** - * Creator used in REST communication. - * @param parser XContentParser to deserialize data from. - */ - @JvmStatic - @Throws(IOException::class) - fun parse(parser: XContentParser): GetNotificationEventResponse { - return GetNotificationEventResponse(NotificationEventSearchResult(parser)) - } - } - - /** - * constructor for creating the class - * @param searchResult the notification event list - */ - constructor(searchResult: NotificationEventSearchResult) { - this.searchResult = searchResult - } - - /** - * {@inheritDoc} - */ - @Throws(IOException::class) - constructor(input: StreamInput) : super(input) { - searchResult = NotificationEventSearchResult(input) - } - - /** - * {@inheritDoc} - */ - @Throws(IOException::class) - override fun writeTo(output: StreamOutput) { - searchResult.writeTo(output) - } - - /** - * {@inheritDoc} - */ - override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { - return searchResult.toXContent(builder, params) - } -} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt index 204d9326..e4a8cfe5 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt @@ -30,11 +30,6 @@ object NotificationsActions { */ const val GET_NOTIFICATION_CONFIG_NAME = "cluster:admin/opensearch/notifications/configs/get" - /** - * Get notification events transport action name. - */ - const val GET_NOTIFICATION_EVENT_NAME = "cluster:admin/opensearch/notifications/events/get" - /** * Get notification plugin features transport action name. */ @@ -80,12 +75,6 @@ object NotificationsActions { val GET_NOTIFICATION_CONFIG_ACTION_TYPE = ActionType(GET_NOTIFICATION_CONFIG_NAME, ::GetNotificationConfigResponse) - /** - * Get notification events transport action type. - */ - val GET_NOTIFICATION_EVENT_ACTION_TYPE = - ActionType(GET_NOTIFICATION_EVENT_NAME, ::GetNotificationEventResponse) - /** * Get notification plugin features transport action type. */ diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfo.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfo.kt deleted file mode 100644 index 51921fc8..00000000 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfo.kt +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ -package org.opensearch.commons.notifications.model - -import org.opensearch.common.Strings -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser -import org.opensearch.common.xcontent.XContentParserUtils -import org.opensearch.commons.notifications.NotificationConstants.CREATED_TIME_TAG -import org.opensearch.commons.notifications.NotificationConstants.EVENT_ID_TAG -import org.opensearch.commons.notifications.NotificationConstants.EVENT_TAG -import org.opensearch.commons.notifications.NotificationConstants.UPDATED_TIME_TAG -import org.opensearch.commons.utils.logger -import java.io.IOException -import java.time.Instant - -/** - * Data class representing Notification event with information. - */ -data class NotificationEventInfo( - val eventId: String, - val lastUpdatedTime: Instant, - val createdTime: Instant, - val notificationEvent: NotificationEvent -) : BaseModel { - - init { - require(!Strings.isNullOrEmpty(eventId)) { "event id is null or empty" } - } - - companion object { - private val log by logger(NotificationEventInfo::class.java) - - /** - * reader to create instance of class from writable. - */ - val reader = Writeable.Reader { NotificationEventInfo(it) } - - /** - * Creator used in REST communication. - * @param parser XContentParser to deserialize data from. - */ - @JvmStatic - @Throws(IOException::class) - fun parse(parser: XContentParser): NotificationEventInfo { - var eventId: String? = null - var lastUpdatedTime: Instant? = null - var createdTime: Instant? = null - var notificationEvent: NotificationEvent? = null - - XContentParserUtils.ensureExpectedToken( - XContentParser.Token.START_OBJECT, - parser.currentToken(), - parser - ) - while (parser.nextToken() != XContentParser.Token.END_OBJECT) { - val fieldName = parser.currentName() - parser.nextToken() - when (fieldName) { - EVENT_ID_TAG -> eventId = parser.text() - UPDATED_TIME_TAG -> lastUpdatedTime = Instant.ofEpochMilli(parser.longValue()) - CREATED_TIME_TAG -> createdTime = Instant.ofEpochMilli(parser.longValue()) - EVENT_TAG -> notificationEvent = NotificationEvent.parse(parser) - else -> { - parser.skipChildren() - log.info("Unexpected field: $fieldName, while parsing event info") - } - } - } - eventId ?: throw IllegalArgumentException("$EVENT_ID_TAG field absent") - lastUpdatedTime ?: throw IllegalArgumentException("$UPDATED_TIME_TAG field absent") - createdTime ?: throw IllegalArgumentException("$CREATED_TIME_TAG field absent") - notificationEvent ?: throw IllegalArgumentException("$EVENT_TAG field absent") - return NotificationEventInfo( - eventId, - lastUpdatedTime, - createdTime, - notificationEvent - ) - } - } - - /** - * {@inheritDoc} - */ - override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { - builder!! - return builder.startObject() - .field(EVENT_ID_TAG, eventId) - .field(UPDATED_TIME_TAG, lastUpdatedTime.toEpochMilli()) - .field(CREATED_TIME_TAG, createdTime.toEpochMilli()) - .field(EVENT_TAG, notificationEvent) - .endObject() - } - - /** - * Constructor used in transport action communication. - * @param input StreamInput stream to deserialize data from. - */ - constructor(input: StreamInput) : this( - eventId = input.readString(), - lastUpdatedTime = input.readInstant(), - createdTime = input.readInstant(), - notificationEvent = NotificationEvent.reader.read(input) - ) - - /** - * {@inheritDoc} - */ - override fun writeTo(output: StreamOutput) { - output.writeString(eventId) - output.writeInstant(lastUpdatedTime) - output.writeInstant(createdTime) - notificationEvent.writeTo(output) - } -} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResult.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResult.kt deleted file mode 100644 index e9b68406..00000000 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResult.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.opensearch.commons.notifications.model - -import org.apache.lucene.search.TotalHits -import org.opensearch.action.search.SearchResponse -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.xcontent.XContentParser -import org.opensearch.commons.notifications.NotificationConstants.EVENT_LIST_TAG - -/** - * Notification Event search results - */ -class NotificationEventSearchResult : SearchResults { - - /** - * single item result constructor - */ - constructor(objectItem: NotificationEventInfo) : super(EVENT_LIST_TAG, objectItem) - - /** - * multiple items result constructor - */ - constructor(objectList: List) : this( - 0, - objectList.size.toLong(), - TotalHits.Relation.EQUAL_TO, - objectList - ) - - /** - * all param constructor - */ - constructor( - startIndex: Long, - totalHits: Long, - totalHitRelation: TotalHits.Relation, - objectList: List - ) : super(startIndex, totalHits, totalHitRelation, EVENT_LIST_TAG, objectList) - - /** - * Constructor used in transport action communication. - * @param input StreamInput stream to deserialize data from. - */ - constructor(input: StreamInput) : super(input, NotificationEventInfo.reader) - - /** - * Construct object from XContentParser - */ - constructor(parser: XContentParser) : super(parser, EVENT_LIST_TAG) - - /** - * Construct object from SearchResponse - */ - constructor(from: Long, response: SearchResponse, searchHitParser: SearchHitParser) : super( - from, - response, - searchHitParser, - EVENT_LIST_TAG - ) - - /** - * {@inheritDoc} - */ - override fun parseItem(parser: XContentParser): NotificationEventInfo { - return NotificationEventInfo.parse(parser) - } -} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt index aaa831d1..556c35b6 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt @@ -29,8 +29,6 @@ import org.opensearch.commons.notifications.action.GetChannelListRequest import org.opensearch.commons.notifications.action.GetChannelListResponse import org.opensearch.commons.notifications.action.GetNotificationConfigRequest import org.opensearch.commons.notifications.action.GetNotificationConfigResponse -import org.opensearch.commons.notifications.action.GetNotificationEventRequest -import org.opensearch.commons.notifications.action.GetNotificationEventResponse import org.opensearch.commons.notifications.action.GetPluginFeaturesRequest import org.opensearch.commons.notifications.action.GetPluginFeaturesResponse import org.opensearch.commons.notifications.action.LegacyPublishNotificationRequest @@ -42,15 +40,10 @@ import org.opensearch.commons.notifications.model.Channel import org.opensearch.commons.notifications.model.ChannelList import org.opensearch.commons.notifications.model.ChannelMessage import org.opensearch.commons.notifications.model.ConfigType -import org.opensearch.commons.notifications.model.DeliveryStatus import org.opensearch.commons.notifications.model.EventSource -import org.opensearch.commons.notifications.model.EventStatus import org.opensearch.commons.notifications.model.NotificationConfig import org.opensearch.commons.notifications.model.NotificationConfigInfo import org.opensearch.commons.notifications.model.NotificationConfigSearchResult -import org.opensearch.commons.notifications.model.NotificationEvent -import org.opensearch.commons.notifications.model.NotificationEventInfo -import org.opensearch.commons.notifications.model.NotificationEventSearchResult import org.opensearch.commons.notifications.model.SeverityType import org.opensearch.commons.notifications.model.Slack import org.opensearch.rest.RestStatus @@ -127,22 +120,6 @@ internal class NotificationsPluginInterfaceTests { verify(listener, times(1)).onResponse(eq(response)) } - @Test - fun getNotificationEvent() { - val request = mock(GetNotificationEventRequest::class.java) - val response = mockGetNotificationEventResponse() - val listener: ActionListener = - mock(ActionListener::class.java) as ActionListener - - doAnswer { - (it.getArgument(2) as ActionListener) - .onResponse(response) - }.whenever(client).execute(any(ActionType::class.java), any(), any()) - - NotificationsPluginInterface.getNotificationEvent(client, request, listener) - verify(listener, times(1)).onResponse(eq(response)) - } - @Test fun getPluginFeatures() { val request = mock(GetPluginFeaturesRequest::class.java) @@ -250,26 +227,4 @@ internal class NotificationsPluginInterfaceTests { ) return GetNotificationConfigResponse(NotificationConfigSearchResult(configInfo)) } - - private fun mockGetNotificationEventResponse(): GetNotificationEventResponse { - val sampleEventSource = EventSource( - "title", - "reference_id", - severity = SeverityType.INFO - ) - val sampleStatus = EventStatus( - "config_id", - "name", - ConfigType.SLACK, - deliveryStatus = DeliveryStatus("404", "invalid recipient") - ) - val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) - val eventInfo = NotificationEventInfo( - "event_id", - Instant.now(), - Instant.now(), - sampleEvent - ) - return GetNotificationEventResponse(NotificationEventSearchResult(eventInfo)) - } } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequestTests.kt deleted file mode 100644 index 53bb825a..00000000 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequestTests.kt +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ -package org.opensearch.commons.notifications.action - -import com.fasterxml.jackson.core.JsonParseException -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.opensearch.commons.utils.createObjectFromJsonString -import org.opensearch.commons.utils.getJsonString -import org.opensearch.commons.utils.recreateObject -import org.opensearch.search.sort.SortOrder - -internal class GetNotificationEventRequestTests { - - private fun assertGetRequestEquals( - expected: GetNotificationEventRequest, - actual: GetNotificationEventRequest - ) { - assertEquals(expected.eventIds, actual.eventIds) - assertEquals(expected.fromIndex, actual.fromIndex) - assertEquals(expected.maxItems, actual.maxItems) - assertEquals(expected.sortField, actual.sortField) - assertEquals(expected.sortOrder, actual.sortOrder) - assertEquals(expected.filterParams, actual.filterParams) - } - - @Test - fun `Get request serialize and deserialize transport object should be equal`() { - val configRequest = GetNotificationEventRequest( - setOf("sample_event_id"), - 0, - 10, - "sortField", - SortOrder.DESC, - mapOf(Pair("filterKey", "filterValue")) - ) - val recreatedObject = recreateObject(configRequest) { GetNotificationEventRequest(it) } - assertGetRequestEquals(configRequest, recreatedObject) - } - - @Test - fun `Get request serialize and deserialize using json object should be equal`() { - val configRequest = GetNotificationEventRequest( - setOf("sample_event_id"), - 0, - 10, - "sortField", - SortOrder.ASC, - mapOf(Pair("filterKey", "filterValue")) - ) - val jsonString = getJsonString(configRequest) - val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } - assertGetRequestEquals(configRequest, recreatedObject) - } - - @Test - fun `Get request with all field should deserialize json object using parser`() { - val configRequest = GetNotificationEventRequest( - setOf("sample_event_id"), - 10, - 100, - "sortField", - SortOrder.DESC, - mapOf( - Pair("filterKey1", "filterValue1"), - Pair("filterKey2", "true"), - Pair("filterKey3", "filter,Value,3"), - Pair("filterKey4", "4") - ) - ) - val jsonString = """ - { - "event_id_list":["${configRequest.eventIds.first()}"], - "from_index":"10", - "max_items":"100", - "sort_field":"sortField", - "sort_order":"desc", - "filter_param_list": { - "filterKey1":"filterValue1", - "filterKey2":"true", - "filterKey3":"filter,Value,3", - "filterKey4":"4" - } - } - """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } - assertGetRequestEquals(configRequest, recreatedObject) - } - - @Test - fun `Get request with only event_id field should deserialize json object using parser`() { - val configRequest = GetNotificationEventRequest(eventIds = setOf("sample_event_id")) - val jsonString = """ - { - "event_id_list":["${configRequest.eventIds.first()}"] - } - """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } - assertGetRequestEquals(configRequest, recreatedObject) - } - - @Test - fun `Get request with only from_index field should deserialize json object using parser`() { - val configRequest = GetNotificationEventRequest(fromIndex = 20) - val jsonString = """ - { - "from_index":"20" - } - """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } - assertGetRequestEquals(configRequest, recreatedObject) - } - - @Test - fun `Get request with only max_items field should deserialize json object using parser`() { - val configRequest = GetNotificationEventRequest(maxItems = 100) - val jsonString = """ - { - "max_items":"100" - } - """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } - assertGetRequestEquals(configRequest, recreatedObject) - } - - @Test - fun `Get request with only sort_field field should deserialize json object using parser`() { - val configRequest = GetNotificationEventRequest(sortField = "sample_sort_field") - val jsonString = """ - { - "sort_field":"sample_sort_field" - } - """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } - assertGetRequestEquals(configRequest, recreatedObject) - } - - @Test - fun `Get request with only sort_order=asc field should deserialize json object using parser`() { - val configRequest = GetNotificationEventRequest(sortOrder = SortOrder.ASC) - val jsonString = """ - { - "sort_order":"asc" - } - """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } - assertGetRequestEquals(configRequest, recreatedObject) - } - - @Test - fun `Get request with only sort_order=ASC field should deserialize json object using parser`() { - val configRequest = GetNotificationEventRequest(sortOrder = SortOrder.ASC) - val jsonString = """ - { - "sort_order":"ASC" - } - """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } - assertGetRequestEquals(configRequest, recreatedObject) - } - - @Test - fun `Get request with only sort_order=desc field should deserialize json object using parser`() { - val configRequest = GetNotificationEventRequest(sortOrder = SortOrder.DESC) - val jsonString = """ - { - "sort_order":"desc" - } - """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } - assertGetRequestEquals(configRequest, recreatedObject) - } - - @Test - fun `Get request with only sort_order=DESC field should deserialize json object using parser`() { - val configRequest = GetNotificationEventRequest(sortOrder = SortOrder.DESC) - val jsonString = """ - { - "sort_order":"DESC" - } - """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } - assertGetRequestEquals(configRequest, recreatedObject) - } - - @Test - fun `Get request with invalid sort_order should throw exception`() { - val jsonString = """ - { - "sort_order":"descending" - } - """.trimIndent() - assertThrows { - createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } - } - } - - @Test - fun `Get request with only filter_param_list field should deserialize json object using parser`() { - val configRequest = GetNotificationEventRequest( - filterParams = mapOf( - Pair("filterKey1", "filterValue1"), - Pair("filterKey2", "true"), - Pair("filterKey3", "filter,Value,3"), - Pair("filterKey4", "4") - ) - ) - val jsonString = """ - { - "filter_param_list": { - "filterKey1":"filterValue1", - "filterKey2":"true", - "filterKey3":"filter,Value,3", - "filterKey4":"4" - } - } - """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } - assertGetRequestEquals(configRequest, recreatedObject) - } - - @Test - fun `Get request no field should deserialize json object using parser`() { - val configRequest = GetNotificationEventRequest() - val jsonString = """ - { - } - """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } - assertGetRequestEquals(configRequest, recreatedObject) - } - - @Test - fun `Get request should throw exception when invalid json object is passed`() { - val jsonString = "sample message" - assertThrows { - createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } - } - } - - @Test - fun `Get request should safely ignore extra field in json object`() { - val configRequest = GetNotificationEventRequest(eventIds = setOf("sample_event_id")) - val jsonString = """ - { - "event_id_list":["${configRequest.eventIds.first()}"], - "extra_field_1":["extra", "value"], - "extra_field_2":{"extra":"value"}, - "extra_field_3":"extra value 3" - } - """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } - assertGetRequestEquals(configRequest, recreatedObject) - } -} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt deleted file mode 100644 index 0de1c78b..00000000 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ -package org.opensearch.commons.notifications.action - -import org.apache.lucene.search.TotalHits -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.opensearch.commons.notifications.model.ConfigType -import org.opensearch.commons.notifications.model.DeliveryStatus -import org.opensearch.commons.notifications.model.EventSource -import org.opensearch.commons.notifications.model.EventStatus -import org.opensearch.commons.notifications.model.NotificationEvent -import org.opensearch.commons.notifications.model.NotificationEventInfo -import org.opensearch.commons.notifications.model.NotificationEventSearchResult -import org.opensearch.commons.notifications.model.SeverityType -import org.opensearch.commons.utils.createObjectFromJsonString -import org.opensearch.commons.utils.getJsonString -import org.opensearch.commons.utils.recreateObject -import java.time.Instant - -internal class GetNotificationEventResponseTests { - - private fun assertSearchResultEquals( - expected: NotificationEventSearchResult, - actual: NotificationEventSearchResult - ) { - assertEquals(expected.startIndex, actual.startIndex) - assertEquals(expected.totalHits, actual.totalHits) - assertEquals(expected.totalHitRelation, actual.totalHitRelation) - assertEquals(expected.objectListFieldName, actual.objectListFieldName) - assertEquals(expected.objectList, actual.objectList) - } - - @Test - fun `Search result serialize and deserialize with event object should be equal`() { - val sampleEventSource = EventSource( - "title", - "reference_id", - severity = SeverityType.INFO - ) - val sampleStatus = EventStatus( - "config_id", - "name", - ConfigType.SLACK, - deliveryStatus = DeliveryStatus("404", "invalid recipient") - ) - val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) - val eventInfo = NotificationEventInfo( - "event_id", - Instant.now(), - Instant.now(), - sampleEvent - ) - val searchResult = NotificationEventSearchResult(eventInfo) - val searchResponse = GetNotificationEventResponse(searchResult) - val recreatedObject = recreateObject(searchResponse) { GetNotificationEventResponse(it) } - assertSearchResultEquals(searchResult, recreatedObject.searchResult) - } - - @Test - fun `Search result serialize and deserialize with multiple event status object should be equal`() { - val eventSource1 = EventSource( - "title 1", - "reference_id_1", - severity = SeverityType.INFO - ) - val eventSource2 = EventSource( - "title 2", - "reference_id_2", - severity = SeverityType.HIGH - ) - val status1 = EventStatus( - "config_id1", - "name", - ConfigType.SLACK, - deliveryStatus = DeliveryStatus("200", "success") - ) - val status2 = EventStatus( - "config_id2", - "name", - ConfigType.CHIME, - deliveryStatus = DeliveryStatus("404", "invalid recipient") - ) - val eventInfo1 = NotificationEventInfo( - "event_id1", - Instant.now(), - Instant.now(), - NotificationEvent(eventSource1, listOf(status1)) - ) - val eventInfo2 = NotificationEventInfo( - "event_id2", - Instant.now(), - Instant.now(), - NotificationEvent(eventSource2, listOf(status2)) - ) - val eventInfo3 = NotificationEventInfo( - "event_id3", - Instant.now(), - Instant.now(), - NotificationEvent(eventSource1, listOf(status1, status2)) - ) - val eventInfo4 = NotificationEventInfo( - "event_id4", - Instant.now(), - Instant.now(), - NotificationEvent(eventSource2, listOf(status1, status2)) - ) - val searchResult = NotificationEventSearchResult( - 100, - 1000, - TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, - listOf(eventInfo1, eventInfo2, eventInfo3, eventInfo4) - ) - val searchResponse = GetNotificationEventResponse(searchResult) - val recreatedObject = recreateObject(searchResponse) { GetNotificationEventResponse(it) } - assertSearchResultEquals(searchResult, recreatedObject.searchResult) - } - - @Test - fun `Search result serialize and deserialize using json event object should be equal`() { - val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) - val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) - val sampleEventSource = EventSource( - "title", - "reference_id", - severity = SeverityType.INFO - ) - val sampleStatus = EventStatus( - "config_id", - "name", - ConfigType.SLACK, - deliveryStatus = DeliveryStatus("404", "invalid recipient") - ) - val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) - val eventInfo = NotificationEventInfo( - "event_id", - lastUpdatedTimeMs, - createdTimeMs, - sampleEvent - ) - val searchResult = NotificationEventSearchResult(eventInfo) - val searchResponse = GetNotificationEventResponse(searchResult) - val jsonString = getJsonString(searchResponse) - val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventResponse.parse(it) } - assertSearchResultEquals(searchResult, recreatedObject.searchResult) - } - - @Test - fun `Search result serialize and deserialize using json with multiple event object should be equal`() { - val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) - val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) - val eventSource1 = EventSource( - "title 1", - "reference_id_1", - severity = SeverityType.INFO - ) - val eventSource2 = EventSource( - "title 2", - "reference_id_2", - severity = SeverityType.HIGH - ) - val status1 = EventStatus( - "config_id1", - "name", - ConfigType.SLACK, - deliveryStatus = DeliveryStatus("200", "success") - ) - val status2 = EventStatus( - "config_id2", - "name", - ConfigType.CHIME, - deliveryStatus = DeliveryStatus("404", "invalid recipient") - ) - val eventInfo1 = NotificationEventInfo( - "event_id1", - lastUpdatedTimeMs, - createdTimeMs, - NotificationEvent(eventSource1, listOf(status1)) - ) - val eventInfo2 = NotificationEventInfo( - "event_id2", - lastUpdatedTimeMs, - createdTimeMs, - NotificationEvent(eventSource2, listOf(status2)) - ) - val searchResult = NotificationEventSearchResult( - 100, - 1000, - TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, - listOf(eventInfo1, eventInfo2) - ) - val searchResponse = GetNotificationEventResponse(searchResult) - val jsonString = getJsonString(searchResponse) - val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventResponse.parse(it) } - assertSearchResultEquals(searchResult, recreatedObject.searchResult) - } - - @Test - fun `Search result should safely ignore extra field in json object`() { - val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) - val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) - val sampleEventSource = EventSource( - "title", - "reference_id", - severity = SeverityType.INFO - ) - val sampleStatus = EventStatus( - "config_id", - "name", - ConfigType.SLACK, - deliveryStatus = DeliveryStatus("200", "success") - ) - val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) - val eventInfo = NotificationEventInfo( - "event_id", - lastUpdatedTimeMs, - createdTimeMs, - sampleEvent - ) - val searchResult = NotificationEventSearchResult(eventInfo) - val jsonString = """ - { - "start_index":"0", - "total_hits":"1", - "total_hit_relation":"eq", - "event_list":[ - { - "event_id":"event_id", - "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", - "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "event":{ - "event_source":{ - "title":"title", - "reference_id":"reference_id", - "severity":"info", - "tags":[] - }, - "status_list":[ - { - "config_id":"config_id", - "config_type":"slack", - "config_name":"name", - "delivery_status": - { - "status_code":"200", - "status_text":"success" - } - } - ] - } - } - ], - "extra_field_1":["extra", "value"], - "extra_field_2":{"extra":"value"}, - "extra_field_3":"extra value 3" - } - """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventResponse.parse(it) } - assertSearchResultEquals(searchResult, recreatedObject.searchResult) - } - - @Test - fun `Search result should safely fallback to default if startIndex, totalHits or totalHitRelation field absent in json object`() { - val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) - val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) - val sampleEventSource = EventSource( - "title", - "reference_id", - severity = SeverityType.INFO - ) - val sampleStatus = EventStatus( - "config_id", - "name", - ConfigType.SLACK, - deliveryStatus = DeliveryStatus("200", "success") - ) - val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) - val eventInfo = NotificationEventInfo( - "event_id", - lastUpdatedTimeMs, - createdTimeMs, - sampleEvent - ) - val searchResult = NotificationEventSearchResult(eventInfo) - val jsonString = """ - { - "event_list":[ - { - "event_id":"event_id", - "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", - "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "event":{ - "event_source":{ - "title":"title", - "reference_id":"reference_id", - "severity":"info", - "tags":[] - }, - "status_list":[ - { - "config_id":"config_id", - "config_type":"slack", - "config_name":"name", - "delivery_status": - { - "status_code":"200", - "status_text":"success" - } - } - ] - } - } - ] - } - """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventResponse.parse(it) } - assertSearchResultEquals(searchResult, recreatedObject.searchResult) - } - - @Test - fun `Search result should throw exception if event is absent in json`() { - val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) - val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) - val jsonString = """ - { - "start_index":"0", - "total_hits":"1", - "total_hit_relation":"eq", - "event_list":[ - { - "event_id":"event_id", - "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", - "created_time_ms":"${createdTimeMs.toEpochMilli()}" - } - ] - } - """.trimIndent() - Assertions.assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { GetNotificationEventResponse.parse(it) } - } - } -} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt deleted file mode 100644 index 85d2dee9..00000000 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ -package org.opensearch.commons.notifications.model - -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.opensearch.commons.utils.createObjectFromJsonString -import org.opensearch.commons.utils.getJsonString -import org.opensearch.commons.utils.recreateObject -import java.time.Instant - -internal class NotificationEventInfoTests { - - @Test - fun `Event info serialize and deserialize with event object should be equal`() { - val sampleEventSource = EventSource( - "title", - "reference_id", - severity = SeverityType.INFO - ) - val sampleStatus = EventStatus( - "config_id", - "name", - ConfigType.SLACK, - deliveryStatus = DeliveryStatus("404", "invalid recipient") - ) - val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) - val eventInfo = NotificationEventInfo( - "event_id", - Instant.now(), - Instant.now(), - sampleEvent - ) - val recreatedObject = recreateObject(eventInfo) { NotificationEventInfo(it) } - assertEquals(eventInfo, recreatedObject) - } - - @Test - fun `Event info serialize and deserialize using json event object should be equal`() { - val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) - val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) - val sampleEventSource = EventSource( - "title", - "reference_id", - severity = SeverityType.INFO - ) - val sampleStatus = EventStatus( - "config_id", - "name", - ConfigType.SLACK, - deliveryStatus = DeliveryStatus("200", "success") - ) - val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) - val eventInfo = NotificationEventInfo( - "event_id", - lastUpdatedTimeMs, - createdTimeMs, - sampleEvent - ) - val jsonString = getJsonString(eventInfo) - val recreatedObject = createObjectFromJsonString(jsonString) { NotificationEventInfo.parse(it) } - assertEquals(eventInfo, recreatedObject) - } - - @Test - fun `Event info should safely ignore extra field in json object`() { - val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) - val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) - val sampleEventSource = EventSource( - "title", - "reference_id", - severity = SeverityType.INFO - ) - val sampleStatus = EventStatus( - "config_id", - "name", - ConfigType.SLACK, - deliveryStatus = DeliveryStatus("200", "success") - ) - val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) - val eventInfo = NotificationEventInfo( - "event_id", - lastUpdatedTimeMs, - createdTimeMs, - sampleEvent - ) - val jsonString = """ - { - "event_id":"event_id", - "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", - "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "event":{ - "event_source":{ - "title":"title", - "reference_id":"reference_id", - "feature":"alerting", - "severity":"info", - "tags":[] - }, - "status_list":[ - { - "config_id":"config_id", - "config_type":"slack", - "config_name":"name", - "delivery_status": - { - "status_code":"200", - "status_text":"success" - } - } - ] - }, - "extra_field_1":["extra", "value"], - "extra_field_2":{"extra":"value"}, - "extra_field_3":"extra value 3" - } - """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { NotificationEventInfo.parse(it) } - assertEquals(eventInfo, recreatedObject) - } - - @Test - fun `Event info should throw exception if event_id is empty`() { - val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) - val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) - val sampleEventSource = EventSource( - "title", - "reference_id", - severity = SeverityType.INFO - ) - val sampleStatus = EventStatus( - "event_id", - "name", - ConfigType.SLACK, - deliveryStatus = DeliveryStatus("200", "success") - ) - val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) - Assertions.assertThrows(IllegalArgumentException::class.java) { - NotificationEventInfo( - "", - lastUpdatedTimeMs, - createdTimeMs, - sampleEvent - ) - } - } - - @Test - fun `Event info should throw exception if event_id is absent in json`() { - val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) - val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) - val jsonString = """ - { - "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", - "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "event":{ - "event_source":{ - "title":"title", - "reference_id":"reference_id", - "feature":"alerting", - "severity":"info", - "tags":["tag1", "tag2"] - }, - "status_list":[ - { - "event_id":"event_id", - "config_type":"slack", - "config_name":"name", - "delivery_status": - { - "status_code":"200", - "status_text":"success" - } - } - ] - } - } - """.trimIndent() - Assertions.assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { NotificationEventInfo.parse(it) } - } - } - - @Test - fun `Event info should throw exception if lastUpdatedTimeMs is absent in json`() { - val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) - val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) - val jsonString = """ - { - "event_id":"event_id", - "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "event":{ - "event_source":{ - "title":"title", - "reference_id":"reference_id", - "feature":"alerting", - "severity":"info", - "tags":["tag1", "tag2"] - }, - "status_list":[ - { - "event_id":"event_id", - "config_type":"slack", - "config_name":"name", - "delivery_status": - { - "status_code":"200", - "status_text":"success" - } - } - ] - } - } - """.trimIndent() - Assertions.assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { NotificationEventInfo.parse(it) } - } - } - - @Test - fun `Event info should throw exception if createdTimeMs is absent in json`() { - val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) - val jsonString = """ - { - "event_id":"event_id", - "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", - "event":{ - "event_source":{ - "title":"title", - "reference_id":"reference_id", - "feature":"alerting", - "severity":"info", - "tags":["tag1", "tag2"] - }, - "status_list":[ - { - "event_id":"event_id", - "config_type":"slack", - "config_name":"name", - "delivery_status": - { - "status_code":"200", - "status_text":"success" - } - } - ] - } - } - """.trimIndent() - Assertions.assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { NotificationEventInfo.parse(it) } - } - } - - @Test - fun `Event info should throw exception if event is absent in json`() { - val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) - val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) - val jsonString = """ - { - "event_id":"event_id", - "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", - "created_time_ms":"${createdTimeMs.toEpochMilli()}" - } - """.trimIndent() - Assertions.assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { NotificationEventInfo.parse(it) } - } - } -} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt deleted file mode 100644 index b65a0783..00000000 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt +++ /dev/null @@ -1,396 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ -package org.opensearch.commons.notifications.model - -import org.apache.lucene.search.TotalHits -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.opensearch.commons.utils.createObjectFromJsonString -import org.opensearch.commons.utils.getJsonString -import org.opensearch.commons.utils.recreateObject -import java.time.Instant - -internal class NotificationEventSearchResultTests { - - private fun assertSearchResultEquals( - expected: NotificationEventSearchResult, - actual: NotificationEventSearchResult - ) { - assertEquals(expected.startIndex, actual.startIndex) - assertEquals(expected.totalHits, actual.totalHits) - assertEquals(expected.totalHitRelation, actual.totalHitRelation) - assertEquals(expected.objectListFieldName, actual.objectListFieldName) - assertEquals(expected.objectList, actual.objectList) - } - - @Test - fun `Search result serialize and deserialize with event object should be equal`() { - val sampleEventSource = EventSource( - "title", - "reference_id", - severity = SeverityType.INFO - ) - val sampleStatus = EventStatus( - "config_id", - "name", - ConfigType.SLACK, - deliveryStatus = DeliveryStatus("404", "invalid recipient") - ) - val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) - val eventInfo = NotificationEventInfo( - "event_id", - Instant.now(), - Instant.now(), - sampleEvent - ) - val searchResult = NotificationEventSearchResult(eventInfo) - val recreatedObject = recreateObject(searchResult) { NotificationEventSearchResult(it) } - assertSearchResultEquals(searchResult, recreatedObject) - } - - @Test - fun `Search result serialize and deserialize with multiple event default values should be equal`() { - val eventSource1 = EventSource( - "title 1", - "reference_id_1", - severity = SeverityType.INFO - ) - val eventSource2 = EventSource( - "title 2", - "reference_id_2", - severity = SeverityType.HIGH - ) - val status1 = EventStatus( - "config_id1", - "name", - ConfigType.SLACK, - deliveryStatus = DeliveryStatus("200", "success") - ) - val status2 = EventStatus( - "config_id2", - "name", - ConfigType.CHIME, - deliveryStatus = DeliveryStatus("404", "invalid recipient") - ) - val eventInfo1 = NotificationEventInfo( - "event_id1", - Instant.now(), - Instant.now(), - NotificationEvent(eventSource1, listOf(status1)) - ) - val eventInfo2 = NotificationEventInfo( - "event_id2", - Instant.now(), - Instant.now(), - NotificationEvent(eventSource2, listOf(status2)) - ) - val eventInfo3 = NotificationEventInfo( - "event_id3", - Instant.now(), - Instant.now(), - NotificationEvent(eventSource1, listOf(status1, status2)) - ) - val eventInfo4 = NotificationEventInfo( - "event_id4", - Instant.now(), - Instant.now(), - NotificationEvent(eventSource2, listOf(status1, status2)) - ) - val searchResult = NotificationEventSearchResult( - listOf(eventInfo1, eventInfo2, eventInfo3, eventInfo4) - ) - val expectedResult = NotificationEventSearchResult( - 0, - 4, - TotalHits.Relation.EQUAL_TO, - listOf(eventInfo1, eventInfo2, eventInfo3, eventInfo4) - ) - val recreatedObject = recreateObject(searchResult) { NotificationEventSearchResult(it) } - assertSearchResultEquals(expectedResult, recreatedObject) - } - - @Test - fun `Search result serialize and deserialize with multiple event status object should be equal`() { - val eventSource1 = EventSource( - "title 1", - "reference_id_1", - severity = SeverityType.INFO - ) - val eventSource2 = EventSource( - "title 2", - "reference_id_2", - severity = SeverityType.HIGH - ) - val status1 = EventStatus( - "config_id1", - "name", - ConfigType.SLACK, - deliveryStatus = DeliveryStatus("200", "success") - ) - val status2 = EventStatus( - "config_id2", - "name", - ConfigType.CHIME, - deliveryStatus = DeliveryStatus("404", "invalid recipient") - ) - val eventInfo1 = NotificationEventInfo( - "event_id1", - Instant.now(), - Instant.now(), - NotificationEvent(eventSource1, listOf(status1)) - ) - val eventInfo2 = NotificationEventInfo( - "event_id2", - Instant.now(), - Instant.now(), - NotificationEvent(eventSource2, listOf(status2)) - ) - val eventInfo3 = NotificationEventInfo( - "event_id3", - Instant.now(), - Instant.now(), - NotificationEvent(eventSource1, listOf(status1, status2)) - ) - val eventInfo4 = NotificationEventInfo( - "event_id4", - Instant.now(), - Instant.now(), - NotificationEvent(eventSource2, listOf(status1, status2)) - ) - val searchResult = NotificationEventSearchResult( - 100, - 1000, - TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, - listOf(eventInfo1, eventInfo2, eventInfo3, eventInfo4) - ) - val recreatedObject = recreateObject(searchResult) { NotificationEventSearchResult(it) } - assertSearchResultEquals(searchResult, recreatedObject) - } - - @Test - fun `Search result serialize and deserialize using json event object should be equal`() { - val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) - val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) - val sampleEventSource = EventSource( - "title", - "reference_id", - severity = SeverityType.INFO - ) - val sampleStatus = EventStatus( - "config_id", - "name", - ConfigType.SLACK, - deliveryStatus = DeliveryStatus("404", "invalid recipient") - ) - val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) - val eventInfo = NotificationEventInfo( - "event_id", - lastUpdatedTimeMs, - createdTimeMs, - sampleEvent - ) - val searchResult = NotificationEventSearchResult(eventInfo) - val jsonString = getJsonString(searchResult) - val recreatedObject = createObjectFromJsonString(jsonString) { NotificationEventSearchResult(it) } - assertSearchResultEquals(searchResult, recreatedObject) - } - - @Test - fun `Search result serialize and deserialize using json with multiple event object should be equal`() { - val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) - val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) - val eventSource1 = EventSource( - "title 1", - "reference_id_1", - severity = SeverityType.INFO - ) - val eventSource2 = EventSource( - "title 2", - "reference_id_2", - severity = SeverityType.HIGH - ) - val status1 = EventStatus( - "config_id1", - "name", - ConfigType.SLACK, - deliveryStatus = DeliveryStatus("200", "success") - ) - val status2 = EventStatus( - "config_id2", - "name", - ConfigType.CHIME, - deliveryStatus = DeliveryStatus("404", "invalid recipient") - ) - val eventInfo1 = NotificationEventInfo( - "event_id1", - lastUpdatedTimeMs, - createdTimeMs, - NotificationEvent(eventSource1, listOf(status1)) - ) - val eventInfo2 = NotificationEventInfo( - "event_id2", - lastUpdatedTimeMs, - createdTimeMs, - NotificationEvent(eventSource2, listOf(status2)) - ) - val searchResult = NotificationEventSearchResult( - 100, - 1000, - TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, - listOf(eventInfo1, eventInfo2) - ) - val jsonString = getJsonString(searchResult) - val recreatedObject = createObjectFromJsonString(jsonString) { NotificationEventSearchResult(it) } - assertSearchResultEquals(searchResult, recreatedObject) - } - - @Test - fun `Search result should safely ignore extra field in json object`() { - val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) - val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) - val sampleEventSource = EventSource( - "title", - "reference_id", - severity = SeverityType.INFO - ) - val sampleStatus = EventStatus( - "config_id", - "name", - ConfigType.SLACK, - deliveryStatus = DeliveryStatus("200", "success") - ) - val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) - val eventInfo = NotificationEventInfo( - "event_id", - lastUpdatedTimeMs, - createdTimeMs, - sampleEvent - ) - val searchResult = NotificationEventSearchResult(eventInfo) - val jsonString = """ - { - "start_index":"0", - "total_hits":"1", - "total_hit_relation":"eq", - "event_list":[ - { - "event_id":"event_id", - "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", - "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "event":{ - "event_source":{ - "title":"title", - "reference_id":"reference_id", - "feature":"alerting", - "severity":"info", - "tags":[] - }, - "status_list":[ - { - "config_id":"config_id", - "config_type":"slack", - "config_name":"name", - "delivery_status": - { - "status_code":"200", - "status_text":"success" - } - } - ] - } - } - ], - "extra_field_1":["extra", "value"], - "extra_field_2":{"extra":"value"}, - "extra_field_3":"extra value 3" - } - """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { NotificationEventSearchResult(it) } - assertSearchResultEquals(searchResult, recreatedObject) - } - - @Test - fun `Search result should safely fallback to default if startIndex, totalHits or totalHitRelation field absent in json object`() { - val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) - val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) - val sampleEventSource = EventSource( - "title", - "reference_id", - severity = SeverityType.INFO - ) - val sampleStatus = EventStatus( - "config_id", - "name", - ConfigType.SLACK, - deliveryStatus = DeliveryStatus("200", "success") - ) - val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) - val eventInfo = NotificationEventInfo( - "event_id", - lastUpdatedTimeMs, - createdTimeMs, - sampleEvent - ) - val searchResult = NotificationEventSearchResult(eventInfo) - val jsonString = """ - { - "event_list":[ - { - "event_id":"event_id", - "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", - "created_time_ms":"${createdTimeMs.toEpochMilli()}", - "event":{ - "event_source":{ - "title":"title", - "reference_id":"reference_id", - "feature":"alerting", - "severity":"info", - "tags":[] - }, - "status_list":[ - { - "config_id":"config_id", - "config_type":"slack", - "config_name":"name", - "delivery_status": - { - "status_code":"200", - "status_text":"success" - } - } - ] - } - } - ] - } - """.trimIndent() - val recreatedObject = createObjectFromJsonString(jsonString) { NotificationEventSearchResult(it) } - assertSearchResultEquals(searchResult, recreatedObject) - } - - @Test - fun `Search result should throw exception if event is absent in json`() { - val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) - val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) - val jsonString = """ - { - "start_index":"0", - "total_hits":"1", - "total_hit_relation":"eq", - "event_list":[ - { - "event_id":"event_id", - "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", - "created_time_ms":"${createdTimeMs.toEpochMilli()}" - } - ] - } - """.trimIndent() - Assertions.assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { NotificationEventSearchResult(it) } - } - } -} From 4553617bac3ec10ec9c8880b6efbad06fd972f9c Mon Sep 17 00:00:00 2001 From: "Daniel Doubrovkine (dB.)" Date: Wed, 6 Apr 2022 16:41:59 -0400 Subject: [PATCH 050/149] Updated issue templates from .github. (#154) Signed-off-by: dblock --- .github/ISSUE_TEMPLATE/bug_report.md | 28 +++++++++++------------ .github/ISSUE_TEMPLATE/config.yml | 7 ++++++ .github/ISSUE_TEMPLATE/feature_request.md | 17 +++++++------- 3 files changed, 28 insertions(+), 24 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 8af6ebb5..29eddb95 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,33 +1,31 @@ --- name: 🐛 Bug report about: Create a report to help us improve -title: "[BUG]" -labels: 'bug, untriaged, Beta' +title: '[BUG]' +labels: 'bug, untriaged' assignees: '' --- -**Describe the bug** -A clear and concise description of what the bug is. +**What is the bug?** +A clear and concise description of the bug. -**To Reproduce** +**How can one reproduce the bug?** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error -**Expected behavior** +**What is the expected behavior?** A clear and concise description of what you expected to happen. -**Plugins** -Please list all plugins currently enabled. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Host/Environment (please complete the following information):** +**What is your host/environment?** - OS: [e.g. iOS] - Version [e.g. 22] + - Plugins + +**Do you have any screenshots?** +If applicable, add screenshots to help explain your problem. -**Additional context** -Add any other context about the problem here. \ No newline at end of file +**Do you have any additional context?** +Add any other context about the problem. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..a8199a10 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,7 @@ +contact_links: + - name: OpenSearch Community Support + url: https://discuss.opendistrocommunity.dev/ + about: Please ask and answer questions here. + - name: AWS/Amazon Security + url: https://aws.amazon.com/security/vulnerability-reporting/ + about: Please report security vulnerabilities here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 2791b808..6198f338 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,19 +1,18 @@ --- name: 🎆 Feature request -about: Suggest an idea for this project -title: '' -labels: enhancement +about: Request a feature in this project +title: '[FEATURE]' +labels: 'enhancement, untriaged' assignees: '' --- +**Is your feature request related to a problem?** +A clear and concise description of what the problem is, e.g. _I'm always frustrated when [...]_ -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** +**What solution would you like?** A clear and concise description of what you want to happen. -**Describe alternatives you've considered** +**What alternatives have you considered?** A clear and concise description of any alternative solutions or features you've considered. -**Additional context** +**Do you have any additional context?** Add any other context or screenshots about the feature request here. \ No newline at end of file From c3794c4768669d649f7a185163be7b85c6593c3c Mon Sep 17 00:00:00 2001 From: Aditya Jindal <13850971+aditjind@users.noreply.github.com> Date: Thu, 7 Apr 2022 15:51:50 -0700 Subject: [PATCH 051/149] Addition of NotificationEvent to SendNotificationResponse and Removal of NotificationID (#156) * Removal of NotificationEvent Request, Response , Search Results Addition of NotificationEventDoc and Removal of NotificationEventInfo Addition of NotificationEventDocTests Signed-off-by: Jindal * Removing NotificationEventDoc and NotificationEventDocTests Signed-off-by: Jindal Co-authored-by: Jindal --- .../action/SendNotificationResponse.kt | 43 ++++----------- .../NotificationsPluginInterfaceTests.kt | 14 ++++- .../action/SendNotificationResponseTests.kt | 52 ++++++++++++++----- 3 files changed, 61 insertions(+), 48 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponse.kt index 96599b3a..8967bdd9 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponse.kt @@ -10,19 +10,16 @@ import org.opensearch.common.io.stream.Writeable import org.opensearch.common.xcontent.ToXContent import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.common.xcontent.XContentParser -import org.opensearch.common.xcontent.XContentParserUtils -import org.opensearch.commons.notifications.NotificationConstants.EVENT_ID_TAG -import org.opensearch.commons.utils.logger +import org.opensearch.commons.notifications.model.NotificationEvent import java.io.IOException /** * Action Response for send notification. */ class SendNotificationResponse : BaseResponse { - val notificationId: String + val notificationEvent: NotificationEvent companion object { - private val log by logger(SendNotificationResponse::class.java) /** * reader to create instance of class from writable. @@ -36,35 +33,16 @@ class SendNotificationResponse : BaseResponse { @JvmStatic @Throws(IOException::class) fun parse(parser: XContentParser): SendNotificationResponse { - var notificationId: String? = null - - XContentParserUtils.ensureExpectedToken( - XContentParser.Token.START_OBJECT, - parser.currentToken(), - parser - ) - while (parser.nextToken() != XContentParser.Token.END_OBJECT) { - val fieldName = parser.currentName() - parser.nextToken() - when (fieldName) { - EVENT_ID_TAG -> notificationId = parser.text() - else -> { - parser.skipChildren() - log.info("Unexpected field: $fieldName, while parsing SendNotificationResponse") - } - } - } - notificationId ?: throw IllegalArgumentException("$EVENT_ID_TAG field absent") - return SendNotificationResponse(notificationId) + return SendNotificationResponse(NotificationEvent.parse(parser)) } } /** * constructor for creating the class - * @param configId the id of the created notification configuration + * @param notificationEvent the id of the created notification configuration */ - constructor(configId: String) { - this.notificationId = configId + constructor(notificationEvent: NotificationEvent) { + this.notificationEvent = notificationEvent } /** @@ -72,7 +50,7 @@ class SendNotificationResponse : BaseResponse { */ @Throws(IOException::class) constructor(input: StreamInput) : super(input) { - notificationId = input.readString() + notificationEvent = NotificationEvent(input) } /** @@ -80,16 +58,13 @@ class SendNotificationResponse : BaseResponse { */ @Throws(IOException::class) override fun writeTo(output: StreamOutput) { - output.writeString(notificationId) + notificationEvent.writeTo(output) } /** * {@inheritDoc} */ override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { - builder!! - return builder.startObject() - .field(EVENT_ID_TAG, notificationId) - .endObject() + return notificationEvent.toXContent(builder, params) } } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt index 556c35b6..61cbca7a 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt @@ -40,10 +40,13 @@ import org.opensearch.commons.notifications.model.Channel import org.opensearch.commons.notifications.model.ChannelList import org.opensearch.commons.notifications.model.ChannelMessage import org.opensearch.commons.notifications.model.ConfigType +import org.opensearch.commons.notifications.model.DeliveryStatus import org.opensearch.commons.notifications.model.EventSource +import org.opensearch.commons.notifications.model.EventStatus import org.opensearch.commons.notifications.model.NotificationConfig import org.opensearch.commons.notifications.model.NotificationConfigInfo import org.opensearch.commons.notifications.model.NotificationConfigSearchResult +import org.opensearch.commons.notifications.model.NotificationEvent import org.opensearch.commons.notifications.model.SeverityType import org.opensearch.commons.notifications.model.Slack import org.opensearch.rest.RestStatus @@ -180,7 +183,16 @@ internal class NotificationsPluginInterfaceTests { null ) - val response = SendNotificationResponse("configId") + val sampleStatus = EventStatus( + "config_id", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("404", "invalid recipient") + ) + + val sampleEvent = NotificationEvent(notificationInfo, listOf(sampleStatus)) + + val response = SendNotificationResponse(sampleEvent) val listener: ActionListener = mock(ActionListener::class.java) as ActionListener diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponseTests.kt index ca7f789c..4d828998 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponseTests.kt @@ -5,9 +5,15 @@ package org.opensearch.commons.notifications.action import com.fasterxml.jackson.core.JsonParseException +import org.junit.Test import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import org.opensearch.commons.notifications.model.ConfigType +import org.opensearch.commons.notifications.model.DeliveryStatus +import org.opensearch.commons.notifications.model.EventSource +import org.opensearch.commons.notifications.model.EventStatus +import org.opensearch.commons.notifications.model.NotificationEvent +import org.opensearch.commons.notifications.model.SeverityType import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject @@ -16,25 +22,29 @@ internal class SendNotificationResponseTests { @Test fun `Create response serialize and deserialize transport object should be equal`() { - val configResponse = SendNotificationResponse("sample_notification_id") - val recreatedObject = recreateObject(configResponse) { SendNotificationResponse(it) } - assertEquals(configResponse.notificationId, recreatedObject.notificationId) + + val sampleEvent = getSampleEvent() + + val recreatedObject = recreateObject(sampleEvent) { SendNotificationResponse(it) } + assertEquals(sampleEvent, recreatedObject) } @Test fun `Create response serialize and deserialize using json object should be equal`() { - val configResponse = SendNotificationResponse("sample_notification_id") - val jsonString = getJsonString(configResponse) + + val sampleEvent = getSampleEvent() + + val jsonString = getJsonString(sampleEvent) val recreatedObject = createObjectFromJsonString(jsonString) { SendNotificationResponse.parse(it) } - assertEquals(configResponse.notificationId, recreatedObject.notificationId) + assertEquals(sampleEvent, recreatedObject) } @Test fun `Create response should deserialize json object using parser`() { - val notificationId = "sample_notification_id" - val jsonString = "{\"event_id\":\"$notificationId\"}" + val sampleEvent = getSampleEvent() + val jsonString = "{\"event_id\":\"$sampleEvent\"}" val recreatedObject = createObjectFromJsonString(jsonString) { SendNotificationResponse.parse(it) } - assertEquals(notificationId, recreatedObject.notificationId) + assertEquals(sampleEvent, recreatedObject) } @Test @@ -55,16 +65,32 @@ internal class SendNotificationResponseTests { @Test fun `Create response should safely ignore extra field in json object`() { - val notificationId = "sample_notification_id" + val sampleEvent = getSampleEvent() val jsonString = """ { - "event_id":"$notificationId", + "event_id":"$sampleEvent", "extra_field_1":["extra", "value"], "extra_field_2":{"extra":"value"}, "extra_field_3":"extra value 3" } """.trimIndent() val recreatedObject = createObjectFromJsonString(jsonString) { SendNotificationResponse.parse(it) } - assertEquals(notificationId, recreatedObject.notificationId) + assertEquals(sampleEvent, recreatedObject) + } + + private fun getSampleEvent(): NotificationEvent { + val sampleEventSource = EventSource( + "title", + "reference_id", + severity = SeverityType.INFO + ) + val sampleStatus = EventStatus( + "config_id", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("404", "invalid recipient") + ) + + return NotificationEvent(sampleEventSource, listOf(sampleStatus)) } } From 14e76ecedde1aac2fae4595ea68d333749295753 Mon Sep 17 00:00:00 2001 From: Saurabh Singh Date: Wed, 13 Apr 2022 23:00:48 -0700 Subject: [PATCH 052/149] Remove JDK 14 (#159) Signed-off-by: Saurabh Singh --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69e7573d..5e017740 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,6 @@ jobs: matrix: java: - 11 - - 14 - 17 name: Build and Test From 17bb37c0809f240e85fb305f827c34a8e0343fc3 Mon Sep 17 00:00:00 2001 From: "Daniel Doubrovkine (dB.)" Date: Fri, 15 Apr 2022 14:06:06 -0400 Subject: [PATCH 053/149] Incremented version to 2.0-rc1. (#160) Signed-off-by: dblock --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index cf3ca5b9..b3680fcd 100644 --- a/build.gradle +++ b/build.gradle @@ -6,9 +6,9 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "2.0.0-alpha1-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.0.0-rc1-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") - buildVersionQualifier = System.getProperty("build.version_qualifier", "alpha1") + buildVersionQualifier = System.getProperty("build.version_qualifier", "rc1") kotlin_version = System.getProperty("kotlin.version", "1.6.10") } From a80b8b1020c79bf0813b256ea9b27b489d72a6c3 Mon Sep 17 00:00:00 2001 From: Mohammad Qureshi <47198598+qreshi@users.noreply.github.com> Date: Sat, 16 Apr 2022 14:24:12 -0700 Subject: [PATCH 054/149] Support sending email message via Notifications passthrough API (#158) * Add legacy email support as a legacy Destination type Signed-off-by: Mohammad Qureshi <47198598+qreshi@users.noreply.github.com> * Add accountName to LegacyEmailMessage Signed-off-by: Mohammad Qureshi <47198598+qreshi@users.noreply.github.com> --- .../message/LegacyCustomWebhookMessage.java | 5 +- .../message/LegacyDestinationType.java | 3 +- .../message/LegacyEmailMessage.java | 234 ++++++++++++++ .../LegacyPublishNotificationRequest.kt | 2 + .../notifications/model/NotificationEvent.kt | 12 + .../message/LegacyEmailMessageTest.java | 295 ++++++++++++++++++ 6 files changed, 548 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/opensearch/commons/destination/message/LegacyEmailMessage.java create mode 100644 src/test/java/org/opensearch/commons/destination/message/LegacyEmailMessageTest.java diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java index d0421ca9..dbc4b7df 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java @@ -236,8 +236,9 @@ public String getMessage() { @Override public void writeTo(StreamOutput streamOutput) throws IOException { super.writeTo(streamOutput); - // Making LegacyCustomWebhookMessage streamable is purely to support the new pass through API from ISM -> Notification plugin - // and it only supports LegacyCustomWebhookMessage when the url is already constructed by ISM. + // Making LegacyCustomWebhookMessage streamable is purely to support the new pass through API from Alerting/ISM -> Notification + // plugin + // and it only supports LegacyCustomWebhookMessage when the url is already constructed by Alerting/ISM. if (Strings.isNullOrEmpty(getUrl())) { throw new IllegalStateException("Cannot use LegacyCustomWebhookMessage across transport wire without defining full url."); } diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java b/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java index 1bad1029..f5086c27 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java @@ -11,5 +11,6 @@ public enum LegacyDestinationType { LEGACY_CHIME, LEGACY_SLACK, - LEGACY_CUSTOM_WEBHOOK + LEGACY_CUSTOM_WEBHOOK, + LEGACY_EMAIL } diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyEmailMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacyEmailMessage.java new file mode 100644 index 00000000..01810868 --- /dev/null +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyEmailMessage.java @@ -0,0 +1,234 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.commons.destination.message; + +import java.io.IOException; +import java.net.URI; +import java.util.List; + +import org.opensearch.common.Strings; +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.commons.notifications.model.MethodType; + +/** + * This class holds the content of an CustomWebhook message + */ +public class LegacyEmailMessage extends LegacyBaseMessage { + + private final String accountName; + private final String host; + private final int port; + private final String method; + private final String from; + private final List recipients; + private final String subject; + private final String message; + + private LegacyEmailMessage( + final String destinationName, + final String accountName, + final String host, + final Integer port, + final String method, + final String from, + final List recipients, + final String subject, + final String message + ) { + super(LegacyDestinationType.LEGACY_EMAIL, destinationName, message); + + if (Strings.isNullOrEmpty(message)) { + throw new IllegalArgumentException("Message content is missing"); + } + + if (Strings.isNullOrEmpty(accountName)) { + throw new IllegalArgumentException("Account name should be provided"); + } + + if (Strings.isNullOrEmpty(host)) { + throw new IllegalArgumentException("Host name should be provided"); + } + + if (Strings.isNullOrEmpty(from)) { + throw new IllegalArgumentException("From address should be provided"); + } + + if (recipients == null || recipients.isEmpty()) { + throw new IllegalArgumentException("List of recipients should be provided"); + } + + this.message = message; + this.accountName = accountName; + this.host = host; + this.port = port == null ? 25 : port; + + if (Strings.isNullOrEmpty(method)) { + // Default to "none" + this.method = "none"; + } else if (!MethodType.NONE.toString().equals(method) + && !MethodType.SSL.toString().equals(method) + && !MethodType.START_TLS.toString().equals(method)) { + throw new IllegalArgumentException("Invalid method supplied. Only none, ssl and start_tls are allowed"); + } else { + this.method = method; + } + + this.from = from; + this.recipients = recipients; + this.subject = Strings.isNullOrEmpty(subject) ? destinationName : subject; + } + + public LegacyEmailMessage(StreamInput streamInput) throws IOException { + super(streamInput); + this.message = super.getMessageContent(); + this.accountName = streamInput.readString(); + this.host = streamInput.readString(); + this.port = streamInput.readInt(); + this.method = streamInput.readString(); + this.from = streamInput.readString(); + this.recipients = streamInput.readStringList(); + this.subject = streamInput.readString(); + } + + @Override + public String toString() { + return "DestinationType: " + + getChannelType() + + ", DestinationName:" + + destinationName + + ", AccountName:" + + accountName + + ", From: " + + from + + ", Host: " + + host + + ", Port: " + + port + + ", Method: " + + method + + ", Subject: <...>" + + ", Message: <...>"; + } + + public static class Builder { + private final String destinationName; + private String accountName; + private String host; + private Integer port; + private String method; + private String from; + private List recipients; + private String subject; + private String message; + + public Builder(String destinationName) { + this.destinationName = destinationName; + } + + public LegacyEmailMessage.Builder withAccountName(String accountName) { + this.accountName = accountName; + return this; + } + + public LegacyEmailMessage.Builder withHost(String host) { + this.host = host; + return this; + } + + public LegacyEmailMessage.Builder withPort(Integer port) { + this.port = port; + return this; + } + + public LegacyEmailMessage.Builder withMethod(String method) { + this.method = method; + return this; + } + + public LegacyEmailMessage.Builder withFrom(String from) { + this.from = from; + return this; + } + + public LegacyEmailMessage.Builder withRecipients(List recipients) { + this.recipients = recipients; + return this; + } + + public LegacyEmailMessage.Builder withSubject(String subject) { + this.subject = subject; + return this; + } + + public LegacyEmailMessage.Builder withMessage(String message) { + this.message = message; + return this; + } + + public LegacyEmailMessage build() { + return new LegacyEmailMessage( + this.destinationName, + this.accountName, + this.host, + this.port, + this.method, + this.from, + this.recipients, + this.subject, + this.message + ); + } + } + + public String getAccountName() { + return accountName; + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + public String getMethod() { + return method; + } + + public String getFrom() { + return from; + } + + public List getRecipients() { + return recipients; + } + + public String getSubject() { + return subject; + } + + public String getMessage() { + return message; + } + + public URI getUri() { + return buildUri(null, null, host, port, null, null); + } + + @Override + public void writeTo(StreamOutput streamOutput) throws IOException { + super.writeTo(streamOutput); + streamOutput.writeString(accountName); + streamOutput.writeString(host); + streamOutput.writeInt(port); + streamOutput.writeString(method); + streamOutput.writeString(from); + streamOutput.writeStringCollection(recipients); + streamOutput.writeString(subject); + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt index 508815a8..211e2076 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt @@ -14,6 +14,7 @@ import org.opensearch.commons.destination.message.LegacyBaseMessage import org.opensearch.commons.destination.message.LegacyChimeMessage import org.opensearch.commons.destination.message.LegacyCustomWebhookMessage import org.opensearch.commons.destination.message.LegacyDestinationType +import org.opensearch.commons.destination.message.LegacyEmailMessage import org.opensearch.commons.destination.message.LegacySlackMessage import java.io.IOException @@ -50,6 +51,7 @@ class LegacyPublishNotificationRequest : ActionRequest { LegacyDestinationType.LEGACY_CHIME -> LegacyChimeMessage(input) LegacyDestinationType.LEGACY_CUSTOM_WEBHOOK -> LegacyCustomWebhookMessage(input) LegacyDestinationType.LEGACY_SLACK -> LegacySlackMessage(input) + LegacyDestinationType.LEGACY_EMAIL -> LegacyEmailMessage(input) } } diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt index a76f552d..6bc49d5f 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt @@ -8,9 +8,12 @@ import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.ToXContent.EMPTY_PARAMS import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentHelper import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.common.xcontent.XContentType import org.opensearch.commons.notifications.NotificationConstants.EVENT_SOURCE_TAG import org.opensearch.commons.notifications.NotificationConstants.STATUS_LIST_TAG import org.opensearch.commons.utils.logger @@ -102,4 +105,13 @@ data class NotificationEvent( .field(STATUS_LIST_TAG, statusList) .endObject() } + + // Overriding toString so consuming plugins can log/output this from the sendNotification response if needed + override fun toString(): String { + return try { + XContentHelper.toXContent(this, XContentType.JSON, EMPTY_PARAMS, true).utf8ToString() + } catch (e: IOException) { + super.toString() + " threw " + e.toString() + } + } } diff --git a/src/test/java/org/opensearch/commons/destination/message/LegacyEmailMessageTest.java b/src/test/java/org/opensearch/commons/destination/message/LegacyEmailMessageTest.java new file mode 100644 index 00000000..06625053 --- /dev/null +++ b/src/test/java/org/opensearch/commons/destination/message/LegacyEmailMessageTest.java @@ -0,0 +1,295 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.commons.destination.message; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.common.io.stream.StreamInput; + +public class LegacyEmailMessageTest { + + @Test + public void testBuildingLegacyEmailMessage() { + LegacyEmailMessage message = new LegacyEmailMessage.Builder("email") + .withAccountName("test_email") + .withHost("smtp.test.com") + .withPort(123) + .withMethod("none") + .withFrom("test@email.com") + .withRecipients(Arrays.asList("test2@email.com", "test3@email.com")) + .withSubject("Test Subject") + .withMessage("Hello world") + .build(); + + assertEquals("email", message.destinationName); + assertEquals(LegacyDestinationType.LEGACY_EMAIL, message.getChannelType()); + assertEquals("test_email", message.getAccountName()); + assertEquals("smtp.test.com", message.getHost()); + assertEquals(123, message.getPort()); + assertEquals("none", message.getMethod()); + assertEquals("test@email.com", message.getFrom()); + assertEquals(Arrays.asList("test2@email.com", "test3@email.com"), message.getRecipients()); + assertEquals("Test Subject", message.getSubject()); + assertEquals("Hello world", message.getMessage()); + } + + @Test + public void testRoundTrippingLegacyEmailMessage() throws IOException { + LegacyEmailMessage message = new LegacyEmailMessage.Builder("email") + .withAccountName("test_email") + .withHost("smtp.test.com") + .withPort(123) + .withMethod("none") + .withFrom("test@email.com") + .withRecipients(Arrays.asList("test2@email.com", "test3@email.com")) + .withSubject("Test Subject") + .withMessage("Hello world") + .build(); + BytesStreamOutput out = new BytesStreamOutput(); + message.writeTo(out); + + StreamInput in = StreamInput.wrap(out.bytes().toBytesRef().bytes); + LegacyEmailMessage newMessage = new LegacyEmailMessage(in); + + assertEquals(newMessage.destinationName, message.destinationName); + assertEquals(newMessage.getChannelType(), message.getChannelType()); + assertEquals(newMessage.getAccountName(), message.getAccountName()); + assertEquals(newMessage.getHost(), message.getHost()); + assertEquals(newMessage.getPort(), message.getPort()); + assertEquals(newMessage.getMethod(), message.getMethod()); + assertEquals(newMessage.getFrom(), message.getFrom()); + assertEquals(newMessage.getRecipients(), message.getRecipients()); + assertEquals(newMessage.getSubject(), message.getSubject()); + assertEquals(newMessage.getMessage(), message.getMessage()); + } + + @Test + public void testContentMissingMessage() { + try { + new LegacyEmailMessage.Builder("email") + .withAccountName("test_email") + .withHost("smtp.test.com") + .withPort(123) + .withMethod("none") + .withFrom("test@email.com") + .withRecipients(Arrays.asList("test2@email.com", "test3@email.com")) + .withSubject("Test Subject") + .build(); + fail("Building legacy email message without message should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Message content is missing", e.getMessage()); + } + } + + @Test + public void testMissingDestinationName() { + try { + new LegacyEmailMessage.Builder(null) + .withAccountName("test_email") + .withHost("smtp.test.com") + .withPort(123) + .withMethod("none") + .withFrom("test@email.com") + .withRecipients(Arrays.asList("test2@email.com", "test3@email.com")) + .withSubject("Test Subject") + .withMessage("Hello world") + .build(); + fail("Building legacy email message with null destination name should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Channel name must be defined", e.getMessage()); + } + } + + @Test + public void testUnsupportedMethods() { + try { + new LegacyEmailMessage.Builder("email") + .withAccountName("test_email") + .withHost("smtp.test.com") + .withPort(123) + .withMethod("unsupported") + .withFrom("test@email.com") + .withRecipients(Arrays.asList("test2@email.com", "test3@email.com")) + .withSubject("Test Subject") + .withMessage("Hello world") + .build(); + fail("Building legacy email message with unsupported method should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Invalid method supplied. Only none, ssl and start_tls are allowed", e.getMessage()); + } + } + + @Test + public void testAccountNameMissingOrEmpty() { + try { + new LegacyEmailMessage.Builder("email") + .withHost("smtp.test.com") + .withPort(123) + .withMethod("none") + .withFrom("test@email.com") + .withRecipients(Arrays.asList("test2@email.com", "test3@email.com")) + .withSubject("Test Subject") + .withMessage("Hello world") + .build(); + fail("Building legacy email message with missing account name should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Account name should be provided", e.getMessage()); + } + + try { + new LegacyEmailMessage.Builder("email") + .withAccountName("") + .withHost("smtp.test.com") + .withPort(123) + .withMethod("none") + .withFrom("test@email.com") + .withRecipients(Arrays.asList("test2@email.com", "test3@email.com")) + .withSubject("Test Subject") + .withMessage("Hello world") + .build(); + fail("Building legacy email message with empty account name should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Account name should be provided", e.getMessage()); + } + } + + @Test + public void testHostMissingOrEmpty() { + try { + new LegacyEmailMessage.Builder("email") + .withAccountName("test_email") + .withPort(123) + .withMethod("none") + .withFrom("test@email.com") + .withRecipients(Arrays.asList("test2@email.com", "test3@email.com")) + .withSubject("Test Subject") + .withMessage("Hello world") + .build(); + fail("Building legacy email message with missing host should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Host name should be provided", e.getMessage()); + } + + try { + new LegacyEmailMessage.Builder("email") + .withAccountName("test_email") + .withHost("") + .withPort(123) + .withMethod("none") + .withFrom("test@email.com") + .withRecipients(Arrays.asList("test2@email.com", "test3@email.com")) + .withSubject("Test Subject") + .withMessage("Hello world") + .build(); + fail("Building legacy email message with empty host should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Host name should be provided", e.getMessage()); + } + } + + @Test + public void testFromMissingOrEmpty() { + try { + new LegacyEmailMessage.Builder("email") + .withAccountName("test_email") + .withHost("smtp.test.com") + .withPort(123) + .withMethod("none") + .withRecipients(Arrays.asList("test2@email.com", "test3@email.com")) + .withSubject("Test Subject") + .withMessage("Hello world") + .build(); + fail("Building legacy email message with missing from should fail"); + } catch (IllegalArgumentException e) { + assertEquals("From address should be provided", e.getMessage()); + } + + try { + new LegacyEmailMessage.Builder("email") + .withAccountName("test_email") + .withHost("smtp.test.com") + .withPort(123) + .withMethod("none") + .withFrom("") + .withRecipients(Arrays.asList("test2@email.com", "test3@email.com")) + .withSubject("Test Subject") + .withMessage("Hello world") + .build(); + fail("Building legacy email message with empty from should fail"); + } catch (IllegalArgumentException e) { + assertEquals("From address should be provided", e.getMessage()); + } + } + + @Test + public void testRecipientsMissingOrEmpty() { + try { + new LegacyEmailMessage.Builder("email") + .withAccountName("test_email") + .withHost("smtp.test.com") + .withPort(123) + .withMethod("none") + .withFrom("test@email.com") + .withSubject("Test Subject") + .withMessage("Hello world") + .build(); + fail("Building legacy email message with missing recipients should fail"); + } catch (IllegalArgumentException e) { + assertEquals("List of recipients should be provided", e.getMessage()); + } + + try { + new LegacyEmailMessage.Builder("email") + .withAccountName("test_email") + .withHost("smtp.test.com") + .withPort(123) + .withMethod("none") + .withFrom("test@email.com") + .withRecipients(List.of()) + .withSubject("Test Subject") + .withMessage("Hello world") + .build(); + fail("Building legacy email message with empty recipients should fail"); + } catch (IllegalArgumentException e) { + assertEquals("List of recipients should be provided", e.getMessage()); + } + } + + @Test + public void testSubjectDefaultsToDestinationNameWhenMissingOrEmpty() { + LegacyEmailMessage message = new LegacyEmailMessage.Builder("email") + .withAccountName("test_email") + .withHost("smtp.test.com") + .withPort(123) + .withMethod("none") + .withFrom("test@email.com") + .withRecipients(Arrays.asList("test2@email.com", "test3@email.com")) + .withMessage("Hello world") + .build(); + + assertEquals("email", message.getSubject()); + + message = new LegacyEmailMessage.Builder("email") + .withAccountName("test_email") + .withHost("smtp.test.com") + .withPort(123) + .withMethod("none") + .withFrom("test@email.com") + .withRecipients(Arrays.asList("test2@email.com", "test3@email.com")) + .withSubject("") + .withMessage("Hello world") + .build(); + + assertEquals("email", message.getSubject()); + } +} From 39bd777641a23ed1fa94a56fae2e001028eca58b Mon Sep 17 00:00:00 2001 From: Saurabh Singh Date: Mon, 25 Apr 2022 15:37:16 -0700 Subject: [PATCH 055/149] Add release notes for version 2.0.0.0-rc1 (#162) Signed-off-by: Saurabh Singh --- ...-common-utils.release-notes-2.0.0.0-rc1.md | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 release-notes/opensearch-common-utils.release-notes-2.0.0.0-rc1.md diff --git a/release-notes/opensearch-common-utils.release-notes-2.0.0.0-rc1.md b/release-notes/opensearch-common-utils.release-notes-2.0.0.0-rc1.md new file mode 100644 index 00000000..84418524 --- /dev/null +++ b/release-notes/opensearch-common-utils.release-notes-2.0.0.0-rc1.md @@ -0,0 +1,29 @@ +## Version 2.0.0.0-rc1 2022-04-25 + +Compatible with OpenSearch 2.0.0-rc1 + +### Enhancements + + * Add SQL/PPL transport request/response models for SQL plugin ([#155](https://github.com/opensearch-project/common-utils/pull/155)) + * Support sending email message via Notifications pass-through API ([#158](https://github.com/opensearch-project/common-utils/pull/158)) + +### Infrastructure + + * Upgrade gradle artifacts to 7.3.3 ([#135](https://github.com/opensearch-project/common-utils/pull/135) + * Update common-utils to depend on the OpenSearch repositories plugin ([#137](https://github.com/opensearch-project/common-utils/pull/137)) + * Add sign-off option for version workflow PR ([#143](https://github.com/opensearch-project/common-utils/pull/143)) + * Add qualifier default to alpha1 in build.gradle ([#151](https://github.com/opensearch-project/common-utils/pull/151)) + * Update issue templates from github for bugs and features ([#154](https://github.com/opensearch-project/common-utils/pull/154)) + * Remove support for JDK 14 ([#159](https://github.com/opensearch-project/common-utils/pull/159)) + +### Refactoring + + * Remove feature and feature_list usage for Notifications ([#136](https://github.com/opensearch-project/common-utils/pull/136)) + * Rename references for Get Channels API for Notifications ([#140](https://github.com/opensearch-project/common-utils/pull/140)) + * Remove allowedConfigFeatureList from GetPluginFeaturesResponse for Notifications ([#144](https://github.com/opensearch-project/common-utils/pull/144)) + * Remove NotificationEvent Request, Response and SearchResults ([#153](https://github.com/opensearch-project/common-utils/pull/153)) + * Add NotificationEvent to SendNotificationResponse and Removal of NotificationID ([#156](https://github.com/opensearch-project/common-utils/pull/156)) + +### Documentation + + * Add release notes for version 2.0.0-rc1 ([#162](https://github.com/opensearch-project/common-utils/pull/162)) From 389bb3c27f88720995846be2ff95afee19b16cd1 Mon Sep 17 00:00:00 2001 From: Aditya Jindal <13850971+aditjind@users.noreply.github.com> Date: Thu, 5 May 2022 19:23:49 -0700 Subject: [PATCH 056/149] Removing RC1 as the qualifier from Common Utils (#168) Signed-off-by: Aditya Jindal --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index b3680fcd..eb2e8bc9 100644 --- a/build.gradle +++ b/build.gradle @@ -6,9 +6,9 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "2.0.0-rc1-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.0.0-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") - buildVersionQualifier = System.getProperty("build.version_qualifier", "rc1") + buildVersionQualifier = System.getProperty("build.version_qualifier", "") kotlin_version = System.getProperty("kotlin.version", "1.6.10") } From 1eef82c0d9a8259e2bdbd2bf23194869f93a2c48 Mon Sep 17 00:00:00 2001 From: Subhobrata Dey Date: Mon, 9 May 2022 14:44:02 -0700 Subject: [PATCH 057/149] add code coverage badge (#169) Signed-off-by: Subhobrata Dey --- .codecov.yml | 12 ++++++++++++ .github/workflows/ci.yml | 3 +-- 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 00000000..e711a04d --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,12 @@ +codecov: + require_ci_to_pass: yes + +coverage: + precision: 2 + round: down + range: "75...100" + status: + project: + default: + target: 75% # the required coverage value + threshold: 1% # the leniency in hitting the target \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e017740..0bcc5e2c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,5 @@ jobs: - name: Upload Coverage Report uses: codecov/codecov-action@v1 with: - file: ./build/reports/jacoco/test/jacocoTestReport.xml - flags: plugin + token: ${{ secrets.CODECOV_TOKEN }} From d36924d5d7b218d2c70f8492c9dff6023560abe1 Mon Sep 17 00:00:00 2001 From: Mohammad Qureshi <47198598+qreshi@users.noreply.github.com> Date: Wed, 11 May 2022 16:45:44 -0700 Subject: [PATCH 058/149] Change BaseModel to extend ToXContentObject instead of ToXContent (#173) Signed-off-by: Mohammad Qureshi <47198598+qreshi@users.noreply.github.com> --- .../org/opensearch/commons/notifications/model/BaseModel.kt | 4 ++-- .../commons/notifications/model/NotificationEvent.kt | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/BaseModel.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/BaseModel.kt index 5ead5594..056fcc6e 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/BaseModel.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/BaseModel.kt @@ -5,9 +5,9 @@ package org.opensearch.commons.notifications.model import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.ToXContentObject /** * interface for representing objects. */ -interface BaseModel : Writeable, ToXContent +interface BaseModel : Writeable, ToXContentObject diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt index 6bc49d5f..2ab5426b 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt @@ -111,6 +111,7 @@ data class NotificationEvent( return try { XContentHelper.toXContent(this, XContentType.JSON, EMPTY_PARAMS, true).utf8ToString() } catch (e: IOException) { + log.debug("Failed to convert NotificationEvent to string", e) super.toString() + " threw " + e.toString() } } From 4557ed1ee9c05ab8baee9424f74e0c9e7e5aa812 Mon Sep 17 00:00:00 2001 From: Subhobrata Dey Date: Mon, 16 May 2022 15:57:04 -0700 Subject: [PATCH 059/149] add code coverage badge (#171) --- README.md | 1 + build-tools/opensearchplugin-coverage.gradle | 49 ++++++++++++++++++++ build.gradle | 1 + 3 files changed, 51 insertions(+) create mode 100644 build-tools/opensearchplugin-coverage.gradle diff --git a/README.md b/README.md index fc95f76e..b900ee32 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ +[![codecov](https://codecov.io/gh/opensearch-project/common-utils/branch/main/graph/badge.svg?token=CQ01D9MNQL)](https://codecov.io/gh/opensearch-project/common-utils) - [OpenSearch Common Utils](#opensearch-common-utils) - [Contributing](#contributing) diff --git a/build-tools/opensearchplugin-coverage.gradle b/build-tools/opensearchplugin-coverage.gradle new file mode 100644 index 00000000..2b4d21c0 --- /dev/null +++ b/build-tools/opensearchplugin-coverage.gradle @@ -0,0 +1,49 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * OpenSearch Plugin build tools don't work with the Gradle Jacoco Plugin to report coverage out of the box. + * https://github.com/elastic/elasticsearch/issues/28867. + * + * This code sets up coverage reporting manually for OpenSearch plugin tests. This is complicated because: + * 1. The OpenSearch integTest Task doesn't implement Gradle's JavaForkOptions so we have to manually start the jacoco agent with the test JVM + * 2. The cluster nodes are stopped using 'kill -9' which means jacoco can't dump it's execution output to a file on VM shutdown + * 3. The Java Security Manager prevents JMX from writing execution output to the file. + * + * To workaround these we start the cluster with jmx enabled and then use Jacoco's JMX MBean to get the execution data before the + * cluster is stopped and dump it to a file. Luckily our current security policy seems to allow this. This will also probably + * break if there are multiple nodes in the integTestCluster. But for now... it sorta works. + */ +apply plugin: 'jacoco' + +// Get gradle to generate the required jvm agent arg for us using a dummy tasks of type Test. Unfortunately Elastic's +// testing tasks don't derive from Test so the jacoco plugin can't do this automatically. +def jacocoDir = "${buildDir}/jacoco" +task dummyTest(type: Test) { + enabled = false + workingDir = file("/") // Force absolute path to jacoco agent jar + jacoco { + destinationFile = file("${jacocoDir}/test.exec") + destinationFile.parentFile.mkdirs() + jmx = true + } +} + +jacocoTestReport { + dependsOn test + executionData dummyTest.jacoco.destinationFile + getSourceDirectories().from(sourceSets.main.allSource) + getClassDirectories().from(sourceSets.main.output) + reports { + html.enabled = true // human readable + xml.enabled = true // for coverlay + } +} + +project.gradle.projectsEvaluated { + jacocoTestReport.dependsOn test +} + +check.dependsOn jacocoTestReport diff --git a/build.gradle b/build.gradle index eb2e8bc9..67e4bef1 100644 --- a/build.gradle +++ b/build.gradle @@ -63,6 +63,7 @@ apply plugin: 'io.gitlab.arturbosch.detekt' apply plugin: 'org.jetbrains.kotlin.jvm' apply plugin: 'org.jetbrains.kotlin.plugin.allopen' apply plugin: 'opensearch.repositories' +apply from: 'build-tools/opensearchplugin-coverage.gradle' configurations { ktlint From 1d0ee34f8cb4d5e859836f962ac0022a8dee368e Mon Sep 17 00:00:00 2001 From: Subhobrata Dey Date: Mon, 16 May 2022 21:46:53 -0700 Subject: [PATCH 060/149] fix code coverage badge (#175) Signed-off-by: Subhobrata Dey --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b900ee32..47a0b97b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ -[![codecov](https://codecov.io/gh/opensearch-project/common-utils/branch/main/graph/badge.svg?token=CQ01D9MNQL)](https://codecov.io/gh/opensearch-project/common-utils) + + + - [OpenSearch Common Utils](#opensearch-common-utils) - [Contributing](#contributing) From e8b991ee0787f7afa16c33383a1454847d88115f Mon Sep 17 00:00:00 2001 From: Subhobrata Dey Date: Tue, 17 May 2022 11:02:52 -0700 Subject: [PATCH 061/149] fix code coverage badge (#176) Signed-off-by: Subhobrata Dey --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 47a0b97b..98ee4ce2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ +[![codecov](https://codecov.io/gh/opensearch-project/common-utils/branch/main/graph/badge.svg)](https://codecov.io/gh/opensearch-project/common-utils) + - - - - [OpenSearch Common Utils](#opensearch-common-utils) - [Contributing](#contributing) From a03453c99b3db82ea4dc579e48a8d8bb605c2ee5 Mon Sep 17 00:00:00 2001 From: Saurabh Singh Date: Wed, 18 May 2022 12:34:37 -0700 Subject: [PATCH 062/149] Add release notes for version 2.0.0.0 (#177) Signed-off-by: Saurabh Singh --- ...arch-common-utils.release-notes-2.0.0.0.md | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 release-notes/opensearch-common-utils.release-notes-2.0.0.0.md diff --git a/release-notes/opensearch-common-utils.release-notes-2.0.0.0.md b/release-notes/opensearch-common-utils.release-notes-2.0.0.0.md new file mode 100644 index 00000000..70295ccc --- /dev/null +++ b/release-notes/opensearch-common-utils.release-notes-2.0.0.0.md @@ -0,0 +1,32 @@ +## Version 2.0.0.0 2022-05-18 + +Compatible with OpenSearch 2.0.0 + +### Enhancements + + * Add SQL/PPL transport request/response models for SQL plugin ([#155](https://github.com/opensearch-project/common-utils/pull/155)) + * Support sending email message via Notifications pass-through API ([#158](https://github.com/opensearch-project/common-utils/pull/158)) + +### Infrastructure + + * Upgrade gradle artifacts to 7.3.3 ([#135](https://github.com/opensearch-project/common-utils/pull/135) + * Update common-utils to depend on the OpenSearch repositories plugin ([#137](https://github.com/opensearch-project/common-utils/pull/137)) + * Add sign-off option for version workflow PR ([#143](https://github.com/opensearch-project/common-utils/pull/143)) + * Add qualifier default to alpha1 in build.gradle ([#151](https://github.com/opensearch-project/common-utils/pull/151)) + * Update issue templates from github for bugs and features ([#154](https://github.com/opensearch-project/common-utils/pull/154)) + * Remove support for JDK 14 ([#159](https://github.com/opensearch-project/common-utils/pull/159)) + * Remove RC1 as the qualifier from Common Utils ([#168](https://github.com/opensearch-project/common-utils/pull/168)) + +### Refactoring + + * Remove feature and feature_list usage for Notifications ([#136](https://github.com/opensearch-project/common-utils/pull/136)) + * Rename references for Get Channels API for Notifications ([#140](https://github.com/opensearch-project/common-utils/pull/140)) + * Remove allowedConfigFeatureList from GetPluginFeaturesResponse for Notifications ([#144](https://github.com/opensearch-project/common-utils/pull/144)) + * Remove NotificationEvent Request, Response and SearchResults ([#153](https://github.com/opensearch-project/common-utils/pull/153)) + * Add NotificationEvent to SendNotificationResponse and Removal of NotificationID ([#156](https://github.com/opensearch-project/common-utils/pull/156)) + * Change BaseModel to extend ToXContentObject instead of ToXContent ([#173](https://github.com/opensearch-project/common-utils/pull/173)) + +### Documentation + + * Add release notes for version 2.0.0-rc1 ([#162](https://github.com/opensearch-project/common-utils/pull/162)) + * Add release notes for version 2.0.0.0 ([#177](https://github.com/opensearch-project/common-utils/pull/177)) From b0544208a851544e33a73912c3322f0591e9ad54 Mon Sep 17 00:00:00 2001 From: "Daniel Doubrovkine (dB.)" Date: Tue, 24 May 2022 14:02:00 -0400 Subject: [PATCH 063/149] Remove duplicate DCO workflow. (#181) Signed-off-by: dblock --- .github/workflows/dco.yml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 .github/workflows/dco.yml diff --git a/.github/workflows/dco.yml b/.github/workflows/dco.yml deleted file mode 100644 index 53ed5304..00000000 --- a/.github/workflows/dco.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Developer Certificate of Origin Check - -on: [pull_request] - -jobs: - check: - runs-on: ubuntu-latest - - steps: - - name: Get PR Commits - id: 'get-pr-commits' - uses: tim-actions/get-pr-commits@v1.1.0 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - name: DCO Check - uses: tim-actions/dco@v1.1.0 - with: - commits: ${{ steps.get-pr-commits.outputs.commits }} \ No newline at end of file From 26a4c3629cfea8e49d6b2fde80d32bfdddc74812 Mon Sep 17 00:00:00 2001 From: Ankit Kala Date: Thu, 23 Jun 2022 00:08:18 +0530 Subject: [PATCH 064/149] Bump up the version to 2.1 (#190) Signed-off-by: Ankit Kala --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 67e4bef1..280a92e7 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "2.0.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.1.0-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") buildVersionQualifier = System.getProperty("build.version_qualifier", "") kotlin_version = System.getProperty("kotlin.version", "1.6.10") From 5d96f3c2084f3ecfe263dea99397f1d9796e7cfc Mon Sep 17 00:00:00 2001 From: Subhobrata Dey Date: Wed, 22 Jun 2022 14:19:40 -0700 Subject: [PATCH 065/149] Upgrade gradle to 7.4.2 (#191) --- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 59821 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..41d9927a4d4fb3f96a785543079b8df6723c946b 100644 GIT binary patch delta 8958 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2e6e5897..aa991fce 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 79a2ae3bf06a2e7c759058e2036033ef87f47012 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Wed, 6 Jul 2022 15:28:18 -0700 Subject: [PATCH 066/149] Added 2.1 release notes. (#194) (#196) (cherry picked from commit 739d155a0f6ed03c83e5cb014abaef6432d8ca23) Signed-off-by: Subhobrata Dey Co-authored-by: Subhobrata Dey --- .../opensearch-common-utils.release-notes-2.1.0.0.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 release-notes/opensearch-common-utils.release-notes-2.1.0.0.md diff --git a/release-notes/opensearch-common-utils.release-notes-2.1.0.0.md b/release-notes/opensearch-common-utils.release-notes-2.1.0.0.md new file mode 100644 index 00000000..3bf4465f --- /dev/null +++ b/release-notes/opensearch-common-utils.release-notes-2.1.0.0.md @@ -0,0 +1,10 @@ +## Version 2.1.0.0 2022-07-06 + +Compatible with OpenSearch 2.1.0 + +### Maintenance +* Upgrade gradle to 7.4.2. ([#191](https://github.com/opensearch-project/common-utils/pull/191)) +* Bump up the version to 2.1. ([#190](https://github.com/opensearch-project/common-utils/pull/190)) + +### Documentation +* Added 2.1 release notes. ([#194](https://github.com/opensearch-project/common-utils/pull/194)) \ No newline at end of file From fe37126bf78ee336a91c0f5b2552eb97aec375f8 Mon Sep 17 00:00:00 2001 From: Mohammad Qureshi <47198598+qreshi@users.noreply.github.com> Date: Mon, 25 Jul 2022 10:02:57 -0700 Subject: [PATCH 067/149] Bump 2.x branch to 2.2 (#204) Signed-off-by: Mohammad Qureshi <47198598+qreshi@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 280a92e7..674ac630 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "2.1.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.2.0-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") buildVersionQualifier = System.getProperty("build.version_qualifier", "") kotlin_version = System.getProperty("kotlin.version", "1.6.10") From 7d531022af8f5c4ec3ef38194596b4f6ccd5bb6b Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Thu, 4 Aug 2022 14:11:16 -0700 Subject: [PATCH 068/149] Staging for version increment automation (#200) (#211) * Version increment automation Signed-off-by: pgodithi * Version increment automation: task rename updateVersion Signed-off-by: pgodithi (cherry picked from commit 366bf16496f43474a45c47b99bb4f91edc4109fb) Signed-off-by: prudhvigodithi Co-authored-by: Prudhvi Godithi --- build.gradle | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/build.gradle b/build.gradle index 674ac630..7849fd91 100644 --- a/build.gradle +++ b/build.gradle @@ -199,3 +199,15 @@ publishing { gradle.startParameter.setShowStacktrace(ShowStacktrace.ALWAYS) gradle.startParameter.setLogLevel(LogLevel.DEBUG) } + +// updateVersion: Task to auto increment to the next development iteration +task updateVersion { + onlyIf { System.getProperty('newVersion') } + doLast { + ext.newVersion = System.getProperty('newVersion') + println "Setting version to ${newVersion}." + // String tokenization to support -SNAPSHOT + // Include the required files that needs to be updated with new Version + ant.replaceregexp(file:'build.gradle', match: '"opensearch.version", "\\d.*"', replace: '"opensearch.version", "' + newVersion.tokenize('-')[0] + '-SNAPSHOT"', flags:'g', byline:true) + } +} \ No newline at end of file From ae064c5668f3d46ba8f47ad6e7ea23a14ede557b Mon Sep 17 00:00:00 2001 From: Saurabh Singh Date: Tue, 9 Aug 2022 11:43:50 -0700 Subject: [PATCH 069/149] Added 2.2 release notes. (#213) Signed-off-by: Saurabh Singh --- .../opensearch-common-utils.release-notes-2.2.0.0.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 release-notes/opensearch-common-utils.release-notes-2.2.0.0.md diff --git a/release-notes/opensearch-common-utils.release-notes-2.2.0.0.md b/release-notes/opensearch-common-utils.release-notes-2.2.0.0.md new file mode 100644 index 00000000..b77f7244 --- /dev/null +++ b/release-notes/opensearch-common-utils.release-notes-2.2.0.0.md @@ -0,0 +1,10 @@ +## Version 2.2.0.0 2022-08-09 + +Compatible with OpenSearch 2.2.0 + +### Infrastructure +* Execute version auto increment in staging ([#200](https://github.com/opensearch-project/common-utils/pull/200)) +* Bump up the version to 2.2. ([#204](https://github.com/opensearch-project/common-utils/pull/204)) + +### Documentation +* Added 2.2 release notes. ([#212](https://github.com/opensearch-project/common-utils/pull/212)) \ No newline at end of file From 53ed7f1e163d73fc1cd82fd01c22f56eb8f526b0 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Tue, 16 Aug 2022 13:11:02 -0700 Subject: [PATCH 070/149] Added 2.2 release notes. (#212) (#214) Signed-off-by: Saurabh Singh (cherry picked from commit b82ef4a63913b6ef5b72dc50b9e4c88ea85302a9) Co-authored-by: Saurabh Singh From dea1aed451e6bf6e7d0b11884955548217820f8d Mon Sep 17 00:00:00 2001 From: Ashish Agrawal Date: Tue, 23 Aug 2022 09:32:22 -0700 Subject: [PATCH 071/149] Upgrade version to 2.3 (#223) Signed-off-by: Ashish Agrawal Signed-off-by: Ashish Agrawal --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7849fd91..bd940e22 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "2.2.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.3.0-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") buildVersionQualifier = System.getProperty("build.version_qualifier", "") kotlin_version = System.getProperty("kotlin.version", "1.6.10") From 72a7261f56a9275ae2983c457a93de23dd3dd439 Mon Sep 17 00:00:00 2001 From: Subhobrata Dey Date: Wed, 7 Sep 2022 15:54:13 -0700 Subject: [PATCH 072/149] fix snakeyaml vulnerability issue by disabling detekt (#237) (#240) Signed-off-by: Subhobrata Dey --- build.gradle | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index bd940e22..2fa22931 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ buildscript { classpath "${opensearch_group}.gradle:build-tools:${opensearch_version}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlin_version}" classpath "org.jetbrains.kotlin:kotlin-allopen:${kotlin_version}" - classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.20.0-RC1" +// classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.20.0-RC1" } } @@ -59,7 +59,7 @@ apply plugin: 'jacoco' apply plugin: 'signing' apply plugin: 'maven-publish' apply plugin: 'com.github.johnrengelman.shadow' -apply plugin: 'io.gitlab.arturbosch.detekt' +// apply plugin: 'io.gitlab.arturbosch.detekt' apply plugin: 'org.jetbrains.kotlin.jvm' apply plugin: 'org.jetbrains.kotlin.plugin.allopen' apply plugin: 'opensearch.repositories' @@ -67,6 +67,7 @@ apply from: 'build-tools/opensearchplugin-coverage.gradle' configurations { ktlint + all*.exclude group: 'org.yaml', module: 'snakeyaml' } dependencies { @@ -103,10 +104,12 @@ spotless { eclipse().configFile rootProject.file('.eclipseformat.xml') } } -detekt { + +// TODO: enable detekt only when snakeyaml vulnerability is fixed +/*detekt { config = files("detekt.yml") buildUponDefaultConfig = true -} +}*/ task ktlint(type: JavaExec, group: "verification") { description = "Check Kotlin code style." From 587d9cb6d1b69df4033cfa6f2f441722341eba30 Mon Sep 17 00:00:00 2001 From: Subhobrata Dey Date: Wed, 7 Sep 2022 16:20:00 -0700 Subject: [PATCH 073/149] Move Alerting data models over to common-utils (#242) Signed-off-by: Subhobrata Dey --- build.gradle | 6 +- detekt.yml | 31 +- .../alerting/AlertingPluginInterface.kt | 62 +++ .../alerting/action/AlertingActions.kt | 14 + .../alerting/action/IndexMonitorRequest.kt | 59 +++ .../alerting/action/IndexMonitorResponse.kt | 64 +++ .../BucketSelectorExtAggregationBuilder.kt | 244 ++++++++++ .../BucketSelectorExtAggregator.kt | 155 ++++++ .../BucketSelectorExtFilter.kt | 140 ++++++ .../BucketSelectorIndices.kt | 68 +++ .../alerting/model/BucketLevelTrigger.kt | 143 ++++++ .../alerting/model/ClusterMetricsInput.kt | 316 +++++++++++++ .../alerting/model/DocLevelMonitorInput.kt | 114 +++++ .../commons/alerting/model/DocLevelQuery.kt | 130 ++++++ .../alerting/model/DocumentLevelTrigger.kt | 159 +++++++ .../commons/alerting/model/Input.kt | 57 +++ .../commons/alerting/model/Monitor.kt | 316 +++++++++++++ .../alerting/model/QueryLevelTrigger.kt | 177 +++++++ .../commons/alerting/model/Schedule.kt | 354 ++++++++++++++ .../commons/alerting/model/ScheduledJob.kt | 85 ++++ .../commons/alerting/model/SearchInput.kt | 87 ++++ .../commons/alerting/model/Trigger.kt | 75 +++ .../commons/alerting/model/action/Action.kt | 178 +++++++ .../model/action/ActionExecutionPolicy.kt | 92 ++++ .../model/action/ActionExecutionScope.kt | 174 +++++++ .../commons/alerting/model/action/Throttle.kt | 87 ++++ .../SupportedClusterMetricsSettings.kt | 7 + .../commons/alerting/util/IndexUtils.kt | 61 +++ .../alerting/AlertingPluginInterfaceTests.kt | 44 ++ .../commons/alerting/MonitorTests.kt | 46 ++ .../commons/alerting/TestHelpers.kt | 406 ++++++++++++++++ .../action/IndexMonitorRequestTests.kt | 54 +++ .../action/IndexMonitorResponseTests.kt | 47 ++ .../model/ClusterMetricsInputTests.kt | 442 ++++++++++++++++++ .../model/DocLevelMonitorInputTests.kt | 109 +++++ .../alerting/model/MockScheduledJob.kt | 31 ++ .../commons/alerting/model/ScheduleTest.kt | 334 +++++++++++++ .../commons/alerting/model/WriteableTests.kt | 157 +++++++ .../alerting/model/XContentTestBase.kt | 27 ++ .../commons/alerting/model/XContentTests.kt | 346 ++++++++++++++ 40 files changed, 5496 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorResponse.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregationBuilder.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregator.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtFilter.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorIndices.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/BucketLevelTrigger.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelMonitorInput.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/DocumentLevelTrigger.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/Input.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/Monitor.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/QueryLevelTrigger.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/Schedule.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/ScheduledJob.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/SearchInput.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/Trigger.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/action/Action.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionPolicy.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionScope.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/action/Throttle.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/settings/SupportedClusterMetricsSettings.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/util/IndexUtils.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/MonitorTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequestTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorResponseTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInputTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/model/DocLevelMonitorInputTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/model/MockScheduledJob.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/model/ScheduleTest.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/model/WriteableTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/model/XContentTestBase.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt diff --git a/build.gradle b/build.gradle index 2fa22931..5c8be6c6 100644 --- a/build.gradle +++ b/build.gradle @@ -75,12 +75,16 @@ dependencies { compileOnly "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}" compileOnly "org.jetbrains.kotlin:kotlin-stdlib-common:${kotlin_version}" compileOnly "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3" // ${kotlin_version} does not work for coroutines + compileOnly "com.cronutils:cron-utils:9.1.6" + compileOnly "commons-validator:commons-validator:1.7" testImplementation "org.opensearch.test:framework:${opensearch_version}" testImplementation "org.jetbrains.kotlin:kotlin-test:${kotlin_version}" testImplementation "org.mockito:mockito-core:3.10.0" testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2' testImplementation 'org.mockito:mockito-junit-jupiter:3.10.0' testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" + testImplementation "com.cronutils:cron-utils:9.1.6" + testImplementation "commons-validator:commons-validator:1.7" testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2' ktlint "com.pinterest:ktlint:0.44.0" @@ -213,4 +217,4 @@ task updateVersion { // Include the required files that needs to be updated with new Version ant.replaceregexp(file:'build.gradle', match: '"opensearch.version", "\\d.*"', replace: '"opensearch.version", "' + newVersion.tokenize('-')[0] + '-SNAPSHOT"', flags:'g', byline:true) } -} \ No newline at end of file +} \ No newline at end of file diff --git a/detekt.yml b/detekt.yml index 251da688..5d1d194d 100644 --- a/detekt.yml +++ b/detekt.yml @@ -7,19 +7,48 @@ style: ForbiddenComment: active: false + LoopWithTooManyJumpStatements: + maxJumpCount: 4 MaxLineLength: - maxLineLength: 150 + maxLineLength: 200 ThrowsCount: active: true max: 10 ReturnCount: active: true max: 10 + UtilityClassWithPublicConstructor: + active: false + +empty-blocks: + EmptyCatchBlock: + excludes: ['**/test/**'] + +exceptions: + SwallowedException: + excludes: ['**/test/**'] + ignoredExceptionTypes: + - 'ZoneRulesException' + - 'DateTimeException' complexity: LargeClass: excludes: ['**/test/**'] LongMethod: excludes: ['**/test/**'] + threshold: 110 LongParameterList: excludes: ['**/test/**'] + constructorThreshold: 8 + ComplexMethod: + threshold: 27 + NestedBlockDepth: + threshold: 10 + +naming: + ObjectPropertyNaming: + constantPattern: '[_A-Za-z][_A-Za-z0-9]*' + +performance: + SpreadOperator: + active: false diff --git a/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt new file mode 100644 index 00000000..a3ea6df0 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt @@ -0,0 +1,62 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.opensearch.commons.alerting + +import org.opensearch.action.ActionListener +import org.opensearch.action.ActionResponse +import org.opensearch.client.node.NodeClient +import org.opensearch.common.io.stream.Writeable +import org.opensearch.commons.alerting.action.AlertingActions +import org.opensearch.commons.alerting.action.IndexMonitorRequest +import org.opensearch.commons.alerting.action.IndexMonitorResponse +import org.opensearch.commons.notifications.action.BaseResponse +import org.opensearch.commons.utils.recreateObject + +/** + * All the transport action plugin interfaces for the Alerting plugin + */ +object AlertingPluginInterface { + + /** + * Index monitor interface. + * @param client Node client for making transport action + * @param request The request object + * @param listener The listener for getting response + */ + fun indexMonitor( + client: NodeClient, + request: IndexMonitorRequest, + listener: ActionListener + ) { + client.execute( + AlertingActions.INDEX_MONITOR_ACTION_TYPE, + request, + wrapActionListener(listener) { response -> + recreateObject(response) { + IndexMonitorResponse( + it + ) + } + } + ) + } + + @Suppress("UNCHECKED_CAST") + private fun wrapActionListener( + listener: ActionListener, + recreate: (Writeable) -> Response + ): ActionListener { + return object : ActionListener { + override fun onResponse(response: ActionResponse) { + val recreated = response as? Response ?: recreate(response) + listener.onResponse(recreated) + } + + override fun onFailure(exception: java.lang.Exception) { + listener.onFailure(exception) + } + } as 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 new file mode 100644 index 00000000..9e4b4003 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt @@ -0,0 +1,14 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.opensearch.commons.alerting.action + +import org.opensearch.action.ActionType + +object AlertingActions { + const val INDEX_MONITOR_ACTION_NAME = "cluster:admin/opendistro/alerting/monitor/write" + + val INDEX_MONITOR_ACTION_TYPE = + ActionType(INDEX_MONITOR_ACTION_NAME, ::IndexMonitorResponse) +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequest.kt new file mode 100644 index 00000000..6a9b75dd --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequest.kt @@ -0,0 +1,59 @@ +package org.opensearch.commons.alerting.action + +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionRequestValidationException +import org.opensearch.action.support.WriteRequest +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.commons.alerting.model.Monitor +import org.opensearch.rest.RestRequest +import java.io.IOException + +class IndexMonitorRequest : ActionRequest { + val monitorId: String + val seqNo: Long + val primaryTerm: Long + val refreshPolicy: WriteRequest.RefreshPolicy + val method: RestRequest.Method + var monitor: Monitor + + constructor( + monitorId: String, + seqNo: Long, + primaryTerm: Long, + refreshPolicy: WriteRequest.RefreshPolicy, + method: RestRequest.Method, + monitor: Monitor + ) : super() { + this.monitorId = monitorId + this.seqNo = seqNo + this.primaryTerm = primaryTerm + this.refreshPolicy = refreshPolicy + this.method = method + this.monitor = monitor + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + monitorId = sin.readString(), + seqNo = sin.readLong(), + primaryTerm = sin.readLong(), + refreshPolicy = WriteRequest.RefreshPolicy.readFrom(sin), + method = sin.readEnum(RestRequest.Method::class.java), + monitor = Monitor.readFrom(sin) as Monitor + ) + + override fun validate(): ActionRequestValidationException? { + return null + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(monitorId) + out.writeLong(seqNo) + out.writeLong(primaryTerm) + refreshPolicy.writeTo(out) + out.writeEnum(method) + monitor.writeTo(out) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorResponse.kt new file mode 100644 index 00000000..5b62a843 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorResponse.kt @@ -0,0 +1,64 @@ +package org.opensearch.commons.alerting.action + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.commons.alerting.model.Monitor +import org.opensearch.commons.alerting.util.IndexUtils.Companion._ID +import org.opensearch.commons.alerting.util.IndexUtils.Companion._PRIMARY_TERM +import org.opensearch.commons.alerting.util.IndexUtils.Companion._SEQ_NO +import org.opensearch.commons.alerting.util.IndexUtils.Companion._VERSION +import org.opensearch.commons.notifications.action.BaseResponse +import java.io.IOException + +class IndexMonitorResponse : BaseResponse { + var id: String + var version: Long + var seqNo: Long + var primaryTerm: Long + var monitor: Monitor + + constructor( + id: String, + version: Long, + seqNo: Long, + primaryTerm: Long, + monitor: Monitor + ) : super() { + this.id = id + this.version = version + this.seqNo = seqNo + this.primaryTerm = primaryTerm + this.monitor = monitor + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readString(), // id + sin.readLong(), // version + sin.readLong(), // seqNo + sin.readLong(), // primaryTerm + Monitor.readFrom(sin) as Monitor // monitor + ) + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(id) + out.writeLong(version) + out.writeLong(seqNo) + out.writeLong(primaryTerm) + monitor.writeTo(out) + } + + @Throws(IOException::class) + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + return builder.startObject() + .field(_ID, id) + .field(_VERSION, version) + .field(_SEQ_NO, seqNo) + .field(_PRIMARY_TERM, primaryTerm) + .field("monitor", monitor) + .endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregationBuilder.kt b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregationBuilder.kt new file mode 100644 index 00000000..d19d0877 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregationBuilder.kt @@ -0,0 +1,244 @@ +package org.opensearch.commons.alerting.aggregation.bucketselectorext + +import org.opensearch.common.ParseField +import org.opensearch.common.ParsingException +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.commons.alerting.aggregation.bucketselectorext.BucketSelectorExtFilter.Companion.BUCKET_SELECTOR_COMPOSITE_AGG_FILTER +import org.opensearch.commons.alerting.aggregation.bucketselectorext.BucketSelectorExtFilter.Companion.BUCKET_SELECTOR_FILTER +import org.opensearch.script.Script +import org.opensearch.search.aggregations.pipeline.AbstractPipelineAggregationBuilder +import org.opensearch.search.aggregations.pipeline.BucketHelpers +import org.opensearch.search.aggregations.pipeline.PipelineAggregator +import java.io.IOException +import java.util.Objects +import kotlin.collections.ArrayList +import kotlin.collections.HashMap + +class BucketSelectorExtAggregationBuilder : + AbstractPipelineAggregationBuilder { + private val bucketsPathsMap: Map + val parentBucketPath: String + val script: Script + val filter: BucketSelectorExtFilter? + private var gapPolicy = BucketHelpers.GapPolicy.SKIP + + constructor( + name: String, + bucketsPathsMap: Map, + script: Script, + parentBucketPath: String, + filter: BucketSelectorExtFilter? + ) : super(name, NAME.preferredName, listOf(parentBucketPath).toTypedArray()) { + this.bucketsPathsMap = bucketsPathsMap + this.script = script + this.parentBucketPath = parentBucketPath + this.filter = filter + } + + @Throws(IOException::class) + @Suppress("UNCHECKED_CAST") + constructor(sin: StreamInput) : super(sin, NAME.preferredName) { + bucketsPathsMap = sin.readMap() as MutableMap + script = Script(sin) + gapPolicy = BucketHelpers.GapPolicy.readFrom(sin) + parentBucketPath = sin.readString() + filter = if (sin.readBoolean()) { + BucketSelectorExtFilter(sin) + } else { + null + } + } + + @Throws(IOException::class) + override fun doWriteTo(out: StreamOutput) { + out.writeMap(bucketsPathsMap) + script.writeTo(out) + gapPolicy.writeTo(out) + out.writeString(parentBucketPath) + if (filter != null) { + out.writeBoolean(true) + filter.writeTo(out) + } else { + out.writeBoolean(false) + } + } + + /** + * Sets the gap policy to use for this aggregation. + */ + fun gapPolicy(gapPolicy: BucketHelpers.GapPolicy?): BucketSelectorExtAggregationBuilder { + requireNotNull(gapPolicy) { "[gapPolicy] must not be null: [$name]" } + this.gapPolicy = gapPolicy + return this + } + + override fun createInternal(metaData: Map?): PipelineAggregator { + return BucketSelectorExtAggregator(name, bucketsPathsMap, parentBucketPath, script, gapPolicy, filter, metaData) + } + + @Throws(IOException::class) + public override fun internalXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.field(PipelineAggregator.Parser.BUCKETS_PATH.preferredName, bucketsPathsMap as Map?) + .field(PARENT_BUCKET_PATH.preferredName, parentBucketPath) + .field(Script.SCRIPT_PARSE_FIELD.preferredName, script) + .field(PipelineAggregator.Parser.GAP_POLICY.preferredName, gapPolicy.getName()) + if (filter != null) { + if (filter.isCompositeAggregation) { + builder.startObject(BUCKET_SELECTOR_COMPOSITE_AGG_FILTER.preferredName) + .value(filter) + .endObject() + } else { + builder.startObject(BUCKET_SELECTOR_FILTER.preferredName) + .value(filter) + .endObject() + } + } + return builder + } + + override fun overrideBucketsPath(): Boolean { + return true + } + + override fun validate(context: ValidationContext) { + // Nothing to check + } + + override fun hashCode(): Int { + return Objects.hash(super.hashCode(), bucketsPathsMap, script, gapPolicy) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || javaClass != other.javaClass) return false + if (!super.equals(other)) return false + val otherCast = other as BucketSelectorExtAggregationBuilder + return ( + bucketsPathsMap == otherCast.bucketsPathsMap && + script == otherCast.script && + gapPolicy == otherCast.gapPolicy + ) + } + + override fun getWriteableName(): String { + return NAME.preferredName + } + + companion object { + val NAME = ParseField("bucket_selector_ext") + val PARENT_BUCKET_PATH = ParseField("parent_bucket_path") + + @Throws(IOException::class) + fun parse(reducerName: String, parser: XContentParser): BucketSelectorExtAggregationBuilder { + var token: XContentParser.Token + var script: Script? = null + var currentFieldName: String? = null + var bucketsPathsMap: MutableMap? = null + var gapPolicy: BucketHelpers.GapPolicy? = null + var parentBucketPath: String? = null + var filter: BucketSelectorExtFilter? = null + while (parser.nextToken().also { token = it } !== XContentParser.Token.END_OBJECT) { + if (token === XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName() + } else if (token === XContentParser.Token.VALUE_STRING) { + when { + PipelineAggregator.Parser.BUCKETS_PATH.match(currentFieldName, parser.deprecationHandler) -> { + bucketsPathsMap = HashMap() + bucketsPathsMap["_value"] = parser.text() + } + PipelineAggregator.Parser.GAP_POLICY.match(currentFieldName, parser.deprecationHandler) -> { + gapPolicy = BucketHelpers.GapPolicy.parse(parser.text(), parser.tokenLocation) + } + Script.SCRIPT_PARSE_FIELD.match(currentFieldName, parser.deprecationHandler) -> { + script = Script.parse(parser) + } + PARENT_BUCKET_PATH.match(currentFieldName, parser.deprecationHandler) -> { + parentBucketPath = parser.text() + } + else -> { + throw ParsingException( + parser.tokenLocation, + "Unknown key for a $token in [$reducerName]: [$currentFieldName]." + ) + } + } + } else if (token === XContentParser.Token.START_ARRAY) { + if (PipelineAggregator.Parser.BUCKETS_PATH.match(currentFieldName, parser.deprecationHandler)) { + val paths: MutableList = ArrayList() + while (parser.nextToken().also { token = it } !== XContentParser.Token.END_ARRAY) { + val path = parser.text() + paths.add(path) + } + bucketsPathsMap = HashMap() + for (i in paths.indices) { + bucketsPathsMap["_value$i"] = paths[i] + } + } else { + throw ParsingException( + parser.tokenLocation, + "Unknown key for a $token in [$reducerName]: [$currentFieldName]." + ) + } + } else if (token === XContentParser.Token.START_OBJECT) { + when { + Script.SCRIPT_PARSE_FIELD.match(currentFieldName, parser.deprecationHandler) -> { + script = Script.parse(parser) + } + PipelineAggregator.Parser.BUCKETS_PATH.match(currentFieldName, parser.deprecationHandler) -> { + val map = parser.map() + bucketsPathsMap = HashMap() + for ((key, value) in map) { + bucketsPathsMap[key] = value.toString() + } + } + BUCKET_SELECTOR_FILTER.match(currentFieldName, parser.deprecationHandler) -> { + filter = BucketSelectorExtFilter.parse(reducerName, false, parser) + } + BUCKET_SELECTOR_COMPOSITE_AGG_FILTER.match( + currentFieldName, + parser.deprecationHandler + ) -> { + filter = BucketSelectorExtFilter.parse(reducerName, true, parser) + } + else -> { + throw ParsingException( + parser.tokenLocation, + "Unknown key for a $token in [$reducerName]: [$currentFieldName]." + ) + } + } + } else { + throw ParsingException(parser.tokenLocation, "Unexpected token $token in [$reducerName].") + } + } + if (bucketsPathsMap == null) { + throw ParsingException( + parser.tokenLocation, + "Missing required field [" + PipelineAggregator.Parser.BUCKETS_PATH.preferredName + "] for bucket_selector aggregation [" + reducerName + "]" + ) + } + if (script == null) { + throw ParsingException( + parser.tokenLocation, + "Missing required field [" + Script.SCRIPT_PARSE_FIELD.preferredName + "] for bucket_selector aggregation [" + reducerName + "]" + ) + } + + if (parentBucketPath == null) { + throw ParsingException( + parser.tokenLocation, + "Missing required field [" + PARENT_BUCKET_PATH + "] for bucket_selector aggregation [" + reducerName + "]" + ) + } + val factory = BucketSelectorExtAggregationBuilder(reducerName, bucketsPathsMap, script, parentBucketPath, filter) + if (gapPolicy != null) { + factory.gapPolicy(gapPolicy) + } + return factory + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregator.kt b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregator.kt new file mode 100644 index 00000000..373bd5e9 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregator.kt @@ -0,0 +1,155 @@ +package org.opensearch.commons.alerting.aggregation.bucketselectorext + +import org.apache.lucene.util.BytesRef +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.commons.alerting.aggregation.bucketselectorext.BucketSelectorExtAggregationBuilder.Companion.NAME +import org.opensearch.script.BucketAggregationSelectorScript +import org.opensearch.script.Script +import org.opensearch.search.DocValueFormat +import org.opensearch.search.aggregations.Aggregations +import org.opensearch.search.aggregations.InternalAggregation +import org.opensearch.search.aggregations.InternalMultiBucketAggregation +import org.opensearch.search.aggregations.bucket.SingleBucketAggregation +import org.opensearch.search.aggregations.bucket.composite.InternalComposite +import org.opensearch.search.aggregations.bucket.terms.IncludeExclude +import org.opensearch.search.aggregations.pipeline.BucketHelpers +import org.opensearch.search.aggregations.pipeline.SiblingPipelineAggregator +import org.opensearch.search.aggregations.support.AggregationPath +import java.io.IOException + +class BucketSelectorExtAggregator : SiblingPipelineAggregator { + private var name: String? = null + private var bucketsPathsMap: Map + private var parentBucketPath: String + private var script: Script + private var gapPolicy: BucketHelpers.GapPolicy + private var bucketSelectorExtFilter: BucketSelectorExtFilter? = null + + constructor( + name: String?, + bucketsPathsMap: Map, + parentBucketPath: String, + script: Script, + gapPolicy: BucketHelpers.GapPolicy, + filter: BucketSelectorExtFilter?, + metadata: Map? + ) : super(name, bucketsPathsMap.values.toTypedArray(), metadata) { + this.bucketsPathsMap = bucketsPathsMap + this.parentBucketPath = parentBucketPath + this.script = script + this.gapPolicy = gapPolicy + this.bucketSelectorExtFilter = filter + } + + /** + * Read from a stream. + */ + @Suppress("UNCHECKED_CAST") + @Throws(IOException::class) + constructor(sin: StreamInput) : super(sin.readString(), null, null) { + script = Script(sin) + gapPolicy = BucketHelpers.GapPolicy.readFrom(sin) + bucketsPathsMap = sin.readMap() as Map + parentBucketPath = sin.readString() + if (sin.readBoolean()) { + bucketSelectorExtFilter = BucketSelectorExtFilter(sin) + } else { + bucketSelectorExtFilter = null + } + } + + @Throws(IOException::class) + override fun doWriteTo(out: StreamOutput) { + out.writeString(name) + script.writeTo(out) + gapPolicy.writeTo(out) + out.writeGenericValue(bucketsPathsMap) + out.writeString(parentBucketPath) + if (bucketSelectorExtFilter != null) { + out.writeBoolean(true) + bucketSelectorExtFilter!!.writeTo(out) + } else { + out.writeBoolean(false) + } + } + + override fun getWriteableName(): String { + return NAME.preferredName + } + + override fun doReduce(aggregations: Aggregations, reduceContext: InternalAggregation.ReduceContext): InternalAggregation { + val parentBucketPathList = AggregationPath.parse(parentBucketPath).pathElementsAsStringList + var subAggregations: Aggregations = aggregations + for (i in 0 until parentBucketPathList.size - 1) { + subAggregations = subAggregations.get(parentBucketPathList[0]).aggregations + } + val originalAgg = subAggregations.get(parentBucketPathList.last()) as InternalMultiBucketAggregation<*, *> + val buckets = originalAgg.buckets + val factory = reduceContext.scriptService().compile(script, BucketAggregationSelectorScript.CONTEXT) + val selectedBucketsIndex: MutableList = ArrayList() + for (i in buckets.indices) { + val bucket = buckets[i] + if (bucketSelectorExtFilter != null) { + var accepted = true + if (bucketSelectorExtFilter!!.isCompositeAggregation) { + val compBucketKeyObj = (bucket as InternalComposite.InternalBucket).key + val filtersMap: HashMap? = bucketSelectorExtFilter!!.filtersMap + for (sourceKey in compBucketKeyObj.keys) { + if (filtersMap != null) { + if (filtersMap.containsKey(sourceKey)) { + val obj = compBucketKeyObj[sourceKey] + accepted = isAccepted(obj!!, filtersMap[sourceKey]) + if (!accepted) break + } else { + accepted = false + break + } + } + } + } else { + accepted = isAccepted(bucket.key, bucketSelectorExtFilter!!.filters) + } + if (!accepted) continue + } + + val vars: MutableMap = HashMap() + if (script.params != null) { + vars.putAll(script.params) + } + for ((varName, bucketsPath) in bucketsPathsMap) { + val value = BucketHelpers.resolveBucketValue(originalAgg, bucket, bucketsPath, gapPolicy) + vars[varName] = value + } + val executableScript = factory.newInstance(vars) + // TODO: can we use one instance of the script for all buckets? it should be stateless? + if (executableScript.execute()) { + selectedBucketsIndex.add(i) + } + } + + return BucketSelectorIndices( + name(), parentBucketPath, selectedBucketsIndex, originalAgg.metadata + ) + } + + private fun isAccepted(obj: Any, filter: IncludeExclude?): Boolean { + return when (obj.javaClass) { + String::class.java -> { + val stringFilter = filter!!.convertToStringFilter(DocValueFormat.RAW) + stringFilter.accept(BytesRef(obj as String)) + } + java.lang.Long::class.java, Long::class.java -> { + val longFilter = filter!!.convertToLongFilter(DocValueFormat.RAW) + longFilter.accept(obj as Long) + } + java.lang.Double::class.java, Double::class.java -> { + val doubleFilter = filter!!.convertToDoubleFilter() + doubleFilter.accept(obj as Long) + } + else -> { + throw IllegalStateException("Object is not comparable. Please use one of String, Long or Double type.") + } + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtFilter.kt b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtFilter.kt new file mode 100644 index 00000000..0a776d0f --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtFilter.kt @@ -0,0 +1,140 @@ +package org.opensearch.commons.alerting.aggregation.bucketselectorext + +import org.opensearch.common.ParseField +import org.opensearch.common.ParsingException +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.commons.notifications.model.BaseModel +import org.opensearch.search.aggregations.bucket.terms.IncludeExclude +import java.io.IOException + +class BucketSelectorExtFilter : BaseModel { + // used for composite aggregations + val filtersMap: HashMap? + // used for filtering string term aggregation + val filters: IncludeExclude? + + constructor(filters: IncludeExclude?) { + filtersMap = null + this.filters = filters + } + + constructor(filtersMap: HashMap?) { + this.filtersMap = filtersMap + filters = null + } + + constructor(sin: StreamInput) { + if (sin.readBoolean()) { + val size: Int = sin.readVInt() + filtersMap = java.util.HashMap() + + var i = 0 + while (i <= size) { + filtersMap[sin.readString()] = IncludeExclude(sin) + ++i + } + filters = null + } else { + filters = IncludeExclude(sin) + filtersMap = null + } + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + val isCompAgg = isCompositeAggregation + out.writeBoolean(isCompAgg) + if (isCompAgg) { + out.writeVInt(filtersMap!!.size) + for ((key, value) in filtersMap) { + out.writeString(key) + value.writeTo(out) + } + } else { + filters!!.writeTo(out) + } + } + + @Throws(IOException::class) + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + if (isCompositeAggregation) { + for ((key, filter) in filtersMap!!) { + builder.startObject(key) + filter.toXContent(builder, params) + builder.endObject() + } + } else { + filters!!.toXContent(builder, params) + } + return builder + } + + val isCompositeAggregation: Boolean + get() = if (filtersMap != null && filters == null) { + true + } else if (filtersMap == null && filters != null) { + false + } else { + throw IllegalStateException("Type of selector cannot be determined") + } + + companion object { + const val NAME = "filter" + var BUCKET_SELECTOR_FILTER = ParseField("filter") + var BUCKET_SELECTOR_COMPOSITE_AGG_FILTER = ParseField("composite_agg_filter") + + @Throws(IOException::class) + fun parse(reducerName: String, isCompositeAggregation: Boolean, parser: XContentParser): BucketSelectorExtFilter { + var token: XContentParser.Token + return if (isCompositeAggregation) { + val filtersMap = HashMap() + while (parser.nextToken().also { token = it } !== XContentParser.Token.END_OBJECT) { + if (token === XContentParser.Token.FIELD_NAME) { + val sourceKey = parser.currentName() + token = parser.nextToken() + filtersMap[sourceKey] = parseIncludeExclude(reducerName, parser) + } else { + throw ParsingException( + parser.tokenLocation, + "Unknown key for a " + token + " in [" + reducerName + "]: [" + parser.currentName() + "]." + ) + } + } + BucketSelectorExtFilter(filtersMap) + } else { + BucketSelectorExtFilter(parseIncludeExclude(reducerName, parser)) + } + } + + @Throws(IOException::class) + private fun parseIncludeExclude(reducerName: String, parser: XContentParser): IncludeExclude { + var token: XContentParser.Token + var include: IncludeExclude? = null + var exclude: IncludeExclude? = null + while (parser.nextToken().also { token = it } !== XContentParser.Token.END_OBJECT) { + val fieldName = parser.currentName() + when { + IncludeExclude.INCLUDE_FIELD.match(fieldName, parser.deprecationHandler) -> { + parser.nextToken() + include = IncludeExclude.parseInclude(parser) + } + IncludeExclude.EXCLUDE_FIELD.match(fieldName, parser.deprecationHandler) -> { + parser.nextToken() + exclude = IncludeExclude.parseExclude(parser) + } + else -> { + throw ParsingException( + parser.tokenLocation, + "Unknown key for a $token in [$reducerName]: [$fieldName]." + ) + } + } + } + return IncludeExclude.merge(include, exclude) + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorIndices.kt b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorIndices.kt new file mode 100644 index 00000000..9cb238e7 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorIndices.kt @@ -0,0 +1,68 @@ +package org.opensearch.commons.alerting.aggregation.bucketselectorext + +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.search.aggregations.InternalAggregation +import java.io.IOException +import java.util.Objects + +open class BucketSelectorIndices( + name: String?, + private var parentBucketPath: String, + var bucketIndices: List, + metaData: Map? +) : InternalAggregation(name, metaData) { + + @Throws(IOException::class) + override fun doWriteTo(out: StreamOutput) { + out.writeString(parentBucketPath) + out.writeIntArray(bucketIndices.stream().mapToInt { i: Int? -> i!! }.toArray()) + } + + override fun getWriteableName(): String { + return name + } + + override fun reduce(aggregations: List, reduceContext: ReduceContext): BucketSelectorIndices { + throw UnsupportedOperationException("Not supported") + } + + override fun mustReduceOnSingleInternalAgg(): Boolean { + return false + } + + override fun getProperty(path: MutableList?): Any { + throw UnsupportedOperationException("Not supported") + } + + object Fields { + const val PARENT_BUCKET_PATH = "parent_bucket_path" + const val BUCKET_INDICES = "bucket_indices" + } + + @Throws(IOException::class) + override fun doXContentBody(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.field(Fields.PARENT_BUCKET_PATH, parentBucketPath) + builder.field(Fields.BUCKET_INDICES, bucketIndices) + otherStatsToXContent(builder) + return builder + } + + @Throws(IOException::class) + protected fun otherStatsToXContent(builder: XContentBuilder): XContentBuilder { + return builder + } + + override fun hashCode(): Int { + return Objects.hash(super.hashCode(), parentBucketPath) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || javaClass != other.javaClass) return false + if (!super.equals(other)) return false + val otherCast = other as BucketSelectorIndices + return name == otherCast.name && parentBucketPath == otherCast.parentBucketPath + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/BucketLevelTrigger.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/BucketLevelTrigger.kt new file mode 100644 index 00000000..56975beb --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/BucketLevelTrigger.kt @@ -0,0 +1,143 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.CheckedFunction +import org.opensearch.common.ParseField +import org.opensearch.common.UUIDs +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.NamedXContentRegistry +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.alerting.aggregation.bucketselectorext.BucketSelectorExtAggregationBuilder +import org.opensearch.commons.alerting.model.Trigger.Companion.ACTIONS_FIELD +import org.opensearch.commons.alerting.model.Trigger.Companion.ID_FIELD +import org.opensearch.commons.alerting.model.Trigger.Companion.NAME_FIELD +import org.opensearch.commons.alerting.model.Trigger.Companion.SEVERITY_FIELD +import org.opensearch.commons.alerting.model.action.Action +import java.io.IOException + +data class BucketLevelTrigger( + override val id: String = UUIDs.base64UUID(), + override val name: String, + override val severity: String, + val bucketSelector: BucketSelectorExtAggregationBuilder, + override val actions: List +) : Trigger { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readString(), // id + sin.readString(), // name + sin.readString(), // severity + BucketSelectorExtAggregationBuilder(sin), // condition + sin.readList(::Action) // actions + ) + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .startObject(BUCKET_LEVEL_TRIGGER_FIELD) + .field(ID_FIELD, id) + .field(NAME_FIELD, name) + .field(SEVERITY_FIELD, severity) + .startObject(CONDITION_FIELD) + bucketSelector.internalXContent(builder, params) + builder.endObject() + .field(ACTIONS_FIELD, actions.toTypedArray()) + .endObject() + .endObject() + return builder + } + + override fun name(): String { + return BUCKET_LEVEL_TRIGGER_FIELD + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(id) + out.writeString(name) + out.writeString(severity) + bucketSelector.writeTo(out) + out.writeCollection(actions) + } + + fun asTemplateArg(): Map { + return mapOf( + ID_FIELD to id, + NAME_FIELD to name, + SEVERITY_FIELD to severity, + ACTIONS_FIELD to actions.map { it.asTemplateArg() }, + PARENT_BUCKET_PATH to getParentBucketPath() + ) + } + + fun getParentBucketPath(): String { + return bucketSelector.parentBucketPath + } + + companion object { + const val BUCKET_LEVEL_TRIGGER_FIELD = "bucket_level_trigger" + const val CONDITION_FIELD = "condition" + const val PARENT_BUCKET_PATH = "parentBucketPath" + + val XCONTENT_REGISTRY = NamedXContentRegistry.Entry( + Trigger::class.java, ParseField(BUCKET_LEVEL_TRIGGER_FIELD), + CheckedFunction { parseInner(it) } + ) + + @JvmStatic + @Throws(IOException::class) + fun parseInner(xcp: XContentParser): BucketLevelTrigger { + 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() + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + lateinit var bucketSelector: BucketSelectorExtAggregationBuilder + + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + + xcp.nextToken() + when (fieldName) { + ID_FIELD -> id = xcp.text() + NAME_FIELD -> name = xcp.text() + SEVERITY_FIELD -> severity = xcp.text() + CONDITION_FIELD -> { + // Using the trigger id as the name in the bucket selector since it is validated for uniqueness within Monitors. + // The contents of the trigger definition are round-tripped through parse and toXContent during Monitor creation + // ensuring that the id is available here in the version of the Monitor object that will be executed, even if the + // user submitted a custom trigger id after the condition definition. + bucketSelector = BucketSelectorExtAggregationBuilder.parse(id, xcp) + } + ACTIONS_FIELD -> { + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_ARRAY, + xcp.currentToken(), + xcp + ) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + actions.add(Action.parse(xcp)) + } + } + } + } + + return BucketLevelTrigger( + id = requireNotNull(id) { "Trigger id is null." }, + name = requireNotNull(name) { "Trigger name is null" }, + severity = requireNotNull(severity) { "Trigger severity is null" }, + bucketSelector = requireNotNull(bucketSelector) { "Trigger condition is null" }, + actions = requireNotNull(actions) { "Trigger actions are null" } + ) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): BucketLevelTrigger { + return BucketLevelTrigger(sin) + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt new file mode 100644 index 00000000..fc805917 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt @@ -0,0 +1,316 @@ +package org.opensearch.commons.alerting.model + +import org.apache.commons.validator.routines.UrlValidator +import org.apache.http.client.utils.URIBuilder +import org.opensearch.common.CheckedFunction +import org.opensearch.common.ParseField +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.NamedXContentRegistry +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import java.io.IOException +import java.net.URI + +val ILLEGAL_PATH_PARAMETER_CHARACTERS = arrayOf(':', '"', '+', '\\', '|', '?', '#', '>', '<', ' ') + +/** + * This is a data class for a URI type of input for Monitors specifically for local clusters. + */ +data class ClusterMetricsInput( + var path: String, + var pathParams: String = "", + var url: String +) : Input { + val clusterMetricType: ClusterMetricType + val constructedUri: URI + + // Verify parameters are valid during creation + init { + require(validateFields()) { + "The uri.api_type field, uri.path field, or uri.uri field must be defined." + } + + // Create an UrlValidator that only accepts "http" and "https" as valid scheme and allows local URLs. + val urlValidator = UrlValidator(arrayOf("http", "https"), UrlValidator.ALLOW_LOCAL_URLS) + + // Build url field by field if not provided as whole. + constructedUri = toConstructedUri() + + require(urlValidator.isValid(constructedUri.toString())) { + "Invalid URI constructed from the path and path_params inputs, or the url input." + } + + if (url.isNotEmpty() && validateFieldsNotEmpty()) + require(constructedUri == constructUrlFromInputs()) { + "The provided URL and URI fields form different URLs." + } + + require(constructedUri.host.lowercase() == SUPPORTED_HOST) { + "Only host '$SUPPORTED_HOST' is supported." + } + require(constructedUri.port == SUPPORTED_PORT) { + "Only port '$SUPPORTED_PORT' is supported." + } + + clusterMetricType = findApiType(constructedUri.path) + this.parseEmptyFields() + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readString(), // path + sin.readString(), // path params + sin.readString() // url + ) + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + return builder.startObject() + .startObject(URI_FIELD) + .field(API_TYPE_FIELD, clusterMetricType) + .field(PATH_FIELD, path) + .field(PATH_PARAMS_FIELD, pathParams) + .field(URL_FIELD, url) + .endObject() + .endObject() + } + + override fun name(): String { + return URI_FIELD + } + + override fun writeTo(out: StreamOutput) { + out.writeString(clusterMetricType.toString()) + out.writeString(path) + out.writeString(pathParams) + out.writeString(url) + } + + companion object { + const val SUPPORTED_SCHEME = "http" + const val SUPPORTED_HOST = "localhost" + const val SUPPORTED_PORT = 9200 + + const val API_TYPE_FIELD = "api_type" + const val PATH_FIELD = "path" + const val PATH_PARAMS_FIELD = "path_params" + const val URL_FIELD = "url" + const val URI_FIELD = "uri" + + val XCONTENT_REGISTRY = NamedXContentRegistry.Entry(Input::class.java, ParseField(URI_FIELD), CheckedFunction { parseInner(it) }) + + /** + * This parse function uses [XContentParser] to parse JSON input and store corresponding fields to create a [ClusterMetricsInput] object + */ + @JvmStatic @Throws(IOException::class) + fun parseInner(xcp: XContentParser): ClusterMetricsInput { + var path = "" + var pathParams = "" + var url = "" + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + when (fieldName) { + PATH_FIELD -> path = xcp.text() + PATH_PARAMS_FIELD -> pathParams = xcp.text() + URL_FIELD -> url = xcp.text() + } + } + return ClusterMetricsInput(path, pathParams, url) + } + } + + /** + * Constructs the [URI] using either the provided [url], or the + * supported scheme, host, and port and provided [path]+[pathParams]. + * @return The [URI] constructed from [url] if it's defined; + * otherwise a [URI] constructed from the provided [URI] fields. + */ + private fun toConstructedUri(): URI { + return if (url.isEmpty()) { + constructUrlFromInputs() + } else { + URIBuilder(url).build() + } + } + + /** + * Isolates just the path parameters from the [ClusterMetricsInput] URI. + * @return The path parameters portion of the [ClusterMetricsInput] URI. + * @throws IllegalArgumentException if the [ClusterMetricType] requires path parameters, but none are supplied; + * or when path parameters are provided for an [ClusterMetricType] that does not use path parameters. + */ + fun parsePathParams(): String { + val path = this.constructedUri.path + val apiType = this.clusterMetricType + + var pathParams: String + if (this.pathParams.isNotEmpty()) { + pathParams = this.pathParams + } else { + val prependPath = if (apiType.supportsPathParams) apiType.prependPath else apiType.defaultPath + pathParams = path.removePrefix(prependPath) + pathParams = pathParams.removeSuffix(apiType.appendPath) + } + + if (pathParams.isNotEmpty()) { + pathParams = pathParams.trim('/') + ILLEGAL_PATH_PARAMETER_CHARACTERS.forEach { character -> + if (pathParams.contains(character)) + throw IllegalArgumentException( + "The provided path parameters contain invalid characters or spaces. Please omit: " + "${ILLEGAL_PATH_PARAMETER_CHARACTERS.joinToString(" ")}" + ) + } + } + + if (apiType.requiresPathParams && pathParams.isEmpty()) + throw IllegalArgumentException("The API requires path parameters.") + if (!apiType.supportsPathParams && pathParams.isNotEmpty()) + throw IllegalArgumentException("The API does not use path parameters.") + + return pathParams + } + + /** + * Examines the path of a [ClusterMetricsInput] to determine which API is being called. + * @param uriPath The path to examine. + * @return The [ClusterMetricType] associated with the [ClusterMetricsInput] monitor. + * @throws IllegalArgumentException when the API to call cannot be determined from the URI. + */ + private fun findApiType(uriPath: String): ClusterMetricType { + var apiType = ClusterMetricType.BLANK + ClusterMetricType.values() + .filter { option -> option != ClusterMetricType.BLANK } + .forEach { option -> + if (uriPath.startsWith(option.prependPath) || uriPath.startsWith(option.defaultPath)) + apiType = option + } + if (apiType.isBlank()) + throw IllegalArgumentException("The API could not be determined from the provided URI.") + return apiType + } + + /** + * Constructs a [URI] from the supported scheme, host, and port, and the provided [path], and [pathParams]. + * @return The constructed [URI]. + */ + private fun constructUrlFromInputs(): URI { + val uriBuilder = URIBuilder() + .setScheme(SUPPORTED_SCHEME) + .setHost(SUPPORTED_HOST) + .setPort(SUPPORTED_PORT) + .setPath(path + pathParams) + return uriBuilder.build() + } + + /** + * If [url] field is empty, populates it with [constructedUri]. + * If [path] and [pathParams] are empty, populates them with values from [url]. + */ + private fun parseEmptyFields() { + if (pathParams.isEmpty()) + pathParams = this.parsePathParams() + if (path.isEmpty()) + path = if (pathParams.isEmpty()) clusterMetricType.defaultPath else clusterMetricType.prependPath + if (url.isEmpty()) + url = constructedUri.toString() + } + + /** + * Helper function to confirm at least [url], or required URI component fields are defined. + * @return TRUE if at least either [url] or the other components are provided; otherwise FALSE. + */ + private fun validateFields(): Boolean { + return url.isNotEmpty() || validateFieldsNotEmpty() + } + + /** + * Confirms that required URI component fields are defined. + * Only validating path for now, as that's the only required field. + * @return TRUE if all those fields are defined; otherwise FALSE. + */ + private fun validateFieldsNotEmpty(): Boolean { + return path.isNotEmpty() + } + + /** + * An enum class to quickly reference various supported API. + */ + enum class ClusterMetricType( + val defaultPath: String, + val prependPath: String, + val appendPath: String, + val supportsPathParams: Boolean, + val requiresPathParams: Boolean + ) { + BLANK("", "", "", false, false), + CAT_PENDING_TASKS( + "/_cat/pending_tasks", + "/_cat/pending_tasks", + "", + false, + false + ), + CAT_RECOVERY( + "/_cat/recovery", + "/_cat/recovery", + "", + true, + false + ), + CAT_SNAPSHOTS( + "/_cat/snapshots", + "/_cat/snapshots", + "", + true, + true + ), + CAT_TASKS( + "/_cat/tasks", + "/_cat/tasks", + "", + false, + false + ), + CLUSTER_HEALTH( + "/_cluster/health", + "/_cluster/health", + "", + true, + false + ), + CLUSTER_SETTINGS( + "/_cluster/settings", + "/_cluster/settings", + "", + false, + false + ), + CLUSTER_STATS( + "/_cluster/stats", + "/_cluster/stats", + "", + true, + false + ), + NODES_STATS( + "/_nodes/stats", + "/_nodes", + "", + false, + false + ); + + /** + * @return TRUE if the [ClusterMetricType] is [BLANK]; otherwise FALSE. + */ + fun isBlank(): Boolean { + return this === BLANK + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelMonitorInput.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelMonitorInput.kt new file mode 100644 index 00000000..9a914f3e --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelMonitorInput.kt @@ -0,0 +1,114 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.CheckedFunction +import org.opensearch.common.ParseField +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.NamedXContentRegistry +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import java.io.IOException + +data class DocLevelMonitorInput( + val description: String = NO_DESCRIPTION, + val indices: List, + val queries: List +) : Input { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readString(), // description + sin.readStringList(), // indices + sin.readList(::DocLevelQuery) // docLevelQueries + ) + + fun asTemplateArg(): Map { + return mapOf( + DESCRIPTION_FIELD to description, + INDICES_FIELD to indices, + QUERIES_FIELD to queries.map { it.asTemplateArg() } + ) + } + + override fun name(): String { + return DOC_LEVEL_INPUT_FIELD + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(description) + out.writeStringCollection(indices) + out.writeCollection(queries) + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .startObject(DOC_LEVEL_INPUT_FIELD) + .field(DESCRIPTION_FIELD, description) + .field(INDICES_FIELD, indices.toTypedArray()) + .field(QUERIES_FIELD, queries.toTypedArray()) + .endObject() + .endObject() + return builder + } + + companion object { + const val DESCRIPTION_FIELD = "description" + const val INDICES_FIELD = "indices" + const val DOC_LEVEL_INPUT_FIELD = "doc_level_input" + const val QUERIES_FIELD = "queries" + + const val NO_DESCRIPTION = "" + + val XCONTENT_REGISTRY = NamedXContentRegistry.Entry( + Input::class.java, + ParseField(DOC_LEVEL_INPUT_FIELD), CheckedFunction { parse(it) } + ) + + @JvmStatic @Throws(IOException::class) + fun parse(xcp: XContentParser): DocLevelMonitorInput { + var description: String = NO_DESCRIPTION + val indices: MutableList = mutableListOf() + val docLevelQueries: MutableList = mutableListOf() + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + when (fieldName) { + DESCRIPTION_FIELD -> description = xcp.text() + INDICES_FIELD -> { + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_ARRAY, + xcp.currentToken(), + xcp + ) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + indices.add(xcp.text()) + } + } + QUERIES_FIELD -> { + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_ARRAY, + xcp.currentToken(), + xcp + ) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + docLevelQueries.add(DocLevelQuery.parse(xcp)) + } + } + } + } + + return DocLevelMonitorInput(description = description, indices = indices, queries = docLevelQueries) + } + + @JvmStatic @Throws(IOException::class) + fun readFrom(sin: StreamInput): DocLevelMonitorInput { + return DocLevelMonitorInput(sin) + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt new file mode 100644 index 00000000..220c928d --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt @@ -0,0 +1,130 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.model.BaseModel +import java.io.IOException +import java.lang.IllegalArgumentException +import java.util.UUID + +data class DocLevelQuery( + val id: String = UUID.randomUUID().toString(), + val name: String, + val query: String, + val tags: List = mutableListOf() +) : BaseModel { + + init { + // Ensure the name and tags have valid characters + validateQuery(name) + for (tag in tags) { + validateQuery(tag) + } + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readString(), // id + sin.readString(), // name + sin.readString(), // query + sin.readStringList() // tags + ) + + fun asTemplateArg(): Map { + return mapOf( + QUERY_ID_FIELD to id, + NAME_FIELD to name, + QUERY_FIELD to query, + TAGS_FIELD to tags + ) + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(id) + out.writeString(name) + out.writeString(query) + out.writeStringCollection(tags) + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .field(QUERY_ID_FIELD, id) + .field(NAME_FIELD, name) + .field(QUERY_FIELD, query) + .field(TAGS_FIELD, tags.toTypedArray()) + .endObject() + return builder + } + + companion object { + const val QUERY_ID_FIELD = "id" + const val NAME_FIELD = "name" + const val QUERY_FIELD = "query" + const val TAGS_FIELD = "tags" + const val NO_ID = "" + val INVALID_CHARACTERS: List = listOf(" ", "[", "]", "{", "}", "(", ")") + + @JvmStatic @Throws(IOException::class) + fun parse(xcp: XContentParser): DocLevelQuery { + var id: String = UUID.randomUUID().toString() + lateinit var query: String + lateinit var name: String + val tags: MutableList = mutableListOf() + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + when (fieldName) { + QUERY_ID_FIELD -> id = xcp.text() + NAME_FIELD -> { + name = xcp.text() + validateQuery(name) + } + QUERY_FIELD -> query = xcp.text() + TAGS_FIELD -> { + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_ARRAY, + xcp.currentToken(), + xcp + ) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + val tag = xcp.text() + validateQuery(tag) + tags.add(tag) + } + } + } + } + + return DocLevelQuery( + id = id, + name = name, + query = query, + tags = tags + ) + } + + @JvmStatic @Throws(IOException::class) + fun readFrom(sin: StreamInput): DocLevelQuery { + return DocLevelQuery(sin) + } + + // TODO: add test for this + private fun validateQuery(stringVal: String) { + for (inValidChar in INVALID_CHARACTERS) { + if (stringVal.contains(inValidChar)) { + throw IllegalArgumentException( + "They query name or tag, $stringVal, contains an invalid character: [' ','[',']','{','}','(',')']" + ) + } + } + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/DocumentLevelTrigger.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/DocumentLevelTrigger.kt new file mode 100644 index 00000000..a9cd0d69 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/DocumentLevelTrigger.kt @@ -0,0 +1,159 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.CheckedFunction +import org.opensearch.common.ParseField +import org.opensearch.common.UUIDs +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.NamedXContentRegistry +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.alerting.model.Trigger.Companion.ACTIONS_FIELD +import org.opensearch.commons.alerting.model.Trigger.Companion.ID_FIELD +import org.opensearch.commons.alerting.model.Trigger.Companion.NAME_FIELD +import org.opensearch.commons.alerting.model.Trigger.Companion.SEVERITY_FIELD +import org.opensearch.commons.alerting.model.action.Action +import org.opensearch.script.Script +import java.io.IOException + +data class DocumentLevelTrigger( + override val id: String = UUIDs.base64UUID(), + override val name: String, + override val severity: String, + override val actions: List, + val condition: Script +) : Trigger { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readString(), // id + sin.readString(), // name + sin.readString(), // severity + sin.readList(::Action), // actions + Script(sin) + ) + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .startObject(DOCUMENT_LEVEL_TRIGGER_FIELD) + .field(ID_FIELD, id) + .field(NAME_FIELD, name) + .field(SEVERITY_FIELD, severity) + .startObject(CONDITION_FIELD) + .field(SCRIPT_FIELD, condition) + .endObject() + .field(ACTIONS_FIELD, actions.toTypedArray()) + .endObject() + .endObject() + return builder + } + + override fun name(): String { + return DOCUMENT_LEVEL_TRIGGER_FIELD + } + + /** Returns a representation of the trigger suitable for passing into painless and mustache scripts. */ + fun asTemplateArg(): Map { + return mapOf( + ID_FIELD to id, + NAME_FIELD to name, + SEVERITY_FIELD to severity, + ACTIONS_FIELD to actions.map { it.asTemplateArg() } + ) + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(id) + out.writeString(name) + out.writeString(severity) + out.writeCollection(actions) + condition.writeTo(out) + } + + companion object { + const val DOCUMENT_LEVEL_TRIGGER_FIELD = "document_level_trigger" + const val CONDITION_FIELD = "condition" + const val SCRIPT_FIELD = "script" + const val QUERY_IDS_FIELD = "query_ids" + + val XCONTENT_REGISTRY = NamedXContentRegistry.Entry( + Trigger::class.java, ParseField(DOCUMENT_LEVEL_TRIGGER_FIELD), + CheckedFunction { parseInner(it) } + ) + + @JvmStatic @Throws(IOException::class) + fun parseInner(xcp: XContentParser): DocumentLevelTrigger { + var id = UUIDs.base64UUID() // assign a default triggerId if one is not specified + lateinit var name: String + lateinit var severity: String + lateinit var condition: Script + val queryIds: MutableList = mutableListOf() + val actions: MutableList = mutableListOf() + + 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) { + ID_FIELD -> id = xcp.text() + NAME_FIELD -> name = xcp.text() + SEVERITY_FIELD -> severity = xcp.text() + CONDITION_FIELD -> { + xcp.nextToken() + condition = Script.parse(xcp) + require(condition.lang == Script.DEFAULT_SCRIPT_LANG) { + "Invalid script language. Allowed languages are [${Script.DEFAULT_SCRIPT_LANG}]" + } + xcp.nextToken() + } + QUERY_IDS_FIELD -> { + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_ARRAY, + xcp.currentToken(), + xcp + ) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + queryIds.add(xcp.text()) + } + } + ACTIONS_FIELD -> { + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_ARRAY, + xcp.currentToken(), + xcp + ) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + actions.add(Action.parse(xcp)) + } + } + } + xcp.nextToken() + } + + return DocumentLevelTrigger( + name = requireNotNull(name) { "Trigger name is null" }, + severity = requireNotNull(severity) { "Trigger severity is null" }, + condition = requireNotNull(condition) { "Trigger condition is null" }, + actions = requireNotNull(actions) { "Trigger actions are null" }, + id = requireNotNull(id) { "Trigger id is null." } + ) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): DocumentLevelTrigger { + return DocumentLevelTrigger(sin) + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Input.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Input.kt new file mode 100644 index 00000000..1a193ba1 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Input.kt @@ -0,0 +1,57 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +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.notifications.model.BaseModel +import java.io.IOException + +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); + + override fun toString(): String { + return value + } + } + + companion object { + + @Throws(IOException::class) + fun parse(xcp: XContentParser): Input { + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + XContentParserUtils.ensureExpectedToken(XContentParser.Token.FIELD_NAME, xcp.nextToken(), xcp) + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.nextToken(), xcp) + val input = if (xcp.currentName() == Type.SEARCH_INPUT.value) { + SearchInput.parseInner(xcp) + } else if (xcp.currentName() == Type.CLUSTER_METRICS_INPUT.value) { + ClusterMetricsInput.parseInner(xcp) + } else { + DocLevelMonitorInput.parse(xcp) + } + XContentParserUtils.ensureExpectedToken(XContentParser.Token.END_OBJECT, xcp.nextToken(), xcp) + return input + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): Input { + return when (val type = sin.readEnum(Input.Type::class.java)) { + Type.DOCUMENT_LEVEL_INPUT -> DocLevelMonitorInput(sin) + Type.CLUSTER_METRICS_INPUT -> ClusterMetricsInput(sin) + Type.SEARCH_INPUT -> SearchInput(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") + } + } + } + + fun name(): String +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Monitor.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Monitor.kt new file mode 100644 index 00000000..6e7c9c45 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Monitor.kt @@ -0,0 +1,316 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.CheckedFunction +import org.opensearch.common.ParseField +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.NamedXContentRegistry +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.alerting.util.IndexUtils.Companion.MONITOR_MAX_INPUTS +import org.opensearch.commons.alerting.util.IndexUtils.Companion.MONITOR_MAX_TRIGGERS +import org.opensearch.commons.alerting.util.IndexUtils.Companion.NO_SCHEMA_VERSION +import org.opensearch.commons.alerting.util.IndexUtils.Companion._ID +import org.opensearch.commons.alerting.util.IndexUtils.Companion._VERSION +import org.opensearch.commons.alerting.util.IndexUtils.Companion.supportedClusterMetricsSettings +import org.opensearch.commons.alerting.util.instant +import org.opensearch.commons.alerting.util.isBucketLevelMonitor +import org.opensearch.commons.alerting.util.optionalTimeField +import org.opensearch.commons.alerting.util.optionalUserField +import org.opensearch.commons.authuser.User +import java.io.IOException +import java.time.Instant +import java.util.Locale + +data class Monitor( + override val id: String = NO_ID, + override val version: Long = NO_VERSION, + override val name: String, + override val enabled: Boolean, + override val schedule: Schedule, + override val lastUpdateTime: Instant, + override val enabledTime: Instant?, + // TODO: Check how this behaves during rolling upgrade/multi-version cluster + // Can read/write and parsing break if it's done from an old -> new version of the plugin? + val monitorType: MonitorType, + val user: User?, + val schemaVersion: Int = NO_SCHEMA_VERSION, + val inputs: List, + val triggers: List, + val uiMetadata: Map +) : ScheduledJob { + + override val type = MONITOR_TYPE + + init { + // Ensure that trigger ids are unique within a monitor + val triggerIds = mutableSetOf() + triggers.forEach { trigger -> + require(triggerIds.add(trigger.id)) { "Duplicate trigger id: ${trigger.id}. Trigger ids must be unique." } + // Verify Trigger type based on Monitor type + when (monitorType) { + MonitorType.QUERY_LEVEL_MONITOR -> + require(trigger is QueryLevelTrigger) { "Incompatible trigger [${trigger.id}] for monitor type [$monitorType]" } + MonitorType.BUCKET_LEVEL_MONITOR -> + require(trigger is BucketLevelTrigger) { "Incompatible trigger [${trigger.id}] for monitor type [$monitorType]" } + MonitorType.CLUSTER_METRICS_MONITOR -> + require(trigger is QueryLevelTrigger) { "Incompatible trigger [${trigger.id}] for monitor type [$monitorType]" } + MonitorType.DOC_LEVEL_MONITOR -> + require(trigger is DocumentLevelTrigger) { "Incompatible trigger [${trigger.id}] for monitor type [$monitorType]" } + } + } + if (enabled) { + requireNotNull(enabledTime) + } else { + require(enabledTime == null) + } + require(inputs.size <= MONITOR_MAX_INPUTS) { "Monitors can only have $MONITOR_MAX_INPUTS search input." } + require(triggers.size <= MONITOR_MAX_TRIGGERS) { "Monitors can only support up to $MONITOR_MAX_TRIGGERS triggers." } + if (this.isBucketLevelMonitor()) { + inputs.forEach { input -> + require(input is SearchInput) { "Unsupported input [$input] for Monitor" } + // TODO: Keeping query validation simple for now, only term aggregations have full support for the "group by" on the + // initial release. Should either add tests for other aggregation types or add validation to prevent using them. + require(input.query.aggregations() != null && !input.query.aggregations().aggregatorFactories.isEmpty()) { + "At least one aggregation is required for the input [$input]" + } + } + } + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + id = sin.readString(), + version = sin.readLong(), + name = sin.readString(), + enabled = sin.readBoolean(), + schedule = Schedule.readFrom(sin), + lastUpdateTime = sin.readInstant(), + enabledTime = sin.readOptionalInstant(), + monitorType = sin.readEnum(MonitorType::class.java), + user = if (sin.readBoolean()) { + User(sin) + } else null, + schemaVersion = sin.readInt(), + inputs = sin.readList((Input)::readFrom), + triggers = sin.readList((Trigger)::readFrom), + uiMetadata = suppressWarning(sin.readMap()) + ) + + // This enum classifies different Monitors + // This is different from 'type' which denotes the Scheduled Job type + enum class MonitorType(val value: String) { + QUERY_LEVEL_MONITOR("query_level_monitor"), + BUCKET_LEVEL_MONITOR("bucket_level_monitor"), + CLUSTER_METRICS_MONITOR("cluster_metrics_monitor"), + DOC_LEVEL_MONITOR("doc_level_monitor"); + + override fun toString(): String { + return value + } + } + + /** Returns a representation of the monitor suitable for passing into painless and mustache scripts. */ + fun asTemplateArg(): Map { + return mapOf(_ID to id, _VERSION to version, NAME_FIELD to name, ENABLED_FIELD to enabled) + } + + fun toXContentWithUser(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + return createXContentBuilder(builder, params, false) + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + return createXContentBuilder(builder, params, true) + } + + private fun createXContentBuilder(builder: XContentBuilder, params: ToXContent.Params, secure: Boolean): XContentBuilder { + builder.startObject() + if (params.paramAsBoolean("with_type", false)) builder.startObject(type) + builder.field(TYPE_FIELD, type) + .field(SCHEMA_VERSION_FIELD, schemaVersion) + .field(NAME_FIELD, name) + .field(MONITOR_TYPE_FIELD, monitorType) + + if (!secure) { + builder.optionalUserField(USER_FIELD, user) + } + + builder.field(ENABLED_FIELD, enabled) + .optionalTimeField(ENABLED_TIME_FIELD, enabledTime) + .field(SCHEDULE_FIELD, schedule) + .field(INPUTS_FIELD, inputs.toTypedArray()) + .field(TRIGGERS_FIELD, triggers.toTypedArray()) + .optionalTimeField(LAST_UPDATE_TIME_FIELD, lastUpdateTime) + if (uiMetadata.isNotEmpty()) builder.field(UI_METADATA_FIELD, uiMetadata) + if (params.paramAsBoolean("with_type", false)) builder.endObject() + return builder.endObject() + } + + override fun fromDocument(id: String, version: Long): Monitor = copy(id = id, version = version) + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(id) + out.writeLong(version) + out.writeString(name) + out.writeBoolean(enabled) + if (schedule is CronSchedule) { + out.writeEnum(Schedule.TYPE.CRON) + } else { + out.writeEnum(Schedule.TYPE.INTERVAL) + } + schedule.writeTo(out) + out.writeInstant(lastUpdateTime) + out.writeOptionalInstant(enabledTime) + out.writeEnum(monitorType) + out.writeBoolean(user != null) + user?.writeTo(out) + out.writeInt(schemaVersion) + // Outputting type with each Input so that the generic Input.readFrom() can read it + out.writeVInt(inputs.size) + inputs.forEach { + if (it is SearchInput) out.writeEnum(Input.Type.SEARCH_INPUT) + else out.writeEnum(Input.Type.DOCUMENT_LEVEL_INPUT) + it.writeTo(out) + } + // Outputting type with each Trigger so that the generic Trigger.readFrom() can read it + out.writeVInt(triggers.size) + triggers.forEach { + when (it) { + is BucketLevelTrigger -> out.writeEnum(Trigger.Type.BUCKET_LEVEL_TRIGGER) + is DocumentLevelTrigger -> out.writeEnum(Trigger.Type.DOCUMENT_LEVEL_TRIGGER) + else -> out.writeEnum(Trigger.Type.QUERY_LEVEL_TRIGGER) + } + it.writeTo(out) + } + out.writeMap(uiMetadata) + } + + companion object { + const val MONITOR_TYPE = "monitor" + const val TYPE_FIELD = "type" + const val MONITOR_TYPE_FIELD = "monitor_type" + const val SCHEMA_VERSION_FIELD = "schema_version" + const val NAME_FIELD = "name" + const val USER_FIELD = "user" + const val ENABLED_FIELD = "enabled" + const val SCHEDULE_FIELD = "schedule" + const val TRIGGERS_FIELD = "triggers" + const val NO_ID = "" + const val NO_VERSION = 1L + const val INPUTS_FIELD = "inputs" + const val LAST_UPDATE_TIME_FIELD = "last_update_time" + const val UI_METADATA_FIELD = "ui_metadata" + const val ENABLED_TIME_FIELD = "enabled_time" + + // This is defined here instead of in ScheduledJob to avoid having the ScheduledJob class know about all + // the different subclasses and creating circular dependencies + val XCONTENT_REGISTRY = NamedXContentRegistry.Entry( + ScheduledJob::class.java, + ParseField(MONITOR_TYPE), + CheckedFunction { parse(it) } + ) + + @JvmStatic + @JvmOverloads + @Throws(IOException::class) + fun parse(xcp: XContentParser, id: String = NO_ID, version: Long = NO_VERSION): Monitor { + var name: String? = null + // Default to QUERY_LEVEL_MONITOR to cover Monitors that existed before the addition of MonitorType + var monitorType: String = MonitorType.QUERY_LEVEL_MONITOR.toString() + var user: User? = null + var schedule: Schedule? = null + var lastUpdateTime: Instant? = null + var enabledTime: Instant? = null + var uiMetadata: Map = mapOf() + var enabled = true + var schemaVersion = NO_SCHEMA_VERSION + val triggers: MutableList = mutableListOf() + val inputs: MutableList = mutableListOf() + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + when (fieldName) { + SCHEMA_VERSION_FIELD -> schemaVersion = xcp.intValue() + NAME_FIELD -> name = xcp.text() + MONITOR_TYPE_FIELD -> { + monitorType = xcp.text() + val allowedTypes = MonitorType.values().map { it.value } + if (!allowedTypes.contains(monitorType)) { + throw IllegalStateException("Monitor type should be one of $allowedTypes") + } + } + USER_FIELD -> user = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) null else User.parse(xcp) + ENABLED_FIELD -> enabled = xcp.booleanValue() + SCHEDULE_FIELD -> schedule = Schedule.parse(xcp) + INPUTS_FIELD -> { + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_ARRAY, + xcp.currentToken(), + xcp + ) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + val input = Input.parse(xcp) + if (input is ClusterMetricsInput) + supportedClusterMetricsSettings?.validateApiType(input) + inputs.add(input) + } + } + TRIGGERS_FIELD -> { + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_ARRAY, + xcp.currentToken(), + xcp + ) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + triggers.add(Trigger.parse(xcp)) + } + } + ENABLED_TIME_FIELD -> enabledTime = xcp.instant() + LAST_UPDATE_TIME_FIELD -> lastUpdateTime = xcp.instant() + UI_METADATA_FIELD -> uiMetadata = xcp.map() + else -> { + xcp.skipChildren() + } + } + } + + if (enabled && enabledTime == null) { + enabledTime = Instant.now() + } else if (!enabled) { + enabledTime = null + } + return Monitor( + id, + version, + requireNotNull(name) { "Monitor name is null" }, + enabled, + requireNotNull(schedule) { "Monitor schedule is null" }, + lastUpdateTime ?: Instant.now(), + enabledTime, + MonitorType.valueOf(monitorType.uppercase(Locale.ROOT)), + user, + schemaVersion, + inputs.toList(), + triggers.toList(), + uiMetadata + ) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): Monitor? { + return Monitor(sin) + } + + @Suppress("UNCHECKED_CAST") + fun suppressWarning(map: MutableMap?): MutableMap { + return map as MutableMap + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/QueryLevelTrigger.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/QueryLevelTrigger.kt new file mode 100644 index 00000000..98011ff5 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/QueryLevelTrigger.kt @@ -0,0 +1,177 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.CheckedFunction +import org.opensearch.common.ParseField +import org.opensearch.common.UUIDs +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.NamedXContentRegistry +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.alerting.model.Trigger.Companion.ACTIONS_FIELD +import org.opensearch.commons.alerting.model.Trigger.Companion.ID_FIELD +import org.opensearch.commons.alerting.model.Trigger.Companion.NAME_FIELD +import org.opensearch.commons.alerting.model.Trigger.Companion.SEVERITY_FIELD +import org.opensearch.commons.alerting.model.action.Action +import org.opensearch.script.Script +import java.io.IOException + +data class QueryLevelTrigger( + override val id: String = UUIDs.base64UUID(), + override val name: String, + override val severity: String, + override val actions: List, + val condition: Script +) : Trigger { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readString(), // id + sin.readString(), // name + sin.readString(), // severity + sin.readList(::Action), // actions + Script(sin) // condition + ) + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .startObject(QUERY_LEVEL_TRIGGER_FIELD) + .field(ID_FIELD, id) + .field(NAME_FIELD, name) + .field(SEVERITY_FIELD, severity) + .startObject(CONDITION_FIELD) + .field(SCRIPT_FIELD, condition) + .endObject() + .field(ACTIONS_FIELD, actions.toTypedArray()) + .endObject() + .endObject() + return builder + } + + override fun name(): String { + return QUERY_LEVEL_TRIGGER_FIELD + } + + /** Returns a representation of the trigger suitable for passing into painless and mustache scripts. */ + fun asTemplateArg(): Map { + return mapOf( + ID_FIELD to id, NAME_FIELD to name, SEVERITY_FIELD to severity, + ACTIONS_FIELD to actions.map { it.asTemplateArg() } + ) + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(id) + out.writeString(name) + out.writeString(severity) + out.writeCollection(actions) + condition.writeTo(out) + } + + companion object { + const val QUERY_LEVEL_TRIGGER_FIELD = "query_level_trigger" + const val CONDITION_FIELD = "condition" + const val SCRIPT_FIELD = "script" + + val XCONTENT_REGISTRY = NamedXContentRegistry.Entry( + Trigger::class.java, ParseField(QUERY_LEVEL_TRIGGER_FIELD), + CheckedFunction { parseInner(it) } + ) + + /** + * This parse method needs to account for both the old and new Trigger format. + * In the old format, only one Trigger existed (which is now QueryLevelTrigger) and it was + * not a named object. + * + * The parse() method in the Trigger interface needs to consume the outer START_OBJECT to be able + * to infer whether it is dealing with the old or new Trigger format. This means that the currentToken at + * the time this parseInner method is called could differ based on which format is being dealt with. + * + * Old Format + * ---------- + * { + * "id": ..., + * ^ + * Current token starts here + * "name" ..., + * ... + * } + * + * New Format + * ---------- + * { + * "query_level_trigger": { + * "id": ..., ^ Current token starts here + * "name": ..., + * ... + * } + * } + * + * It isn't typically conventional but this parse method will account for both START_OBJECT + * and FIELD_NAME as the starting token to cover both cases. + */ + @JvmStatic @Throws(IOException::class) + fun parseInner(xcp: XContentParser): QueryLevelTrigger { + var id = UUIDs.base64UUID() // assign a default triggerId if one is not specified + lateinit var name: String + lateinit var severity: String + lateinit var condition: Script + val actions: MutableList = mutableListOf() + + 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) { + ID_FIELD -> id = xcp.text() + NAME_FIELD -> name = xcp.text() + SEVERITY_FIELD -> severity = xcp.text() + CONDITION_FIELD -> { + xcp.nextToken() + condition = Script.parse(xcp) + require(condition.lang == Script.DEFAULT_SCRIPT_LANG) { + "Invalid script language. Allowed languages are [${Script.DEFAULT_SCRIPT_LANG}]" + } + xcp.nextToken() + } + ACTIONS_FIELD -> { + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_ARRAY, + xcp.currentToken(), + xcp + ) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + actions.add(Action.parse(xcp)) + } + } + } + xcp.nextToken() + } + + return QueryLevelTrigger( + name = requireNotNull(name) { "Trigger name is null" }, + severity = requireNotNull(severity) { "Trigger severity is null" }, + condition = requireNotNull(condition) { "Trigger condition is null" }, + actions = requireNotNull(actions) { "Trigger actions are null" }, + id = requireNotNull(id) { "Trigger id is null." } + ) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): QueryLevelTrigger { + return QueryLevelTrigger(sin) + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Schedule.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Schedule.kt new file mode 100644 index 00000000..68c174ca --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Schedule.kt @@ -0,0 +1,354 @@ +package org.opensearch.commons.alerting.model + +import com.cronutils.model.CronType +import com.cronutils.model.definition.CronDefinitionBuilder +import com.cronutils.model.time.ExecutionTime +import com.cronutils.parser.CronParser +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.model.BaseModel +import java.io.IOException +import java.time.DateTimeException +import java.time.Duration +import java.time.Instant +import java.time.ZoneId +import java.time.ZonedDateTime +import java.time.temporal.ChronoUnit +import java.time.zone.ZoneRulesException +import java.util.Locale + +sealed class Schedule : BaseModel { + enum class TYPE { CRON, INTERVAL } + companion object { + const val CRON_FIELD = "cron" + const val EXPRESSION_FIELD = "expression" + const val TIMEZONE_FIELD = "timezone" + const val PERIOD_FIELD = "period" + const val INTERVAL_FIELD = "interval" + const val UNIT_FIELD = "unit" + + val cronParser = CronParser(CronDefinitionBuilder.instanceDefinitionFor(CronType.UNIX)) + + @JvmStatic @Throws(IOException::class) + fun parse(xcp: XContentParser): Schedule { + var expression: String? = null + var timezone: ZoneId? = null + var interval: Int? = null + var unit: ChronoUnit? = null + var schedule: Schedule? = null + var type: TYPE? = null + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldname = xcp.currentName() + xcp.nextToken() + // If the type field has already been set the customer has provide more than one type of schedule. + if (type != null) { + throw IllegalArgumentException("You can only specify one type of schedule.") + } + when (fieldname) { + CRON_FIELD -> { + type = TYPE.CRON + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val cronFieldName = xcp.currentName() + xcp.nextToken() + when (cronFieldName) { + EXPRESSION_FIELD -> expression = xcp.textOrNull() + TIMEZONE_FIELD -> timezone = getTimeZone(xcp.text()) + } + } + } + PERIOD_FIELD -> { + type = TYPE.INTERVAL + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val cronFieldName = xcp.currentName() + xcp.nextToken() + when (cronFieldName) { + INTERVAL_FIELD -> interval = xcp.intValue() + UNIT_FIELD -> unit = ChronoUnit.valueOf(xcp.text().uppercase(Locale.getDefault())) + } + } + } + else -> { + throw IllegalArgumentException("Invalid field: [$fieldname] found in schedule.") + } + } + } + if (type == TYPE.CRON) { + schedule = CronSchedule( + requireNotNull(expression) { "Expression in cron schedule is null." }, + requireNotNull(timezone) { "Timezone in cron schedule is null." } + ) + } else if (type == TYPE.INTERVAL) { + schedule = IntervalSchedule( + requireNotNull(interval) { "Interval in period schedule is null." }, + requireNotNull(unit) { "Unit in period schedule is null." } + ) + } + return requireNotNull(schedule) { "Schedule is null." } + } + + @JvmStatic @Throws(IllegalArgumentException::class) + private fun getTimeZone(timeZone: String): ZoneId { + try { + return ZoneId.of(timeZone) + } catch (zre: ZoneRulesException) { + throw IllegalArgumentException("Timezone $timeZone is not supported") + } catch (dte: DateTimeException) { + throw IllegalArgumentException("Timezone $timeZone is not supported") + } + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): Schedule { + val type = sin.readEnum(Schedule.TYPE::class.java) + if (type == Schedule.TYPE.CRON) + return CronSchedule(sin) + else + return IntervalSchedule(sin) + } + } + + /** + * @param enabledTime is used in IntervalSchedule to calculate next time to execute the schedule. + */ + abstract fun nextTimeToExecute(enabledTime: Instant): Duration? + + /** + * @param expectedPreviousExecutionTime is the calculated previous execution time that should always be correct, + * the first time this is called the value passed in is the enabledTime which acts as the expectedPreviousExecutionTime + */ + abstract fun getExpectedNextExecutionTime(enabledTime: Instant, expectedPreviousExecutionTime: Instant?): Instant? + + /** + * Returns the start and end time for this schedule starting at the given start time (if provided). + * If not, the start time is assumed to be the last time the Schedule would have executed (if it's a Cron schedule) + * or [Instant.now] if it's an interval schedule. + * + * If this is a schedule that runs only once this function will return [Instant.now] for both start and end time. + */ + abstract fun getPeriodStartingAt(startTime: Instant?): Pair + + /** + * Returns the start and end time for this schedule ending at the given end time (if provided). + * If not, the end time is assumed to be the next time the Schedule would have executed (if it's a Cron schedule) + * or [Instant.now] if it's an interval schedule. + * + * If this is a schedule that runs only once this function will return [Instant.now] for both start and end time. + */ + abstract fun getPeriodEndingAt(endTime: Instant?): Pair + + abstract fun runningOnTime(lastExecutionTime: Instant?): Boolean +} + +/** + * @param testInstant Normally this not be set and it should only be used in unit test to control time. + */ +data class CronSchedule( + val expression: String, + val timezone: ZoneId, + // visible for testing + @Transient val testInstant: Instant? = null +) : Schedule() { + @Transient + val executionTime: ExecutionTime = ExecutionTime.forCron(cronParser.parse(expression)) + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readString(), // expression + sin.readZoneId() // timezone + ) + + companion object { + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): CronSchedule { + return CronSchedule(sin) + } + } + + /* + * @param enabledTime is not used in CronSchedule. + */ + override fun nextTimeToExecute(enabledTime: Instant): Duration? { + val zonedDateTime = ZonedDateTime.ofInstant(testInstant ?: Instant.now(), timezone) + val timeToNextExecution = executionTime.timeToNextExecution(zonedDateTime) + return timeToNextExecution.orElse(null) + } + + override fun getExpectedNextExecutionTime(enabledTime: Instant, expectedPreviousExecutionTime: Instant?): Instant? { + val zonedDateTime = ZonedDateTime.ofInstant(expectedPreviousExecutionTime ?: testInstant ?: Instant.now(), timezone) + val nextExecution = executionTime.nextExecution(zonedDateTime) + return nextExecution.orElse(null)?.toInstant() + } + + override fun getPeriodStartingAt(startTime: Instant?): Pair { + val realStartTime = if (startTime != null) { + startTime + } else { + // Probably the first time we're running. Try to figure out the last execution time + val lastExecutionTime = executionTime.lastExecution(ZonedDateTime.now(timezone)) + // This shouldn't happen unless the cron is configured to run only once, which our current cron syntax doesn't support + if (!lastExecutionTime.isPresent) { + val currentTime = Instant.now() + return Pair(currentTime, currentTime) + } + lastExecutionTime.get().toInstant() + } + val zonedDateTime = ZonedDateTime.ofInstant(realStartTime, timezone) + val newEndTime = executionTime.nextExecution(zonedDateTime).orElse(null) + return Pair(realStartTime, newEndTime?.toInstant() ?: realStartTime) + } + + override fun getPeriodEndingAt(endTime: Instant?): Pair { + val realEndTime = if (endTime != null) { + endTime + } else { + val nextExecutionTime = executionTime.nextExecution(ZonedDateTime.now(timezone)) + // This shouldn't happen unless the cron is configured to run only once which our current cron syntax doesn't support + if (!nextExecutionTime.isPresent) { + val currentTime = Instant.now() + return Pair(currentTime, currentTime) + } + nextExecutionTime.get().toInstant() + } + val zonedDateTime = ZonedDateTime.ofInstant(realEndTime, timezone) + val newStartTime = executionTime.lastExecution(zonedDateTime).orElse(null) + return Pair(newStartTime?.toInstant() ?: realEndTime, realEndTime) + } + + override fun runningOnTime(lastExecutionTime: Instant?): Boolean { + if (lastExecutionTime == null) { + return true + } + + val zonedDateTime = ZonedDateTime.ofInstant(testInstant ?: Instant.now(), timezone) + val expectedExecutionTime = executionTime.lastExecution(zonedDateTime) + + if (!expectedExecutionTime.isPresent) { + // At this point we know lastExecutionTime is not null, this should never happen. + // If expected execution time is null, we shouldn't have executed the ScheduledJob. + return false + } + val actualExecutionTime = ZonedDateTime.ofInstant(lastExecutionTime, timezone) + + return ChronoUnit.SECONDS.between(expectedExecutionTime.get(), actualExecutionTime) == 0L + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .startObject(CRON_FIELD) + .field(EXPRESSION_FIELD, expression) + .field(TIMEZONE_FIELD, timezone.id) + .endObject() + .endObject() + return builder + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(expression) + out.writeZoneId(timezone) + } +} + +data class IntervalSchedule( + val interval: Int, + val unit: ChronoUnit, + // visible for testing + @Transient val testInstant: Instant? = null +) : Schedule() { + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readInt(), // interval + sin.readEnum(ChronoUnit::class.java) // unit + ) + companion object { + @Transient + private val SUPPORTED_UNIT = listOf(ChronoUnit.MINUTES, ChronoUnit.HOURS, ChronoUnit.DAYS) + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): IntervalSchedule { + return IntervalSchedule(sin) + } + } + + init { + if (!SUPPORTED_UNIT.contains(unit)) { + throw IllegalArgumentException("Timezone $unit is not supported expected $SUPPORTED_UNIT") + } + + if (interval <= 0) { + throw IllegalArgumentException("Interval is not allowed to be 0 or negative") + } + } + + @Transient + private val intervalInMills = Duration.of(interval.toLong(), unit).toMillis() + + override fun nextTimeToExecute(enabledTime: Instant): Duration? { + val enabledTimeEpochMillis = enabledTime.toEpochMilli() + + val currentTime = testInstant ?: Instant.now() + val delta = currentTime.toEpochMilli() - enabledTimeEpochMillis + // Remainder of the Delta time is how much we have already spent waiting. + // We need to subtract remainder of that time from the interval time to get remaining schedule time to wait. + val remainingScheduleTime = intervalInMills - delta.rem(intervalInMills) + return Duration.of(remainingScheduleTime, ChronoUnit.MILLIS) + } + + override fun getExpectedNextExecutionTime(enabledTime: Instant, expectedPreviousExecutionTime: Instant?): Instant? { + val expectedPreviousExecutionTimeEpochMillis = (expectedPreviousExecutionTime ?: enabledTime).toEpochMilli() + // We still need to calculate the delta even when using expectedPreviousExecutionTime because the initial value passed in + // is the enabledTime (which also happens with cluster/node restart) + val currentTime = testInstant ?: Instant.now() + val delta = currentTime.toEpochMilli() - expectedPreviousExecutionTimeEpochMillis + // Remainder of the Delta time is how much we have already spent waiting. + // We need to subtract remainder of that time from the interval time to get remaining schedule time to wait. + val remainingScheduleTime = intervalInMills - delta.rem(intervalInMills) + return Instant.ofEpochMilli(currentTime.toEpochMilli() + remainingScheduleTime) + } + + override fun getPeriodStartingAt(startTime: Instant?): Pair { + val realStartTime = startTime ?: Instant.now() + val newEndTime = realStartTime.plusMillis(intervalInMills) + return Pair(realStartTime, newEndTime) + } + + override fun getPeriodEndingAt(endTime: Instant?): Pair { + val realEndTime = endTime ?: Instant.now() + val newStartTime = realEndTime.minusMillis(intervalInMills) + return Pair(newStartTime, realEndTime) + } + + override fun runningOnTime(lastExecutionTime: Instant?): Boolean { + if (lastExecutionTime == null) { + return true + } + + // Make sure the lastExecutionTime is less than interval time. + val delta = ChronoUnit.MILLIS.between(lastExecutionTime, testInstant ?: Instant.now()) + return 0 < delta && delta < intervalInMills + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .startObject(PERIOD_FIELD) + .field(INTERVAL_FIELD, interval) + .field(UNIT_FIELD, unit.name) + .endObject() + .endObject() + return builder + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeInt(interval) + out.writeEnum(unit) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/ScheduledJob.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/ScheduledJob.kt new file mode 100644 index 00000000..ac1037f0 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/ScheduledJob.kt @@ -0,0 +1,85 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.model.BaseModel +import java.io.IOException +import java.time.Instant + +interface ScheduledJob : BaseModel { + + fun toXContentWithType(builder: XContentBuilder): XContentBuilder = toXContent(builder, XCONTENT_WITH_TYPE) + + companion object { + /** The name of the ElasticSearch index in which we store jobs */ + const val SCHEDULED_JOBS_INDEX = ".opendistro-alerting-config" + const val DOC_LEVEL_QUERIES_INDEX = ".opensearch-alerting-queries" + + const val NO_ID = "" + + const val NO_VERSION = 1L + + private val XCONTENT_WITH_TYPE = ToXContent.MapParams(mapOf("with_type" to "true")) + + /** + * This function parses the job, delegating to the specific subtype parser registered in the [XContentParser.getXContentRegistry] + * at runtime. Each concrete job subclass is expected to register a parser in this registry. + * The Job's json representation is expected to be of the form: + * { "" : { } } + * + * If the job comes from an OpenSearch index it's [id] and [version] can also be supplied. + */ + @Throws(IOException::class) + fun parse(xcp: XContentParser, id: String = NO_ID, version: Long = NO_VERSION): ScheduledJob { + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.nextToken(), xcp) + XContentParserUtils.ensureExpectedToken(XContentParser.Token.FIELD_NAME, xcp.nextToken(), xcp) + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.nextToken(), xcp) + val job = xcp.namedObject(ScheduledJob::class.java, xcp.currentName(), null) + XContentParserUtils.ensureExpectedToken(XContentParser.Token.END_OBJECT, xcp.nextToken(), xcp) + return job.fromDocument(id, version) + } + + /** + * This function parses the job, but expects the type to be passed in. This is for the specific + * use case in sweeper where we first want to check if the job is allowed to be swept before + * trying to fully parse it. If you need to parse a job, you most likely want to use + * the above parse function. + */ + @Throws(IOException::class) + fun parse(xcp: XContentParser, type: String, id: String = NO_ID, version: Long = NO_VERSION): ScheduledJob { + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.nextToken(), xcp) + val job = xcp.namedObject(ScheduledJob::class.java, type, null) + XContentParserUtils.ensureExpectedToken(XContentParser.Token.END_OBJECT, xcp.nextToken(), xcp) + return job.fromDocument(id, version) + } + } + + /** The id of the job in the [SCHEDULED_JOBS_INDEX] or [NO_ID] if not persisted */ + val id: String + + /** The version of the job in the [SCHEDULED_JOBS_INDEX] or [NO_VERSION] if not persisted */ + val version: Long + + /** The name of the job */ + val name: String + + /** The type of the job */ + val type: String + + /** Controls whether the job will be scheduled or not */ + val enabled: Boolean + + /** The schedule for running the job */ + val schedule: Schedule + + /** The last time the job was updated */ + val lastUpdateTime: Instant + + /** The time the job was enabled */ + val enabledTime: Instant? + + /** Copy constructor for persisted jobs */ + fun fromDocument(id: String, version: Long): ScheduledJob +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/SearchInput.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/SearchInput.kt new file mode 100644 index 00000000..d12e935b --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/SearchInput.kt @@ -0,0 +1,87 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.CheckedFunction +import org.opensearch.common.ParseField +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.NamedXContentRegistry +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.search.builder.SearchSourceBuilder +import java.io.IOException + +data class SearchInput(val indices: List, val query: SearchSourceBuilder) : Input { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readStringList(), // indices + SearchSourceBuilder(sin) // query + ) + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + return builder.startObject() + .startObject(SEARCH_FIELD) + .field(INDICES_FIELD, indices.toTypedArray()) + .field(QUERY_FIELD, query) + .endObject() + .endObject() + } + + override fun name(): String { + return SEARCH_FIELD + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeStringCollection(indices) + query.writeTo(out) + } + + companion object { + const val INDICES_FIELD = "indices" + const val QUERY_FIELD = "query" + const val SEARCH_FIELD = "search" + + val XCONTENT_REGISTRY = NamedXContentRegistry.Entry(Input::class.java, ParseField("search"), CheckedFunction { parseInner(it) }) + + @JvmStatic @Throws(IOException::class) + fun parseInner(xcp: XContentParser): SearchInput { + val indices = mutableListOf() + lateinit var searchSourceBuilder: SearchSourceBuilder + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + when (fieldName) { + INDICES_FIELD -> { + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_ARRAY, + xcp.currentToken(), + xcp + ) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + indices.add(xcp.text()) + } + } + QUERY_FIELD -> { + searchSourceBuilder = SearchSourceBuilder.fromXContent(xcp, false) + } + } + } + + return SearchInput( + indices, + requireNotNull(searchSourceBuilder) { "SearchInput query is null" } + ) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): SearchInput { + return SearchInput(sin) + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Trigger.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Trigger.kt new file mode 100644 index 00000000..dfc2797d --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Trigger.kt @@ -0,0 +1,75 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.alerting.model.action.Action +import org.opensearch.commons.notifications.model.BaseModel +import java.io.IOException + +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); + + override fun toString(): String { + return value + } + } + + companion object { + const val ID_FIELD = "id" + const val NAME_FIELD = "name" + const val SEVERITY_FIELD = "severity" + const val ACTIONS_FIELD = "actions" + + @Throws(IOException::class) + fun parse(xcp: XContentParser): Trigger { + val trigger: Trigger + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + XContentParserUtils.ensureExpectedToken(XContentParser.Token.FIELD_NAME, xcp.nextToken(), xcp) + val triggerTypeNames = Type.values().map { it.toString() } + if (triggerTypeNames.contains(xcp.currentName())) { + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.nextToken(), xcp) + trigger = xcp.namedObject(Trigger::class.java, xcp.currentName(), null) + XContentParserUtils.ensureExpectedToken(XContentParser.Token.END_OBJECT, xcp.nextToken(), xcp) + } else { + // Infer the old Trigger (now called QueryLevelTrigger) when it is not defined as a named + // object to remain backwards compatible when parsing the old format + trigger = QueryLevelTrigger.parseInner(xcp) + XContentParserUtils.ensureExpectedToken(XContentParser.Token.END_OBJECT, xcp.currentToken(), xcp) + } + return trigger + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): Trigger { + return when (val type = sin.readEnum(Trigger.Type::class.java)) { + Type.QUERY_LEVEL_TRIGGER -> QueryLevelTrigger(sin) + Type.BUCKET_LEVEL_TRIGGER -> BucketLevelTrigger(sin) + Type.DOCUMENT_LEVEL_TRIGGER -> DocumentLevelTrigger(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") + } + } + } + + /** The id of the Trigger in the [SCHEDULED_JOBS_INDEX] */ + val id: String + + /** The name of the Trigger */ + val name: String + + /** The severity of the Trigger, used to classify the subsequent Alert */ + val severity: String + + /** The actions executed if the Trigger condition evaluates to true */ + val actions: List + + fun name(): String +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/action/Action.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/action/Action.kt new file mode 100644 index 00000000..e8b9f87e --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/action/Action.kt @@ -0,0 +1,178 @@ +package org.opensearch.commons.alerting.model.action + +import org.opensearch.common.UUIDs +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.model.BaseModel +import org.opensearch.script.Script +import java.io.IOException + +data class Action( + val name: String, + val destinationId: String, + val subjectTemplate: Script?, + val messageTemplate: Script, + val throttleEnabled: Boolean, + val throttle: Throttle?, + val id: String = UUIDs.base64UUID(), + val actionExecutionPolicy: ActionExecutionPolicy? = null +) : BaseModel { + + init { + if (subjectTemplate != null) { + require(subjectTemplate.lang == MUSTACHE) { "subject_template must be a mustache script" } + } + require(messageTemplate.lang == MUSTACHE) { "message_template must be a mustache script" } + + if (actionExecutionPolicy?.actionExecutionScope is PerExecutionActionScope) { + require(throttle == null) { "Throttle is currently not supported for per execution action scope" } + } + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readString(), // name + sin.readString(), // destinationId + sin.readOptionalWriteable(::Script), // subjectTemplate + Script(sin), // messageTemplate + sin.readBoolean(), // throttleEnabled + sin.readOptionalWriteable(::Throttle), // throttle + sin.readString(), // id + sin.readOptionalWriteable(::ActionExecutionPolicy) // actionExecutionPolicy + ) + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + val xContentBuilder = builder.startObject() + .field(ID_FIELD, id) + .field(NAME_FIELD, name) + .field(DESTINATION_ID_FIELD, destinationId) + .field(MESSAGE_TEMPLATE_FIELD, messageTemplate) + .field(THROTTLE_ENABLED_FIELD, throttleEnabled) + if (subjectTemplate != null) { + xContentBuilder.field(SUBJECT_TEMPLATE_FIELD, subjectTemplate) + } + if (throttle != null) { + xContentBuilder.field(THROTTLE_FIELD, throttle) + } + if (actionExecutionPolicy != null) { + xContentBuilder.field(ACTION_EXECUTION_POLICY_FIELD, actionExecutionPolicy) + } + return xContentBuilder.endObject() + } + + fun asTemplateArg(): Map { + return mapOf(NAME_FIELD to name) + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(name) + out.writeString(destinationId) + if (subjectTemplate != null) { + out.writeBoolean(true) + subjectTemplate.writeTo(out) + } else { + out.writeBoolean(false) + } + messageTemplate.writeTo(out) + out.writeBoolean(throttleEnabled) + if (throttle != null) { + out.writeBoolean(true) + throttle.writeTo(out) + } else { + out.writeBoolean(false) + } + out.writeString(id) + if (actionExecutionPolicy != null) { + out.writeBoolean(true) + actionExecutionPolicy.writeTo(out) + } else { + out.writeBoolean(false) + } + } + + companion object { + const val ID_FIELD = "id" + const val NAME_FIELD = "name" + const val DESTINATION_ID_FIELD = "destination_id" + const val SUBJECT_TEMPLATE_FIELD = "subject_template" + const val MESSAGE_TEMPLATE_FIELD = "message_template" + const val THROTTLE_ENABLED_FIELD = "throttle_enabled" + const val THROTTLE_FIELD = "throttle" + const val ACTION_EXECUTION_POLICY_FIELD = "action_execution_policy" + const val MUSTACHE = "mustache" + const val SUBJECT = "subject" + const val MESSAGE = "message" + const val MESSAGE_ID = "messageId" + + @JvmStatic + @Throws(IOException::class) + fun parse(xcp: XContentParser): Action { + var id = UUIDs.base64UUID() // assign a default action id if one is not specified + lateinit var name: String + lateinit var destinationId: String + var subjectTemplate: Script? = null // subject template could be null for some destinations + lateinit var messageTemplate: Script + var throttleEnabled = false + var throttle: Throttle? = null + var actionExecutionPolicy: ActionExecutionPolicy? = null + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + when (fieldName) { + ID_FIELD -> id = xcp.text() + NAME_FIELD -> name = xcp.textOrNull() + DESTINATION_ID_FIELD -> destinationId = xcp.textOrNull() + SUBJECT_TEMPLATE_FIELD -> { + subjectTemplate = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) null else + Script.parse(xcp, Script.DEFAULT_TEMPLATE_LANG) + } + MESSAGE_TEMPLATE_FIELD -> messageTemplate = Script.parse(xcp, Script.DEFAULT_TEMPLATE_LANG) + THROTTLE_FIELD -> { + throttle = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) null else Throttle.parse(xcp) + } + THROTTLE_ENABLED_FIELD -> { + throttleEnabled = xcp.booleanValue() + } + ACTION_EXECUTION_POLICY_FIELD -> { + actionExecutionPolicy = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) { + null + } else { + ActionExecutionPolicy.parse(xcp) + } + } + else -> { + throw IllegalStateException("Unexpected field: $fieldName, while parsing action") + } + } + } + + if (throttleEnabled) { + requireNotNull(throttle, { "Action throttle enabled but not set throttle value" }) + } + + return Action( + requireNotNull(name) { "Action name is null" }, + requireNotNull(destinationId) { "Destination id is null" }, + subjectTemplate, + requireNotNull(messageTemplate) { "Action message template is null" }, + throttleEnabled, + throttle, + id = requireNotNull(id), + actionExecutionPolicy = actionExecutionPolicy + ) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): Action { + return Action(sin) + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionPolicy.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionPolicy.kt new file mode 100644 index 00000000..044f3a24 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionPolicy.kt @@ -0,0 +1,92 @@ +package org.opensearch.commons.alerting.model.action + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.model.BaseModel +import java.io.IOException + +data class ActionExecutionPolicy( + val actionExecutionScope: ActionExecutionScope +) : BaseModel { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this ( + ActionExecutionScope.readFrom(sin) // actionExecutionScope + ) + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .field(ACTION_EXECUTION_SCOPE, actionExecutionScope) + return builder.endObject() + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + if (actionExecutionScope is PerAlertActionScope) { + out.writeEnum(ActionExecutionScope.Type.PER_ALERT) + } else { + out.writeEnum(ActionExecutionScope.Type.PER_EXECUTION) + } + actionExecutionScope.writeTo(out) + } + + companion object { + const val ACTION_EXECUTION_SCOPE = "action_execution_scope" + + @JvmStatic + @Throws(IOException::class) + fun parse(xcp: XContentParser): ActionExecutionPolicy { + lateinit var actionExecutionScope: ActionExecutionScope + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + when (fieldName) { + ACTION_EXECUTION_SCOPE -> actionExecutionScope = ActionExecutionScope.parse(xcp) + } + } + + return ActionExecutionPolicy( + requireNotNull(actionExecutionScope) { "Action execution scope is null" } + ) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): ActionExecutionPolicy { + return ActionExecutionPolicy(sin) + } + + /** + * The default [ActionExecutionPolicy] configuration for Bucket-Level Monitors. + * + * If Query-Level Monitors integrate the use of [ActionExecutionPolicy] then a separate default configuration + * will need to be made depending on the desired behavior. + */ + fun getDefaultConfigurationForBucketLevelMonitor(): ActionExecutionPolicy { + val defaultActionExecutionScope = PerAlertActionScope( + actionableAlerts = setOf(AlertCategory.DEDUPED, AlertCategory.NEW) + ) + return ActionExecutionPolicy(actionExecutionScope = defaultActionExecutionScope) + } + + /** + * The default [ActionExecutionPolicy] configuration for Document-Level Monitors. + * + * If Query-Level Monitors integrate the use of [ActionExecutionPolicy] then a separate default configuration + * will need to be made depending on the desired behavior. + */ + fun getDefaultConfigurationForDocumentLevelMonitor(): ActionExecutionPolicy { + val defaultActionExecutionScope = PerAlertActionScope( + actionableAlerts = setOf(AlertCategory.DEDUPED, AlertCategory.NEW) + ) + return ActionExecutionPolicy(actionExecutionScope = defaultActionExecutionScope) + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionScope.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionScope.kt new file mode 100644 index 00000000..2e252475 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionScope.kt @@ -0,0 +1,174 @@ +package org.opensearch.commons.alerting.model.action + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.model.BaseModel +import java.io.IOException +import java.lang.IllegalArgumentException + +sealed class ActionExecutionScope : BaseModel { + + enum class Type { PER_ALERT, PER_EXECUTION } + + companion object { + const val PER_ALERT_FIELD = "per_alert" + const val PER_EXECUTION_FIELD = "per_execution" + const val ACTIONABLE_ALERTS_FIELD = "actionable_alerts" + + @JvmStatic + @Throws(IOException::class) + fun parse(xcp: XContentParser): ActionExecutionScope { + var type: Type? = null + var actionExecutionScope: ActionExecutionScope? = null + val alertFilter = mutableSetOf() + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + // If the type field has already been set, the user has provided more than one type of schedule + if (type != null) { + throw IllegalArgumentException("You can only specify one type of action execution scope.") + } + + when (fieldName) { + PER_ALERT_FIELD -> { + type = Type.PER_ALERT + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val perAlertFieldName = xcp.currentName() + xcp.nextToken() + when (perAlertFieldName) { + ACTIONABLE_ALERTS_FIELD -> { + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_ARRAY, + xcp.currentToken(), + xcp + ) + val allowedCategories = AlertCategory.values().map { it.toString() } + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + val alertCategory = xcp.text() + if (!allowedCategories.contains(alertCategory)) { + throw IllegalStateException("Actionable alerts should be one of $allowedCategories") + } + alertFilter.add(AlertCategory.valueOf(alertCategory)) + } + } + else -> throw IllegalArgumentException( + "Invalid field [$perAlertFieldName] found in per alert action execution scope." + ) + } + } + } + PER_EXECUTION_FIELD -> { + type = Type.PER_EXECUTION + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + // empty while block + } + } + else -> throw IllegalArgumentException("Invalid field [$fieldName] found in action execution scope.") + } + } + + if (type == Type.PER_ALERT) { + actionExecutionScope = PerAlertActionScope(alertFilter) + } else if (type == Type.PER_EXECUTION) { + actionExecutionScope = PerExecutionActionScope() + } + + return requireNotNull(actionExecutionScope) { "Action execution scope is null." } + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): ActionExecutionScope { + val type = sin.readEnum(ActionExecutionScope.Type::class.java) + return if (type == Type.PER_ALERT) { + PerAlertActionScope(sin) + } else { + PerExecutionActionScope(sin) + } + } + } + + abstract fun getExecutionScope(): Type +} + +data class PerAlertActionScope( + val actionableAlerts: Set +) : ActionExecutionScope() { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readSet { si -> si.readEnum(AlertCategory::class.java) } // alertFilter + ) + + override fun getExecutionScope(): Type = Type.PER_ALERT + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .startObject(PER_ALERT_FIELD) + .field(ACTIONABLE_ALERTS_FIELD, actionableAlerts.toTypedArray()) + .endObject() + return builder.endObject() + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeCollection(actionableAlerts) { o, v -> o.writeEnum(v) } + } + + companion object { + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): PerAlertActionScope { + return PerAlertActionScope(sin) + } + } +} + +class PerExecutionActionScope() : ActionExecutionScope() { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this() + + override fun hashCode(): Int { + return javaClass.hashCode() + } + + // Creating an equals method that just checks class type rather than reference since this is currently stateless. + // Otherwise, it would have been a dataclass which would have handled this. + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other?.javaClass != javaClass) return false + return true + } + + override fun getExecutionScope(): Type = Type.PER_EXECUTION + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .startObject(PER_EXECUTION_FIELD) + .endObject() + return builder.endObject() + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + // body empty + } + + companion object { + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): PerExecutionActionScope { + return PerExecutionActionScope(sin) + } + } +} + +enum class AlertCategory { DEDUPED, NEW, COMPLETED } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/action/Throttle.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/action/Throttle.kt new file mode 100644 index 00000000..c7defeee --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/action/Throttle.kt @@ -0,0 +1,87 @@ +package org.opensearch.commons.alerting.model.action + +import org.apache.commons.codec.binary.StringUtils +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.model.BaseModel +import java.io.IOException +import java.time.temporal.ChronoUnit +import java.util.Locale + +data class Throttle( + val value: Int, + val unit: ChronoUnit +) : BaseModel { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this ( + sin.readInt(), // value + sin.readEnum(ChronoUnit::class.java) // unit + ) + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + return builder.startObject() + .field(VALUE_FIELD, value) + .field(UNIT_FIELD, unit.name) + .endObject() + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeInt(value) + out.writeEnum(unit) + } + + companion object { + const val VALUE_FIELD = "value" + const val UNIT_FIELD = "unit" + + @JvmStatic + @Throws(IOException::class) + fun parse(xcp: XContentParser): Throttle { + var value: Int = 0 + var unit: ChronoUnit = ChronoUnit.MINUTES // only support MINUTES throttle unit currently + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + when (fieldName) { + UNIT_FIELD -> { + val unitString = xcp.text().uppercase(Locale.ROOT) + require(StringUtils.equals(unitString, ChronoUnit.MINUTES.name), { "Only support MINUTES throttle unit currently" }) + unit = ChronoUnit.valueOf(unitString) + } + VALUE_FIELD -> { + val currentToken = xcp.currentToken() + require(currentToken != XContentParser.Token.VALUE_NULL, { "Throttle value can't be null" }) + when { + currentToken.isValue -> { + value = xcp.intValue() + require(value > 0, { "Can only set positive throttle period" }) + } + else -> { + XContentParserUtils.throwUnknownToken(currentToken, xcp.tokenLocation) + } + } + } + + else -> { + throw IllegalStateException("Unexpected field: $fieldName, while parsing action") + } + } + } + return Throttle(value = value, unit = requireNotNull(unit)) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): Throttle { + return Throttle(sin) + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/settings/SupportedClusterMetricsSettings.kt b/src/main/kotlin/org/opensearch/commons/alerting/settings/SupportedClusterMetricsSettings.kt new file mode 100644 index 00000000..e414e3ee --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/settings/SupportedClusterMetricsSettings.kt @@ -0,0 +1,7 @@ +package org.opensearch.commons.alerting.settings + +import org.opensearch.commons.alerting.model.ClusterMetricsInput + +interface SupportedClusterMetricsSettings { + fun validateApiType(clusterMetricsInput: ClusterMetricsInput) +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/util/IndexUtils.kt b/src/main/kotlin/org/opensearch/commons/alerting/util/IndexUtils.kt new file mode 100644 index 00000000..eef89a0a --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/util/IndexUtils.kt @@ -0,0 +1,61 @@ +package org.opensearch.commons.alerting.util + +import org.opensearch.common.bytes.BytesReference +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.alerting.model.Monitor +import org.opensearch.commons.alerting.settings.SupportedClusterMetricsSettings +import org.opensearch.commons.authuser.User +import java.time.Instant + +class IndexUtils { + companion object { + const val NO_SCHEMA_VERSION = 0 + + const val MONITOR_MAX_INPUTS = 1 + + const val MONITOR_MAX_TRIGGERS = 10 + + const val _ID = "_id" + const val _VERSION = "_version" + + const val _SEQ_NO = "_seq_no" + const val _PRIMARY_TERM = "_primary_term" + + var supportedClusterMetricsSettings: SupportedClusterMetricsSettings? = null + } +} + +fun Monitor.isBucketLevelMonitor(): Boolean = this.monitorType == Monitor.MonitorType.BUCKET_LEVEL_MONITOR + +fun XContentBuilder.optionalUserField(name: String, user: User?): XContentBuilder { + if (user == null) { + return nullField(name) + } + return this.field(name, user) +} + +fun XContentBuilder.optionalTimeField(name: String, instant: Instant?): XContentBuilder { + if (instant == null) { + return nullField(name) + } + // second name as readableName should be different than first name + return this.timeField(name, "${name}_in_millis", instant.toEpochMilli()) +} + +fun XContentParser.instant(): Instant? { + return when { + currentToken() == XContentParser.Token.VALUE_NULL -> null + currentToken().isValue -> Instant.ofEpochMilli(longValue()) + else -> { + XContentParserUtils.throwUnknownToken(currentToken(), tokenLocation) + null // unreachable + } + } +} + +/** + * Extension function for ES 6.3 and above that duplicates the ES 6.2 XContentBuilder.string() method. + */ +fun XContentBuilder.string(): String = BytesReference.bytes(this).utf8ToString() diff --git a/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt new file mode 100644 index 00000000..2c2f5038 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt @@ -0,0 +1,44 @@ +package org.opensearch.commons.alerting + +import com.nhaarman.mockitokotlin2.whenever +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.Answers +import org.mockito.ArgumentMatchers +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.mock +import org.mockito.junit.jupiter.MockitoExtension +import org.opensearch.action.ActionListener +import org.opensearch.action.ActionType +import org.opensearch.client.node.NodeClient +import org.opensearch.commons.alerting.action.IndexMonitorRequest +import org.opensearch.commons.alerting.action.IndexMonitorResponse +import org.opensearch.commons.alerting.model.Monitor +import org.opensearch.index.seqno.SequenceNumbers + +@Suppress("UNCHECKED_CAST") +@ExtendWith(MockitoExtension::class) +internal class AlertingPluginInterfaceTests { + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private lateinit var client: NodeClient + + @Test + fun indexMonitor() { + 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 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.indexMonitor(client, request, listener) + Mockito.verify(listener, Mockito.times(1)).onResponse(ArgumentMatchers.eq(response)) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/MonitorTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/MonitorTests.kt new file mode 100644 index 00000000..bc34bf94 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/MonitorTests.kt @@ -0,0 +1,46 @@ +package org.opensearch.commons.alerting + +import org.junit.jupiter.api.Test +import org.opensearch.commons.alerting.model.Trigger +import org.opensearch.test.OpenSearchTestCase +import java.lang.IllegalArgumentException +import java.time.Instant + +internal class MonitorTests { + @Test + fun `test enabled time`() { + val monitor = randomQueryLevelMonitor() + val enabledMonitor = monitor.copy(enabled = true, enabledTime = Instant.now()) + try { + enabledMonitor.copy(enabled = false) + OpenSearchTestCase.fail("Disabling monitor with enabled time set should fail.") + } catch (e: IllegalArgumentException) { + } + + val disabledMonitor = monitor.copy(enabled = false, enabledTime = null) + + try { + disabledMonitor.copy(enabled = true) + OpenSearchTestCase.fail("Enabling monitor without enabled time should fail") + } catch (e: IllegalArgumentException) { + } + } + + @Test + fun `test max triggers`() { + val monitor = randomQueryLevelMonitor() + + val tooManyTriggers = mutableListOf() + var i = 0 + while (i <= 10) { + tooManyTriggers.add(randomQueryLevelTrigger()) + ++i + } + + try { + monitor.copy(triggers = tooManyTriggers) + OpenSearchTestCase.fail("Monitor with too many triggers should be rejected.") + } catch (e: IllegalArgumentException) { + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt new file mode 100644 index 00000000..68b860d4 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt @@ -0,0 +1,406 @@ +package org.opensearch.commons.alerting + +import com.carrotsearch.randomizedtesting.generators.RandomNumbers +import com.carrotsearch.randomizedtesting.generators.RandomStrings +import junit.framework.TestCase.assertNull +import org.apache.http.Header +import org.apache.http.HttpEntity +import org.opensearch.client.Request +import org.opensearch.client.RequestOptions +import org.opensearch.client.Response +import org.opensearch.client.RestClient +import org.opensearch.client.WarningsHandler +import org.opensearch.common.UUIDs +import org.opensearch.common.settings.Settings +import org.opensearch.common.xcontent.LoggingDeprecationHandler +import org.opensearch.common.xcontent.NamedXContentRegistry +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentFactory +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentType +import org.opensearch.commons.alerting.aggregation.bucketselectorext.BucketSelectorExtAggregationBuilder +import org.opensearch.commons.alerting.aggregation.bucketselectorext.BucketSelectorExtFilter +import org.opensearch.commons.alerting.model.BucketLevelTrigger +import org.opensearch.commons.alerting.model.ClusterMetricsInput +import org.opensearch.commons.alerting.model.DocLevelMonitorInput +import org.opensearch.commons.alerting.model.DocLevelQuery +import org.opensearch.commons.alerting.model.DocumentLevelTrigger +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.QueryLevelTrigger +import org.opensearch.commons.alerting.model.Schedule +import org.opensearch.commons.alerting.model.SearchInput +import org.opensearch.commons.alerting.model.Trigger +import org.opensearch.commons.alerting.model.action.Action +import org.opensearch.commons.alerting.model.action.ActionExecutionPolicy +import org.opensearch.commons.alerting.model.action.ActionExecutionScope +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.util.string +import org.opensearch.commons.authuser.User +import org.opensearch.index.query.QueryBuilders +import org.opensearch.script.Script +import org.opensearch.script.ScriptType +import org.opensearch.search.SearchModule +import org.opensearch.search.aggregations.bucket.terms.IncludeExclude +import org.opensearch.search.aggregations.bucket.terms.TermsAggregationBuilder +import org.opensearch.search.builder.SearchSourceBuilder +import java.time.Instant +import java.time.temporal.ChronoUnit +import java.util.Random + +const val ALL_ACCESS_ROLE = "all_access" + +fun randomQueryLevelMonitor( + name: String = RandomStrings.randomAsciiLettersOfLength(Random(), 10), + user: User = randomUser(), + inputs: List = listOf(SearchInput(emptyList(), SearchSourceBuilder().query(QueryBuilders.matchAllQuery()))), + schedule: Schedule = IntervalSchedule(interval = 5, unit = ChronoUnit.MINUTES), + enabled: Boolean = Random().nextBoolean(), + triggers: List = (1..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { randomQueryLevelTrigger() }, + enabledTime: Instant? = if (enabled) Instant.now().truncatedTo(ChronoUnit.MILLIS) else null, + lastUpdateTime: Instant = Instant.now().truncatedTo(ChronoUnit.MILLIS), + withMetadata: Boolean = false +): Monitor { + return Monitor( + name = name, monitorType = Monitor.MonitorType.QUERY_LEVEL_MONITOR, enabled = enabled, inputs = inputs, + schedule = schedule, triggers = triggers, enabledTime = enabledTime, lastUpdateTime = lastUpdateTime, user = user, + uiMetadata = if (withMetadata) mapOf("foo" to "bar") else mapOf() + ) +} + +// Monitor of older versions without security. +fun randomQueryLevelMonitorWithoutUser( + name: String = RandomStrings.randomAsciiLettersOfLength(Random(), 10), + inputs: List = listOf(SearchInput(emptyList(), SearchSourceBuilder().query(QueryBuilders.matchAllQuery()))), + schedule: Schedule = IntervalSchedule(interval = 5, unit = ChronoUnit.MINUTES), + enabled: Boolean = Random().nextBoolean(), + triggers: List = (1..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { randomQueryLevelTrigger() }, + enabledTime: Instant? = if (enabled) Instant.now().truncatedTo(ChronoUnit.MILLIS) else null, + lastUpdateTime: Instant = Instant.now().truncatedTo(ChronoUnit.MILLIS), + withMetadata: Boolean = false +): Monitor { + return Monitor( + name = name, monitorType = Monitor.MonitorType.QUERY_LEVEL_MONITOR, enabled = enabled, inputs = inputs, + schedule = schedule, triggers = triggers, enabledTime = enabledTime, lastUpdateTime = lastUpdateTime, user = null, + uiMetadata = if (withMetadata) mapOf("foo" to "bar") else mapOf() + ) +} + +fun randomBucketLevelMonitor( + name: String = RandomStrings.randomAsciiLettersOfLength(Random(), 10), + user: User = randomUser(), + inputs: List = listOf( + SearchInput( + emptyList(), + SearchSourceBuilder().query(QueryBuilders.matchAllQuery()) + .aggregation(TermsAggregationBuilder("test_agg").field("test_field")) + ) + ), + schedule: Schedule = IntervalSchedule(interval = 5, unit = ChronoUnit.MINUTES), + enabled: Boolean = Random().nextBoolean(), + triggers: List = (1..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { randomBucketLevelTrigger() }, + enabledTime: Instant? = if (enabled) Instant.now().truncatedTo(ChronoUnit.MILLIS) else null, + lastUpdateTime: Instant = Instant.now().truncatedTo(ChronoUnit.MILLIS), + withMetadata: Boolean = false +): Monitor { + return Monitor( + name = name, monitorType = Monitor.MonitorType.BUCKET_LEVEL_MONITOR, enabled = enabled, inputs = inputs, + schedule = schedule, triggers = triggers, enabledTime = enabledTime, lastUpdateTime = lastUpdateTime, user = user, + uiMetadata = if (withMetadata) mapOf("foo" to "bar") else mapOf() + ) +} + +fun randomClusterMetricsMonitor( + name: String = RandomStrings.randomAsciiLettersOfLength(Random(), 10), + user: User = randomUser(), + inputs: List = listOf(randomClusterMetricsInput()), + schedule: Schedule = IntervalSchedule(interval = 5, unit = ChronoUnit.MINUTES), + enabled: Boolean = Random().nextBoolean(), + triggers: List = (1..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { randomQueryLevelTrigger() }, + enabledTime: Instant? = if (enabled) Instant.now().truncatedTo(ChronoUnit.MILLIS) else null, + lastUpdateTime: Instant = Instant.now().truncatedTo(ChronoUnit.MILLIS), + withMetadata: Boolean = false +): Monitor { + return Monitor( + name = name, monitorType = Monitor.MonitorType.CLUSTER_METRICS_MONITOR, enabled = enabled, inputs = inputs, + schedule = schedule, triggers = triggers, enabledTime = enabledTime, lastUpdateTime = lastUpdateTime, user = user, + uiMetadata = if (withMetadata) mapOf("foo" to "bar") else mapOf() + ) +} + +fun randomDocumentLevelMonitor( + name: String = RandomStrings.randomAsciiLettersOfLength(Random(), 10), + user: User? = randomUser(), + inputs: List = listOf(DocLevelMonitorInput("description", listOf("index"), emptyList())), + schedule: Schedule = IntervalSchedule(interval = 5, unit = ChronoUnit.MINUTES), + enabled: Boolean = Random().nextBoolean(), + triggers: List = (1..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { randomQueryLevelTrigger() }, + enabledTime: Instant? = if (enabled) Instant.now().truncatedTo(ChronoUnit.MILLIS) else null, + lastUpdateTime: Instant = Instant.now().truncatedTo(ChronoUnit.MILLIS), + withMetadata: Boolean = false +): Monitor { + return Monitor( + name = name, monitorType = Monitor.MonitorType.DOC_LEVEL_MONITOR, enabled = enabled, inputs = inputs, + schedule = schedule, triggers = triggers, enabledTime = enabledTime, lastUpdateTime = lastUpdateTime, user = user, + uiMetadata = if (withMetadata) mapOf("foo" to "bar") else mapOf() + ) +} + +fun randomQueryLevelTrigger( + id: String = UUIDs.base64UUID(), + name: String = RandomStrings.randomAsciiLettersOfLength(Random(), 10), + severity: String = "1", + condition: Script = randomScript(), + actions: List = mutableListOf(), + destinationId: String = "" +): QueryLevelTrigger { + return QueryLevelTrigger( + id = id, + name = name, + severity = severity, + condition = condition, + actions = if (actions.isEmpty()) (0..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { randomAction(destinationId = destinationId) } else actions + ) +} + +fun randomBucketLevelTrigger( + id: String = UUIDs.base64UUID(), + name: String = RandomStrings.randomAsciiLettersOfLength(Random(), 10), + severity: String = "1", + bucketSelector: BucketSelectorExtAggregationBuilder = randomBucketSelectorExtAggregationBuilder(name = id), + actions: List = mutableListOf(), + destinationId: String = "" +): BucketLevelTrigger { + return BucketLevelTrigger( + id = id, + name = name, + severity = severity, + bucketSelector = bucketSelector, + actions = if (actions.isEmpty()) randomActionsForBucketLevelTrigger(destinationId = destinationId) else actions + ) +} + +fun randomActionsForBucketLevelTrigger(min: Int = 0, max: Int = 10, destinationId: String = ""): List = + (min..RandomNumbers.randomIntBetween(Random(), 0, max)).map { randomActionWithPolicy(destinationId = destinationId) } + +fun randomDocumentLevelTrigger( + id: String = UUIDs.base64UUID(), + name: String = RandomStrings.randomAsciiLettersOfLength(Random(), 10), + severity: String = "1", + condition: Script = randomScript(), + actions: List = mutableListOf(), + destinationId: String = "" +): DocumentLevelTrigger { + return DocumentLevelTrigger( + id = id, + name = name, + severity = severity, + condition = condition, + actions = if (actions.isEmpty() && destinationId.isNotBlank()) + (0..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { randomAction(destinationId = destinationId) } + else actions + ) +} + +fun randomBucketSelectorExtAggregationBuilder( + name: String = RandomStrings.randomAsciiLettersOfLength(Random(), 10), + bucketsPathsMap: MutableMap = mutableMapOf("avg" to "10"), + script: Script = randomBucketSelectorScript(params = bucketsPathsMap), + parentBucketPath: String = "testPath", + filter: BucketSelectorExtFilter = BucketSelectorExtFilter(IncludeExclude("foo*", "bar*")) +): BucketSelectorExtAggregationBuilder { + return BucketSelectorExtAggregationBuilder(name, bucketsPathsMap, script, parentBucketPath, filter) +} + +fun randomBucketSelectorScript( + idOrCode: String = "params.avg >= 0", + params: Map = mutableMapOf("avg" to "10") +): Script { + return Script(Script.DEFAULT_SCRIPT_TYPE, Script.DEFAULT_SCRIPT_LANG, idOrCode, emptyMap(), params) +} + +fun randomScript(source: String = "return " + Random().nextBoolean().toString()): Script = Script(source) + +fun randomTemplateScript( + source: String, + params: Map = emptyMap() +): Script = Script(ScriptType.INLINE, Script.DEFAULT_TEMPLATE_LANG, source, params) + +fun randomAction( + name: String = RandomStrings.randomAsciiLettersOfLength(Random(), 10), + template: Script = randomTemplateScript("Hello World"), + destinationId: String = "", + throttleEnabled: Boolean = false, + throttle: Throttle = randomThrottle() +) = Action(name, destinationId, template, template, throttleEnabled, throttle, actionExecutionPolicy = null) + +fun randomActionWithPolicy( + name: String = RandomStrings.randomAsciiLettersOfLength(Random(), 10), + template: Script = randomTemplateScript("Hello World"), + destinationId: String = "", + throttleEnabled: Boolean = false, + throttle: Throttle = randomThrottle(), + actionExecutionPolicy: ActionExecutionPolicy? = randomActionExecutionPolicy() +): Action { + return if (actionExecutionPolicy?.actionExecutionScope is PerExecutionActionScope) { + // Return null for throttle when using PerExecutionActionScope since throttling is currently not supported for it + Action(name, destinationId, template, template, throttleEnabled, null, actionExecutionPolicy = actionExecutionPolicy) + } else { + Action(name, destinationId, template, template, throttleEnabled, throttle, actionExecutionPolicy = actionExecutionPolicy) + } +} + +fun randomThrottle( + value: Int = RandomNumbers.randomIntBetween(Random(), 60, 120), + unit: ChronoUnit = ChronoUnit.MINUTES +) = Throttle(value, unit) + +fun randomActionExecutionPolicy( + actionExecutionScope: ActionExecutionScope = randomActionExecutionScope() +) = ActionExecutionPolicy(actionExecutionScope) + +fun randomActionExecutionScope(): ActionExecutionScope { + return if (Random().nextBoolean()) { + val alertCategories = AlertCategory.values() + PerAlertActionScope(actionableAlerts = (1..RandomNumbers.randomIntBetween(Random(), 0, alertCategories.size)).map { alertCategories[it - 1] }.toSet()) + } else { + PerExecutionActionScope() + } +} + +fun randomDocLevelQuery( + id: String = RandomStrings.randomAsciiLettersOfLength(Random(), 10), + query: String = RandomStrings.randomAsciiLettersOfLength(Random(), 10), + name: String = "${RandomNumbers.randomIntBetween(Random(), 0, 5)}", + tags: List = mutableListOf(0..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { RandomStrings.randomAsciiLettersOfLength(Random(), 10) } +): DocLevelQuery { + return DocLevelQuery(id = id, query = query, name = name, tags = tags) +} + +fun randomDocLevelMonitorInput( + description: String = RandomStrings.randomAsciiLettersOfLength(Random(), RandomNumbers.randomIntBetween(Random(), 0, 10)), + indices: List = listOf(1..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { RandomStrings.randomAsciiLettersOfLength(Random(), 10) }, + queries: List = listOf(1..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { randomDocLevelQuery() } +): DocLevelMonitorInput { + return DocLevelMonitorInput(description = description, indices = indices, queries = queries) +} + +fun randomClusterMetricsInput( + path: String = ClusterMetricsInput.ClusterMetricType.CLUSTER_HEALTH.defaultPath, + pathParams: String = "", + url: String = "" +): ClusterMetricsInput { + return ClusterMetricsInput(path, pathParams, url) +} + +fun Monitor.toJsonString(): String { + val builder = XContentFactory.jsonBuilder() + return this.toXContent(builder, ToXContent.EMPTY_PARAMS).string() +} + +fun Monitor.toJsonStringWithUser(): String { + val builder = XContentFactory.jsonBuilder() + return this.toXContentWithUser(builder, ToXContent.EMPTY_PARAMS).string() +} + +fun randomUser(): User { + return User( + RandomStrings.randomAsciiLettersOfLength(Random(), 10), + listOf( + RandomStrings.randomAsciiLettersOfLength(Random(), 10), + RandomStrings.randomAsciiLettersOfLength(Random(), 10) + ), + listOf(RandomStrings.randomAsciiLettersOfLength(Random(), 10), ALL_ACCESS_ROLE), + listOf("test_attr=test") + ) +} + +fun randomUserEmpty(): User { + return User("", listOf(), listOf(), listOf()) +} + +/** + * Wrapper for [RestClient.performRequest] which was deprecated in ES 6.5 and is used in tests. This provides + * a single place to suppress deprecation warnings. This will probably need further work when the API is removed entirely + * but that's an exercise for another day. + */ +@Suppress("DEPRECATION") +fun RestClient.makeRequest( + method: String, + endpoint: String, + params: Map = emptyMap(), + entity: HttpEntity? = null, + vararg headers: Header +): Response { + val request = Request(method, endpoint) + // TODO: remove PERMISSIVE option after moving system index access to REST API call + val options = RequestOptions.DEFAULT.toBuilder() + options.setWarningsHandler(WarningsHandler.PERMISSIVE) + headers.forEach { options.addHeader(it.name, it.value) } + request.options = options.build() + params.forEach { request.addParameter(it.key, it.value) } + if (entity != null) { + request.entity = entity + } + return performRequest(request) +} + +/** + * Wrapper for [RestClient.performRequest] which was deprecated in ES 6.5 and is used in tests. This provides + * a single place to suppress deprecation warnings. This will probably need further work when the API is removed entirely + * but that's an exercise for another day. + */ +@Suppress("DEPRECATION") +fun RestClient.makeRequest( + method: String, + endpoint: String, + entity: HttpEntity? = null, + vararg headers: Header +): Response { + val request = Request(method, endpoint) + val options = RequestOptions.DEFAULT.toBuilder() + // TODO: remove PERMISSIVE option after moving system index access to REST API call + options.setWarningsHandler(WarningsHandler.PERMISSIVE) + headers.forEach { options.addHeader(it.name, it.value) } + request.options = options.build() + if (entity != null) { + request.entity = entity + } + return performRequest(request) +} + +fun builder(): XContentBuilder { + return XContentBuilder.builder(XContentType.JSON.xContent()) +} + +fun parser(xc: String): XContentParser { + val parser = XContentType.JSON.xContent().createParser(xContentRegistry(), LoggingDeprecationHandler.INSTANCE, xc) + parser.nextToken() + return parser +} + +fun xContentRegistry(): NamedXContentRegistry { + return NamedXContentRegistry( + listOf( + SearchInput.XCONTENT_REGISTRY, + DocLevelMonitorInput.XCONTENT_REGISTRY, + QueryLevelTrigger.XCONTENT_REGISTRY, + BucketLevelTrigger.XCONTENT_REGISTRY, + DocumentLevelTrigger.XCONTENT_REGISTRY + ) + SearchModule(Settings.EMPTY, emptyList()).namedXContents + ) +} + +fun assertUserNull(map: Map) { + val user = map["user"] + assertNull("User is not null", user) +} + +fun assertUserNull(monitor: Monitor) { + assertNull("User is not null", monitor.user) +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequestTests.kt new file mode 100644 index 00000000..ce26c7d1 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequestTests.kt @@ -0,0 +1,54 @@ +package org.opensearch.commons.alerting.action + +import org.junit.jupiter.api.Assertions +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 +import org.opensearch.commons.alerting.model.SearchInput +import org.opensearch.commons.alerting.randomQueryLevelMonitor +import org.opensearch.rest.RestRequest +import org.opensearch.search.builder.SearchSourceBuilder + +class IndexMonitorRequestTests { + + @Test + fun `test index monitor post request`() { + + val req = IndexMonitorRequest( + "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.POST, + randomQueryLevelMonitor().copy(inputs = listOf(SearchInput(emptyList(), SearchSourceBuilder()))) + ) + Assertions.assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = IndexMonitorRequest(sin) + Assertions.assertEquals("1234", newReq.monitorId) + Assertions.assertEquals(1L, newReq.seqNo) + Assertions.assertEquals(2L, newReq.primaryTerm) + Assertions.assertEquals(RestRequest.Method.POST, newReq.method) + Assertions.assertNotNull(newReq.monitor) + } + + @Test + fun `test index monitor put request`() { + + val req = IndexMonitorRequest( + "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + randomQueryLevelMonitor().copy(inputs = listOf(SearchInput(emptyList(), SearchSourceBuilder()))) + ) + Assertions.assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = IndexMonitorRequest(sin) + Assertions.assertEquals("1234", newReq.monitorId) + Assertions.assertEquals(1L, newReq.seqNo) + Assertions.assertEquals(2L, newReq.primaryTerm) + Assertions.assertEquals(RestRequest.Method.PUT, newReq.method) + Assertions.assertNotNull(newReq.monitor) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorResponseTests.kt new file mode 100644 index 00000000..f9332649 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorResponseTests.kt @@ -0,0 +1,47 @@ +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.CronSchedule +import org.opensearch.commons.alerting.model.Monitor +import org.opensearch.commons.alerting.randomUser +import java.time.Instant +import java.time.ZoneId + +class IndexMonitorResponseTests { + + @Test + fun `test index monitor response with monitor`() { + val cronExpression = "31 * * * *" // Run at minute 31. + val testInstance = Instant.ofEpochSecond(1538164858L) + + val cronSchedule = CronSchedule(cronExpression, ZoneId.of("Asia/Kolkata"), testInstance) + val monitor = Monitor( + id = "123", + version = 0L, + name = "test-monitor", + enabled = true, + schedule = cronSchedule, + lastUpdateTime = Instant.now(), + enabledTime = Instant.now(), + monitorType = Monitor.MonitorType.QUERY_LEVEL_MONITOR, + user = randomUser(), + schemaVersion = 0, + inputs = mutableListOf(), + triggers = mutableListOf(), + uiMetadata = mutableMapOf() + ) + val req = IndexMonitorResponse("1234", 1L, 2L, 0L, monitor) + Assertions.assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = IndexMonitorResponse(sin) + Assertions.assertEquals("1234", newReq.id) + Assertions.assertEquals(1L, newReq.version) + Assertions.assertNotNull(newReq.monitor) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInputTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInputTests.kt new file mode 100644 index 00000000..d9dcd1f3 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInputTests.kt @@ -0,0 +1,442 @@ +package org.opensearch.commons.alerting.model + +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class ClusterMetricsInputTests { + private var path = "/_cluster/health" + private var pathParams = "" + private var url = "" + + @Test + fun `test valid ClusterMetricsInput creation using HTTP URI component fields`() { + // GIVEN + val testUrl = "http://localhost:9200/_cluster/health" + + // WHEN + val clusterMetricsInput = ClusterMetricsInput(path, pathParams, url) + + // THEN + assertEquals(path, clusterMetricsInput.path) + assertEquals(pathParams, clusterMetricsInput.pathParams) + assertEquals(testUrl, clusterMetricsInput.url) + } + + @Test + fun `test valid ClusterMetricsInput creation using HTTP url field`() { + // GIVEN + path = "" + url = "http://localhost:9200/_cluster/health" + + // WHEN + val clusterMetricsInput = ClusterMetricsInput(path, pathParams, url) + + // THEN + assertEquals(url, clusterMetricsInput.url) + } + + @Test + fun `test valid ClusterMetricsInput creation using HTTPS url field`() { + // GIVEN + path = "" + url = "https://localhost:9200/_cluster/health" + + // WHEN + val clusterMetricsInput = ClusterMetricsInput(path, pathParams, url) + + // THEN + assertEquals(url, clusterMetricsInput.url) + } + + @Test + fun `test invalid path`() { + // GIVEN + path = "///" + + // WHEN + THEN + assertFailsWith("Invalid URL.") { + ClusterMetricsInput(path, pathParams, url) + } + } + + @Test + fun `test invalid url`() { + // GIVEN + url = "///" + + // WHEN + THEN + assertFailsWith("Invalid URL.") { + ClusterMetricsInput(path, pathParams, url) + } + } + + @Test + fun `test url field and URI component fields create equal URI`() { + // GIVEN + url = "http://localhost:9200/_cluster/health" + + // WHEN + val clusterMetricsInput = ClusterMetricsInput(path, pathParams, url) + + // THEN + assertEquals(path, clusterMetricsInput.path) + assertEquals(pathParams, clusterMetricsInput.pathParams) + assertEquals(url, clusterMetricsInput.url) + assertEquals(url, clusterMetricsInput.constructedUri.toString()) + } + + @Test + fun `test url field and URI component fields with path params create equal URI`() { + // GIVEN + path = "/_cluster/health/" + pathParams = "index1,index2,index3,index4,index5" + url = "http://localhost:9200/_cluster/health/index1,index2,index3,index4,index5" + + // WHEN + val clusterMetricsInput = ClusterMetricsInput(path, pathParams, url) + + // THEN + assertEquals(path, clusterMetricsInput.path) + assertEquals(pathParams, clusterMetricsInput.pathParams) + assertEquals(url, clusterMetricsInput.url) + assertEquals(url, clusterMetricsInput.constructedUri.toString()) + } + + @Test + fun `test url field and URI component fields create different URI`() { + // GIVEN + url = "http://localhost:9200/_cluster/stats" + + // WHEN + THEN + assertFailsWith("The provided URL and URI fields form different URLs.") { + ClusterMetricsInput(path, pathParams, url) + } + } + + @Test + fun `test url field and URI component fields with path params create different URI`() { + // GIVEN + pathParams = "index1,index2,index3,index4,index5" + url = "http://localhost:9200/_cluster/stats/index1,index2,index3,index4,index5" + + // WHEN + THEN + assertFailsWith("The provided URL and URI fields form different URLs.") { + ClusterMetricsInput(path, pathParams, url) + } + } + + @Test + fun `test ClusterMetricsInput creation when all inputs are empty`() { + // GIVEN + path = "" + pathParams = "" + url = "" + + // WHEN + THEN + assertFailsWith("The uri.api_type field, uri.path field, or uri.uri field must be defined.") { + ClusterMetricsInput(path, pathParams, url) + } + } + + @Test + fun `test ClusterMetricsInput creation when all inputs but path params are empty`() { + // GIVEN + path = "" + pathParams = "index1,index2,index3,index4,index5" + url = "" + + // WHEN + THEN + assertFailsWith("The uri.api_type field, uri.path field, or uri.uri field must be defined.") { + ClusterMetricsInput(path, pathParams, url) + } + } + + @Test + fun `test invalid scheme in url field`() { + // GIVEN + path = "" + url = "invalidScheme://localhost:9200/_cluster/health" + + // WHEN + THEN + assertFailsWith("Invalid URL.") { + ClusterMetricsInput(path, pathParams, url) + } + } + + @Test + fun `test invalid host in url field`() { + // GIVEN + path = "" + url = "http://127.0.0.1:9200/_cluster/health" + + // WHEN + THEN + assertFailsWith("Only host '${ClusterMetricsInput.SUPPORTED_HOST}' is supported.") { + ClusterMetricsInput(path, pathParams, url) + } + } + + @Test + fun `test invalid port in url field`() { + // GIVEN + path = "" + url = "http://localhost:${ClusterMetricsInput.SUPPORTED_PORT + 1}/_cluster/health" + + // WHEN + THEN + assertFailsWith("Only port '${ClusterMetricsInput.SUPPORTED_PORT}' is supported.") { + ClusterMetricsInput(path, pathParams, url) + } + } + + @Test + fun `test parsePathParams with no path params`() { + // GIVEN + val testUrl = "http://localhost:9200/_cluster/health" + val clusterMetricsInput = ClusterMetricsInput(path, pathParams, url) + + // WHEN + val params = clusterMetricsInput.parsePathParams() + + // THEN + assertEquals(pathParams, params) + assertEquals(testUrl, clusterMetricsInput.constructedUri.toString()) + } + + @Test + fun `test parsePathParams with path params as URI field`() { + // GIVEN + path = "/_cluster/health/" + pathParams = "index1,index2,index3,index4,index5" + val testUrl = "http://localhost:9200/_cluster/health/index1,index2,index3,index4,index5" + val clusterMetricsInput = ClusterMetricsInput(path, pathParams, url) + + // WHEN + val params = clusterMetricsInput.parsePathParams() + + // THEN + assertEquals(pathParams, params) + assertEquals(testUrl, clusterMetricsInput.constructedUri.toString()) + } + + @Test + fun `test parsePathParams with path params in url`() { + // GIVEN + path = "" + val testParams = "index1,index2,index3,index4,index5" + url = "http://localhost:9200/_cluster/health/index1,index2,index3,index4,index5" + val clusterMetricsInput = ClusterMetricsInput(path, pathParams, url) + + // WHEN + val params = clusterMetricsInput.parsePathParams() + + // THEN + assertEquals(testParams, params) + assertEquals(url, clusterMetricsInput.constructedUri.toString()) + } + + @Test + fun `test parsePathParams with no path params for ApiType that requires path params`() { + // GIVEN + path = "/_cat/snapshots" + + // WHEN + THEN + assertFailsWith("The API requires path parameters.") { + ClusterMetricsInput(path, pathParams, url) + } + } + + @Test + fun `test parsePathParams with path params for ApiType that doesn't support path params`() { + // GIVEN + path = "/_cluster/settings" + pathParams = "index1,index2,index3,index4,index5" + val clusterMetricsInput = ClusterMetricsInput(path, pathParams, url) + + // WHEN + THEN + assertFailsWith("The API does not use path parameters.") { + clusterMetricsInput.parsePathParams() + } + } + + @Test + fun `test parsePathParams with path params containing illegal characters`() { + var testCount = 0 // Start off with count of 1 to account for ApiType.BLANK + ILLEGAL_PATH_PARAMETER_CHARACTERS.forEach { character -> + // GIVEN + pathParams = "index1,index2,$character,index4,index5" + val clusterMetricsInput = ClusterMetricsInput(path, pathParams, url) + + // WHEN + THEN + assertFailsWith( + "The provided path parameters contain invalid characters or spaces. Please omit: " + "${ILLEGAL_PATH_PARAMETER_CHARACTERS.joinToString(" ")}" + ) { + clusterMetricsInput.parsePathParams() + } + testCount++ + } + assertEquals(ILLEGAL_PATH_PARAMETER_CHARACTERS.size, testCount) + } + + @Test + fun `test ClusterMetricsInput correctly determines ApiType when path is provided as URI component`() { + var testCount = 1 // Start off with count of 1 to account for ApiType.BLANK + ClusterMetricsInput.ClusterMetricType.values() + .filter { enum -> enum != ClusterMetricsInput.ClusterMetricType.BLANK } + .forEach { testApiType -> + // GIVEN + path = testApiType.defaultPath + pathParams = if (testApiType.supportsPathParams) "index1,index2,index3,index4,index5" else "" + + // WHEN + val clusterMetricsInput = ClusterMetricsInput(path, pathParams, url) + + // THEN + assertEquals(testApiType, clusterMetricsInput.clusterMetricType) + testCount++ + } + assertEquals(ClusterMetricsInput.ClusterMetricType.values().size, testCount) + } + + @Test + fun `test ClusterMetricsInput correctly determines ApiType when path and path params are provided as URI components`() { + var testCount = 1 // Start off with count of 1 to account for ApiType.BLANK + ClusterMetricsInput.ClusterMetricType.values() + .filter { enum -> enum != ClusterMetricsInput.ClusterMetricType.BLANK } + .forEach { testApiType -> + // GIVEN + path = testApiType.defaultPath + pathParams = "index1,index2,index3,index4,index5" + + // WHEN + val clusterMetricsInput = ClusterMetricsInput(path, pathParams, url) + + // THEN + assertEquals(testApiType, clusterMetricsInput.clusterMetricType) + testCount++ + } + assertEquals(ClusterMetricsInput.ClusterMetricType.values().size, testCount) + } + + @Test + fun `test ClusterMetricsInput correctly determines ApiType when path is provided in URL field`() { + var testCount = 1 // Start off with count of 1 to account for ApiType.BLANK + ClusterMetricsInput.ClusterMetricType.values() + .filter { enum -> enum != ClusterMetricsInput.ClusterMetricType.BLANK } + .forEach { testApiType -> + // GIVEN + path = "" + pathParams = if (testApiType.supportsPathParams) "index1,index2,index3,index4,index5" else "" + url = "http://localhost:9200${testApiType.defaultPath}" + + // WHEN + val clusterMetricsInput = ClusterMetricsInput(path, pathParams, url) + + // THEN + assertEquals(testApiType, clusterMetricsInput.clusterMetricType) + testCount++ + } + assertEquals(ClusterMetricsInput.ClusterMetricType.values().size, testCount) + } + + @Test + fun `test ClusterMetricsInput correctly determines ApiType when path and path params are provided in URL field`() { + var testCount = 1 // Start off with count of 1 to account for ApiType.BLANK + ClusterMetricsInput.ClusterMetricType.values() + .filter { enum -> enum != ClusterMetricsInput.ClusterMetricType.BLANK } + .forEach { testApiType -> + // GIVEN + path = "" + pathParams = if (testApiType.supportsPathParams) "/index1,index2,index3,index4,index5" else "" + url = "http://localhost:9200${testApiType.defaultPath}$pathParams" + + // WHEN + val clusterMetricsInput = ClusterMetricsInput(path, pathParams, url) + + // THEN + assertEquals(testApiType, clusterMetricsInput.clusterMetricType) + testCount++ + } + assertEquals(ClusterMetricsInput.ClusterMetricType.values().size, testCount) + } + + @Test + fun `test ClusterMetricsInput cannot determine ApiType when invalid path is provided as URI component`() { + // GIVEN + path = "/_cat/paws" + + // WHEN + THEN + assertFailsWith("The API could not be determined from the provided URI.") { + ClusterMetricsInput(path, pathParams, url) + } + } + + @Test + fun `test ClusterMetricsInput cannot determine ApiType when invalid path and path params are provided as URI components`() { + // GIVEN + path = "/_cat/paws" + pathParams = "index1,index2,index3,index4,index5" + + // WHEN + THEN + assertFailsWith("The API could not be determined from the provided URI.") { + ClusterMetricsInput(path, pathParams, url) + } + } + + @Test + fun `test ClusterMetricsInput cannot determine ApiType when invaid path is provided in URL`() { + // GIVEN + path = "" + url = "http://localhost:9200/_cat/paws" + + // WHEN + THEN + assertFailsWith("The API could not be determined from the provided URI.") { + ClusterMetricsInput(path, pathParams, url) + } + } + + @Test + fun `test ClusterMetricsInput cannot determine ApiType when invaid path and path params are provided in URL`() { + // GIVEN + path = "" + url = "http://localhost:9200/_cat/paws/index1,index2,index3,index4,index5" + + // WHEN + THEN + assertFailsWith("The API could not be determined from the provided URI.") { + ClusterMetricsInput(path, pathParams, url) + } + } + + @Test + fun `test parseEmptyFields populates empty path and path_params when url is provided`() { + // GIVEN + path = "" + pathParams = "" + val testPath = "/_cluster/health" + val testPathParams = "index1,index2,index3,index4,index5" + url = "http://localhost:9200$testPath$testPathParams" + + // WHEN + val clusterMetricsInput = ClusterMetricsInput(path, pathParams, url) + + // THEN + assertEquals(testPath, clusterMetricsInput.path) + assertEquals(testPathParams, clusterMetricsInput.pathParams) + assertEquals(url, clusterMetricsInput.url) + } + + @Test + fun `test parseEmptyFields populates empty url field when path and path_params are provided`() { + // GIVEN + path = "/_cluster/health/" + pathParams = "index1,index2,index3,index4,index5" + val testUrl = "http://localhost:9200$path$pathParams" + + // WHEN + val clusterMetricsInput = ClusterMetricsInput(path, pathParams, url) + + // THEN + assertEquals(path, clusterMetricsInput.path) + assertEquals(pathParams, clusterMetricsInput.pathParams) + assertEquals(testUrl, clusterMetricsInput.url) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/model/DocLevelMonitorInputTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/model/DocLevelMonitorInputTests.kt new file mode 100644 index 00000000..fb0ae8e3 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/DocLevelMonitorInputTests.kt @@ -0,0 +1,109 @@ +package org.opensearch.commons.alerting.model + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentType +import org.opensearch.commons.alerting.randomDocLevelMonitorInput +import org.opensearch.commons.alerting.randomDocLevelQuery +import org.opensearch.commons.alerting.util.string +import java.lang.IllegalArgumentException + +class DocLevelMonitorInputTests { + @Test + fun `test DocLevelQuery asTemplateArgs`() { + // GIVEN + val query = randomDocLevelQuery() + + // WHEN + val templateArgs = query.asTemplateArg() + + // THEN + Assertions.assertEquals( + templateArgs[DocLevelQuery.QUERY_ID_FIELD], + query.id, + "Template args 'id' field does not match:" + ) + Assertions.assertEquals( + templateArgs[DocLevelQuery.QUERY_FIELD], + query.query, + "Template args 'query' field does not match:" + ) + Assertions.assertEquals( + templateArgs[DocLevelQuery.NAME_FIELD], + query.name, + "Template args 'name' field does not match:" + ) + Assertions.assertEquals( + templateArgs[DocLevelQuery.TAGS_FIELD], + query.tags, + "Template args 'tags' field does not match:" + ) + } + + @Test + fun `test create Doc Level Query with invalid characters for name`() { + val badString = "query with space" + try { + randomDocLevelQuery(name = badString) + Assertions.fail("Expecting an illegal argument exception") + } catch (e: IllegalArgumentException) { + Assertions.assertEquals( + "They query name or tag, $badString, contains an invalid character: [' ','[',']','{','}','(',')']", + e.message + ) + } + } + + @Test + @Throws(IllegalArgumentException::class) + fun `test create Doc Level Query with invalid characters for tags`() { + val badString = "[(){}]" + try { + randomDocLevelQuery(tags = listOf(badString)) + Assertions.fail("Expecting an illegal argument exception") + } catch (e: IllegalArgumentException) { + Assertions.assertEquals( + "They query name or tag, $badString, contains an invalid character: [' ','[',']','{','}','(',')']", + e.message + ) + } + } + + @Test + fun `test DocLevelMonitorInput asTemplateArgs`() { + // GIVEN + val input = randomDocLevelMonitorInput() + + // test + input.toXContent(XContentBuilder.builder(XContentType.JSON.xContent()), ToXContent.EMPTY_PARAMS).string() + // assertEquals("test", inputString) + // test end + // WHEN + val templateArgs = input.asTemplateArg() + + // THEN + Assertions.assertEquals( + templateArgs[DocLevelMonitorInput.DESCRIPTION_FIELD], + input.description, + "Template args 'description' field does not match:" + ) + Assertions.assertEquals( + templateArgs[DocLevelMonitorInput.INDICES_FIELD], + input.indices, + "Template args 'indices' field does not match:" + ) + Assertions.assertEquals( + input.queries.size, + (templateArgs[DocLevelMonitorInput.QUERIES_FIELD] as List<*>).size, + "Template args 'queries' field does not contain the expected number of queries:" + ) + input.queries.forEach { + Assertions.assertTrue( + (templateArgs[DocLevelMonitorInput.QUERIES_FIELD] as List<*>).contains(it.asTemplateArg()), + "Template args 'queries' field does not match:" + ) + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/model/MockScheduledJob.kt b/src/test/kotlin/org/opensearch/commons/alerting/model/MockScheduledJob.kt new file mode 100644 index 00000000..178c6ba8 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/MockScheduledJob.kt @@ -0,0 +1,31 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import java.io.IOException +import java.time.Instant + +class MockScheduledJob( + override val id: String, + override val version: Long, + override val name: String, + override val type: String, + override val enabled: Boolean, + override val schedule: Schedule, + override var lastUpdateTime: Instant, + override val enabledTime: Instant? +) : ScheduledJob { + override fun fromDocument(id: String, version: Long): ScheduledJob { + TODO("not implemented") + } + + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + TODO("not implemented") + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + TODO("not implemented") + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/model/ScheduleTest.kt b/src/test/kotlin/org/opensearch/commons/alerting/model/ScheduleTest.kt new file mode 100644 index 00000000..6eee00c8 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/ScheduleTest.kt @@ -0,0 +1,334 @@ +package org.opensearch.commons.alerting.model + +import org.junit.jupiter.api.Test +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.commons.alerting.util.string +import java.time.Instant +import java.time.ZoneId +import java.time.ZonedDateTime +import java.time.temporal.ChronoUnit +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class ScheduleTest : XContentTestBase { + @Test + fun `test time zone conversion`() { + val cronExpression = "31 * * * *" // Run at minute 31. + // This is 2018-09-27 20:00:58 GMT which will in conversion lead to 30min 58 seconds IST + val testInstance = Instant.ofEpochSecond(1538164858L) + + val cronSchedule = CronSchedule(cronExpression, ZoneId.of("Asia/Kolkata"), testInstance) + val nextTimeToExecute = cronSchedule.nextTimeToExecute(Instant.now()) + assertNotNull(nextTimeToExecute, "There should be next execute time.") + assertEquals(2L, nextTimeToExecute.seconds, "Execute time should be 2 seconds") + } + + @Test + fun `test time zone`() { + val cronExpression = "0 11 * * 3" // Run at 11:00 on Wednesday. + // This is 2018-09-26 01:59:58 GMT which will in conversion lead to Wednesday 10:59:58 JST + val testInstance = Instant.ofEpochSecond(1537927198L) + + val cronSchedule = CronSchedule(cronExpression, ZoneId.of("Asia/Tokyo"), testInstance) + val nextTimeToExecute = cronSchedule.nextTimeToExecute(Instant.now()) + assertNotNull(nextTimeToExecute, "There should be next execute time.") + assertEquals(2L, nextTimeToExecute.seconds, "Execute time should be 2 seconds") + } + + @Test + fun `test cron calculates next time to execute after restart`() { + val cronExpression = "* * * * *" + // This is 2018-09-26 01:59:58 GMT + val testInstance = Instant.ofEpochSecond(1537927198L) + // This enabled time represents GMT: Wednesday, September 19, 2018 3:19:51 AM + val enabledTimeInstance = Instant.ofEpochSecond(1537327191) + + val cronSchedule = CronSchedule(cronExpression, ZoneId.of("America/Los_Angeles"), testInstance) + // The nextTimeToExecute should be the minute after the test instance, not enabledTimeInstance, replicating a cluster restart + val nextTimeToExecute = cronSchedule.getExpectedNextExecutionTime(enabledTimeInstance, null) + assertNotNull(nextTimeToExecute, "There should be next execute time") + assertEquals( + testInstance.plusSeconds(2L), nextTimeToExecute, + "nextTimeToExecute should be 2 seconds after test instance" + ) + } + + @Test + fun `test cron calculates next time to execute using cached previous time`() { + val cronExpression = "* * * * *" + // This is 2018-09-26 01:59:58 GMT + val previousExecutionTimeInstance = Instant.ofEpochSecond(1537927198L) + // This enabled time represents GMT: Wednesday, September 19, 2018 3:19:51 AM + val enabledTimeInstance = Instant.ofEpochSecond(1537327191) + + val cronSchedule = CronSchedule(cronExpression, ZoneId.of("America/Los_Angeles")) + // The nextTimeToExecute should be the minute after the previous execution time instance, not enabledTimeInstance + val nextTimeToExecute = cronSchedule.getExpectedNextExecutionTime(enabledTimeInstance, previousExecutionTimeInstance) + assertNotNull(nextTimeToExecute, "There should be next execute time") + assertEquals( + previousExecutionTimeInstance.plusSeconds(2L), nextTimeToExecute, + "nextTimeToExecute should be 2 seconds after test instance" + ) + } + + @Test + fun `test interval calculates next time to execute using enabled time`() { + // This enabled time represents 2018-09-26 01:59:58 GMT + val enabledTimeInstance = Instant.ofEpochSecond(1537927138L) + // This is 2018-09-26 01:59:59 GMT, which is 61 seconds after enabledTime + val testInstance = Instant.ofEpochSecond(1537927199L) + + val intervalSchedule = IntervalSchedule(1, ChronoUnit.MINUTES, testInstance) + + // The nextTimeToExecute should be 120 seconds after the enabled time + val nextTimeToExecute = intervalSchedule.getExpectedNextExecutionTime(enabledTimeInstance, null) + assertNotNull(nextTimeToExecute, "There should be next execute time") + assertEquals( + enabledTimeInstance.plusSeconds(120L), nextTimeToExecute, + "nextTimeToExecute should be 120 seconds seconds after enabled time" + ) + } + + @Test + fun `test interval calculates next time to execute using cached previous time`() { + // This is 2018-09-26 01:59:58 GMT + val previousExecutionTimeInstance = Instant.ofEpochSecond(1537927198L) + // This is 2018-09-26 02:00:00 GMT + val testInstance = Instant.ofEpochSecond(1537927200L) + // This enabled time represents 2018-09-26 01:58:58 GMT + val enabledTimeInstance = Instant.ofEpochSecond(1537927138L) + + val intervalSchedule = IntervalSchedule(1, ChronoUnit.MINUTES, testInstance) + + // The nextTimeToExecute should be the minute after the previous execution time instance + val nextTimeToExecute = intervalSchedule.getExpectedNextExecutionTime(enabledTimeInstance, previousExecutionTimeInstance) + assertNotNull(nextTimeToExecute, "There should be next execute time") + assertEquals( + previousExecutionTimeInstance.plusSeconds(60L), nextTimeToExecute, + "nextTimeToExecute should be 60 seconds after previous execution time" + ) + } + + @Test + fun `test cron schedule round trip`() { + val cronExpression = "0 * * * *" + val cronSchedule = CronSchedule(cronExpression, ZoneId.of("Asia/Tokyo")) + + val scheduleString = cronSchedule.toXContent(builder(), ToXContent.EMPTY_PARAMS).string() + val parsedSchedule = Schedule.parse(parser(scheduleString)) + + assertTrue(parsedSchedule is CronSchedule, "Parsed scheduled is not Cron Scheduled Type.") + assertEquals(cronSchedule, parsedSchedule, "Round tripping Cron Schedule doesn't work") + } + + @Test + fun `test interval schedule round trip`() { + val intervalSchedule = IntervalSchedule(1, ChronoUnit.MINUTES) + + val scheduleString = intervalSchedule.toXContent(builder(), ToXContent.EMPTY_PARAMS).string() + val parsedSchedule = Schedule.parse(parser(scheduleString)) + assertTrue(parsedSchedule is IntervalSchedule, "Parsed scheduled is not Interval Scheduled Type.") + assertEquals(intervalSchedule, parsedSchedule, "Round tripping Interval Schedule doesn't work") + } + + @Test + fun `test cron invalid missing timezone`() { + val scheduleString = "{\"cron\":{\"expression\":\"0 * * * *\"}}" + assertFailsWith(IllegalArgumentException::class, "Expected IllegalArgumentException") { + Schedule.parse(parser(scheduleString)) + } + } + + @Test + fun `test cron invalid timezone rule`() { + val scheduleString = "{\"cron\":{\"expression\":\"0 * * * *\",\"timezone\":\"Going/Nowhere\"}}" + assertFailsWith(IllegalArgumentException::class, "Expected IllegalArgumentException") { + Schedule.parse(parser(scheduleString)) + } + } + + @Test + fun `test cron invalid timezone offset`() { + val scheduleString = "{\"cron\":{\"expression\":\"0 * * * *\",\"timezone\":\"+++9\"}}" + assertFailsWith(IllegalArgumentException::class, "Expected IllegalArgumentException") { + Schedule.parse(parser(scheduleString)) + } + } + + @Test + fun `test invalid type`() { + val scheduleString = "{\"foobarzzz\":{\"expression\":\"0 * * * *\",\"timezone\":\"+++9\"}}" + assertFailsWith(IllegalArgumentException::class, "Expected IllegalArgumentException") { Schedule.parse(parser(scheduleString)) } + } + + @Test + fun `test two types`() { + val scheduleString = "{\"cron\":{\"expression\":\"0 * * * *\",\"timezone\":\"Asia/Tokyo\"}, \"period\":{\"interval\":\"1\",\"unit\":\"Minutes\"}}" + assertFailsWith(IllegalArgumentException::class, "Expected IllegalArgumentException") { + Schedule.parse(parser(scheduleString)) + } + } + + @Test + fun `test invalid cron expression`() { + val scheduleString = "{\"cron\":{\"expression\":\"5 * 1 * * *\",\"timezone\":\"Asia/Tokyo\"}}" + assertFailsWith(IllegalArgumentException::class, "Expected IllegalArgumentException") { + Schedule.parse(parser(scheduleString)) + } + } + + @Test + fun `test interval period starting at`() { + val intervalSchedule = IntervalSchedule(1, ChronoUnit.MINUTES) + + val (periodStartTime, periodEndTime) = intervalSchedule.getPeriodStartingAt(null) + + assertEquals(periodStartTime, periodEndTime.minus(1, ChronoUnit.MINUTES), "Period didn't match interval") + + val startTime = Instant.now() + // Kotlin has destructuring declarations but no destructuring assignments? Gee, thanks... + val (periodStartTime2, _) = intervalSchedule.getPeriodStartingAt(startTime) + assertEquals(startTime, periodStartTime2, "Periods doesn't start at provided start time") + } + + @Test + fun `test interval period ending at`() { + val intervalSchedule = IntervalSchedule(1, ChronoUnit.MINUTES) + + val (periodStartTime, periodEndTime) = intervalSchedule.getPeriodEndingAt(null) + + assertEquals(periodStartTime, periodEndTime.minus(1, ChronoUnit.MINUTES), "Period didn't match interval") + + val endTime = Instant.now() + // destructuring declarations but no destructuring assignments? Gee, thanks... https://youtrack.jetbrains.com/issue/KT-11362 + val (_, periodEndTime2) = intervalSchedule.getPeriodEndingAt(endTime) + assertEquals(endTime, periodEndTime2, "Periods doesn't end at provided end time") + } + + @Test + fun `test cron period starting at`() { + val cronSchedule = CronSchedule("0 * * * *", ZoneId.of("Asia/Tokyo")) + + val (startTime1, endTime) = cronSchedule.getPeriodStartingAt(null) + assertTrue(startTime1 <= Instant.now(), "startTime is in future; should be the last execution time") + assertTrue(cronSchedule.executionTime.isMatch(ZonedDateTime.ofInstant(endTime, ZoneId.of("Asia/Tokyo")))) + + val (startTime, _) = cronSchedule.getPeriodStartingAt(endTime) + assertEquals(startTime, endTime, "Subsequent period doesn't start at provided end time") + } + + @Test + fun `test cron period ending at`() { + val cronSchedule = CronSchedule("0 * * * *", ZoneId.of("Asia/Tokyo")) + + val (startTime, endTime1) = cronSchedule.getPeriodEndingAt(null) + assertTrue(endTime1 >= Instant.now(), "endTime is in past; should be the next execution time") + assertTrue(cronSchedule.executionTime.isMatch(ZonedDateTime.ofInstant(startTime, ZoneId.of("Asia/Tokyo")))) + + val (_, endTime2) = cronSchedule.getPeriodEndingAt(startTime) + assertEquals(endTime2, startTime, "Previous period doesn't end at provided start time") + } + + @Test + fun `cron job not running on time`() { + val cronSchedule = createTestCronSchedule() + + val lastExecutionTime = 1539715560L + assertFalse(cronSchedule.runningOnTime(Instant.ofEpochSecond(lastExecutionTime))) + } + + @Test + fun `cron job running on time`() { + val cronSchedule = createTestCronSchedule() + + val lastExecutionTime = 1539715620L + assertTrue(cronSchedule.runningOnTime(Instant.ofEpochSecond(lastExecutionTime))) + } + + @Test + fun `period job running exactly at interval`() { + val testInstance = Instant.ofEpochSecond(1539715678L) + val enabledTime = Instant.ofEpochSecond(1539615178L) + val intervalSchedule = IntervalSchedule(1, ChronoUnit.MINUTES, testInstance) + + val nextTimeToExecute = intervalSchedule.nextTimeToExecute(enabledTime) + assertNotNull(nextTimeToExecute, "There should be next execute time.") + assertEquals(60L, nextTimeToExecute.seconds, "Excepted 60 seconds but was ${nextTimeToExecute.seconds}") + } + + @Test + fun `period job 3 minutes`() { + val testInstance = Instant.ofEpochSecond(1539615226L) + val enabledTime = Instant.ofEpochSecond(1539615144L) + val intervalSchedule = IntervalSchedule(3, ChronoUnit.MINUTES, testInstance) + + val nextTimeToExecute = intervalSchedule.nextTimeToExecute(enabledTime) + assertNotNull(nextTimeToExecute, "There should be next execute time.") + assertEquals(98L, nextTimeToExecute.seconds, "Excepted 98 seconds but was ${nextTimeToExecute.seconds}") + } + + @Test + fun `period job running on time`() { + val intervalSchedule = createTestIntervalSchedule() + + val lastExecutionTime = 1539715620L + assertTrue(intervalSchedule.runningOnTime(Instant.ofEpochSecond(lastExecutionTime))) + } + + @Test + fun `period job not running on time`() { + val intervalSchedule = createTestIntervalSchedule() + + val lastExecutionTime = 1539715560L + assertFalse(intervalSchedule.runningOnTime(Instant.ofEpochSecond(lastExecutionTime))) + } + + @Test + fun `period job test null last execution time`() { + val intervalSchedule = createTestIntervalSchedule() + + assertTrue(intervalSchedule.runningOnTime(null)) + } + + private fun createTestIntervalSchedule(): IntervalSchedule { + val testInstance = Instant.ofEpochSecond(1539715678L) + val enabledTime = Instant.ofEpochSecond(1539615146L) + val intervalSchedule = IntervalSchedule(1, ChronoUnit.MINUTES, testInstance) + + val nextTimeToExecute = intervalSchedule.nextTimeToExecute(enabledTime) + assertNotNull(nextTimeToExecute, "There should be next execute time.") + assertEquals(28L, nextTimeToExecute.seconds, "Excepted 28 seconds but was ${nextTimeToExecute.seconds}") + + return intervalSchedule + } + + private fun createTestCronSchedule(): CronSchedule { + val cronExpression = "* * * * *" + val testInstance = Instant.ofEpochSecond(1539715678L) + + val cronSchedule = CronSchedule(cronExpression, ZoneId.of("UTC"), testInstance) + val nextTimeToExecute = cronSchedule.nextTimeToExecute(Instant.now()) + assertNotNull(nextTimeToExecute, "There should be next execute time.") + assertEquals(2L, nextTimeToExecute.seconds, "Execute time should be 2 seconds") + + return cronSchedule + } + + @Test + fun `test invalid interval units`() { + assertFailsWith(IllegalArgumentException::class, "Expected IllegalArgumentException") { + IntervalSchedule(1, ChronoUnit.SECONDS) + } + + assertFailsWith(IllegalArgumentException::class, "Expected IllegalArgumentException") { + IntervalSchedule(1, ChronoUnit.MONTHS) + } + + assertFailsWith(IllegalArgumentException::class, "Expected IllegalArgumentException") { + IntervalSchedule(-1, ChronoUnit.MINUTES) + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/model/WriteableTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/model/WriteableTests.kt new file mode 100644 index 00000000..9f5e26b9 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/WriteableTests.kt @@ -0,0 +1,157 @@ +package org.opensearch.commons.alerting.model + +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.action.Action +import org.opensearch.commons.alerting.model.action.ActionExecutionPolicy +import org.opensearch.commons.alerting.model.action.Throttle +import org.opensearch.commons.alerting.randomAction +import org.opensearch.commons.alerting.randomActionExecutionPolicy +import org.opensearch.commons.alerting.randomBucketLevelTrigger +import org.opensearch.commons.alerting.randomDocumentLevelTrigger +import org.opensearch.commons.alerting.randomQueryLevelMonitor +import org.opensearch.commons.alerting.randomQueryLevelTrigger +import org.opensearch.commons.alerting.randomThrottle +import org.opensearch.commons.alerting.randomUser +import org.opensearch.commons.alerting.randomUserEmpty +import org.opensearch.commons.authuser.User +import org.opensearch.search.builder.SearchSourceBuilder + +class WriteableTests { + + @Test + fun `test throttle as stream`() { + val throttle = randomThrottle() + val out = BytesStreamOutput() + throttle.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newThrottle = Throttle(sin) + Assertions.assertEquals(throttle, newThrottle, "Round tripping Throttle doesn't work") + } + + @Test + fun `test action as stream`() { + val action = randomAction() + val out = BytesStreamOutput() + action.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newAction = Action(sin) + Assertions.assertEquals(action, newAction, "Round tripping Action doesn't work") + } + + @Test + fun `test action as stream with null subject template`() { + val action = randomAction().copy(subjectTemplate = null) + val out = BytesStreamOutput() + action.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newAction = Action(sin) + Assertions.assertEquals(action, newAction, "Round tripping Action doesn't work") + } + + @Test + fun `test action as stream with null throttle`() { + val action = randomAction().copy(throttle = null) + val out = BytesStreamOutput() + action.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newAction = Action(sin) + Assertions.assertEquals(action, newAction, "Round tripping Action doesn't work") + } + + @Test + fun `test action as stream with throttled enabled and null throttle`() { + val action = randomAction().copy(throttle = null).copy(throttleEnabled = true) + val out = BytesStreamOutput() + action.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newAction = Action(sin) + Assertions.assertEquals(action, newAction, "Round tripping Action doesn't work") + } + + @Test + fun `test query-level monitor as stream`() { + val monitor = randomQueryLevelMonitor().copy(inputs = listOf(SearchInput(emptyList(), SearchSourceBuilder()))) + val out = BytesStreamOutput() + monitor.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newMonitor = Monitor(sin) + Assertions.assertEquals(monitor, newMonitor, "Round tripping QueryLevelMonitor doesn't work") + } + + @Test + fun `test query-level trigger as stream`() { + val trigger = randomQueryLevelTrigger() + val out = BytesStreamOutput() + trigger.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newTrigger = QueryLevelTrigger.readFrom(sin) + Assertions.assertEquals(trigger, newTrigger, "Round tripping QueryLevelTrigger doesn't work") + } + + @Test + fun `test bucket-level trigger as stream`() { + val trigger = randomBucketLevelTrigger() + val out = BytesStreamOutput() + trigger.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newTrigger = BucketLevelTrigger.readFrom(sin) + Assertions.assertEquals(trigger, newTrigger, "Round tripping BucketLevelTrigger doesn't work") + } + + @Test + fun `test doc-level trigger as stream`() { + val trigger = randomDocumentLevelTrigger() + val out = BytesStreamOutput() + trigger.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newTrigger = DocumentLevelTrigger.readFrom(sin) + Assertions.assertEquals(trigger, newTrigger, "Round tripping DocumentLevelTrigger doesn't work") + } + + @Test + fun `test searchinput as stream`() { + val input = SearchInput(emptyList(), SearchSourceBuilder()) + val out = BytesStreamOutput() + input.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newInput = SearchInput(sin) + Assertions.assertEquals(input, newInput, "Round tripping MonitorRunResult doesn't work") + } + + @Test + fun `test user as stream`() { + val user = randomUser() + val out = BytesStreamOutput() + user.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newUser = User(sin) + Assertions.assertEquals(user, newUser, "Round tripping User doesn't work") + } + + @Test + fun `test empty user as stream`() { + val user = randomUserEmpty() + val out = BytesStreamOutput() + user.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newUser = User(sin) + Assertions.assertEquals(user, newUser, "Round tripping User doesn't work") + } + + @Test + fun `test action execution policy as stream`() { + val actionExecutionPolicy = randomActionExecutionPolicy() + val out = BytesStreamOutput() + actionExecutionPolicy.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newActionExecutionPolicy = ActionExecutionPolicy.readFrom(sin) + Assertions.assertEquals( + actionExecutionPolicy, + newActionExecutionPolicy, + "Round tripping ActionExecutionPolicy doesn't work" + ) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTestBase.kt b/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTestBase.kt new file mode 100644 index 00000000..45ef0d52 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTestBase.kt @@ -0,0 +1,27 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.settings.Settings +import org.opensearch.common.xcontent.LoggingDeprecationHandler +import org.opensearch.common.xcontent.NamedXContentRegistry +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentType +import org.opensearch.search.SearchModule + +interface XContentTestBase { + fun builder(): XContentBuilder { + return XContentBuilder.builder(XContentType.JSON.xContent()) + } + + fun parser(xc: String): XContentParser { + val parser = XContentType.JSON.xContent().createParser(xContentRegistry(), LoggingDeprecationHandler.INSTANCE, xc) + parser.nextToken() + return parser + } + + fun xContentRegistry(): NamedXContentRegistry { + return NamedXContentRegistry( + listOf(SearchInput.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 new file mode 100644 index 00000000..520a9d3c --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt @@ -0,0 +1,346 @@ +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.xcontent.ToXContent +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.parser +import org.opensearch.commons.alerting.randomAction +import org.opensearch.commons.alerting.randomActionExecutionPolicy +import org.opensearch.commons.alerting.randomActionWithPolicy +import org.opensearch.commons.alerting.randomBucketLevelMonitor +import org.opensearch.commons.alerting.randomBucketLevelTrigger +import org.opensearch.commons.alerting.randomQueryLevelMonitor +import org.opensearch.commons.alerting.randomQueryLevelMonitorWithoutUser +import org.opensearch.commons.alerting.randomQueryLevelTrigger +import org.opensearch.commons.alerting.randomThrottle +import org.opensearch.commons.alerting.randomUser +import org.opensearch.commons.alerting.randomUserEmpty +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.index.query.QueryBuilders +import org.opensearch.search.builder.SearchSourceBuilder +import java.time.temporal.ChronoUnit +import kotlin.test.assertFailsWith + +class XContentTests { + + @Test + fun `test action parsing`() { + val action = randomAction() + val actionString = action.toXContent(builder(), ToXContent.EMPTY_PARAMS).string() + val parsedAction = Action.parse(parser(actionString)) + Assertions.assertEquals(action, parsedAction, "Round tripping Action doesn't work") + } + + @Test + fun `test action parsing with null subject template`() { + val action = randomAction().copy(subjectTemplate = null) + val actionString = action.toXContent(builder(), ToXContent.EMPTY_PARAMS).string() + val parsedAction = Action.parse(parser(actionString)) + Assertions.assertEquals(action, parsedAction, "Round tripping Action doesn't work") + } + + @Test + fun `test action parsing with null throttle`() { + val action = randomAction().copy(throttle = null) + val actionString = action.toXContent(builder(), ToXContent.EMPTY_PARAMS).string() + val parsedAction = Action.parse(parser(actionString)) + Assertions.assertEquals(action, parsedAction, "Round tripping Action doesn't work") + } + + fun `test action parsing with throttled enabled and null throttle`() { + val action = randomAction().copy(throttle = null).copy(throttleEnabled = true) + val actionString = action.toXContent(builder(), ToXContent.EMPTY_PARAMS).string() + assertFailsWith("Action throttle enabled but not set throttle value") { + Action.parse(parser(actionString)) + } + } + + @Test + fun `test action with per execution scope does not support throttling`() { + try { + randomActionWithPolicy().copy( + throttleEnabled = true, + throttle = Throttle(value = 5, unit = ChronoUnit.MINUTES), + actionExecutionPolicy = ActionExecutionPolicy(PerExecutionActionScope()) + ) + Assertions.fail("Creating an action with per execution scope and throttle enabled did not fail.") + } catch (ignored: IllegalArgumentException) { + } + } + + @Test + fun `test throttle parsing`() { + val throttle = randomThrottle() + val throttleString = throttle.toXContent(builder(), ToXContent.EMPTY_PARAMS).string() + val parsedThrottle = Throttle.parse(parser(throttleString)) + Assertions.assertEquals(throttle, parsedThrottle, "Round tripping Monitor doesn't work") + } + + @Test + fun `test throttle parsing with wrong unit`() { + val throttle = randomThrottle() + 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)) } + } + + @Test + fun `test throttle parsing with negative value`() { + 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)) } + } + + fun `test query-level monitor parsing`() { + val monitor = randomQueryLevelMonitor() + + val monitorString = monitor.toJsonStringWithUser() + val parsedMonitor = Monitor.parse(parser(monitorString)) + assertEquals("Round tripping QueryLevelMonitor doesn't work", monitor, parsedMonitor) + } + + @Test + fun `test monitor parsing with no name`() { + val monitorStringWithoutName = """ + { + "type": "monitor", + "enabled": false, + "schedule": { + "period": { + "interval": 1, + "unit": "MINUTES" + } + }, + "inputs": [], + "triggers": [] + } + """.trimIndent() + + assertFailsWith("Monitor name is null") { Monitor.parse(parser(monitorStringWithoutName)) } + } + + @Test + fun `test monitor parsing with no schedule`() { + val monitorStringWithoutSchedule = """ + { + "type": "monitor", + "name": "asdf", + "enabled": false, + "inputs": [], + "triggers": [] + } + """.trimIndent() + + assertFailsWith("Monitor schedule is null") { + Monitor.parse(parser(monitorStringWithoutSchedule)) + } + } + + @Test + fun `test bucket-level monitor parsing`() { + val monitor = randomBucketLevelMonitor() + + val monitorString = monitor.toJsonStringWithUser() + val parsedMonitor = Monitor.parse(parser(monitorString)) + Assertions.assertEquals(monitor, parsedMonitor, "Round tripping BucketLevelMonitor doesn't work") + } + + @Test + fun `test query-level trigger parsing`() { + val trigger = randomQueryLevelTrigger() + + val triggerString = trigger.toXContent(builder(), ToXContent.EMPTY_PARAMS).string() + val parsedTrigger = Trigger.parse(parser(triggerString)) + + Assertions.assertEquals(trigger, parsedTrigger, "Round tripping QueryLevelTrigger doesn't work") + } + + @Test + fun `test bucket-level trigger parsing`() { + val trigger = randomBucketLevelTrigger() + + 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 { + val repeatedTrigger = randomQueryLevelTrigger() + randomQueryLevelMonitor().copy(triggers = listOf(repeatedTrigger, repeatedTrigger)) + Assertions.fail("Creating a monitor with duplicate triggers did not fail.") + } catch (ignored: IllegalArgumentException) { + } + } + + @Test + fun `test user parsing`() { + val user = randomUser() + val userString = user.toXContent(builder(), ToXContent.EMPTY_PARAMS).string() + val parsedUser = User.parse(parser(userString)) + Assertions.assertEquals(user, parsedUser, "Round tripping user doesn't work") + } + + @Test + fun `test empty user parsing`() { + val user = randomUserEmpty() + val userString = user.toXContent(builder(), ToXContent.EMPTY_PARAMS).string() + + val parsedUser = User.parse(parser(userString)) + Assertions.assertEquals(user, parsedUser, "Round tripping user doesn't work") + Assertions.assertEquals("", parsedUser.name) + Assertions.assertEquals(0, parsedUser.roles.size) + } + + @Test + fun `test query-level monitor parsing without user`() { + val monitor = randomQueryLevelMonitorWithoutUser() + + val monitorString = monitor.toJsonString() + val parsedMonitor = Monitor.parse(parser(monitorString)) + Assertions.assertEquals(monitor, parsedMonitor, "Round tripping QueryLevelMonitor doesn't work") + Assertions.assertNull(parsedMonitor.user) + } + + @Test + fun `test old monitor format parsing`() { + val monitorString = """ + { + "type": "monitor", + "schema_version": 3, + "name": "asdf", + "user": { + "name": "admin123", + "backend_roles": [], + "roles": [ + "all_access", + "security_manager" + ], + "custom_attribute_names": [], + "user_requested_tenant": null + }, + "enabled": true, + "enabled_time": 1613530078244, + "schedule": { + "period": { + "interval": 1, + "unit": "MINUTES" + } + }, + "inputs": [ + { + "search": { + "indices": [ + "test_index" + ], + "query": { + "size": 0, + "query": { + "bool": { + "filter": [ + { + "range": { + "order_date": { + "from": "{{period_end}}||-1h", + "to": "{{period_end}}", + "include_lower": true, + "include_upper": true, + "format": "epoch_millis", + "boost": 1.0 + } + } + } + ], + "adjust_pure_negative": true, + "boost": 1.0 + } + }, + "aggregations": {} + } + } + } + ], + "triggers": [ + { + "id": "e_sc0XcB98Q42rHjTh4K", + "name": "abc", + "severity": "1", + "condition": { + "script": { + "source": "ctx.results[0].hits.total.value > 100000", + "lang": "painless" + } + }, + "actions": [] + } + ], + "last_update_time": 1614121489719 + } + """.trimIndent() + val parsedMonitor = Monitor.parse(parser(monitorString)) + Assertions.assertEquals( + Monitor.MonitorType.QUERY_LEVEL_MONITOR, + parsedMonitor.monitorType, + "Incorrect monitor type" + ) + Assertions.assertEquals(1, parsedMonitor.triggers.size, "Incorrect trigger count") + val trigger = parsedMonitor.triggers.first() + Assertions.assertTrue(trigger is QueryLevelTrigger, "Incorrect trigger type") + Assertions.assertEquals("abc", trigger.name, "Incorrect name for parsed trigger") + } + + @Test + fun `test creating an query-level monitor with invalid trigger type fails`() { + try { + val bucketLevelTrigger = randomBucketLevelTrigger() + randomQueryLevelMonitor().copy(triggers = listOf(bucketLevelTrigger)) + Assertions.fail("Creating a query-level monitor with bucket-level triggers did not fail.") + } catch (ignored: IllegalArgumentException) { + } + } + + @Test + fun `test creating an bucket-level monitor with invalid trigger type fails`() { + try { + val queryLevelTrigger = randomQueryLevelTrigger() + randomBucketLevelMonitor().copy(triggers = listOf(queryLevelTrigger)) + Assertions.fail("Creating a bucket-level monitor with query-level triggers did not fail.") + } catch (ignored: IllegalArgumentException) { + } + } + + @Test + fun `test creating an bucket-level monitor with invalid input fails`() { + try { + val invalidInput = SearchInput(emptyList(), SearchSourceBuilder().query(QueryBuilders.matchAllQuery())) + randomBucketLevelMonitor().copy(inputs = listOf(invalidInput)) + Assertions.fail("Creating an bucket-level monitor with an invalid input did not fail.") + } catch (ignored: IllegalArgumentException) { + } + } + + @Test + fun `test action execution policy`() { + val actionExecutionPolicy = randomActionExecutionPolicy() + val actionExecutionPolicyString = actionExecutionPolicy.toXContent(builder(), ToXContent.EMPTY_PARAMS).string() + val parsedActionExecutionPolicy = ActionExecutionPolicy.parse(parser(actionExecutionPolicyString)) + Assertions.assertEquals( + actionExecutionPolicy, + parsedActionExecutionPolicy, + "Round tripping ActionExecutionPolicy doesn't work" + ) + } +} From 33c1c33a91a69f738c36d16644c6cb0985de15cb Mon Sep 17 00:00:00 2001 From: Subhobrata Dey Date: Mon, 12 Sep 2022 13:13:46 -0700 Subject: [PATCH 074/149] upgrade 2.x to 2.4 (#246) Signed-off-by: Subhobrata Dey --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5c8be6c6..b7fa032e 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "2.3.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.4.0-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") buildVersionQualifier = System.getProperty("build.version_qualifier", "") kotlin_version = System.getProperty("kotlin.version", "1.6.10") From 3dce8f7caca1ffd7de92d610a566332514315640 Mon Sep 17 00:00:00 2001 From: Surya Sashank Nistala Date: Mon, 12 Sep 2022 15:24:43 -0700 Subject: [PATCH 075/149] Copy over monitor datasources config from alerting to common utils (#247) * copy over monitor datasources config from alerting to common utils Signed-off-by: Surya Sashank Nistala --- .../commons/alerting/model/DataSources.kt | 128 ++++++++++++++++++ .../commons/alerting/model/Monitor.kt | 20 ++- 2 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt new file mode 100644 index 00000000..d27bfdf3 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt @@ -0,0 +1,128 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.ToXContentObject +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import java.io.IOException + +data class DataSources( + /** Configures a custom query index name for the monitor. Creates a new index if index with given name not present.*/ + val queryIndex: String = ScheduledJob.DOC_LEVEL_QUERIES_INDEX, + + /** Configures a custom index to store findings for a monitor. Creates a new index if index with given name not present. + * If index is pre-existing, mapping is updated*/ + val findingsIndex: String = ".opensearch-alerting-finding-history-write", // AlertIndices.FINDING_HISTORY_WRITE_INDEX + + /** Configures a custom index to store alerts for a monitor. Creates a new index if index with given name not present. + * If index is pre-existing, mapping is updated. */ + val alertsIndex: String = ".opendistro-alerting-alerts", // AlertIndices.ALERT_INDEX + + /** Configures custom mappings by field type for query index. + * Custom query index mappings are configurable, only if a custom query index is configured too. */ + val queryIndexMappingsByType: Map> = mapOf() + +) : Writeable, ToXContentObject { + + init { + require(queryIndex.isNotEmpty()) { + "Query index cannot be empty" + } + require(findingsIndex.isNotEmpty()) { + "Findings index cannot be empty" + } + require(alertsIndex.isNotEmpty()) { + "Alerts index cannot be empty" + } + if (queryIndexMappingsByType.isNotEmpty()) { + require(queryIndex != ScheduledJob.DOC_LEVEL_QUERIES_INDEX) { + "Custom query index mappings are configurable only if a custom query index is configured too." + } + require( + queryIndexMappingsByType.size == 1 && + queryIndexMappingsByType.containsKey("text") && + queryIndexMappingsByType.get("text")?.size == 1 && + queryIndexMappingsByType.get("text")!!.containsKey("analyzer") + ) { + "Custom query index mappings are currently configurable only for 'text' fields and mapping parameter can only be 'analyzer'" + } + } + } + + @Throws(IOException::class) + @Suppress("UNCHECKED_CAST") + constructor(sin: StreamInput) : this( + queryIndex = sin.readString(), + findingsIndex = sin.readString(), + alertsIndex = sin.readString(), + queryIndexMappingsByType = sin.readMap() as Map> + ) + + @Suppress("UNCHECKED_CAST") + fun asTemplateArg(): Map { + return mapOf( + QUERY_INDEX_FIELD to queryIndex, + FINDINGS_INDEX_FIELD to findingsIndex, + ALERTS_INDEX_FIELD to alertsIndex, + QUERY_INDEX_MAPPINGS_BY_TYPE to queryIndexMappingsByType + ) as Map + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + builder.field(QUERY_INDEX_FIELD, queryIndex) + builder.field(FINDINGS_INDEX_FIELD, findingsIndex) + builder.field(ALERTS_INDEX_FIELD, alertsIndex) + builder.field(QUERY_INDEX_MAPPINGS_BY_TYPE, queryIndexMappingsByType as Map) + builder.endObject() + return builder + } + + companion object { + const val QUERY_INDEX_FIELD = "query_index" + const val FINDINGS_INDEX_FIELD = "findings_index" + const val ALERTS_INDEX_FIELD = "alerts_index" + const val QUERY_INDEX_MAPPINGS_BY_TYPE = "query_index_mappings_by_type" + + @JvmStatic + @Throws(IOException::class) + @Suppress("UNCHECKED_CAST") + fun parse(xcp: XContentParser): DataSources { + var queryIndex = "" + var findingsIndex = "" + var alertsIndex = "" + var queryIndexMappingsByType: Map> = mapOf() + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + when (fieldName) { + QUERY_INDEX_FIELD -> queryIndex = xcp.text() + FINDINGS_INDEX_FIELD -> findingsIndex = xcp.text() + ALERTS_INDEX_FIELD -> alertsIndex = xcp.text() + QUERY_INDEX_MAPPINGS_BY_TYPE -> queryIndexMappingsByType = xcp.map() as Map> + } + } + return DataSources( + queryIndex = queryIndex, + findingsIndex = findingsIndex, + alertsIndex = alertsIndex, + queryIndexMappingsByType = queryIndexMappingsByType + ) + } + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(queryIndex) + out.writeString(findingsIndex) + out.writeString(alertsIndex) + out.writeMap(queryIndexMappingsByType as Map) + } +} 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 6e7c9c45..9f3e22a4 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Monitor.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Monitor.kt @@ -39,7 +39,8 @@ data class Monitor( val schemaVersion: Int = NO_SCHEMA_VERSION, val inputs: List, val triggers: List, - val uiMetadata: Map + val uiMetadata: Map, + val dataSources: DataSources = DataSources() ) : ScheduledJob { override val type = MONITOR_TYPE @@ -96,7 +97,12 @@ data class Monitor( schemaVersion = sin.readInt(), inputs = sin.readList((Input)::readFrom), triggers = sin.readList((Trigger)::readFrom), - uiMetadata = suppressWarning(sin.readMap()) + uiMetadata = suppressWarning(sin.readMap()), + dataSources = if (sin.readBoolean()) { + DataSources(sin) + } else { + DataSources() + } ) // This enum classifies different Monitors @@ -144,6 +150,7 @@ data class Monitor( .field(TRIGGERS_FIELD, triggers.toTypedArray()) .optionalTimeField(LAST_UPDATE_TIME_FIELD, lastUpdateTime) if (uiMetadata.isNotEmpty()) builder.field(UI_METADATA_FIELD, uiMetadata) + builder.field(DATA_SOURCES_FIELD, dataSources) if (params.paramAsBoolean("with_type", false)) builder.endObject() return builder.endObject() } @@ -186,6 +193,8 @@ data class Monitor( it.writeTo(out) } out.writeMap(uiMetadata) + out.writeBoolean(dataSources != null) // for backward compatibility with pre-existing monitors which don't have datasources field + dataSources.writeTo(out) } companion object { @@ -203,6 +212,7 @@ data class Monitor( const val INPUTS_FIELD = "inputs" const val LAST_UPDATE_TIME_FIELD = "last_update_time" const val UI_METADATA_FIELD = "ui_metadata" + const val DATA_SOURCES_FIELD = "data_sources" const val ENABLED_TIME_FIELD = "enabled_time" // This is defined here instead of in ScheduledJob to avoid having the ScheduledJob class know about all @@ -229,6 +239,7 @@ data class Monitor( var schemaVersion = NO_SCHEMA_VERSION val triggers: MutableList = mutableListOf() val inputs: MutableList = mutableListOf() + var dataSources = DataSources() XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { @@ -274,6 +285,8 @@ data class Monitor( ENABLED_TIME_FIELD -> enabledTime = xcp.instant() LAST_UPDATE_TIME_FIELD -> lastUpdateTime = xcp.instant() UI_METADATA_FIELD -> uiMetadata = xcp.map() + DATA_SOURCES_FIELD -> dataSources = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) DataSources() + else DataSources.parse(xcp) else -> { xcp.skipChildren() } @@ -298,7 +311,8 @@ data class Monitor( schemaVersion, inputs.toList(), triggers.toList(), - uiMetadata + uiMetadata, + dataSources ) } From 68da16809377c25c92c2330772e5b06d0c5166fa Mon Sep 17 00:00:00 2001 From: Subhobrata Dey Date: Fri, 23 Sep 2022 11:20:09 -0700 Subject: [PATCH 076/149] expose delete monitor api from alerting (#251) Signed-off-by: Subhobrata Dey --- .../alerting/AlertingPluginInterface.kt | 20 ++++++++++ .../alerting/action/AlertingActions.kt | 5 +++ .../alerting/action/DeleteMonitorRequest.kt | 35 +++++++++++++++++ .../alerting/action/DeleteMonitorResponse.kt | 38 +++++++++++++++++++ .../alerting/AlertingPluginInterfaceTests.kt | 18 +++++++++ .../action/DeleteMonitorRequestTests.kt | 26 +++++++++++++ 6 files changed, 142 insertions(+) create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorResponse.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorRequestTests.kt diff --git a/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt index a3ea6df0..34402524 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt @@ -9,6 +9,8 @@ import org.opensearch.action.ActionResponse import org.opensearch.client.node.NodeClient import org.opensearch.common.io.stream.Writeable import org.opensearch.commons.alerting.action.AlertingActions +import org.opensearch.commons.alerting.action.DeleteMonitorRequest +import org.opensearch.commons.alerting.action.DeleteMonitorResponse import org.opensearch.commons.alerting.action.IndexMonitorRequest import org.opensearch.commons.alerting.action.IndexMonitorResponse import org.opensearch.commons.notifications.action.BaseResponse @@ -43,6 +45,24 @@ object AlertingPluginInterface { ) } + fun deleteMonitor( + client: NodeClient, + request: DeleteMonitorRequest, + listener: ActionListener + ) { + client.execute( + AlertingActions.DELETE_MONITOR_ACTION_TYPE, + request, + wrapActionListener(listener) { response -> + recreateObject(response) { + DeleteMonitorResponse( + 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 9e4b4003..258374c3 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt @@ -11,4 +11,9 @@ object AlertingActions { val INDEX_MONITOR_ACTION_TYPE = ActionType(INDEX_MONITOR_ACTION_NAME, ::IndexMonitorResponse) + + const val DELETE_MONITOR_ACTION_NAME = "cluster:admin/opendistro/alerting/monitor/delete" + + val DELETE_MONITOR_ACTION_TYPE = + ActionType(DELETE_MONITOR_ACTION_NAME, ::DeleteMonitorResponse) } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorRequest.kt new file mode 100644 index 00000000..2d236c36 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorRequest.kt @@ -0,0 +1,35 @@ +package org.opensearch.commons.alerting.action + +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionRequestValidationException +import org.opensearch.action.support.WriteRequest +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import java.io.IOException + +class DeleteMonitorRequest : ActionRequest { + + val monitorId: String + val refreshPolicy: WriteRequest.RefreshPolicy + + constructor(monitorId: String, refreshPolicy: WriteRequest.RefreshPolicy) : super() { + this.monitorId = monitorId + this.refreshPolicy = refreshPolicy + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + monitorId = sin.readString(), + refreshPolicy = WriteRequest.RefreshPolicy.readFrom(sin) + ) + + override fun validate(): ActionRequestValidationException? { + return null + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(monitorId) + refreshPolicy.writeTo(out) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorResponse.kt new file mode 100644 index 00000000..08766925 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorResponse.kt @@ -0,0 +1,38 @@ +package org.opensearch.commons.alerting.action + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.commons.alerting.util.IndexUtils +import org.opensearch.commons.notifications.action.BaseResponse + +class DeleteMonitorResponse : BaseResponse { + var id: String + var version: Long + + constructor( + id: String, + version: Long + ) : super() { + this.id = id + this.version = version + } + + constructor(sin: StreamInput) : this( + sin.readString(), // id + sin.readLong() // version + ) + + override fun writeTo(out: StreamOutput) { + out.writeString(id) + out.writeLong(version) + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + return builder.startObject() + .field(IndexUtils._ID, id) + .field(IndexUtils._VERSION, version) + .endObject() + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt index 2c2f5038..a7f79c9b 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt @@ -12,6 +12,8 @@ import org.mockito.junit.jupiter.MockitoExtension import org.opensearch.action.ActionListener import org.opensearch.action.ActionType import org.opensearch.client.node.NodeClient +import org.opensearch.commons.alerting.action.DeleteMonitorRequest +import org.opensearch.commons.alerting.action.DeleteMonitorResponse import org.opensearch.commons.alerting.action.IndexMonitorRequest import org.opensearch.commons.alerting.action.IndexMonitorResponse import org.opensearch.commons.alerting.model.Monitor @@ -41,4 +43,20 @@ internal class AlertingPluginInterfaceTests { AlertingPluginInterface.indexMonitor(client, request, listener) Mockito.verify(listener, Mockito.times(1)).onResponse(ArgumentMatchers.eq(response)) } + + @Test + fun deleteMonitor() { + val request = mock(DeleteMonitorRequest::class.java) + val response = DeleteMonitorResponse(Monitor.NO_ID, Monitor.NO_VERSION) + 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.deleteMonitor(client, request, listener) + Mockito.verify(listener, Mockito.times(1)).onResponse(ArgumentMatchers.eq(response)) + } } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorRequestTests.kt new file mode 100644 index 00000000..43fa43e3 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorRequestTests.kt @@ -0,0 +1,26 @@ +package org.opensearch.commons.alerting.action + +import org.junit.Assert +import org.junit.Test +import org.opensearch.action.support.WriteRequest +import org.opensearch.common.io.stream.BytesStreamOutput +import org.opensearch.common.io.stream.StreamInput + +class DeleteMonitorRequestTests { + + @Test + 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) + + 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) + } +} From d4a4aaa931bd773e1a737f149c9de4272cef6272 Mon Sep 17 00:00:00 2001 From: Surya Sashank Nistala Date: Fri, 23 Sep 2022 15:05:46 -0700 Subject: [PATCH 077/149] Move Findings and Alerts action, request, response and models from alerting to common-utils (#254) Signed-off-by: Surya Sashank Nistala --- .../alerting/AlertingPluginInterface.kt | 52 +++ .../alerting/action/AlertingActions.kt | 9 +- .../alerting/action/GetAlertsRequest.kt | 52 +++ .../alerting/action/GetAlertsResponse.kt | 47 +++ .../alerting/action/GetFindingsRequest.kt | 47 +++ .../alerting/action/GetFindingsResponse.kt | 61 +++ .../commons/alerting/alerts/AlertError.kt | 66 ++++ .../alerting/model/ActionExecutionResult.kt | 85 ++++ .../alerting/model/AggregationResultBucket.kt | 84 ++++ .../commons/alerting/model/Alert.kt | 373 ++++++++++++++++++ .../commons/alerting/model/DataSources.kt | 2 +- .../commons/alerting/model/Finding.kt | 140 +++++++ .../commons/alerting/model/FindingDocument.kt | 86 ++++ .../commons/alerting/model/FindingWithDocs.kt | 80 ++++ .../commons/alerting/model/Table.kt | 44 +++ .../opensearch/commons/alerting/AlertTests.kt | 61 +++ .../alerting/AlertingPluginInterfaceTests.kt | 47 +++ .../commons/alerting/TestHelpers.kt | 53 +++ .../alerting/action/GetAlertsRequestTests.kt | 58 +++ .../alerting/action/GetAlertsResponseTests.kt | 106 +++++ .../action/GetFindingsRequestTests.kt | 40 ++ .../commons/alerting/model/FindingTests.kt | 40 ++ .../commons/alerting/model/XContentTests.kt | 47 +++ 23 files changed, 1678 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponse.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponse.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/alerts/AlertError.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/ActionExecutionResult.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/AggregationResultBucket.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/FindingDocument.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/FindingWithDocs.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/Table.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequestTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponseTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/model/FindingTests.kt diff --git a/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt index 34402524..be511203 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt @@ -11,6 +11,10 @@ import org.opensearch.common.io.stream.Writeable import org.opensearch.commons.alerting.action.AlertingActions import org.opensearch.commons.alerting.action.DeleteMonitorRequest import org.opensearch.commons.alerting.action.DeleteMonitorResponse +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.IndexMonitorRequest import org.opensearch.commons.alerting.action.IndexMonitorResponse import org.opensearch.commons.notifications.action.BaseResponse @@ -63,6 +67,54 @@ object AlertingPluginInterface { ) } + /** + * Get Alerts interface. + * @param client Node client for making transport action + * @param request The request object + * @param listener The listener for getting response + */ + fun getAlerts( + client: NodeClient, + request: GetAlertsRequest, + listener: ActionListener + ) { + client.execute( + AlertingActions.GET_ALERTS_ACTION_TYPE, + request, + wrapActionListener(listener) { response -> + recreateObject(response) { + GetAlertsResponse( + it + ) + } + } + ) + } + + /** + * Get Findings interface. + * @param client Node client for making transport action + * @param request The request object + * @param listener The listener for getting response + */ + fun getFindings( + client: NodeClient, + request: GetFindingsRequest, + listener: ActionListener + ) { + client.execute( + AlertingActions.GET_FINDINGS_ACTION_TYPE, + request, + wrapActionListener(listener) { response -> + recreateObject(response) { + GetFindingsResponse( + 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 258374c3..c20c52f1 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt @@ -8,12 +8,19 @@ import org.opensearch.action.ActionType object AlertingActions { const val INDEX_MONITOR_ACTION_NAME = "cluster:admin/opendistro/alerting/monitor/write" + const val GET_ALERTS_ACTION_NAME = "cluster:admin/opendistro/alerting/alerts/get" + const val DELETE_MONITOR_ACTION_NAME = "cluster:admin/opendistro/alerting/monitor/delete" + const val GET_FINDINGS_ACTION_NAME = "cluster:admin/opensearch/alerting/findings/get" val INDEX_MONITOR_ACTION_TYPE = ActionType(INDEX_MONITOR_ACTION_NAME, ::IndexMonitorResponse) - const val DELETE_MONITOR_ACTION_NAME = "cluster:admin/opendistro/alerting/monitor/delete" + val GET_ALERTS_ACTION_TYPE = + ActionType(GET_ALERTS_ACTION_NAME, ::GetAlertsResponse) val DELETE_MONITOR_ACTION_TYPE = ActionType(DELETE_MONITOR_ACTION_NAME, ::DeleteMonitorResponse) + + val GET_FINDINGS_ACTION_TYPE = + ActionType(GET_FINDINGS_ACTION_NAME, ::GetFindingsResponse) } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequest.kt new file mode 100644 index 00000000..bdda4e93 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequest.kt @@ -0,0 +1,52 @@ +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 GetAlertsRequest : ActionRequest { + val table: Table + val severityLevel: String + val alertState: String + val monitorId: String? + val alertIndex: String? + + constructor( + table: Table, + severityLevel: String, + alertState: String, + monitorId: String?, + alertIndex: String? + ) : super() { + this.table = table + this.severityLevel = severityLevel + this.alertState = alertState + this.monitorId = monitorId + this.alertIndex = alertIndex + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + table = Table.readFrom(sin), + severityLevel = sin.readString(), + alertState = sin.readString(), + monitorId = sin.readOptionalString(), + alertIndex = sin.readOptionalString() + ) + + 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(monitorId) + out.writeOptionalString(alertIndex) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponse.kt new file mode 100644 index 00000000..6be61a1c --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponse.kt @@ -0,0 +1,47 @@ +package org.opensearch.commons.alerting.action + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.commons.alerting.model.Alert +import org.opensearch.commons.notifications.action.BaseResponse +import java.io.IOException +import java.util.Collections + +class GetAlertsResponse : BaseResponse { + val alerts: 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, + totalAlerts: Int? + ) : super() { + this.alerts = alerts + this.totalAlerts = totalAlerts + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + alerts = Collections.unmodifiableList(sin.readList(::Alert)), + totalAlerts = sin.readOptionalInt() + ) + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeCollection(alerts) + out.writeOptionalInt(totalAlerts) + } + + @Throws(IOException::class) + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .field("alerts", alerts) + .field("totalAlerts", totalAlerts) + + return builder.endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequest.kt new file mode 100644 index 00000000..4f852a64 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequest.kt @@ -0,0 +1,47 @@ +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 GetFindingsRequest : ActionRequest { + val findingId: String? + val table: Table + val monitorId: String? + val findingIndex: String? + + constructor( + findingId: String?, + table: Table, + monitorId: String? = null, + findingIndexName: String? = null + ) : super() { + this.findingId = findingId + this.table = table + this.monitorId = monitorId + this.findingIndex = findingIndexName + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + findingId = sin.readOptionalString(), + table = Table.readFrom(sin), + monitorId = sin.readOptionalString(), + findingIndexName = sin.readOptionalString() + ) + + override fun validate(): ActionRequestValidationException? { + return null + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeOptionalString(findingId) + table.writeTo(out) + out.writeOptionalString(monitorId) + out.writeOptionalString(findingIndex) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponse.kt new file mode 100644 index 00000000..216666be --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponse.kt @@ -0,0 +1,61 @@ +package org.opensearch.commons.alerting.action + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.commons.alerting.model.FindingWithDocs +import org.opensearch.commons.notifications.action.BaseResponse +import org.opensearch.rest.RestStatus +import java.io.IOException + +class GetFindingsResponse : BaseResponse { + private var status: RestStatus + var totalFindings: Int? + var findings: List + + constructor( + status: RestStatus, + totalFindings: Int?, + findings: List + ) : super() { + this.status = status + this.totalFindings = totalFindings + this.findings = findings + } + + @Throws(IOException::class) + constructor(sin: StreamInput) { + this.status = sin.readEnum(RestStatus::class.java) + val findings = mutableListOf() + this.totalFindings = sin.readOptionalInt() + var currentSize = sin.readInt() + for (i in 0 until currentSize) { + findings.add(FindingWithDocs.readFrom(sin)) + } + this.findings = findings + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeEnum(status) + out.writeOptionalInt(totalFindings) + out.writeInt(findings.size) + for (finding in findings) { + finding.writeTo(out) + } + } + + @Throws(IOException::class) + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .field("total_findings", totalFindings) + .field("findings", findings) + + return builder.endObject() + } + + override fun getStatus(): RestStatus { + return this.status + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/alerts/AlertError.kt b/src/main/kotlin/org/opensearch/commons/alerting/alerts/AlertError.kt new file mode 100644 index 00000000..9854f9e5 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/alerts/AlertError.kt @@ -0,0 +1,66 @@ +package org.opensearch.commons.alerting.alerts + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken +import org.opensearch.commons.alerting.util.instant +import org.opensearch.commons.alerting.util.optionalTimeField +import java.io.IOException +import java.time.Instant + +data class AlertError(val timestamp: Instant, val message: String) : Writeable, ToXContent { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readInstant(), // timestamp + sin.readString() // message + ) + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeInstant(timestamp) + out.writeString(message) + } + companion object { + + const val TIMESTAMP_FIELD = "timestamp" + const val MESSAGE_FIELD = "message" + + @JvmStatic + @Throws(IOException::class) + fun parse(xcp: XContentParser): AlertError { + + lateinit var timestamp: Instant + lateinit var message: String + + ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + when (fieldName) { + TIMESTAMP_FIELD -> timestamp = requireNotNull(xcp.instant()) + MESSAGE_FIELD -> message = xcp.text() + } + } + return AlertError(timestamp = timestamp, message = message) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): AlertError { + return AlertError(sin) + } + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + return builder.startObject() + .optionalTimeField(TIMESTAMP_FIELD, timestamp) + .field(MESSAGE_FIELD, message) + .endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/ActionExecutionResult.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/ActionExecutionResult.kt new file mode 100644 index 00000000..cf29d297 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/ActionExecutionResult.kt @@ -0,0 +1,85 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.ToXContentObject +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.alerting.util.instant +import org.opensearch.commons.alerting.util.optionalTimeField +import java.io.IOException +import java.time.Instant + +/** + * When an alert triggered, the trigger's actions will be executed. + * Action execution result records action throttle result and is a part of Alert. + */ +data class ActionExecutionResult( + val actionId: String, + val lastExecutionTime: Instant?, + val throttledCount: Int = 0 +) : Writeable, ToXContentObject { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readString(), // actionId + sin.readOptionalInstant(), // lastExecutionTime + sin.readInt() // throttledCount + ) + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + return builder.startObject() + .field(ACTION_ID_FIELD, actionId) + .optionalTimeField(LAST_EXECUTION_TIME_FIELD, lastExecutionTime) + .field(THROTTLED_COUNT_FIELD, throttledCount) + .endObject() + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(actionId) + out.writeOptionalInstant(lastExecutionTime) + out.writeInt(throttledCount) + } + + companion object { + const val ACTION_ID_FIELD = "action_id" + const val LAST_EXECUTION_TIME_FIELD = "last_execution_time" + const val THROTTLED_COUNT_FIELD = "throttled_count" + + @JvmStatic + @Throws(IOException::class) + fun parse(xcp: XContentParser): ActionExecutionResult { + lateinit var actionId: String + var throttledCount: Int = 0 + var lastExecutionTime: Instant? = null + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + when (fieldName) { + ACTION_ID_FIELD -> actionId = xcp.text() + THROTTLED_COUNT_FIELD -> throttledCount = xcp.intValue() + LAST_EXECUTION_TIME_FIELD -> lastExecutionTime = xcp.instant() + + else -> { + throw IllegalStateException("Unexpected field: $fieldName, while parsing action") + } + } + } + + requireNotNull(actionId) { "Must set action id" } + return ActionExecutionResult(actionId, lastExecutionTime, throttledCount) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): ActionExecutionResult { + return ActionExecutionResult(sin) + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/AggregationResultBucket.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/AggregationResultBucket.kt new file mode 100644 index 00000000..2c75332d --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/AggregationResultBucket.kt @@ -0,0 +1,84 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.ParsingException +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.ToXContentObject +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParser.Token +import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken +import java.io.IOException +import java.util.Locale + +data class AggregationResultBucket( + val parentBucketPath: String?, + val bucketKeys: List, + val bucket: Map? // TODO: Should reduce contents to only top-level to not include sub-aggs here +) : Writeable, ToXContentObject { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this(sin.readString(), sin.readStringList(), sin.readMap()) + + override fun writeTo(out: StreamOutput) { + out.writeString(parentBucketPath) + out.writeStringCollection(bucketKeys) + out.writeMap(bucket) + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + innerXContent(builder) + return builder.endObject() + } + + fun innerXContent(builder: XContentBuilder): XContentBuilder { + builder.startObject(CONFIG_NAME) + .field(PARENTS_BUCKET_PATH, parentBucketPath) + .field(BUCKET_KEYS, bucketKeys.toTypedArray()) + .field(BUCKET, bucket) + .endObject() + return builder + } + + companion object { + const val CONFIG_NAME = "agg_alert_content" + const val PARENTS_BUCKET_PATH = "parent_bucket_path" + const val BUCKET_KEYS = "bucket_keys" + private const val BUCKET = "bucket" + + fun parse(xcp: XContentParser): AggregationResultBucket { + var parentBucketPath: String? = null + var bucketKeys = mutableListOf() + var bucket: MutableMap? = null + ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + + if (CONFIG_NAME != xcp.currentName()) { + throw ParsingException( + xcp.tokenLocation, + String.format( + Locale.ROOT, "Failed to parse object: expecting token with name [%s] but found [%s]", + CONFIG_NAME, xcp.currentName() + ) + ) + } + while (xcp.nextToken() != Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + when (fieldName) { + PARENTS_BUCKET_PATH -> parentBucketPath = xcp.text() + BUCKET_KEYS -> { + ensureExpectedToken(Token.START_ARRAY, xcp.currentToken(), xcp) + while (xcp.nextToken() != Token.END_ARRAY) { + bucketKeys.add(xcp.text()) + } + } + BUCKET -> bucket = xcp.map() + } + } + return AggregationResultBucket(parentBucketPath, bucketKeys, bucket) + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt new file mode 100644 index 00000000..44b8b235 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt @@ -0,0 +1,373 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.lucene.uid.Versions +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken +import org.opensearch.commons.alerting.alerts.AlertError +import org.opensearch.commons.alerting.util.IndexUtils.Companion.NO_SCHEMA_VERSION +import org.opensearch.commons.alerting.util.instant +import org.opensearch.commons.alerting.util.optionalTimeField +import org.opensearch.commons.alerting.util.optionalUserField +import org.opensearch.commons.authuser.User +import java.io.IOException +import java.time.Instant + +data class Alert( + val id: String = NO_ID, + val version: Long = NO_VERSION, + val schemaVersion: Int = NO_SCHEMA_VERSION, + val monitorId: String, + val monitorName: String, + val monitorVersion: Long, + val monitorUser: User?, + val triggerId: String, + val triggerName: String, + val findingIds: List, + val relatedDocIds: List, + val state: State, + val startTime: Instant, + val endTime: Instant? = null, + val lastNotificationTime: Instant? = null, + val acknowledgedTime: Instant? = null, + val errorMessage: String? = null, + val errorHistory: List, + val severity: String, + val actionExecutionResults: List, + val aggregationResultBucket: AggregationResultBucket? = null +) : Writeable, ToXContent { + + init { + if (errorMessage != null) require(state == State.DELETED || state == State.ERROR) { + "Attempt to create an alert with an error in state: $state" + } + } + + constructor( + monitor: Monitor, + trigger: QueryLevelTrigger, + startTime: Instant, + lastNotificationTime: Instant?, + state: State = State.ACTIVE, + errorMessage: String? = null, + errorHistory: List = mutableListOf(), + actionExecutionResults: List = mutableListOf(), + schemaVersion: Int = NO_SCHEMA_VERSION + ) : 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() + ) + + constructor( + monitor: Monitor, + trigger: BucketLevelTrigger, + startTime: Instant, + lastNotificationTime: Instant?, + state: State = State.ACTIVE, + errorMessage: String? = null, + errorHistory: List = mutableListOf(), + actionExecutionResults: List = mutableListOf(), + schemaVersion: Int = NO_SCHEMA_VERSION + ) : 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() + ) + + constructor( + monitor: Monitor, + trigger: BucketLevelTrigger, + startTime: Instant, + lastNotificationTime: Instant?, + state: State = State.ACTIVE, + errorMessage: String? = null, + errorHistory: List = mutableListOf(), + actionExecutionResults: List = mutableListOf(), + schemaVersion: Int = NO_SCHEMA_VERSION, + aggregationResultBucket: AggregationResultBucket + ) : 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 = emptyList(), relatedDocIds = emptyList() + ) + + constructor( + id: String = NO_ID, + monitor: Monitor, + trigger: DocumentLevelTrigger, + findingIds: List, + relatedDocIds: List, + startTime: Instant, + lastNotificationTime: Instant?, + state: State = State.ACTIVE, + errorMessage: String? = null, + errorHistory: List = mutableListOf(), + actionExecutionResults: 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 = actionExecutionResults, schemaVersion = schemaVersion, + aggregationResultBucket = null, findingIds = findingIds, relatedDocIds = relatedDocIds + ) + + enum class State { + ACTIVE, ACKNOWLEDGED, COMPLETED, ERROR, DELETED + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + id = sin.readString(), + version = sin.readLong(), + schemaVersion = sin.readInt(), + monitorId = sin.readString(), + monitorName = sin.readString(), + monitorVersion = sin.readLong(), + monitorUser = if (sin.readBoolean()) { + User(sin) + } else null, + triggerId = sin.readString(), + triggerName = sin.readString(), + findingIds = sin.readStringList(), + relatedDocIds = sin.readStringList(), + state = sin.readEnum(State::class.java), + startTime = sin.readInstant(), + endTime = sin.readOptionalInstant(), + lastNotificationTime = sin.readOptionalInstant(), + acknowledgedTime = sin.readOptionalInstant(), + errorMessage = sin.readOptionalString(), + errorHistory = sin.readList(::AlertError), + severity = sin.readString(), + actionExecutionResults = sin.readList(::ActionExecutionResult), + aggregationResultBucket = if (sin.readBoolean()) AggregationResultBucket(sin) else null + ) + + fun isAcknowledged(): Boolean = (state == State.ACKNOWLEDGED) + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(id) + out.writeLong(version) + out.writeInt(schemaVersion) + out.writeString(monitorId) + out.writeString(monitorName) + out.writeLong(monitorVersion) + out.writeBoolean(monitorUser != null) + monitorUser?.writeTo(out) + out.writeString(triggerId) + out.writeString(triggerName) + out.writeStringCollection(findingIds) + out.writeStringCollection(relatedDocIds) + out.writeEnum(state) + out.writeInstant(startTime) + out.writeOptionalInstant(endTime) + out.writeOptionalInstant(lastNotificationTime) + out.writeOptionalInstant(acknowledgedTime) + out.writeOptionalString(errorMessage) + out.writeCollection(errorHistory) + out.writeString(severity) + out.writeCollection(actionExecutionResults) + if (aggregationResultBucket != null) { + out.writeBoolean(true) + aggregationResultBucket.writeTo(out) + } else { + out.writeBoolean(false) + } + } + + companion object { + + const val ALERT_ID_FIELD = "id" + const val SCHEMA_VERSION_FIELD = "schema_version" + const val ALERT_VERSION_FIELD = "version" + const val MONITOR_ID_FIELD = "monitor_id" + const val MONITOR_VERSION_FIELD = "monitor_version" + const val MONITOR_NAME_FIELD = "monitor_name" + const val MONITOR_USER_FIELD = "monitor_user" + const val TRIGGER_ID_FIELD = "trigger_id" + const val TRIGGER_NAME_FIELD = "trigger_name" + const val FINDING_IDS = "finding_ids" + const val RELATED_DOC_IDS = "related_doc_ids" + const val STATE_FIELD = "state" + const val START_TIME_FIELD = "start_time" + const val LAST_NOTIFICATION_TIME_FIELD = "last_notification_time" + const val END_TIME_FIELD = "end_time" + const val ACKNOWLEDGED_TIME_FIELD = "acknowledged_time" + const val ERROR_MESSAGE_FIELD = "error_message" + const val ALERT_HISTORY_FIELD = "alert_history" + const val SEVERITY_FIELD = "severity" + const val ACTION_EXECUTION_RESULTS_FIELD = "action_execution_results" + const val BUCKET_KEYS = AggregationResultBucket.BUCKET_KEYS + const val PARENTS_BUCKET_PATH = AggregationResultBucket.PARENTS_BUCKET_PATH + const val NO_ID = "" + const val NO_VERSION = Versions.NOT_FOUND + + @JvmStatic @JvmOverloads + @Throws(IOException::class) + fun parse(xcp: XContentParser, id: String = NO_ID, version: Long = NO_VERSION): Alert { + + lateinit var monitorId: String + var schemaVersion = NO_SCHEMA_VERSION + lateinit var monitorName: String + var monitorVersion: Long = Versions.NOT_FOUND + var monitorUser: User? = null + lateinit var triggerId: String + lateinit var triggerName: String + val findingIds = mutableListOf() + val relatedDocIds = mutableListOf() + lateinit var state: State + lateinit var startTime: Instant + lateinit var severity: String + var endTime: Instant? = null + var lastNotificationTime: Instant? = null + var acknowledgedTime: Instant? = null + var errorMessage: String? = null + val errorHistory: MutableList = mutableListOf() + val actionExecutionResults: MutableList = mutableListOf() + var aggAlertBucket: AggregationResultBucket? = null + ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + when (fieldName) { + MONITOR_ID_FIELD -> monitorId = xcp.text() + 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) + TRIGGER_ID_FIELD -> triggerId = xcp.text() + FINDING_IDS -> { + ensureExpectedToken(XContentParser.Token.START_ARRAY, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + findingIds.add(xcp.text()) + } + } + RELATED_DOC_IDS -> { + ensureExpectedToken(XContentParser.Token.START_ARRAY, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + relatedDocIds.add(xcp.text()) + } + } + STATE_FIELD -> state = State.valueOf(xcp.text()) + TRIGGER_NAME_FIELD -> triggerName = xcp.text() + START_TIME_FIELD -> startTime = requireNotNull(xcp.instant()) + END_TIME_FIELD -> endTime = xcp.instant() + LAST_NOTIFICATION_TIME_FIELD -> lastNotificationTime = xcp.instant() + ACKNOWLEDGED_TIME_FIELD -> acknowledgedTime = xcp.instant() + ERROR_MESSAGE_FIELD -> errorMessage = xcp.textOrNull() + ALERT_HISTORY_FIELD -> { + ensureExpectedToken(XContentParser.Token.START_ARRAY, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + errorHistory.add(AlertError.parse(xcp)) + } + } + SEVERITY_FIELD -> severity = xcp.text() + ACTION_EXECUTION_RESULTS_FIELD -> { + ensureExpectedToken(XContentParser.Token.START_ARRAY, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + actionExecutionResults.add(ActionExecutionResult.parse(xcp)) + } + } + AggregationResultBucket.CONFIG_NAME -> { + // If an Alert with aggAlertBucket contents is indexed into the alerts index first, then + // that field will be added to the mappings. + // In this case, that field will default to null when it isn't present for Alerts created by Query-Level Monitors + // (even though the toXContent doesn't output the field) so null is being accounted for here. + aggAlertBucket = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) { + null + } else { + AggregationResultBucket.parse(xcp) + } + } + } + } + + 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 + ) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): Alert { + return Alert(sin) + } + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + return createXContentBuilder(builder, true) + } + + 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(SCHEMA_VERSION_FIELD, schemaVersion) + .field(MONITOR_VERSION_FIELD, monitorVersion) + .field(MONITOR_NAME_FIELD, monitorName) + + if (!secure) { + builder.optionalUserField(MONITOR_USER_FIELD, monitorUser) + } + + builder.field(TRIGGER_ID_FIELD, triggerId) + .field(TRIGGER_NAME_FIELD, triggerName) + .field(FINDING_IDS, findingIds.toTypedArray()) + .field(RELATED_DOC_IDS, relatedDocIds.toTypedArray()) + .field(STATE_FIELD, state) + .field(ERROR_MESSAGE_FIELD, errorMessage) + .field(ALERT_HISTORY_FIELD, errorHistory.toTypedArray()) + .field(SEVERITY_FIELD, severity) + .field(ACTION_EXECUTION_RESULTS_FIELD, actionExecutionResults.toTypedArray()) + .optionalTimeField(START_TIME_FIELD, startTime) + .optionalTimeField(LAST_NOTIFICATION_TIME_FIELD, lastNotificationTime) + .optionalTimeField(END_TIME_FIELD, endTime) + .optionalTimeField(ACKNOWLEDGED_TIME_FIELD, acknowledgedTime) + aggregationResultBucket?.innerXContent(builder) + builder.endObject() + return builder + } + + fun asTemplateArg(): Map { + return mapOf( + ACKNOWLEDGED_TIME_FIELD to acknowledgedTime?.toEpochMilli(), + ALERT_ID_FIELD to id, + ALERT_VERSION_FIELD to version, + END_TIME_FIELD to endTime?.toEpochMilli(), + ERROR_MESSAGE_FIELD to errorMessage, + LAST_NOTIFICATION_TIME_FIELD to lastNotificationTime?.toEpochMilli(), + SEVERITY_FIELD to severity, + START_TIME_FIELD to startTime.toEpochMilli(), + STATE_FIELD to state.toString(), + // Converting bucket keys to comma separated String to avoid manipulation in Action mustache templates + BUCKET_KEYS to aggregationResultBucket?.bucketKeys?.joinToString(","), + PARENTS_BUCKET_PATH to aggregationResultBucket?.parentBucketPath, + FINDING_IDS to findingIds.joinToString(","), + RELATED_DOC_IDS to relatedDocIds.joinToString(",") + ) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt index d27bfdf3..589f745a 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt @@ -69,7 +69,7 @@ data class DataSources( FINDINGS_INDEX_FIELD to findingsIndex, ALERTS_INDEX_FIELD to alertsIndex, QUERY_INDEX_MAPPINGS_BY_TYPE to queryIndexMappingsByType - ) as Map + ) } override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt new file mode 100644 index 00000000..899189b8 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt @@ -0,0 +1,140 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken +import org.opensearch.commons.alerting.util.instant +import java.io.IOException +import java.time.Instant + +/** + * A wrapper of the log event that enriches the event by also including information about the monitor it triggered. + */ +class Finding( + val id: String = NO_ID, + val relatedDocIds: List, + val monitorId: String, + val monitorName: String, + val index: String, + val docLevelQueries: List, + val timestamp: Instant +) : Writeable, ToXContent { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + id = sin.readString(), + relatedDocIds = sin.readStringList(), + monitorId = sin.readString(), + monitorName = sin.readString(), + index = sin.readString(), + docLevelQueries = sin.readList((DocLevelQuery)::readFrom), + timestamp = sin.readInstant() + ) + + fun asTemplateArg(): Map { + return mapOf( + FINDING_ID_FIELD to id, + RELATED_DOC_IDS_FIELD to relatedDocIds, + MONITOR_ID_FIELD to monitorId, + MONITOR_NAME_FIELD to monitorName, + INDEX_FIELD to index, + QUERIES_FIELD to docLevelQueries, + TIMESTAMP_FIELD to timestamp.toEpochMilli() + ) + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .field(FINDING_ID_FIELD, id) + .field(RELATED_DOC_IDS_FIELD, relatedDocIds) + .field(MONITOR_ID_FIELD, monitorId) + .field(MONITOR_NAME_FIELD, monitorName) + .field(INDEX_FIELD, index) + .field(QUERIES_FIELD, docLevelQueries.toTypedArray()) + .field(TIMESTAMP_FIELD, timestamp.toEpochMilli()) + builder.endObject() + return builder + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(id) + out.writeStringCollection(relatedDocIds) + out.writeString(monitorId) + out.writeString(monitorName) + out.writeString(index) + out.writeCollection(docLevelQueries) + out.writeInstant(timestamp) + } + + companion object { + const val FINDING_ID_FIELD = "id" + const val RELATED_DOC_IDS_FIELD = "related_doc_ids" + const val MONITOR_ID_FIELD = "monitor_id" + const val MONITOR_NAME_FIELD = "monitor_name" + const val INDEX_FIELD = "index" + const val QUERIES_FIELD = "queries" + const val TIMESTAMP_FIELD = "timestamp" + const val NO_ID = "" + + @JvmStatic @JvmOverloads + @Throws(IOException::class) + fun parse(xcp: XContentParser): Finding { + var id: String = NO_ID + val relatedDocIds: MutableList = mutableListOf() + lateinit var monitorId: String + lateinit var monitorName: String + lateinit var index: String + val queries: MutableList = mutableListOf() + lateinit var timestamp: Instant + + ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + when (fieldName) { + FINDING_ID_FIELD -> id = xcp.text() + RELATED_DOC_IDS_FIELD -> { + ensureExpectedToken(XContentParser.Token.START_ARRAY, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + relatedDocIds.add(xcp.text()) + } + } + MONITOR_ID_FIELD -> monitorId = xcp.text() + MONITOR_NAME_FIELD -> monitorName = xcp.text() + INDEX_FIELD -> index = xcp.text() + QUERIES_FIELD -> { + ensureExpectedToken(XContentParser.Token.START_ARRAY, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + queries.add(DocLevelQuery.parse(xcp)) + } + } + TIMESTAMP_FIELD -> { + timestamp = requireNotNull(xcp.instant()) + } + } + } + + return Finding( + id = id, + relatedDocIds = relatedDocIds, + monitorId = monitorId, + monitorName = monitorName, + index = index, + docLevelQueries = queries, + timestamp = timestamp + ) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): Finding { + return Finding(sin) + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/FindingDocument.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/FindingDocument.kt new file mode 100644 index 00000000..c30eb6be --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/FindingDocument.kt @@ -0,0 +1,86 @@ +package org.opensearch.commons.alerting.model + +import org.apache.logging.log4j.LogManager +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import java.io.IOException + +private val log = LogManager.getLogger(FindingDocument::class.java) + +class FindingDocument( + val index: String, + val id: String, + val found: Boolean, + val document: String +) : Writeable, ToXContent { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + index = sin.readString(), + id = sin.readString(), + found = sin.readBoolean(), + document = sin.readString() + ) + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + return builder.startObject() + .field(INDEX_FIELD, index) + .field(FINDING_DOCUMENT_ID_FIELD, id) + .field(FOUND_FIELD, found) + .field(DOCUMENT_FIELD, document) + .endObject() + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(index) + out.writeString(id) + out.writeBoolean(found) + out.writeString(document) + } + + companion object { + const val INDEX_FIELD = "index" + const val FINDING_DOCUMENT_ID_FIELD = "id" + const val FOUND_FIELD = "found" + const val DOCUMENT_FIELD = "document" + const val NO_ID = "" + const val NO_INDEX = "" + + @JvmStatic @JvmOverloads + @Throws(IOException::class) + fun parse(xcp: XContentParser, id: String = NO_ID, index: String = NO_INDEX): FindingDocument { + var found = false + var document: String = "" + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + when (fieldName) { + FOUND_FIELD -> found = xcp.booleanValue() + DOCUMENT_FIELD -> document = xcp.text() + } + } + + return FindingDocument( + index = index, + id = id, + found = found, + document = document + ) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): FindingDocument { + return FindingDocument(sin) + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/FindingWithDocs.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/FindingWithDocs.kt new file mode 100644 index 00000000..28995f1d --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/FindingWithDocs.kt @@ -0,0 +1,80 @@ +package org.opensearch.commons.alerting.model + +import org.apache.logging.log4j.LogManager +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import java.io.IOException + +private val log = LogManager.getLogger(Finding::class.java) + +class FindingWithDocs( + val finding: Finding, + val documents: List +) : Writeable, ToXContent { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + finding = Finding.readFrom(sin), + documents = sin.readList((FindingDocument)::readFrom) + ) + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + finding.writeTo(out) + documents.forEach { + it.writeTo(out) + } + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .field(FINDING_FIELD, finding) + .field(DOCUMENTS_FIELD, documents) + builder.endObject() + return builder + } + + companion object { + const val FINDING_FIELD = "finding" + const val DOCUMENTS_FIELD = "document_list" + + @JvmStatic + @Throws(IOException::class) + fun parse(xcp: XContentParser): FindingWithDocs { + lateinit var finding: Finding + val documents: MutableList = mutableListOf() + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + when (fieldName) { + FINDING_FIELD -> finding = Finding.parse(xcp) + DOCUMENTS_FIELD -> { + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_ARRAY, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + documents.add(FindingDocument.parse(xcp)) + } + } + } + } + + return FindingWithDocs( + finding = finding, + documents = documents + ) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): FindingWithDocs { + return FindingWithDocs(sin) + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Table.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Table.kt new file mode 100644 index 00000000..66074d21 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Table.kt @@ -0,0 +1,44 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import java.io.IOException + +data class Table( + val sortOrder: String, + val sortString: String, + val missing: String?, + val size: Int, + val startIndex: Int, + val searchString: String? +) : Writeable { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sortOrder = sin.readString(), + sortString = sin.readString(), + missing = sin.readOptionalString(), + size = sin.readInt(), + startIndex = sin.readInt(), + searchString = sin.readOptionalString() + ) + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(sortOrder) + out.writeString(sortString) + out.writeOptionalString(missing) + out.writeInt(size) + out.writeInt(startIndex) + out.writeOptionalString(searchString) + } + + companion object { + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): Table { + return Table(sin) + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt new file mode 100644 index 00000000..23e47825 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt @@ -0,0 +1,61 @@ +package org.opensearch.commons.alerting + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.opensearch.commons.alerting.model.Alert + +class AlertTests { + @Test + fun `test alert as template args`() { + val alert = randomAlert().copy(acknowledgedTime = null, lastNotificationTime = null) + + val templateArgs = alert.asTemplateArg() + + assertEquals(templateArgs[Alert.ALERT_ID_FIELD], alert.id, "Template args id does not match") + assertEquals(templateArgs[Alert.ALERT_VERSION_FIELD], alert.version, "Template args version does not match") + assertEquals(templateArgs[Alert.STATE_FIELD], alert.state.toString(), "Template args state does not match") + assertEquals(templateArgs[Alert.ERROR_MESSAGE_FIELD], alert.errorMessage, "Template args error message does not match") + assertEquals(templateArgs[Alert.ACKNOWLEDGED_TIME_FIELD], null, "Template args acknowledged time does not match") + assertEquals(templateArgs[Alert.END_TIME_FIELD], alert.endTime?.toEpochMilli(), "Template args end time does not") + assertEquals(templateArgs[Alert.START_TIME_FIELD], alert.startTime.toEpochMilli(), "Template args start time does not") + assertEquals(templateArgs[Alert.LAST_NOTIFICATION_TIME_FIELD], null, "Template args last notification time does not match") + assertEquals(templateArgs[Alert.SEVERITY_FIELD], alert.severity, "Template args severity does not match") + } + + @Test + fun `test agg alert as template args`() { + val alert = randomAlertWithAggregationResultBucket().copy(acknowledgedTime = null, lastNotificationTime = null) + + val templateArgs = alert.asTemplateArg() + + assertEquals(templateArgs[Alert.ALERT_ID_FIELD], alert.id, "Template args id does not match") + assertEquals(templateArgs[Alert.ALERT_VERSION_FIELD], alert.version, "Template args version does not match") + assertEquals(templateArgs[Alert.STATE_FIELD], alert.state.toString(), "Template args state does not match") + assertEquals(templateArgs[Alert.ERROR_MESSAGE_FIELD], alert.errorMessage, "Template args error message does not match") + assertEquals(templateArgs[Alert.ACKNOWLEDGED_TIME_FIELD], null, "Template args acknowledged time does not match") + assertEquals(templateArgs[Alert.END_TIME_FIELD], alert.endTime?.toEpochMilli(), "Template args end time does not") + assertEquals(templateArgs[Alert.START_TIME_FIELD], alert.startTime.toEpochMilli(), "Template args start time does not") + assertEquals(templateArgs[Alert.LAST_NOTIFICATION_TIME_FIELD], null, "Template args last notification time does not match") + assertEquals(templateArgs[Alert.SEVERITY_FIELD], alert.severity, "Template args severity does not match") + assertEquals( + templateArgs[Alert.BUCKET_KEYS], + alert.aggregationResultBucket?.bucketKeys?.joinToString(","), + "Template args bucketKeys do not match" + ) + assertEquals( + templateArgs[Alert.PARENTS_BUCKET_PATH], + alert.aggregationResultBucket?.parentBucketPath, + "Template args parentBucketPath does not match", + ) + } + + @Test + fun `test alert acknowledged`() { + val ackAlert = randomAlert().copy(state = Alert.State.ACKNOWLEDGED) + Assertions.assertTrue(ackAlert.isAcknowledged(), "Alert is not acknowledged") + + val activeAlert = randomAlert().copy(state = Alert.State.ACTIVE) + Assertions.assertFalse(activeAlert.isAcknowledged(), "Alert is acknowledged") + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt index a7f79c9b..6171b8a9 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt @@ -14,10 +14,17 @@ import org.opensearch.action.ActionType import org.opensearch.client.node.NodeClient import org.opensearch.commons.alerting.action.DeleteMonitorRequest import org.opensearch.commons.alerting.action.DeleteMonitorResponse +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.IndexMonitorRequest import org.opensearch.commons.alerting.action.IndexMonitorResponse +import org.opensearch.commons.alerting.model.FindingDocument +import org.opensearch.commons.alerting.model.FindingWithDocs import org.opensearch.commons.alerting.model.Monitor import org.opensearch.index.seqno.SequenceNumbers +import org.opensearch.rest.RestStatus @Suppress("UNCHECKED_CAST") @ExtendWith(MockitoExtension::class) @@ -57,6 +64,46 @@ internal class AlertingPluginInterfaceTests { }.whenever(client).execute(Mockito.any(ActionType::class.java), Mockito.any(), Mockito.any()) AlertingPluginInterface.deleteMonitor(client, request, listener) + } + + @Test + fun getAlerts() { + val monitor = randomQueryLevelMonitor() + val alert = randomAlert(monitor) + val request = mock(GetAlertsRequest::class.java) + val response = GetAlertsResponse(listOf(alert), 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.getAlerts(client, request, listener) + Mockito.verify(listener, Mockito.times(1)).onResponse(ArgumentMatchers.eq(response)) + } + + @Test + fun getFindings() { + val finding = randomFinding() + val documentIds = finding.relatedDocIds + val relatedDocs = mutableListOf() + val request = mock(GetFindingsRequest::class.java) + val documents: Map = mutableMapOf() + for (docId in documentIds) { + val key = "${finding.index}|$docId" + documents[key]?.let { document -> relatedDocs.add(document) } + } + val findingWithDocs = FindingWithDocs(finding, relatedDocs) + val response = GetFindingsResponse(RestStatus.OK, 1, listOf(findingWithDocs)) + 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.getFindings(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 68b860d4..6ab056c5 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt @@ -21,11 +21,15 @@ import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentType import org.opensearch.commons.alerting.aggregation.bucketselectorext.BucketSelectorExtAggregationBuilder import org.opensearch.commons.alerting.aggregation.bucketselectorext.BucketSelectorExtFilter +import org.opensearch.commons.alerting.model.ActionExecutionResult +import org.opensearch.commons.alerting.model.AggregationResultBucket +import org.opensearch.commons.alerting.model.Alert import org.opensearch.commons.alerting.model.BucketLevelTrigger import org.opensearch.commons.alerting.model.ClusterMetricsInput import org.opensearch.commons.alerting.model.DocLevelMonitorInput import org.opensearch.commons.alerting.model.DocLevelQuery import org.opensearch.commons.alerting.model.DocumentLevelTrigger +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 @@ -404,3 +408,52 @@ fun assertUserNull(map: Map) { fun assertUserNull(monitor: Monitor) { assertNull("User is not null", monitor.user) } + +fun randomAlert(monitor: Monitor = randomQueryLevelMonitor()): Alert { + val trigger = randomQueryLevelTrigger() + val actionExecutionResults = mutableListOf(randomActionExecutionResult(), randomActionExecutionResult()) + return Alert( + monitor, trigger, Instant.now().truncatedTo(ChronoUnit.MILLIS), null, + actionExecutionResults = actionExecutionResults + ) +} + +fun randomActionExecutionResult( + actionId: String = UUIDs.base64UUID(), + lastExecutionTime: Instant = Instant.now().truncatedTo(ChronoUnit.MILLIS), + throttledCount: Int = 0 +) = ActionExecutionResult(actionId, lastExecutionTime, throttledCount) + +fun randomAlertWithAggregationResultBucket(monitor: Monitor = randomBucketLevelMonitor()): Alert { + val trigger = randomBucketLevelTrigger() + val actionExecutionResults = mutableListOf(randomActionExecutionResult(), randomActionExecutionResult()) + return Alert( + monitor, trigger, Instant.now().truncatedTo(ChronoUnit.MILLIS), null, + actionExecutionResults = actionExecutionResults, + aggregationResultBucket = AggregationResultBucket( + "parent_bucket_path_1", + listOf("bucket_key_1"), + mapOf("k1" to "val1", "k2" to "val2") + ) + ) +} + +fun randomFinding( + id: String = UUIDs.base64UUID(), + relatedDocIds: List = listOf(UUIDs.base64UUID()), + monitorId: String = UUIDs.base64UUID(), + monitorName: String = UUIDs.base64UUID(), + index: String = UUIDs.base64UUID(), + docLevelQueries: List = listOf(randomDocLevelQuery()), + timestamp: Instant = Instant.now() +): Finding { + return Finding( + id = id, + relatedDocIds = relatedDocIds, + monitorId = monitorId, + monitorName = monitorName, + index = index, + docLevelQueries = docLevelQueries, + timestamp = timestamp + ) +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequestTests.kt new file mode 100644 index 00000000..3b2024a5 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequestTests.kt @@ -0,0 +1,58 @@ +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 +import org.opensearch.commons.alerting.model.Table + +internal class GetAlertsRequestTests { + + @Test + fun `test get alerts request`() { + + val table = Table("asc", "sortString", null, 1, 0, "") + + val req = GetAlertsRequest(table, "1", "active", null, null) + assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = GetAlertsRequest(sin) + + assertEquals("1", newReq.severityLevel) + assertEquals("active", newReq.alertState) + assertNull(newReq.monitorId) + assertEquals(table, newReq.table) + } + + @Test + fun `test get alerts request with filter`() { + + val table = Table("asc", "sortString", null, 1, 0, "") + val req = GetAlertsRequest(table, "1", "active", null, null) + assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = GetAlertsRequest(sin) + + assertEquals("1", newReq.severityLevel) + assertEquals("active", newReq.alertState) + assertNull(newReq.monitorId) + assertEquals(table, newReq.table) + } + + @Test + fun `test validate returns null`() { + val table = Table("asc", "sortString", null, 1, 0, "") + + val req = GetAlertsRequest(table, "1", "active", null, null) + assertNotNull(req) + assertNull(req.validate()) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponseTests.kt new file mode 100644 index 00000000..9a6eb589 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponseTests.kt @@ -0,0 +1,106 @@ +package org.opensearch.commons.alerting.action + +import org.junit.jupiter.api.Assertions +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.common.xcontent.ToXContent +import org.opensearch.commons.alerting.builder +import org.opensearch.commons.alerting.model.Alert +import org.opensearch.commons.alerting.randomUser +import org.opensearch.commons.alerting.util.string +import java.time.Instant +import java.util.Collections + +class GetAlertsResponseTests { + + @Test + fun `test get alerts response with no alerts`() { + val req = GetAlertsResponse(Collections.emptyList(), 0) + assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = GetAlertsResponse(sin) + Assertions.assertTrue(newReq.alerts.isEmpty()) + assertEquals(0, newReq.totalAlerts) + } + + @Test + fun `test get alerts response with alerts`() { + val alert = Alert( + "id", + 0L, + 0, + "monitorId", + "monitorName", + 0L, + randomUser(), + "triggerId", + "triggerName", + Collections.emptyList(), + Collections.emptyList(), + Alert.State.ACKNOWLEDGED, + Instant.MIN, + null, + null, + null, + null, + Collections.emptyList(), + "severity", + Collections.emptyList(), + null + ) + val req = GetAlertsResponse(listOf(alert), 1) + assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = GetAlertsResponse(sin) + assertEquals(1, newReq.alerts.size) + assertEquals(alert, newReq.alerts[0]) + assertEquals(1, newReq.totalAlerts) + } + + @Test + fun `test toXContent for get alerts response`() { + val now = Instant.now() + + val alert = Alert( + "id", + 0L, + 0, + "monitorId", + "monitorName", + 0L, + null, + "triggerId", + "triggerName", + Collections.emptyList(), + Collections.emptyList(), + Alert.State.ACKNOWLEDGED, + now, + null, + null, + null, + null, + Collections.emptyList(), + "severity", + Collections.emptyList(), + null + ) + 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\"," + + "\"schema_version\":0,\"monitor_version\":0,\"monitor_name\":\"monitorName\"," + + "\"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}" + assertEquals(expectedXContentString, actualXContentString) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt new file mode 100644 index 00000000..54fab737 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt @@ -0,0 +1,40 @@ +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 +import org.opensearch.commons.alerting.model.Table + +internal class GetFindingsRequestTests { + + @Test + fun `test get findings request`() { + + val table = Table("asc", "sortString", null, 1, 0, "") + + val req = GetFindingsRequest("2121", table, "1", "finding_index_name") + assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = GetFindingsRequest(sin) + + assertEquals("1", newReq.monitorId) + assertEquals("2121", newReq.findingId) + assertEquals("finding_index_name", newReq.findingIndex) + assertEquals(table, newReq.table) + } + + @Test + fun `test validate returns null`() { + val table = Table("asc", "sortString", null, 1, 0, "") + + val req = GetFindingsRequest("2121", table, "1", "active") + assertNotNull(req) + assertNull(req.validate()) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/model/FindingTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/model/FindingTests.kt new file mode 100644 index 00000000..50615cb2 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/FindingTests.kt @@ -0,0 +1,40 @@ +package org.opensearch.commons.alerting.model + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.opensearch.commons.alerting.randomFinding + +internal class FindingTests { + @Test + fun `test finding asTemplateArgs`() { + // GIVEN + val finding = randomFinding() + + // WHEN + val templateArgs = finding.asTemplateArg() + + // THEN + assertEquals(templateArgs[Finding.FINDING_ID_FIELD], finding.id, "Template args 'id' field does not match:") + assertEquals( + templateArgs[Finding.RELATED_DOC_IDS_FIELD], + finding.relatedDocIds, + "Template args 'relatedDocIds' field does not match:" + ) + assertEquals(templateArgs[Finding.MONITOR_ID_FIELD], finding.monitorId, "Template args 'monitorId' field does not match:") + assertEquals( + templateArgs[Finding.MONITOR_NAME_FIELD], + finding.monitorName, + "Template args 'monitorName' field does not match:", + ) + assertEquals( + templateArgs[Finding.QUERIES_FIELD], + finding.docLevelQueries, + "Template args 'queries' field does not match:" + ) + assertEquals( + templateArgs[Finding.TIMESTAMP_FIELD], + finding.timestamp.toEpochMilli(), + "Template args 'timestamp' field does not match:" + ) + } +} 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 520a9d3c..aa3b6e6f 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt @@ -12,7 +12,9 @@ import org.opensearch.commons.alerting.model.action.Throttle import org.opensearch.commons.alerting.parser import org.opensearch.commons.alerting.randomAction import org.opensearch.commons.alerting.randomActionExecutionPolicy +import org.opensearch.commons.alerting.randomActionExecutionResult import org.opensearch.commons.alerting.randomActionWithPolicy +import org.opensearch.commons.alerting.randomAlert import org.opensearch.commons.alerting.randomBucketLevelMonitor import org.opensearch.commons.alerting.randomBucketLevelTrigger import org.opensearch.commons.alerting.randomQueryLevelMonitor @@ -27,6 +29,7 @@ import org.opensearch.commons.alerting.util.string import org.opensearch.commons.authuser.User import org.opensearch.index.query.QueryBuilders import org.opensearch.search.builder.SearchSourceBuilder +import org.opensearch.test.OpenSearchTestCase import java.time.temporal.ChronoUnit import kotlin.test.assertFailsWith @@ -343,4 +346,48 @@ class XContentTests { "Round tripping ActionExecutionPolicy doesn't work" ) } + + @Test + fun `test alert parsing`() { + val alert = randomAlert() + + val alertString = alert.toXContentWithUser(builder()).string() + val parsedAlert = Alert.parse(parser(alertString)) + + assertEquals("Round tripping alert doesn't work", alert, parsedAlert) + } + + @Test + fun `test alert parsing without user`() { + val alertStr = "{\"id\":\"\",\"version\":-1,\"monitor_id\":\"\",\"schema_version\":0,\"monitor_version\":1," + + "\"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) + } + + @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 parsedAlert = Alert.parse(parser(alertStr)) + OpenSearchTestCase.assertNull(parsedAlert.monitorUser) + } + + @Test + fun `test action execution result parsing`() { + val actionExecutionResult = randomActionExecutionResult() + + val actionExecutionResultString = actionExecutionResult.toXContent(builder(), ToXContent.EMPTY_PARAMS).string() + val parsedActionExecutionResultString = ActionExecutionResult.parse(parser(actionExecutionResultString)) + + assertEquals("Round tripping alert doesn't work", actionExecutionResult, parsedActionExecutionResultString) + } } From d64d992ab0a6e5ad92370e60fd3422608fc217bf Mon Sep 17 00:00:00 2001 From: Petar Dzepina Date: Wed, 28 Sep 2022 00:31:48 +0200 Subject: [PATCH 078/149] added JvmField annotation to prevent generating getters (#255) Signed-off-by: Petar Dzepina --- .../opensearch/commons/alerting/action/AlertingActions.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 c20c52f1..434a53d6 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt @@ -12,15 +12,16 @@ object AlertingActions { const val DELETE_MONITOR_ACTION_NAME = "cluster:admin/opendistro/alerting/monitor/delete" const val GET_FINDINGS_ACTION_NAME = "cluster:admin/opensearch/alerting/findings/get" + @JvmField val INDEX_MONITOR_ACTION_TYPE = ActionType(INDEX_MONITOR_ACTION_NAME, ::IndexMonitorResponse) - + @JvmField val GET_ALERTS_ACTION_TYPE = ActionType(GET_ALERTS_ACTION_NAME, ::GetAlertsResponse) - + @JvmField val DELETE_MONITOR_ACTION_TYPE = ActionType(DELETE_MONITOR_ACTION_NAME, ::DeleteMonitorResponse) - + @JvmField val GET_FINDINGS_ACTION_TYPE = ActionType(GET_FINDINGS_ACTION_NAME, ::GetFindingsResponse) } From 36eafa68dc2aed55e1fd792aef9eeb461720aa62 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Fri, 30 Sep 2022 14:27:25 -0400 Subject: [PATCH 079/149] Change TrustStoreTest to use File.separator to support Windows path (#258) (#261) Signed-off-by: Peter Zhu Signed-off-by: Peter Zhu (cherry picked from commit 7dcb3a03168836aee9ca5e8bc2bab29749cea41c) Co-authored-by: Peter Zhu --- src/test/java/org/opensearch/commons/rest/TrustStoreTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/opensearch/commons/rest/TrustStoreTest.java b/src/test/java/org/opensearch/commons/rest/TrustStoreTest.java index 108a9e8d..271b1892 100644 --- a/src/test/java/org/opensearch/commons/rest/TrustStoreTest.java +++ b/src/test/java/org/opensearch/commons/rest/TrustStoreTest.java @@ -20,7 +20,7 @@ public class TrustStoreTest { public void testCreate() throws Exception { String resourceName = "sample.pem"; String absolutePath = new File(getClass().getClassLoader().getResource(resourceName).getFile()).getAbsolutePath(); - assertTrue(absolutePath.endsWith("/sample.pem")); + assertTrue(absolutePath.endsWith(File.separator + "sample.pem")); KeyStore store = new TrustStore(absolutePath).create(); assertNotNull(store); From 082c776cdba62331e2081f6be8814e3822f2b309 Mon Sep 17 00:00:00 2001 From: Subhobrata Dey Date: Mon, 3 Oct 2022 22:52:09 +0000 Subject: [PATCH 080/149] remove force snakeyaml removal (#263) Signed-off-by: Subhobrata Dey --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index b7fa032e..b0821bcb 100644 --- a/build.gradle +++ b/build.gradle @@ -67,7 +67,6 @@ apply from: 'build-tools/opensearchplugin-coverage.gradle' configurations { ktlint - all*.exclude group: 'org.yaml', module: 'snakeyaml' } dependencies { From 16cf2b6a4521531b5a2da2dcc5f632913a94ecca Mon Sep 17 00:00:00 2001 From: Surya Sashank Nistala Date: Mon, 10 Oct 2022 14:54:20 -0700 Subject: [PATCH 081/149] Accept of list of monitor ids in findings and alerts request dtos (#277) (#278) Signed-off-by: Surya Sashank Nistala Signed-off-by: Surya Sashank Nistala --- .../commons/alerting/action/GetAlertsRequest.kt | 9 +++++++-- .../commons/alerting/action/GetFindingsRequest.kt | 9 +++++++-- .../commons/alerting/action/GetAlertsRequestTests.kt | 5 ++++- .../commons/alerting/action/GetFindingsRequestTests.kt | 5 ++++- 4 files changed, 22 insertions(+), 6 deletions(-) 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 bdda4e93..5804ff08 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequest.kt @@ -13,19 +13,22 @@ class GetAlertsRequest : ActionRequest { val alertState: String val monitorId: String? val alertIndex: String? + val monitorIds: List? constructor( table: Table, severityLevel: String, alertState: String, monitorId: String?, - alertIndex: String? + alertIndex: String?, + monitorIds: List? = null ) : super() { this.table = table this.severityLevel = severityLevel this.alertState = alertState this.monitorId = monitorId this.alertIndex = alertIndex + this.monitorIds = monitorIds } @Throws(IOException::class) @@ -34,7 +37,8 @@ class GetAlertsRequest : ActionRequest { severityLevel = sin.readString(), alertState = sin.readString(), monitorId = sin.readOptionalString(), - alertIndex = sin.readOptionalString() + alertIndex = sin.readOptionalString(), + monitorIds = sin.readOptionalStringList() ) override fun validate(): ActionRequestValidationException? { @@ -48,5 +52,6 @@ class GetAlertsRequest : ActionRequest { out.writeString(alertState) out.writeOptionalString(monitorId) out.writeOptionalString(alertIndex) + out.writeOptionalStringCollection(monitorIds) } } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequest.kt index 4f852a64..ae65ca26 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequest.kt @@ -11,18 +11,21 @@ class GetFindingsRequest : ActionRequest { val findingId: String? val table: Table val monitorId: String? + val monitorIds: List? val findingIndex: String? constructor( findingId: String?, table: Table, monitorId: String? = null, - findingIndexName: String? = null + findingIndexName: String? = null, + monitorIds: List? = null ) : super() { this.findingId = findingId this.table = table this.monitorId = monitorId this.findingIndex = findingIndexName + this.monitorIds = monitorIds } @Throws(IOException::class) @@ -30,7 +33,8 @@ class GetFindingsRequest : ActionRequest { findingId = sin.readOptionalString(), table = Table.readFrom(sin), monitorId = sin.readOptionalString(), - findingIndexName = sin.readOptionalString() + findingIndexName = sin.readOptionalString(), + monitorIds = sin.readOptionalStringList() ) override fun validate(): ActionRequestValidationException? { @@ -43,5 +47,6 @@ class GetFindingsRequest : ActionRequest { table.writeTo(out) out.writeOptionalString(monitorId) out.writeOptionalString(findingIndex) + out.writeOptionalStringCollection(monitorIds) } } 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 3b2024a5..43c2f61c 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequestTests.kt @@ -3,6 +3,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.Assertions.assertTrue import org.junit.jupiter.api.Test import org.opensearch.common.io.stream.BytesStreamOutput import org.opensearch.common.io.stream.StreamInput @@ -15,7 +16,7 @@ internal class GetAlertsRequestTests { val table = Table("asc", "sortString", null, 1, 0, "") - val req = GetAlertsRequest(table, "1", "active", null, null) + val req = GetAlertsRequest(table, "1", "active", null, null, listOf("1", "2")) assertNotNull(req) val out = BytesStreamOutput() @@ -27,6 +28,8 @@ internal class GetAlertsRequestTests { assertEquals("active", newReq.alertState) assertNull(newReq.monitorId) assertEquals(table, newReq.table) + assertTrue(newReq.monitorIds!!.contains("1")) + assertTrue(newReq.monitorIds!!.contains("2")) } @Test diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt index 54fab737..f83cb8de 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt @@ -3,6 +3,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.Assertions.assertTrue import org.junit.jupiter.api.Test import org.opensearch.common.io.stream.BytesStreamOutput import org.opensearch.common.io.stream.StreamInput @@ -15,7 +16,7 @@ internal class GetFindingsRequestTests { val table = Table("asc", "sortString", null, 1, 0, "") - val req = GetFindingsRequest("2121", table, "1", "finding_index_name") + val req = GetFindingsRequest("2121", table, "1", "finding_index_name", listOf("1", "2")) assertNotNull(req) val out = BytesStreamOutput() @@ -27,6 +28,8 @@ internal class GetFindingsRequestTests { assertEquals("2121", newReq.findingId) assertEquals("finding_index_name", newReq.findingIndex) assertEquals(table, newReq.table) + assertTrue(newReq.monitorIds!!.contains("1")) + assertTrue(newReq.monitorIds!!.contains("2")) } @Test From b643bbe0b62071707ab63d68b97307573a289bdc Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Mon, 10 Oct 2022 16:18:34 -0700 Subject: [PATCH 082/149] Added legacy support for SNS messages. (#225) (#269) * Added legacy support for SNS messages. Signed-off-by: AWSHurneyt * Added license header to new classes. Signed-off-by: AWSHurneyt * Fixed style errors. Signed-off-by: AWSHurneyt Signed-off-by: AWSHurneyt (cherry picked from commit 9c2621eb5a2089c99ded4e1d205a362b71b4439c) Co-authored-by: AWSHurneyt --- .../message/LegacyDestinationType.java | 3 +- .../destination/message/LegacySNSMessage.java | 156 ++++++++++++++++++ .../commons/destination/util/Util.java | 35 ++++ .../LegacyPublishNotificationRequest.kt | 2 + .../message/LegacySNSMessageTest.java | 91 ++++++++++ .../commons/destination/util/UtilTest.java | 65 ++++++++ 6 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/opensearch/commons/destination/message/LegacySNSMessage.java create mode 100644 src/main/java/org/opensearch/commons/destination/util/Util.java create mode 100644 src/test/java/org/opensearch/commons/destination/message/LegacySNSMessageTest.java create mode 100644 src/test/java/org/opensearch/commons/destination/util/UtilTest.java diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java b/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java index f5086c27..cf4071aa 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java @@ -12,5 +12,6 @@ public enum LegacyDestinationType { LEGACY_CHIME, LEGACY_SLACK, LEGACY_CUSTOM_WEBHOOK, - LEGACY_EMAIL + LEGACY_EMAIL, + LEGACY_SNS } diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacySNSMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacySNSMessage.java new file mode 100644 index 00000000..0476f74e --- /dev/null +++ b/src/main/java/org/opensearch/commons/destination/message/LegacySNSMessage.java @@ -0,0 +1,156 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.commons.destination.message; + +import java.io.IOException; + +import org.opensearch.common.Strings; +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.commons.destination.util.Util; + +/** + * This class holds the content of an SNS message + */ +public class LegacySNSMessage extends LegacyBaseMessage { + + private final String subject; + private final String message; + private final String roleArn; + private final String topicArn; + private final String clusterName; + + private LegacySNSMessage( + final String destinationName, + final String roleArn, + final String topicArn, + final String clusterName, + final String subject, + final String message + ) { + super(LegacyDestinationType.LEGACY_SNS, destinationName, message); + + if (Strings.isNullOrEmpty(message)) { + throw new IllegalArgumentException("Message content is missing"); + } + if (Strings.isNullOrEmpty(roleArn) || !Util.isValidIAMArn(roleArn)) { + throw new IllegalArgumentException("Role arn is missing/invalid: " + roleArn); + } + + if (Strings.isNullOrEmpty(topicArn) || !Util.isValidSNSArn(topicArn)) { + throw new IllegalArgumentException("Topic arn is missing/invalid: " + topicArn); + } + + if (Strings.isNullOrEmpty(message)) { + throw new IllegalArgumentException("Message content is missing"); + } + + this.subject = subject; + this.message = message; + this.roleArn = roleArn; + this.topicArn = topicArn; + this.clusterName = clusterName; + } + + public LegacySNSMessage(StreamInput streamInput) throws java.io.IOException { + super(streamInput); + this.subject = streamInput.readString(); + this.message = super.getMessageContent(); + this.roleArn = streamInput.readString(); + this.topicArn = streamInput.readString(); + this.clusterName = streamInput.readString(); + } + + @Override + public String toString() { + return "DestinationType: " + + getChannelType() + + ", DestinationName: " + + destinationName + + ", RoleARn: " + + roleArn + + ", TopicArn: " + + topicArn + + ", ClusterName: " + + clusterName + + ", Subject: " + + subject + + ", Message: " + + message; + } + + public static class Builder { + private final String destinationName; + private String subject; + private String message; + private String roleArn; + private String topicArn; + private String clusterName; + + public Builder(String destinationName) { + this.destinationName = destinationName; + } + + public Builder withSubject(String subject) { + this.subject = subject; + return this; + } + + public Builder withMessage(String message) { + this.message = message; + return this; + } + + public Builder withRole(String roleArn) { + this.roleArn = roleArn; + return this; + } + + public Builder withTopicArn(String topicArn) { + this.topicArn = topicArn; + return this; + } + + public Builder withClusterName(String clusterName) { + this.clusterName = clusterName; + return this; + } + + public LegacySNSMessage build() { + return new LegacySNSMessage(this.destinationName, this.roleArn, this.topicArn, this.clusterName, this.subject, this.message); + } + } + + public String getSubject() { + return subject; + } + + public String getMessage() { + return message; + } + + public String getRoleArn() { + return roleArn; + } + + public String getTopicArn() { + return topicArn; + } + + public String getClusterName() { + return clusterName; + } + + @Override + public void writeTo(StreamOutput streamOutput) throws IOException { + super.writeTo(streamOutput); + streamOutput.writeString(subject); + streamOutput.writeString(message); + streamOutput.writeString(roleArn); + streamOutput.writeString(topicArn); + streamOutput.writeString(clusterName); + } +} diff --git a/src/main/java/org/opensearch/commons/destination/util/Util.java b/src/main/java/org/opensearch/commons/destination/util/Util.java new file mode 100644 index 00000000..9cb65ccb --- /dev/null +++ b/src/main/java/org/opensearch/commons/destination/util/Util.java @@ -0,0 +1,35 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.commons.destination.util; + +import java.util.regex.Pattern; + +import org.opensearch.common.Strings; +import org.opensearch.common.ValidationException; + +public class Util { + private Util() {} + + public static final Pattern SNS_ARN_REGEX = Pattern + .compile("^arn:aws(-[^:]+)?:sns:([a-zA-Z0-9-]+):([0-9]{12}):([a-zA-Z0-9-_]+)(\\.fifo)?$"); + public static final Pattern IAM_ARN_REGEX = Pattern.compile("^arn:aws(-[^:]+)?:iam::([0-9]{12}):([a-zA-Z0-9-/_+=@.,]+)$"); + + public static String getRegion(String arn) { + // sample topic arn arn:aws:sns:us-west-2:075315751589:test-notification + if (isValidSNSArn(arn)) { + return arn.split(":")[3]; + } + throw new IllegalArgumentException("Unable to retrieve region from ARN " + arn); + } + + public static boolean isValidIAMArn(String arn) { + return Strings.hasLength(arn) && IAM_ARN_REGEX.matcher(arn).find(); + } + + public static boolean isValidSNSArn(String arn) throws ValidationException { + return Strings.hasLength(arn) && SNS_ARN_REGEX.matcher(arn).find(); + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt index 211e2076..a4f8024d 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt @@ -15,6 +15,7 @@ import org.opensearch.commons.destination.message.LegacyChimeMessage import org.opensearch.commons.destination.message.LegacyCustomWebhookMessage import org.opensearch.commons.destination.message.LegacyDestinationType import org.opensearch.commons.destination.message.LegacyEmailMessage +import org.opensearch.commons.destination.message.LegacySNSMessage import org.opensearch.commons.destination.message.LegacySlackMessage import java.io.IOException @@ -52,6 +53,7 @@ class LegacyPublishNotificationRequest : ActionRequest { LegacyDestinationType.LEGACY_CUSTOM_WEBHOOK -> LegacyCustomWebhookMessage(input) LegacyDestinationType.LEGACY_SLACK -> LegacySlackMessage(input) LegacyDestinationType.LEGACY_EMAIL -> LegacyEmailMessage(input) + LegacyDestinationType.LEGACY_SNS -> LegacySNSMessage(input) } } diff --git a/src/test/java/org/opensearch/commons/destination/message/LegacySNSMessageTest.java b/src/test/java/org/opensearch/commons/destination/message/LegacySNSMessageTest.java new file mode 100644 index 00000000..a9fd1dd3 --- /dev/null +++ b/src/test/java/org/opensearch/commons/destination/message/LegacySNSMessageTest.java @@ -0,0 +1,91 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.commons.destination.message; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class LegacySNSMessageTest { + + @Test + public void testCreateRoleArnMissingMessage() { + try { + LegacySNSMessage message = new LegacySNSMessage.Builder("sms").withMessage("dummyMessage").build(); + } catch (Exception ex) { + assertEquals("Role arn is missing/invalid: null", ex.getMessage()); + throw ex; + } + } + + @Test + public void testCreateTopicArnMissingMessage() { + try { + LegacySNSMessage message = new LegacySNSMessage.Builder("sms") + .withMessage("dummyMessage") + .withRole("arn:aws:iam::853806060000:role/domain/abc") + .build(); + } catch (Exception ex) { + assertEquals("Topic arn is missing/invalid: null", ex.getMessage()); + throw ex; + } + } + + @Test + public void testCreateContentMissingMessage() { + try { + LegacySNSMessage message = new LegacySNSMessage.Builder("sms") + .withRole("arn:aws:iam::853806060000:role/domain/abc") + .withTopicArn("arn:aws:sns:us-west-2:475313751589:test-notification") + .build(); + } catch (Exception ex) { + assertEquals("Message content is missing", ex.getMessage()); + throw ex; + } + } + + @Test + public void testInValidRoleMessage() { + try { + LegacySNSMessage message = new LegacySNSMessage.Builder("sms") + .withMessage("dummyMessage") + .withRole("dummyRole") + .withTopicArn("arn:aws:sns:us-west-2:475313751589:test-notification") + .build(); + } catch (Exception ex) { + assertEquals("Role arn is missing/invalid: dummyRole", ex.getMessage()); + throw ex; + } + } + + @Test + public void testValidMessage() { + LegacySNSMessage message = new LegacySNSMessage.Builder("sms") + .withMessage("dummyMessage") + .withRole("arn:aws:iam::853806060000:role/domain/abc") + .withTopicArn("arn:aws:sns:us-west-2:475313751589:test-notification") + .build(); + assertEquals(LegacyDestinationType.LEGACY_SNS, message.getChannelType()); + assertEquals("sms", message.getChannelName()); + assertEquals("dummyMessage", message.getMessage()); + assertEquals("arn:aws:iam::853806060000:role/domain/abc", message.getRoleArn()); + assertEquals("arn:aws:sns:us-west-2:475313751589:test-notification", message.getTopicArn()); + } + + @Test + public void testInValidChannelName() { + try { + LegacySNSMessage message = new LegacySNSMessage.Builder("") + .withMessage("dummyMessage") + .withRole("arn:aws:iam::853806060000:role/domain/abc") + .withTopicArn("arn:aws:sns:us-west-2:475313751589:test-notification") + .build(); + } catch (Exception ex) { + assertEquals("Channel name must be defined", ex.getMessage()); + throw ex; + } + } +} diff --git a/src/test/java/org/opensearch/commons/destination/util/UtilTest.java b/src/test/java/org/opensearch/commons/destination/util/UtilTest.java new file mode 100644 index 00000000..506beb42 --- /dev/null +++ b/src/test/java/org/opensearch/commons/destination/util/UtilTest.java @@ -0,0 +1,65 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.commons.destination.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class UtilTest { + + @Test + public void testValidSNSTopicArn() { + String topicArn = "arn:aws:sns:us-west-2:475313751589:test-notification"; + assertTrue("topic arn should be valid", Util.isValidSNSArn(topicArn)); + topicArn = "arn:aws-cn:sns:us-west-2:475313751589:test-notification"; + assertTrue("topic arn should be valid", Util.isValidSNSArn(topicArn)); + topicArn = "arn:aws-cn:sns:us-west-2:475313751589:test-notification.fifo"; + assertTrue("topic arn should be valid", Util.isValidSNSArn(topicArn)); + } + + @Test + public void testInvalidSNSTopicArn() { + String topicArn = "arn:aws:sns1:us-west-2:475313751589:test-notification"; + assertFalse("topic arn should be Invalid", Util.isValidSNSArn(topicArn)); + topicArn = "arn:aws:sns:us-west-2:475313751589:test-notification.fifo.fifo"; + assertFalse("topic arn should be Invalid", Util.isValidSNSArn(topicArn)); + topicArn = "arn:aws:sns:us-west-2:475313751589:test-notification.fi"; + assertFalse("topic arn should be Invalid", Util.isValidSNSArn(topicArn)); + topicArn = "arn:aws:sns:us-west-2:475313751589:test-notifica.tion"; + assertFalse("topic arn should be Invalid", Util.isValidSNSArn(topicArn)); + topicArn = "arn:aws:sns:us-west-2:475313751589:test-notification&fifo"; + assertFalse("topic arn should be Invalid", Util.isValidSNSArn(topicArn)); + } + + @Test + public void testIAMRoleArn() { + String roleArn = "arn:aws:iam::853806060000:role/domain/abc"; + assertTrue("IAM role arn should be valid", Util.isValidIAMArn(roleArn)); + roleArn = "arn:aws:iam::853806060000:role/domain/a@+=.,-_bc"; + assertTrue("IAM role arn should be valid", Util.isValidIAMArn(roleArn)); + } + + @Test + public void testInvalidIAMRoleArn() { + String roleArn = "arn:aws:iam::85380606000000000:role/domain/010-asdf"; + assertFalse("IAM role arn should be Invalid", Util.isValidIAMArn(roleArn)); + } + + @Test + public void testGetRegion() { + String topicArn = "arn:aws:sns:us-west-2:475313751589:test-notification"; + assertEquals(Util.getRegion(topicArn), "us-west-2"); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidGetRegion() { + String topicArn = "arn:aws:abs:us-west-2:475313751589:test-notification"; + assertEquals(Util.getRegion(topicArn), "us-west-2"); + } +} From 99006a858edcabce37a9ce2fcc34d46ed0d524b5 Mon Sep 17 00:00:00 2001 From: Surya Sashank Nistala Date: Thu, 13 Oct 2022 14:39:43 -0700 Subject: [PATCH 083/149] move acknowledge alerts dtos from alerting to common-utils (#282) Signed-off-by: Surya Sashank Nistala --- .../alerting/AlertingPluginInterface.kt | 26 +++++++ .../action/AcknowledgeAlertRequest.kt | 48 ++++++++++++ .../action/AcknowledgeAlertResponse.kt | 78 +++++++++++++++++++ .../alerting/action/AlertingActions.kt | 4 + .../action/AcknowledgeAlertRequestTests.kt | 29 +++++++ .../action/AcknowledgeAlertResponseTests.kt | 48 ++++++++++++ 6 files changed, 233 insertions(+) create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponse.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertRequestTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponseTests.kt diff --git a/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt index be511203..cbeb12fc 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt @@ -8,6 +8,8 @@ import org.opensearch.action.ActionListener import org.opensearch.action.ActionResponse import org.opensearch.client.node.NodeClient 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.AlertingActions import org.opensearch.commons.alerting.action.DeleteMonitorRequest import org.opensearch.commons.alerting.action.DeleteMonitorResponse @@ -115,6 +117,30 @@ object AlertingPluginInterface { ) } + /** + * Acknowledge Alerts interface. + * @param client Node client for making transport action + * @param request The request object + * @param listener The listener for getting response + */ + fun acknowledgeAlerts( + client: NodeClient, + request: AcknowledgeAlertRequest, + listener: ActionListener + ) { + client.execute( + AlertingActions.ACKNOWLEDGE_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/AcknowledgeAlertRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertRequest.kt new file mode 100644 index 00000000..2576458e --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertRequest.kt @@ -0,0 +1,48 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.commons.alerting.action + +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionRequestValidationException +import org.opensearch.action.support.WriteRequest +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import java.io.IOException +import java.util.Collections + +class AcknowledgeAlertRequest : ActionRequest { + val monitorId: String + val alertIds: List + val refreshPolicy: WriteRequest.RefreshPolicy + + constructor( + monitorId: String, + alertIds: List, + refreshPolicy: WriteRequest.RefreshPolicy + ) : super() { + this.monitorId = monitorId + this.alertIds = alertIds + this.refreshPolicy = refreshPolicy + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readString(), // monitorId + Collections.unmodifiableList(sin.readStringList()), // alertIds + WriteRequest.RefreshPolicy.readFrom(sin) // refreshPolicy + ) + + override fun validate(): ActionRequestValidationException? { + return null + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(monitorId) + out.writeStringCollection(alertIds) + refreshPolicy.writeTo(out) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponse.kt new file mode 100644 index 00000000..de0e26da --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponse.kt @@ -0,0 +1,78 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.opensearch.commons.alerting.action + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.commons.alerting.model.Alert +import org.opensearch.commons.notifications.action.BaseResponse +import java.io.IOException +import java.util.Collections + +class AcknowledgeAlertResponse : BaseResponse { + + val acknowledged: List + val failed: List + val missing: List + + constructor( + acknowledged: List, + failed: List, + missing: List + ) : super() { + this.acknowledged = acknowledged + this.failed = failed + this.missing = missing + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + Collections.unmodifiableList(sin.readList(::Alert)), // acknowledged + Collections.unmodifiableList(sin.readList(::Alert)), // failed + Collections.unmodifiableList(sin.readStringList()) // missing + ) + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeCollection(acknowledged) + out.writeCollection(failed) + out.writeStringCollection(missing) + } + + @Throws(IOException::class) + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + + builder.startObject().startArray("success") + acknowledged.forEach { builder.value(it.id) } + builder.endArray().startArray("failed") + failed.forEach { buildFailedAlertAcknowledgeObject(builder, it) } + missing.forEach { buildMissingAlertAcknowledgeObject(builder, it) } + return builder.endArray().endObject() + } + + private fun buildFailedAlertAcknowledgeObject(builder: XContentBuilder, failedAlert: Alert) { + builder.startObject() + .startObject(failedAlert.id) + val reason = when (failedAlert.state) { + Alert.State.ERROR -> "Alert is in an error state and can not be acknowledged." + Alert.State.COMPLETED -> "Alert has already completed and can not be acknowledged." + Alert.State.ACKNOWLEDGED -> "Alert has already been acknowledged." + else -> "Alert state unknown and can not be acknowledged" + } + builder.field("failed_reason", reason) + .endObject() + .endObject() + } + + private fun buildMissingAlertAcknowledgeObject(builder: XContentBuilder, alertID: String) { + builder.startObject() + .startObject(alertID) + .field("failed_reason", "Alert: $alertID does not exist (it may have already completed).") + .endObject() + .endObject() + } +} 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 434a53d6..23a5ce77 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt @@ -11,6 +11,7 @@ object AlertingActions { const val GET_ALERTS_ACTION_NAME = "cluster:admin/opendistro/alerting/alerts/get" const val DELETE_MONITOR_ACTION_NAME = "cluster:admin/opendistro/alerting/monitor/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" @JvmField val INDEX_MONITOR_ACTION_TYPE = @@ -24,4 +25,7 @@ object AlertingActions { @JvmField val GET_FINDINGS_ACTION_TYPE = ActionType(GET_FINDINGS_ACTION_NAME, ::GetFindingsResponse) + @JvmField + val ACKNOWLEDGE_ALERTS_ACTION_TYPE = + ActionType(ACKNOWLEDGE_ALERTS_ACTION_NAME, ::AcknowledgeAlertResponse) } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertRequestTests.kt new file mode 100644 index 00000000..a3be117a --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertRequestTests.kt @@ -0,0 +1,29 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +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.action.support.WriteRequest +import org.opensearch.common.io.stream.BytesStreamOutput +import org.opensearch.common.io.stream.StreamInput + +class AcknowledgeAlertRequestTests { + + @Test + fun `test acknowledge alert request`() { + val req = AcknowledgeAlertRequest("1234", mutableListOf("1", "2", "3", "4"), WriteRequest.RefreshPolicy.IMMEDIATE) + assertNotNull(req) + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = AcknowledgeAlertRequest(sin) + assertEquals("1234", newReq.monitorId) + assertEquals(4, newReq.alertIds.size) + assertEquals(WriteRequest.RefreshPolicy.IMMEDIATE, newReq.refreshPolicy) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponseTests.kt new file mode 100644 index 00000000..16d5ef9a --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponseTests.kt @@ -0,0 +1,48 @@ +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.alerts.AlertError +import org.opensearch.commons.alerting.model.ActionExecutionResult +import org.opensearch.commons.alerting.model.Alert +import org.opensearch.commons.alerting.randomUser +import java.time.Instant + +class AcknowledgeAlertResponseTests { + + @Test + fun `test acknowledge alert response`() { + + 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 + ) + ) + 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 + ) + ) + val missing = mutableListOf("1", "2", "3", "4") + + val req = AcknowledgeAlertResponse(acknowledged, failed, missing) + assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = AcknowledgeAlertResponse(sin) + assertEquals(1, newReq.acknowledged.size) + assertEquals(1, newReq.failed.size) + assertEquals(4, newReq.missing.size) + } +} From 40404e23bce515fd419c6021ef069337bcc2656e Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 14:25:41 -0700 Subject: [PATCH 084/149] add list of alert ids in get alerts request (#284) (#286) Signed-off-by: Surya Sashank Nistala (cherry picked from commit fdd7cbb6df1add94eedcd596c1e5164c0f162776) --- .../commons/alerting/action/GetAlertsRequest.kt | 9 +++++++-- .../commons/alerting/action/GetAlertsRequestTests.kt | 4 +++- 2 files changed, 10 insertions(+), 3 deletions(-) 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 5804ff08..c0571753 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 alertIds: List? constructor( table: Table, @@ -21,7 +22,8 @@ class GetAlertsRequest : ActionRequest { alertState: String, monitorId: String?, alertIndex: String?, - monitorIds: List? = null + monitorIds: List? = null, + alertIds: List? = null ) : super() { this.table = table this.severityLevel = severityLevel @@ -29,6 +31,7 @@ class GetAlertsRequest : ActionRequest { this.monitorId = monitorId this.alertIndex = alertIndex this.monitorIds = monitorIds + this.alertIds = alertIds } @Throws(IOException::class) @@ -38,7 +41,8 @@ class GetAlertsRequest : ActionRequest { alertState = sin.readString(), monitorId = sin.readOptionalString(), alertIndex = sin.readOptionalString(), - monitorIds = sin.readOptionalStringList() + monitorIds = sin.readOptionalStringList(), + alertIds = sin.readOptionalStringList() ) override fun validate(): ActionRequestValidationException? { @@ -53,5 +57,6 @@ class GetAlertsRequest : ActionRequest { out.writeOptionalString(monitorId) out.writeOptionalString(alertIndex) out.writeOptionalStringCollection(monitorIds) + out.writeOptionalStringCollection(alertIds) } } 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 43c2f61c..32ade4ba 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,7 @@ internal class GetAlertsRequestTests { val table = Table("asc", "sortString", null, 1, 0, "") - val req = GetAlertsRequest(table, "1", "active", null, null, listOf("1", "2")) + val req = GetAlertsRequest(table, "1", "active", null, null, listOf("1", "2"), listOf("alert1", "alert2")) assertNotNull(req) val out = BytesStreamOutput() @@ -30,6 +30,8 @@ internal class GetAlertsRequestTests { assertEquals(table, newReq.table) assertTrue(newReq.monitorIds!!.contains("1")) assertTrue(newReq.monitorIds!!.contains("2")) + assertTrue(newReq.alertIds!!.contains("alert1")) + assertTrue(newReq.alertIds!!.contains("alert2")) } @Test From c1fdd4713ac01b33381d065b2675c5db86a480c2 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Wed, 19 Oct 2022 18:32:02 -0700 Subject: [PATCH 085/149] fix security-analytics alerting findings api integration (#292) (#293) Signed-off-by: Subhobrata Dey --- .../alerting/action/GetFindingsResponse.kt | 29 +++++-------------- .../commons/alerting/model/FindingDocument.kt | 8 ++--- .../commons/alerting/model/FindingWithDocs.kt | 25 ++++++++++------ 3 files changed, 27 insertions(+), 35 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponse.kt index 216666be..997778b6 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponse.kt @@ -10,7 +10,6 @@ import org.opensearch.rest.RestStatus import java.io.IOException class GetFindingsResponse : BaseResponse { - private var status: RestStatus var totalFindings: Int? var findings: List @@ -19,43 +18,29 @@ class GetFindingsResponse : BaseResponse { totalFindings: Int?, findings: List ) : super() { - this.status = status this.totalFindings = totalFindings this.findings = findings } @Throws(IOException::class) - constructor(sin: StreamInput) { - this.status = sin.readEnum(RestStatus::class.java) - val findings = mutableListOf() - this.totalFindings = sin.readOptionalInt() - var currentSize = sin.readInt() - for (i in 0 until currentSize) { - findings.add(FindingWithDocs.readFrom(sin)) - } - this.findings = findings - } + constructor(sin: StreamInput) : this( + status = RestStatus.OK, + totalFindings = sin.readOptionalInt(), + findings = sin.readList((FindingWithDocs)::readFrom) + ) @Throws(IOException::class) override fun writeTo(out: StreamOutput) { - out.writeEnum(status) out.writeOptionalInt(totalFindings) - out.writeInt(findings.size) - for (finding in findings) { - finding.writeTo(out) - } + out.writeCollection(findings) } @Throws(IOException::class) override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { builder.startObject() .field("total_findings", totalFindings) - .field("findings", findings) + .field("findings", findings.toTypedArray()) return builder.endObject() } - - override fun getStatus(): RestStatus { - return this.status - } } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/FindingDocument.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/FindingDocument.kt index c30eb6be..88fea291 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/FindingDocument.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/FindingDocument.kt @@ -3,21 +3,21 @@ package org.opensearch.commons.alerting.model import org.apache.logging.log4j.LogManager import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable import org.opensearch.common.xcontent.ToXContent import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.model.BaseModel import java.io.IOException private val log = LogManager.getLogger(FindingDocument::class.java) -class FindingDocument( +data class FindingDocument( val index: String, val id: String, val found: Boolean, val document: String -) : Writeable, ToXContent { +) : BaseModel { @Throws(IOException::class) constructor(sin: StreamInput) : this( @@ -52,7 +52,7 @@ class FindingDocument( const val NO_ID = "" const val NO_INDEX = "" - @JvmStatic @JvmOverloads + @JvmStatic @Throws(IOException::class) fun parse(xcp: XContentParser, id: String = NO_ID, index: String = NO_INDEX): FindingDocument { var found = false diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/FindingWithDocs.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/FindingWithDocs.kt index 28995f1d..ccf7abe7 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/FindingWithDocs.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/FindingWithDocs.kt @@ -3,19 +3,26 @@ package org.opensearch.commons.alerting.model import org.apache.logging.log4j.LogManager import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable import org.opensearch.common.xcontent.ToXContent import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.model.BaseModel import java.io.IOException private val log = LogManager.getLogger(Finding::class.java) -class FindingWithDocs( - val finding: Finding, - val documents: List -) : Writeable, ToXContent { +class FindingWithDocs : BaseModel { + var finding: Finding + var documents: List + + constructor( + finding: Finding, + documents: List + ) : super() { + this.finding = finding + this.documents = documents + } @Throws(IOException::class) constructor(sin: StreamInput) : this( @@ -26,15 +33,15 @@ class FindingWithDocs( @Throws(IOException::class) override fun writeTo(out: StreamOutput) { finding.writeTo(out) - documents.forEach { - it.writeTo(out) - } + out.writeCollection(documents) } override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { builder.startObject() + .startObject("finding_with_docs") .field(FINDING_FIELD, finding) - .field(DOCUMENTS_FIELD, documents) + .field(DOCUMENTS_FIELD, documents.toTypedArray()) + .endObject() builder.endObject() return builder } From cfe4d8daca417877bdbb2539626441ae438cb4cd Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Thu, 20 Oct 2022 09:46:18 -0700 Subject: [PATCH 086/149] added params to Datasources (#290) (#291) Signed-off-by: Petar (cherry picked from commit b9f370f08256444c79a20a3dae021fa53e1d4a34) Co-authored-by: Petar Dzepina --- .../commons/alerting/model/DataSources.kt | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt index 589f745a..73b856e1 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt @@ -18,10 +18,19 @@ data class DataSources( * If index is pre-existing, mapping is updated*/ val findingsIndex: String = ".opensearch-alerting-finding-history-write", // AlertIndices.FINDING_HISTORY_WRITE_INDEX + /** Configures a custom index pattern for findingsIndex alias.*/ + val findingsIndexPattern: String? = "<.opensearch-alerting-finding-history-{now/d}-1>", // AlertIndices.FINDING_HISTORY_INDEX_PATTERN + /** Configures a custom index to store alerts for a monitor. Creates a new index if index with given name not present. * If index is pre-existing, mapping is updated. */ val alertsIndex: String = ".opendistro-alerting-alerts", // AlertIndices.ALERT_INDEX + /** Configures a custom index alias to store historic alerts for a monitor.*/ + val alertsHistoryIndex: String? = ".opendistro-alerting-alert-history-write", // AlertIndices.ALERT_HISTORY_WRITE_INDEX + + /** Configures a custom index pattern for alertHistoryIndex alias.*/ + val alertsHistoryIndexPattern: String? = "<.opendistro-alerting-alert-history-{now/d}-1>", // AlertIndices.ALERT_HISTORY_INDEX_PATTERN + /** Configures custom mappings by field type for query index. * Custom query index mappings are configurable, only if a custom query index is configured too. */ val queryIndexMappingsByType: Map> = mapOf() @@ -58,16 +67,22 @@ data class DataSources( constructor(sin: StreamInput) : this( queryIndex = sin.readString(), findingsIndex = sin.readString(), + findingsIndexPattern = sin.readOptionalString(), alertsIndex = sin.readString(), + alertsHistoryIndex = sin.readOptionalString(), + alertsHistoryIndexPattern = sin.readOptionalString(), queryIndexMappingsByType = sin.readMap() as Map> ) @Suppress("UNCHECKED_CAST") - fun asTemplateArg(): Map { + fun asTemplateArg(): Map { return mapOf( QUERY_INDEX_FIELD to queryIndex, FINDINGS_INDEX_FIELD to findingsIndex, + FINDINGS_INDEX_PATTERN_FIELD to findingsIndexPattern, ALERTS_INDEX_FIELD to alertsIndex, + ALERTS_HISTORY_INDEX_FIELD to alertsHistoryIndex, + ALERTS_HISTORY_INDEX_PATTERN_FIELD to alertsHistoryIndexPattern, QUERY_INDEX_MAPPINGS_BY_TYPE to queryIndexMappingsByType ) } @@ -76,7 +91,10 @@ data class DataSources( builder.startObject() builder.field(QUERY_INDEX_FIELD, queryIndex) builder.field(FINDINGS_INDEX_FIELD, findingsIndex) + builder.field(FINDINGS_INDEX_PATTERN_FIELD, findingsIndexPattern) builder.field(ALERTS_INDEX_FIELD, alertsIndex) + builder.field(ALERTS_HISTORY_INDEX_FIELD, alertsHistoryIndex) + builder.field(ALERTS_HISTORY_INDEX_PATTERN_FIELD, alertsHistoryIndexPattern) builder.field(QUERY_INDEX_MAPPINGS_BY_TYPE, queryIndexMappingsByType as Map) builder.endObject() return builder @@ -85,7 +103,10 @@ data class DataSources( companion object { const val QUERY_INDEX_FIELD = "query_index" const val FINDINGS_INDEX_FIELD = "findings_index" + const val FINDINGS_INDEX_PATTERN_FIELD = "findings_index_pattern" const val ALERTS_INDEX_FIELD = "alerts_index" + const val ALERTS_HISTORY_INDEX_FIELD = "alerts_history_index" + const val ALERTS_HISTORY_INDEX_PATTERN_FIELD = "alerts_history_index_pattern" const val QUERY_INDEX_MAPPINGS_BY_TYPE = "query_index_mappings_by_type" @JvmStatic @@ -94,7 +115,10 @@ data class DataSources( fun parse(xcp: XContentParser): DataSources { var queryIndex = "" var findingsIndex = "" + var findingsIndexPattern = "" var alertsIndex = "" + var alertsHistoryIndex = "" + var alertsHistoryIndexPattern = "" var queryIndexMappingsByType: Map> = mapOf() XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) @@ -105,14 +129,20 @@ data class DataSources( when (fieldName) { QUERY_INDEX_FIELD -> queryIndex = xcp.text() FINDINGS_INDEX_FIELD -> findingsIndex = xcp.text() + FINDINGS_INDEX_PATTERN_FIELD -> findingsIndexPattern = xcp.text() ALERTS_INDEX_FIELD -> alertsIndex = xcp.text() + ALERTS_HISTORY_INDEX_FIELD -> alertsHistoryIndex = xcp.text() + ALERTS_HISTORY_INDEX_PATTERN_FIELD -> alertsHistoryIndexPattern = xcp.text() QUERY_INDEX_MAPPINGS_BY_TYPE -> queryIndexMappingsByType = xcp.map() as Map> } } return DataSources( queryIndex = queryIndex, findingsIndex = findingsIndex, + findingsIndexPattern = findingsIndexPattern, alertsIndex = alertsIndex, + alertsHistoryIndex = alertsHistoryIndex, + alertsHistoryIndexPattern = alertsHistoryIndexPattern, queryIndexMappingsByType = queryIndexMappingsByType ) } @@ -122,7 +152,10 @@ data class DataSources( override fun writeTo(out: StreamOutput) { out.writeString(queryIndex) out.writeString(findingsIndex) + out.writeOptionalString(findingsIndexPattern) out.writeString(alertsIndex) + out.writeOptionalString(alertsHistoryIndex) + out.writeOptionalString(alertsHistoryIndexPattern) out.writeMap(queryIndexMappingsByType as Map) } } From 5904e9c58a2813674a43722b560ae33e885fca5e Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Thu, 20 Oct 2022 17:40:48 -0700 Subject: [PATCH 087/149] fix security-analytics to alerting integration (#294) (#295) Signed-off-by: Subhobrata Dey (cherry picked from commit b737142b80258909a89a21a9bc67854e32e94504) Co-authored-by: Subhobrata Dey --- .../alerting/action/GetFindingsResponse.kt | 29 ++++++--- .../commons/alerting/model/FindingDocument.kt | 8 +-- .../commons/alerting/model/FindingWithDocs.kt | 21 ++----- .../action/GetFindingsResponseTests.kt | 61 +++++++++++++++++++ 4 files changed, 93 insertions(+), 26 deletions(-) create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponse.kt index 997778b6..216666be 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponse.kt @@ -10,6 +10,7 @@ import org.opensearch.rest.RestStatus import java.io.IOException class GetFindingsResponse : BaseResponse { + private var status: RestStatus var totalFindings: Int? var findings: List @@ -18,29 +19,43 @@ class GetFindingsResponse : BaseResponse { totalFindings: Int?, findings: List ) : super() { + this.status = status this.totalFindings = totalFindings this.findings = findings } @Throws(IOException::class) - constructor(sin: StreamInput) : this( - status = RestStatus.OK, - totalFindings = sin.readOptionalInt(), - findings = sin.readList((FindingWithDocs)::readFrom) - ) + constructor(sin: StreamInput) { + this.status = sin.readEnum(RestStatus::class.java) + val findings = mutableListOf() + this.totalFindings = sin.readOptionalInt() + var currentSize = sin.readInt() + for (i in 0 until currentSize) { + findings.add(FindingWithDocs.readFrom(sin)) + } + this.findings = findings + } @Throws(IOException::class) override fun writeTo(out: StreamOutput) { + out.writeEnum(status) out.writeOptionalInt(totalFindings) - out.writeCollection(findings) + out.writeInt(findings.size) + for (finding in findings) { + finding.writeTo(out) + } } @Throws(IOException::class) override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { builder.startObject() .field("total_findings", totalFindings) - .field("findings", findings.toTypedArray()) + .field("findings", findings) return builder.endObject() } + + override fun getStatus(): RestStatus { + return this.status + } } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/FindingDocument.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/FindingDocument.kt index 88fea291..c30eb6be 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/FindingDocument.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/FindingDocument.kt @@ -3,21 +3,21 @@ package org.opensearch.commons.alerting.model import org.apache.logging.log4j.LogManager import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable import org.opensearch.common.xcontent.ToXContent import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils -import org.opensearch.commons.notifications.model.BaseModel import java.io.IOException private val log = LogManager.getLogger(FindingDocument::class.java) -data class FindingDocument( +class FindingDocument( val index: String, val id: String, val found: Boolean, val document: String -) : BaseModel { +) : Writeable, ToXContent { @Throws(IOException::class) constructor(sin: StreamInput) : this( @@ -52,7 +52,7 @@ data class FindingDocument( const val NO_ID = "" const val NO_INDEX = "" - @JvmStatic + @JvmStatic @JvmOverloads @Throws(IOException::class) fun parse(xcp: XContentParser, id: String = NO_ID, index: String = NO_INDEX): FindingDocument { var found = false diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/FindingWithDocs.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/FindingWithDocs.kt index ccf7abe7..445791f6 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/FindingWithDocs.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/FindingWithDocs.kt @@ -3,26 +3,19 @@ package org.opensearch.commons.alerting.model import org.apache.logging.log4j.LogManager import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable import org.opensearch.common.xcontent.ToXContent import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils -import org.opensearch.commons.notifications.model.BaseModel import java.io.IOException private val log = LogManager.getLogger(Finding::class.java) -class FindingWithDocs : BaseModel { - var finding: Finding - var documents: List - - constructor( - finding: Finding, - documents: List - ) : super() { - this.finding = finding - this.documents = documents - } +class FindingWithDocs( + val finding: Finding, + val documents: List +) : Writeable, ToXContent { @Throws(IOException::class) constructor(sin: StreamInput) : this( @@ -38,10 +31,8 @@ class FindingWithDocs : BaseModel { override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { builder.startObject() - .startObject("finding_with_docs") .field(FINDING_FIELD, finding) - .field(DOCUMENTS_FIELD, documents.toTypedArray()) - .endObject() + .field(DOCUMENTS_FIELD, documents) builder.endObject() return builder } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt new file mode 100644 index 00000000..1f7e733e --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt @@ -0,0 +1,61 @@ +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.DocLevelQuery +import org.opensearch.commons.alerting.model.Finding +import org.opensearch.commons.alerting.model.FindingDocument +import org.opensearch.commons.alerting.model.FindingWithDocs +import org.opensearch.rest.RestStatus +import java.time.Instant +import java.util.List + +internal class GetFindingsResponseTests { + + @Test + fun `test get findings response`() { + + // Alerting GetFindingsResponse mock #1 + val finding1 = Finding( + "1", + listOf("doc1", "doc2", "doc3"), + "monitor_id1", + "monitor_name1", + "test_index1", + listOf(DocLevelQuery("1", "myQuery", "fieldA:valABC", List.of())), + Instant.now() + ) + val findingDocument1 = FindingDocument("test_index1", "doc1", true, "document 1 payload") + val findingDocument2 = FindingDocument("test_index1", "doc2", true, "document 2 payload") + val findingDocument3 = FindingDocument("test_index1", "doc3", true, "document 3 payload") + + val findingWithDocs1 = FindingWithDocs(finding1, listOf(findingDocument1, findingDocument2, findingDocument3)) + + // Alerting GetFindingsResponse mock #2 + + // Alerting GetFindingsResponse mock #2 + val finding2 = Finding( + "1", + listOf("doc21", "doc22"), + "monitor_id2", + "monitor_name2", + "test_index2", + listOf(DocLevelQuery("1", "myQuery", "fieldA:valABC", List.of())), + Instant.now() + ) + val findingDocument21 = FindingDocument("test_index2", "doc21", true, "document 21 payload") + val findingDocument22 = FindingDocument("test_index2", "doc22", true, "document 22 payload") + + val findingWithDocs2 = FindingWithDocs(finding2, listOf(findingDocument21, findingDocument22)) + + val req = GetFindingsResponse(RestStatus.OK, 2, listOf(findingWithDocs1, findingWithDocs2)) + Assertions.assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = GetFindingsResponse(sin) + } +} From 2b2f242202d986573af3b9e4588ab9e34e3a55d3 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Tue, 1 Nov 2022 15:26:09 -0700 Subject: [PATCH 088/149] =?UTF-8?q?Added=20function=20for=20request=20recr?= =?UTF-8?q?eation=20that=20considers=20the=20writeable=20re=E2=80=A6=20(#3?= =?UTF-8?q?03)=20(#304)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added function for request recreation that considers the writeable registry used for parsing the aggregations Co-authored-by: Stevan Buzejic (cherry picked from commit 6a169f7efacb60ff6d86b539d99950300fc37276) Co-authored-by: Stevan Buzejic <30922513+stevanbz@users.noreply.github.com> --- .../alerting/AlertingPluginInterface.kt | 6 ++- .../commons/utils/TransportHelpers.kt | 18 ++++++++ .../alerting/AlertingPluginInterfaceTests.kt | 25 +++++++++- .../action/IndexMonitorRequestTests.kt | 46 +++++++++++++++++++ 4 files changed, 92 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt index cbeb12fc..05c60be8 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt @@ -7,6 +7,7 @@ package org.opensearch.commons.alerting import org.opensearch.action.ActionListener import org.opensearch.action.ActionResponse import org.opensearch.client.node.NodeClient +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 @@ -31,18 +32,20 @@ object AlertingPluginInterface { * Index monitor interface. * @param client Node client for making transport action * @param request The request object + * @param namedWriteableRegistry Registry for building aggregations * @param listener The listener for getting response */ fun indexMonitor( client: NodeClient, request: IndexMonitorRequest, + namedWriteableRegistry: NamedWriteableRegistry, listener: ActionListener ) { client.execute( AlertingActions.INDEX_MONITOR_ACTION_TYPE, request, wrapActionListener(listener) { response -> - recreateObject(response) { + recreateObject(response, namedWriteableRegistry) { IndexMonitorResponse( it ) @@ -50,7 +53,6 @@ object AlertingPluginInterface { } ) } - fun deleteMonitor( client: NodeClient, request: DeleteMonitorRequest, diff --git a/src/main/kotlin/org/opensearch/commons/utils/TransportHelpers.kt b/src/main/kotlin/org/opensearch/commons/utils/TransportHelpers.kt index 049dabdc..cec7ba81 100644 --- a/src/main/kotlin/org/opensearch/commons/utils/TransportHelpers.kt +++ b/src/main/kotlin/org/opensearch/commons/utils/TransportHelpers.kt @@ -6,6 +6,8 @@ package org.opensearch.commons.utils import org.opensearch.common.io.stream.InputStreamStreamInput +import org.opensearch.common.io.stream.NamedWriteableAwareStreamInput +import org.opensearch.common.io.stream.NamedWriteableRegistry import org.opensearch.common.io.stream.OutputStreamStreamOutput import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput @@ -36,3 +38,19 @@ inline fun recreateObject(writeable: Writeable, block: (Stream } } } + +/** + * Re create the object from the writeable. Uses NamedWriteableRegistry in order to build the aggregations. + * This method needs to be inline and reified so that when this is called from + * doExecute() of transport action, the object may be created from other JVM. + */ +inline fun recreateObject(writeable: Writeable, namedWriteableRegistry: NamedWriteableRegistry, block: (StreamInput) -> Request): Request { + ByteArrayOutputStream().use { byteArrayOutputStream -> + OutputStreamStreamOutput(byteArrayOutputStream).use { + writeable.writeTo(it) + InputStreamStreamInput(ByteArrayInputStream(byteArrayOutputStream.toByteArray())).use { streamInput -> + return block(NamedWriteableAwareStreamInput(streamInput, namedWriteableRegistry)) + } + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt index 6171b8a9..62e425d9 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt @@ -12,6 +12,8 @@ import org.mockito.junit.jupiter.MockitoExtension import org.opensearch.action.ActionListener 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.DeleteMonitorRequest import org.opensearch.commons.alerting.action.DeleteMonitorResponse import org.opensearch.commons.alerting.action.GetAlertsRequest @@ -25,6 +27,7 @@ import org.opensearch.commons.alerting.model.FindingWithDocs import org.opensearch.commons.alerting.model.Monitor import org.opensearch.index.seqno.SequenceNumbers import org.opensearch.rest.RestStatus +import org.opensearch.search.SearchModule @Suppress("UNCHECKED_CAST") @ExtendWith(MockitoExtension::class) @@ -41,13 +44,33 @@ internal class AlertingPluginInterfaceTests { 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) Mockito.doAnswer { (it.getArgument(2) as ActionListener) .onResponse(response) }.whenever(client).execute(Mockito.any(ActionType::class.java), Mockito.any(), Mockito.any()) - AlertingPluginInterface.indexMonitor(client, request, listener) + AlertingPluginInterface.indexMonitor(client, request, namedWriteableRegistry, listener) + Mockito.verify(listener, Mockito.times(1)).onResponse(ArgumentMatchers.eq(response)) + } + + @Test + fun indexBucketMonitor() { + 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 listener: ActionListener = + mock(ActionListener::class.java) as ActionListener + val namedWriteableRegistry = NamedWriteableRegistry(SearchModule(Settings.EMPTY, emptyList()).namedWriteables) + + Mockito.doAnswer { + (it.getArgument(2) as ActionListener) + .onResponse(response) + }.whenever(client).execute(Mockito.any(ActionType::class.java), Mockito.any(), Mockito.any()) + AlertingPluginInterface.indexMonitor(client, request, namedWriteableRegistry, listener) + Mockito.verify(listener, Mockito.times(1)).onResponse(ArgumentMatchers.eq(response)) Mockito.verify(listener, Mockito.times(1)).onResponse(ArgumentMatchers.eq(response)) } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequestTests.kt index ce26c7d1..34a1c334 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequestTests.kt @@ -4,10 +4,16 @@ import org.junit.jupiter.api.Assertions 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.NamedWriteableAwareStreamInput +import org.opensearch.common.io.stream.NamedWriteableRegistry import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.settings.Settings import org.opensearch.commons.alerting.model.SearchInput +import org.opensearch.commons.alerting.randomBucketLevelMonitor import org.opensearch.commons.alerting.randomQueryLevelMonitor +import org.opensearch.commons.utils.recreateObject import org.opensearch.rest.RestRequest +import org.opensearch.search.SearchModule import org.opensearch.search.builder.SearchSourceBuilder class IndexMonitorRequestTests { @@ -32,6 +38,46 @@ class IndexMonitorRequestTests { Assertions.assertNotNull(newReq.monitor) } + @Test + fun `test index bucket monitor post request`() { + val req = IndexMonitorRequest( + "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.POST, + randomBucketLevelMonitor() + ) + Assertions.assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val namedWriteableRegistry = NamedWriteableRegistry(SearchModule(Settings.EMPTY, emptyList()).namedWriteables) + val newReq = IndexMonitorRequest(NamedWriteableAwareStreamInput(sin, namedWriteableRegistry)) + Assertions.assertEquals("1234", newReq.monitorId) + Assertions.assertEquals(1L, newReq.seqNo) + Assertions.assertEquals(2L, newReq.primaryTerm) + Assertions.assertEquals(RestRequest.Method.POST, newReq.method) + Assertions.assertNotNull(newReq.monitor) + } + + @Test + fun `Index bucket monitor serialize and deserialize transport object should be equal`() { + val bucketLevelMonitorRequest = IndexMonitorRequest( + "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.POST, + randomBucketLevelMonitor() + ) + + Assertions.assertThrows(UnsupportedOperationException::class.java) { + recreateObject(bucketLevelMonitorRequest) { IndexMonitorRequest(it) } + } + + val recreatedObject = recreateObject(bucketLevelMonitorRequest, NamedWriteableRegistry(SearchModule(Settings.EMPTY, emptyList()).namedWriteables)) { IndexMonitorRequest(it) } + Assertions.assertEquals(bucketLevelMonitorRequest.monitorId, recreatedObject.monitorId) + Assertions.assertEquals(bucketLevelMonitorRequest.seqNo, recreatedObject.seqNo) + Assertions.assertEquals(bucketLevelMonitorRequest.primaryTerm, recreatedObject.primaryTerm) + Assertions.assertEquals(bucketLevelMonitorRequest.method, recreatedObject.method) + Assertions.assertNotNull(recreatedObject.monitor) + Assertions.assertEquals(bucketLevelMonitorRequest.monitor, recreatedObject.monitor) + } + @Test fun `test index monitor put request`() { From 278ca9f096f13f63a98c30979b8235499c20c719 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Wed, 2 Nov 2022 11:06:27 -0700 Subject: [PATCH 089/149] add findings enabled flag and findings field in bucket level monitor alerts constructor (#305) (#307) Signed-off-by: Surya Sashank Nistala Signed-off-by: Surya Sashank Nistala (cherry picked from commit 67fda1075074c9a7b2be2d414c19bfa50f85c1b7) Co-authored-by: Surya Sashank Nistala --- .../commons/alerting/model/Alert.kt | 10 ++++++---- .../commons/alerting/model/DataSources.kt | 19 +++++++++++++++---- 2 files changed, 21 insertions(+), 8 deletions(-) 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 44b8b235..a3f096b9 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt @@ -74,13 +74,14 @@ data class Alert( errorMessage: String? = null, errorHistory: List = mutableListOf(), actionExecutionResults: List = mutableListOf(), - schemaVersion: Int = NO_SCHEMA_VERSION + schemaVersion: Int = NO_SCHEMA_VERSION, + findingIds: List = emptyList() ) : 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() + aggregationResultBucket = null, findingIds = findingIds, relatedDocIds = emptyList() ) constructor( @@ -93,13 +94,14 @@ data class Alert( errorHistory: List = mutableListOf(), actionExecutionResults: List = mutableListOf(), schemaVersion: Int = NO_SCHEMA_VERSION, - aggregationResultBucket: AggregationResultBucket + aggregationResultBucket: AggregationResultBucket, + findingIds: List = emptyList() ) : 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 = emptyList(), relatedDocIds = emptyList() + aggregationResultBucket = aggregationResultBucket, findingIds = findingIds, relatedDocIds = emptyList() ) constructor( diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt index 73b856e1..2c48f6f6 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt @@ -33,7 +33,10 @@ data class DataSources( /** Configures custom mappings by field type for query index. * Custom query index mappings are configurable, only if a custom query index is configured too. */ - val queryIndexMappingsByType: Map> = mapOf() + val queryIndexMappingsByType: Map> = mapOf(), + + /** Configures flag to enable or disable creating and storing findings. */ + val findingsEnabled: Boolean? = false ) : Writeable, ToXContentObject { @@ -71,7 +74,8 @@ data class DataSources( alertsIndex = sin.readString(), alertsHistoryIndex = sin.readOptionalString(), alertsHistoryIndexPattern = sin.readOptionalString(), - queryIndexMappingsByType = sin.readMap() as Map> + queryIndexMappingsByType = sin.readMap() as Map>, + findingsEnabled = sin.readOptionalBoolean() ) @Suppress("UNCHECKED_CAST") @@ -83,7 +87,8 @@ data class DataSources( ALERTS_INDEX_FIELD to alertsIndex, ALERTS_HISTORY_INDEX_FIELD to alertsHistoryIndex, ALERTS_HISTORY_INDEX_PATTERN_FIELD to alertsHistoryIndexPattern, - QUERY_INDEX_MAPPINGS_BY_TYPE to queryIndexMappingsByType + QUERY_INDEX_MAPPINGS_BY_TYPE to queryIndexMappingsByType, + FINDINGS_ENABLED_FIELD to findingsEnabled, ) } @@ -96,6 +101,7 @@ data class DataSources( builder.field(ALERTS_HISTORY_INDEX_FIELD, alertsHistoryIndex) builder.field(ALERTS_HISTORY_INDEX_PATTERN_FIELD, alertsHistoryIndexPattern) builder.field(QUERY_INDEX_MAPPINGS_BY_TYPE, queryIndexMappingsByType as Map) + builder.field(FINDINGS_ENABLED_FIELD, findingsEnabled) builder.endObject() return builder } @@ -108,6 +114,7 @@ data class DataSources( const val ALERTS_HISTORY_INDEX_FIELD = "alerts_history_index" const val ALERTS_HISTORY_INDEX_PATTERN_FIELD = "alerts_history_index_pattern" const val QUERY_INDEX_MAPPINGS_BY_TYPE = "query_index_mappings_by_type" + const val FINDINGS_ENABLED_FIELD = "findings_enabled" @JvmStatic @Throws(IOException::class) @@ -120,6 +127,7 @@ data class DataSources( var alertsHistoryIndex = "" var alertsHistoryIndexPattern = "" var queryIndexMappingsByType: Map> = mapOf() + var findingsEnabled = false XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { @@ -134,6 +142,7 @@ data class DataSources( ALERTS_HISTORY_INDEX_FIELD -> alertsHistoryIndex = xcp.text() ALERTS_HISTORY_INDEX_PATTERN_FIELD -> alertsHistoryIndexPattern = xcp.text() QUERY_INDEX_MAPPINGS_BY_TYPE -> queryIndexMappingsByType = xcp.map() as Map> + FINDINGS_ENABLED_FIELD -> findingsEnabled = xcp.booleanValue() } } return DataSources( @@ -143,7 +152,8 @@ data class DataSources( alertsIndex = alertsIndex, alertsHistoryIndex = alertsHistoryIndex, alertsHistoryIndexPattern = alertsHistoryIndexPattern, - queryIndexMappingsByType = queryIndexMappingsByType + queryIndexMappingsByType = queryIndexMappingsByType, + findingsEnabled = findingsEnabled ) } } @@ -157,5 +167,6 @@ data class DataSources( out.writeOptionalString(alertsHistoryIndex) out.writeOptionalString(alertsHistoryIndexPattern) out.writeMap(queryIndexMappingsByType as Map) + out.writeOptionalBoolean(findingsEnabled) } } From 2c53c254b6ae59fb1c9fa2bc6f4dbc26e9878af8 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Wed, 2 Nov 2022 11:45:19 -0700 Subject: [PATCH 090/149] Support backend roles in indexMonitorRequest (#308) (#309) Signed-off-by: Ashish Agrawal Signed-off-by: Ashish Agrawal (cherry picked from commit b7ceeec5a9d62a4f5188e1a62a27616c0ad63ca8) Co-authored-by: Ashish Agrawal --- .../commons/alerting/action/IndexMonitorRequest.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequest.kt index 6a9b75dd..8a7f4b87 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequest.kt @@ -16,6 +16,7 @@ class IndexMonitorRequest : ActionRequest { val refreshPolicy: WriteRequest.RefreshPolicy val method: RestRequest.Method var monitor: Monitor + val rbacRoles: List? constructor( monitorId: String, @@ -23,7 +24,8 @@ class IndexMonitorRequest : ActionRequest { primaryTerm: Long, refreshPolicy: WriteRequest.RefreshPolicy, method: RestRequest.Method, - monitor: Monitor + monitor: Monitor, + rbacRoles: List? = null ) : super() { this.monitorId = monitorId this.seqNo = seqNo @@ -31,6 +33,7 @@ class IndexMonitorRequest : ActionRequest { this.refreshPolicy = refreshPolicy this.method = method this.monitor = monitor + this.rbacRoles = rbacRoles } @Throws(IOException::class) @@ -40,7 +43,8 @@ class IndexMonitorRequest : ActionRequest { primaryTerm = sin.readLong(), refreshPolicy = WriteRequest.RefreshPolicy.readFrom(sin), method = sin.readEnum(RestRequest.Method::class.java), - monitor = Monitor.readFrom(sin) as Monitor + monitor = Monitor.readFrom(sin) as Monitor, + rbacRoles = sin.readOptionalStringList() ) override fun validate(): ActionRequestValidationException? { @@ -55,5 +59,6 @@ class IndexMonitorRequest : ActionRequest { refreshPolicy.writeTo(out) out.writeEnum(method) monitor.writeTo(out) + out.writeOptionalStringCollection(rbacRoles) } } From 3a83a1a0d8827d76733bb4d6336cc3dd41c66ec6 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Thu, 3 Nov 2022 13:23:12 -0700 Subject: [PATCH 091/149] Adds owner field in monitor model (#313) (#314) Signed-off-by: Surya Sashank Nistala Signed-off-by: Surya Sashank Nistala (cherry picked from commit dfa926306f5cfc6ba07344004f5881f6f51de7b8) Co-authored-by: Surya Sashank Nistala --- .../opensearch/commons/alerting/model/Monitor.kt | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) 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 9f3e22a4..a12e5ef7 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Monitor.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Monitor.kt @@ -40,7 +40,8 @@ data class Monitor( val inputs: List, val triggers: List, val uiMetadata: Map, - val dataSources: DataSources = DataSources() + val dataSources: DataSources = DataSources(), + val owner: String? = "alerting" ) : ScheduledJob { override val type = MONITOR_TYPE @@ -102,7 +103,8 @@ data class Monitor( DataSources(sin) } else { DataSources() - } + }, + owner = sin.readOptionalString() ) // This enum classifies different Monitors @@ -151,6 +153,7 @@ data class Monitor( .optionalTimeField(LAST_UPDATE_TIME_FIELD, lastUpdateTime) if (uiMetadata.isNotEmpty()) builder.field(UI_METADATA_FIELD, uiMetadata) builder.field(DATA_SOURCES_FIELD, dataSources) + builder.field(OWNER_FIELD, owner) if (params.paramAsBoolean("with_type", false)) builder.endObject() return builder.endObject() } @@ -195,6 +198,7 @@ data class Monitor( out.writeMap(uiMetadata) out.writeBoolean(dataSources != null) // for backward compatibility with pre-existing monitors which don't have datasources field dataSources.writeTo(out) + out.writeOptionalString(owner) } companion object { @@ -214,6 +218,7 @@ data class Monitor( const val UI_METADATA_FIELD = "ui_metadata" const val DATA_SOURCES_FIELD = "data_sources" const val ENABLED_TIME_FIELD = "enabled_time" + const val OWNER_FIELD = "owner" // This is defined here instead of in ScheduledJob to avoid having the ScheduledJob class know about all // the different subclasses and creating circular dependencies @@ -240,6 +245,7 @@ data class Monitor( val triggers: MutableList = mutableListOf() val inputs: MutableList = mutableListOf() var dataSources = DataSources() + var owner = "alerting" XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { @@ -287,6 +293,7 @@ data class Monitor( UI_METADATA_FIELD -> uiMetadata = xcp.map() DATA_SOURCES_FIELD -> dataSources = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) DataSources() else DataSources.parse(xcp) + OWNER_FIELD -> owner = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) owner else xcp.text() else -> { xcp.skipChildren() } @@ -312,7 +319,8 @@ data class Monitor( inputs.toList(), triggers.toList(), uiMetadata, - dataSources + dataSources, + owner ) } From 65cf94522f7a2b5661b2466028837126cd94d9f6 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Mon, 7 Nov 2022 14:54:33 -0800 Subject: [PATCH 092/149] Adding CI workflow for Windows OS (#299) (#301) Signed-off-by: Surya Sashank Nistala Signed-off-by: Surya Sashank Nistala (cherry picked from commit 6887c01b498ea97f1b7edc9de47a59f37cca2bdb) Co-authored-by: Surya Sashank Nistala --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0bcc5e2c..b3a4edc7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,9 +14,9 @@ jobs: java: - 11 - 17 - + os: [ubuntu-latest, windows-latest] name: Build and Test - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} steps: - name: Checkout From cc3b254da2bd2d2f972eb57fe4919cd505f0371e Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Thu, 17 Nov 2022 11:40:43 -0800 Subject: [PATCH 093/149] Increment version to 2.5.0-SNAPSHOT (#300) Signed-off-by: opensearch-ci-bot Signed-off-by: opensearch-ci-bot Co-authored-by: opensearch-ci-bot --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b0821bcb..a50ca7c7 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "2.4.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.5.0-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") buildVersionQualifier = System.getProperty("build.version_qualifier", "") kotlin_version = System.getProperty("kotlin.version", "1.6.10") From 97dc9052df6394bb87e7cb93fa2b8448a1c6a847 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Fri, 18 Nov 2022 16:41:40 -0800 Subject: [PATCH 094/149] Add a super admin client builder w/ hosts params (#322) (#323) Signed-off-by: bowenlan-amzn Signed-off-by: bowenlan-amzn (cherry picked from commit 050c95db0da3f78e200da3ad2123faacf1a673b3) Co-authored-by: bowenlan-amzn --- .../opensearch/commons/rest/SecureRestClientBuilder.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/org/opensearch/commons/rest/SecureRestClientBuilder.java b/src/main/java/org/opensearch/commons/rest/SecureRestClientBuilder.java index 96af1540..d4d33e97 100644 --- a/src/main/java/org/opensearch/commons/rest/SecureRestClientBuilder.java +++ b/src/main/java/org/opensearch/commons/rest/SecureRestClientBuilder.java @@ -132,6 +132,15 @@ public SecureRestClientBuilder(Settings settings, Path configPath) { hosts.add(new HttpHost(host, port, httpSSLEnabled ? ConfigConstants.HTTPS : ConfigConstants.HTTP)); } + public SecureRestClientBuilder(Settings settings, Path configPath, HttpHost[] httpHosts) { + this.httpSSLEnabled = settings.getAsBoolean(ConfigConstants.OPENSEARCH_SECURITY_SSL_HTTP_ENABLED, false); + this.settings = settings; + this.configPath = configPath; + this.user = null; + this.passwd = null; + hosts.addAll(Arrays.asList(httpHosts)); + } + /** * Creates a low-level Rest client. * @return From 17f9b4992279cb21228973e0f9c8aa11869b65a2 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Tue, 24 Jan 2023 09:50:27 -0800 Subject: [PATCH 095/149] Fix streaming functions for LegacySNSMessage (#324) (#325) Signed-off-by: Ashish Agrawal Signed-off-by: Ashish Agrawal (cherry picked from commit 84b4ee3eb43a3efb08c74eb897dfd044885dbfff) Co-authored-by: Ashish Agrawal --- .../commons/destination/message/LegacySNSMessage.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacySNSMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacySNSMessage.java index 0476f74e..f8cf6a5b 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacySNSMessage.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacySNSMessage.java @@ -57,8 +57,8 @@ private LegacySNSMessage( public LegacySNSMessage(StreamInput streamInput) throws java.io.IOException { super(streamInput); - this.subject = streamInput.readString(); this.message = super.getMessageContent(); + this.subject = streamInput.readString(); this.roleArn = streamInput.readString(); this.topicArn = streamInput.readString(); this.clusterName = streamInput.readString(); @@ -148,7 +148,6 @@ public String getClusterName() { public void writeTo(StreamOutput streamOutput) throws IOException { super.writeTo(streamOutput); streamOutput.writeString(subject); - streamOutput.writeString(message); streamOutput.writeString(roleArn); streamOutput.writeString(topicArn); streamOutput.writeString(clusterName); From 005e01882d3e7060f24da8cc74c77aa0afae5551 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Thu, 2 Feb 2023 08:20:58 -0800 Subject: [PATCH 096/149] Increment version to 2.6.0-SNAPSHOT (#344) Signed-off-by: opensearch-ci-bot Co-authored-by: opensearch-ci-bot --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a50ca7c7..8639b12d 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "2.5.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.6.0-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") buildVersionQualifier = System.getProperty("build.version_qualifier", "") kotlin_version = System.getProperty("kotlin.version", "1.6.10") From 0778ce270d661abce6c17d88d1492fd5ecd40921 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Tue, 21 Feb 2023 16:49:31 -0800 Subject: [PATCH 097/149] Add release notes (#363) (#364) Signed-off-by: Ashish Agrawal (cherry picked from commit 1ca639f65302ea7acc84621ccf705c24b4278755) Co-authored-by: Ashish Agrawal --- ...ensearch-common-utils.release-notes-2.6.0.0.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 release-notes/opensearch-common-utils.release-notes-2.6.0.0.md diff --git a/release-notes/opensearch-common-utils.release-notes-2.6.0.0.md b/release-notes/opensearch-common-utils.release-notes-2.6.0.0.md new file mode 100644 index 00000000..83549684 --- /dev/null +++ b/release-notes/opensearch-common-utils.release-notes-2.6.0.0.md @@ -0,0 +1,15 @@ +## Version 2.6.0.0 2023-02-21 + +Compatible with OpenSearch 2.6.0 + +### Maintenance +* Increment version to 2.6.0-SNAPSHOT ([#344](https://github.com/opensearch-project/common-utils/pull/344)) + +### Refactoring +* Add a super admin client builder w/ hosts params ([#322](https://github.com/opensearch-project/common-utils/pull/322)) + +### Bug Fixes +* Fix streaming functions for LegacySNSMessage ([#324](https://github.com/opensearch-project/common-utils/pull/324)) + +### Documentation +* Added 2.6 release notes. ([#363](https://github.com/opensearch-project/common-utils/pull/363)) \ No newline at end of file From 5b9de9826de9cc3f12e6c09dbe7707a464c75a5c Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Wed, 22 Feb 2023 10:13:10 -0800 Subject: [PATCH 098/149] 2.x branch - Publish snapshots to maven via GHA (#365) * Publish snapshots to maven via GHA Signed-off-by: Ashish Agrawal * fix task name Signed-off-by: Ashish Agrawal * Adjusted maven function used by workflow. Signed-off-by: AWSHurneyt --------- Signed-off-by: Ashish Agrawal Signed-off-by: AWSHurneyt Co-authored-by: Ashish Agrawal --- .github/workflows/maven-publish.yml | 39 +++++++++++++++++++++++++++++ build.gradle | 8 ++++++ 2 files changed, 47 insertions(+) create mode 100644 .github/workflows/maven-publish.yml diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml new file mode 100644 index 00000000..944c248c --- /dev/null +++ b/.github/workflows/maven-publish.yml @@ -0,0 +1,39 @@ +name: Publish snapshots to maven + +on: + workflow_dispatch: + push: + branches: [ + main + 1.* + 2.* + ] + +jobs: + build-and-publish-snapshots: + strategy: + fail-fast: false + if: github.repository == 'opensearch-project/common-utils' + runs-on: ubuntu-latest + + permissions: + id-token: write + contents: write + + steps: + - uses: actions/setup-java@v3 + with: + distribution: temurin # Temurin is a distribution of adoptium + java-version: 17 + - uses: actions/checkout@v3 + - uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: ${{ secrets.PUBLISH_SNAPSHOTS_ROLE }} + aws-region: us-east-1 + - name: publish snapshots to maven + run: | + export SONATYPE_USERNAME=$(aws secretsmanager get-secret-value --secret-id maven-snapshots-username --query SecretString --output text) + export SONATYPE_PASSWORD=$(aws secretsmanager get-secret-value --secret-id maven-snapshots-password --query SecretString --output text) + echo "::add-mask::$SONATYPE_USERNAME" + echo "::add-mask::$SONATYPE_PASSWORD" + ./gradlew publishShadowPublicationToSnapshotsRepository diff --git a/build.gradle b/build.gradle index 8639b12d..3942a9fa 100644 --- a/build.gradle +++ b/build.gradle @@ -165,6 +165,14 @@ publishing { name = 'staging' url = "${rootProject.buildDir}/local-staging-repo" } + maven { + name = "Snapshots" + url = "https://aws.oss.sonatype.org/content/repositories/snapshots" + credentials { + username "$System.env.SONATYPE_USERNAME" + password "$System.env.SONATYPE_PASSWORD" + } + } } publications { shadow(MavenPublication) { From f95d26a1ca84379cdb94bc196a967d5577480e9b Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Thu, 23 Feb 2023 09:57:02 -0800 Subject: [PATCH 099/149] Increment version to 2.7.0-SNAPSHOT (#371) Signed-off-by: opensearch-ci-bot Co-authored-by: opensearch-ci-bot --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3942a9fa..22a8682a 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "2.6.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.7.0-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") buildVersionQualifier = System.getProperty("build.version_qualifier", "") kotlin_version = System.getProperty("kotlin.version", "1.6.10") From 95d168bc340521253b17e6f2fd3bd60332323e32 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Wed, 1 Mar 2023 13:06:02 -0800 Subject: [PATCH 100/149] Add auto-release workflow (#376) (#378) Signed-off-by: Craig Perkins (cherry picked from commit 89b74570d030809421f378d173044aacfd347adb) Co-authored-by: Craig Perkins --- .github/workflows/auto-release.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/auto-release.yml diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml new file mode 100644 index 00000000..4453d652 --- /dev/null +++ b/.github/workflows/auto-release.yml @@ -0,0 +1,29 @@ +name: Releases + +on: + push: + tags: + - '*' + +jobs: + + build: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: GitHub App token + id: github_app_token + uses: tibdex/github-app-token@v1.5.0 + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + installation_id: 22958780 + - name: Get tag + id: tag + uses: dawidd6/action-get-tag@v1 + - uses: actions/checkout@v2 + - uses: ncipollo/release-action@v1 + with: + github_token: ${{ steps.github_app_token.outputs.token }} + bodyFile: release-notes/opensearch-common-utils.release-notes-${{steps.tag.outputs.tag}}.md \ No newline at end of file From 76375a731dec68662d08073e01da4eabd9eceb9e Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Wed, 15 Mar 2023 09:37:51 -0700 Subject: [PATCH 101/149] Fix SNS pattern regex to correctly valid sns fifo topic ARNs (#381) (#383) Signed-off-by: Surya Sashank Nistala (cherry picked from commit deed4bda567ed371cdea79f3b0f7596ceb63dda6) Co-authored-by: Surya Sashank Nistala --- .../opensearch/commons/notifications/model/Sns.kt | 2 +- .../commons/notifications/model/SnsTests.kt | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt index 583aec49..fc377b75 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt @@ -58,7 +58,7 @@ data class Sns(val topicArn: String, val roleArn: String?) : BaseConfigData { private val log by logger(Sns::class.java) private val SNS_ARN_REGEX = - Pattern.compile("^arn:aws(-[^:]+)?:sns:([a-zA-Z0-9-]+):([0-9]{12}):([a-zA-Z0-9-_]+)$") + Pattern.compile("^arn:aws(-[^:]+)?:sns:([a-zA-Z0-9-]+):([0-9]{12}):([a-zA-Z_0-9+=,.@\\-_/]+)$") /** * reader to create instance of class from writable. diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/SnsTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/SnsTests.kt index 494f09e9..f81e8d3f 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/SnsTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/SnsTests.kt @@ -8,6 +8,7 @@ package org.opensearch.commons.notifications.model import com.fasterxml.jackson.core.JsonParseException import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Assertions.fail import org.junit.jupiter.api.Test import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString @@ -49,6 +50,18 @@ internal class SnsTests { } } + @Test + fun `test SNS correctly validates SNS FIFO topic ARN`() { + try { + Sns( + "arn:aws:sns:ap-southeast-2:333654771707:sns-fifo-alerting.fifo", + "arn:aws:iam::012345678912:role/iam-test" + ) + } catch (e: Exception) { + fail("Expected fifo sns topic ARN to be validated successfully", e) + } + } + @Test fun `SNS serialize and deserialize transport object should be equal`() { val sampleSns = Sns("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") From 3914598bca04a8309f7422845ab57337c226aca7 Mon Sep 17 00:00:00 2001 From: Angie Zhang Date: Tue, 28 Mar 2023 13:26:43 -0700 Subject: [PATCH 102/149] Fixed xContent dependencies due to OSCore changes (#392) Signed-off-by: Angie Zhang --- .../org/opensearch/commons/authuser/User.java | 6 +-- .../action/AcknowledgeAlertResponse.kt | 5 +-- .../alerting/action/AlertingActions.kt | 4 ++ .../alerting/action/DeleteMonitorResponse.kt | 4 +- .../alerting/action/GetAlertsResponse.kt | 4 +- .../alerting/action/GetFindingsResponse.kt | 4 +- .../alerting/action/IndexMonitorResponse.kt | 4 +- .../BucketSelectorExtAggregationBuilder.kt | 8 ++-- .../BucketSelectorExtAggregator.kt | 5 ++- .../BucketSelectorExtFilter.kt | 9 +++-- .../BucketSelectorIndices.kt | 4 +- .../commons/alerting/alerts/AlertError.kt | 7 ++-- .../alerting/model/ActionExecutionResult.kt | 8 ++-- .../alerting/model/AggregationResultBucket.kt | 16 ++++---- .../commons/alerting/model/Alert.kt | 10 ++--- .../alerting/model/BucketLevelTrigger.kt | 13 +++--- .../alerting/model/ClusterMetricsInput.kt | 40 ++++++++++++------- .../commons/alerting/model/DataSources.kt | 10 ++--- .../alerting/model/DocLevelMonitorInput.kt | 19 +++++---- .../commons/alerting/model/DocLevelQuery.kt | 12 +++--- .../alerting/model/DocumentLevelTrigger.kt | 16 ++++---- .../commons/alerting/model/Finding.kt | 9 +++-- .../commons/alerting/model/FindingDocument.kt | 9 +++-- .../commons/alerting/model/FindingWithDocs.kt | 6 +-- .../commons/alerting/model/Input.kt | 2 +- .../commons/alerting/model/Monitor.kt | 13 +++--- .../alerting/model/QueryLevelTrigger.kt | 20 ++++++---- .../commons/alerting/model/Schedule.kt | 17 ++++---- .../commons/alerting/model/ScheduledJob.kt | 6 +-- .../commons/alerting/model/SearchInput.kt | 13 +++--- .../commons/alerting/model/Trigger.kt | 2 +- .../commons/alerting/model/action/Action.kt | 9 +++-- .../model/action/ActionExecutionPolicy.kt | 6 +-- .../model/action/ActionExecutionScope.kt | 6 +-- .../commons/alerting/model/action/Throttle.kt | 6 +-- .../commons/alerting/util/IndexUtils.kt | 4 +- .../notifications/action/BaseResponse.kt | 2 +- .../action/CreateNotificationConfigRequest.kt | 8 ++-- .../CreateNotificationConfigResponse.kt | 6 +-- .../action/DeleteNotificationConfigRequest.kt | 8 ++-- .../DeleteNotificationConfigResponse.kt | 6 +-- .../action/GetChannelListRequest.kt | 8 ++-- .../action/GetChannelListResponse.kt | 6 +-- .../action/GetNotificationConfigRequest.kt | 8 ++-- .../action/GetNotificationConfigResponse.kt | 6 +-- .../action/GetPluginFeaturesRequest.kt | 8 ++-- .../action/GetPluginFeaturesResponse.kt | 6 +-- .../LegacyPublishNotificationResponse.kt | 4 +- .../action/SendNotificationRequest.kt | 8 ++-- .../action/SendNotificationResponse.kt | 6 +-- .../action/UpdateNotificationConfigRequest.kt | 8 ++-- .../UpdateNotificationConfigResponse.kt | 6 +-- .../commons/notifications/model/Attachment.kt | 6 +-- .../commons/notifications/model/BaseModel.kt | 2 +- .../commons/notifications/model/Channel.kt | 6 +-- .../notifications/model/ChannelList.kt | 2 +- .../notifications/model/ChannelMessage.kt | 7 ++-- .../commons/notifications/model/Chime.kt | 6 +-- .../notifications/model/DeliveryStatus.kt | 6 +-- .../commons/notifications/model/Email.kt | 6 +-- .../commons/notifications/model/EmailGroup.kt | 6 +-- .../notifications/model/EmailRecipient.kt | 6 +-- .../model/EmailRecipientStatus.kt | 6 +-- .../notifications/model/EventSource.kt | 6 +-- .../notifications/model/EventStatus.kt | 6 +-- .../notifications/model/NotificationConfig.kt | 6 +-- .../model/NotificationConfigInfo.kt | 6 +-- .../model/NotificationConfigSearchResult.kt | 2 +- .../notifications/model/NotificationEvent.kt | 8 ++-- .../notifications/model/SearchResults.kt | 6 +-- .../commons/notifications/model/SesAccount.kt | 6 +-- .../commons/notifications/model/Slack.kt | 6 +-- .../notifications/model/SmtpAccount.kt | 6 +-- .../commons/notifications/model/Sns.kt | 6 +-- .../commons/notifications/model/Webhook.kt | 6 +-- .../commons/notifications/model/XParser.kt | 2 +- .../model/config/ConfigDataProperties.kt | 2 +- .../opensearch/commons/utils/EnumHelpers.kt | 2 +- .../commons/utils/XContentHelpers.kt | 12 +++--- .../commons/alerting/TestHelpers.kt | 22 ++++++---- .../alerting/action/GetAlertsResponseTests.kt | 2 +- .../model/DocLevelMonitorInputTests.kt | 4 +- .../alerting/model/MockScheduledJob.kt | 4 +- .../commons/alerting/model/ScheduleTest.kt | 14 ++++--- .../alerting/model/XContentTestBase.kt | 6 +-- .../commons/alerting/model/XContentTests.kt | 2 +- .../opensearch/commons/utils/TestHelpers.kt | 8 ++-- 87 files changed, 348 insertions(+), 301 deletions(-) diff --git a/src/main/java/org/opensearch/commons/authuser/User.java b/src/main/java/org/opensearch/commons/authuser/User.java index f698d36b..b271b371 100644 --- a/src/main/java/org/opensearch/commons/authuser/User.java +++ b/src/main/java/org/opensearch/commons/authuser/User.java @@ -22,11 +22,11 @@ import org.opensearch.common.io.stream.StreamInput; import org.opensearch.common.io.stream.StreamOutput; import org.opensearch.common.io.stream.Writeable; -import org.opensearch.common.xcontent.ToXContent; -import org.opensearch.common.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentHelper; -import org.opensearch.common.xcontent.XContentParser; import org.opensearch.common.xcontent.json.JsonXContent; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; /** * Gets current Authenticated User - name, odfe roles. diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponse.kt index de0e26da..858ad4be 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponse.kt @@ -6,10 +6,10 @@ package org.opensearch.commons.alerting.action import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder 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 @@ -45,7 +45,6 @@ class AcknowledgeAlertResponse : BaseResponse { @Throws(IOException::class) override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { - builder.startObject().startArray("success") acknowledged.forEach { builder.value(it.id) } builder.endArray().startArray("failed") 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 23a5ce77..739217f2 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt @@ -16,15 +16,19 @@ object AlertingActions { @JvmField val INDEX_MONITOR_ACTION_TYPE = ActionType(INDEX_MONITOR_ACTION_NAME, ::IndexMonitorResponse) + @JvmField val GET_ALERTS_ACTION_TYPE = ActionType(GET_ALERTS_ACTION_NAME, ::GetAlertsResponse) + @JvmField val DELETE_MONITOR_ACTION_TYPE = ActionType(DELETE_MONITOR_ACTION_NAME, ::DeleteMonitorResponse) + @JvmField val GET_FINDINGS_ACTION_TYPE = ActionType(GET_FINDINGS_ACTION_NAME, ::GetFindingsResponse) + @JvmField val ACKNOWLEDGE_ALERTS_ACTION_TYPE = ActionType(ACKNOWLEDGE_ALERTS_ACTION_NAME, ::AcknowledgeAlertResponse) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorResponse.kt index 08766925..3d9b9920 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorResponse.kt @@ -2,10 +2,10 @@ package org.opensearch.commons.alerting.action import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.commons.alerting.util.IndexUtils import org.opensearch.commons.notifications.action.BaseResponse +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder class DeleteMonitorResponse : BaseResponse { var id: String diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponse.kt index 6be61a1c..37ffaf61 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponse.kt @@ -2,10 +2,10 @@ package org.opensearch.commons.alerting.action import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder 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 diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponse.kt index 216666be..e0e0d06d 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponse.kt @@ -2,10 +2,10 @@ package org.opensearch.commons.alerting.action import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.commons.alerting.model.FindingWithDocs import org.opensearch.commons.notifications.action.BaseResponse +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.rest.RestStatus import java.io.IOException diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorResponse.kt index 5b62a843..f0ddda17 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorResponse.kt @@ -2,14 +2,14 @@ package org.opensearch.commons.alerting.action import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.commons.alerting.model.Monitor import org.opensearch.commons.alerting.util.IndexUtils.Companion._ID import org.opensearch.commons.alerting.util.IndexUtils.Companion._PRIMARY_TERM import org.opensearch.commons.alerting.util.IndexUtils.Companion._SEQ_NO import org.opensearch.commons.alerting.util.IndexUtils.Companion._VERSION import org.opensearch.commons.notifications.action.BaseResponse +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder import java.io.IOException class IndexMonitorResponse : BaseResponse { diff --git a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregationBuilder.kt b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregationBuilder.kt index d19d0877..37c13350 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregationBuilder.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregationBuilder.kt @@ -1,14 +1,14 @@ package org.opensearch.commons.alerting.aggregation.bucketselectorext -import org.opensearch.common.ParseField import org.opensearch.common.ParsingException import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.commons.alerting.aggregation.bucketselectorext.BucketSelectorExtFilter.Companion.BUCKET_SELECTOR_COMPOSITE_AGG_FILTER import org.opensearch.commons.alerting.aggregation.bucketselectorext.BucketSelectorExtFilter.Companion.BUCKET_SELECTOR_FILTER +import org.opensearch.core.ParseField +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import org.opensearch.script.Script import org.opensearch.search.aggregations.pipeline.AbstractPipelineAggregationBuilder import org.opensearch.search.aggregations.pipeline.BucketHelpers diff --git a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregator.kt b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregator.kt index 373bd5e9..c0e48b41 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregator.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregator.kt @@ -129,7 +129,10 @@ class BucketSelectorExtAggregator : SiblingPipelineAggregator { } return BucketSelectorIndices( - name(), parentBucketPath, selectedBucketsIndex, originalAgg.metadata + name(), + parentBucketPath, + selectedBucketsIndex, + originalAgg.metadata ) } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtFilter.kt b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtFilter.kt index 0a776d0f..e971deb1 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtFilter.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtFilter.kt @@ -1,19 +1,20 @@ package org.opensearch.commons.alerting.aggregation.bucketselectorext -import org.opensearch.common.ParseField import org.opensearch.common.ParsingException import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.commons.notifications.model.BaseModel +import org.opensearch.core.ParseField +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import org.opensearch.search.aggregations.bucket.terms.IncludeExclude import java.io.IOException class BucketSelectorExtFilter : BaseModel { // used for composite aggregations val filtersMap: HashMap? + // used for filtering string term aggregation val filters: IncludeExclude? diff --git a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorIndices.kt b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorIndices.kt index 9cb238e7..94790987 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorIndices.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorIndices.kt @@ -1,8 +1,8 @@ package org.opensearch.commons.alerting.aggregation.bucketselectorext import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.search.aggregations.InternalAggregation import java.io.IOException import java.util.Objects diff --git a/src/main/kotlin/org/opensearch/commons/alerting/alerts/AlertError.kt b/src/main/kotlin/org/opensearch/commons/alerting/alerts/AlertError.kt index 9854f9e5..11c8ae17 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/alerts/AlertError.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/alerts/AlertError.kt @@ -3,12 +3,12 @@ package org.opensearch.commons.alerting.alerts import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken import org.opensearch.commons.alerting.util.instant import org.opensearch.commons.alerting.util.optionalTimeField +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException import java.time.Instant @@ -33,7 +33,6 @@ data class AlertError(val timestamp: Instant, val message: String) : Writeable, @JvmStatic @Throws(IOException::class) fun parse(xcp: XContentParser): AlertError { - lateinit var timestamp: Instant lateinit var message: String diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/ActionExecutionResult.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/ActionExecutionResult.kt index cf29d297..84fd292b 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/ActionExecutionResult.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/ActionExecutionResult.kt @@ -3,13 +3,13 @@ package org.opensearch.commons.alerting.model import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.ToXContentObject -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.alerting.util.instant import org.opensearch.commons.alerting.util.optionalTimeField +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.ToXContentObject +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException import java.time.Instant diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/AggregationResultBucket.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/AggregationResultBucket.kt index 2c75332d..97a48afb 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/AggregationResultBucket.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/AggregationResultBucket.kt @@ -4,12 +4,12 @@ import org.opensearch.common.ParsingException import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.ToXContentObject -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser -import org.opensearch.common.xcontent.XContentParser.Token import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.ToXContentObject +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParser.Token import java.io.IOException import java.util.Locale @@ -59,8 +59,10 @@ data class AggregationResultBucket( throw ParsingException( xcp.tokenLocation, String.format( - Locale.ROOT, "Failed to parse object: expecting token with name [%s] but found [%s]", - CONFIG_NAME, xcp.currentName() + Locale.ROOT, + "Failed to parse object: expecting token with name [%s] but found [%s]", + CONFIG_NAME, + xcp.currentName() ) ) } 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 a3f096b9..032c484a 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt @@ -4,9 +4,6 @@ import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable import org.opensearch.common.lucene.uid.Versions -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken import org.opensearch.commons.alerting.alerts.AlertError import org.opensearch.commons.alerting.util.IndexUtils.Companion.NO_SCHEMA_VERSION @@ -14,6 +11,9 @@ import org.opensearch.commons.alerting.util.instant import org.opensearch.commons.alerting.util.optionalTimeField import org.opensearch.commons.alerting.util.optionalUserField import org.opensearch.commons.authuser.User +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException import java.time.Instant @@ -216,10 +216,10 @@ data class Alert( const val NO_ID = "" const val NO_VERSION = Versions.NOT_FOUND - @JvmStatic @JvmOverloads + @JvmStatic + @JvmOverloads @Throws(IOException::class) fun parse(xcp: XContentParser, id: String = NO_ID, version: Long = NO_VERSION): Alert { - lateinit var monitorId: String var schemaVersion = NO_SCHEMA_VERSION lateinit var monitorName: String diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/BucketLevelTrigger.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/BucketLevelTrigger.kt index 56975beb..38af9d3a 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/BucketLevelTrigger.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/BucketLevelTrigger.kt @@ -1,14 +1,9 @@ package org.opensearch.commons.alerting.model import org.opensearch.common.CheckedFunction -import org.opensearch.common.ParseField import org.opensearch.common.UUIDs import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.NamedXContentRegistry -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.alerting.aggregation.bucketselectorext.BucketSelectorExtAggregationBuilder import org.opensearch.commons.alerting.model.Trigger.Companion.ACTIONS_FIELD @@ -16,6 +11,11 @@ import org.opensearch.commons.alerting.model.Trigger.Companion.ID_FIELD import org.opensearch.commons.alerting.model.Trigger.Companion.NAME_FIELD import org.opensearch.commons.alerting.model.Trigger.Companion.SEVERITY_FIELD 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 BucketLevelTrigger( @@ -83,7 +83,8 @@ data class BucketLevelTrigger( const val PARENT_BUCKET_PATH = "parentBucketPath" val XCONTENT_REGISTRY = NamedXContentRegistry.Entry( - Trigger::class.java, ParseField(BUCKET_LEVEL_TRIGGER_FIELD), + Trigger::class.java, + ParseField(BUCKET_LEVEL_TRIGGER_FIELD), CheckedFunction { parseInner(it) } ) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt index fc805917..bb658df3 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt @@ -3,14 +3,14 @@ package org.opensearch.commons.alerting.model import org.apache.commons.validator.routines.UrlValidator import org.apache.http.client.utils.URIBuilder import org.opensearch.common.CheckedFunction -import org.opensearch.common.ParseField import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.NamedXContentRegistry -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils +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 import java.net.URI @@ -43,10 +43,11 @@ data class ClusterMetricsInput( "Invalid URI constructed from the path and path_params inputs, or the url input." } - if (url.isNotEmpty() && validateFieldsNotEmpty()) + if (url.isNotEmpty() && validateFieldsNotEmpty()) { require(constructedUri == constructUrlFromInputs()) { "The provided URL and URI fields form different URLs." } + } require(constructedUri.host.lowercase() == SUPPORTED_HOST) { "Only host '$SUPPORTED_HOST' is supported." @@ -104,7 +105,8 @@ data class ClusterMetricsInput( /** * This parse function uses [XContentParser] to parse JSON input and store corresponding fields to create a [ClusterMetricsInput] object */ - @JvmStatic @Throws(IOException::class) + @JvmStatic + @Throws(IOException::class) fun parseInner(xcp: XContentParser): ClusterMetricsInput { var path = "" var pathParams = "" @@ -161,17 +163,20 @@ data class ClusterMetricsInput( if (pathParams.isNotEmpty()) { pathParams = pathParams.trim('/') ILLEGAL_PATH_PARAMETER_CHARACTERS.forEach { character -> - if (pathParams.contains(character)) + if (pathParams.contains(character)) { throw IllegalArgumentException( "The provided path parameters contain invalid characters or spaces. Please omit: " + "${ILLEGAL_PATH_PARAMETER_CHARACTERS.joinToString(" ")}" ) + } } } - if (apiType.requiresPathParams && pathParams.isEmpty()) + if (apiType.requiresPathParams && pathParams.isEmpty()) { throw IllegalArgumentException("The API requires path parameters.") - if (!apiType.supportsPathParams && pathParams.isNotEmpty()) + } + if (!apiType.supportsPathParams && pathParams.isNotEmpty()) { throw IllegalArgumentException("The API does not use path parameters.") + } return pathParams } @@ -187,11 +192,13 @@ data class ClusterMetricsInput( ClusterMetricType.values() .filter { option -> option != ClusterMetricType.BLANK } .forEach { option -> - if (uriPath.startsWith(option.prependPath) || uriPath.startsWith(option.defaultPath)) + if (uriPath.startsWith(option.prependPath) || uriPath.startsWith(option.defaultPath)) { apiType = option + } } - if (apiType.isBlank()) + if (apiType.isBlank()) { throw IllegalArgumentException("The API could not be determined from the provided URI.") + } return apiType } @@ -213,12 +220,15 @@ data class ClusterMetricsInput( * If [path] and [pathParams] are empty, populates them with values from [url]. */ private fun parseEmptyFields() { - if (pathParams.isEmpty()) + if (pathParams.isEmpty()) { pathParams = this.parsePathParams() - if (path.isEmpty()) + } + if (path.isEmpty()) { path = if (pathParams.isEmpty()) clusterMetricType.defaultPath else clusterMetricType.prependPath - if (url.isEmpty()) + } + if (url.isEmpty()) { url = constructedUri.toString() + } } /** diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt index 2c48f6f6..64c2c819 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt @@ -3,11 +3,11 @@ package org.opensearch.commons.alerting.model import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.ToXContentObject -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.ToXContentObject +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException data class DataSources( @@ -88,7 +88,7 @@ data class DataSources( ALERTS_HISTORY_INDEX_FIELD to alertsHistoryIndex, ALERTS_HISTORY_INDEX_PATTERN_FIELD to alertsHistoryIndexPattern, QUERY_INDEX_MAPPINGS_BY_TYPE to queryIndexMappingsByType, - FINDINGS_ENABLED_FIELD to findingsEnabled, + FINDINGS_ENABLED_FIELD to findingsEnabled ) } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelMonitorInput.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelMonitorInput.kt index 9a914f3e..b16d0c08 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelMonitorInput.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelMonitorInput.kt @@ -1,14 +1,14 @@ package org.opensearch.commons.alerting.model import org.opensearch.common.CheckedFunction -import org.opensearch.common.ParseField import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.NamedXContentRegistry -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils +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 DocLevelMonitorInput( @@ -64,10 +64,12 @@ data class DocLevelMonitorInput( val XCONTENT_REGISTRY = NamedXContentRegistry.Entry( Input::class.java, - ParseField(DOC_LEVEL_INPUT_FIELD), CheckedFunction { parse(it) } + ParseField(DOC_LEVEL_INPUT_FIELD), + CheckedFunction { parse(it) } ) - @JvmStatic @Throws(IOException::class) + @JvmStatic + @Throws(IOException::class) fun parse(xcp: XContentParser): DocLevelMonitorInput { var description: String = NO_DESCRIPTION val indices: MutableList = mutableListOf() @@ -106,7 +108,8 @@ data class DocLevelMonitorInput( return DocLevelMonitorInput(description = description, indices = indices, queries = docLevelQueries) } - @JvmStatic @Throws(IOException::class) + @JvmStatic + @Throws(IOException::class) fun readFrom(sin: StreamInput): DocLevelMonitorInput { return DocLevelMonitorInput(sin) } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt index 220c928d..5d3749dd 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt @@ -2,11 +2,11 @@ package org.opensearch.commons.alerting.model import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.model.BaseModel +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException import java.lang.IllegalArgumentException import java.util.UUID @@ -69,7 +69,8 @@ data class DocLevelQuery( const val NO_ID = "" val INVALID_CHARACTERS: List = listOf(" ", "[", "]", "{", "}", "(", ")") - @JvmStatic @Throws(IOException::class) + @JvmStatic + @Throws(IOException::class) fun parse(xcp: XContentParser): DocLevelQuery { var id: String = UUID.randomUUID().toString() lateinit var query: String @@ -111,7 +112,8 @@ data class DocLevelQuery( ) } - @JvmStatic @Throws(IOException::class) + @JvmStatic + @Throws(IOException::class) fun readFrom(sin: StreamInput): DocLevelQuery { return DocLevelQuery(sin) } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/DocumentLevelTrigger.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/DocumentLevelTrigger.kt index a9cd0d69..0403f94c 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/DocumentLevelTrigger.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/DocumentLevelTrigger.kt @@ -1,20 +1,20 @@ package org.opensearch.commons.alerting.model import org.opensearch.common.CheckedFunction -import org.opensearch.common.ParseField import org.opensearch.common.UUIDs import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.NamedXContentRegistry -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.alerting.model.Trigger.Companion.ACTIONS_FIELD import org.opensearch.commons.alerting.model.Trigger.Companion.ID_FIELD import org.opensearch.commons.alerting.model.Trigger.Companion.NAME_FIELD import org.opensearch.commons.alerting.model.Trigger.Companion.SEVERITY_FIELD 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 org.opensearch.script.Script import java.io.IOException @@ -80,11 +80,13 @@ data class DocumentLevelTrigger( const val QUERY_IDS_FIELD = "query_ids" val XCONTENT_REGISTRY = NamedXContentRegistry.Entry( - Trigger::class.java, ParseField(DOCUMENT_LEVEL_TRIGGER_FIELD), + Trigger::class.java, + ParseField(DOCUMENT_LEVEL_TRIGGER_FIELD), CheckedFunction { parseInner(it) } ) - @JvmStatic @Throws(IOException::class) + @JvmStatic + @Throws(IOException::class) fun parseInner(xcp: XContentParser): DocumentLevelTrigger { var id = UUIDs.base64UUID() // assign a default triggerId if one is not specified lateinit var name: String diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt index 899189b8..e8102779 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt @@ -3,11 +3,11 @@ package org.opensearch.commons.alerting.model import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken import org.opensearch.commons.alerting.util.instant +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException import java.time.Instant @@ -81,7 +81,8 @@ class Finding( const val TIMESTAMP_FIELD = "timestamp" const val NO_ID = "" - @JvmStatic @JvmOverloads + @JvmStatic + @JvmOverloads @Throws(IOException::class) fun parse(xcp: XContentParser): Finding { var id: String = NO_ID diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/FindingDocument.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/FindingDocument.kt index c30eb6be..0017dc0e 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/FindingDocument.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/FindingDocument.kt @@ -4,10 +4,10 @@ import org.apache.logging.log4j.LogManager import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException private val log = LogManager.getLogger(FindingDocument::class.java) @@ -52,7 +52,8 @@ class FindingDocument( const val NO_ID = "" const val NO_INDEX = "" - @JvmStatic @JvmOverloads + @JvmStatic + @JvmOverloads @Throws(IOException::class) fun parse(xcp: XContentParser, id: String = NO_ID, index: String = NO_INDEX): FindingDocument { var found = false diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/FindingWithDocs.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/FindingWithDocs.kt index 445791f6..69505fc2 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/FindingWithDocs.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/FindingWithDocs.kt @@ -4,10 +4,10 @@ import org.apache.logging.log4j.LogManager import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException private val log = LogManager.getLogger(Finding::class.java) 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 1a193ba1..927cb7e8 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Input.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Input.kt @@ -1,12 +1,12 @@ package org.opensearch.commons.alerting.model import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils 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.notifications.model.BaseModel +import org.opensearch.core.xcontent.XContentParser import java.io.IOException interface Input : BaseModel { 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 a12e5ef7..0c969939 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Monitor.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Monitor.kt @@ -1,13 +1,8 @@ package org.opensearch.commons.alerting.model import org.opensearch.common.CheckedFunction -import org.opensearch.common.ParseField import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.NamedXContentRegistry -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.alerting.util.IndexUtils.Companion.MONITOR_MAX_INPUTS import org.opensearch.commons.alerting.util.IndexUtils.Companion.MONITOR_MAX_TRIGGERS @@ -20,6 +15,11 @@ import org.opensearch.commons.alerting.util.isBucketLevelMonitor import org.opensearch.commons.alerting.util.optionalTimeField import org.opensearch.commons.alerting.util.optionalUserField import org.opensearch.commons.authuser.User +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 import java.time.Instant import java.util.Locale @@ -273,8 +273,9 @@ data class Monitor( ) while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { val input = Input.parse(xcp) - if (input is ClusterMetricsInput) + if (input is ClusterMetricsInput) { supportedClusterMetricsSettings?.validateApiType(input) + } inputs.add(input) } } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/QueryLevelTrigger.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/QueryLevelTrigger.kt index 98011ff5..421b15a9 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/QueryLevelTrigger.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/QueryLevelTrigger.kt @@ -1,20 +1,20 @@ package org.opensearch.commons.alerting.model import org.opensearch.common.CheckedFunction -import org.opensearch.common.ParseField import org.opensearch.common.UUIDs import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.NamedXContentRegistry -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.alerting.model.Trigger.Companion.ACTIONS_FIELD import org.opensearch.commons.alerting.model.Trigger.Companion.ID_FIELD import org.opensearch.commons.alerting.model.Trigger.Companion.NAME_FIELD import org.opensearch.commons.alerting.model.Trigger.Companion.SEVERITY_FIELD 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 org.opensearch.script.Script import java.io.IOException @@ -57,7 +57,9 @@ data class QueryLevelTrigger( /** Returns a representation of the trigger suitable for passing into painless and mustache scripts. */ fun asTemplateArg(): Map { return mapOf( - ID_FIELD to id, NAME_FIELD to name, SEVERITY_FIELD to severity, + ID_FIELD to id, + NAME_FIELD to name, + SEVERITY_FIELD to severity, ACTIONS_FIELD to actions.map { it.asTemplateArg() } ) } @@ -77,7 +79,8 @@ data class QueryLevelTrigger( const val SCRIPT_FIELD = "script" val XCONTENT_REGISTRY = NamedXContentRegistry.Entry( - Trigger::class.java, ParseField(QUERY_LEVEL_TRIGGER_FIELD), + Trigger::class.java, + ParseField(QUERY_LEVEL_TRIGGER_FIELD), CheckedFunction { parseInner(it) } ) @@ -113,7 +116,8 @@ data class QueryLevelTrigger( * It isn't typically conventional but this parse method will account for both START_OBJECT * and FIELD_NAME as the starting token to cover both cases. */ - @JvmStatic @Throws(IOException::class) + @JvmStatic + @Throws(IOException::class) fun parseInner(xcp: XContentParser): QueryLevelTrigger { var id = UUIDs.base64UUID() // assign a default triggerId if one is not specified lateinit var name: String diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Schedule.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Schedule.kt index 68c174ca..1f6be01c 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Schedule.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Schedule.kt @@ -6,11 +6,11 @@ import com.cronutils.model.time.ExecutionTime import com.cronutils.parser.CronParser import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.model.BaseModel +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException import java.time.DateTimeException import java.time.Duration @@ -33,7 +33,8 @@ sealed class Schedule : BaseModel { val cronParser = CronParser(CronDefinitionBuilder.instanceDefinitionFor(CronType.UNIX)) - @JvmStatic @Throws(IOException::class) + @JvmStatic + @Throws(IOException::class) fun parse(xcp: XContentParser): Schedule { var expression: String? = null var timezone: ZoneId? = null @@ -91,7 +92,8 @@ sealed class Schedule : BaseModel { return requireNotNull(schedule) { "Schedule is null." } } - @JvmStatic @Throws(IllegalArgumentException::class) + @JvmStatic + @Throws(IllegalArgumentException::class) private fun getTimeZone(timeZone: String): ZoneId { try { return ZoneId.of(timeZone) @@ -106,10 +108,11 @@ sealed class Schedule : BaseModel { @Throws(IOException::class) fun readFrom(sin: StreamInput): Schedule { val type = sin.readEnum(Schedule.TYPE::class.java) - if (type == Schedule.TYPE.CRON) + if (type == Schedule.TYPE.CRON) { return CronSchedule(sin) - else + } else { return IntervalSchedule(sin) + } } } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/ScheduledJob.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/ScheduledJob.kt index ac1037f0..3df83609 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/ScheduledJob.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/ScheduledJob.kt @@ -1,10 +1,10 @@ package org.opensearch.commons.alerting.model -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.model.BaseModel +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException import java.time.Instant diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/SearchInput.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/SearchInput.kt index d12e935b..4fcbdd63 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/SearchInput.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/SearchInput.kt @@ -1,14 +1,14 @@ package org.opensearch.commons.alerting.model import org.opensearch.common.CheckedFunction -import org.opensearch.common.ParseField import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.NamedXContentRegistry -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils +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 org.opensearch.search.builder.SearchSourceBuilder import java.io.IOException @@ -46,7 +46,8 @@ data class SearchInput(val indices: List, val query: SearchSourceBuilder val XCONTENT_REGISTRY = NamedXContentRegistry.Entry(Input::class.java, ParseField("search"), CheckedFunction { parseInner(it) }) - @JvmStatic @Throws(IOException::class) + @JvmStatic + @Throws(IOException::class) fun parseInner(xcp: XContentParser): SearchInput { val indices = mutableListOf() lateinit var searchSourceBuilder: SearchSourceBuilder 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 dfc2797d..4b83fed3 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Trigger.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Trigger.kt @@ -1,10 +1,10 @@ package org.opensearch.commons.alerting.model import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.alerting.model.action.Action import org.opensearch.commons.notifications.model.BaseModel +import org.opensearch.core.xcontent.XContentParser import java.io.IOException interface Trigger : BaseModel { diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/action/Action.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/action/Action.kt index e8b9f87e..3a51dec5 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/action/Action.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/action/Action.kt @@ -3,11 +3,11 @@ package org.opensearch.commons.alerting.model.action import org.opensearch.common.UUIDs import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.model.BaseModel +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import org.opensearch.script.Script import java.io.IOException @@ -130,8 +130,9 @@ data class Action( NAME_FIELD -> name = xcp.textOrNull() DESTINATION_ID_FIELD -> destinationId = xcp.textOrNull() SUBJECT_TEMPLATE_FIELD -> { - subjectTemplate = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) null else + subjectTemplate = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) null else { Script.parse(xcp, Script.DEFAULT_TEMPLATE_LANG) + } } MESSAGE_TEMPLATE_FIELD -> messageTemplate = Script.parse(xcp, Script.DEFAULT_TEMPLATE_LANG) THROTTLE_FIELD -> { diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionPolicy.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionPolicy.kt index 044f3a24..214f49f4 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionPolicy.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionPolicy.kt @@ -2,11 +2,11 @@ package org.opensearch.commons.alerting.model.action import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.model.BaseModel +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException data class ActionExecutionPolicy( diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionScope.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionScope.kt index 2e252475..8455d6b7 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionScope.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionScope.kt @@ -2,11 +2,11 @@ package org.opensearch.commons.alerting.model.action import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.model.BaseModel +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException import java.lang.IllegalArgumentException diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/action/Throttle.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/action/Throttle.kt index c7defeee..2150d60f 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/action/Throttle.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/action/Throttle.kt @@ -3,11 +3,11 @@ package org.opensearch.commons.alerting.model.action import org.apache.commons.codec.binary.StringUtils import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.model.BaseModel +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException import java.time.temporal.ChronoUnit import java.util.Locale diff --git a/src/main/kotlin/org/opensearch/commons/alerting/util/IndexUtils.kt b/src/main/kotlin/org/opensearch/commons/alerting/util/IndexUtils.kt index eef89a0a..803ab675 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/util/IndexUtils.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/util/IndexUtils.kt @@ -1,12 +1,12 @@ package org.opensearch.commons.alerting.util import org.opensearch.common.bytes.BytesReference -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.alerting.model.Monitor import org.opensearch.commons.alerting.settings.SupportedClusterMetricsSettings import org.opensearch.commons.authuser.User +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.time.Instant class IndexUtils { diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/BaseResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/BaseResponse.kt index e0659e8f..8632d9ab 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/BaseResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/BaseResponse.kt @@ -7,7 +7,7 @@ package org.opensearch.commons.notifications.action import org.opensearch.action.ActionResponse import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.xcontent.ToXContentObject +import org.opensearch.core.xcontent.ToXContentObject import org.opensearch.rest.RestStatus import java.io.IOException diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequest.kt index dfbd1b95..29d08084 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequest.kt @@ -9,10 +9,6 @@ import org.opensearch.action.ActionRequestValidationException import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.ToXContentObject -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_TAG import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TAG @@ -20,6 +16,10 @@ import org.opensearch.commons.notifications.model.NotificationConfig import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateId +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.ToXContentObject +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponse.kt index e8bce5af..f91fb89f 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponse.kt @@ -7,12 +7,12 @@ package org.opensearch.commons.notifications.action import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_TAG import org.opensearch.commons.utils.logger +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequest.kt index 1bcd355a..699274f1 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequest.kt @@ -10,14 +10,14 @@ import org.opensearch.action.ValidateActions import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.ToXContentObject -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_LIST_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.stringList +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.ToXContentObject +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponse.kt index efb68005..71b1e7d9 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponse.kt @@ -7,9 +7,6 @@ package org.opensearch.commons.notifications.action import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.DELETE_RESPONSE_LIST_TAG import org.opensearch.commons.utils.STRING_READER @@ -17,6 +14,9 @@ import org.opensearch.commons.utils.STRING_WRITER import org.opensearch.commons.utils.enumReader import org.opensearch.commons.utils.enumWriter import org.opensearch.commons.utils.logger +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import org.opensearch.rest.RestStatus import java.io.IOException diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListRequest.kt index 96a54eb2..8be96bfd 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListRequest.kt @@ -9,13 +9,13 @@ import org.opensearch.action.ActionRequestValidationException import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.ToXContentObject -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.COMPACT_TAG import org.opensearch.commons.utils.logger +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.ToXContentObject +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListResponse.kt index 89623ed0..d91b4b77 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListResponse.kt @@ -7,10 +7,10 @@ package org.opensearch.commons.notifications.action import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.commons.notifications.model.ChannelList +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequest.kt index a78d0d51..c5ff31d8 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequest.kt @@ -10,10 +10,6 @@ import org.opensearch.action.ValidateActions import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.ToXContentObject -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_LIST_TAG import org.opensearch.commons.notifications.NotificationConstants.DEFAULT_MAX_ITEMS @@ -28,6 +24,10 @@ import org.opensearch.commons.utils.enumReader import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.stringList +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.ToXContentObject +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import org.opensearch.search.sort.SortOrder import java.io.IOException diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponse.kt index 1825ccca..8240d767 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponse.kt @@ -7,10 +7,10 @@ package org.opensearch.commons.notifications.action import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.commons.notifications.model.NotificationConfigSearchResult +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequest.kt index 9437f376..5e9b930d 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequest.kt @@ -9,13 +9,13 @@ import org.opensearch.action.ActionRequestValidationException import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.ToXContentObject -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.COMPACT_TAG import org.opensearch.commons.utils.logger +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.ToXContentObject +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt index 73db57a0..6601ef67 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt @@ -7,9 +7,6 @@ package org.opensearch.commons.notifications.action import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.ALLOWED_CONFIG_TYPE_LIST_TAG import org.opensearch.commons.notifications.NotificationConstants.PLUGIN_FEATURES_TAG @@ -17,6 +14,9 @@ import org.opensearch.commons.utils.STRING_READER import org.opensearch.commons.utils.STRING_WRITER import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.stringList +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponse.kt index f7311da2..b32f9ac2 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponse.kt @@ -8,9 +8,9 @@ package org.opensearch.commons.notifications.action import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.commons.destination.response.LegacyDestinationResponse +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequest.kt index 9f4d3159..da30648f 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequest.kt @@ -10,10 +10,6 @@ import org.opensearch.action.ValidateActions import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.ToXContentObject -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CHANNEL_ID_LIST_TAG import org.opensearch.commons.notifications.NotificationConstants.CHANNEL_MESSAGE_TAG @@ -24,6 +20,10 @@ import org.opensearch.commons.notifications.model.EventSource import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.stringList +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.ToXContentObject +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponse.kt index 8967bdd9..8feeed8d 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponse.kt @@ -7,10 +7,10 @@ package org.opensearch.commons.notifications.action import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.commons.notifications.model.NotificationEvent +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequest.kt index 765533be..414540d1 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequest.kt @@ -11,15 +11,15 @@ import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.ToXContentObject -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_TAG import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TAG import org.opensearch.commons.notifications.model.NotificationConfig import org.opensearch.commons.utils.logger +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.ToXContentObject +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponse.kt index 7de2e58c..9d1f94ca 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponse.kt @@ -7,12 +7,12 @@ package org.opensearch.commons.notifications.action import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_TAG import org.opensearch.commons.utils.logger +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Attachment.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Attachment.kt index 7768273d..8c035071 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Attachment.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Attachment.kt @@ -8,9 +8,6 @@ package org.opensearch.commons.notifications.model import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.FILE_CONTENT_TYPE_TAG import org.opensearch.commons.notifications.NotificationConstants.FILE_DATA_TAG @@ -18,6 +15,9 @@ import org.opensearch.commons.notifications.NotificationConstants.FILE_ENCODING_ import org.opensearch.commons.notifications.NotificationConstants.FILE_NAME_TAG import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser /** * Data class for storing attachment of channel message. diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/BaseModel.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/BaseModel.kt index 056fcc6e..2f9e267c 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/BaseModel.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/BaseModel.kt @@ -5,7 +5,7 @@ package org.opensearch.commons.notifications.model import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContentObject +import org.opensearch.core.xcontent.ToXContentObject /** * interface for representing objects. diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Channel.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Channel.kt index a235d974..2345449b 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Channel.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Channel.kt @@ -8,9 +8,6 @@ import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_TAG import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TYPE_TAG @@ -18,6 +15,9 @@ import org.opensearch.commons.notifications.NotificationConstants.DESCRIPTION_TA import org.opensearch.commons.notifications.NotificationConstants.IS_ENABLED_TAG import org.opensearch.commons.notifications.NotificationConstants.NAME_TAG import org.opensearch.commons.utils.logger +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelList.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelList.kt index 7cb25c3a..b7d3c5a7 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelList.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelList.kt @@ -8,8 +8,8 @@ package org.opensearch.commons.notifications.model import org.apache.lucene.search.TotalHits import org.opensearch.action.search.SearchResponse import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.xcontent.XContentParser import org.opensearch.commons.notifications.NotificationConstants.CHANNEL_LIST_TAG +import org.opensearch.core.xcontent.XContentParser /** * Channel search results diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelMessage.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelMessage.kt index 18a65d39..f0fd4aef 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelMessage.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelMessage.kt @@ -9,15 +9,15 @@ import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.ATTACHMENT_TAG import org.opensearch.commons.notifications.NotificationConstants.HTML_DESCRIPTION_TAG import org.opensearch.commons.notifications.NotificationConstants.TEXT_DESCRIPTION_TAG import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** @@ -48,7 +48,6 @@ data class ChannelMessage( @JvmStatic @Throws(IOException::class) fun parse(parser: XContentParser): ChannelMessage { - var textDescription: String? = null var htmlDescription: String? = null var attachment: Attachment? = null diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt index f39a3f7a..3783c430 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt @@ -8,13 +8,13 @@ import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.URL_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateUrl +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/DeliveryStatus.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/DeliveryStatus.kt index 41009ba7..1e15a6b7 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/DeliveryStatus.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/DeliveryStatus.kt @@ -8,13 +8,13 @@ import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.STATUS_CODE_TAG import org.opensearch.commons.notifications.NotificationConstants.STATUS_TEXT_TAG import org.opensearch.commons.utils.logger +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt index 07c055d6..52cc8c4c 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt @@ -8,9 +8,6 @@ import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.EMAIL_ACCOUNT_ID_TAG import org.opensearch.commons.notifications.NotificationConstants.EMAIL_GROUP_ID_LIST_TAG @@ -18,6 +15,9 @@ import org.opensearch.commons.notifications.NotificationConstants.RECIPIENT_LIST import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.objectList import org.opensearch.commons.utils.stringList +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailGroup.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailGroup.kt index c286b66e..036f47ed 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailGroup.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailGroup.kt @@ -7,13 +7,13 @@ package org.opensearch.commons.notifications.model import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.RECIPIENT_LIST_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.objectList +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipient.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipient.kt index aa523346..732614e6 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipient.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipient.kt @@ -7,13 +7,13 @@ package org.opensearch.commons.notifications.model import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.RECIPIENT_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateEmail +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatus.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatus.kt index 12774764..9f70ba89 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatus.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatus.kt @@ -8,14 +8,14 @@ package org.opensearch.commons.notifications.model import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.DELIVERY_STATUS_TAG import org.opensearch.commons.notifications.NotificationConstants.RECIPIENT_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateEmail +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt index 10bf04e3..a4625e0b 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt @@ -8,9 +8,6 @@ import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.REFERENCE_ID_TAG import org.opensearch.commons.notifications.NotificationConstants.SEVERITY_TAG @@ -18,6 +15,9 @@ import org.opensearch.commons.notifications.NotificationConstants.TAGS_TAG import org.opensearch.commons.notifications.NotificationConstants.TITLE_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.stringList +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt index b322e401..4ca07c2a 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt @@ -9,9 +9,6 @@ import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_TAG import org.opensearch.commons.notifications.NotificationConstants.CONFIG_NAME_TAG @@ -21,6 +18,9 @@ import org.opensearch.commons.notifications.NotificationConstants.EMAIL_RECIPIEN import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.objectList +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt index f490e8b1..55e34808 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt @@ -8,9 +8,6 @@ import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TYPE_TAG import org.opensearch.commons.notifications.NotificationConstants.DESCRIPTION_TAG @@ -21,6 +18,9 @@ import org.opensearch.commons.notifications.model.config.ConfigDataProperties.ge import org.opensearch.commons.notifications.model.config.ConfigDataProperties.validateConfigData import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt index 7867a2d2..af83376e 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt @@ -9,15 +9,15 @@ import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_TAG import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TAG import org.opensearch.commons.notifications.NotificationConstants.CREATED_TIME_TAG import org.opensearch.commons.notifications.NotificationConstants.UPDATED_TIME_TAG import org.opensearch.commons.utils.logger +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException import java.time.Instant diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResult.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResult.kt index 579a16cb..50d18e07 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResult.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResult.kt @@ -8,8 +8,8 @@ package org.opensearch.commons.notifications.model import org.apache.lucene.search.TotalHits import org.opensearch.action.search.SearchResponse import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.xcontent.XContentParser import org.opensearch.commons.notifications.NotificationConstants.CONFIG_LIST_TAG +import org.opensearch.core.xcontent.XContentParser /** * NotificationConfig search results diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt index 2ab5426b..a03e0c52 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt @@ -7,17 +7,17 @@ package org.opensearch.commons.notifications.model import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.ToXContent.EMPTY_PARAMS -import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.common.xcontent.XContentHelper -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.common.xcontent.XContentType import org.opensearch.commons.notifications.NotificationConstants.EVENT_SOURCE_TAG import org.opensearch.commons.notifications.NotificationConstants.STATUS_LIST_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.objectList +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.ToXContent.EMPTY_PARAMS +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SearchResults.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SearchResults.kt index 226f8d5d..a193f9a8 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/SearchResults.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SearchResults.kt @@ -12,14 +12,14 @@ import org.opensearch.action.search.SearchResponse import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent.Params -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.START_INDEX_TAG import org.opensearch.commons.notifications.NotificationConstants.TOTAL_HITS_TAG import org.opensearch.commons.notifications.NotificationConstants.TOTAL_HIT_RELATION_TAG import org.opensearch.commons.utils.logger +import org.opensearch.core.xcontent.ToXContent.Params +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import org.opensearch.search.SearchHit abstract class SearchResults : BaseModel { diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt index 4aea8eb4..ce169fed 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt @@ -9,9 +9,6 @@ import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.FROM_ADDRESS_TAG import org.opensearch.commons.notifications.NotificationConstants.REGION_TAG @@ -20,6 +17,9 @@ import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateEmail import org.opensearch.commons.utils.validateIamRoleArn +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt index f022470b..aad4b7e7 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt @@ -8,13 +8,13 @@ import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.URL_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateUrl +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SmtpAccount.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SmtpAccount.kt index 6022a913..fc563bc7 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/SmtpAccount.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SmtpAccount.kt @@ -8,9 +8,6 @@ import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.FROM_ADDRESS_TAG import org.opensearch.commons.notifications.NotificationConstants.HOST_TAG @@ -18,6 +15,9 @@ import org.opensearch.commons.notifications.NotificationConstants.METHOD_TAG import org.opensearch.commons.notifications.NotificationConstants.PORT_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateEmail +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt index fc377b75..82585ae6 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt @@ -8,15 +8,15 @@ package org.opensearch.commons.notifications.model import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.ROLE_ARN_TAG import org.opensearch.commons.notifications.NotificationConstants.TOPIC_ARN_TAG import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateIamRoleArn +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException import java.util.regex.Pattern diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt index 710a9594..806bf123 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt @@ -8,9 +8,6 @@ import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.HEADER_PARAMS_TAG import org.opensearch.commons.notifications.NotificationConstants.METHOD_TAG @@ -19,6 +16,9 @@ import org.opensearch.commons.utils.STRING_READER import org.opensearch.commons.utils.STRING_WRITER import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateUrl +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/XParser.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/XParser.kt index e019acd7..6e3316a4 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/XParser.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/XParser.kt @@ -4,7 +4,7 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParser /** * Functional interface to create config data object using XContentParser diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt index 7fb4327f..1fe4cae6 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt @@ -5,7 +5,6 @@ package org.opensearch.commons.notifications.model.config import org.opensearch.common.io.stream.Writeable.Reader -import org.opensearch.common.xcontent.XContentParser import org.opensearch.commons.notifications.model.BaseConfigData import org.opensearch.commons.notifications.model.Chime import org.opensearch.commons.notifications.model.ConfigType @@ -17,6 +16,7 @@ import org.opensearch.commons.notifications.model.SmtpAccount import org.opensearch.commons.notifications.model.Sns import org.opensearch.commons.notifications.model.Webhook import org.opensearch.commons.notifications.model.XParser +import org.opensearch.core.xcontent.XContentParser internal object ConfigDataProperties { /** diff --git a/src/main/kotlin/org/opensearch/commons/utils/EnumHelpers.kt b/src/main/kotlin/org/opensearch/commons/utils/EnumHelpers.kt index 39db7949..bb57dc50 100644 --- a/src/main/kotlin/org/opensearch/commons/utils/EnumHelpers.kt +++ b/src/main/kotlin/org/opensearch/commons/utils/EnumHelpers.kt @@ -6,8 +6,8 @@ package org.opensearch.commons.utils import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.core.xcontent.XContentParser import java.util.EnumSet inline fun > XContentParser.enumSet(enumParser: EnumParser): EnumSet { diff --git a/src/main/kotlin/org/opensearch/commons/utils/XContentHelpers.kt b/src/main/kotlin/org/opensearch/commons/utils/XContentHelpers.kt index e4af6007..a049aefc 100644 --- a/src/main/kotlin/org/opensearch/commons/utils/XContentHelpers.kt +++ b/src/main/kotlin/org/opensearch/commons/utils/XContentHelpers.kt @@ -6,14 +6,14 @@ package org.opensearch.commons.utils import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.xcontent.DeprecationHandler -import org.opensearch.common.xcontent.NamedXContentRegistry -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.ToXContentObject -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.common.xcontent.XContentType +import org.opensearch.core.xcontent.DeprecationHandler +import org.opensearch.core.xcontent.NamedXContentRegistry +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.ToXContentObject +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import org.opensearch.rest.RestRequest fun StreamInput.createJsonParser(): XContentParser { diff --git a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt index 6ab056c5..62d13555 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt @@ -13,11 +13,7 @@ import org.opensearch.client.WarningsHandler import org.opensearch.common.UUIDs import org.opensearch.common.settings.Settings import org.opensearch.common.xcontent.LoggingDeprecationHandler -import org.opensearch.common.xcontent.NamedXContentRegistry -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.common.xcontent.XContentFactory -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentType import org.opensearch.commons.alerting.aggregation.bucketselectorext.BucketSelectorExtAggregationBuilder import org.opensearch.commons.alerting.aggregation.bucketselectorext.BucketSelectorExtFilter @@ -46,6 +42,10 @@ import org.opensearch.commons.alerting.model.action.PerExecutionActionScope import org.opensearch.commons.alerting.model.action.Throttle import org.opensearch.commons.alerting.util.string import org.opensearch.commons.authuser.User +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.index.query.QueryBuilders import org.opensearch.script.Script import org.opensearch.script.ScriptType @@ -205,9 +205,9 @@ fun randomDocumentLevelTrigger( name = name, severity = severity, condition = condition, - actions = if (actions.isEmpty() && destinationId.isNotBlank()) + actions = if (actions.isEmpty() && destinationId.isNotBlank()) { (0..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { randomAction(destinationId = destinationId) } - else actions + } else actions ) } @@ -413,7 +413,10 @@ fun randomAlert(monitor: Monitor = randomQueryLevelMonitor()): Alert { val trigger = randomQueryLevelTrigger() val actionExecutionResults = mutableListOf(randomActionExecutionResult(), randomActionExecutionResult()) return Alert( - monitor, trigger, Instant.now().truncatedTo(ChronoUnit.MILLIS), null, + monitor, + trigger, + Instant.now().truncatedTo(ChronoUnit.MILLIS), + null, actionExecutionResults = actionExecutionResults ) } @@ -428,7 +431,10 @@ fun randomAlertWithAggregationResultBucket(monitor: Monitor = randomBucketLevelM val trigger = randomBucketLevelTrigger() val actionExecutionResults = mutableListOf(randomActionExecutionResult(), randomActionExecutionResult()) return Alert( - monitor, trigger, Instant.now().truncatedTo(ChronoUnit.MILLIS), null, + monitor, + trigger, + Instant.now().truncatedTo(ChronoUnit.MILLIS), + null, actionExecutionResults = actionExecutionResults, aggregationResultBucket = AggregationResultBucket( "parent_bucket_path_1", 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 9a6eb589..5d5b2364 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponseTests.kt @@ -6,11 +6,11 @@ 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.common.xcontent.ToXContent import org.opensearch.commons.alerting.builder import org.opensearch.commons.alerting.model.Alert 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 diff --git a/src/test/kotlin/org/opensearch/commons/alerting/model/DocLevelMonitorInputTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/model/DocLevelMonitorInputTests.kt index fb0ae8e3..7110d925 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/DocLevelMonitorInputTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/DocLevelMonitorInputTests.kt @@ -2,12 +2,12 @@ package org.opensearch.commons.alerting.model import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.common.xcontent.XContentType import org.opensearch.commons.alerting.randomDocLevelMonitorInput import org.opensearch.commons.alerting.randomDocLevelQuery import org.opensearch.commons.alerting.util.string +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder import java.lang.IllegalArgumentException class DocLevelMonitorInputTests { diff --git a/src/test/kotlin/org/opensearch/commons/alerting/model/MockScheduledJob.kt b/src/test/kotlin/org/opensearch/commons/alerting/model/MockScheduledJob.kt index 178c6ba8..bf2ddfa3 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/MockScheduledJob.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/MockScheduledJob.kt @@ -1,8 +1,8 @@ package org.opensearch.commons.alerting.model import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.ToXContent -import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder import java.io.IOException import java.time.Instant diff --git a/src/test/kotlin/org/opensearch/commons/alerting/model/ScheduleTest.kt b/src/test/kotlin/org/opensearch/commons/alerting/model/ScheduleTest.kt index 6eee00c8..590dda04 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/ScheduleTest.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/ScheduleTest.kt @@ -1,8 +1,8 @@ package org.opensearch.commons.alerting.model import org.junit.jupiter.api.Test -import org.opensearch.common.xcontent.ToXContent import org.opensearch.commons.alerting.util.string +import org.opensearch.core.xcontent.ToXContent import java.time.Instant import java.time.ZoneId import java.time.ZonedDateTime @@ -51,7 +51,8 @@ class ScheduleTest : XContentTestBase { val nextTimeToExecute = cronSchedule.getExpectedNextExecutionTime(enabledTimeInstance, null) assertNotNull(nextTimeToExecute, "There should be next execute time") assertEquals( - testInstance.plusSeconds(2L), nextTimeToExecute, + testInstance.plusSeconds(2L), + nextTimeToExecute, "nextTimeToExecute should be 2 seconds after test instance" ) } @@ -69,7 +70,8 @@ class ScheduleTest : XContentTestBase { val nextTimeToExecute = cronSchedule.getExpectedNextExecutionTime(enabledTimeInstance, previousExecutionTimeInstance) assertNotNull(nextTimeToExecute, "There should be next execute time") assertEquals( - previousExecutionTimeInstance.plusSeconds(2L), nextTimeToExecute, + previousExecutionTimeInstance.plusSeconds(2L), + nextTimeToExecute, "nextTimeToExecute should be 2 seconds after test instance" ) } @@ -87,7 +89,8 @@ class ScheduleTest : XContentTestBase { val nextTimeToExecute = intervalSchedule.getExpectedNextExecutionTime(enabledTimeInstance, null) assertNotNull(nextTimeToExecute, "There should be next execute time") assertEquals( - enabledTimeInstance.plusSeconds(120L), nextTimeToExecute, + enabledTimeInstance.plusSeconds(120L), + nextTimeToExecute, "nextTimeToExecute should be 120 seconds seconds after enabled time" ) } @@ -107,7 +110,8 @@ class ScheduleTest : XContentTestBase { val nextTimeToExecute = intervalSchedule.getExpectedNextExecutionTime(enabledTimeInstance, previousExecutionTimeInstance) assertNotNull(nextTimeToExecute, "There should be next execute time") assertEquals( - previousExecutionTimeInstance.plusSeconds(60L), nextTimeToExecute, + previousExecutionTimeInstance.plusSeconds(60L), + nextTimeToExecute, "nextTimeToExecute should be 60 seconds after previous execution time" ) } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTestBase.kt b/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTestBase.kt index 45ef0d52..9a42c780 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTestBase.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTestBase.kt @@ -2,10 +2,10 @@ package org.opensearch.commons.alerting.model import org.opensearch.common.settings.Settings import org.opensearch.common.xcontent.LoggingDeprecationHandler -import org.opensearch.common.xcontent.NamedXContentRegistry -import org.opensearch.common.xcontent.XContentBuilder -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentType +import org.opensearch.core.xcontent.NamedXContentRegistry +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser import org.opensearch.search.SearchModule interface XContentTestBase { 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 aa3b6e6f..fa93da5f 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt @@ -3,7 +3,6 @@ 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.xcontent.ToXContent import org.opensearch.commons.alerting.builder import org.opensearch.commons.alerting.model.action.Action import org.opensearch.commons.alerting.model.action.ActionExecutionPolicy @@ -27,6 +26,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.xcontent.ToXContent import org.opensearch.index.query.QueryBuilders import org.opensearch.search.builder.SearchSourceBuilder import org.opensearch.test.OpenSearchTestCase diff --git a/src/test/kotlin/org/opensearch/commons/utils/TestHelpers.kt b/src/test/kotlin/org/opensearch/commons/utils/TestHelpers.kt index 81dc33e0..1170851b 100644 --- a/src/test/kotlin/org/opensearch/commons/utils/TestHelpers.kt +++ b/src/test/kotlin/org/opensearch/commons/utils/TestHelpers.kt @@ -5,12 +5,12 @@ package org.opensearch.commons.utils -import org.opensearch.common.xcontent.DeprecationHandler -import org.opensearch.common.xcontent.NamedXContentRegistry -import org.opensearch.common.xcontent.ToXContent import org.opensearch.common.xcontent.XContentFactory -import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentType +import org.opensearch.core.xcontent.DeprecationHandler +import org.opensearch.core.xcontent.NamedXContentRegistry +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentParser import java.io.ByteArrayOutputStream fun getJsonString(xContent: ToXContent): String { From 3120d6837244d22dad1c89ec5872d71e88efe872 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Fri, 14 Apr 2023 10:44:55 -0700 Subject: [PATCH 103/149] InjectSecurity - inject User object in UserInfo in threadContext (#396) (#399) * Added user_info injection of User object in InjectSecurity Signed-off-by: Petar (cherry picked from commit f7639aac2a3a5692299bf225f866b89f3b0159fa) Co-authored-by: Petar Dzepina --- .../opensearch/commons/InjectSecurity.java | 39 ++++++++++++++++++- .../commons/InjectSecurityTest.java | 39 +++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opensearch/commons/InjectSecurity.java b/src/main/java/org/opensearch/commons/InjectSecurity.java index f2644c29..c6d581f6 100644 --- a/src/main/java/org/opensearch/commons/InjectSecurity.java +++ b/src/main/java/org/opensearch/commons/InjectSecurity.java @@ -10,12 +10,14 @@ import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_USE_INJECTED_USER_FOR_PLUGINS; import java.util.List; +import java.util.StringJoiner; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.commons.authuser.User; /** * For background jobs usage only. User or Roles injection can be done using transport layer only. @@ -91,6 +93,7 @@ public InjectSecurity(final String id, final Settings settings, final ThreadCont /** * Injects user or roles, based on opendistro_security_use_injected_user_for_plugins setting. By default injects roles. + * Expects threadContext to be stashed * @param user * @param roles */ @@ -104,7 +107,8 @@ public void inject(final String user, final List roles) { /** * Injects user. - * @param user + * Expects threadContext to be stashed + * @param user name */ public void injectUser(final String user) { if (Strings.isNullOrEmpty(user)) { @@ -115,8 +119,39 @@ public void injectUser(final String user) { threadContext.putTransient(INJECTED_USER, user); log.debug("{}, InjectSecurity - inject roles: {}", Thread.currentThread().getName(), id); } else { - log.error("{}, InjectSecurity- most likely thread context corruption : {}", Thread.currentThread().getName(), id); + log.error("{}, InjectSecurity - most likely thread context corruption : {}", Thread.currentThread().getName(), id); + } + } + + /** + * Injects user object into user info. + * Expects threadContext to be stashed. + * @param user + */ + public void injectUserInfo(final User user) { + if (user == null) { + return; + } + String userObjectAsString = threadContext.getTransient(ConfigConstants.OPENSEARCH_SECURITY_USER_INFO_THREAD_CONTEXT); + if (userObjectAsString != null) { + log + .error( + "{}, InjectSecurity - id: [{}] found existing user_info: {}", + Thread.currentThread().getName(), + id, + userObjectAsString + ); + return; + } + StringJoiner joiner = new StringJoiner("|"); + joiner.add(user.getName()); + joiner.add(java.lang.String.join(",", user.getBackendRoles())); + joiner.add(java.lang.String.join(",", user.getRoles())); + String requestedTenant = user.getRequestedTenant(); + if (!Strings.isNullOrEmpty(requestedTenant)) { + joiner.add(requestedTenant); } + threadContext.putTransient(ConfigConstants.OPENSEARCH_SECURITY_USER_INFO_THREAD_CONTEXT, joiner.toString()); } /** diff --git a/src/test/java/org/opensearch/commons/InjectSecurityTest.java b/src/test/java/org/opensearch/commons/InjectSecurityTest.java index 818aa9c7..b2dea7f3 100644 --- a/src/test/java/org/opensearch/commons/InjectSecurityTest.java +++ b/src/test/java/org/opensearch/commons/InjectSecurityTest.java @@ -12,14 +12,17 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opensearch.commons.ConfigConstants.INJECTED_USER; import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_INJECTED_ROLES; +import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_USER_INFO_THREAD_CONTEXT; import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_USE_INJECTED_USER_FOR_PLUGINS; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import org.junit.jupiter.api.Test; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.commons.authuser.User; public class InjectSecurityTest { @@ -85,6 +88,42 @@ public void testInjectUser() { assertNull(threadContext.getTransient(INJECTED_USER)); } + @Test + public void testInjectUserInfo() { + Settings settings = Settings.builder().build(); + Settings headerSettings = Settings.builder().put("request.headers.default", "1").build(); + ThreadContext threadContext = new ThreadContext(headerSettings); + threadContext.putHeader("name", "opendistro"); + threadContext.putTransient("ctx.name", "plugin"); + + assertEquals("1", threadContext.getHeader("default")); + assertEquals("opendistro", threadContext.getHeader("name")); + assertEquals("plugin", threadContext.getTransient("ctx.name")); + + User user = new User( + "Bob", + List.of("backendRole1", "backendRole2"), + List.of("role1", "role2"), + List.of("attr1", "attr2"), + "tenant1" + ); + try (InjectSecurity helper = new InjectSecurity("test-name", null, threadContext)) { + helper.injectUserInfo(user); + assertEquals("1", threadContext.getHeader("default")); + assertEquals("opendistro", threadContext.getHeader("name")); + assertEquals("plugin", threadContext.getTransient("ctx.name")); + assertNotNull(threadContext.getTransient(OPENSEARCH_SECURITY_USER_INFO_THREAD_CONTEXT)); + assertEquals( + "Bob|backendRole1,backendRole2|role1,role2|tenant1", + threadContext.getTransient(OPENSEARCH_SECURITY_USER_INFO_THREAD_CONTEXT) + ); + } + assertEquals("1", threadContext.getHeader("default")); + assertEquals("opendistro", threadContext.getHeader("name")); + assertEquals("plugin", threadContext.getTransient("ctx.name")); + assertNull(threadContext.getTransient(OPENSEARCH_SECURITY_USER_INFO_THREAD_CONTEXT)); + } + @Test public void testInjectProperty() { Settings settings = Settings.builder().put(OPENSEARCH_SECURITY_USE_INJECTED_USER_FOR_PLUGINS, false).build(); From 65ecf3730e798d0696cf191007d145a8dc097929 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Mon, 17 Apr 2023 18:26:34 -0700 Subject: [PATCH 104/149] Added 2.7 release notes. (#407) (#408) * Added 2.7 release notes. Signed-off-by: AWSHurneyt * Added 2.7 release notes. Signed-off-by: AWSHurneyt --------- Signed-off-by: AWSHurneyt (cherry picked from commit 88c537da85a377e417235f74e16fc45f94fa6b52) Co-authored-by: AWSHurneyt --- ...arch-common-utils.release-notes-2.7.0.0.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 release-notes/opensearch-common-utils.release-notes-2.7.0.0.md diff --git a/release-notes/opensearch-common-utils.release-notes-2.7.0.0.md b/release-notes/opensearch-common-utils.release-notes-2.7.0.0.md new file mode 100644 index 00000000..94e9399c --- /dev/null +++ b/release-notes/opensearch-common-utils.release-notes-2.7.0.0.md @@ -0,0 +1,22 @@ +## Version 2.7.0.0 2023-04-17 + +Compatible with OpenSearch 2.7.0 + +### Maintenance +* Increment version to 2.7.0-SNAPSHOT. ([#371](https://github.com/opensearch-project/common-utils/pull/371)) + +### Refactoring +* Fixed xContent dependencies due to OSCore changes. ([#392](https://github.com/opensearch-project/common-utils/pull/392)) + +### Infrastructure +* Publish snapshots to maven via GHA. ([#365](https://github.com/opensearch-project/common-utils/pull/365)) +* Add auto Github release workflow. ([#376](https://github.com/opensearch-project/common-utils/pull/376)) + +### Feature +* InjectSecurity - inject User object in UserInfo in threadContext. ([#396](https://github.com/opensearch-project/common-utils/pull/396)) + +### Bug Fixes +* Fix SNS regex for validation on notification channel to support SNS FIFO topics. ([#381](https://github.com/opensearch-project/common-utils/pull/381)) + +### Documentation +* Added 2.7 release notes. ([#407](https://github.com/opensearch-project/common-utils/pull/407)) \ No newline at end of file From d6f88ce515ea76d3c495f61045bd781879dd88bd Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Tue, 18 Apr 2023 19:11:10 -0700 Subject: [PATCH 105/149] integrate security-analytics & alerting for correlation engine (#412) (#414) Signed-off-by: Subhobrata Dey --- .../alerting/AlertingPluginInterface.kt | 20 ++++++++++ .../alerting/action/AlertingActions.kt | 4 ++ .../alerting/action/PublishFindingsRequest.kt | 38 +++++++++++++++++++ .../action/SubscribeFindingsResponse.kt | 38 +++++++++++++++++++ .../commons/alerting/model/Finding.kt | 14 +++++++ .../action/GetFindingsResponseTests.kt | 2 + 6 files changed, 116 insertions(+) create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/PublishFindingsRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/SubscribeFindingsResponse.kt diff --git a/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt index 05c60be8..7dd3e6e5 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt @@ -20,6 +20,8 @@ import org.opensearch.commons.alerting.action.GetFindingsRequest import org.opensearch.commons.alerting.action.GetFindingsResponse import org.opensearch.commons.alerting.action.IndexMonitorRequest import org.opensearch.commons.alerting.action.IndexMonitorResponse +import org.opensearch.commons.alerting.action.PublishFindingsRequest +import org.opensearch.commons.alerting.action.SubscribeFindingsResponse import org.opensearch.commons.notifications.action.BaseResponse import org.opensearch.commons.utils.recreateObject @@ -143,6 +145,24 @@ object AlertingPluginInterface { ) } + fun publishFinding( + client: NodeClient, + request: PublishFindingsRequest, + listener: ActionListener + ) { + client.execute( + AlertingActions.SUBSCRIBE_FINDINGS_ACTION_TYPE, + request, + wrapActionListener(listener) { response -> + recreateObject(response) { + SubscribeFindingsResponse( + 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 739217f2..12e07ebe 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt @@ -12,6 +12,7 @@ object AlertingActions { const val DELETE_MONITOR_ACTION_NAME = "cluster:admin/opendistro/alerting/monitor/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 SUBSCRIBE_FINDINGS_ACTION_NAME = "cluster:admin/opensearch/alerting/findings/subscribe" @JvmField val INDEX_MONITOR_ACTION_TYPE = @@ -32,4 +33,7 @@ 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) } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/PublishFindingsRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/PublishFindingsRequest.kt new file mode 100644 index 00000000..7118ec44 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/PublishFindingsRequest.kt @@ -0,0 +1,38 @@ +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.Finding +import java.io.IOException + +class PublishFindingsRequest : ActionRequest { + + val monitorId: String + + val finding: Finding + + constructor( + monitorId: String, + finding: Finding + ) : super() { + this.monitorId = monitorId + this.finding = finding + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + monitorId = sin.readString(), + finding = Finding.readFrom(sin) + ) + + override fun validate(): ActionRequestValidationException? { + return null + } + + override fun writeTo(out: StreamOutput) { + out.writeString(monitorId) + finding.writeTo(out) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/SubscribeFindingsResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/SubscribeFindingsResponse.kt new file mode 100644 index 00000000..494cb701 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/SubscribeFindingsResponse.kt @@ -0,0 +1,38 @@ +package org.opensearch.commons.alerting.action + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.commons.notifications.action.BaseResponse +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.rest.RestStatus +import java.io.IOException + +class SubscribeFindingsResponse : BaseResponse { + + private var status: RestStatus + + constructor(status: RestStatus) : super() { + this.status = status + } + + @Throws(IOException::class) + constructor(sin: StreamInput) { + this.status = sin.readEnum(RestStatus::class.java) + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeEnum(status) + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .field("status", status.status) + return builder.endObject() + } + + override fun getStatus(): RestStatus { + return this.status + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt index e8102779..c4a45fb4 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt @@ -17,6 +17,7 @@ import java.time.Instant class Finding( val id: String = NO_ID, val relatedDocIds: List, + val correlatedDocIds: List = listOf(), val monitorId: String, val monitorName: String, val index: String, @@ -28,6 +29,7 @@ class Finding( constructor(sin: StreamInput) : this( id = sin.readString(), relatedDocIds = sin.readStringList(), + correlatedDocIds = sin.readStringList(), monitorId = sin.readString(), monitorName = sin.readString(), index = sin.readString(), @@ -39,6 +41,7 @@ class Finding( return mapOf( FINDING_ID_FIELD to id, RELATED_DOC_IDS_FIELD to relatedDocIds, + CORRELATED_DOC_IDS_FIELD to correlatedDocIds, MONITOR_ID_FIELD to monitorId, MONITOR_NAME_FIELD to monitorName, INDEX_FIELD to index, @@ -51,6 +54,7 @@ class Finding( builder.startObject() .field(FINDING_ID_FIELD, id) .field(RELATED_DOC_IDS_FIELD, relatedDocIds) + .field(CORRELATED_DOC_IDS_FIELD, correlatedDocIds) .field(MONITOR_ID_FIELD, monitorId) .field(MONITOR_NAME_FIELD, monitorName) .field(INDEX_FIELD, index) @@ -64,6 +68,7 @@ class Finding( override fun writeTo(out: StreamOutput) { out.writeString(id) out.writeStringCollection(relatedDocIds) + out.writeStringCollection(correlatedDocIds) out.writeString(monitorId) out.writeString(monitorName) out.writeString(index) @@ -74,6 +79,7 @@ class Finding( companion object { const val FINDING_ID_FIELD = "id" const val RELATED_DOC_IDS_FIELD = "related_doc_ids" + const val CORRELATED_DOC_IDS_FIELD = "correlated_doc_ids" const val MONITOR_ID_FIELD = "monitor_id" const val MONITOR_NAME_FIELD = "monitor_name" const val INDEX_FIELD = "index" @@ -87,6 +93,7 @@ class Finding( fun parse(xcp: XContentParser): Finding { var id: String = NO_ID val relatedDocIds: MutableList = mutableListOf() + val correlatedDocIds: MutableList = mutableListOf() lateinit var monitorId: String lateinit var monitorName: String lateinit var index: String @@ -106,6 +113,12 @@ class Finding( relatedDocIds.add(xcp.text()) } } + CORRELATED_DOC_IDS_FIELD -> { + ensureExpectedToken(XContentParser.Token.START_ARRAY, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + correlatedDocIds.add(xcp.text()) + } + } MONITOR_ID_FIELD -> monitorId = xcp.text() MONITOR_NAME_FIELD -> monitorName = xcp.text() INDEX_FIELD -> index = xcp.text() @@ -124,6 +137,7 @@ class Finding( return Finding( id = id, relatedDocIds = relatedDocIds, + correlatedDocIds = correlatedDocIds, monitorId = monitorId, monitorName = monitorName, index = index, diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt index 1f7e733e..7349078b 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt @@ -21,6 +21,7 @@ internal class GetFindingsResponseTests { val finding1 = Finding( "1", listOf("doc1", "doc2", "doc3"), + listOf("doc1", "doc2", "doc3"), "monitor_id1", "monitor_name1", "test_index1", @@ -39,6 +40,7 @@ internal class GetFindingsResponseTests { val finding2 = Finding( "1", listOf("doc21", "doc22"), + listOf("doc21", "doc22"), "monitor_id2", "monitor_name2", "test_index2", From 1ff2298f7489e91f109a8e62a33819a7f45f3309 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Thu, 27 Apr 2023 09:52:20 -0700 Subject: [PATCH 106/149] Increment version to 2.8.0-SNAPSHOT (#413) Signed-off-by: opensearch-ci-bot Co-authored-by: opensearch-ci-bot --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 22a8682a..9cea89df 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "2.7.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.8.0-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") buildVersionQualifier = System.getProperty("build.version_qualifier", "") kotlin_version = System.getProperty("kotlin.version", "1.6.10") From 26a117e513d8c8bdcb702279d9b5d9468200f52d Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Tue, 2 May 2023 17:16:24 -0700 Subject: [PATCH 107/149] upgrade gradle to 8.1.1 (#418) (#421) Signed-off-by: Subhobrata Dey --- build-tools/opensearchplugin-coverage.gradle | 4 +-- build.gradle | 8 +++--- gradle/wrapper/gradle-wrapper.jar | Bin 59821 -> 62076 bytes gradle/wrapper/gradle-wrapper.properties | 3 ++- gradlew | 25 +++++++++++++------ gradlew.bat | 15 ++++++----- 6 files changed, 35 insertions(+), 20 deletions(-) diff --git a/build-tools/opensearchplugin-coverage.gradle b/build-tools/opensearchplugin-coverage.gradle index 2b4d21c0..b5b176a3 100644 --- a/build-tools/opensearchplugin-coverage.gradle +++ b/build-tools/opensearchplugin-coverage.gradle @@ -37,8 +37,8 @@ jacocoTestReport { getSourceDirectories().from(sourceSets.main.allSource) getClassDirectories().from(sourceSets.main.output) reports { - html.enabled = true // human readable - xml.enabled = true // for coverlay + html.required = true // human readable + xml.required = true // for coverlay } } diff --git a/build.gradle b/build.gradle index 9cea89df..431eed39 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ buildscript { plugins { id 'java-library' id 'maven-publish' - id "com.diffplug.gradle.spotless" version "3.26.1" + id 'com.diffplug.spotless' version '6.18.0' } repositories { @@ -147,16 +147,16 @@ compileTestKotlin { } shadowJar { - classifier = null + archiveClassifier = null } task sourcesJar(type: Jar) { - classifier = 'sources' + archiveClassifier = 'sources' from sourceSets.main.allJava } task javadocJar(type: Jar) { - classifier = 'javadoc' + archiveClassifier = 'javadoc' from javadoc.destinationDir } publishing { diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 41d9927a4d4fb3f96a785543079b8df6723c946b..c1962a79e29d3e0ab67b14947c167a862655af9b 100644 GIT binary patch delta 40199 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 delta 37942 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/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f9..6689b85b 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From db605322423db7e51a6cfc3e819ade027f86b0d6 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Thu, 4 May 2023 15:32:14 -0700 Subject: [PATCH 108/149] NoOpTrigger (#420) (#424) * added noop trigger Signed-off-by: Petar Dzepina (cherry picked from commit c507ac9611280e0129414f2163b44aa52c45ab27) Co-authored-by: Petar Dzepina --- .../commons/alerting/model/Alert.kt | 18 +++++ .../commons/alerting/model/Monitor.kt | 3 + .../commons/alerting/model/NoOpTrigger.kt | 76 +++++++++++++++++++ .../commons/alerting/model/Trigger.kt | 3 +- .../commons/alerting/TestHelpers.kt | 4 +- .../commons/alerting/model/XContentTests.kt | 21 +++++ 6 files changed, 123 insertions(+), 2 deletions(-) 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 032c484a..3c87011f 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 0c969939..cd993c18 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..92456492 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt @@ -0,0 +1,76 @@ +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) + .field(ID_FIELD, id) + .endObject() + .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 ID_FIELD = "id" + 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 { + 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) { + XContentParserUtils.throwUnknownToken(xcp.currentToken(), xcp.tokenLocation) + } + return NoOpTrigger(id = id) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): NoOpTrigger { + return NoOpTrigger(sin) + } + } +} 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/TestHelpers.kt b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt index 62d13555..94c7d23a 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..f8c98842 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 @@ -179,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 NoOpTrigger doesn't work") + } + @Test fun `test creating a monitor with duplicate trigger ids fails`() { try { @@ -357,6 +368,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 2a6a6de8710ffdc34ff40a18a5d9be9c3ac40d95 Mon Sep 17 00:00:00 2001 From: Surya Sashank Nistala Date: Wed, 24 May 2023 09:06:44 -0700 Subject: [PATCH 109/149] Adds workflows model and workflow actions for Alerting Plugin (#436) --- .../alerting/AlertingPluginInterface.kt | 74 +++++ .../alerting/action/AlertingActions.kt | 13 + .../alerting/action/DeleteWorkflowRequest.kt | 38 +++ .../alerting/action/DeleteWorkflowResponse.kt | 48 ++++ .../alerting/action/GetWorkflowRequest.kt | 42 +++ .../alerting/action/GetWorkflowResponse.kt | 88 ++++++ .../alerting/action/IndexWorkflowRequest.kt | 137 +++++++++ .../alerting/action/IndexWorkflowResponse.kt | 61 ++++ .../alerting/model/ChainedMonitorFindings.kt | 77 +++++ .../commons/alerting/model/CompositeInput.kt | 84 ++++++ .../commons/alerting/model/Delegate.kt | 118 ++++++++ .../commons/alerting/model/Finding.kt | 40 ++- .../commons/alerting/model/Sequence.kt | 75 +++++ .../commons/alerting/model/Workflow.kt | 265 ++++++++++++++++++ .../commons/alerting/model/WorkflowInput.kt | 48 ++++ .../commons/alerting/util/IndexUtils.kt | 1 + .../alerting/AlertingPluginInterfaceTests.kt | 45 +++ .../commons/alerting/TestHelpers.kt | 73 +++++ .../action/DeleteWorkflowRequestTests.kt | 23 ++ .../action/IndexWorkflowRequestTests.kt | 210 ++++++++++++++ .../action/IndexWorkflowResponseTests.kt | 45 +++ .../alerting/model/CompositeInputTests.kt | 86 ++++++ .../commons/alerting/model/XContentTests.kt | 9 + 23 files changed, 1696 insertions(+), 4 deletions(-) create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowResponse.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponse.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowResponse.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/ChainedMonitorFindings.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/CompositeInput.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/Delegate.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/Sequence.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/Workflow.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/WorkflowInput.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequestTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequestTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowResponseTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/model/CompositeInputTests.kt diff --git a/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt index 7dd3e6e5..eafe8e69 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt @@ -14,12 +14,18 @@ import org.opensearch.commons.alerting.action.AcknowledgeAlertResponse import org.opensearch.commons.alerting.action.AlertingActions import org.opensearch.commons.alerting.action.DeleteMonitorRequest import org.opensearch.commons.alerting.action.DeleteMonitorResponse +import org.opensearch.commons.alerting.action.DeleteWorkflowRequest +import org.opensearch.commons.alerting.action.DeleteWorkflowResponse 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.GetWorkflowRequest +import org.opensearch.commons.alerting.action.GetWorkflowResponse 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.notifications.action.BaseResponse @@ -55,6 +61,7 @@ object AlertingPluginInterface { } ) } + fun deleteMonitor( client: NodeClient, request: DeleteMonitorRequest, @@ -73,6 +80,49 @@ object AlertingPluginInterface { ) } + /** + * Index monitor interface. + * @param client Node client for making transport action + * @param request The request object + * @param namedWriteableRegistry Registry for building aggregations + * @param listener The listener for getting response + */ + fun indexWorkflow( + client: NodeClient, + request: IndexWorkflowRequest, + listener: ActionListener + ) { + client.execute( + AlertingActions.INDEX_WORKFLOW_ACTION_TYPE, + request, + wrapActionListener(listener) { response -> + recreateObject(response) { + IndexWorkflowResponse( + it + ) + } + } + ) + } + + fun deleteWorkflow( + client: NodeClient, + request: DeleteWorkflowRequest, + listener: ActionListener + ) { + client.execute( + AlertingActions.DELETE_WORKFLOW_ACTION_TYPE, + request, + wrapActionListener(listener) { response -> + recreateObject(response) { + DeleteWorkflowResponse( + it + ) + } + } + ) + } + /** * Get Alerts interface. * @param client Node client for making transport action @@ -97,6 +147,30 @@ object AlertingPluginInterface { ) } + /** + * Get Workflow interface. + * @param client Node client for making transport action + * @param request The request object + * @param listener The listener for getting response + */ + fun getWorkflow( + client: NodeClient, + request: GetWorkflowRequest, + listener: ActionListener + ) { + client.execute( + AlertingActions.GET_WORKFLOW_ACTION_TYPE, + request, + wrapActionListener(listener) { response -> + recreateObject(response) { + GetWorkflowResponse( + it + ) + } + } + ) + } + /** * Get Findings interface. * @param client Node client for making transport action 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 12e07ebe..55598a76 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt @@ -8,8 +8,11 @@ import org.opensearch.action.ActionType 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_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 SUBSCRIBE_FINDINGS_ACTION_NAME = "cluster:admin/opensearch/alerting/findings/subscribe" @@ -19,14 +22,24 @@ object AlertingActions { ActionType(INDEX_MONITOR_ACTION_NAME, ::IndexMonitorResponse) @JvmField + val INDEX_WORKFLOW_ACTION_TYPE = + ActionType(INDEX_WORKFLOW_ACTION_NAME, ::IndexWorkflowResponse) + @JvmField val GET_ALERTS_ACTION_TYPE = ActionType(GET_ALERTS_ACTION_NAME, ::GetAlertsResponse) + @JvmField + val GET_WORKFLOW_ACTION_TYPE = + ActionType(GET_WORKFLOW_ACTION_NAME, ::GetWorkflowResponse) + @JvmField val DELETE_MONITOR_ACTION_TYPE = ActionType(DELETE_MONITOR_ACTION_NAME, ::DeleteMonitorResponse) @JvmField + val DELETE_WORKFLOW_ACTION_TYPE = + ActionType(DELETE_WORKFLOW_ACTION_NAME, ::DeleteWorkflowResponse) + @JvmField val GET_FINDINGS_ACTION_TYPE = ActionType(GET_FINDINGS_ACTION_NAME, ::GetFindingsResponse) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequest.kt new file mode 100644 index 00000000..4990f497 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequest.kt @@ -0,0 +1,38 @@ +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 java.io.IOException + +class DeleteWorkflowRequest : ActionRequest { + + val workflowId: String + /** + * Flag that indicates whether the delegate monitors should be deleted or not. + * If the flag is set to true, Delegate monitors will be deleted only in the case when they are part of the specified workflow and no other. + */ + val deleteDelegateMonitors: Boolean? + + constructor(workflowId: String, deleteDelegateMonitors: Boolean?) : super() { + this.workflowId = workflowId + this.deleteDelegateMonitors = deleteDelegateMonitors + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + workflowId = sin.readString(), + deleteDelegateMonitors = sin.readOptionalBoolean() + ) + + override fun validate(): ActionRequestValidationException? { + return null + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(workflowId) + out.writeOptionalBoolean(deleteDelegateMonitors) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowResponse.kt new file mode 100644 index 00000000..8da62c5e --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowResponse.kt @@ -0,0 +1,48 @@ +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.util.IndexUtils +import org.opensearch.commons.notifications.action.BaseResponse +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder + +class DeleteWorkflowResponse : BaseResponse { + var id: String + var version: Long + var nonDeletedMonitors: List? = null + + constructor( + id: String, + version: Long, + nonDeletedMonitors: List? = null + ) : super() { + this.id = id + this.version = version + this.nonDeletedMonitors = nonDeletedMonitors + } + + constructor(sin: StreamInput) : this( + sin.readString(), // id + sin.readLong(), // version + sin.readOptionalStringList() + ) + + override fun writeTo(out: StreamOutput) { + out.writeString(id) + out.writeLong(version) + out.writeOptionalStringCollection(nonDeletedMonitors) + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + return builder.startObject() + .field(IndexUtils._ID, id) + .field(IndexUtils._VERSION, version) + .field(NON_DELETED_MONITORS, nonDeletedMonitors) + .endObject() + } + + companion object { + const val NON_DELETED_MONITORS = "NON_DELETED_MONITORS" + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowRequest.kt new file mode 100644 index 00000000..1b7948cd --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowRequest.kt @@ -0,0 +1,42 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +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.rest.RestRequest +import java.io.IOException + +class GetWorkflowRequest : ActionRequest { + val workflowId: String + val method: RestRequest.Method + + constructor( + workflowId: String, + method: RestRequest.Method + ) : super() { + this.workflowId = workflowId + this.method = method + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readString(), // workflowId + sin.readEnum(RestRequest.Method::class.java) // method + ) + + override fun validate(): ActionRequestValidationException? { + return null + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(workflowId) + out.writeEnum(method) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponse.kt new file mode 100644 index 00000000..f18550c5 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponse.kt @@ -0,0 +1,88 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +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.Workflow +import org.opensearch.commons.alerting.util.IndexUtils.Companion._ID +import org.opensearch.commons.alerting.util.IndexUtils.Companion._PRIMARY_TERM +import org.opensearch.commons.alerting.util.IndexUtils.Companion._SEQ_NO +import org.opensearch.commons.alerting.util.IndexUtils.Companion._VERSION +import org.opensearch.commons.notifications.action.BaseResponse +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.rest.RestStatus +import java.io.IOException + +class GetWorkflowResponse : BaseResponse { + var id: String + var version: Long + var seqNo: Long + var primaryTerm: Long + private var status: RestStatus + var workflow: Workflow? + + constructor( + id: String, + version: Long, + seqNo: Long, + primaryTerm: Long, + status: RestStatus, + workflow: Workflow? + ) : super() { + this.id = id + this.version = version + this.seqNo = seqNo + this.primaryTerm = primaryTerm + this.status = status + this.workflow = workflow + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readString(), // id + sin.readLong(), // version + sin.readLong(), // seqNo + sin.readLong(), // primaryTerm + sin.readEnum(RestStatus::class.java), // RestStatus + if (sin.readBoolean()) { + Workflow.readFrom(sin) // monitor + } else null + ) + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(id) + out.writeLong(version) + out.writeLong(seqNo) + out.writeLong(primaryTerm) + out.writeEnum(status) + if (workflow != null) { + out.writeBoolean(true) + workflow?.writeTo(out) + } else { + out.writeBoolean(false) + } + } + + @Throws(IOException::class) + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .field(_ID, id) + .field(_VERSION, version) + .field(_SEQ_NO, seqNo) + .field(_PRIMARY_TERM, primaryTerm) + if (workflow != null) + builder.field("workflow", workflow) + + return builder.endObject() + } + + override fun getStatus(): RestStatus { + return this.status + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequest.kt new file mode 100644 index 00000000..15a895ca --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequest.kt @@ -0,0 +1,137 @@ +package org.opensearch.commons.alerting.action + +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionRequestValidationException +import org.opensearch.action.ValidateActions +import org.opensearch.action.support.WriteRequest +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.commons.alerting.model.CompositeInput +import org.opensearch.commons.alerting.model.Workflow +import org.opensearch.rest.RestRequest +import java.io.IOException +import java.util.stream.Collectors + +class IndexWorkflowRequest : ActionRequest { + val workflowId: String + val seqNo: Long + val primaryTerm: Long + val refreshPolicy: WriteRequest.RefreshPolicy + val method: RestRequest.Method + var workflow: Workflow + val rbacRoles: List? + + private val MAX_DELEGATE_SIZE = 25 + + constructor( + workflowId: String, + seqNo: Long, + primaryTerm: Long, + refreshPolicy: WriteRequest.RefreshPolicy, + method: RestRequest.Method, + workflow: Workflow, + rbacRoles: List? = null + ) : super() { + this.workflowId = workflowId + this.seqNo = seqNo + this.primaryTerm = primaryTerm + this.refreshPolicy = refreshPolicy + this.method = method + this.workflow = workflow + this.rbacRoles = rbacRoles + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + workflowId = sin.readString(), + seqNo = sin.readLong(), + primaryTerm = sin.readLong(), + refreshPolicy = WriteRequest.RefreshPolicy.readFrom(sin), + method = sin.readEnum(RestRequest.Method::class.java), + workflow = Workflow.readFrom(sin) as Workflow, + rbacRoles = sin.readOptionalStringList() + ) + + override fun validate(): ActionRequestValidationException? { + var validationException: ActionRequestValidationException? = null + + if (workflow.inputs.isEmpty()) { + validationException = ValidateActions.addValidationError( + "Input list can not be empty.", validationException + ) + return validationException + } + if (workflow.inputs.size > 1) { + validationException = ValidateActions.addValidationError( + "Input list can contain only one element.", validationException + ) + return validationException + } + if (workflow.inputs[0] !is CompositeInput) { + validationException = ValidateActions.addValidationError( + "When creating a workflow input must be CompositeInput", validationException + ) + } + val compositeInput = workflow.inputs[0] as CompositeInput + val monitorIds = compositeInput.sequence.delegates.stream().map { it.monitorId }.collect(Collectors.toList()) + + if (monitorIds.isNullOrEmpty()) { + validationException = ValidateActions.addValidationError( + "Delegates list can not be empty.", validationException + ) + // Break the flow because next checks are dependant on non-null monitorIds + return validationException + } + + if (monitorIds.size > MAX_DELEGATE_SIZE) { + validationException = ValidateActions.addValidationError( + "Delegates list can not be larger then $MAX_DELEGATE_SIZE.", validationException + ) + } + + if (monitorIds.toSet().size != monitorIds.size) { + validationException = ValidateActions.addValidationError( + "Duplicate delegates not allowed", validationException + ) + } + val delegates = compositeInput.sequence.delegates + val orderSet = delegates.stream().filter { it.order > 0 }.map { it.order }.collect(Collectors.toSet()) + if (orderSet.size != delegates.size) { + validationException = ValidateActions.addValidationError( + "Sequence ordering of delegate monitor shouldn't contain duplicate order values", validationException + ) + } + + val monitorIdOrderMap: Map = delegates.associate { it.monitorId to it.order } + delegates.forEach { + if (it.chainedMonitorFindings != null) { + if (monitorIdOrderMap.containsKey(it.chainedMonitorFindings!!.monitorId) == false) { + validationException = ValidateActions.addValidationError( + "Chained Findings Monitor ${it.chainedMonitorFindings!!.monitorId} doesn't exist in sequence", + validationException + ) + // Break the flow because next check will generate the NPE + return validationException + } + if (it.order <= monitorIdOrderMap[it.chainedMonitorFindings!!.monitorId]!!) { + validationException = ValidateActions.addValidationError( + "Chained Findings Monitor ${it.chainedMonitorFindings!!.monitorId} should be executed before monitor ${it.monitorId}", + validationException + ) + } + } + } + return validationException + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(workflowId) + out.writeLong(seqNo) + out.writeLong(primaryTerm) + refreshPolicy.writeTo(out) + out.writeEnum(method) + workflow.writeTo(out) + out.writeOptionalStringCollection(rbacRoles) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowResponse.kt new file mode 100644 index 00000000..89863ba5 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowResponse.kt @@ -0,0 +1,61 @@ +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.Workflow +import org.opensearch.commons.alerting.util.IndexUtils +import org.opensearch.commons.notifications.action.BaseResponse +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import java.io.IOException + +class IndexWorkflowResponse : BaseResponse { + var id: String + var version: Long + var seqNo: Long + var primaryTerm: Long + var workflow: Workflow + + constructor( + id: String, + version: Long, + seqNo: Long, + primaryTerm: Long, + workflow: Workflow + ) : super() { + this.id = id + this.version = version + this.seqNo = seqNo + this.primaryTerm = primaryTerm + this.workflow = workflow + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readString(), // id + sin.readLong(), // version + sin.readLong(), // seqNo + sin.readLong(), // primaryTerm + Workflow.readFrom(sin) as Workflow // workflow + ) + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(id) + out.writeLong(version) + out.writeLong(seqNo) + out.writeLong(primaryTerm) + workflow.writeTo(out) + } + + @Throws(IOException::class) + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + return builder.startObject() + .field(IndexUtils._ID, id) + .field(IndexUtils._VERSION, version) + .field(IndexUtils._SEQ_NO, seqNo) + .field(IndexUtils._PRIMARY_TERM, primaryTerm) + .field("workflow", workflow) + .endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/ChainedMonitorFindings.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/ChainedMonitorFindings.kt new file mode 100644 index 00000000..cf2bafd6 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/ChainedMonitorFindings.kt @@ -0,0 +1,77 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.model.BaseModel +import org.opensearch.commons.utils.validateId +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser +import java.io.IOException + +/** + * Context passed in delegate monitor to filter data queried by a monitor based on the findings of the given monitor id. + */ +// TODO - Remove the class and move the monitorId to Delegate (as a chainedMonitorId property) if this class won't be updated by adding new properties +data class ChainedMonitorFindings( + val monitorId: String +) : BaseModel { + + init { + validateId(monitorId) + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readString(), // monitorId + ) + + fun asTemplateArg(): Map { + return mapOf( + MONITOR_ID_FIELD to monitorId, + ) + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(monitorId) + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .field(MONITOR_ID_FIELD, monitorId) + .endObject() + return builder + } + + companion object { + const val MONITOR_ID_FIELD = "monitor_id" + + @JvmStatic + @Throws(IOException::class) + fun parse(xcp: XContentParser): ChainedMonitorFindings { + lateinit var monitorId: String + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + when (fieldName) { + MONITOR_ID_FIELD -> { + monitorId = xcp.text() + validateId(monitorId) + } + } + } + return ChainedMonitorFindings(monitorId) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): ChainedMonitorFindings { + return ChainedMonitorFindings(sin) + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/CompositeInput.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/CompositeInput.kt new file mode 100644 index 00000000..229f20e2 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/CompositeInput.kt @@ -0,0 +1,84 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.CheckedFunction +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.XContentParserUtils +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 CompositeInput( + val sequence: Sequence, +) : WorkflowInput { + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + Sequence(sin) + ) + + fun asTemplateArg(): Map { + return mapOf( + SEQUENCE_FIELD to sequence + ) + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + sequence.writeTo(out) + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .startObject(COMPOSITE_INPUT_FIELD) + .field(SEQUENCE_FIELD, sequence) + .endObject() + .endObject() + return builder + } + + override fun name(): String { + return COMPOSITE_INPUT_FIELD + } + + fun getMonitorIds(): List { + return sequence.delegates.map { delegate -> delegate.monitorId } + } + + companion object { + const val COMPOSITE_INPUT_FIELD = "composite_input" + const val SEQUENCE_FIELD = "sequence" + + val XCONTENT_REGISTRY = NamedXContentRegistry.Entry( + WorkflowInput::class.java, + ParseField(COMPOSITE_INPUT_FIELD), CheckedFunction { CompositeInput.parse(it) } + ) + + @JvmStatic + @Throws(IOException::class) + fun parse(xcp: XContentParser): CompositeInput { + var sequence = Sequence(emptyList()) + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + when (fieldName) { + SEQUENCE_FIELD -> { + sequence = Sequence.parse(xcp) + } + } + } + + return CompositeInput(sequence) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): CompositeInput { + return CompositeInput(sin) + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Delegate.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Delegate.kt new file mode 100644 index 00000000..65158a68 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Delegate.kt @@ -0,0 +1,118 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.model.BaseModel +import org.opensearch.commons.utils.validateId +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser +import java.io.IOException + +/** + * Each underlying monitors defined in the composite monitor sequence input. + * They are executed sequentially in the order mentioned. + * Optionally accepts chained findings context. + * */ +data class Delegate( + /** + * Defines the order of the monitor in delegate list + */ + val order: Int, + /** + * Id of the monitor + */ + val monitorId: String, + /** + * Keeps the track of the previously executed monitor in a chain list. + * Used for pre-filtering by getting the findings doc ids for the given monitor + */ + val chainedMonitorFindings: ChainedMonitorFindings? = null +) : BaseModel { + + init { + validateId(monitorId) + validateOrder(order) + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + order = sin.readInt(), + monitorId = sin.readString(), + chainedMonitorFindings = if (sin.readBoolean()) { + ChainedMonitorFindings(sin) + } else null, + ) + + fun asTemplateArg(): Map { + return mapOf( + ORDER_FIELD to order, + MONITOR_ID_FIELD to monitorId, + ) + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeInt(order) + out.writeString(monitorId) + out.writeBoolean(chainedMonitorFindings != null) + chainedMonitorFindings?.writeTo(out) + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .field(ORDER_FIELD, order) + .field(MONITOR_ID_FIELD, monitorId) + if (chainedMonitorFindings != null) { + builder.field(CHAINED_FINDINGS_FIELD, chainedMonitorFindings) + } + builder.endObject() + return builder + } + + companion object { + const val ORDER_FIELD = "order" + const val MONITOR_ID_FIELD = "monitor_id" + const val CHAINED_FINDINGS_FIELD = "chained_monitor_findings" + + @JvmStatic + @Throws(IOException::class) + fun parse(xcp: XContentParser): Delegate { + lateinit var monitorId: String + var order = 0 + var chainedMonitorFindings: ChainedMonitorFindings? = null + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + when (fieldName) { + ORDER_FIELD -> { + order = xcp.intValue() + validateOrder(order) + } + MONITOR_ID_FIELD -> { + monitorId = xcp.text() + validateId(monitorId) + } + CHAINED_FINDINGS_FIELD -> { + chainedMonitorFindings = ChainedMonitorFindings.parse(xcp) + } + } + } + return Delegate(order, monitorId, chainedMonitorFindings) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): Delegate { + return Delegate(sin) + } + + fun validateOrder(order: Int) { + require(order > 0) { "Invalid delgate order" } + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt index c4a45fb4..34e84994 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt @@ -22,9 +22,33 @@ class Finding( val monitorName: String, val index: String, val docLevelQueries: List, - val timestamp: Instant + val timestamp: Instant, + /** + * Keeps the track of the workflow-monitor exact execution. + * Used for filtering the data when chaining monitors in a workflow. + */ + val executionId: String? = null, ) : Writeable, ToXContent { + constructor( + id: String = NO_ID, + relatedDocIds: List, + monitorId: String, + monitorName: String, + index: String, + docLevelQueries: List, + timestamp: Instant + ) : this ( + id = id, + relatedDocIds = relatedDocIds, + monitorId = monitorId, + monitorName = monitorName, + index = index, + docLevelQueries = docLevelQueries, + timestamp = timestamp, + executionId = null + ) + @Throws(IOException::class) constructor(sin: StreamInput) : this( id = sin.readString(), @@ -34,7 +58,8 @@ class Finding( monitorName = sin.readString(), index = sin.readString(), docLevelQueries = sin.readList((DocLevelQuery)::readFrom), - timestamp = sin.readInstant() + timestamp = sin.readInstant(), + executionId = sin.readOptionalString() ) fun asTemplateArg(): Map { @@ -46,7 +71,8 @@ class Finding( MONITOR_NAME_FIELD to monitorName, INDEX_FIELD to index, QUERIES_FIELD to docLevelQueries, - TIMESTAMP_FIELD to timestamp.toEpochMilli() + TIMESTAMP_FIELD to timestamp.toEpochMilli(), + EXECUTION_ID_FIELD to executionId ) } @@ -60,6 +86,7 @@ class Finding( .field(INDEX_FIELD, index) .field(QUERIES_FIELD, docLevelQueries.toTypedArray()) .field(TIMESTAMP_FIELD, timestamp.toEpochMilli()) + .field(EXECUTION_ID_FIELD, executionId) builder.endObject() return builder } @@ -74,6 +101,7 @@ class Finding( out.writeString(index) out.writeCollection(docLevelQueries) out.writeInstant(timestamp) + out.writeOptionalString(executionId) } companion object { @@ -85,6 +113,7 @@ class Finding( const val INDEX_FIELD = "index" const val QUERIES_FIELD = "queries" const val TIMESTAMP_FIELD = "timestamp" + const val EXECUTION_ID_FIELD = "execution_id" const val NO_ID = "" @JvmStatic @@ -99,6 +128,7 @@ class Finding( lateinit var index: String val queries: MutableList = mutableListOf() lateinit var timestamp: Instant + var executionId: String? = null ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { @@ -131,6 +161,7 @@ class Finding( TIMESTAMP_FIELD -> { timestamp = requireNotNull(xcp.instant()) } + EXECUTION_ID_FIELD -> executionId = xcp.textOrNull() } } @@ -142,7 +173,8 @@ class Finding( monitorName = monitorName, index = index, docLevelQueries = queries, - timestamp = timestamp + timestamp = timestamp, + executionId = executionId ) } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Sequence.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Sequence.kt new file mode 100644 index 00000000..108f4004 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Sequence.kt @@ -0,0 +1,75 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.model.BaseModel +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser +import java.io.IOException + +/** Delegate monitors passed as input for composite monitors. */ +data class Sequence( + val delegates: List +) : BaseModel { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readList(::Delegate) + ) + + fun asTemplateArg(): Map { + return mapOf( + DELEGATES_FIELD to delegates, + ) + } + + companion object { + const val SEQUENCE_FIELD = "sequence" + const val DELEGATES_FIELD = "delegates" + + @JvmStatic + @Throws(IOException::class) + fun parse(xcp: XContentParser): Sequence { + val delegates: MutableList = mutableListOf() + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + when (fieldName) { + DELEGATES_FIELD -> { + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_ARRAY, + xcp.currentToken(), + xcp + ) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + delegates.add(Delegate.parse(xcp)) + } + } + } + } + return Sequence(delegates) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): DocLevelMonitorInput { + return DocLevelMonitorInput(sin) + } + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeCollection(delegates) + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + return builder.startObject() + .field(DELEGATES_FIELD, delegates.toTypedArray()) + .endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Workflow.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Workflow.kt new file mode 100644 index 00000000..fd563cc8 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Workflow.kt @@ -0,0 +1,265 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.CheckedFunction +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.util.IndexUtils.Companion.NO_SCHEMA_VERSION +import org.opensearch.commons.alerting.util.IndexUtils.Companion.WORKFLOW_MAX_INPUTS +import org.opensearch.commons.alerting.util.IndexUtils.Companion._ID +import org.opensearch.commons.alerting.util.IndexUtils.Companion._VERSION +import org.opensearch.commons.alerting.util.instant +import org.opensearch.commons.alerting.util.optionalTimeField +import org.opensearch.commons.alerting.util.optionalUserField +import org.opensearch.commons.authuser.User +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 +import java.time.Instant +import java.util.Locale + +data class Workflow( + override val id: String = NO_ID, + override val version: Long = NO_VERSION, + override val name: String, + override val enabled: Boolean, + override val schedule: Schedule, + override val lastUpdateTime: Instant, + override val enabledTime: Instant?, + // TODO: Check how this behaves during rolling upgrade/multi-version cluster + // Can read/write and parsing break if it's done from an old -> new version of the plugin? + val workflowType: WorkflowType, + val user: User?, + val schemaVersion: Int = NO_SCHEMA_VERSION, + val inputs: List, + val owner: String? = DEFAULT_OWNER +) : ScheduledJob { + override val type = WORKFLOW_TYPE + + init { + if (enabled) { + requireNotNull(enabledTime) + } else { + require(enabledTime == null) + } + require(inputs.size <= WORKFLOW_MAX_INPUTS) { "Workflows can only have $WORKFLOW_MAX_INPUTS search input." } + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + id = sin.readString(), + version = sin.readLong(), + name = sin.readString(), + enabled = sin.readBoolean(), + schedule = Schedule.readFrom(sin), + lastUpdateTime = sin.readInstant(), + enabledTime = sin.readOptionalInstant(), + workflowType = sin.readEnum(WorkflowType::class.java), + user = if (sin.readBoolean()) { + User(sin) + } else null, + schemaVersion = sin.readInt(), + inputs = sin.readList((WorkflowInput)::readFrom), + owner = sin.readOptionalString() + ) + + // This enum classifies different workflows + // This is different from 'type' which denotes the Scheduled Job type + enum class WorkflowType(val value: String) { + COMPOSITE("composite"); + + override fun toString(): String { + return value + } + } + + /** Returns a representation of the workflow suitable for passing into painless and mustache scripts. */ + fun asTemplateArg(): Map { + return mapOf(_ID to id, _VERSION to version, NAME_FIELD to name, ENABLED_FIELD to enabled) + } + + fun toXContentWithUser(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + return createXContentBuilder(builder, params, false) + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + return createXContentBuilder(builder, params, true) + } + + private fun createXContentBuilder( + builder: XContentBuilder, + params: ToXContent.Params, + secure: Boolean + ): XContentBuilder { + builder.startObject() + if (params.paramAsBoolean("with_type", false)) builder.startObject(type) + builder.field(TYPE_FIELD, type) + .field(SCHEMA_VERSION_FIELD, schemaVersion) + .field(NAME_FIELD, name) + .field(WORKFLOW_TYPE_FIELD, workflowType) + + if (!secure) { + builder.optionalUserField(USER_FIELD, user) + } + + builder.field(ENABLED_FIELD, enabled) + .optionalTimeField(ENABLED_TIME_FIELD, enabledTime) + .field(SCHEDULE_FIELD, schedule) + .field(INPUTS_FIELD, inputs.toTypedArray()) + .optionalTimeField(LAST_UPDATE_TIME_FIELD, lastUpdateTime) + builder.field(OWNER_FIELD, owner) + if (params.paramAsBoolean("with_type", false)) builder.endObject() + return builder.endObject() + } + + override fun fromDocument(id: String, version: Long): Workflow = copy(id = id, version = version) + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(id) + out.writeLong(version) + out.writeString(name) + out.writeBoolean(enabled) + if (schedule is CronSchedule) { + out.writeEnum(Schedule.TYPE.CRON) + } else { + out.writeEnum(Schedule.TYPE.INTERVAL) + } + schedule.writeTo(out) + out.writeInstant(lastUpdateTime) + out.writeOptionalInstant(enabledTime) + out.writeEnum(workflowType) + out.writeBoolean(user != null) + user?.writeTo(out) + out.writeInt(schemaVersion) + // Outputting type with each Input so that the generic Input.readFrom() can read it + out.writeVInt(inputs.size) + inputs.forEach { + if (it is CompositeInput) out.writeEnum(WorkflowInput.Type.COMPOSITE_INPUT) + it.writeTo(out) + } + // Outputting type with each Trigger so that the generic Trigger.readFrom() can read it + out.writeOptionalString(owner) + } + + companion object { + const val WORKFLOW_DELEGATE_PATH = "workflow.inputs.composite_input.sequence.delegates" + const val WORKFLOW_MONITOR_PATH = "workflow.inputs.composite_input.sequence.delegates.monitor_id" + const val WORKFLOW_TYPE = "workflow" + const val TYPE_FIELD = "type" + const val WORKFLOW_TYPE_FIELD = "workflow_type" + const val SCHEMA_VERSION_FIELD = "schema_version" + const val NAME_FIELD = "name" + const val USER_FIELD = "user" + const val ENABLED_FIELD = "enabled" + const val SCHEDULE_FIELD = "schedule" + const val NO_ID = "" + const val NO_VERSION = 1L + const val INPUTS_FIELD = "inputs" + const val LAST_UPDATE_TIME_FIELD = "last_update_time" + const val ENABLED_TIME_FIELD = "enabled_time" + const val OWNER_FIELD = "owner" + + // This is defined here instead of in ScheduledJob to avoid having the ScheduledJob class know about all + // the different subclasses and creating circular dependencies + val XCONTENT_REGISTRY = NamedXContentRegistry.Entry( + ScheduledJob::class.java, + ParseField(WORKFLOW_TYPE), + CheckedFunction { parse(it) } + ) + + @JvmStatic + @JvmOverloads + @Throws(IOException::class) + fun parse(xcp: XContentParser, id: String = NO_ID, version: Long = NO_VERSION): Workflow { + var name: String? = null + var workflowType: String = WorkflowType.COMPOSITE.toString() + var user: User? = null + var schedule: Schedule? = null + var lastUpdateTime: Instant? = null + var enabledTime: Instant? = null + var enabled = true + var schemaVersion = NO_SCHEMA_VERSION + val inputs: MutableList = mutableListOf() + var owner = DEFAULT_OWNER + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + when (fieldName) { + SCHEMA_VERSION_FIELD -> schemaVersion = xcp.intValue() + NAME_FIELD -> name = xcp.text() + WORKFLOW_TYPE_FIELD -> { + workflowType = xcp.text() + val allowedTypes = WorkflowType.values().map { it.value } + if (!allowedTypes.contains(workflowType)) { + throw IllegalStateException("Workflow type should be one of $allowedTypes") + } + } + USER_FIELD -> { + user = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) null else User.parse(xcp) + } + ENABLED_FIELD -> enabled = xcp.booleanValue() + SCHEDULE_FIELD -> schedule = Schedule.parse(xcp) + INPUTS_FIELD -> { + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_ARRAY, + xcp.currentToken(), + xcp + ) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + val input = WorkflowInput.parse(xcp) + inputs.add(input) + } + } + ENABLED_TIME_FIELD -> enabledTime = xcp.instant() + LAST_UPDATE_TIME_FIELD -> lastUpdateTime = xcp.instant() + OWNER_FIELD -> { + owner = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) owner else xcp.text() + } + else -> { + xcp.skipChildren() + } + } + } + + if (enabled && enabledTime == null) { + enabledTime = Instant.now() + } else if (!enabled) { + enabledTime = null + } + return Workflow( + id, + version, + requireNotNull(name) { "Workflow name is null" }, + enabled, + requireNotNull(schedule) { "Workflow schedule is null" }, + lastUpdateTime ?: Instant.now(), + enabledTime, + WorkflowType.valueOf(workflowType.uppercase(Locale.ROOT)), + user, + schemaVersion, + inputs.toList(), + owner + ) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): Workflow? { + return Workflow(sin) + } + + @Suppress("UNCHECKED_CAST") + fun suppressWarning(map: MutableMap?): MutableMap { + return map as MutableMap + } + + private const val DEFAULT_OWNER = "alerting" + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/WorkflowInput.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/WorkflowInput.kt new file mode 100644 index 00000000..bdead75a --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/WorkflowInput.kt @@ -0,0 +1,48 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.model.BaseModel +import org.opensearch.core.xcontent.XContentParser +import java.io.IOException + +interface WorkflowInput : BaseModel { + + enum class Type(val value: String) { + COMPOSITE_INPUT(CompositeInput.COMPOSITE_INPUT_FIELD); + + override fun toString(): String { + return value + } + } + + companion object { + + @Throws(IOException::class) + fun parse(xcp: XContentParser): WorkflowInput { + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + XContentParserUtils.ensureExpectedToken(XContentParser.Token.FIELD_NAME, xcp.nextToken(), xcp) + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.nextToken(), xcp) + val input = if (xcp.currentName() == Type.COMPOSITE_INPUT.value) { + CompositeInput.parse(xcp) + } else { + throw IllegalStateException("Unexpected input type when reading Input") + } + XContentParserUtils.ensureExpectedToken(XContentParser.Token.END_OBJECT, xcp.nextToken(), xcp) + return input + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): WorkflowInput { + return when (val type = sin.readEnum(Type::class.java)) { + Type.COMPOSITE_INPUT -> CompositeInput(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") + } + } + } + + fun name(): String +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/util/IndexUtils.kt b/src/main/kotlin/org/opensearch/commons/alerting/util/IndexUtils.kt index 803ab675..51f9be52 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/util/IndexUtils.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/util/IndexUtils.kt @@ -14,6 +14,7 @@ class IndexUtils { const val NO_SCHEMA_VERSION = 0 const val MONITOR_MAX_INPUTS = 1 + const val WORKFLOW_MAX_INPUTS = 1 const val MONITOR_MAX_TRIGGERS = 10 diff --git a/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt index 62e425d9..781097e4 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt @@ -16,15 +16,20 @@ import org.opensearch.common.io.stream.NamedWriteableRegistry import org.opensearch.common.settings.Settings import org.opensearch.commons.alerting.action.DeleteMonitorRequest import org.opensearch.commons.alerting.action.DeleteMonitorResponse +import org.opensearch.commons.alerting.action.DeleteWorkflowRequest +import org.opensearch.commons.alerting.action.DeleteWorkflowResponse 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.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.model.FindingDocument import org.opensearch.commons.alerting.model.FindingWithDocs import org.opensearch.commons.alerting.model.Monitor +import org.opensearch.commons.alerting.model.Workflow import org.opensearch.index.seqno.SequenceNumbers import org.opensearch.rest.RestStatus import org.opensearch.search.SearchModule @@ -55,6 +60,31 @@ internal class AlertingPluginInterfaceTests { Mockito.verify(listener, Mockito.times(1)).onResponse(ArgumentMatchers.eq(response)) } + @Test + fun indexWorkflow() { + val workflow = randomWorkflow() + + val request = mock(IndexWorkflowRequest::class.java) + val response = IndexWorkflowResponse( + Workflow.NO_ID, + Workflow.NO_VERSION, + SequenceNumbers.UNASSIGNED_SEQ_NO, + SequenceNumbers.UNASSIGNED_PRIMARY_TERM, + workflow + ) + val listener: ActionListener = + mock(ActionListener::class.java) as ActionListener + val namedWriteableRegistry = NamedWriteableRegistry(SearchModule(Settings.EMPTY, emptyList()).namedWriteables) + + Mockito.doAnswer { + (it.getArgument(2) as ActionListener) + .onResponse(response) + }.whenever(client).execute(Mockito.any(ActionType::class.java), Mockito.any(), Mockito.any()) + + AlertingPluginInterface.indexWorkflow(client, request, listener) + Mockito.verify(listener, Mockito.times(1)).onResponse(ArgumentMatchers.eq(response)) + } + @Test fun indexBucketMonitor() { val monitor = randomBucketLevelMonitor() @@ -89,6 +119,21 @@ internal class AlertingPluginInterfaceTests { AlertingPluginInterface.deleteMonitor(client, request, listener) } + @Test + fun deleteWorkflow() { + val request = mock(DeleteWorkflowRequest::class.java) + val response = DeleteWorkflowResponse(Workflow.NO_ID, Workflow.NO_VERSION) + 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.deleteWorkflow(client, request, listener) + } + @Test fun getAlerts() { val monitor = randomQueryLevelMonitor() diff --git a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt index 94c7d23a..b7f463ed 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt @@ -21,7 +21,10 @@ import org.opensearch.commons.alerting.model.ActionExecutionResult import org.opensearch.commons.alerting.model.AggregationResultBucket import org.opensearch.commons.alerting.model.Alert import org.opensearch.commons.alerting.model.BucketLevelTrigger +import org.opensearch.commons.alerting.model.ChainedMonitorFindings import org.opensearch.commons.alerting.model.ClusterMetricsInput +import org.opensearch.commons.alerting.model.CompositeInput +import org.opensearch.commons.alerting.model.Delegate import org.opensearch.commons.alerting.model.DocLevelMonitorInput import org.opensearch.commons.alerting.model.DocLevelQuery import org.opensearch.commons.alerting.model.DocumentLevelTrigger @@ -33,7 +36,10 @@ 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 +import org.opensearch.commons.alerting.model.Sequence import org.opensearch.commons.alerting.model.Trigger +import org.opensearch.commons.alerting.model.Workflow +import org.opensearch.commons.alerting.model.WorkflowInput import org.opensearch.commons.alerting.model.action.Action import org.opensearch.commons.alerting.model.action.ActionExecutionPolicy import org.opensearch.commons.alerting.model.action.ActionExecutionScope @@ -156,6 +162,73 @@ fun randomDocumentLevelMonitor( ) } +fun randomWorkflow( + name: String = RandomStrings.randomAsciiLettersOfLength(Random(), 10), + user: User? = randomUser(), + monitorIds: List? = null, + schedule: Schedule = IntervalSchedule(interval = 5, unit = ChronoUnit.MINUTES), + enabled: Boolean = Random().nextBoolean(), + enabledTime: Instant? = if (enabled) Instant.now().truncatedTo(ChronoUnit.MILLIS) else null, + lastUpdateTime: Instant = Instant.now().truncatedTo(ChronoUnit.MILLIS), +): Workflow { + val delegates = mutableListOf() + if (!monitorIds.isNullOrEmpty()) { + delegates.add(Delegate(1, monitorIds[0])) + for (i in 1 until monitorIds.size) { + // Order of monitors in workflow will be the same like forwarded meaning that the first monitorId will be used as second monitor chained finding + delegates.add(Delegate(i + 1, monitorIds [i], ChainedMonitorFindings(monitorIds[i - 1]))) + } + } + var input = listOf(CompositeInput(Sequence(delegates))) + if (input == null) { + input = listOf( + CompositeInput( + Sequence( + listOf(Delegate(1, "delegate1")) + ) + ) + ) + } + return Workflow( + name = name, workflowType = Workflow.WorkflowType.COMPOSITE, enabled = enabled, inputs = input, + schedule = schedule, enabledTime = enabledTime, lastUpdateTime = lastUpdateTime, user = user, + ) +} + +fun randomWorkflowWithDelegates( + name: String = RandomStrings.randomAsciiLettersOfLength(Random(), 10), + user: User? = randomUser(), + input: List, + schedule: Schedule = IntervalSchedule(interval = 5, unit = ChronoUnit.MINUTES), + enabled: Boolean = Random().nextBoolean(), + enabledTime: Instant? = if (enabled) Instant.now().truncatedTo(ChronoUnit.MILLIS) else null, + lastUpdateTime: Instant = Instant.now().truncatedTo(ChronoUnit.MILLIS), +): Workflow { + return Workflow( + name = name, workflowType = Workflow.WorkflowType.COMPOSITE, enabled = enabled, inputs = input, + schedule = schedule, enabledTime = enabledTime, lastUpdateTime = lastUpdateTime, user = user, + ) +} + +fun Workflow.toJsonStringWithUser(): String { + val builder = XContentFactory.jsonBuilder() + return this.toXContentWithUser(builder, ToXContent.EMPTY_PARAMS).string() +} + +fun randomSequence( + delegates: List = listOf(randomDelegate()) +): Sequence { + return Sequence(delegates) +} + +fun randomDelegate( + order: Int = 1, + monitorId: String = RandomStrings.randomAsciiLettersOfLength(Random(), 10), + chainedMonitorFindings: ChainedMonitorFindings? = null +): Delegate { + return Delegate(order, monitorId, chainedMonitorFindings) +} + fun randomQueryLevelTrigger( id: String = UUIDs.base64UUID(), name: String = RandomStrings.randomAsciiLettersOfLength(Random(), 10), diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequestTests.kt new file mode 100644 index 00000000..20bcb27e --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequestTests.kt @@ -0,0 +1,23 @@ +package org.opensearch.commons.alerting.action + +import org.junit.Assert +import org.junit.Test +import org.opensearch.common.io.stream.BytesStreamOutput +import org.opensearch.common.io.stream.StreamInput + +class DeleteWorkflowRequestTests { + + @Test + fun `test delete workflow request`() { + + val req = DeleteWorkflowRequest("1234", true) + Assert.assertNotNull(req) + Assert.assertEquals("1234", req.workflowId) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = DeleteWorkflowRequest(sin) + Assert.assertEquals("1234", newReq.workflowId) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequestTests.kt new file mode 100644 index 00000000..d25cd5b2 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequestTests.kt @@ -0,0 +1,210 @@ +package org.opensearch.commons.alerting.action + +import org.junit.Assert +import org.junit.jupiter.api.Assertions +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.NamedWriteableAwareStreamInput +import org.opensearch.common.io.stream.NamedWriteableRegistry +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.settings.Settings +import org.opensearch.commons.alerting.model.ChainedMonitorFindings +import org.opensearch.commons.alerting.model.CompositeInput +import org.opensearch.commons.alerting.model.Delegate +import org.opensearch.commons.alerting.model.Sequence +import org.opensearch.commons.alerting.randomWorkflow +import org.opensearch.commons.alerting.randomWorkflowWithDelegates +import org.opensearch.commons.utils.recreateObject +import org.opensearch.rest.RestRequest +import org.opensearch.search.SearchModule +import java.lang.Exception +import java.lang.IllegalArgumentException +import java.util.UUID + +class IndexWorkflowRequestTests { + + @Test + fun `test index workflow post request`() { + + val req = IndexWorkflowRequest( + "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.POST, + randomWorkflow() + ) + Assertions.assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = IndexWorkflowRequest(sin) + Assertions.assertEquals("1234", newReq.workflowId) + Assertions.assertEquals(1L, newReq.seqNo) + Assertions.assertEquals(2L, newReq.primaryTerm) + Assertions.assertEquals(RestRequest.Method.POST, newReq.method) + Assertions.assertNotNull(newReq.workflow) + } + + @Test + fun `test index composite workflow post request`() { + val req = IndexWorkflowRequest( + "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.POST, + randomWorkflow() + ) + Assertions.assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val namedWriteableRegistry = NamedWriteableRegistry(SearchModule(Settings.EMPTY, emptyList()).namedWriteables) + val newReq = IndexWorkflowRequest(NamedWriteableAwareStreamInput(sin, namedWriteableRegistry)) + Assertions.assertEquals("1234", newReq.workflowId) + Assertions.assertEquals(1L, newReq.seqNo) + Assertions.assertEquals(2L, newReq.primaryTerm) + Assertions.assertEquals(RestRequest.Method.POST, newReq.method) + Assertions.assertNotNull(newReq.workflow) + } + + @Test + fun `Index composite workflow serialize and deserialize transport object should be equal`() { + val compositeWorkflowRequest = IndexWorkflowRequest( + "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.POST, + randomWorkflow() + ) + + val recreatedObject = recreateObject( + compositeWorkflowRequest, + NamedWriteableRegistry(SearchModule(Settings.EMPTY, emptyList()).namedWriteables) + ) { IndexWorkflowRequest(it) } + Assertions.assertEquals(compositeWorkflowRequest.workflowId, recreatedObject.workflowId) + Assertions.assertEquals(compositeWorkflowRequest.seqNo, recreatedObject.seqNo) + Assertions.assertEquals(compositeWorkflowRequest.primaryTerm, recreatedObject.primaryTerm) + Assertions.assertEquals(compositeWorkflowRequest.method, recreatedObject.method) + Assertions.assertNotNull(recreatedObject.workflow) + Assertions.assertEquals(compositeWorkflowRequest.workflow, recreatedObject.workflow) + } + + @Test + fun `test index workflow put request`() { + + val req = IndexWorkflowRequest( + "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + randomWorkflow() + ) + Assertions.assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = IndexWorkflowRequest(sin) + Assertions.assertEquals("1234", newReq.workflowId) + Assertions.assertEquals(1L, newReq.seqNo) + Assertions.assertEquals(2L, newReq.primaryTerm) + Assertions.assertEquals(RestRequest.Method.PUT, newReq.method) + Assertions.assertNotNull(newReq.workflow) + } + + @Test + fun `test validate`() { + val req = IndexWorkflowRequest( + "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + randomWorkflow(monitorIds = emptyList()) + ) + Assertions.assertNotNull(req) + // Empty input list + var validate = req.validate() + Assert.assertTrue(validate != null) + Assert.assertTrue(validate!!.message!!.contains("Delegates list can not be empty.;")) + // Duplicate delegate + val req1 = IndexWorkflowRequest( + "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + randomWorkflow(monitorIds = listOf("1L", "1L", "2L")) + ) + validate = req1.validate() + Assert.assertTrue(validate != null) + Assert.assertTrue(validate!!.message!!.contains("Duplicate delegates not allowed")) + // Sequence not correct + var delegates = listOf( + Delegate(1, "monitor-1"), + Delegate(1, "monitor-2"), + Delegate(2, "monitor-3") + ) + val req2 = IndexWorkflowRequest( + "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + randomWorkflowWithDelegates( + input = listOf(CompositeInput(Sequence(delegates = delegates))) + ) + ) + validate = req2.validate() + Assert.assertTrue(validate != null) + Assert.assertTrue(validate!!.message!!.contains("Sequence ordering of delegate monitor shouldn't contain duplicate order values")) + // Chained finding sequence not correct + delegates = listOf( + Delegate(1, "monitor-1"), + Delegate(2, "monitor-2", ChainedMonitorFindings("monitor-1")), + Delegate(3, "monitor-3", ChainedMonitorFindings("monitor-x")) + ) + val req3 = IndexWorkflowRequest( + "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + randomWorkflowWithDelegates( + input = listOf(CompositeInput(Sequence(delegates = delegates))) + ) + ) + validate = req3.validate() + Assert.assertTrue(validate != null) + Assert.assertTrue(validate!!.message!!.contains("Chained Findings Monitor monitor-x doesn't exist in sequence")) + // Order not correct + delegates = listOf( + Delegate(1, "monitor-1"), + Delegate(3, "monitor-2", ChainedMonitorFindings("monitor-1")), + Delegate(2, "monitor-3", ChainedMonitorFindings("monitor-2")) + ) + val req4 = IndexWorkflowRequest( + "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + randomWorkflowWithDelegates( + input = listOf(CompositeInput(Sequence(delegates = delegates))) + ) + ) + validate = req4.validate() + Assert.assertTrue(validate != null) + Assert.assertTrue(validate!!.message!!.contains("Chained Findings Monitor monitor-2 should be executed before monitor monitor-3")) + // Max monitor size + val monitorsIds = mutableListOf() + for (i in 0..25) { + monitorsIds.add(UUID.randomUUID().toString()) + } + val req5 = IndexWorkflowRequest( + "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + randomWorkflow( + monitorIds = monitorsIds + ) + ) + validate = req5.validate() + Assert.assertTrue(validate != null) + Assert.assertTrue(validate!!.message!!.contains("Delegates list can not be larger then 25.")) + // Input list empty + val req6 = IndexWorkflowRequest( + "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + randomWorkflowWithDelegates( + input = emptyList() + ) + ) + validate = req6.validate() + Assert.assertTrue(validate != null) + Assert.assertTrue(validate!!.message!!.contains("Input list can not be empty.")) + // Input list multiple elements + delegates = listOf( + Delegate(1, "monitor-1") + ) + try { + IndexWorkflowRequest( + "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + randomWorkflowWithDelegates( + input = listOf(CompositeInput(Sequence(delegates = delegates)), CompositeInput(Sequence(delegates = delegates))) + ) + ) + } catch (ex: Exception) { + Assert.assertTrue(ex is IllegalArgumentException) + Assert.assertTrue(ex.message!!.contains("Workflows can only have 1 search input.")) + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowResponseTests.kt new file mode 100644 index 00000000..523f5650 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowResponseTests.kt @@ -0,0 +1,45 @@ +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.CronSchedule +import org.opensearch.commons.alerting.model.Workflow +import org.opensearch.commons.alerting.randomUser +import java.time.Instant +import java.time.ZoneId + +class IndexWorkflowResponseTests { + + @Test + fun `test index workflow response with workflow`() { + val cronExpression = "31 * * * *" // Run at minute 31. + val testInstance = Instant.ofEpochSecond(1538164858L) + + val cronSchedule = CronSchedule(cronExpression, ZoneId.of("Asia/Kolkata"), testInstance) + val workflow = Workflow( + id = "123", + version = 0L, + name = "test-workflow", + enabled = true, + schedule = cronSchedule, + lastUpdateTime = Instant.now(), + enabledTime = Instant.now(), + workflowType = Workflow.WorkflowType.COMPOSITE, + user = randomUser(), + schemaVersion = 0, + inputs = mutableListOf(), + ) + val req = IndexWorkflowResponse("1234", 1L, 2L, 0L, workflow) + Assertions.assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = IndexWorkflowResponse(sin) + Assertions.assertEquals("1234", newReq.id) + Assertions.assertEquals(1L, newReq.version) + Assertions.assertNotNull(newReq.workflow) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/model/CompositeInputTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/model/CompositeInputTests.kt new file mode 100644 index 00000000..9680bdbe --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/CompositeInputTests.kt @@ -0,0 +1,86 @@ +package org.opensearch.commons.alerting.model + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.opensearch.commons.alerting.randomDelegate +import org.opensearch.commons.alerting.randomSequence + +class CompositeInputTests { + @Test + fun `test sequence asTemplateArgs`() { + val sequence = randomSequence() + // WHEN + val templateArgs = sequence.asTemplateArg() + + // THEN + val templateDelegates = templateArgs + Assertions.assertEquals( + templateDelegates[Sequence.DELEGATES_FIELD], + sequence.delegates, + "Template args 'id' field does not match:" + ) + } + + @Test + fun `test delegate asTemplateArgs`() { + val delegate = randomDelegate() + // WHEN + val templateArgs = delegate.asTemplateArg() + + // THEN + val templateDelegates = templateArgs + Assertions.assertEquals( + templateDelegates[Delegate.ORDER_FIELD], + delegate.order, + "Template args 'id' field does not match:" + ) + Assertions.assertEquals( + templateDelegates[Delegate.MONITOR_ID_FIELD], + delegate.monitorId, + "Template args 'id' field does not match:" + ) + } + + @Test + fun `test create Delegate with illegal order value`() { + try { + randomDelegate(-1) + Assertions.fail("Expecting an illegal argument exception") + } catch (e: IllegalArgumentException) { + Assertions.assertEquals( + "Invalid delgate order", + e.message + ) + } + } + + @Test + fun `test create Delegate with illegal monitorId value`() { + try { + randomDelegate(1, "") + Assertions.fail("Expecting an illegal argument exception") + } catch (e: IllegalArgumentException) { + e.message?.let { + Assertions.assertTrue( + it.contains("Invalid characters in id") + + ) + } + } + } + + @Test + fun `test create Chained Findings with illegal monitorId value`() { + try { + ChainedMonitorFindings("") + Assertions.fail("Expecting an illegal argument exception") + } catch (e: IllegalArgumentException) { + e.message?.let { + Assertions.assertTrue( + it.contains("Invalid characters in 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 f8c98842..c5afed05 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt @@ -22,6 +22,7 @@ import org.opensearch.commons.alerting.randomQueryLevelTrigger import org.opensearch.commons.alerting.randomThrottle import org.opensearch.commons.alerting.randomUser import org.opensearch.commons.alerting.randomUserEmpty +import org.opensearch.commons.alerting.randomWorkflow import org.opensearch.commons.alerting.toJsonString import org.opensearch.commons.alerting.toJsonStringWithUser import org.opensearch.commons.alerting.util.string @@ -160,6 +161,14 @@ class XContentTests { Assertions.assertEquals(monitor, parsedMonitor, "Round tripping BucketLevelMonitor doesn't work") } + @Test + fun `test composite workflow parsing`() { + val workflow = randomWorkflow() + 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() From f435dbca179c459b74df2bd19cb4b46f0e8cd8c5 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Fri, 26 May 2023 12:48:35 -0700 Subject: [PATCH 110/149] Added 2.8 release notes (#441) (#442) Signed-off-by: bowenlan-amzn (cherry picked from commit 4c6f49719f3ada4c3b991636d78bb7e02c655185) Co-authored-by: bowenlan-amzn --- ...arch-common-utils.release-notes-2.8.0.0.md | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 release-notes/opensearch-common-utils.release-notes-2.8.0.0.md diff --git a/release-notes/opensearch-common-utils.release-notes-2.8.0.0.md b/release-notes/opensearch-common-utils.release-notes-2.8.0.0.md new file mode 100644 index 00000000..31769b9f --- /dev/null +++ b/release-notes/opensearch-common-utils.release-notes-2.8.0.0.md @@ -0,0 +1,20 @@ +## Version 2.8.0.0 2023-05-26 + +Compatible with OpenSearch 2.8.0 + +### Maintenance +* upgrade gradle to 8.1.1. ([#418](https://github.com/opensearch-project/common-utils/pull/418)) +* Sync up MAINTAINERS to CODEOWNERS. ([#427](https://github.com/opensearch-project/common-utils/pull/427)) +* Fix build errors after refactoring of Strings class in core. ([#432](https://github.com/opensearch-project/common-utils/pull/432)) +* updating maintainers and codeowners. ([#438](https://github.com/opensearch-project/common-utils/pull/438)) +* fix codeowners file format. ([#440](https://github.com/opensearch-project/common-utils/pull/440)) + +### Infrastructure +* Switch publish maven branches to list. ([#423](https://github.com/opensearch-project/common-utils/pull/423)) + +### Feature +* integrate security-analytics & alerting for correlation engine. ([#412](https://github.com/opensearch-project/common-utils/pull/412)) +* NoOpTrigger. ([#420](https://github.com/opensearch-project/common-utils/pull/420)) + +### Documentation +* Added 2.8 release notes. ([#441](https://github.com/opensearch-project/common-utils/pull/441)) \ No newline at end of file From 53e10be701ce0258a739beee993df83d8538a2f8 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Thu, 8 Jun 2023 10:16:28 -0700 Subject: [PATCH 111/149] Increment version to 2.9.0-SNAPSHOT (#444) Signed-off-by: opensearch-ci-bot Co-authored-by: opensearch-ci-bot --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 431eed39..6684a7cd 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "2.8.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.9.0-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") buildVersionQualifier = System.getProperty("build.version_qualifier", "") kotlin_version = System.getProperty("kotlin.version", "1.6.10") From 03505f0118dfea817ffb8a8b54a4c03810a5d6ac Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Tue, 20 Jun 2023 08:52:32 -0700 Subject: [PATCH 112/149] Adds Chained alerts triggers for workflows (#456) (#457) * adds models for chained alert trigger and chained alerts (#426) Signed-off-by: Surya Sashank Nistala * fix execution id field in alerts (#429) Signed-off-by: Surya Sashank Nistala * accept workflow argument in chained alert constructor (#435) Signed-off-by: Surya Sashank Nistala * add tests for chained alert Signed-off-by: Surya Sashank Nistala --------- Signed-off-by: Surya Sashank Nistala (cherry picked from commit 5ef9b55771d8391f3234544e2aa64c1e379a487d) Co-authored-by: Surya Sashank Nistala --- .../commons/alerting/model/Alert.kt | 56 +++++-- .../alerting/model/ChainedAlertTrigger.kt | 150 ++++++++++++++++++ .../commons/alerting/model/Trigger.kt | 4 +- .../commons/alerting/model/Workflow.kt | 37 ++++- .../opensearch/commons/alerting/AlertTests.kt | 10 ++ .../alerting/AlertingPluginInterfaceTests.kt | 1 - .../commons/alerting/TestHelpers.kt | 41 +++++ .../alerting/action/GetAlertsResponseTests.kt | 2 +- .../action/GetFindingsResponseTests.kt | 5 +- .../action/IndexWorkflowResponseTests.kt | 16 ++ .../commons/alerting/model/WriteableTests.kt | 11 ++ 11 files changed, 311 insertions(+), 22 deletions(-) create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/ChainedAlertTrigger.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 3c87011f..dc57fb53 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt @@ -38,7 +38,8 @@ data class Alert( val errorHistory: List, val severity: String, val actionExecutionResults: List, - val aggregationResultBucket: AggregationResultBucket? = null + val aggregationResultBucket: AggregationResultBucket? = null, + val executionId: String? = null, ) : Writeable, ToXContent { init { @@ -47,6 +48,24 @@ data class Alert( } } + constructor( + startTime: Instant, + lastNotificationTime: Instant?, + state: State = State.ACTIVE, + errorMessage: String? = null, + schemaVersion: Int = NO_SCHEMA_VERSION, + executionId: String, + chainedAlertTrigger: ChainedAlertTrigger, + workflow: Workflow + ) : 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 + ) + constructor( monitor: Monitor, trigger: QueryLevelTrigger, @@ -56,13 +75,15 @@ data class Alert( errorMessage: String? = null, errorHistory: List = mutableListOf(), actionExecutionResults: List = mutableListOf(), - schemaVersion: Int = NO_SCHEMA_VERSION + schemaVersion: Int = NO_SCHEMA_VERSION, + executionId: 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() + aggregationResultBucket = null, findingIds = emptyList(), relatedDocIds = emptyList(), + executionId = executionId ) constructor( @@ -75,13 +96,15 @@ data class Alert( errorHistory: List = mutableListOf(), actionExecutionResults: List = mutableListOf(), schemaVersion: Int = NO_SCHEMA_VERSION, - findingIds: List = emptyList() + findingIds: List = emptyList(), + executionId: 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() + aggregationResultBucket = null, findingIds = findingIds, relatedDocIds = emptyList(), + executionId = executionId ) constructor( @@ -95,13 +118,15 @@ data class Alert( actionExecutionResults: List = mutableListOf(), schemaVersion: Int = NO_SCHEMA_VERSION, aggregationResultBucket: AggregationResultBucket, - findingIds: List = emptyList() + findingIds: List = emptyList(), + executionId: 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() + aggregationResultBucket = aggregationResultBucket, findingIds = findingIds, relatedDocIds = emptyList(), + executionId = executionId ) constructor( @@ -116,13 +141,15 @@ data class Alert( errorMessage: String? = null, errorHistory: List = mutableListOf(), actionExecutionResults: List = mutableListOf(), - schemaVersion: Int = NO_SCHEMA_VERSION + schemaVersion: Int = NO_SCHEMA_VERSION, + executionId: 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 + aggregationResultBucket = null, findingIds = findingIds, relatedDocIds = relatedDocIds, + executionId = executionId ) constructor( @@ -171,7 +198,8 @@ data class Alert( errorHistory = sin.readList(::AlertError), severity = sin.readString(), actionExecutionResults = sin.readList(::ActionExecutionResult), - aggregationResultBucket = if (sin.readBoolean()) AggregationResultBucket(sin) else null + aggregationResultBucket = if (sin.readBoolean()) AggregationResultBucket(sin) else null, + executionId = sin.readOptionalString() ) fun isAcknowledged(): Boolean = (state == State.ACKNOWLEDGED) @@ -205,6 +233,7 @@ data class Alert( } else { out.writeBoolean(false) } + out.writeOptionalString(executionId) } companion object { @@ -229,6 +258,7 @@ data class Alert( const val ALERT_HISTORY_FIELD = "alert_history" const val SEVERITY_FIELD = "severity" const val ACTION_EXECUTION_RESULTS_FIELD = "action_execution_results" + const val EXECUTION_ID_FIELD = "execution_id" const val BUCKET_KEYS = AggregationResultBucket.BUCKET_KEYS const val PARENTS_BUCKET_PATH = AggregationResultBucket.PARENTS_BUCKET_PATH const val NO_ID = "" @@ -254,6 +284,7 @@ data class Alert( var lastNotificationTime: Instant? = null var acknowledgedTime: Instant? = null var errorMessage: String? = null + var executionId: String? = null val errorHistory: MutableList = mutableListOf() val actionExecutionResults: MutableList = mutableListOf() var aggAlertBucket: AggregationResultBucket? = null @@ -288,6 +319,7 @@ data class Alert( LAST_NOTIFICATION_TIME_FIELD -> lastNotificationTime = xcp.instant() ACKNOWLEDGED_TIME_FIELD -> acknowledgedTime = xcp.instant() ERROR_MESSAGE_FIELD -> errorMessage = xcp.textOrNull() + EXECUTION_ID_FIELD -> executionId = xcp.textOrNull() ALERT_HISTORY_FIELD -> { ensureExpectedToken(XContentParser.Token.START_ARRAY, xcp.currentToken(), xcp) while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { @@ -323,7 +355,7 @@ data class Alert( lastNotificationTime = lastNotificationTime, acknowledgedTime = acknowledgedTime, errorMessage = errorMessage, errorHistory = errorHistory, severity = severity, actionExecutionResults = actionExecutionResults, aggregationResultBucket = aggAlertBucket, findingIds = findingIds, - relatedDocIds = relatedDocIds + relatedDocIds = relatedDocIds, executionId = executionId ) } @@ -349,6 +381,7 @@ data class Alert( .field(SCHEMA_VERSION_FIELD, schemaVersion) .field(MONITOR_VERSION_FIELD, monitorVersion) .field(MONITOR_NAME_FIELD, monitorName) + .field(EXECUTION_ID_FIELD, executionId) if (!secure) { builder.optionalUserField(MONITOR_USER_FIELD, monitorUser) @@ -379,6 +412,7 @@ data class Alert( ALERT_VERSION_FIELD to version, END_TIME_FIELD to endTime?.toEpochMilli(), ERROR_MESSAGE_FIELD to errorMessage, + EXECUTION_ID_FIELD to executionId, LAST_NOTIFICATION_TIME_FIELD to lastNotificationTime?.toEpochMilli(), SEVERITY_FIELD to severity, START_TIME_FIELD to startTime.toEpochMilli(), diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/ChainedAlertTrigger.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/ChainedAlertTrigger.kt new file mode 100644 index 00000000..debc4ca6 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/ChainedAlertTrigger.kt @@ -0,0 +1,150 @@ +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.Trigger.Companion.ACTIONS_FIELD +import org.opensearch.commons.alerting.model.Trigger.Companion.ID_FIELD +import org.opensearch.commons.alerting.model.Trigger.Companion.NAME_FIELD +import org.opensearch.commons.alerting.model.Trigger.Companion.SEVERITY_FIELD +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 org.opensearch.script.Script +import java.io.IOException + +data class ChainedAlertTrigger( + override val id: String = UUIDs.base64UUID(), + override val name: String, + override val severity: String, + override val actions: List, + val condition: Script +) : Trigger { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readString(), // id + sin.readString(), // name + sin.readString(), // severity + sin.readList(::Action), // actions + Script(sin) + ) + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .startObject(CHAINED_ALERT_TRIGGER_FIELD) + .field(ID_FIELD, id) + .field(NAME_FIELD, name) + .field(SEVERITY_FIELD, severity) + .startObject(CONDITION_FIELD) + .field(SCRIPT_FIELD, condition) + .endObject() + .field(ACTIONS_FIELD, actions.toTypedArray()) + .endObject() + .endObject() + return builder + } + + override fun name(): String { + return CHAINED_ALERT_TRIGGER_FIELD + } + + /** Returns a representation of the trigger suitable for passing into painless and mustache scripts. */ + fun asTemplateArg(): Map { + return mapOf( + ID_FIELD to id, + NAME_FIELD to name, + SEVERITY_FIELD to severity, + ACTIONS_FIELD to actions.map { it.asTemplateArg() } + ) + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(id) + out.writeString(name) + out.writeString(severity) + out.writeCollection(actions) + condition.writeTo(out) + } + + companion object { + const val CHAINED_ALERT_TRIGGER_FIELD = "chained_alert_trigger" + const val CONDITION_FIELD = "condition" + const val SCRIPT_FIELD = "script" + const val QUERY_IDS_FIELD = "query_ids" + + val XCONTENT_REGISTRY = NamedXContentRegistry.Entry( + Trigger::class.java, + ParseField(CHAINED_ALERT_TRIGGER_FIELD), + CheckedFunction { parseInner(it) } + ) + + @JvmStatic + @Throws(IOException::class) + fun parseInner(xcp: XContentParser): ChainedAlertTrigger { + var id = UUIDs.base64UUID() // assign a default triggerId if one is not specified + lateinit var name: String + lateinit var severity: String + lateinit var condition: Script + val actions: MutableList = mutableListOf() + + 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) { + ID_FIELD -> id = xcp.text() + NAME_FIELD -> name = xcp.text() + SEVERITY_FIELD -> severity = xcp.text() + CONDITION_FIELD -> { + xcp.nextToken() + condition = Script.parse(xcp) + require(condition.lang == Script.DEFAULT_SCRIPT_LANG) { + "Invalid script language. Allowed languages are [${Script.DEFAULT_SCRIPT_LANG}]" + } + xcp.nextToken() + } + ACTIONS_FIELD -> { + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_ARRAY, + xcp.currentToken(), + xcp + ) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + actions.add(Action.parse(xcp)) + } + } + } + xcp.nextToken() + } + + return ChainedAlertTrigger( + name = requireNotNull(name) { "Trigger name is null" }, + severity = requireNotNull(severity) { "Trigger severity is null" }, + condition = requireNotNull(condition) { "Trigger condition is null" }, + actions = requireNotNull(actions) { "Trigger actions are null" }, + id = requireNotNull(id) { "Trigger id is null." } + ) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): ChainedAlertTrigger { + return ChainedAlertTrigger(sin) + } + } +} 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 254b6401..fdc7ae80 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Trigger.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Trigger.kt @@ -13,7 +13,8 @@ interface Trigger : BaseModel { DOCUMENT_LEVEL_TRIGGER(DocumentLevelTrigger.DOCUMENT_LEVEL_TRIGGER_FIELD), QUERY_LEVEL_TRIGGER(QueryLevelTrigger.QUERY_LEVEL_TRIGGER_FIELD), BUCKET_LEVEL_TRIGGER(BucketLevelTrigger.BUCKET_LEVEL_TRIGGER_FIELD), - NOOP_TRIGGER(NoOpTrigger.NOOP_TRIGGER_FIELD); + NOOP_TRIGGER(NoOpTrigger.NOOP_TRIGGER_FIELD), + CHAINED_ALERT_TRIGGER(ChainedAlertTrigger.CHAINED_ALERT_TRIGGER_FIELD); override fun toString(): String { return value @@ -53,6 +54,7 @@ interface Trigger : BaseModel { Type.QUERY_LEVEL_TRIGGER -> QueryLevelTrigger(sin) Type.BUCKET_LEVEL_TRIGGER -> BucketLevelTrigger(sin) Type.DOCUMENT_LEVEL_TRIGGER -> DocumentLevelTrigger(sin) + Type.CHAINED_ALERT_TRIGGER -> ChainedAlertTrigger(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/Workflow.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Workflow.kt index fd563cc8..d2f2518d 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Workflow.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Workflow.kt @@ -35,7 +35,8 @@ data class Workflow( val user: User?, val schemaVersion: Int = NO_SCHEMA_VERSION, val inputs: List, - val owner: String? = DEFAULT_OWNER + val owner: String? = DEFAULT_OWNER, + val triggers: List ) : ScheduledJob { override val type = WORKFLOW_TYPE @@ -46,6 +47,11 @@ data class Workflow( require(enabledTime == null) } require(inputs.size <= WORKFLOW_MAX_INPUTS) { "Workflows can only have $WORKFLOW_MAX_INPUTS search input." } + triggers.forEach { trigger -> + run { + require(trigger is ChainedAlertTrigger) { "Incompatible trigger [${trigger.name}] for workflow. " } + } + } } @Throws(IOException::class) @@ -63,7 +69,8 @@ data class Workflow( } else null, schemaVersion = sin.readInt(), inputs = sin.readList((WorkflowInput)::readFrom), - owner = sin.readOptionalString() + owner = sin.readOptionalString(), + triggers = sin.readList((Trigger)::readFrom) ) // This enum classifies different workflows @@ -109,6 +116,7 @@ data class Workflow( .optionalTimeField(ENABLED_TIME_FIELD, enabledTime) .field(SCHEDULE_FIELD, schedule) .field(INPUTS_FIELD, inputs.toTypedArray()) + .field(TRIGGERS_FIELD, triggers.toTypedArray()) .optionalTimeField(LAST_UPDATE_TIME_FIELD, lastUpdateTime) builder.field(OWNER_FIELD, owner) if (params.paramAsBoolean("with_type", false)) builder.endObject() @@ -143,11 +151,17 @@ data class Workflow( } // Outputting type with each Trigger so that the generic Trigger.readFrom() can read it out.writeOptionalString(owner) + out.writeVInt(triggers.size) + triggers.forEach { + when (it) { + is ChainedAlertTrigger -> out.writeEnum(Trigger.Type.CHAINED_ALERT_TRIGGER) + else -> throw IOException("Unsupported trigger type for workflow") + } + it.writeTo(out) + } } companion object { - const val WORKFLOW_DELEGATE_PATH = "workflow.inputs.composite_input.sequence.delegates" - const val WORKFLOW_MONITOR_PATH = "workflow.inputs.composite_input.sequence.delegates.monitor_id" const val WORKFLOW_TYPE = "workflow" const val TYPE_FIELD = "type" const val WORKFLOW_TYPE_FIELD = "workflow_type" @@ -161,6 +175,7 @@ data class Workflow( const val INPUTS_FIELD = "inputs" const val LAST_UPDATE_TIME_FIELD = "last_update_time" const val ENABLED_TIME_FIELD = "enabled_time" + const val TRIGGERS_FIELD = "triggers" const val OWNER_FIELD = "owner" // This is defined here instead of in ScheduledJob to avoid having the ScheduledJob class know about all @@ -184,6 +199,7 @@ data class Workflow( var enabled = true var schemaVersion = NO_SCHEMA_VERSION val inputs: MutableList = mutableListOf() + val triggers: MutableList = mutableListOf() var owner = DEFAULT_OWNER XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) @@ -206,6 +222,16 @@ data class Workflow( } ENABLED_FIELD -> enabled = xcp.booleanValue() SCHEDULE_FIELD -> schedule = Schedule.parse(xcp) + Monitor.TRIGGERS_FIELD -> { + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_ARRAY, + xcp.currentToken(), + xcp + ) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + triggers.add(Trigger.parse(xcp)) + } + } INPUTS_FIELD -> { XContentParserUtils.ensureExpectedToken( XContentParser.Token.START_ARRAY, @@ -245,7 +271,8 @@ data class Workflow( user, schemaVersion, inputs.toList(), - owner + owner, + triggers ) } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt index 23e47825..29b32bb5 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.opensearch.commons.alerting.model.Alert +import kotlin.test.assertTrue class AlertTests { @Test @@ -58,4 +59,13 @@ class AlertTests { val activeAlert = randomAlert().copy(state = Alert.State.ACTIVE) Assertions.assertFalse(activeAlert.isAcknowledged(), "Alert is acknowledged") } + + @Test + fun `test chained alert`() { + val workflow = randomWorkflow() + val trigger = randomChainedAlertTrigger() + val alert = randomChainedAlert(workflow = workflow, trigger = trigger) + assertEquals(alert.monitorId, "") + assertEquals(alert.id, "") + } } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt index 781097e4..69eb756c 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt @@ -74,7 +74,6 @@ internal class AlertingPluginInterfaceTests { ) val listener: ActionListener = mock(ActionListener::class.java) as ActionListener - val namedWriteableRegistry = NamedWriteableRegistry(SearchModule(Settings.EMPTY, emptyList()).namedWriteables) Mockito.doAnswer { (it.getArgument(2) as ActionListener) diff --git a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt index b7f463ed..b6721a0f 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt @@ -21,6 +21,7 @@ import org.opensearch.commons.alerting.model.ActionExecutionResult import org.opensearch.commons.alerting.model.AggregationResultBucket import org.opensearch.commons.alerting.model.Alert import org.opensearch.commons.alerting.model.BucketLevelTrigger +import org.opensearch.commons.alerting.model.ChainedAlertTrigger import org.opensearch.commons.alerting.model.ChainedMonitorFindings import org.opensearch.commons.alerting.model.ClusterMetricsInput import org.opensearch.commons.alerting.model.CompositeInput @@ -63,6 +64,7 @@ import org.opensearch.search.builder.SearchSourceBuilder import java.time.Instant import java.time.temporal.ChronoUnit import java.util.Random +import java.util.UUID const val ALL_ACCESS_ROLE = "all_access" @@ -170,6 +172,7 @@ fun randomWorkflow( enabled: Boolean = Random().nextBoolean(), enabledTime: Instant? = if (enabled) Instant.now().truncatedTo(ChronoUnit.MILLIS) else null, lastUpdateTime: Instant = Instant.now().truncatedTo(ChronoUnit.MILLIS), + triggers: List = listOf(randomChainedAlertTrigger()), ): Workflow { val delegates = mutableListOf() if (!monitorIds.isNullOrEmpty()) { @@ -192,6 +195,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 ) } @@ -203,10 +207,12 @@ fun randomWorkflowWithDelegates( enabled: Boolean = Random().nextBoolean(), enabledTime: Instant? = if (enabled) Instant.now().truncatedTo(ChronoUnit.MILLIS) else null, lastUpdateTime: Instant = Instant.now().truncatedTo(ChronoUnit.MILLIS), + triggers: List = (1..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { randomChainedAlertTrigger() }, ): Workflow { return Workflow( name = name, workflowType = Workflow.WorkflowType.COMPOSITE, enabled = enabled, inputs = input, schedule = schedule, enabledTime = enabledTime, lastUpdateTime = lastUpdateTime, user = user, + triggers = triggers ) } @@ -285,6 +291,25 @@ fun randomDocumentLevelTrigger( ) } +fun randomChainedAlertTrigger( + id: String = UUIDs.base64UUID(), + name: String = RandomStrings.randomAsciiLettersOfLength(Random(), 10), + severity: String = "1", + condition: Script = randomScript(), + actions: List = mutableListOf(), + destinationId: String = "" +): ChainedAlertTrigger { + return ChainedAlertTrigger( + id = id, + name = name, + severity = severity, + condition = condition, + actions = if (actions.isEmpty() && destinationId.isNotBlank()) { + (0..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { randomAction(destinationId = destinationId) } + } else actions + ) +} + fun randomBucketSelectorExtAggregationBuilder( name: String = RandomStrings.randomAsciiLettersOfLength(Random(), 10), bucketsPathsMap: MutableMap = mutableMapOf("avg" to "10"), @@ -470,6 +495,7 @@ fun xContentRegistry(): NamedXContentRegistry { QueryLevelTrigger.XCONTENT_REGISTRY, BucketLevelTrigger.XCONTENT_REGISTRY, DocumentLevelTrigger.XCONTENT_REGISTRY, + ChainedAlertTrigger.XCONTENT_REGISTRY, NoOpTrigger.XCONTENT_REGISTRY ) + SearchModule(Settings.EMPTY, emptyList()).namedXContents ) @@ -496,6 +522,21 @@ fun randomAlert(monitor: Monitor = randomQueryLevelMonitor()): Alert { ) } +fun randomChainedAlert( + workflow: Workflow = randomWorkflow(), + trigger: ChainedAlertTrigger = randomChainedAlertTrigger(), +): Alert { + return Alert( + startTime = Instant.now(), + lastNotificationTime = Instant.now(), + state = Alert.State.ACTIVE, + errorMessage = null, + executionId = UUID.randomUUID().toString(), + chainedAlertTrigger = trigger, + workflow = workflow + ) +} + fun randomActionExecutionResult( actionId: String = UUIDs.base64UUID(), lastExecutionTime: Instant = Instant.now().truncatedTo(ChronoUnit.MILLIS), 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 5d5b2364..f04745d3 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponseTests.kt @@ -97,7 +97,7 @@ class GetAlertsResponseTests { var actualXContentString = req.toXContent(builder(), ToXContent.EMPTY_PARAMS).string() val expectedXContentString = "{\"alerts\":[{\"id\":\"id\",\"version\":0,\"monitor_id\":\"monitorId\"," + "\"schema_version\":0,\"monitor_version\":0,\"monitor_name\":\"monitorName\"," + - "\"trigger_id\":\"triggerId\",\"trigger_name\":\"triggerName\"," + + "\"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}" diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt index 7349078b..e5b391bb 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt @@ -10,7 +10,6 @@ import org.opensearch.commons.alerting.model.FindingDocument import org.opensearch.commons.alerting.model.FindingWithDocs import org.opensearch.rest.RestStatus import java.time.Instant -import java.util.List internal class GetFindingsResponseTests { @@ -25,7 +24,7 @@ internal class GetFindingsResponseTests { "monitor_id1", "monitor_name1", "test_index1", - listOf(DocLevelQuery("1", "myQuery", "fieldA:valABC", List.of())), + listOf(DocLevelQuery("1", "myQuery", "fieldA:valABC", listOf())), Instant.now() ) val findingDocument1 = FindingDocument("test_index1", "doc1", true, "document 1 payload") @@ -44,7 +43,7 @@ internal class GetFindingsResponseTests { "monitor_id2", "monitor_name2", "test_index2", - listOf(DocLevelQuery("1", "myQuery", "fieldA:valABC", List.of())), + listOf(DocLevelQuery("1", "myQuery", "fieldA:valABC", listOf())), Instant.now() ) val findingDocument21 = FindingDocument("test_index2", "doc21", true, "document 21 payload") diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowResponseTests.kt index 523f5650..8565d093 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowResponseTests.kt @@ -4,8 +4,10 @@ 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.ChainedAlertTrigger import org.opensearch.commons.alerting.model.CronSchedule import org.opensearch.commons.alerting.model.Workflow +import org.opensearch.commons.alerting.randomChainedAlertTrigger import org.opensearch.commons.alerting.randomUser import java.time.Instant import java.time.ZoneId @@ -30,6 +32,7 @@ class IndexWorkflowResponseTests { user = randomUser(), schemaVersion = 0, inputs = mutableListOf(), + triggers = listOf(randomChainedAlertTrigger()) ) val req = IndexWorkflowResponse("1234", 1L, 2L, 0L, workflow) Assertions.assertNotNull(req) @@ -41,5 +44,18 @@ class IndexWorkflowResponseTests { Assertions.assertEquals("1234", newReq.id) Assertions.assertEquals(1L, newReq.version) Assertions.assertNotNull(newReq.workflow) + Assertions.assertEquals(newReq.workflow.triggers.size, 1) + Assertions.assertEquals(newReq.workflow.triggers.get(0).name, req.workflow.triggers.get(0).name) + Assertions.assertEquals(newReq.workflow.triggers.get(0).id, req.workflow.triggers.get(0).id) + Assertions.assertEquals(newReq.workflow.triggers.get(0).severity, req.workflow.triggers.get(0).severity) + Assertions.assertEquals( + (newReq.workflow.triggers.get(0) as ChainedAlertTrigger).condition.idOrCode, + (req.workflow.triggers.get(0) as ChainedAlertTrigger).condition.idOrCode + ) + + Assertions.assertEquals( + (newReq.workflow.triggers.get(0) as ChainedAlertTrigger).condition.lang, + (req.workflow.triggers.get(0) as ChainedAlertTrigger).condition.lang + ) } } 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 9f5e26b9..a3e83026 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/WriteableTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/WriteableTests.kt @@ -10,6 +10,7 @@ import org.opensearch.commons.alerting.model.action.Throttle import org.opensearch.commons.alerting.randomAction import org.opensearch.commons.alerting.randomActionExecutionPolicy import org.opensearch.commons.alerting.randomBucketLevelTrigger +import org.opensearch.commons.alerting.randomChainedAlertTrigger import org.opensearch.commons.alerting.randomDocumentLevelTrigger import org.opensearch.commons.alerting.randomQueryLevelMonitor import org.opensearch.commons.alerting.randomQueryLevelTrigger @@ -111,6 +112,16 @@ class WriteableTests { Assertions.assertEquals(trigger, newTrigger, "Round tripping DocumentLevelTrigger doesn't work") } + @Test + fun `test chained alert trigger as stream`() { + val trigger = randomChainedAlertTrigger() + val out = BytesStreamOutput() + trigger.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newTrigger = ChainedAlertTrigger.readFrom(sin) + Assertions.assertEquals(trigger, newTrigger, "Round tripping DocumentLevelTrigger doesn't work") + } + @Test fun `test searchinput as stream`() { val input = SearchInput(emptyList(), SearchSourceBuilder()) From c44cc5c0964220c3c42853061f6ccc7a4319c031 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Wed, 21 Jun 2023 09:28:43 -0700 Subject: [PATCH 113/149] acknowledge chained alert request for workflow (#459) (#460) Signed-off-by: Surya Sashank Nistala (cherry picked from commit fcb853e8543240629fff568d3d5712a65a287f47) Co-authored-by: Surya Sashank Nistala --- .../action/AcknowledgeChainedAlertRequest.kt | 43 +++++++++++++++++++ .../AcknowledgeChainedAlertRequestTests.kt | 27 ++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequest.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequestTests.kt diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequest.kt new file mode 100644 index 00000000..a89c6332 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequest.kt @@ -0,0 +1,43 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +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 java.io.IOException +import java.util.Collections + +/** Request DTO for acknowledging chained alerts generated by workflow.*/ +class AcknowledgeChainedAlertRequest : ActionRequest { + val workflowId: String + val alertIds: List + + constructor( + workflowId: String, + alertIds: List, + ) : super() { + this.workflowId = workflowId + this.alertIds = alertIds + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readString(), // workflowId + Collections.unmodifiableList(sin.readStringList()), // alertIds + ) + + override fun validate(): ActionRequestValidationException? { + return null + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(workflowId) + out.writeStringCollection(alertIds) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequestTests.kt new file mode 100644 index 00000000..012e370e --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequestTests.kt @@ -0,0 +1,27 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +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 + +class AcknowledgeChainedAlertRequestTests { + + @Test + fun `test acknowledge chained alert request`() { + val req = AcknowledgeChainedAlertRequest("1234", mutableListOf("1", "2", "3", "4")) + assertNotNull(req) + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = AcknowledgeChainedAlertRequest(sin) + assertEquals("1234", newReq.workflowId) + assertEquals(4, newReq.alertIds.size) + } +} From dd41bfc52e740be83a87e6087770f87c4a23dc26 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:42:08 -0700 Subject: [PATCH 114/149] adds audit state in Alert (#461) (#462) Signed-off-by: Surya Sashank Nistala (cherry picked from commit f1c0f1dca447e972294867d1583a55030c86a990) Co-authored-by: Surya Sashank Nistala --- .../org/opensearch/commons/alerting/model/Alert.kt | 7 ++++++- .../org/opensearch/commons/alerting/AlertTests.kt | 12 +++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) 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 dc57fb53..1ae72401 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt @@ -171,7 +171,12 @@ data class Alert( ) enum class State { - ACTIVE, ACKNOWLEDGED, COMPLETED, ERROR, DELETED + 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. + // Audit state alerts will be created in the history index and do not need to be acknowledged by users. + AUDIT } @Throws(IOException::class) diff --git a/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt index 29b32bb5..d9579ccf 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt @@ -4,7 +4,8 @@ import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.opensearch.commons.alerting.model.Alert -import kotlin.test.assertTrue +import java.time.Instant +import java.time.temporal.ChronoUnit class AlertTests { @Test @@ -60,6 +61,15 @@ class AlertTests { Assertions.assertFalse(activeAlert.isAcknowledged(), "Alert is acknowledged") } + @Test + fun `test alert in audit state`() { + val auditAlert = Alert( + randomQueryLevelMonitor(), randomQueryLevelTrigger(), Instant.now().truncatedTo(ChronoUnit.MILLIS), + null, actionExecutionResults = listOf(randomActionExecutionResult()) + ) + Assertions.assertFalse(auditAlert.isAcknowledged(), "Alert should not be in acknowledged state") + } + @Test fun `test chained alert`() { val workflow = randomWorkflow() From 55a4dd5769edf199716998d142c8c795df9c459e Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Wed, 21 Jun 2023 18:04:24 -0700 Subject: [PATCH 115/149] add workflowId field in alert (#463) (#464) Signed-off-by: Surya Sashank Nistala (cherry picked from commit 110bf0158417c3e06d08fafe6ebdccf900aecd0d) Co-authored-by: Surya Sashank Nistala --- .../commons/alerting/model/Alert.kt | 38 +++++++++++++------ .../opensearch/commons/alerting/AlertTests.kt | 1 + .../action/AcknowledgeAlertResponseTests.kt | 4 +- .../alerting/action/GetAlertsResponseTests.kt | 5 ++- 4 files changed, 33 insertions(+), 15 deletions(-) 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 1ae72401..667deb8c 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt @@ -22,6 +22,7 @@ data class Alert( val version: Long = NO_VERSION, val schemaVersion: Int = NO_SCHEMA_VERSION, val monitorId: String, + val workflowId: String, val monitorName: String, val monitorVersion: Long, val monitorUser: User?, @@ -48,6 +49,7 @@ data class Alert( } } + // constructor for chained alerts. constructor( startTime: Instant, lastNotificationTime: Instant?, @@ -63,7 +65,7 @@ data class Alert( lastNotificationTime = lastNotificationTime, errorMessage = errorMessage, errorHistory = emptyList(), severity = chainedAlertTrigger.severity, actionExecutionResults = emptyList(), schemaVersion = schemaVersion, aggregationResultBucket = null, findingIds = emptyList(), relatedDocIds = emptyList(), - executionId = executionId + executionId = executionId, workflowId = workflow.id ) constructor( @@ -76,14 +78,15 @@ data class Alert( errorHistory: List = mutableListOf(), actionExecutionResults: List = mutableListOf(), schemaVersion: Int = NO_SCHEMA_VERSION, - executionId: String? = null + executionId: String? = null, + workflow: Workflow? = 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 + executionId = executionId, workflowId = workflow?.id ?: "" ) constructor( @@ -97,14 +100,15 @@ data class Alert( actionExecutionResults: List = mutableListOf(), schemaVersion: Int = NO_SCHEMA_VERSION, findingIds: List = emptyList(), - executionId: String? = null + executionId: String? = null, + workflow: Workflow? = 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 + executionId = executionId, workflowId = workflow?.id ?: "" ) constructor( @@ -119,14 +123,15 @@ data class Alert( schemaVersion: Int = NO_SCHEMA_VERSION, aggregationResultBucket: AggregationResultBucket, findingIds: List = emptyList(), - executionId: String? = null + executionId: String? = null, + workflow: Workflow? = 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 + executionId = executionId, workflowId = workflow?.id ?: "" ) constructor( @@ -142,14 +147,15 @@ data class Alert( errorHistory: List = mutableListOf(), actionExecutionResults: List = mutableListOf(), schemaVersion: Int = NO_SCHEMA_VERSION, - executionId: String? = null + executionId: String? = null, + workflow: Workflow? = 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 + executionId = executionId, workflowId = workflow?.id ?: "" ) constructor( @@ -161,13 +167,15 @@ data class Alert( state: State = State.ERROR, errorMessage: String, errorHistory: List = mutableListOf(), - schemaVersion: Int = NO_SCHEMA_VERSION + schemaVersion: Int = NO_SCHEMA_VERSION, + workflow: Workflow? = 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 = listOf(), schemaVersion = schemaVersion, - aggregationResultBucket = null, findingIds = listOf(), relatedDocIds = listOf() + aggregationResultBucket = null, findingIds = listOf(), relatedDocIds = listOf(), + workflowId = workflow?.id ?: "" ) enum class State { @@ -185,6 +193,7 @@ data class Alert( version = sin.readLong(), schemaVersion = sin.readInt(), monitorId = sin.readString(), + workflowId = sin.readString(), monitorName = sin.readString(), monitorVersion = sin.readLong(), monitorUser = if (sin.readBoolean()) { @@ -215,6 +224,7 @@ data class Alert( out.writeLong(version) out.writeInt(schemaVersion) out.writeString(monitorId) + out.writeString(workflowId) out.writeString(monitorName) out.writeLong(monitorVersion) out.writeBoolean(monitorUser != null) @@ -247,6 +257,7 @@ data class Alert( const val SCHEMA_VERSION_FIELD = "schema_version" const val ALERT_VERSION_FIELD = "version" const val MONITOR_ID_FIELD = "monitor_id" + const val WORKFLOW_ID_FIELD = "workflow_id" const val MONITOR_VERSION_FIELD = "monitor_version" const val MONITOR_NAME_FIELD = "monitor_name" const val MONITOR_USER_FIELD = "monitor_user" @@ -274,6 +285,7 @@ data class Alert( @Throws(IOException::class) fun parse(xcp: XContentParser, id: String = NO_ID, version: Long = NO_VERSION): Alert { lateinit var monitorId: String + var workflowId = "" var schemaVersion = NO_SCHEMA_VERSION lateinit var monitorName: String var monitorVersion: Long = Versions.NOT_FOUND @@ -300,6 +312,7 @@ data class Alert( when (fieldName) { MONITOR_ID_FIELD -> monitorId = xcp.text() + WORKFLOW_ID_FIELD -> workflowId = xcp.text() SCHEMA_VERSION_FIELD -> schemaVersion = xcp.intValue() MONITOR_NAME_FIELD -> monitorName = xcp.text() MONITOR_VERSION_FIELD -> monitorVersion = xcp.longValue() @@ -360,7 +373,7 @@ data class Alert( lastNotificationTime = lastNotificationTime, acknowledgedTime = acknowledgedTime, errorMessage = errorMessage, errorHistory = errorHistory, severity = severity, actionExecutionResults = actionExecutionResults, aggregationResultBucket = aggAlertBucket, findingIds = findingIds, - relatedDocIds = relatedDocIds, executionId = executionId + relatedDocIds = relatedDocIds, executionId = executionId, workflowId = workflowId ) } @@ -383,6 +396,7 @@ data class Alert( .field(ALERT_ID_FIELD, id) .field(ALERT_VERSION_FIELD, version) .field(MONITOR_ID_FIELD, monitorId) + .field(WORKFLOW_ID_FIELD, workflowId) .field(SCHEMA_VERSION_FIELD, schemaVersion) .field(MONITOR_VERSION_FIELD, monitorVersion) .field(MONITOR_NAME_FIELD, monitorName) diff --git a/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt index d9579ccf..cf6a4947 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt @@ -77,5 +77,6 @@ class AlertTests { val alert = randomChainedAlert(workflow = workflow, trigger = trigger) assertEquals(alert.monitorId, "") assertEquals(alert.id, "") + assertEquals(workflow.id, alert.workflowId) } } 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 16d5ef9a..0b4fd699 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponseTests.kt @@ -18,7 +18,7 @@ class AcknowledgeAlertResponseTests { val acknowledged = mutableListOf( Alert( - "1234", 0L, 1, "monitor-1234", "test-monitor", 0L, randomUser(), + "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 @@ -26,7 +26,7 @@ class AcknowledgeAlertResponseTests { ) val failed = mutableListOf( Alert( - "1234", 0L, 1, "monitor-1234", "test-monitor", 0L, randomUser(), + "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 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 f04745d3..524b0aaa 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponseTests.kt @@ -36,6 +36,7 @@ class GetAlertsResponseTests { 0L, 0, "monitorId", + "workflowId", "monitorName", 0L, randomUser(), @@ -64,6 +65,7 @@ class GetAlertsResponseTests { assertEquals(1, newReq.alerts.size) assertEquals(alert, newReq.alerts[0]) assertEquals(1, newReq.totalAlerts) + assertEquals(newReq.alerts[0].workflowId, "workflowId") } @Test @@ -75,6 +77,7 @@ class GetAlertsResponseTests { 0L, 0, "monitorId", + "workflowId", "monitorName", 0L, null, @@ -96,7 +99,7 @@ class GetAlertsResponseTests { 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\"," + - "\"schema_version\":0,\"monitor_version\":0,\"monitor_name\":\"monitorName\"," + + "\"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() + From fca6d8edf565069f1c7aaf66e34f7e0574609f42 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Thu, 22 Jun 2023 16:24:49 -0700 Subject: [PATCH 116/149] pass workflow id in alert constructors (#465) (#466) Signed-off-by: Surya Sashank Nistala (cherry picked from commit 37e36b7780c463dfde10cc7e128e37db4f8c4630) Co-authored-by: Surya Sashank Nistala --- .../commons/alerting/model/Alert.kt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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 667deb8c..7bf6b538 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt @@ -79,14 +79,14 @@ data class Alert( actionExecutionResults: List = mutableListOf(), schemaVersion: Int = NO_SCHEMA_VERSION, executionId: String? = null, - workflow: Workflow? = 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 = workflow?.id ?: "" + executionId = executionId, workflowId = workflowId ?: "" ) constructor( @@ -101,14 +101,14 @@ data class Alert( schemaVersion: Int = NO_SCHEMA_VERSION, findingIds: List = emptyList(), executionId: String? = null, - workflow: Workflow? = 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 = workflow?.id ?: "" + executionId = executionId, workflowId = workflowId ?: "" ) constructor( @@ -124,14 +124,14 @@ data class Alert( aggregationResultBucket: AggregationResultBucket, findingIds: List = emptyList(), executionId: String? = null, - workflow: Workflow? = 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 = workflow?.id ?: "" + executionId = executionId, workflowId = workflowId ?: "" ) constructor( @@ -148,14 +148,14 @@ data class Alert( actionExecutionResults: List = mutableListOf(), schemaVersion: Int = NO_SCHEMA_VERSION, executionId: String? = null, - workflow: Workflow? = 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 = workflow?.id ?: "" + executionId = executionId, workflowId = workflowId ?: "" ) constructor( @@ -168,14 +168,14 @@ data class Alert( errorMessage: String, errorHistory: List = mutableListOf(), schemaVersion: Int = NO_SCHEMA_VERSION, - workflow: Workflow? = 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 = listOf(), schemaVersion = schemaVersion, aggregationResultBucket = null, findingIds = listOf(), relatedDocIds = listOf(), - workflowId = workflow?.id ?: "" + workflowId = workflowId ?: "" ) enum class State { From f4758795e8e29e5280fa57079a75d61bf6bba89f Mon Sep 17 00:00:00 2001 From: Surya Sashank Nistala Date: Thu, 6 Jul 2023 16:50:44 -0700 Subject: [PATCH 117/149] opensearch commons strings library dependency import (#474) * fix opensearch commons strings library dependency import Signed-off-by: Surya Sashank Nistala * gradle apply spotless check Signed-off-by: Surya Sashank Nistala --------- Signed-off-by: Surya Sashank Nistala --- src/main/java/org/opensearch/commons/InjectSecurity.java | 2 +- .../org/opensearch/commons/authuser/AuthUserRequestBuilder.java | 2 +- src/main/java/org/opensearch/commons/authuser/User.java | 2 +- .../commons/destination/message/LegacyBaseMessage.java | 2 +- .../commons/destination/message/LegacyChimeMessage.java | 2 +- .../commons/destination/message/LegacyCustomWebhookMessage.java | 2 +- .../commons/destination/message/LegacyEmailMessage.java | 2 +- .../commons/destination/message/LegacySNSMessage.java | 2 +- .../commons/destination/message/LegacySlackMessage.java | 2 +- src/main/java/org/opensearch/commons/destination/util/Util.java | 2 +- .../org/opensearch/commons/rest/SecureRestClientBuilder.java | 2 +- .../notifications/action/UpdateNotificationConfigRequest.kt | 2 +- .../org/opensearch/commons/notifications/model/Channel.kt | 2 +- .../opensearch/commons/notifications/model/ChannelMessage.kt | 2 +- .../kotlin/org/opensearch/commons/notifications/model/Chime.kt | 2 +- .../opensearch/commons/notifications/model/DeliveryStatus.kt | 2 +- .../kotlin/org/opensearch/commons/notifications/model/Email.kt | 2 +- .../org/opensearch/commons/notifications/model/EventSource.kt | 2 +- .../org/opensearch/commons/notifications/model/EventStatus.kt | 2 +- .../commons/notifications/model/NotificationConfig.kt | 2 +- .../commons/notifications/model/NotificationConfigInfo.kt | 2 +- .../org/opensearch/commons/notifications/model/SesAccount.kt | 2 +- .../kotlin/org/opensearch/commons/notifications/model/Slack.kt | 2 +- .../org/opensearch/commons/notifications/model/SmtpAccount.kt | 2 +- .../org/opensearch/commons/notifications/model/Webhook.kt | 2 +- src/test/java/org/opensearch/commons/authuser/UserTest.java | 2 +- 26 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/opensearch/commons/InjectSecurity.java b/src/main/java/org/opensearch/commons/InjectSecurity.java index c6d581f6..e6b283d9 100644 --- a/src/main/java/org/opensearch/commons/InjectSecurity.java +++ b/src/main/java/org/opensearch/commons/InjectSecurity.java @@ -14,10 +14,10 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.commons.authuser.User; +import org.opensearch.core.common.Strings; /** * For background jobs usage only. User or Roles injection can be done using transport layer only. diff --git a/src/main/java/org/opensearch/commons/authuser/AuthUserRequestBuilder.java b/src/main/java/org/opensearch/commons/authuser/AuthUserRequestBuilder.java index 32602744..19811b5b 100644 --- a/src/main/java/org/opensearch/commons/authuser/AuthUserRequestBuilder.java +++ b/src/main/java/org/opensearch/commons/authuser/AuthUserRequestBuilder.java @@ -7,8 +7,8 @@ import org.opensearch.client.Request; import org.opensearch.client.RequestOptions; -import org.opensearch.common.Strings; import org.opensearch.commons.ConfigConstants; +import org.opensearch.core.common.Strings; public class AuthUserRequestBuilder { private final String auth; diff --git a/src/main/java/org/opensearch/commons/authuser/User.java b/src/main/java/org/opensearch/commons/authuser/User.java index b271b371..d96d72a1 100644 --- a/src/main/java/org/opensearch/commons/authuser/User.java +++ b/src/main/java/org/opensearch/commons/authuser/User.java @@ -17,13 +17,13 @@ import org.apache.http.util.EntityUtils; import org.opensearch.client.Response; import org.opensearch.common.Nullable; -import org.opensearch.common.Strings; import org.opensearch.common.inject.internal.ToStringBuilder; import org.opensearch.common.io.stream.StreamInput; import org.opensearch.common.io.stream.StreamOutput; import org.opensearch.common.io.stream.Writeable; import org.opensearch.common.xcontent.XContentHelper; import org.opensearch.common.xcontent.json.JsonXContent; +import org.opensearch.core.common.Strings; import org.opensearch.core.xcontent.ToXContent; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.core.xcontent.XContentParser; diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyBaseMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacyBaseMessage.java index 96bd06ba..c38e0dba 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacyBaseMessage.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyBaseMessage.java @@ -11,10 +11,10 @@ import java.util.Map; import org.apache.http.client.utils.URIBuilder; -import org.opensearch.common.Strings; import org.opensearch.common.io.stream.StreamInput; import org.opensearch.common.io.stream.StreamOutput; import org.opensearch.common.io.stream.Writeable; +import org.opensearch.core.common.Strings; /** * This class holds the generic parameters required for a diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyChimeMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacyChimeMessage.java index 31b45d1f..52e18903 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacyChimeMessage.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyChimeMessage.java @@ -7,8 +7,8 @@ import java.io.IOException; -import org.opensearch.common.Strings; import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.core.common.Strings; /** * This class holds the contents of an Chime message diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java index dbc4b7df..a5bd186d 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java @@ -12,9 +12,9 @@ import org.apache.http.client.methods.HttpPatch; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; -import org.opensearch.common.Strings; import org.opensearch.common.io.stream.StreamInput; import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.core.common.Strings; /** * This class holds the content of an CustomWebhook message diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyEmailMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacyEmailMessage.java index 01810868..480ae76b 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacyEmailMessage.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyEmailMessage.java @@ -9,10 +9,10 @@ import java.net.URI; import java.util.List; -import org.opensearch.common.Strings; import org.opensearch.common.io.stream.StreamInput; import org.opensearch.common.io.stream.StreamOutput; import org.opensearch.commons.notifications.model.MethodType; +import org.opensearch.core.common.Strings; /** * This class holds the content of an CustomWebhook message diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacySNSMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacySNSMessage.java index f8cf6a5b..afef192c 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacySNSMessage.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacySNSMessage.java @@ -7,10 +7,10 @@ import java.io.IOException; -import org.opensearch.common.Strings; import org.opensearch.common.io.stream.StreamInput; import org.opensearch.common.io.stream.StreamOutput; import org.opensearch.commons.destination.util.Util; +import org.opensearch.core.common.Strings; /** * This class holds the content of an SNS message diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacySlackMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacySlackMessage.java index 02d30bb4..b42bd87e 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacySlackMessage.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacySlackMessage.java @@ -7,8 +7,8 @@ import java.io.IOException; -import org.opensearch.common.Strings; import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.core.common.Strings; /** * This class holds the content of an Slack message diff --git a/src/main/java/org/opensearch/commons/destination/util/Util.java b/src/main/java/org/opensearch/commons/destination/util/Util.java index 9cb65ccb..a97f04b2 100644 --- a/src/main/java/org/opensearch/commons/destination/util/Util.java +++ b/src/main/java/org/opensearch/commons/destination/util/Util.java @@ -7,8 +7,8 @@ import java.util.regex.Pattern; -import org.opensearch.common.Strings; import org.opensearch.common.ValidationException; +import org.opensearch.core.common.Strings; public class Util { private Util() {} diff --git a/src/main/java/org/opensearch/commons/rest/SecureRestClientBuilder.java b/src/main/java/org/opensearch/commons/rest/SecureRestClientBuilder.java index d4d33e97..1c747025 100644 --- a/src/main/java/org/opensearch/commons/rest/SecureRestClientBuilder.java +++ b/src/main/java/org/opensearch/commons/rest/SecureRestClientBuilder.java @@ -33,9 +33,9 @@ import org.opensearch.client.RestClient; import org.opensearch.client.RestClientBuilder; import org.opensearch.client.RestHighLevelClient; -import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; import org.opensearch.commons.ConfigConstants; +import org.opensearch.core.common.Strings; /** * Provides builder to create low-level and high-level REST client to make calls to OpenSearch. diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequest.kt index 414540d1..865e54f7 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequest.kt @@ -7,7 +7,6 @@ package org.opensearch.commons.notifications.action import org.opensearch.action.ActionRequest import org.opensearch.action.ActionRequestValidationException import org.opensearch.action.ValidateActions -import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable @@ -16,6 +15,7 @@ import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_TAG import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TAG import org.opensearch.commons.notifications.model.NotificationConfig import org.opensearch.commons.utils.logger +import org.opensearch.core.common.Strings import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.ToXContentObject import org.opensearch.core.xcontent.XContentBuilder diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Channel.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Channel.kt index 2345449b..3eb307bd 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Channel.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Channel.kt @@ -4,7 +4,6 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable @@ -15,6 +14,7 @@ import org.opensearch.commons.notifications.NotificationConstants.DESCRIPTION_TA import org.opensearch.commons.notifications.NotificationConstants.IS_ENABLED_TAG import org.opensearch.commons.notifications.NotificationConstants.NAME_TAG import org.opensearch.commons.utils.logger +import org.opensearch.core.common.Strings import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelMessage.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelMessage.kt index f0fd4aef..2183bc46 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelMessage.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelMessage.kt @@ -5,7 +5,6 @@ package org.opensearch.commons.notifications.model -import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable @@ -15,6 +14,7 @@ import org.opensearch.commons.notifications.NotificationConstants.HTML_DESCRIPTI import org.opensearch.commons.notifications.NotificationConstants.TEXT_DESCRIPTION_TAG import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger +import org.opensearch.core.common.Strings import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt index 3783c430..64ab4973 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt @@ -4,7 +4,6 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable @@ -12,6 +11,7 @@ import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.URL_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateUrl +import org.opensearch.core.common.Strings import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/DeliveryStatus.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/DeliveryStatus.kt index 1e15a6b7..7b4a427d 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/DeliveryStatus.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/DeliveryStatus.kt @@ -4,7 +4,6 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable @@ -12,6 +11,7 @@ import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.STATUS_CODE_TAG import org.opensearch.commons.notifications.NotificationConstants.STATUS_TEXT_TAG import org.opensearch.commons.utils.logger +import org.opensearch.core.common.Strings import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt index 52cc8c4c..ab6b95e6 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt @@ -4,7 +4,6 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable @@ -15,6 +14,7 @@ import org.opensearch.commons.notifications.NotificationConstants.RECIPIENT_LIST import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.objectList import org.opensearch.commons.utils.stringList +import org.opensearch.core.common.Strings import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt index a4625e0b..5d634d89 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt @@ -4,7 +4,6 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable @@ -15,6 +14,7 @@ import org.opensearch.commons.notifications.NotificationConstants.TAGS_TAG import org.opensearch.commons.notifications.NotificationConstants.TITLE_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.stringList +import org.opensearch.core.common.Strings import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt index 4ca07c2a..1fc0be11 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt @@ -5,7 +5,6 @@ package org.opensearch.commons.notifications.model -import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable @@ -18,6 +17,7 @@ import org.opensearch.commons.notifications.NotificationConstants.EMAIL_RECIPIEN import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.objectList +import org.opensearch.core.common.Strings import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt index 55e34808..cfbc8fa8 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt @@ -4,7 +4,6 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable @@ -18,6 +17,7 @@ import org.opensearch.commons.notifications.model.config.ConfigDataProperties.ge import org.opensearch.commons.notifications.model.config.ConfigDataProperties.validateConfigData import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger +import org.opensearch.core.common.Strings import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt index af83376e..e7126f1a 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt @@ -5,7 +5,6 @@ package org.opensearch.commons.notifications.model -import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable @@ -15,6 +14,7 @@ import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TAG import org.opensearch.commons.notifications.NotificationConstants.CREATED_TIME_TAG import org.opensearch.commons.notifications.NotificationConstants.UPDATED_TIME_TAG import org.opensearch.commons.utils.logger +import org.opensearch.core.common.Strings import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt index ce169fed..f45f217f 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt @@ -5,7 +5,6 @@ package org.opensearch.commons.notifications.model -import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable @@ -17,6 +16,7 @@ import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateEmail import org.opensearch.commons.utils.validateIamRoleArn +import org.opensearch.core.common.Strings import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt index aad4b7e7..da0d46a3 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt @@ -4,7 +4,6 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable @@ -12,6 +11,7 @@ import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.URL_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateUrl +import org.opensearch.core.common.Strings import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SmtpAccount.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SmtpAccount.kt index fc563bc7..2fd8aac2 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/SmtpAccount.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SmtpAccount.kt @@ -4,7 +4,6 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable @@ -15,6 +14,7 @@ import org.opensearch.commons.notifications.NotificationConstants.METHOD_TAG import org.opensearch.commons.notifications.NotificationConstants.PORT_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateEmail +import org.opensearch.core.common.Strings import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt index 806bf123..40d71790 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt @@ -4,7 +4,6 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.Strings import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.io.stream.StreamOutput import org.opensearch.common.io.stream.Writeable @@ -16,6 +15,7 @@ import org.opensearch.commons.utils.STRING_READER import org.opensearch.commons.utils.STRING_WRITER import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateUrl +import org.opensearch.core.common.Strings import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser diff --git a/src/test/java/org/opensearch/commons/authuser/UserTest.java b/src/test/java/org/opensearch/commons/authuser/UserTest.java index 4df30a44..3453d802 100644 --- a/src/test/java/org/opensearch/commons/authuser/UserTest.java +++ b/src/test/java/org/opensearch/commons/authuser/UserTest.java @@ -15,11 +15,11 @@ import java.util.Arrays; import org.junit.jupiter.api.Test; -import org.opensearch.common.Strings; import org.opensearch.common.io.stream.BytesStreamOutput; import org.opensearch.common.io.stream.StreamInput; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.common.Strings; public class UserTest { From 1bba7348cd73648f7c099754c3e2cd874a0872f1 Mon Sep 17 00:00:00 2001 From: Surya Sashank Nistala Date: Fri, 7 Jul 2023 14:09:01 -0700 Subject: [PATCH 118/149] 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) } From 180097413ce988d545e5ca09950ebe9b22ce9113 Mon Sep 17 00:00:00 2001 From: Hailong Cui Date: Mon, 10 Jul 2023 10:30:17 +0800 Subject: [PATCH 119/149] Modify triggers to push snapshots on all branches (#454) (#475) (cherry picked from commit 1484bff81af8496c93e5f3e9634cb93c1f948750) Signed-off-by: Sayali Gaikawad Signed-off-by: Hailong Cui Co-authored-by: Sayali Gaikawad <61760125+gaiksaya@users.noreply.github.com> --- .github/workflows/maven-publish.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index 944c248c..be57f77d 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -3,12 +3,10 @@ name: Publish snapshots to maven on: workflow_dispatch: push: - branches: [ - main - 1.* - 2.* - ] - + branches: + - main + - '[0-9]+.[0-9]+' + - '[0-9]+.x' jobs: build-and-publish-snapshots: strategy: From 90bea0c58f53d2a60b91f5fba6600a5d5edd9105 Mon Sep 17 00:00:00 2001 From: Surya Sashank Nistala Date: Mon, 10 Jul 2023 19:06:04 -0700 Subject: [PATCH 120/149] 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 --- .../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() From 0d7ba90553205a30576a05b21e28c3fc13a31a4c Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Tue, 11 Jul 2023 17:15:13 -0700 Subject: [PATCH 121/149] Implemented support for configuring a cluster metrics monitor to call cat/indices, and cat/shards. (#479) (#480) Signed-off-by: AWSHurneyt (cherry picked from commit f0622321fe20c886bc877b59c794e8192bb0c5dc) Co-authored-by: AWSHurneyt --- .../commons/alerting/model/ClusterMetricsInput.kt | 14 ++++++++++++++ .../org/opensearch/commons/alerting/TestHelpers.kt | 5 ++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt index bb658df3..ed522ef7 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt @@ -259,6 +259,13 @@ data class ClusterMetricsInput( val requiresPathParams: Boolean ) { BLANK("", "", "", false, false), + CAT_INDICES( + "/_cat/indices", + "/_cat/indices", + "", + true, + false + ), CAT_PENDING_TASKS( "/_cat/pending_tasks", "/_cat/pending_tasks", @@ -273,6 +280,13 @@ data class ClusterMetricsInput( true, false ), + CAT_SHARDS( + "/_cat/shards", + "/_cat/shards", + "", + true, + false + ), CAT_SNAPSHOTS( "/_cat/snapshots", "/_cat/snapshots", diff --git a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt index 49792cb2..c4a68d44 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt @@ -395,7 +395,10 @@ fun randomDocLevelMonitorInput( } fun randomClusterMetricsInput( - path: String = ClusterMetricsInput.ClusterMetricType.CLUSTER_HEALTH.defaultPath, + path: String = ClusterMetricsInput.ClusterMetricType.values() + .filter { it.defaultPath.isNotBlank() && !it.requiresPathParams } + .random() + .defaultPath, pathParams: String = "", url: String = "" ): ClusterMetricsInput { From ccc98783e409f3cbd3b9c55e63940f99b7e3a5d1 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Wed, 12 Jul 2023 09:17:38 -0700 Subject: [PATCH 122/149] Added 2.9 release notes. (#482) (#483) * Added 2.9 release notes. Signed-off-by: AWSHurneyt * Added 2.9 release notes. Signed-off-by: AWSHurneyt * Added 2.9 release notes. Signed-off-by: AWSHurneyt --------- Signed-off-by: AWSHurneyt (cherry picked from commit d21660f60a482c8e92a39c51457f1a21a6d22a24) Co-authored-by: AWSHurneyt --- ...arch-common-utils.release-notes-2.9.0.0.md | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 release-notes/opensearch-common-utils.release-notes-2.9.0.0.md diff --git a/release-notes/opensearch-common-utils.release-notes-2.9.0.0.md b/release-notes/opensearch-common-utils.release-notes-2.9.0.0.md new file mode 100644 index 00000000..c5f78503 --- /dev/null +++ b/release-notes/opensearch-common-utils.release-notes-2.9.0.0.md @@ -0,0 +1,26 @@ +## Version 2.9.0.0 2023-07-11 + +Compatible with OpenSearch 2.9.0 + +### Maintenance +* Increment version to 2.9.0-SNAPSHOT. ([#444](https://github.com/opensearch-project/common-utils/pull/444)) +* Modify triggers to push snapshots on all branches. ([#454](https://github.com/opensearch-project/common-utils/pull/454)) + +### Feature +* Adds Chained alerts triggers for workflows. ([#456](https://github.com/opensearch-project/common-utils/pull/456)) +* Acknowledge chained alert request for workflow. ([#459](https://github.com/opensearch-project/common-utils/pull/459)) +* Adds audit state in Alert. ([#461](https://github.com/opensearch-project/common-utils/pull/461)) +* Add workflowId field in alert. (([#463](https://github.com/opensearch-project/common-utils/pull/463)) +* APIs for get workflow alerts and acknowledge chained alerts. ([#472](https://github.com/opensearch-project/common-utils/pull/472)) +* Add auditDelegateMonitorAlerts flag. ([#476](https://github.com/opensearch-project/common-utils/pull/476)) +* Implemented support for configuring a cluster metrics monitor to call cat/indices, and cat/shards. ([#479](https://github.com/opensearch-project/common-utils/pull/479)) + + +### Bug Fixes +* OpenSearch commons strings library dependency import. ([#474](https://github.com/opensearch-project/common-utils/pull/474)) + +### Refactoring +* Pass workflow id in alert constructors. ([#465](https://github.com/opensearch-project/common-utils/pull/465)) + +### Documentation +* Added 2.9 release notes. ([#482](https://github.com/opensearch-project/common-utils/pull/482)) \ No newline at end of file From a00936976b9e789032381cb39331c47fdfe080f8 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Fri, 14 Jul 2023 12:12:08 -0700 Subject: [PATCH 123/149] Increment version to 2.10.0-SNAPSHOT (#485) Signed-off-by: opensearch-ci-bot Co-authored-by: opensearch-ci-bot --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6684a7cd..c82577c8 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "2.9.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.10.0-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") buildVersionQualifier = System.getProperty("build.version_qualifier", "") kotlin_version = System.getProperty("kotlin.version", "1.6.10") From 0062835e8b0e4ccb928d61a8271923504aa1ab79 Mon Sep 17 00:00:00 2001 From: Hailong Cui Date: Sat, 22 Jul 2023 09:12:02 +0800 Subject: [PATCH 124/149] Fix after core #8157 (#486) (#499) * Fix after core #8157 * Fix ktlint issue * Fix spotless java check --------- (cherry picked from commit 003071c50e5cf08f7489f4940e0f1e7092ade1d8) Signed-off-by: bowenlan-amzn Signed-off-by: Hailong Cui Co-authored-by: bowenlan-amzn --- build.gradle | 10 ++++++---- .../java/org/opensearch/commons/authuser/User.java | 8 ++++---- .../destination/message/LegacyBaseMessage.java | 6 +++--- .../destination/message/LegacyChimeMessage.java | 2 +- .../message/LegacyCustomWebhookMessage.java | 4 ++-- .../destination/message/LegacyEmailMessage.java | 4 ++-- .../destination/message/LegacySNSMessage.java | 4 ++-- .../destination/message/LegacySlackMessage.java | 2 +- .../destination/response/LegacyBaseResponse.java | 6 +++--- .../response/LegacyDestinationResponse.java | 4 ++-- .../commons/alerting/AlertingPluginInterface.kt | 4 ++-- .../alerting/action/AcknowledgeAlertRequest.kt | 4 ++-- .../alerting/action/AcknowledgeAlertResponse.kt | 4 ++-- .../action/AcknowledgeChainedAlertRequest.kt | 4 ++-- .../alerting/action/DeleteMonitorRequest.kt | 4 ++-- .../alerting/action/DeleteMonitorResponse.kt | 4 ++-- .../alerting/action/DeleteWorkflowRequest.kt | 4 ++-- .../alerting/action/DeleteWorkflowResponse.kt | 4 ++-- .../commons/alerting/action/GetAlertsRequest.kt | 4 ++-- .../commons/alerting/action/GetAlertsResponse.kt | 4 ++-- .../commons/alerting/action/GetFindingsRequest.kt | 4 ++-- .../commons/alerting/action/GetFindingsResponse.kt | 6 +++--- .../alerting/action/GetWorkflowAlertsRequest.kt | 4 ++-- .../alerting/action/GetWorkflowAlertsResponse.kt | 4 ++-- .../commons/alerting/action/GetWorkflowRequest.kt | 4 ++-- .../commons/alerting/action/GetWorkflowResponse.kt | 6 +++--- .../commons/alerting/action/IndexMonitorRequest.kt | 4 ++-- .../alerting/action/IndexMonitorResponse.kt | 4 ++-- .../alerting/action/IndexWorkflowRequest.kt | 12 ++++++------ .../alerting/action/IndexWorkflowResponse.kt | 4 ++-- .../alerting/action/PublishFindingsRequest.kt | 4 ++-- .../alerting/action/SubscribeFindingsResponse.kt | 6 +++--- .../BucketSelectorExtAggregationBuilder.kt | 6 +++--- .../BucketSelectorExtAggregator.kt | 8 ++++---- .../bucketselectorext/BucketSelectorExtFilter.kt | 6 +++--- .../bucketselectorext/BucketSelectorIndices.kt | 2 +- .../commons/alerting/alerts/AlertError.kt | 8 ++++---- .../alerting/model/ActionExecutionResult.kt | 8 ++++---- .../alerting/model/AggregationResultBucket.kt | 10 +++++----- .../org/opensearch/commons/alerting/model/Alert.kt | 8 ++++---- .../commons/alerting/model/BucketLevelTrigger.kt | 6 +++--- .../commons/alerting/model/ChainedAlertTrigger.kt | 6 +++--- .../alerting/model/ChainedMonitorFindings.kt | 6 +++--- .../commons/alerting/model/ClusterMetricsInput.kt | 6 +++--- .../commons/alerting/model/CompositeInput.kt | 6 +++--- .../commons/alerting/model/DataSources.kt | 8 ++++---- .../opensearch/commons/alerting/model/Delegate.kt | 6 +++--- .../commons/alerting/model/DocLevelMonitorInput.kt | 6 +++--- .../commons/alerting/model/DocLevelQuery.kt | 6 +++--- .../commons/alerting/model/DocumentLevelTrigger.kt | 6 +++--- .../opensearch/commons/alerting/model/Finding.kt | 8 ++++---- .../commons/alerting/model/FindingDocument.kt | 8 ++++---- .../commons/alerting/model/FindingWithDocs.kt | 8 ++++---- .../org/opensearch/commons/alerting/model/Input.kt | 4 ++-- .../opensearch/commons/alerting/model/Monitor.kt | 6 +++--- .../commons/alerting/model/NoOpTrigger.kt | 6 +++--- .../commons/alerting/model/QueryLevelTrigger.kt | 6 +++--- .../opensearch/commons/alerting/model/Schedule.kt | 6 +++--- .../commons/alerting/model/ScheduledJob.kt | 2 +- .../commons/alerting/model/SearchInput.kt | 6 +++--- .../opensearch/commons/alerting/model/Sequence.kt | 6 +++--- .../org/opensearch/commons/alerting/model/Table.kt | 6 +++--- .../opensearch/commons/alerting/model/Trigger.kt | 4 ++-- .../opensearch/commons/alerting/model/Workflow.kt | 6 +++--- .../commons/alerting/model/WorkflowInput.kt | 4 ++-- .../commons/alerting/model/action/Action.kt | 6 +++--- .../alerting/model/action/ActionExecutionPolicy.kt | 6 +++--- .../alerting/model/action/ActionExecutionScope.kt | 6 +++--- .../commons/alerting/model/action/Throttle.kt | 6 +++--- .../opensearch/commons/alerting/util/IndexUtils.kt | 4 ++-- .../notifications/NotificationsPluginInterface.kt | 2 +- .../commons/notifications/action/BaseResponse.kt | 4 ++-- .../action/CreateNotificationConfigRequest.kt | 8 ++++---- .../action/CreateNotificationConfigResponse.kt | 8 ++++---- .../action/DeleteNotificationConfigRequest.kt | 8 ++++---- .../action/DeleteNotificationConfigResponse.kt | 10 +++++----- .../notifications/action/GetChannelListRequest.kt | 8 ++++---- .../notifications/action/GetChannelListResponse.kt | 6 +++--- .../action/GetNotificationConfigRequest.kt | 8 ++++---- .../action/GetNotificationConfigResponse.kt | 6 +++--- .../action/GetPluginFeaturesRequest.kt | 8 ++++---- .../action/GetPluginFeaturesResponse.kt | 8 ++++---- .../action/LegacyPublishNotificationRequest.kt | 6 +++--- .../action/LegacyPublishNotificationResponse.kt | 6 +++--- .../action/SendNotificationRequest.kt | 8 ++++---- .../action/SendNotificationResponse.kt | 6 +++--- .../action/UpdateNotificationConfigRequest.kt | 8 ++++---- .../action/UpdateNotificationConfigResponse.kt | 8 ++++---- .../commons/notifications/model/Attachment.kt | 8 ++++---- .../commons/notifications/model/BaseModel.kt | 2 +- .../commons/notifications/model/Channel.kt | 8 ++++---- .../commons/notifications/model/ChannelList.kt | 2 +- .../commons/notifications/model/ChannelMessage.kt | 8 ++++---- .../commons/notifications/model/Chime.kt | 8 ++++---- .../commons/notifications/model/DeliveryStatus.kt | 8 ++++---- .../commons/notifications/model/Email.kt | 8 ++++---- .../commons/notifications/model/EmailGroup.kt | 8 ++++---- .../commons/notifications/model/EmailRecipient.kt | 8 ++++---- .../notifications/model/EmailRecipientStatus.kt | 8 ++++---- .../commons/notifications/model/EventSource.kt | 8 ++++---- .../commons/notifications/model/EventStatus.kt | 8 ++++---- .../notifications/model/NotificationConfig.kt | 8 ++++---- .../notifications/model/NotificationConfigInfo.kt | 8 ++++---- .../model/NotificationConfigSearchResult.kt | 2 +- .../notifications/model/NotificationEvent.kt | 8 ++++---- .../commons/notifications/model/SearchResults.kt | 8 ++++---- .../commons/notifications/model/SesAccount.kt | 8 ++++---- .../commons/notifications/model/Slack.kt | 8 ++++---- .../commons/notifications/model/SmtpAccount.kt | 8 ++++---- .../opensearch/commons/notifications/model/Sns.kt | 8 ++++---- .../commons/notifications/model/Webhook.kt | 8 ++++---- .../model/config/ConfigDataProperties.kt | 2 +- .../org/opensearch/commons/utils/EnumHelpers.kt | 6 +++--- .../opensearch/commons/utils/TransportHelpers.kt | 14 +++++++------- .../opensearch/commons/utils/XContentHelpers.kt | 4 ++-- .../org/opensearch/commons/authuser/UserTest.java | 2 +- .../message/LegacyChimeMessageTest.java | 2 +- .../message/LegacyCustomWebhookMessageTest.java | 2 +- .../message/LegacyEmailMessageTest.java | 2 +- .../message/LegacySlackMessageTest.java | 2 +- .../response/LegacyDestinationResponseTest.java | 2 +- .../alerting/AlertingPluginInterfaceTests.kt | 4 ++-- .../action/AcknowledgeAlertRequestTests.kt | 2 +- .../action/AcknowledgeAlertResponseTests.kt | 2 +- .../action/AcknowledgeChainedAlertRequestTests.kt | 2 +- .../alerting/action/DeleteMonitorRequestTests.kt | 2 +- .../alerting/action/DeleteWorkflowRequestTests.kt | 2 +- .../alerting/action/GetAlertsRequestTests.kt | 2 +- .../alerting/action/GetAlertsResponseTests.kt | 2 +- .../alerting/action/GetFindingsRequestTests.kt | 2 +- .../alerting/action/GetFindingsResponseTests.kt | 4 ++-- .../action/GetWorkflowAlertsRequestTests.kt | 2 +- .../action/GetWorkflowAlertsResponseTests.kt | 2 +- .../alerting/action/GetWorkflowResponseTests.kt | 4 ++-- .../alerting/action/IndexMonitorRequestTests.kt | 6 +++--- .../alerting/action/IndexMonitorResponseTests.kt | 2 +- .../alerting/action/IndexWorkflowRequestTests.kt | 6 +++--- .../alerting/action/IndexWorkflowResponseTests.kt | 2 +- .../alerting/action/PublishFindingsRequestTests.kt | 2 +- .../commons/alerting/model/MockScheduledJob.kt | 2 +- .../commons/alerting/model/WriteableTests.kt | 2 +- .../NotificationsPluginInterfaceTests.kt | 2 +- .../DeleteNotificationConfigResponseTests.kt | 2 +- 143 files changed, 387 insertions(+), 385 deletions(-) diff --git a/build.gradle b/build.gradle index c82577c8..50d3732e 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { opensearch_version = System.getProperty("opensearch.version", "2.10.0-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") buildVersionQualifier = System.getProperty("build.version_qualifier", "") - kotlin_version = System.getProperty("kotlin.version", "1.6.10") + kotlin_version = System.getProperty("kotlin.version", "1.8.21") } repositories { @@ -114,7 +114,7 @@ spotless { buildUponDefaultConfig = true }*/ -task ktlint(type: JavaExec, group: "verification") { +tasks.register('ktlint', JavaExec) { description = "Check Kotlin code style." main = "com.pinterest.ktlint.Main" classpath = configurations.ktlint @@ -126,10 +126,12 @@ task ktlint(type: JavaExec, group: "verification") { check.dependsOn ktlint -task ktlintFormat(type: JavaExec, group: "formatting") { +tasks.register('ktlintFormat', JavaExec) { description = "Fix Kotlin code style deviations." - main = "com.pinterest.ktlint.Main" classpath = configurations.ktlint + // https://github.com/pinterest/ktlint/issues/1391#issuecomment-1251287020 + jvmArgs "--add-opens=java.base/java.lang=ALL-UNNAMED" + setProperty("mainClass", "com.pinterest.ktlint.Main") args "-F", "src/**/*.kt" } diff --git a/src/main/java/org/opensearch/commons/authuser/User.java b/src/main/java/org/opensearch/commons/authuser/User.java index d96d72a1..ff40e243 100644 --- a/src/main/java/org/opensearch/commons/authuser/User.java +++ b/src/main/java/org/opensearch/commons/authuser/User.java @@ -5,7 +5,7 @@ package org.opensearch.commons.authuser; -import static org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken; +import static org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken; import java.io.IOException; import java.util.ArrayList; @@ -18,12 +18,12 @@ import org.opensearch.client.Response; import org.opensearch.common.Nullable; import org.opensearch.common.inject.internal.ToStringBuilder; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; -import org.opensearch.common.io.stream.Writeable; import org.opensearch.common.xcontent.XContentHelper; import org.opensearch.common.xcontent.json.JsonXContent; import org.opensearch.core.common.Strings; +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.core.xcontent.ToXContent; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.core.xcontent.XContentParser; diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyBaseMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacyBaseMessage.java index c38e0dba..580377d4 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacyBaseMessage.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyBaseMessage.java @@ -11,10 +11,10 @@ import java.util.Map; import org.apache.http.client.utils.URIBuilder; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; -import org.opensearch.common.io.stream.Writeable; import org.opensearch.core.common.Strings; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.common.io.stream.Writeable; /** * This class holds the generic parameters required for a diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyChimeMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacyChimeMessage.java index 52e18903..cbdadb65 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacyChimeMessage.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyChimeMessage.java @@ -7,8 +7,8 @@ import java.io.IOException; -import org.opensearch.common.io.stream.StreamInput; import org.opensearch.core.common.Strings; +import org.opensearch.core.common.io.stream.StreamInput; /** * This class holds the contents of an Chime message diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java index a5bd186d..08058d0f 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java @@ -12,9 +12,9 @@ import org.apache.http.client.methods.HttpPatch; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; import org.opensearch.core.common.Strings; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; /** * This class holds the content of an CustomWebhook message diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyEmailMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacyEmailMessage.java index 480ae76b..b0fbc7ec 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacyEmailMessage.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyEmailMessage.java @@ -9,10 +9,10 @@ import java.net.URI; import java.util.List; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; import org.opensearch.commons.notifications.model.MethodType; import org.opensearch.core.common.Strings; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; /** * This class holds the content of an CustomWebhook message diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacySNSMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacySNSMessage.java index afef192c..8fc7a554 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacySNSMessage.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacySNSMessage.java @@ -7,10 +7,10 @@ import java.io.IOException; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; import org.opensearch.commons.destination.util.Util; import org.opensearch.core.common.Strings; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; /** * This class holds the content of an SNS message diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacySlackMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacySlackMessage.java index b42bd87e..48cc842a 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacySlackMessage.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacySlackMessage.java @@ -7,8 +7,8 @@ import java.io.IOException; -import org.opensearch.common.io.stream.StreamInput; import org.opensearch.core.common.Strings; +import org.opensearch.core.common.io.stream.StreamInput; /** * This class holds the content of an Slack message diff --git a/src/main/java/org/opensearch/commons/destination/response/LegacyBaseResponse.java b/src/main/java/org/opensearch/commons/destination/response/LegacyBaseResponse.java index 4d34b67f..218cb89a 100644 --- a/src/main/java/org/opensearch/commons/destination/response/LegacyBaseResponse.java +++ b/src/main/java/org/opensearch/commons/destination/response/LegacyBaseResponse.java @@ -7,9 +7,9 @@ import java.io.IOException; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; -import org.opensearch.common.io.stream.Writeable; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.common.io.stream.Writeable; /** * This class holds the generic response attributes diff --git a/src/main/java/org/opensearch/commons/destination/response/LegacyDestinationResponse.java b/src/main/java/org/opensearch/commons/destination/response/LegacyDestinationResponse.java index d55f5f3d..fd6467f6 100644 --- a/src/main/java/org/opensearch/commons/destination/response/LegacyDestinationResponse.java +++ b/src/main/java/org/opensearch/commons/destination/response/LegacyDestinationResponse.java @@ -7,8 +7,8 @@ import java.io.IOException; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; /** * This class is a place holder for destination response metadata diff --git a/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt index ea2f1391..c905d2b3 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt @@ -7,8 +7,6 @@ package org.opensearch.commons.alerting import org.opensearch.action.ActionListener import org.opensearch.action.ActionResponse import org.opensearch.client.node.NodeClient -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 @@ -33,6 +31,8 @@ import org.opensearch.commons.alerting.action.PublishFindingsRequest import org.opensearch.commons.alerting.action.SubscribeFindingsResponse import org.opensearch.commons.notifications.action.BaseResponse import org.opensearch.commons.utils.recreateObject +import org.opensearch.core.common.io.stream.NamedWriteableRegistry +import org.opensearch.core.common.io.stream.Writeable /** * All the transport action plugin interfaces for the Alerting plugin diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertRequest.kt index 2576458e..01f3fdd2 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertRequest.kt @@ -8,8 +8,8 @@ package org.opensearch.commons.alerting.action import org.opensearch.action.ActionRequest import org.opensearch.action.ActionRequestValidationException import org.opensearch.action.support.WriteRequest -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput import java.io.IOException import java.util.Collections diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponse.kt index 858ad4be..38d81778 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponse.kt @@ -4,10 +4,10 @@ */ 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.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 java.io.IOException diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequest.kt index a89c6332..2c482f26 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequest.kt @@ -7,8 +7,8 @@ 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.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput import java.io.IOException import java.util.Collections diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorRequest.kt index 2d236c36..b0bd9992 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorRequest.kt @@ -3,8 +3,8 @@ package org.opensearch.commons.alerting.action import org.opensearch.action.ActionRequest import org.opensearch.action.ActionRequestValidationException import org.opensearch.action.support.WriteRequest -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput import java.io.IOException class DeleteMonitorRequest : ActionRequest { diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorResponse.kt index 3d9b9920..90ac17ba 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorResponse.kt @@ -1,9 +1,9 @@ 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.util.IndexUtils import org.opensearch.commons.notifications.action.BaseResponse +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 diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequest.kt index 4990f497..10189e89 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequest.kt @@ -2,8 +2,8 @@ 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.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput import java.io.IOException class DeleteWorkflowRequest : ActionRequest { diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowResponse.kt index 8da62c5e..3ccb9f41 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowResponse.kt @@ -1,9 +1,9 @@ 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.util.IndexUtils import org.opensearch.commons.notifications.action.BaseResponse +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 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 bfebdb2b..8b8a5a1f 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequest.kt @@ -2,9 +2,9 @@ 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 org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput import java.io.IOException class GetAlertsRequest : ActionRequest { diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponse.kt index 37ffaf61..50a4ec53 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponse.kt @@ -1,9 +1,9 @@ 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.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 java.io.IOException diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequest.kt index ae65ca26..0004d4f1 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequest.kt @@ -2,9 +2,9 @@ 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 org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput import java.io.IOException class GetFindingsRequest : ActionRequest { diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponse.kt index e0e0d06d..9caeebde 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponse.kt @@ -1,12 +1,12 @@ 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.FindingWithDocs import org.opensearch.commons.notifications.action.BaseResponse +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput +import org.opensearch.core.rest.RestStatus import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder -import org.opensearch.rest.RestStatus import java.io.IOException class GetFindingsResponse : BaseResponse { 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 9e2c8b2b..bfa80044 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequest.kt @@ -2,9 +2,9 @@ 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 org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput import java.io.IOException class GetWorkflowAlertsRequest : ActionRequest { diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsResponse.kt index b426f7e0..4856d747 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsResponse.kt @@ -1,9 +1,9 @@ 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.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 java.io.IOException diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowRequest.kt index 1b7948cd..cff81206 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowRequest.kt @@ -7,8 +7,8 @@ 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.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput import org.opensearch.rest.RestRequest import java.io.IOException diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponse.kt index f18550c5..67bad9d0 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponse.kt @@ -5,17 +5,17 @@ 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.Workflow import org.opensearch.commons.alerting.util.IndexUtils.Companion._ID import org.opensearch.commons.alerting.util.IndexUtils.Companion._PRIMARY_TERM import org.opensearch.commons.alerting.util.IndexUtils.Companion._SEQ_NO import org.opensearch.commons.alerting.util.IndexUtils.Companion._VERSION import org.opensearch.commons.notifications.action.BaseResponse +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput +import org.opensearch.core.rest.RestStatus import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder -import org.opensearch.rest.RestStatus import java.io.IOException class GetWorkflowResponse : BaseResponse { diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequest.kt index 8a7f4b87..34e8d314 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequest.kt @@ -3,9 +3,9 @@ package org.opensearch.commons.alerting.action import org.opensearch.action.ActionRequest import org.opensearch.action.ActionRequestValidationException import org.opensearch.action.support.WriteRequest -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput import org.opensearch.commons.alerting.model.Monitor +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput import org.opensearch.rest.RestRequest import java.io.IOException diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorResponse.kt index f0ddda17..0464bff8 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexMonitorResponse.kt @@ -1,13 +1,13 @@ 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.Monitor import org.opensearch.commons.alerting.util.IndexUtils.Companion._ID import org.opensearch.commons.alerting.util.IndexUtils.Companion._PRIMARY_TERM import org.opensearch.commons.alerting.util.IndexUtils.Companion._SEQ_NO import org.opensearch.commons.alerting.util.IndexUtils.Companion._VERSION import org.opensearch.commons.notifications.action.BaseResponse +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 java.io.IOException diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequest.kt index 15a895ca..dc2b0936 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequest.kt @@ -4,10 +4,10 @@ import org.opensearch.action.ActionRequest import org.opensearch.action.ActionRequestValidationException import org.opensearch.action.ValidateActions import org.opensearch.action.support.WriteRequest -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput import org.opensearch.commons.alerting.model.CompositeInput import org.opensearch.commons.alerting.model.Workflow +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput import org.opensearch.rest.RestRequest import java.io.IOException import java.util.stream.Collectors @@ -105,17 +105,17 @@ class IndexWorkflowRequest : ActionRequest { val monitorIdOrderMap: Map = delegates.associate { it.monitorId to it.order } delegates.forEach { if (it.chainedMonitorFindings != null) { - if (monitorIdOrderMap.containsKey(it.chainedMonitorFindings!!.monitorId) == false) { + if (monitorIdOrderMap.containsKey(it.chainedMonitorFindings.monitorId) == false) { validationException = ValidateActions.addValidationError( - "Chained Findings Monitor ${it.chainedMonitorFindings!!.monitorId} doesn't exist in sequence", + "Chained Findings Monitor ${it.chainedMonitorFindings.monitorId} doesn't exist in sequence", validationException ) // Break the flow because next check will generate the NPE return validationException } - if (it.order <= monitorIdOrderMap[it.chainedMonitorFindings!!.monitorId]!!) { + if (it.order <= monitorIdOrderMap[it.chainedMonitorFindings.monitorId]!!) { validationException = ValidateActions.addValidationError( - "Chained Findings Monitor ${it.chainedMonitorFindings!!.monitorId} should be executed before monitor ${it.monitorId}", + "Chained Findings Monitor ${it.chainedMonitorFindings.monitorId} should be executed before monitor ${it.monitorId}", validationException ) } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowResponse.kt index 89863ba5..b54d0d85 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowResponse.kt @@ -1,10 +1,10 @@ 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.Workflow import org.opensearch.commons.alerting.util.IndexUtils import org.opensearch.commons.notifications.action.BaseResponse +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 java.io.IOException diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/PublishFindingsRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/PublishFindingsRequest.kt index 7118ec44..6e8f78f1 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/PublishFindingsRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/PublishFindingsRequest.kt @@ -2,9 +2,9 @@ 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.Finding +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput import java.io.IOException class PublishFindingsRequest : ActionRequest { diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/SubscribeFindingsResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/SubscribeFindingsResponse.kt index 494cb701..58419d7c 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/SubscribeFindingsResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/SubscribeFindingsResponse.kt @@ -1,11 +1,11 @@ package org.opensearch.commons.alerting.action -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput import org.opensearch.commons.notifications.action.BaseResponse +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput +import org.opensearch.core.rest.RestStatus import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder -import org.opensearch.rest.RestStatus import java.io.IOException class SubscribeFindingsResponse : BaseResponse { diff --git a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregationBuilder.kt b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregationBuilder.kt index 37c13350..75d1f3ae 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregationBuilder.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregationBuilder.kt @@ -1,11 +1,11 @@ package org.opensearch.commons.alerting.aggregation.bucketselectorext -import org.opensearch.common.ParsingException -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput import org.opensearch.commons.alerting.aggregation.bucketselectorext.BucketSelectorExtFilter.Companion.BUCKET_SELECTOR_COMPOSITE_AGG_FILTER import org.opensearch.commons.alerting.aggregation.bucketselectorext.BucketSelectorExtFilter.Companion.BUCKET_SELECTOR_FILTER import org.opensearch.core.ParseField +import org.opensearch.core.common.ParsingException +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 diff --git a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregator.kt b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregator.kt index c0e48b41..133ebc89 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregator.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregator.kt @@ -1,9 +1,9 @@ package org.opensearch.commons.alerting.aggregation.bucketselectorext import org.apache.lucene.util.BytesRef -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput import org.opensearch.commons.alerting.aggregation.bucketselectorext.BucketSelectorExtAggregationBuilder.Companion.NAME +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput import org.opensearch.script.BucketAggregationSelectorScript import org.opensearch.script.Script import org.opensearch.search.DocValueFormat @@ -33,7 +33,7 @@ class BucketSelectorExtAggregator : SiblingPipelineAggregator { script: Script, gapPolicy: BucketHelpers.GapPolicy, filter: BucketSelectorExtFilter?, - metadata: Map? + metadata: Map?, ) : super(name, bucketsPathsMap.values.toTypedArray(), metadata) { this.bucketsPathsMap = bucketsPathsMap this.parentBucketPath = parentBucketPath @@ -132,7 +132,7 @@ class BucketSelectorExtAggregator : SiblingPipelineAggregator { name(), parentBucketPath, selectedBucketsIndex, - originalAgg.metadata + originalAgg.metadata, ) } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtFilter.kt b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtFilter.kt index e971deb1..3d9466eb 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtFilter.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtFilter.kt @@ -1,10 +1,10 @@ package org.opensearch.commons.alerting.aggregation.bucketselectorext -import org.opensearch.common.ParsingException -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput import org.opensearch.commons.notifications.model.BaseModel import org.opensearch.core.ParseField +import org.opensearch.core.common.ParsingException +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 diff --git a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorIndices.kt b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorIndices.kt index 94790987..e2352be9 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorIndices.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorIndices.kt @@ -1,6 +1,6 @@ package org.opensearch.commons.alerting.aggregation.bucketselectorext -import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.core.common.io.stream.StreamOutput import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.search.aggregations.InternalAggregation diff --git a/src/main/kotlin/org/opensearch/commons/alerting/alerts/AlertError.kt b/src/main/kotlin/org/opensearch/commons/alerting/alerts/AlertError.kt index 11c8ae17..3959187b 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/alerts/AlertError.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/alerts/AlertError.kt @@ -1,14 +1,14 @@ package org.opensearch.commons.alerting.alerts -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken import org.opensearch.commons.alerting.util.instant import org.opensearch.commons.alerting.util.optionalTimeField +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.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken import java.io.IOException import java.time.Instant diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/ActionExecutionResult.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/ActionExecutionResult.kt index 84fd292b..6d4614e8 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/ActionExecutionResult.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/ActionExecutionResult.kt @@ -1,15 +1,15 @@ package org.opensearch.commons.alerting.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.alerting.util.instant import org.opensearch.commons.alerting.util.optionalTimeField +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.core.xcontent.ToXContent import org.opensearch.core.xcontent.ToXContentObject import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils import java.io.IOException import java.time.Instant diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/AggregationResultBucket.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/AggregationResultBucket.kt index 97a48afb..2d36a51c 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/AggregationResultBucket.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/AggregationResultBucket.kt @@ -1,15 +1,15 @@ package org.opensearch.commons.alerting.model -import org.opensearch.common.ParsingException -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken +import org.opensearch.core.common.ParsingException +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.core.xcontent.ToXContent import org.opensearch.core.xcontent.ToXContentObject import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser import org.opensearch.core.xcontent.XContentParser.Token +import org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken import java.io.IOException import java.util.Locale 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 626d2133..04df1b28 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt @@ -1,19 +1,19 @@ package org.opensearch.commons.alerting.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable import org.opensearch.common.lucene.uid.Versions -import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken import org.opensearch.commons.alerting.alerts.AlertError import org.opensearch.commons.alerting.util.IndexUtils.Companion.NO_SCHEMA_VERSION import org.opensearch.commons.alerting.util.instant import org.opensearch.commons.alerting.util.optionalTimeField import org.opensearch.commons.alerting.util.optionalUserField 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.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken import java.io.IOException import java.time.Instant diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/BucketLevelTrigger.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/BucketLevelTrigger.kt index 38af9d3a..59dbae7f 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/BucketLevelTrigger.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/BucketLevelTrigger.kt @@ -2,9 +2,6 @@ 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.aggregation.bucketselectorext.BucketSelectorExtAggregationBuilder import org.opensearch.commons.alerting.model.Trigger.Companion.ACTIONS_FIELD import org.opensearch.commons.alerting.model.Trigger.Companion.ID_FIELD @@ -12,10 +9,13 @@ import org.opensearch.commons.alerting.model.Trigger.Companion.NAME_FIELD import org.opensearch.commons.alerting.model.Trigger.Companion.SEVERITY_FIELD import org.opensearch.commons.alerting.model.action.Action import org.opensearch.core.ParseField +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 data class BucketLevelTrigger( diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/ChainedAlertTrigger.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/ChainedAlertTrigger.kt index debc4ca6..c56ce856 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/ChainedAlertTrigger.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/ChainedAlertTrigger.kt @@ -2,19 +2,19 @@ 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.Trigger.Companion.ACTIONS_FIELD import org.opensearch.commons.alerting.model.Trigger.Companion.ID_FIELD import org.opensearch.commons.alerting.model.Trigger.Companion.NAME_FIELD import org.opensearch.commons.alerting.model.Trigger.Companion.SEVERITY_FIELD import org.opensearch.commons.alerting.model.action.Action import org.opensearch.core.ParseField +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 org.opensearch.script.Script import java.io.IOException diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/ChainedMonitorFindings.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/ChainedMonitorFindings.kt index cf2bafd6..dbc15e34 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/ChainedMonitorFindings.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/ChainedMonitorFindings.kt @@ -1,13 +1,13 @@ package org.opensearch.commons.alerting.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.model.BaseModel import org.opensearch.commons.utils.validateId +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 /** diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt index ed522ef7..81432546 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt @@ -3,14 +3,14 @@ package org.opensearch.commons.alerting.model import org.apache.commons.validator.routines.UrlValidator import org.apache.http.client.utils.URIBuilder import org.opensearch.common.CheckedFunction -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.core.ParseField +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.net.URI diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/CompositeInput.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/CompositeInput.kt index 229f20e2..7487232b 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/CompositeInput.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/CompositeInput.kt @@ -1,14 +1,14 @@ package org.opensearch.commons.alerting.model import org.opensearch.common.CheckedFunction -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.core.ParseField +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 data class CompositeInput( diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt index 64c2c819..b922a706 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt @@ -1,13 +1,13 @@ package org.opensearch.commons.alerting.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils +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.core.xcontent.ToXContent import org.opensearch.core.xcontent.ToXContentObject import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils import java.io.IOException data class DataSources( diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Delegate.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Delegate.kt index 65158a68..7b36ed88 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Delegate.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Delegate.kt @@ -1,13 +1,13 @@ package org.opensearch.commons.alerting.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.model.BaseModel import org.opensearch.commons.utils.validateId +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 /** diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelMonitorInput.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelMonitorInput.kt index b16d0c08..4ed95cdb 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelMonitorInput.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelMonitorInput.kt @@ -1,14 +1,14 @@ package org.opensearch.commons.alerting.model import org.opensearch.common.CheckedFunction -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.core.ParseField +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 data class DocLevelMonitorInput( diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt index 5d3749dd..a12b3240 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt @@ -1,12 +1,12 @@ package org.opensearch.commons.alerting.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.model.BaseModel +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.lang.IllegalArgumentException import java.util.UUID diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/DocumentLevelTrigger.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/DocumentLevelTrigger.kt index 0403f94c..df584234 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/DocumentLevelTrigger.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/DocumentLevelTrigger.kt @@ -2,19 +2,19 @@ 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.Trigger.Companion.ACTIONS_FIELD import org.opensearch.commons.alerting.model.Trigger.Companion.ID_FIELD import org.opensearch.commons.alerting.model.Trigger.Companion.NAME_FIELD import org.opensearch.commons.alerting.model.Trigger.Companion.SEVERITY_FIELD import org.opensearch.commons.alerting.model.action.Action import org.opensearch.core.ParseField +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 org.opensearch.script.Script import java.io.IOException diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt index 34e84994..16bf14f8 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt @@ -1,13 +1,13 @@ package org.opensearch.commons.alerting.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken import org.opensearch.commons.alerting.util.instant +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.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken import java.io.IOException import java.time.Instant diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/FindingDocument.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/FindingDocument.kt index 0017dc0e..202693fd 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/FindingDocument.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/FindingDocument.kt @@ -1,13 +1,13 @@ package org.opensearch.commons.alerting.model import org.apache.logging.log4j.LogManager -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils +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.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 private val log = LogManager.getLogger(FindingDocument::class.java) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/FindingWithDocs.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/FindingWithDocs.kt index 69505fc2..057e2214 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/FindingWithDocs.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/FindingWithDocs.kt @@ -1,13 +1,13 @@ package org.opensearch.commons.alerting.model import org.apache.logging.log4j.LogManager -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils +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.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 private val log = LogManager.getLogger(Finding::class.java) 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 927cb7e8..b3472f8a 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Input.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Input.kt @@ -1,12 +1,12 @@ package org.opensearch.commons.alerting.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.xcontent.XContentParserUtils 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.notifications.model.BaseModel +import org.opensearch.core.common.io.stream.StreamInput import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils import java.io.IOException interface Input : BaseModel { 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 cd993c18..1adfa958 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Monitor.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Monitor.kt @@ -1,9 +1,6 @@ package org.opensearch.commons.alerting.model import org.opensearch.common.CheckedFunction -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.util.IndexUtils.Companion.MONITOR_MAX_INPUTS import org.opensearch.commons.alerting.util.IndexUtils.Companion.MONITOR_MAX_TRIGGERS import org.opensearch.commons.alerting.util.IndexUtils.Companion.NO_SCHEMA_VERSION @@ -16,10 +13,13 @@ import org.opensearch.commons.alerting.util.optionalTimeField import org.opensearch.commons.alerting.util.optionalUserField import org.opensearch.commons.authuser.User import org.opensearch.core.ParseField +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.time.Instant import java.util.Locale 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 92456492..f0d08cbb 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt @@ -2,15 +2,15 @@ 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.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 data class NoOpTrigger( diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/QueryLevelTrigger.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/QueryLevelTrigger.kt index 421b15a9..0be93671 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/QueryLevelTrigger.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/QueryLevelTrigger.kt @@ -2,19 +2,19 @@ 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.Trigger.Companion.ACTIONS_FIELD import org.opensearch.commons.alerting.model.Trigger.Companion.ID_FIELD import org.opensearch.commons.alerting.model.Trigger.Companion.NAME_FIELD import org.opensearch.commons.alerting.model.Trigger.Companion.SEVERITY_FIELD import org.opensearch.commons.alerting.model.action.Action import org.opensearch.core.ParseField +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 org.opensearch.script.Script import java.io.IOException diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Schedule.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Schedule.kt index 1f6be01c..d82bc375 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Schedule.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Schedule.kt @@ -4,13 +4,13 @@ import com.cronutils.model.CronType import com.cronutils.model.definition.CronDefinitionBuilder import com.cronutils.model.time.ExecutionTime import com.cronutils.parser.CronParser -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.model.BaseModel +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.time.DateTimeException import java.time.Duration diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/ScheduledJob.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/ScheduledJob.kt index 3df83609..cf8417c2 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/ScheduledJob.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/ScheduledJob.kt @@ -1,10 +1,10 @@ package org.opensearch.commons.alerting.model -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.model.BaseModel 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.time.Instant diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/SearchInput.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/SearchInput.kt index 4fcbdd63..7579a10d 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/SearchInput.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/SearchInput.kt @@ -1,14 +1,14 @@ package org.opensearch.commons.alerting.model import org.opensearch.common.CheckedFunction -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.core.ParseField +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 org.opensearch.search.builder.SearchSourceBuilder import java.io.IOException diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Sequence.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Sequence.kt index 108f4004..22d4683b 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Sequence.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Sequence.kt @@ -1,12 +1,12 @@ package org.opensearch.commons.alerting.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.model.BaseModel +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 /** Delegate monitors passed as input for composite monitors. */ diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Table.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Table.kt index 66074d21..56d8a575 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Table.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Table.kt @@ -1,8 +1,8 @@ package org.opensearch.commons.alerting.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable +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 java.io.IOException data class Table( 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 fdc7ae80..1834f3b7 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Trigger.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Trigger.kt @@ -1,10 +1,10 @@ package org.opensearch.commons.alerting.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.alerting.model.action.Action import org.opensearch.commons.notifications.model.BaseModel +import org.opensearch.core.common.io.stream.StreamInput import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils import java.io.IOException interface Trigger : BaseModel { 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 a509f8d7..2038edfb 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Workflow.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Workflow.kt @@ -1,9 +1,6 @@ package org.opensearch.commons.alerting.model import org.opensearch.common.CheckedFunction -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.util.IndexUtils.Companion.NO_SCHEMA_VERSION import org.opensearch.commons.alerting.util.IndexUtils.Companion.WORKFLOW_MAX_INPUTS import org.opensearch.commons.alerting.util.IndexUtils.Companion._ID @@ -13,10 +10,13 @@ import org.opensearch.commons.alerting.util.optionalTimeField import org.opensearch.commons.alerting.util.optionalUserField import org.opensearch.commons.authuser.User import org.opensearch.core.ParseField +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.time.Instant import java.util.Locale diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/WorkflowInput.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/WorkflowInput.kt index bdead75a..d9fd2129 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/WorkflowInput.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/WorkflowInput.kt @@ -1,9 +1,9 @@ package org.opensearch.commons.alerting.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.model.BaseModel +import org.opensearch.core.common.io.stream.StreamInput import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils import java.io.IOException interface WorkflowInput : BaseModel { diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/action/Action.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/action/Action.kt index 3a51dec5..cad8a864 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/action/Action.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/action/Action.kt @@ -1,13 +1,13 @@ package org.opensearch.commons.alerting.model.action 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.notifications.model.BaseModel +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 org.opensearch.script.Script import java.io.IOException diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionPolicy.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionPolicy.kt index 214f49f4..024d9cb2 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionPolicy.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionPolicy.kt @@ -1,12 +1,12 @@ package org.opensearch.commons.alerting.model.action -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.model.BaseModel +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 data class ActionExecutionPolicy( diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionScope.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionScope.kt index 8455d6b7..4326f4b7 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionScope.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/action/ActionExecutionScope.kt @@ -1,12 +1,12 @@ package org.opensearch.commons.alerting.model.action -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.model.BaseModel +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.lang.IllegalArgumentException diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/action/Throttle.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/action/Throttle.kt index 2150d60f..79bdb6e3 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/action/Throttle.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/action/Throttle.kt @@ -1,13 +1,13 @@ package org.opensearch.commons.alerting.model.action import org.apache.commons.codec.binary.StringUtils -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.model.BaseModel +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.time.temporal.ChronoUnit import java.util.Locale diff --git a/src/main/kotlin/org/opensearch/commons/alerting/util/IndexUtils.kt b/src/main/kotlin/org/opensearch/commons/alerting/util/IndexUtils.kt index 51f9be52..2dbda47b 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/util/IndexUtils.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/util/IndexUtils.kt @@ -1,12 +1,12 @@ package org.opensearch.commons.alerting.util -import org.opensearch.common.bytes.BytesReference -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.alerting.model.Monitor import org.opensearch.commons.alerting.settings.SupportedClusterMetricsSettings import org.opensearch.commons.authuser.User +import org.opensearch.core.common.bytes.BytesReference import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils import java.time.Instant class IndexUtils { diff --git a/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt b/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt index c3baa938..59b25054 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt @@ -7,7 +7,6 @@ package org.opensearch.commons.notifications import org.opensearch.action.ActionListener import org.opensearch.action.ActionResponse import org.opensearch.client.node.NodeClient -import org.opensearch.common.io.stream.Writeable import org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_USER_INFO_THREAD_CONTEXT import org.opensearch.commons.notifications.action.BaseResponse import org.opensearch.commons.notifications.action.CreateNotificationConfigRequest @@ -38,6 +37,7 @@ import org.opensearch.commons.notifications.model.ChannelMessage import org.opensearch.commons.notifications.model.EventSource import org.opensearch.commons.utils.SecureClientWrapper import org.opensearch.commons.utils.recreateObject +import org.opensearch.core.common.io.stream.Writeable /** * All the transport action plugin interfaces for the Notification plugin diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/BaseResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/BaseResponse.kt index 8632d9ab..68837e9c 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/BaseResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/BaseResponse.kt @@ -6,9 +6,9 @@ package org.opensearch.commons.notifications.action import org.opensearch.action.ActionResponse -import org.opensearch.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.rest.RestStatus import org.opensearch.core.xcontent.ToXContentObject -import org.opensearch.rest.RestStatus import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequest.kt index 29d08084..dea36ef0 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequest.kt @@ -6,20 +6,20 @@ package org.opensearch.commons.notifications.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.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_TAG import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TAG import org.opensearch.commons.notifications.model.NotificationConfig import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateId +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.core.xcontent.ToXContent import org.opensearch.core.xcontent.ToXContentObject import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponse.kt index f91fb89f..3e32a49b 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponse.kt @@ -4,15 +4,15 @@ */ package org.opensearch.commons.notifications.action -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_TAG import org.opensearch.commons.utils.logger +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.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 /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequest.kt index 699274f1..6dd78923 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequest.kt @@ -7,17 +7,17 @@ package org.opensearch.commons.notifications.action import org.opensearch.action.ActionRequest import org.opensearch.action.ActionRequestValidationException import org.opensearch.action.ValidateActions -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_LIST_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.stringList +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.core.xcontent.ToXContent import org.opensearch.core.xcontent.ToXContentObject import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponse.kt index 71b1e7d9..2337355f 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponse.kt @@ -4,20 +4,20 @@ */ package org.opensearch.commons.notifications.action -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.DELETE_RESPONSE_LIST_TAG import org.opensearch.commons.utils.STRING_READER import org.opensearch.commons.utils.STRING_WRITER import org.opensearch.commons.utils.enumReader import org.opensearch.commons.utils.enumWriter import org.opensearch.commons.utils.logger +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.core.rest.RestStatus import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser -import org.opensearch.rest.RestStatus +import org.opensearch.core.xcontent.XContentParserUtils import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListRequest.kt index 8be96bfd..c62f38a7 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListRequest.kt @@ -6,16 +6,16 @@ package org.opensearch.commons.notifications.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.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.COMPACT_TAG import org.opensearch.commons.utils.logger +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.core.xcontent.ToXContent import org.opensearch.core.xcontent.ToXContentObject import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListResponse.kt index d91b4b77..57a94a4e 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetChannelListResponse.kt @@ -4,10 +4,10 @@ */ package org.opensearch.commons.notifications.action -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable import org.opensearch.commons.notifications.model.ChannelList +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.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequest.kt index c5ff31d8..34485a3c 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequest.kt @@ -7,10 +7,6 @@ package org.opensearch.commons.notifications.action import org.opensearch.action.ActionRequest import org.opensearch.action.ActionRequestValidationException import org.opensearch.action.ValidateActions -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_LIST_TAG import org.opensearch.commons.notifications.NotificationConstants.DEFAULT_MAX_ITEMS import org.opensearch.commons.notifications.NotificationConstants.FILTER_PARAM_LIST_TAG @@ -24,10 +20,14 @@ import org.opensearch.commons.utils.enumReader import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.stringList +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.core.xcontent.ToXContent import org.opensearch.core.xcontent.ToXContentObject import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils import org.opensearch.search.sort.SortOrder import java.io.IOException diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponse.kt index 8240d767..6723c015 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponse.kt @@ -4,10 +4,10 @@ */ package org.opensearch.commons.notifications.action -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable import org.opensearch.commons.notifications.model.NotificationConfigSearchResult +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.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequest.kt index 5e9b930d..0eb0d14d 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequest.kt @@ -6,16 +6,16 @@ package org.opensearch.commons.notifications.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.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.COMPACT_TAG import org.opensearch.commons.utils.logger +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.core.xcontent.ToXContent import org.opensearch.core.xcontent.ToXContentObject import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt index 6601ef67..41a80875 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt @@ -4,19 +4,19 @@ */ package org.opensearch.commons.notifications.action -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.ALLOWED_CONFIG_TYPE_LIST_TAG import org.opensearch.commons.notifications.NotificationConstants.PLUGIN_FEATURES_TAG import org.opensearch.commons.utils.STRING_READER import org.opensearch.commons.utils.STRING_WRITER import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.stringList +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.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 /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt index a4f8024d..7d8cf391 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt @@ -7,9 +7,6 @@ package org.opensearch.commons.notifications.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.common.io.stream.Writeable import org.opensearch.commons.destination.message.LegacyBaseMessage import org.opensearch.commons.destination.message.LegacyChimeMessage import org.opensearch.commons.destination.message.LegacyCustomWebhookMessage @@ -17,6 +14,9 @@ import org.opensearch.commons.destination.message.LegacyDestinationType import org.opensearch.commons.destination.message.LegacyEmailMessage import org.opensearch.commons.destination.message.LegacySNSMessage import org.opensearch.commons.destination.message.LegacySlackMessage +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 java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponse.kt index b32f9ac2..c6084358 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponse.kt @@ -5,10 +5,10 @@ package org.opensearch.commons.notifications.action -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable import org.opensearch.commons.destination.response.LegacyDestinationResponse +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.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import java.io.IOException diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequest.kt index da30648f..060ae545 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequest.kt @@ -7,10 +7,6 @@ package org.opensearch.commons.notifications.action import org.opensearch.action.ActionRequest import org.opensearch.action.ActionRequestValidationException import org.opensearch.action.ValidateActions -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CHANNEL_ID_LIST_TAG import org.opensearch.commons.notifications.NotificationConstants.CHANNEL_MESSAGE_TAG import org.opensearch.commons.notifications.NotificationConstants.EVENT_SOURCE_TAG @@ -20,10 +16,14 @@ import org.opensearch.commons.notifications.model.EventSource import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.stringList +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.core.xcontent.ToXContent import org.opensearch.core.xcontent.ToXContentObject import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponse.kt index 8feeed8d..e3642e1b 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponse.kt @@ -4,10 +4,10 @@ */ package org.opensearch.commons.notifications.action -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable import org.opensearch.commons.notifications.model.NotificationEvent +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.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequest.kt index 865e54f7..d28060aa 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequest.kt @@ -7,19 +7,19 @@ package org.opensearch.commons.notifications.action import org.opensearch.action.ActionRequest import org.opensearch.action.ActionRequestValidationException import org.opensearch.action.ValidateActions -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_TAG import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TAG import org.opensearch.commons.notifications.model.NotificationConfig import org.opensearch.commons.utils.logger import org.opensearch.core.common.Strings +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.core.xcontent.ToXContent import org.opensearch.core.xcontent.ToXContentObject import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponse.kt index 9d1f94ca..42e71a13 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponse.kt @@ -4,15 +4,15 @@ */ package org.opensearch.commons.notifications.action -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_TAG import org.opensearch.commons.utils.logger +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.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 /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Attachment.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Attachment.kt index 8c035071..ff650a20 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Attachment.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Attachment.kt @@ -5,19 +5,19 @@ package org.opensearch.commons.notifications.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.FILE_CONTENT_TYPE_TAG import org.opensearch.commons.notifications.NotificationConstants.FILE_DATA_TAG import org.opensearch.commons.notifications.NotificationConstants.FILE_ENCODING_TAG import org.opensearch.commons.notifications.NotificationConstants.FILE_NAME_TAG import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger +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.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils /** * Data class for storing attachment of channel message. diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/BaseModel.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/BaseModel.kt index 2f9e267c..2271fa7f 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/BaseModel.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/BaseModel.kt @@ -4,7 +4,7 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.io.stream.Writeable +import org.opensearch.core.common.io.stream.Writeable import org.opensearch.core.xcontent.ToXContentObject /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Channel.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Channel.kt index 3eb307bd..68f42b27 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Channel.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Channel.kt @@ -4,10 +4,6 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_TAG import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TYPE_TAG import org.opensearch.commons.notifications.NotificationConstants.DESCRIPTION_TAG @@ -15,9 +11,13 @@ import org.opensearch.commons.notifications.NotificationConstants.IS_ENABLED_TAG import org.opensearch.commons.notifications.NotificationConstants.NAME_TAG import org.opensearch.commons.utils.logger import org.opensearch.core.common.Strings +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.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 /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelList.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelList.kt index b7d3c5a7..b92b8425 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelList.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelList.kt @@ -7,8 +7,8 @@ package org.opensearch.commons.notifications.model import org.apache.lucene.search.TotalHits import org.opensearch.action.search.SearchResponse -import org.opensearch.common.io.stream.StreamInput import org.opensearch.commons.notifications.NotificationConstants.CHANNEL_LIST_TAG +import org.opensearch.core.common.io.stream.StreamInput import org.opensearch.core.xcontent.XContentParser /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelMessage.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelMessage.kt index 2183bc46..d733a8bc 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelMessage.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelMessage.kt @@ -5,19 +5,19 @@ package org.opensearch.commons.notifications.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.ATTACHMENT_TAG import org.opensearch.commons.notifications.NotificationConstants.HTML_DESCRIPTION_TAG import org.opensearch.commons.notifications.NotificationConstants.TEXT_DESCRIPTION_TAG import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger import org.opensearch.core.common.Strings +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.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 /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt index 64ab4973..ee1c5aa8 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt @@ -4,17 +4,17 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.URL_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateUrl import org.opensearch.core.common.Strings +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.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 /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/DeliveryStatus.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/DeliveryStatus.kt index 7b4a427d..2e25cc70 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/DeliveryStatus.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/DeliveryStatus.kt @@ -4,17 +4,17 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.STATUS_CODE_TAG import org.opensearch.commons.notifications.NotificationConstants.STATUS_TEXT_TAG import org.opensearch.commons.utils.logger import org.opensearch.core.common.Strings +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.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 /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt index ab6b95e6..a67619b0 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt @@ -4,10 +4,6 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.EMAIL_ACCOUNT_ID_TAG import org.opensearch.commons.notifications.NotificationConstants.EMAIL_GROUP_ID_LIST_TAG import org.opensearch.commons.notifications.NotificationConstants.RECIPIENT_LIST_TAG @@ -15,9 +11,13 @@ import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.objectList import org.opensearch.commons.utils.stringList import org.opensearch.core.common.Strings +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.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 /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailGroup.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailGroup.kt index 036f47ed..c5377530 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailGroup.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailGroup.kt @@ -4,16 +4,16 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.RECIPIENT_LIST_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.objectList +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.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 /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipient.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipient.kt index 732614e6..c601e840 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipient.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipient.kt @@ -4,16 +4,16 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.RECIPIENT_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateEmail +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.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 /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatus.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatus.kt index 9f70ba89..d1d78165 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatus.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatus.kt @@ -5,17 +5,17 @@ package org.opensearch.commons.notifications.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.DELIVERY_STATUS_TAG import org.opensearch.commons.notifications.NotificationConstants.RECIPIENT_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateEmail +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.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 /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt index 5d634d89..91deb445 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt @@ -4,10 +4,6 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.REFERENCE_ID_TAG import org.opensearch.commons.notifications.NotificationConstants.SEVERITY_TAG import org.opensearch.commons.notifications.NotificationConstants.TAGS_TAG @@ -15,9 +11,13 @@ import org.opensearch.commons.notifications.NotificationConstants.TITLE_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.stringList import org.opensearch.core.common.Strings +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.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 /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt index 1fc0be11..afb023ce 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt @@ -5,10 +5,6 @@ package org.opensearch.commons.notifications.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_TAG import org.opensearch.commons.notifications.NotificationConstants.CONFIG_NAME_TAG import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TYPE_TAG @@ -18,9 +14,13 @@ import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.objectList import org.opensearch.core.common.Strings +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.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 /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt index cfbc8fa8..5b0b8a90 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt @@ -4,10 +4,6 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TYPE_TAG import org.opensearch.commons.notifications.NotificationConstants.DESCRIPTION_TAG import org.opensearch.commons.notifications.NotificationConstants.IS_ENABLED_TAG @@ -18,9 +14,13 @@ import org.opensearch.commons.notifications.model.config.ConfigDataProperties.va import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger import org.opensearch.core.common.Strings +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.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 /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt index e7126f1a..34b6285b 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt @@ -5,19 +5,19 @@ package org.opensearch.commons.notifications.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_TAG import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TAG import org.opensearch.commons.notifications.NotificationConstants.CREATED_TIME_TAG import org.opensearch.commons.notifications.NotificationConstants.UPDATED_TIME_TAG import org.opensearch.commons.utils.logger import org.opensearch.core.common.Strings +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.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.time.Instant diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResult.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResult.kt index 50d18e07..7a44836e 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResult.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResult.kt @@ -7,8 +7,8 @@ package org.opensearch.commons.notifications.model import org.apache.lucene.search.TotalHits import org.opensearch.action.search.SearchResponse -import org.opensearch.common.io.stream.StreamInput import org.opensearch.commons.notifications.NotificationConstants.CONFIG_LIST_TAG +import org.opensearch.core.common.io.stream.StreamInput import org.opensearch.core.xcontent.XContentParser /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt index a03e0c52..87f637b0 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt @@ -4,20 +4,20 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable import org.opensearch.common.xcontent.XContentHelper -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.common.xcontent.XContentType import org.opensearch.commons.notifications.NotificationConstants.EVENT_SOURCE_TAG import org.opensearch.commons.notifications.NotificationConstants.STATUS_LIST_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.objectList +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.core.xcontent.ToXContent import org.opensearch.core.xcontent.ToXContent.EMPTY_PARAMS import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils import java.io.IOException /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SearchResults.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SearchResults.kt index a193f9a8..f6b3a295 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/SearchResults.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SearchResults.kt @@ -9,17 +9,17 @@ import org.apache.lucene.search.TotalHits.Relation import org.apache.lucene.search.TotalHits.Relation.EQUAL_TO import org.apache.lucene.search.TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO import org.opensearch.action.search.SearchResponse -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.START_INDEX_TAG import org.opensearch.commons.notifications.NotificationConstants.TOTAL_HITS_TAG import org.opensearch.commons.notifications.NotificationConstants.TOTAL_HIT_RELATION_TAG import org.opensearch.commons.utils.logger +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.core.xcontent.ToXContent.Params import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils import org.opensearch.search.SearchHit abstract class SearchResults : BaseModel { diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt index f45f217f..13370ed9 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt @@ -5,10 +5,6 @@ package org.opensearch.commons.notifications.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.FROM_ADDRESS_TAG import org.opensearch.commons.notifications.NotificationConstants.REGION_TAG import org.opensearch.commons.notifications.NotificationConstants.ROLE_ARN_TAG @@ -17,9 +13,13 @@ import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateEmail import org.opensearch.commons.utils.validateIamRoleArn import org.opensearch.core.common.Strings +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.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 /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt index da0d46a3..b4433b95 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt @@ -4,17 +4,17 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.URL_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateUrl import org.opensearch.core.common.Strings +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.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 /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SmtpAccount.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SmtpAccount.kt index 2fd8aac2..ca3a762a 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/SmtpAccount.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SmtpAccount.kt @@ -4,10 +4,6 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.FROM_ADDRESS_TAG import org.opensearch.commons.notifications.NotificationConstants.HOST_TAG import org.opensearch.commons.notifications.NotificationConstants.METHOD_TAG @@ -15,9 +11,13 @@ import org.opensearch.commons.notifications.NotificationConstants.PORT_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateEmail import org.opensearch.core.common.Strings +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.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 /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt index 82585ae6..d0809f9f 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt @@ -5,18 +5,18 @@ package org.opensearch.commons.notifications.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.ROLE_ARN_TAG import org.opensearch.commons.notifications.NotificationConstants.TOPIC_ARN_TAG import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateIamRoleArn +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.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.util.regex.Pattern diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt index 40d71790..e48f29f4 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt @@ -4,10 +4,6 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.HEADER_PARAMS_TAG import org.opensearch.commons.notifications.NotificationConstants.METHOD_TAG import org.opensearch.commons.notifications.NotificationConstants.URL_TAG @@ -16,9 +12,13 @@ import org.opensearch.commons.utils.STRING_WRITER import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateUrl import org.opensearch.core.common.Strings +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.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 /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt index 1fe4cae6..4d70b18f 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt @@ -4,7 +4,6 @@ */ package org.opensearch.commons.notifications.model.config -import org.opensearch.common.io.stream.Writeable.Reader import org.opensearch.commons.notifications.model.BaseConfigData import org.opensearch.commons.notifications.model.Chime import org.opensearch.commons.notifications.model.ConfigType @@ -16,6 +15,7 @@ import org.opensearch.commons.notifications.model.SmtpAccount import org.opensearch.commons.notifications.model.Sns import org.opensearch.commons.notifications.model.Webhook import org.opensearch.commons.notifications.model.XParser +import org.opensearch.core.common.io.stream.Writeable.Reader import org.opensearch.core.xcontent.XContentParser internal object ConfigDataProperties { diff --git a/src/main/kotlin/org/opensearch/commons/utils/EnumHelpers.kt b/src/main/kotlin/org/opensearch/commons/utils/EnumHelpers.kt index bb57dc50..6ffcdaed 100644 --- a/src/main/kotlin/org/opensearch/commons/utils/EnumHelpers.kt +++ b/src/main/kotlin/org/opensearch/commons/utils/EnumHelpers.kt @@ -4,10 +4,10 @@ */ package org.opensearch.commons.utils -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable -import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.core.common.io.stream.StreamOutput +import org.opensearch.core.common.io.stream.Writeable import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils import java.util.EnumSet inline fun > XContentParser.enumSet(enumParser: EnumParser): EnumSet { diff --git a/src/main/kotlin/org/opensearch/commons/utils/TransportHelpers.kt b/src/main/kotlin/org/opensearch/commons/utils/TransportHelpers.kt index cec7ba81..07d1ddb4 100644 --- a/src/main/kotlin/org/opensearch/commons/utils/TransportHelpers.kt +++ b/src/main/kotlin/org/opensearch/commons/utils/TransportHelpers.kt @@ -5,13 +5,13 @@ package org.opensearch.commons.utils -import org.opensearch.common.io.stream.InputStreamStreamInput -import org.opensearch.common.io.stream.NamedWriteableAwareStreamInput -import org.opensearch.common.io.stream.NamedWriteableRegistry -import org.opensearch.common.io.stream.OutputStreamStreamOutput -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.io.stream.StreamOutput -import org.opensearch.common.io.stream.Writeable +import org.opensearch.core.common.io.stream.InputStreamStreamInput +import org.opensearch.core.common.io.stream.NamedWriteableAwareStreamInput +import org.opensearch.core.common.io.stream.NamedWriteableRegistry +import org.opensearch.core.common.io.stream.OutputStreamStreamOutput +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 java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream diff --git a/src/main/kotlin/org/opensearch/commons/utils/XContentHelpers.kt b/src/main/kotlin/org/opensearch/commons/utils/XContentHelpers.kt index a049aefc..e2fe0ddf 100644 --- a/src/main/kotlin/org/opensearch/commons/utils/XContentHelpers.kt +++ b/src/main/kotlin/org/opensearch/commons/utils/XContentHelpers.kt @@ -5,15 +5,15 @@ package org.opensearch.commons.utils -import org.opensearch.common.io.stream.StreamInput -import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.common.xcontent.XContentType +import org.opensearch.core.common.io.stream.StreamInput import org.opensearch.core.xcontent.DeprecationHandler import org.opensearch.core.xcontent.NamedXContentRegistry import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.ToXContentObject import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils import org.opensearch.rest.RestRequest fun StreamInput.createJsonParser(): XContentParser { diff --git a/src/test/java/org/opensearch/commons/authuser/UserTest.java b/src/test/java/org/opensearch/commons/authuser/UserTest.java index 3453d802..56152477 100644 --- a/src/test/java/org/opensearch/commons/authuser/UserTest.java +++ b/src/test/java/org/opensearch/commons/authuser/UserTest.java @@ -16,10 +16,10 @@ import org.junit.jupiter.api.Test; import org.opensearch.common.io.stream.BytesStreamOutput; -import org.opensearch.common.io.stream.StreamInput; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.core.common.Strings; +import org.opensearch.core.common.io.stream.StreamInput; public class UserTest { diff --git a/src/test/java/org/opensearch/commons/destination/message/LegacyChimeMessageTest.java b/src/test/java/org/opensearch/commons/destination/message/LegacyChimeMessageTest.java index 191fef30..b5f57b46 100644 --- a/src/test/java/org/opensearch/commons/destination/message/LegacyChimeMessageTest.java +++ b/src/test/java/org/opensearch/commons/destination/message/LegacyChimeMessageTest.java @@ -12,7 +12,7 @@ import org.junit.jupiter.api.Test; import org.opensearch.common.io.stream.BytesStreamOutput; -import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamInput; public class LegacyChimeMessageTest { diff --git a/src/test/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessageTest.java b/src/test/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessageTest.java index 7443d060..2e68952c 100644 --- a/src/test/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessageTest.java +++ b/src/test/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessageTest.java @@ -16,7 +16,7 @@ import org.apache.http.client.methods.HttpPost; import org.junit.jupiter.api.Test; import org.opensearch.common.io.stream.BytesStreamOutput; -import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamInput; public class LegacyCustomWebhookMessageTest { diff --git a/src/test/java/org/opensearch/commons/destination/message/LegacyEmailMessageTest.java b/src/test/java/org/opensearch/commons/destination/message/LegacyEmailMessageTest.java index 06625053..bc4546fd 100644 --- a/src/test/java/org/opensearch/commons/destination/message/LegacyEmailMessageTest.java +++ b/src/test/java/org/opensearch/commons/destination/message/LegacyEmailMessageTest.java @@ -14,7 +14,7 @@ import org.junit.jupiter.api.Test; import org.opensearch.common.io.stream.BytesStreamOutput; -import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamInput; public class LegacyEmailMessageTest { diff --git a/src/test/java/org/opensearch/commons/destination/message/LegacySlackMessageTest.java b/src/test/java/org/opensearch/commons/destination/message/LegacySlackMessageTest.java index 6b04d651..2ea3ea59 100644 --- a/src/test/java/org/opensearch/commons/destination/message/LegacySlackMessageTest.java +++ b/src/test/java/org/opensearch/commons/destination/message/LegacySlackMessageTest.java @@ -12,7 +12,7 @@ import org.junit.jupiter.api.Test; import org.opensearch.common.io.stream.BytesStreamOutput; -import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamInput; public class LegacySlackMessageTest { diff --git a/src/test/java/org/opensearch/commons/destination/response/LegacyDestinationResponseTest.java b/src/test/java/org/opensearch/commons/destination/response/LegacyDestinationResponseTest.java index e9189d24..dc7cc079 100644 --- a/src/test/java/org/opensearch/commons/destination/response/LegacyDestinationResponseTest.java +++ b/src/test/java/org/opensearch/commons/destination/response/LegacyDestinationResponseTest.java @@ -12,7 +12,7 @@ import org.junit.jupiter.api.Test; import org.opensearch.common.io.stream.BytesStreamOutput; -import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamInput; public class LegacyDestinationResponseTest { diff --git a/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt index c9e2189a..b8aea1b8 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt @@ -12,7 +12,6 @@ import org.mockito.junit.jupiter.MockitoExtension import org.opensearch.action.ActionListener 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 @@ -37,8 +36,9 @@ import org.opensearch.commons.alerting.model.FindingDocument import org.opensearch.commons.alerting.model.FindingWithDocs import org.opensearch.commons.alerting.model.Monitor import org.opensearch.commons.alerting.model.Workflow +import org.opensearch.core.common.io.stream.NamedWriteableRegistry +import org.opensearch.core.rest.RestStatus import org.opensearch.index.seqno.SequenceNumbers -import org.opensearch.rest.RestStatus import org.opensearch.search.SearchModule @Suppress("UNCHECKED_CAST") diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertRequestTests.kt index a3be117a..cbd9e98a 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertRequestTests.kt @@ -10,7 +10,7 @@ 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 +import org.opensearch.core.common.io.stream.StreamInput class AcknowledgeAlertRequestTests { 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 7e22a1a3..5733c550 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponseTests.kt @@ -4,11 +4,11 @@ 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.alerts.AlertError import org.opensearch.commons.alerting.model.ActionExecutionResult import org.opensearch.commons.alerting.model.Alert import org.opensearch.commons.alerting.randomUser +import org.opensearch.core.common.io.stream.StreamInput import java.time.Instant class AcknowledgeAlertResponseTests { 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 3d571198..b624edfa 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequestTests.kt @@ -10,7 +10,7 @@ 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 +import org.opensearch.core.common.io.stream.StreamInput class AcknowledgeChainedAlertRequestTests { 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 0abd9d88..38c89fc5 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorRequestTests.kt @@ -5,7 +5,7 @@ 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 +import org.opensearch.core.common.io.stream.StreamInput class DeleteMonitorRequestTests { diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequestTests.kt index 20bcb27e..774be288 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequestTests.kt @@ -3,7 +3,7 @@ package org.opensearch.commons.alerting.action import org.junit.Assert import org.junit.Test import org.opensearch.common.io.stream.BytesStreamOutput -import org.opensearch.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamInput class DeleteWorkflowRequestTests { 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 a56a0f02..bf301f74 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequestTests.kt @@ -6,8 +6,8 @@ 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 +import org.opensearch.core.common.io.stream.StreamInput internal class GetAlertsRequestTests { 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 dc0aac1e..6a1cb877 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsResponseTests.kt @@ -5,11 +5,11 @@ 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.builder import org.opensearch.commons.alerting.model.Alert import org.opensearch.commons.alerting.randomUser import org.opensearch.commons.alerting.util.string +import org.opensearch.core.common.io.stream.StreamInput import org.opensearch.core.xcontent.ToXContent import java.time.Instant import java.util.Collections diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt index f83cb8de..253f4708 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt @@ -6,8 +6,8 @@ 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 +import org.opensearch.core.common.io.stream.StreamInput internal class GetFindingsRequestTests { diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt index e5b391bb..8d40492a 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt @@ -3,12 +3,12 @@ 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.DocLevelQuery import org.opensearch.commons.alerting.model.Finding import org.opensearch.commons.alerting.model.FindingDocument import org.opensearch.commons.alerting.model.FindingWithDocs -import org.opensearch.rest.RestStatus +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.rest.RestStatus import java.time.Instant internal class GetFindingsResponseTests { 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 da480fbc..1cf50eec 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequestTests.kt @@ -6,8 +6,8 @@ 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 +import org.opensearch.core.common.io.stream.StreamInput internal class GetWorkflowAlertsRequestTests { diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsResponseTests.kt index fd656d67..947fead8 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsResponseTests.kt @@ -5,13 +5,13 @@ 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.common.io.stream.StreamInput import org.opensearch.core.xcontent.ToXContent import java.time.Instant import java.util.Collections diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponseTests.kt index e1977357..8e78b61f 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponseTests.kt @@ -3,14 +3,14 @@ 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 org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.rest.RestStatus import java.time.Instant import java.time.temporal.ChronoUnit diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequestTests.kt index 34a1c334..20381c9b 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequestTests.kt @@ -4,14 +4,14 @@ import org.junit.jupiter.api.Assertions 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.NamedWriteableAwareStreamInput -import org.opensearch.common.io.stream.NamedWriteableRegistry -import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.settings.Settings import org.opensearch.commons.alerting.model.SearchInput import org.opensearch.commons.alerting.randomBucketLevelMonitor import org.opensearch.commons.alerting.randomQueryLevelMonitor import org.opensearch.commons.utils.recreateObject +import org.opensearch.core.common.io.stream.NamedWriteableAwareStreamInput +import org.opensearch.core.common.io.stream.NamedWriteableRegistry +import org.opensearch.core.common.io.stream.StreamInput import org.opensearch.rest.RestRequest import org.opensearch.search.SearchModule import org.opensearch.search.builder.SearchSourceBuilder diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorResponseTests.kt index f9332649..2b5ee04d 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorResponseTests.kt @@ -3,10 +3,10 @@ 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.CronSchedule import org.opensearch.commons.alerting.model.Monitor import org.opensearch.commons.alerting.randomUser +import org.opensearch.core.common.io.stream.StreamInput import java.time.Instant import java.time.ZoneId 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 bb4c453a..8ff08738 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequestTests.kt @@ -5,9 +5,6 @@ import org.junit.jupiter.api.Assertions 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.NamedWriteableAwareStreamInput -import org.opensearch.common.io.stream.NamedWriteableRegistry -import org.opensearch.common.io.stream.StreamInput import org.opensearch.common.settings.Settings import org.opensearch.commons.alerting.model.ChainedMonitorFindings import org.opensearch.commons.alerting.model.CompositeInput @@ -16,6 +13,9 @@ import org.opensearch.commons.alerting.model.Sequence import org.opensearch.commons.alerting.randomWorkflow import org.opensearch.commons.alerting.randomWorkflowWithDelegates import org.opensearch.commons.utils.recreateObject +import org.opensearch.core.common.io.stream.NamedWriteableAwareStreamInput +import org.opensearch.core.common.io.stream.NamedWriteableRegistry +import org.opensearch.core.common.io.stream.StreamInput import org.opensearch.rest.RestRequest import org.opensearch.search.SearchModule import java.lang.Exception diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowResponseTests.kt index 8565d093..6594010e 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowResponseTests.kt @@ -3,12 +3,12 @@ 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.ChainedAlertTrigger import org.opensearch.commons.alerting.model.CronSchedule import org.opensearch.commons.alerting.model.Workflow import org.opensearch.commons.alerting.randomChainedAlertTrigger import org.opensearch.commons.alerting.randomUser +import org.opensearch.core.common.io.stream.StreamInput import java.time.Instant import java.time.ZoneId diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/PublishFindingsRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/PublishFindingsRequestTests.kt index c3d69ace..d29ef7c6 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/PublishFindingsRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/PublishFindingsRequestTests.kt @@ -4,8 +4,8 @@ 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 +import org.opensearch.core.common.io.stream.StreamInput class PublishFindingsRequestTests { diff --git a/src/test/kotlin/org/opensearch/commons/alerting/model/MockScheduledJob.kt b/src/test/kotlin/org/opensearch/commons/alerting/model/MockScheduledJob.kt index bf2ddfa3..59f3771b 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/MockScheduledJob.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/MockScheduledJob.kt @@ -1,6 +1,6 @@ package org.opensearch.commons.alerting.model -import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.core.common.io.stream.StreamOutput import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.XContentBuilder import java.io.IOException 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 a3e83026..b605cfbe 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/WriteableTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/WriteableTests.kt @@ -3,7 +3,6 @@ package org.opensearch.commons.alerting.model 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.action.Action import org.opensearch.commons.alerting.model.action.ActionExecutionPolicy import org.opensearch.commons.alerting.model.action.Throttle @@ -18,6 +17,7 @@ import org.opensearch.commons.alerting.randomThrottle 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.search.builder.SearchSourceBuilder class WriteableTests { diff --git a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt index 61cbca7a..2e09db60 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt @@ -49,7 +49,7 @@ import org.opensearch.commons.notifications.model.NotificationConfigSearchResult import org.opensearch.commons.notifications.model.NotificationEvent import org.opensearch.commons.notifications.model.SeverityType import org.opensearch.commons.notifications.model.Slack -import org.opensearch.rest.RestStatus +import org.opensearch.core.rest.RestStatus import java.time.Instant @Suppress("UNCHECKED_CAST") diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponseTests.kt index fb574114..a05fbca5 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponseTests.kt @@ -11,7 +11,7 @@ import org.junit.jupiter.api.assertThrows import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject -import org.opensearch.rest.RestStatus +import org.opensearch.core.rest.RestStatus internal class DeleteNotificationConfigResponseTests { From 0ce0f3eafc49ea4f728670a0cbab04ff26647ce6 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Wed, 9 Aug 2023 15:52:56 -0700 Subject: [PATCH 125/149] Upgrade the backport workflow (#487) (#495) (cherry picked from commit 6b783645f23e9b9a40cd0ceb0a3859c6763e3bd7) Signed-off-by: Ashish Agrawal Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] --- .github/workflows/backport.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index e3f96a44..56fef507 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -23,7 +23,9 @@ jobs: installation_id: 22958780 - name: Backport - uses: VachaShah/backport@v1.1.4 + uses: VachaShah/backport@v2.2.0 with: github_token: ${{ steps.github_app_token.outputs.token }} branch_name: backport/backport-${{ github.event.number }} + labels_template: "<%= JSON.stringify([...labels, 'autocut']) %>" + failure_labels: "failed backport" From 7e1e226ad0dc45674aead99c8c57917c6b25d1cd Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Wed, 16 Aug 2023 17:49:29 -0700 Subject: [PATCH 126/149] make common-utils core compatible (#507) (#510) (cherry picked from commit fad45254c65c22eeda03ebefd4a2204ba30e7c64) Signed-off-by: Subhobrata Dey Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] --- .../opensearch/commons/alerting/AlertingPluginInterface.kt | 4 ++-- .../commons/notifications/NotificationsPluginInterface.kt | 4 ++-- .../opensearch/commons/notifications/action/BaseResponse.kt | 2 +- .../commons/notifications/model/NotificationEvent.kt | 2 +- .../org/opensearch/commons/utils/SecureClientWrapper.kt | 6 +++--- .../commons/alerting/AlertingPluginInterfaceTests.kt | 2 +- .../notifications/NotificationsPluginInterfaceTests.kt | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt index c905d2b3..5949582b 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt @@ -4,8 +4,6 @@ */ package org.opensearch.commons.alerting -import org.opensearch.action.ActionListener -import org.opensearch.action.ActionResponse import org.opensearch.client.node.NodeClient import org.opensearch.commons.alerting.action.AcknowledgeAlertRequest import org.opensearch.commons.alerting.action.AcknowledgeAlertResponse @@ -31,6 +29,8 @@ import org.opensearch.commons.alerting.action.PublishFindingsRequest import org.opensearch.commons.alerting.action.SubscribeFindingsResponse import org.opensearch.commons.notifications.action.BaseResponse import org.opensearch.commons.utils.recreateObject +import org.opensearch.core.action.ActionListener +import org.opensearch.core.action.ActionResponse import org.opensearch.core.common.io.stream.NamedWriteableRegistry import org.opensearch.core.common.io.stream.Writeable diff --git a/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt b/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt index 59b25054..38359f32 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt @@ -4,8 +4,6 @@ */ package org.opensearch.commons.notifications -import org.opensearch.action.ActionListener -import org.opensearch.action.ActionResponse import org.opensearch.client.node.NodeClient import org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_USER_INFO_THREAD_CONTEXT import org.opensearch.commons.notifications.action.BaseResponse @@ -37,6 +35,8 @@ import org.opensearch.commons.notifications.model.ChannelMessage import org.opensearch.commons.notifications.model.EventSource import org.opensearch.commons.utils.SecureClientWrapper import org.opensearch.commons.utils.recreateObject +import org.opensearch.core.action.ActionListener +import org.opensearch.core.action.ActionResponse import org.opensearch.core.common.io.stream.Writeable /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/BaseResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/BaseResponse.kt index 68837e9c..ecbbf4e9 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/BaseResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/BaseResponse.kt @@ -5,7 +5,7 @@ package org.opensearch.commons.notifications.action -import org.opensearch.action.ActionResponse +import org.opensearch.core.action.ActionResponse import org.opensearch.core.common.io.stream.StreamInput import org.opensearch.core.rest.RestStatus import org.opensearch.core.xcontent.ToXContentObject diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt index 87f637b0..2b3c8f40 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt @@ -4,7 +4,6 @@ */ package org.opensearch.commons.notifications.model -import org.opensearch.common.xcontent.XContentHelper import org.opensearch.common.xcontent.XContentType import org.opensearch.commons.notifications.NotificationConstants.EVENT_SOURCE_TAG import org.opensearch.commons.notifications.NotificationConstants.STATUS_LIST_TAG @@ -16,6 +15,7 @@ import org.opensearch.core.common.io.stream.Writeable import org.opensearch.core.xcontent.ToXContent import org.opensearch.core.xcontent.ToXContent.EMPTY_PARAMS import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentHelper import org.opensearch.core.xcontent.XContentParser import org.opensearch.core.xcontent.XContentParserUtils import java.io.IOException diff --git a/src/main/kotlin/org/opensearch/commons/utils/SecureClientWrapper.kt b/src/main/kotlin/org/opensearch/commons/utils/SecureClientWrapper.kt index bc0f0596..ffdb1676 100644 --- a/src/main/kotlin/org/opensearch/commons/utils/SecureClientWrapper.kt +++ b/src/main/kotlin/org/opensearch/commons/utils/SecureClientWrapper.kt @@ -5,10 +5,7 @@ package org.opensearch.commons.utils -import org.opensearch.action.ActionFuture -import org.opensearch.action.ActionListener import org.opensearch.action.ActionRequest -import org.opensearch.action.ActionResponse import org.opensearch.action.ActionType import org.opensearch.action.bulk.BulkRequest import org.opensearch.action.bulk.BulkResponse @@ -38,7 +35,10 @@ import org.opensearch.action.termvectors.TermVectorsResponse import org.opensearch.action.update.UpdateRequest import org.opensearch.action.update.UpdateResponse import org.opensearch.client.Client +import org.opensearch.common.action.ActionFuture import org.opensearch.common.util.concurrent.ThreadContext +import org.opensearch.core.action.ActionListener +import org.opensearch.core.action.ActionResponse /** * Wrapper class on [Client] with security context removed. diff --git a/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt index b8aea1b8..57670fc8 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt @@ -9,7 +9,6 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.mock import org.mockito.junit.jupiter.MockitoExtension -import org.opensearch.action.ActionListener import org.opensearch.action.ActionType import org.opensearch.client.node.NodeClient import org.opensearch.common.settings.Settings @@ -36,6 +35,7 @@ import org.opensearch.commons.alerting.model.FindingDocument import org.opensearch.commons.alerting.model.FindingWithDocs import org.opensearch.commons.alerting.model.Monitor import org.opensearch.commons.alerting.model.Workflow +import org.opensearch.core.action.ActionListener import org.opensearch.core.common.io.stream.NamedWriteableRegistry import org.opensearch.core.rest.RestStatus import org.opensearch.index.seqno.SequenceNumbers diff --git a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt index 2e09db60..ae7d0f3f 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt @@ -17,7 +17,6 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.junit.jupiter.MockitoExtension -import org.opensearch.action.ActionListener import org.opensearch.action.ActionType import org.opensearch.client.node.NodeClient import org.opensearch.commons.destination.response.LegacyDestinationResponse @@ -49,6 +48,7 @@ import org.opensearch.commons.notifications.model.NotificationConfigSearchResult import org.opensearch.commons.notifications.model.NotificationEvent import org.opensearch.commons.notifications.model.SeverityType import org.opensearch.commons.notifications.model.Slack +import org.opensearch.core.action.ActionListener import org.opensearch.core.rest.RestStatus import java.time.Instant From e6783b60288afc46df70a43c1d9f9613c198d3f5 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Fri, 18 Aug 2023 11:16:24 -0700 Subject: [PATCH 127/149] common utils to support Microsoft teams in notifications (#428) (#512) * Added common utils for microsoft teams * Added configType ,eventstatus,configDataProperties and msTeams files * Added ConfigType,EventStatus,ConfigDataProperties and MicrosoftTeams * fix build * fix build, add more test * change strings import * fix after core --------- (cherry picked from commit bae1beeac2a7890bf1824f7bdfc9ffadf9fee813) Signed-off-by: danielkyalo599 Signed-off-by: dankyalo599 Signed-off-by: zhichao-aws Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] Co-authored-by: danielkyalo599 --- .../commons/notifications/model/ConfigType.kt | 5 + .../notifications/model/EventStatus.kt | 1 + .../notifications/model/MicrosoftTeams.kt | 99 +++++++++++++++++++ .../model/config/ConfigDataProperties.kt | 5 +- .../CreateNotificationConfigRequestTests.kt | 60 +++++++++++ .../action/GetChannelListResponseTests.kt | 16 ++- .../GetNotificationConfigResponseTests.kt | 29 +++++- .../UpdateNotificationConfigRequestTests.kt | 29 +++++- .../notifications/model/ChannelListTests.kt | 31 +++++- .../notifications/model/EventStatusTests.kt | 11 +++ .../model/FilterConfigListTests.kt | 16 ++- .../model/MicrosoftTeamsTests.kt | 87 ++++++++++++++++ .../model/NotificationConfigTests.kt | 27 +++++ .../model/config/ConfigPropertiesTests.kt | 16 ++- 14 files changed, 417 insertions(+), 15 deletions(-) create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeams.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeamsTests.kt diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt index e9888e2b..50f1f3bc 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt @@ -54,6 +54,11 @@ enum class ConfigType(val tag: String) { override fun toString(): String { return tag } + }, + MICROSOFT_TEAMS("microsoft_teams") { + override fun toString(): String { + return tag + } }; companion object { diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt index afb023ce..8d1b5a7c 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt @@ -43,6 +43,7 @@ data class EventStatus( ConfigType.SLACK -> requireNotNull(deliveryStatus) ConfigType.EMAIL -> require(emailRecipientStatus.isNotEmpty()) ConfigType.SNS -> requireNotNull(deliveryStatus) + ConfigType.MICROSOFT_TEAMS -> requireNotNull(deliveryStatus) ConfigType.NONE -> log.info("Some config field not recognized") else -> { log.info("non-allowed config type for Status") diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeams.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeams.kt new file mode 100644 index 00000000..48e32f15 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeams.kt @@ -0,0 +1,99 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.opensearch.commons.notifications.model + +import org.opensearch.commons.notifications.NotificationConstants.URL_TAG +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.validateUrl +import org.opensearch.core.common.Strings +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.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 + +/** + * Data class representing MicrosoftTeams channel. + */ +data class MicrosoftTeams( + val url: String +) : BaseConfigData { + + init { + require(!Strings.isNullOrEmpty(url)) { "URL is null or empty" } + validateUrl(url) + } + + companion object { + private val log by logger(MicrosoftTeams::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { MicrosoftTeams(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): MicrosoftTeams { + var url: String? = null + + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + URL_TAG -> url = parser.text() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing MicrosoftTeams destination") + } + } + } + url ?: throw IllegalArgumentException("$URL_TAG field absent") + return MicrosoftTeams(url) + } + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + url = input.readString() + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(url) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(URL_TAG, url) + .endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt index 4d70b18f..28063e30 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt @@ -9,6 +9,7 @@ import org.opensearch.commons.notifications.model.Chime import org.opensearch.commons.notifications.model.ConfigType import org.opensearch.commons.notifications.model.Email import org.opensearch.commons.notifications.model.EmailGroup +import org.opensearch.commons.notifications.model.MicrosoftTeams import org.opensearch.commons.notifications.model.SesAccount import org.opensearch.commons.notifications.model.Slack import org.opensearch.commons.notifications.model.SmtpAccount @@ -36,7 +37,8 @@ internal object ConfigDataProperties { Pair(ConfigType.SNS, ConfigProperty(Sns.reader, Sns.xParser)), Pair(ConfigType.SES_ACCOUNT, ConfigProperty(SesAccount.reader, SesAccount.xParser)), Pair(ConfigType.EMAIL_GROUP, ConfigProperty(EmailGroup.reader, EmailGroup.xParser)), - Pair(ConfigType.SMTP_ACCOUNT, ConfigProperty(SmtpAccount.reader, SmtpAccount.xParser)) + Pair(ConfigType.SMTP_ACCOUNT, ConfigProperty(SmtpAccount.reader, SmtpAccount.xParser)), + Pair(ConfigType.MICROSOFT_TEAMS, ConfigProperty(MicrosoftTeams.reader, MicrosoftTeams.xParser)) ) /** @@ -62,6 +64,7 @@ internal object ConfigDataProperties { ConfigType.CHIME -> configData is Chime ConfigType.SNS -> configData is Sns ConfigType.SES_ACCOUNT -> configData is SesAccount + ConfigType.MICROSOFT_TEAMS -> configData is MicrosoftTeams ConfigType.NONE -> true } } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt index 24f389e2..117375e7 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt @@ -15,6 +15,7 @@ import org.opensearch.commons.notifications.model.Email import org.opensearch.commons.notifications.model.EmailGroup import org.opensearch.commons.notifications.model.EmailRecipient import org.opensearch.commons.notifications.model.MethodType +import org.opensearch.commons.notifications.model.MicrosoftTeams import org.opensearch.commons.notifications.model.NotificationConfig import org.opensearch.commons.notifications.model.Slack import org.opensearch.commons.notifications.model.SmtpAccount @@ -57,6 +58,16 @@ internal class CreateNotificationConfigRequestTests { isEnabled = true ) } + private fun createMicrosoftTeamsContentConfigObject(): NotificationConfig { + val sampleMicrosoftTeams = MicrosoftTeams("https://domain.com/sample_microsoft_teams_url#1234567890") + return NotificationConfig( + "name", + "description", + ConfigType.MICROSOFT_TEAMS, + configData = sampleMicrosoftTeams, + isEnabled = true + ) + } private fun createEmailGroupContentConfigObject(): NotificationConfig { val sampleEmailGroup = EmailGroup(listOf(EmailRecipient("dummy@company.com"))) @@ -114,6 +125,20 @@ internal class CreateNotificationConfigRequestTests { assertNull(recreatedObject.validate()) assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) } + @Test + fun `Create config serialize and deserialize transport object should be equal microsoft teams`() { + val configRequest = CreateNotificationConfigRequest( + createMicrosoftTeamsContentConfigObject() + ) + val recreatedObject = + recreateObject(configRequest) { + CreateNotificationConfigRequest( + it + ) + } + assertNull(recreatedObject.validate()) + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + } @Test fun `Create config serialize and deserialize transport object should be equal slack`() { @@ -189,6 +214,15 @@ internal class CreateNotificationConfigRequestTests { assertNull(recreatedObject.validate()) assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) } + @Test + fun `Create config serialize and deserialize using json object should be equal microsoft teams`() { + val configRequest = CreateNotificationConfigRequest( + createMicrosoftTeamsContentConfigObject() + ) + val jsonString = getJsonString(configRequest) + val recreatedObject = createObjectFromJsonString(jsonString) { CreateNotificationConfigRequest.parse(it) } + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + } @Test fun `Create config serialize and deserialize using json object should be equal`() { @@ -275,6 +309,32 @@ internal class CreateNotificationConfigRequestTests { val recreatedObject = createObjectFromJsonString(jsonString) { CreateNotificationConfigRequest.parse(it) } assertEquals(config, recreatedObject.notificationConfig) } + @Test + fun `Create config should deserialize json object using parser microsoft teams`() { + val sampleMicrosoftTeams = MicrosoftTeams("https://domain.com/sample_microsoft_teams_url#1234567890") + val config = NotificationConfig( + "name", + "description", + ConfigType.MICROSOFT_TEAMS, + configData = sampleMicrosoftTeams, + isEnabled = true + ) + + val jsonString = """ + { + "config_id":"config_id1", + "config":{ + "name":"name", + "description":"description", + "config_type":"microsoft_teams", + "is_enabled":true, + "microsoft_teams":{"url":"https://domain.com/sample_microsoft_teams_url#1234567890"} + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { CreateNotificationConfigRequest.parse(it) } + assertEquals(config, recreatedObject.notificationConfig) + } @Test fun `Create config should deserialize json object using parser webhook`() { diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetChannelListResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetChannelListResponseTests.kt index eb4023dd..4f19ab4b 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetChannelListResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetChannelListResponseTests.kt @@ -62,11 +62,17 @@ internal class GetChannelListResponseTests { "description3", ConfigType.WEBHOOK ) + val sampleConfig4 = Channel( + "config_id5", + "name4", + "description4", + ConfigType.MICROSOFT_TEAMS + ) val searchResult = ChannelList( 100, 1000, TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, - listOf(sampleConfig1, sampleConfig2, sampleConfig3) + listOf(sampleConfig1, sampleConfig2, sampleConfig3, sampleConfig4) ) val getResponse = GetChannelListResponse(searchResult) val recreatedObject = recreateObject(getResponse) { GetChannelListResponse(it) } @@ -108,11 +114,17 @@ internal class GetChannelListResponseTests { "description3", ConfigType.WEBHOOK ) + val sampleConfig4 = Channel( + "config_id5", + "name4", + "description4", + ConfigType.MICROSOFT_TEAMS + ) val searchResult = ChannelList( 100, 1000, TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, - listOf(sampleConfig1, sampleConfig2, sampleConfig3) + listOf(sampleConfig1, sampleConfig2, sampleConfig3, sampleConfig4) ) val getResponse = GetChannelListResponse(searchResult) val jsonString = getJsonString(getResponse) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt index 3cfccfcb..a0c025ac 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt @@ -10,6 +10,7 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.opensearch.commons.notifications.model.Chime import org.opensearch.commons.notifications.model.ConfigType +import org.opensearch.commons.notifications.model.MicrosoftTeams import org.opensearch.commons.notifications.model.NotificationConfig import org.opensearch.commons.notifications.model.NotificationConfigInfo import org.opensearch.commons.notifications.model.NotificationConfigSearchResult @@ -79,11 +80,23 @@ internal class GetNotificationConfigResponseTests { Instant.now(), sampleConfig2 ) + val sampleConfig3 = NotificationConfig( + "name", + "description", + ConfigType.MICROSOFT_TEAMS, + configData = MicrosoftTeams("https://domain.com/sample_url#1234567890") + ) + val configInfo3 = NotificationConfigInfo( + "config_id3", + Instant.now(), + Instant.now(), + sampleConfig3 + ) val searchResult = NotificationConfigSearchResult( 100, 1000, TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, - listOf(configInfo1, configInfo2) + listOf(configInfo1, configInfo2, configInfo3) ) val searchResponse = GetNotificationConfigResponse(searchResult) val recreatedObject = recreateObject(searchResponse) { GetNotificationConfigResponse(it) } @@ -142,11 +155,23 @@ internal class GetNotificationConfigResponseTests { createdTimeMs, sampleConfig2 ) + val sampleConfig3 = NotificationConfig( + "name", + "description", + ConfigType.MICROSOFT_TEAMS, + configData = MicrosoftTeams("https://domain.com/sample_url#1234567890") + ) + val configInfo3 = NotificationConfigInfo( + "config_id3", + lastUpdatedTimeMs, + createdTimeMs, + sampleConfig3 + ) val searchResult = NotificationConfigSearchResult( 100, 1000, TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, - listOf(configInfo1, configInfo2) + listOf(configInfo1, configInfo2, configInfo3) ) val searchResponse = GetNotificationConfigResponse(searchResult) val jsonString = getJsonString(searchResponse) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt index a6e0077a..e07d7747 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt @@ -15,6 +15,7 @@ import org.opensearch.commons.notifications.model.Email import org.opensearch.commons.notifications.model.EmailGroup import org.opensearch.commons.notifications.model.EmailRecipient import org.opensearch.commons.notifications.model.MethodType +import org.opensearch.commons.notifications.model.MicrosoftTeams import org.opensearch.commons.notifications.model.NotificationConfig import org.opensearch.commons.notifications.model.Slack import org.opensearch.commons.notifications.model.SmtpAccount @@ -35,7 +36,16 @@ internal class UpdateNotificationConfigRequestTests { isEnabled = true ) } - + private fun createMicrosoftTeamsContentConfigObject(): NotificationConfig { + val sampleMicrosoftTeams = MicrosoftTeams("https://domain.com/sample_microsoft_teams_url#1234567890") + return NotificationConfig( + "name", + "description", + ConfigType.MICROSOFT_TEAMS, + configData = sampleMicrosoftTeams, + isEnabled = true + ) + } private fun createSlackContentConfigObject(): NotificationConfig { val sampleSlack = Slack("https://domain.com/sample_slack_url#1234567890") return NotificationConfig( @@ -109,6 +119,15 @@ internal class UpdateNotificationConfigRequestTests { assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) assertEquals("config_id", recreatedObject.configId) } + @Test + fun `Update config serialize and deserialize transport object should be equal Microsoft Teams`() { + val configRequest = UpdateNotificationConfigRequest("config_id", createMicrosoftTeamsContentConfigObject()) + val recreatedObject = + recreateObject(configRequest) { UpdateNotificationConfigRequest(it) } + assertNull(recreatedObject.validate()) + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + assertEquals("config_id", recreatedObject.configId) + } @Test fun `Update config serialize and deserialize transport object should be equal Slack`() { @@ -168,6 +187,14 @@ internal class UpdateNotificationConfigRequestTests { assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) assertEquals("config_id", recreatedObject.configId) } + @Test + fun `Update config serialize and deserialize using json object should be equal microsoft Teams`() { + val configRequest = UpdateNotificationConfigRequest("config_id", createMicrosoftTeamsContentConfigObject()) + val jsonString = getJsonString(configRequest) + val recreatedObject = createObjectFromJsonString(jsonString) { UpdateNotificationConfigRequest.parse(it) } + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + assertEquals("config_id", recreatedObject.configId) + } @Test fun `Update config serialize and deserialize using json object should be equal slack`() { diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/ChannelListTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/ChannelListTests.kt index 06387d37..cad52261 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/ChannelListTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/ChannelListTests.kt @@ -55,12 +55,19 @@ internal class ChannelListTests { ConfigType.CHIME, true ) - val channelList = ChannelList(listOf(channel1, channel2)) + val channel3 = Channel( + "configId3", + "name3", + "description3", + ConfigType.MICROSOFT_TEAMS, + true + ) + val channelList = ChannelList(listOf(channel1, channel2, channel3)) val expectedResult = ChannelList( 0, - 2, + 3, TotalHits.Relation.EQUAL_TO, - listOf(channel1, channel2) + listOf(channel1, channel2, channel3) ) val recreatedObject = recreateObject(channelList) { ChannelList(it) } assertSearchResultEquals(expectedResult, recreatedObject) @@ -82,11 +89,18 @@ internal class ChannelListTests { ConfigType.CHIME, true ) + val channel3 = Channel( + "configId3", + "name3", + "description3", + ConfigType.MICROSOFT_TEAMS, + true + ) val channelList = ChannelList( 100, 1000, TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, - listOf(channel1, channel2) + listOf(channel1, channel2, channel3) ) val recreatedObject = recreateObject(channelList) { ChannelList(it) } assertSearchResultEquals(channelList, recreatedObject) @@ -123,11 +137,18 @@ internal class ChannelListTests { ConfigType.CHIME, true ) + val channel3 = Channel( + "configId3", + "name3", + "description3", + ConfigType.MICROSOFT_TEAMS, + true + ) val channelList = ChannelList( 100, 1000, TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, - listOf(channel1, channel2) + listOf(channel1, channel2, channel3) ) val jsonString = getJsonString(channelList) val recreatedObject = createObjectFromJsonString(jsonString) { ChannelList(it) } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/EventStatusTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/EventStatusTests.kt index 65173cfe..94b48e64 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/EventStatusTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/EventStatusTests.kt @@ -118,6 +118,17 @@ internal class EventStatusTests { } } + @Test + fun `Event throw exception if deliveryStatus is empty for config type MicrosoftTeams`() { + Assertions.assertThrows(IllegalArgumentException::class.java) { + EventStatus( + "config_id", + "name", + ConfigType.MICROSOFT_TEAMS + ) + } + } + @Test fun `Event throw exception if deliveryStatus is empty for config type Webhook`() { Assertions.assertThrows(IllegalArgumentException::class.java) { diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigListTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigListTests.kt index de648bcd..11488e38 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigListTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigListTests.kt @@ -58,11 +58,17 @@ internal class FilterConfigListTests { "description3", ConfigType.WEBHOOK ) + val sampleConfig4 = Channel( + "config_id4", + "name4", + "description4", + ConfigType.MICROSOFT_TEAMS + ) val searchResult = ChannelList( 100, 1000, TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, - listOf(sampleConfig1, sampleConfig2, sampleConfig3) + listOf(sampleConfig1, sampleConfig2, sampleConfig3, sampleConfig4) ) val recreatedObject = recreateObject(searchResult) { ChannelList(it) } assertSearchResultEquals(searchResult, recreatedObject) @@ -102,11 +108,17 @@ internal class FilterConfigListTests { "description3", ConfigType.WEBHOOK ) + val sampleConfig4 = Channel( + "config_id4", + "name4", + "description4", + ConfigType.MICROSOFT_TEAMS + ) val searchResult = ChannelList( 100, 1000, TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, - listOf(sampleConfig1, sampleConfig2, sampleConfig3) + listOf(sampleConfig1, sampleConfig2, sampleConfig3, sampleConfig4) ) val jsonString = getJsonString(searchResult) val recreatedObject = createObjectFromJsonString(jsonString) { ChannelList(it) } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeamsTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeamsTests.kt new file mode 100644 index 00000000..4543c06b --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeamsTests.kt @@ -0,0 +1,87 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.opensearch.commons.notifications.model + +import com.fasterxml.jackson.core.JsonParseException +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.opensearch.commons.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import org.opensearch.commons.utils.recreateObject +import java.net.MalformedURLException + +internal class MicrosoftTeamsTests { + + @Test + fun `Microsoft Teams serialize and deserialize transport object should be equal`() { + val sampleMicrosoftTeams = MicrosoftTeams("https://domain.com/sample_url#1234567890") + val recreatedObject = recreateObject(sampleMicrosoftTeams) { MicrosoftTeams(it) } + assertEquals(sampleMicrosoftTeams, recreatedObject) + } + + @Test + fun `Microsoft Teams serialize and deserialize using json object should be equal`() { + val sampleMicrosoftTeams = MicrosoftTeams("https://domain.com/sample_url#1234567890") + val jsonString = getJsonString(sampleMicrosoftTeams) + val recreatedObject = createObjectFromJsonString(jsonString) { MicrosoftTeams.parse(it) } + assertEquals(sampleMicrosoftTeams, recreatedObject) + } + + @Test + fun `Microsoft Teams should deserialize json object using parser`() { + val sampleMicrosoftTeams = MicrosoftTeams("https://domain.com/sample_url#1234567890") + val jsonString = "{\"url\":\"${sampleMicrosoftTeams.url}\"}" + val recreatedObject = createObjectFromJsonString(jsonString) { MicrosoftTeams.parse(it) } + assertEquals(sampleMicrosoftTeams, recreatedObject) + } + + @Test + fun `Microsoft Teams should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { MicrosoftTeams.parse(it) } + } + } + + @Test + fun `Microsoft Teams should throw exception when url is replace with url2 in json object`() { + val sampleMicrosoftTeams = MicrosoftTeams("https://domain.com/sample_url#1234567890") + val jsonString = "{\"url2\":\"${sampleMicrosoftTeams.url}\"}" + assertThrows { + createObjectFromJsonString(jsonString) { MicrosoftTeams.parse(it) } + } + } + + @Test + fun `Microsoft Teams should throw exception when url is not proper`() { + assertThrows { + MicrosoftTeams("domain.com/sample_url#1234567890") + } + val jsonString = "{\"url\":\"domain.com/sample_url\"}" + assertThrows { + createObjectFromJsonString(jsonString) { MicrosoftTeams.parse(it) } + } + } + + @Test + fun `Microsoft Teams should throw exception when url protocol is not https or http`() { + assertThrows { + MicrosoftTeams("ftp://domain.com/sample_url#1234567890") + } + val jsonString = "{\"url\":\"ftp://domain.com/sample_url\"}" + assertThrows { + createObjectFromJsonString(jsonString) { MicrosoftTeams.parse(it) } + } + } + + @Test + fun `Microsoft Teams should safely ignore extra field in json object`() { + val sampleMicrosoftTeams = MicrosoftTeams("https://domain.com/sample_url#1234567890") + val jsonString = "{\"url\":\"${sampleMicrosoftTeams.url}\", \"another\":\"field\"}" + val recreatedObject = createObjectFromJsonString(jsonString) { MicrosoftTeams.parse(it) } + assertEquals(sampleMicrosoftTeams, recreatedObject) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt index f8aa9e9d..31791120 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt @@ -66,6 +66,33 @@ internal class NotificationConfigTests { assertEquals(sampleConfig, recreatedObject) } + @Test + fun `Config serialize and deserialize with microsoft teams object should be equal`() { + val sampleMicrosoftTeams = MicrosoftTeams("https://domain.com/sample_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.MICROSOFT_TEAMS, + configData = sampleMicrosoftTeams + ) + val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } + assertEquals(sampleConfig, recreatedObject) + } + + @Test + fun `Config serialize and deserialize with json microsoft teams object should be equal`() { + val sampleMicrosoftTeams = MicrosoftTeams("https://domain.com/sample_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.MICROSOFT_TEAMS, + configData = sampleMicrosoftTeams + ) + val jsonString = getJsonString(sampleConfig) + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationConfig.parse(it) } + assertEquals(sampleConfig, recreatedObject) + } + @Test fun `Config serialize and deserialize with webhook object should be equal`() { val sampleWebhook = Webhook("https://domain.com/sample_url#1234567890") diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/config/ConfigPropertiesTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/config/ConfigPropertiesTests.kt index 80a20b88..881cf9d4 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/config/ConfigPropertiesTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/config/ConfigPropertiesTests.kt @@ -7,6 +7,7 @@ import org.opensearch.commons.notifications.model.Email import org.opensearch.commons.notifications.model.EmailGroup import org.opensearch.commons.notifications.model.EmailRecipient import org.opensearch.commons.notifications.model.MethodType +import org.opensearch.commons.notifications.model.MicrosoftTeams import org.opensearch.commons.notifications.model.Slack import org.opensearch.commons.notifications.model.SmtpAccount import org.opensearch.commons.notifications.model.Webhook @@ -22,6 +23,11 @@ internal class ConfigPropertiesTests { assertEquals(getReaderForConfigType(ConfigType.SLACK), Slack.reader) } + @Test + fun `Validate config property reader Microsoft Teams`() { + assertEquals(getReaderForConfigType(ConfigType.MICROSOFT_TEAMS), MicrosoftTeams.reader) + } + @Test fun `Validate config property reader chime`() { assertEquals(getReaderForConfigType(ConfigType.CHIME), Chime.reader) @@ -48,7 +54,7 @@ internal class ConfigPropertiesTests { } @Test - fun `Validate config data parse slack`() { + fun `Validate config data parse slack`() { val sampleSlack = Slack("https://domain.com/sample_url#1234567890") val jsonString = getJsonString(sampleSlack) val recreatedObject = createObjectFromJsonString(jsonString) { createConfigData(ConfigType.SLACK, it) } @@ -70,7 +76,13 @@ internal class ConfigPropertiesTests { val recreatedObject = createObjectFromJsonString(jsonString) { createConfigData(ConfigType.WEBHOOK, it) } assertEquals(sampleWebhook, recreatedObject) } - + @Test + fun `Validate config data parse Microsoft Teams`() { + val sampleMicrosoftTeams = MicrosoftTeams("https://domain.com/sample_url#1234567890") + val jsonString = getJsonString(sampleMicrosoftTeams) + val recreatedObject = createObjectFromJsonString(jsonString) { createConfigData(ConfigType.MICROSOFT_TEAMS, it) } + assertEquals(sampleMicrosoftTeams, recreatedObject) + } @Test fun `Validate config data parse EmailGroup`() { val sampleEmailGroup = EmailGroup(listOf(EmailRecipient("email1@email.com"), EmailRecipient("email2@email.com"))) From 41c9fcf4118939632536801d64c0f647eec2cd4e Mon Sep 17 00:00:00 2001 From: Surya Sashank Nistala Date: Fri, 1 Sep 2023 13:36:06 -0700 Subject: [PATCH 128/149] support list of monitor ids in Chained Monitor Findings (#514) (#515) support list of monitor ids in Chained Monitor Findings Signed-off-by: Surya Sashank Nistala --- .../alerting/action/IndexWorkflowRequest.kt | 48 ++++++++++++++----- .../alerting/model/ChainedMonitorFindings.kt | 48 ++++++++++++++----- .../commons/alerting/TestHelpers.kt | 10 ++++ .../action/IndexWorkflowRequestTests.kt | 34 +++++++++++++ .../alerting/model/CompositeInputTests.kt | 17 ++++++- .../commons/alerting/model/XContentTests.kt | 24 ++++++++++ 6 files changed, 156 insertions(+), 25 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequest.kt index dc2b0936..6fe9c47b 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequest.kt @@ -105,19 +105,41 @@ class IndexWorkflowRequest : ActionRequest { val monitorIdOrderMap: Map = delegates.associate { it.monitorId to it.order } delegates.forEach { if (it.chainedMonitorFindings != null) { - if (monitorIdOrderMap.containsKey(it.chainedMonitorFindings.monitorId) == false) { - validationException = ValidateActions.addValidationError( - "Chained Findings Monitor ${it.chainedMonitorFindings.monitorId} doesn't exist in sequence", - validationException - ) - // Break the flow because next check will generate the NPE - return validationException - } - if (it.order <= monitorIdOrderMap[it.chainedMonitorFindings.monitorId]!!) { - validationException = ValidateActions.addValidationError( - "Chained Findings Monitor ${it.chainedMonitorFindings.monitorId} should be executed before monitor ${it.monitorId}", - validationException - ) + + if (it.chainedMonitorFindings.monitorId != null) { + if (monitorIdOrderMap.containsKey(it.chainedMonitorFindings.monitorId) == false) { + validationException = ValidateActions.addValidationError( + "Chained Findings Monitor ${it.chainedMonitorFindings.monitorId} doesn't exist in sequence", + validationException + ) + // Break the flow because next check will generate the NPE + return validationException + } + if (it.order <= monitorIdOrderMap[it.chainedMonitorFindings.monitorId]!!) { + validationException = ValidateActions.addValidationError( + "Chained Findings Monitor ${it.chainedMonitorFindings.monitorId} should be executed before monitor ${it.monitorId}", + validationException + ) + } + } else { + for (monitorId in it.chainedMonitorFindings.monitorIds) { + if (!monitorIdOrderMap.containsKey(monitorId)) { + validationException = ValidateActions.addValidationError( + "Chained Findings Monitor $monitorId doesn't exist in sequence", + validationException + ) + return validationException + } else { + val order = monitorIdOrderMap.get(monitorId)!! + if (order >= it.order) { + return ValidateActions.addValidationError( + "Chained Findings Monitor ${it.chainedMonitorFindings.monitorId} should be executed before monitor ${it.monitorId}. " + + "Order of monitor being chained [$order] should be smaller than order of monitor using findings as source data [${it.order}] in sequence", + validationException + ) + } + } + } } } } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/ChainedMonitorFindings.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/ChainedMonitorFindings.kt index dbc15e34..92192eec 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/ChainedMonitorFindings.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/ChainedMonitorFindings.kt @@ -9,50 +9,65 @@ import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser import org.opensearch.core.xcontent.XContentParserUtils import java.io.IOException +import java.util.Collections /** - * Context passed in delegate monitor to filter data queried by a monitor based on the findings of the given monitor id. + * Context passed in delegate monitor to filter data matched by a list of monitors based on the findings of the given monitor ids. */ // TODO - Remove the class and move the monitorId to Delegate (as a chainedMonitorId property) if this class won't be updated by adding new properties data class ChainedMonitorFindings( - val monitorId: String + val monitorId: String? = null, + val monitorIds: List = emptyList(), // if monitorId field is non-null it would be given precendence for BWC ) : BaseModel { init { - validateId(monitorId) + require(!(monitorId.isNullOrBlank() && monitorIds.isEmpty())) { + "at least one of fields, 'monitorIds' and 'monitorId' should be provided" + } + if (monitorId != null && monitorId.isBlank()) { + validateId(monitorId) + } else { + monitorIds.forEach { validateId(it) } + } } @Throws(IOException::class) constructor(sin: StreamInput) : this( - sin.readString(), // monitorId + sin.readOptionalString(), // monitorId + Collections.unmodifiableList(sin.readStringList()) ) + @Suppress("UNCHECKED_CAST") fun asTemplateArg(): Map { return mapOf( MONITOR_ID_FIELD to monitorId, - ) + MONITOR_IDS_FIELD to monitorIds + ) as Map } @Throws(IOException::class) override fun writeTo(out: StreamOutput) { - out.writeString(monitorId) + out.writeOptionalString(monitorId) + out.writeStringCollection(monitorIds) } override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { builder.startObject() .field(MONITOR_ID_FIELD, monitorId) + .field(MONITOR_IDS_FIELD, monitorIds) .endObject() return builder } companion object { const val MONITOR_ID_FIELD = "monitor_id" + const val MONITOR_IDS_FIELD = "monitor_ids" @JvmStatic @Throws(IOException::class) fun parse(xcp: XContentParser): ChainedMonitorFindings { - lateinit var monitorId: String - + var monitorId: String? = null + val monitorIds = mutableListOf() XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { val fieldName = xcp.currentName() @@ -60,12 +75,23 @@ data class ChainedMonitorFindings( when (fieldName) { MONITOR_ID_FIELD -> { - monitorId = xcp.text() - validateId(monitorId) + if (!xcp.currentToken().equals(XContentParser.Token.VALUE_NULL)) + monitorId = xcp.text() + } + + MONITOR_IDS_FIELD -> { + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_ARRAY, + xcp.currentToken(), + xcp + ) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + monitorIds.add(xcp.text()) + } } } } - return ChainedMonitorFindings(monitorId) + return ChainedMonitorFindings(monitorId, monitorIds) } @JvmStatic diff --git a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt index c4a68d44..a4f880a9 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt @@ -405,6 +405,16 @@ fun randomClusterMetricsInput( return ClusterMetricsInput(path, pathParams, url) } +fun ChainedMonitorFindings.toJsonString(): String { + val builder = XContentFactory.jsonBuilder() + return this.toXContent(builder, ToXContent.EMPTY_PARAMS).string() +} + +fun Workflow.toJsonString(): String { + val builder = XContentFactory.jsonBuilder() + return this.toXContentWithUser(builder, ToXContent.EMPTY_PARAMS).string() +} + fun Monitor.toJsonString(): String { val builder = XContentFactory.jsonBuilder() return this.toXContent(builder, ToXContent.EMPTY_PARAMS).string() 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 8ff08738..58feffb2 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequestTests.kt @@ -21,6 +21,9 @@ import org.opensearch.search.SearchModule import java.lang.Exception import java.lang.IllegalArgumentException import java.util.UUID +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue class IndexWorkflowRequestTests { @@ -196,6 +199,21 @@ class IndexWorkflowRequestTests { delegates = listOf( Delegate(1, "monitor-1") ) + + // Chained finding list of monitors valid + delegates = listOf( + Delegate(1, "monitor-1"), + Delegate(2, "monitor-2"), + Delegate(3, "monitor-3", ChainedMonitorFindings(null, listOf("monitor-1", "monitor-2"))), + + ) + val req7 = IndexWorkflowRequest( + "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + randomWorkflowWithDelegates( + input = listOf(CompositeInput(Sequence(delegates = delegates))) + ) + ) + assertNull(req7.validate()) try { IndexWorkflowRequest( "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, @@ -207,5 +225,21 @@ class IndexWorkflowRequestTests { Assert.assertTrue(ex is IllegalArgumentException) Assert.assertTrue(ex.message!!.contains("Workflows can only have 1 search input.")) } + + // Chained finding list of monitors invalid order and old field null + delegates = listOf( + Delegate(1, "monitor-1"), + Delegate(3, "monitor-2"), + Delegate(2, "monitor-3", ChainedMonitorFindings(null, listOf("monitor-1", "monitor-2"))), + + ) + val req8 = IndexWorkflowRequest( + "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + randomWorkflowWithDelegates( + input = listOf(CompositeInput(Sequence(delegates = delegates))) + ) + ) + assertNotNull(req8.validate()) + assertTrue(req8.validate()!!.message!!.contains("should be executed before monitor")) } } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/model/CompositeInputTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/model/CompositeInputTests.kt index 9680bdbe..ad0d2b24 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/CompositeInputTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/CompositeInputTests.kt @@ -70,10 +70,25 @@ class CompositeInputTests { } @Test - fun `test create Chained Findings with illegal monitorId value`() { + fun `test create Chained Findings with illegal monitorId value and empty monitorIds list`() { try { ChainedMonitorFindings("") Assertions.fail("Expecting an illegal argument exception") + } catch (e: IllegalArgumentException) { + e.message?.let { + Assertions.assertTrue( + it.contains("at least one of fields, 'monitorIds' and 'monitorId' should be provided") + + ) + } + } + } + + @Test + fun `test create Chained Findings with null monitorId value and monitorIds list with blank monitorIds`() { + try { + ChainedMonitorFindings("", listOf("", "")) + Assertions.fail("Expecting an illegal argument exception") } catch (e: IllegalArgumentException) { e.message?.let { Assertions.assertTrue( 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 67e16908..a284c187 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt @@ -264,6 +264,30 @@ class XContentTests { Assertions.assertNull(parsedMonitor.user) } + @Test + fun `test workflow parsing`() { + val workflow = randomWorkflow(monitorIds = listOf("1", "2", "3")) + val monitorString = workflow.toJsonString() + val parsedWorkflow = Workflow.parse(parser(monitorString)) + Assertions.assertEquals(workflow, parsedWorkflow, "Round tripping workflow failed") + } + + @Test + fun `test chainedMonitorFindings parsing`() { + val cmf1 = ChainedMonitorFindings(monitorId = "m1") + val cmf1String = cmf1.toJsonString() + Assertions.assertEquals( + ChainedMonitorFindings.parse(parser(cmf1String)), cmf1, + "Round tripping chained monitor findings failed" + ) + val cmf2 = ChainedMonitorFindings(monitorIds = listOf("m1", "m2")) + val cmf2String = cmf2.toJsonString() + Assertions.assertEquals( + ChainedMonitorFindings.parse(parser(cmf2String)), cmf2, + "Round tripping chained monitor findings failed" + ) + } + @Test fun `test old monitor format parsing`() { val monitorString = """ From c6205dbdf4165f90c8319f2cc8bf015601401dda Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Thu, 7 Sep 2023 23:07:10 -0400 Subject: [PATCH 129/149] Updates demo certs used in rest tests (#518) (#521) (cherry picked from commit d797db5a15baa85df47b5084667c675ed1f49b5e) Signed-off-by: Darshit Chanpura Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] --- src/test/resources/sample.pem | 49 +++++++++++++++---------------- src/test/resources/test-kirk.jks | Bin 3874 -> 4504 bytes 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/test/resources/sample.pem b/src/test/resources/sample.pem index 7ba92534..a1fc20a7 100644 --- a/src/test/resources/sample.pem +++ b/src/test/resources/sample.pem @@ -1,28 +1,25 @@ -----BEGIN CERTIFICATE----- -MIIEyTCCA7GgAwIBAgIGAWLrc1O2MA0GCSqGSIb3DQEBCwUAMIGPMRMwEQYKCZIm -iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ -RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290 -IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwHhcNMTgwNDIy -MDM0MzQ3WhcNMjgwNDE5MDM0MzQ3WjBeMRIwEAYKCZImiZPyLGQBGRYCZGUxDTAL -BgNVBAcMBHRlc3QxDTALBgNVBAoMBG5vZGUxDTALBgNVBAsMBG5vZGUxGzAZBgNV -BAMMEm5vZGUtMC5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC -AQoCggEBAJa+f476vLB+AwK53biYByUwN+40D8jMIovGXm6wgT8+9Sbs899dDXgt -9CE1Beo65oP1+JUz4c7UHMrCY3ePiDt4cidHVzEQ2g0YoVrQWv0RedS/yx/DKhs8 -Pw1O715oftP53p/2ijD5DifFv1eKfkhFH+lwny/vMSNxellpl6NxJTiJVnQ9HYOL -gf2t971ITJHnAuuxUF48HcuNovW4rhtkXef8kaAN7cE3LU+A9T474ULNCKkEFPIl -ZAKN3iJNFdVsxrTU+CUBHzk73Do1cCkEvJZ0ZFjp0Z3y8wLY/gqWGfGVyA9l2CUq -eIZNf55PNPtGzOrvvONiui48vBKH1LsCAwEAAaOCAVkwggFVMIG8BgNVHSMEgbQw -gbGAFJI1DOAPHitF9k0583tfouYSl0BzoYGVpIGSMIGPMRMwEQYKCZImiZPyLGQB -GRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQRXhhbXBs -ZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290IENBMSEw -HwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0GCAQEwHQYDVR0OBBYEFKyv -78ZmFjVKM9g7pMConYH7FVBHMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgXg -MCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjA1BgNVHREELjAsiAUq -AwQFBYISbm9kZS0wLmV4YW1wbGUuY29tgglsb2NhbGhvc3SHBH8AAAEwDQYJKoZI -hvcNAQELBQADggEBAIOKuyXsFfGv1hI/Lkpd/73QNqjqJdxQclX57GOMWNbOM5H0 -5/9AOIZ5JQsWULNKN77aHjLRr4owq2jGbpc/Z6kAd+eiatkcpnbtbGrhKpOtoEZy -8KuslwkeixpzLDNISSbkeLpXz4xJI1ETMN/VG8ZZP1bjzlHziHHDu0JNZ6TnNzKr -XzCGMCohFfem8vnKNnKUneMQMvXd3rzUaAgvtf7Hc2LTBlf4fZzZF1EkwdSXhaMA -1lkfHiqOBxtgeDLxCHESZ2fqgVqsWX+t3qHQfivcPW6txtDyrFPRdJOGhiMGzT/t -e/9kkAtQRgpTb3skYdIOOUOV0WGQ60kJlFhAzIs= +MIIEPDCCAySgAwIBAgIUZjrlDPP8azRDPZchA/XEsx0X2iIwDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yMzA4MjkwNDIzMTJaFw0zMzA4MjYwNDIzMTJaMFcxCzAJBgNVBAYT +AmRlMQ0wCwYDVQQHDAR0ZXN0MQ0wCwYDVQQKDARub2RlMQ0wCwYDVQQLDARub2Rl +MRswGQYDVQQDDBJub2RlLTAuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCm93kXteDQHMAvbUPNPW5pyRHKDD42XGWSgq0k1D29C/Ud +yL21HLzTJa49ZU2ldIkSKs9JqbkHdyK0o8MO6L8dotLoYbxDWbJFW8bp1w6tDTU0 +HGkn47XVu3EwbfrTENg3jFu+Oem6a/501SzITzJWtS0cn2dIFOBimTVpT/4Zv5qr +XA6Cp4biOmoTYWhi/qQl8d0IaADiqoZ1MvZbZ6x76qTrRAbg+UWkpTEXoH1xTc8n +dibR7+HP6OTqCKvo1NhE8uP4pY+fWd6b6l+KLo3IKpfTbAIJXIO+M67FLtWKtttD +ao94B069skzKk6FPgW/OZh6PRCD0oxOavV+ld2SjAgMBAAGjgcYwgcMwRwYDVR0R +BEAwPogFKgMEBQWCEm5vZGUtMC5leGFtcGxlLmNvbYIJbG9jYWxob3N0hxAAAAAA +AAAAAAAAAAAAAAABhwR/AAABMAsGA1UdDwQEAwIF4DAdBgNVHSUEFjAUBggrBgEF +BQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU0/qDQaY10jIo +wCjLUpz/HfQXyt8wHwYDVR0jBBgwFoAUF4ffoFrrZhKn1dD4uhJFPLcrAJwwDQYJ +KoZIhvcNAQELBQADggEBAD2hkndVih6TWxoe/oOW0i2Bq7ScNO/n7/yHWL04HJmR +MaHv/Xjc8zLFLgHuHaRvC02ikWIJyQf5xJt0Oqu2GVbqXH9PBGKuEP2kCsRRyU27 +zTclAzfQhqmKBTYQ/3lJ3GhRQvXIdYTe+t4aq78TCawp1nSN+vdH/1geG6QjMn5N +1FU8tovDd4x8Ib/0dv8RJx+n9gytI8n/giIaDCEbfLLpe4EkV5e5UNpOnRgJjjuy +vtZutc81TQnzBtkS9XuulovDE0qI+jQrKkKu8xgGLhgH0zxnPkKtUg2I3Aq6zl1L +zYkEOUF8Y25J6WeY88Yfnc0iigI+Pnz5NK8R9GL7TYo= -----END CERTIFICATE----- diff --git a/src/test/resources/test-kirk.jks b/src/test/resources/test-kirk.jks index 174dbda656f41b10341adb78ab91a46afaae8a1c..6dbc51e714784fa58a4209c75deab8b9ed1698ff 100644 GIT binary patch literal 4504 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 ROz*Khv5A{`^It3We*oUlR=)rM literal 3874 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 Date: Fri, 8 Sep 2023 10:20:57 -0700 Subject: [PATCH 130/149] Increment version to 2.11.0-SNAPSHOT (#520) Signed-off-by: opensearch-ci-bot Co-authored-by: opensearch-ci-bot --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 50d3732e..3d683560 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "2.10.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.11.0-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") buildVersionQualifier = System.getProperty("build.version_qualifier", "") kotlin_version = System.getProperty("kotlin.version", "1.8.21") From aaa4c2a061601105c62cbaf9838a0a4bd9986aa3 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Fri, 8 Sep 2023 14:22:48 -0700 Subject: [PATCH 131/149] Adds 2.10.0.0 release notes (#531) (#532) (cherry picked from commit 1b98f033b3e838ee19bf10b5c53abd2792d81cdb) Signed-off-by: Darshit Chanpura Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] --- ...ensearch-common-utils.release-notes-2.10.0.0.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 release-notes/opensearch-common-utils.release-notes-2.10.0.0.md diff --git a/release-notes/opensearch-common-utils.release-notes-2.10.0.0.md b/release-notes/opensearch-common-utils.release-notes-2.10.0.0.md new file mode 100644 index 00000000..435174f6 --- /dev/null +++ b/release-notes/opensearch-common-utils.release-notes-2.10.0.0.md @@ -0,0 +1,14 @@ +## Version 2.10.0.0 2023-08-31 + +Compatible with OpenSearch 2.10.0 + +### Maintenance +* Upgrade the backport workflow ([#487](https://github.com/opensearch-project/common-utils/pull/487)) +* Updates demo certs used in rest tests ([#518](https://github.com/opensearch-project/common-utils/pull/518)) + +### Feature +* common utils to support Microsoft teams in notifications ([#428](https://github.com/opensearch-project/common-utils/pull/428)) +* support list of monitor ids in Chained Monitor Findings ([#514](https://github.com/opensearch-project/common-utils/pull/514)) + +### Documentation +* Added 2.10.0.0 release notes ([#531](https://github.com/opensearch-project/common-utils/pull/531)) \ No newline at end of file From 65f39631b974a7d5b3e2d532a725c304d601d193 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Wed, 11 Oct 2023 09:24:02 -0700 Subject: [PATCH 132/149] Increment version to 2.12.0-SNAPSHOT (#545) Signed-off-by: opensearch-ci-bot Co-authored-by: opensearch-ci-bot --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3d683560..7dc55f4e 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "2.11.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.12.0-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") buildVersionQualifier = System.getProperty("build.version_qualifier", "") kotlin_version = System.getProperty("kotlin.version", "1.8.21") From 38cd12b9a02c165ef29240f5d38c07c64fe6aa3a Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Thu, 19 Oct 2023 12:11:26 -0700 Subject: [PATCH 133/149] add 'fields' parameter in doc level query object. (#546) (#550) (cherry picked from commit 2ef47f9cdcc947f35127a74ee58e4e875ffdd4d4) Signed-off-by: Surya Sashank Nistala Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] --- .../commons/alerting/model/DocLevelQuery.kt | 18 ++++++++++++++++++ .../opensearch/commons/alerting/TestHelpers.kt | 2 +- .../action/GetFindingsResponseTests.kt | 4 ++-- .../commons/alerting/model/WriteableTests.kt | 11 +++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt index a12b3240..576be89b 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt @@ -14,6 +14,7 @@ import java.util.UUID data class DocLevelQuery( val id: String = UUID.randomUUID().toString(), val name: String, + val fields: List, val query: String, val tags: List = mutableListOf() ) : BaseModel { @@ -30,6 +31,7 @@ data class DocLevelQuery( constructor(sin: StreamInput) : this( sin.readString(), // id sin.readString(), // name + sin.readStringList(), // fields sin.readString(), // query sin.readStringList() // tags ) @@ -38,6 +40,7 @@ data class DocLevelQuery( return mapOf( QUERY_ID_FIELD to id, NAME_FIELD to name, + FIELDS_FIELD to fields, QUERY_FIELD to query, TAGS_FIELD to tags ) @@ -47,6 +50,7 @@ data class DocLevelQuery( override fun writeTo(out: StreamOutput) { out.writeString(id) out.writeString(name) + out.writeStringCollection(fields) out.writeString(query) out.writeStringCollection(tags) } @@ -64,6 +68,7 @@ data class DocLevelQuery( companion object { const val QUERY_ID_FIELD = "id" const val NAME_FIELD = "name" + const val FIELDS_FIELD = "fields" const val QUERY_FIELD = "query" const val TAGS_FIELD = "tags" const val NO_ID = "" @@ -76,6 +81,7 @@ data class DocLevelQuery( lateinit var query: String lateinit var name: String val tags: MutableList = mutableListOf() + val fields: MutableList = mutableListOf() XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { @@ -101,12 +107,24 @@ data class DocLevelQuery( tags.add(tag) } } + FIELDS_FIELD -> { + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_ARRAY, + xcp.currentToken(), + xcp + ) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + val field = xcp.text() + fields.add(field) + } + } } } return DocLevelQuery( id = id, name = name, + fields = fields, query = query, tags = tags ) diff --git a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt index a4f880a9..3889d1d6 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt @@ -383,7 +383,7 @@ fun randomDocLevelQuery( name: String = "${RandomNumbers.randomIntBetween(Random(), 0, 5)}", tags: List = mutableListOf(0..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { RandomStrings.randomAsciiLettersOfLength(Random(), 10) } ): DocLevelQuery { - return DocLevelQuery(id = id, query = query, name = name, tags = tags) + return DocLevelQuery(id = id, query = query, name = name, tags = tags, fields = listOf("*")) } fun randomDocLevelMonitorInput( diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt index 8d40492a..1a8b2bdb 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt @@ -24,7 +24,7 @@ internal class GetFindingsResponseTests { "monitor_id1", "monitor_name1", "test_index1", - listOf(DocLevelQuery("1", "myQuery", "fieldA:valABC", listOf())), + listOf(DocLevelQuery("1", "myQuery", listOf(), "fieldA:valABC", listOf())), Instant.now() ) val findingDocument1 = FindingDocument("test_index1", "doc1", true, "document 1 payload") @@ -43,7 +43,7 @@ internal class GetFindingsResponseTests { "monitor_id2", "monitor_name2", "test_index2", - listOf(DocLevelQuery("1", "myQuery", "fieldA:valABC", listOf())), + listOf(DocLevelQuery("1", "myQuery", listOf(), "fieldA:valABC", listOf())), Instant.now() ) val findingDocument21 = FindingDocument("test_index2", "doc21", true, "document 21 payload") 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 b605cfbe..a66d7222 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/WriteableTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/WriteableTests.kt @@ -10,6 +10,7 @@ import org.opensearch.commons.alerting.randomAction import org.opensearch.commons.alerting.randomActionExecutionPolicy import org.opensearch.commons.alerting.randomBucketLevelTrigger import org.opensearch.commons.alerting.randomChainedAlertTrigger +import org.opensearch.commons.alerting.randomDocLevelQuery import org.opensearch.commons.alerting.randomDocumentLevelTrigger import org.opensearch.commons.alerting.randomQueryLevelMonitor import org.opensearch.commons.alerting.randomQueryLevelTrigger @@ -112,6 +113,16 @@ class WriteableTests { Assertions.assertEquals(trigger, newTrigger, "Round tripping DocumentLevelTrigger doesn't work") } + @Test + fun `test doc-level query as stream`() { + val dlq = randomDocLevelQuery() + val out = BytesStreamOutput() + dlq.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newDlq = DocLevelQuery.readFrom(sin) + Assertions.assertEquals(dlq, newDlq, "Round tripping DocLevelQuery doesn't work") + } + @Test fun `test chained alert trigger as stream`() { val trigger = randomChainedAlertTrigger() From 60a090e366bbb3c5a6605fdd22ce21a5525d2840 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Tue, 24 Oct 2023 11:49:22 -0700 Subject: [PATCH 134/149] add fields param in toxcontent() for doc level query (#549) (#554) (cherry picked from commit f290fec6c133adfac463e7a6c572bbd1bfea2266) Signed-off-by: Surya Sashank Nistala Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] --- .../commons/alerting/model/DocLevelQuery.kt | 1 + .../commons/alerting/model/XContentTests.kt | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt index 576be89b..ba3e4e4c 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt @@ -59,6 +59,7 @@ data class DocLevelQuery( builder.startObject() .field(QUERY_ID_FIELD, id) .field(NAME_FIELD, name) + .field(FIELDS_FIELD, fields.toTypedArray()) .field(QUERY_FIELD, query) .field(TAGS_FIELD, tags.toTypedArray()) .endObject() 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 a284c187..7c52ff42 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt @@ -417,6 +417,18 @@ class XContentTests { ) } + @Test + fun `test doc level query toXcontent`() { + val dlq = DocLevelQuery("id", "name", listOf("f1", "f2"), "query", listOf("t1", "t2")) + val dlqString = dlq.toXContent(builder(), ToXContent.EMPTY_PARAMS).string() + val parsedDlq = DocLevelQuery.parse(parser(dlqString)) + Assertions.assertEquals( + dlq, + parsedDlq, + "Round tripping Doc level query doesn't work" + ) + } + @Test fun `test alert parsing`() { val alert = randomAlert() From ace0d5a6bf4b489bbeaccad65f62f6ccf20df491 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Tue, 31 Oct 2023 13:05:37 -0400 Subject: [PATCH 135/149] Onboard prod jenkins docker image to github actions (#557) (#558) * Onboard prod jenkins docker image to github actions * Add more --------- (cherry picked from commit 0e2daa114d52bcc70e69d8cc8138b88825f3c257) Signed-off-by: Peter Zhu Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] --- .github/workflows/ci.yml | 46 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3a4edc7..b339ad2f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,15 +8,55 @@ on: - "*" jobs: - build: + Get-CI-Image-Tag: + uses: opensearch-project/opensearch-build/.github/workflows/get-ci-image-tag.yml@main + with: + product: opensearch + + build-linux: + needs: Get-CI-Image-Tag + strategy: + matrix: + java: + - 11 + - 17 + name: Build and Test + runs-on: ubuntu-latest + container: + # using the same image which is used by opensearch-build team to build the OpenSearch Distribution + # this image tag is subject to change as more dependencies and updates will arrive over time + image: ${{ needs.Get-CI-Image-Tag.outputs.ci-image-version-linux }} + # need to switch to root so that github actions can install runner binary on container without permission issues. + options: --user root + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup Java ${{ matrix.java }} + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.java }} + + - name: Build and Test + run: | + chown -R 1000:1000 `pwd` + su `id -un 1000` -c "./gradlew build && + ./gradlew publishToMavenLocal" + + - name: Upload Coverage Report + uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + build-windows: strategy: matrix: java: - 11 - 17 - os: [ubuntu-latest, windows-latest] name: Build and Test - runs-on: ${{ matrix.os }} + runs-on: windows-latest steps: - name: Checkout From d44f4801dcc96cadd0100bbb8c46148b54a4e876 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 08:18:06 -0800 Subject: [PATCH 136/149] Add User.isAdminDn to User class (#547) (#562) * Add User.isSuperUser to User class * Add null check * Add another test * Make method non-static and require a user to exist to call method * Change to isAdminDn --------- (cherry picked from commit 107be59161db84cf22d4478586d3008eab4cdfb3) Signed-off-by: Craig Perkins Signed-off-by: Craig Perkins Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] --- .../opensearch/commons/ConfigConstants.java | 2 + .../org/opensearch/commons/authuser/User.java | 11 +++++ .../opensearch/commons/authuser/UserTest.java | 44 +++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/src/main/java/org/opensearch/commons/ConfigConstants.java b/src/main/java/org/opensearch/commons/ConfigConstants.java index 6fc6e362..a34f7e2f 100644 --- a/src/main/java/org/opensearch/commons/ConfigConstants.java +++ b/src/main/java/org/opensearch/commons/ConfigConstants.java @@ -25,5 +25,7 @@ public class ConfigConstants { public static final String INJECTED_USER = "injected_user"; public static final String OPENSEARCH_SECURITY_USE_INJECTED_USER_FOR_PLUGINS = "plugins.security_use_injected_user_for_plugins"; public static final String OPENSEARCH_SECURITY_SSL_HTTP_ENABLED = "plugins.security.ssl.http.enabled"; + public static final String OPENSEARCH_SECURITY_AUTHCZ_ADMIN_DN = "plugins.security.authcz.admin_dn"; public static final String OPENSEARCH_SECURITY_USER_INFO_THREAD_CONTEXT = "_opendistro_security_user_info"; + } diff --git a/src/main/java/org/opensearch/commons/authuser/User.java b/src/main/java/org/opensearch/commons/authuser/User.java index ff40e243..a203b33d 100644 --- a/src/main/java/org/opensearch/commons/authuser/User.java +++ b/src/main/java/org/opensearch/commons/authuser/User.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; @@ -18,8 +19,10 @@ import org.opensearch.client.Response; import org.opensearch.common.Nullable; import org.opensearch.common.inject.internal.ToStringBuilder; +import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentHelper; import org.opensearch.common.xcontent.json.JsonXContent; +import org.opensearch.commons.ConfigConstants; import org.opensearch.core.common.Strings; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.common.io.stream.StreamOutput; @@ -249,4 +252,12 @@ public List getCustomAttNames() { public String getRequestedTenant() { return requestedTenant; } + + public boolean isAdminDn(Settings settings) { + if (settings == null) { + return false; + } + List adminDns = settings.getAsList(ConfigConstants.OPENSEARCH_SECURITY_AUTHCZ_ADMIN_DN, Collections.emptyList()); + return adminDns.contains(this.name); + } } diff --git a/src/test/java/org/opensearch/commons/authuser/UserTest.java b/src/test/java/org/opensearch/commons/authuser/UserTest.java index 56152477..df4e6602 100644 --- a/src/test/java/org/opensearch/commons/authuser/UserTest.java +++ b/src/test/java/org/opensearch/commons/authuser/UserTest.java @@ -13,11 +13,13 @@ import java.io.IOException; import java.util.Arrays; +import java.util.List; import org.junit.jupiter.api.Test; import org.opensearch.common.io.stream.BytesStreamOutput; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.commons.ConfigConstants; import org.opensearch.core.common.Strings; import org.opensearch.core.common.io.stream.StreamInput; @@ -202,4 +204,46 @@ public void testParseUserStringMalformed() { User user = User.parse(str); assertNull(user); } + + @Test + public void testUserIsAdminDnTrue() { + Settings settings = Settings + .builder() + .putList(ConfigConstants.OPENSEARCH_SECURITY_AUTHCZ_ADMIN_DN, List.of("CN=kirk,OU=client,O=client,L=test, C=de")) + .build(); + ThreadContext tc = new ThreadContext(Settings.EMPTY); + tc + .putTransient( + OPENSEARCH_SECURITY_USER_INFO_THREAD_CONTEXT, + "CN=kirk,OU=client,O=client,L=test, C=de|backendrole1,backendrole2|role1,role2" + ); + String str = tc.getTransient(OPENSEARCH_SECURITY_USER_INFO_THREAD_CONTEXT); + User user = User.parse(str); + assertTrue(user.isAdminDn(settings)); + } + + @Test + public void testUserIsAdminDnFalse() { + Settings settings = Settings + .builder() + .putList(ConfigConstants.OPENSEARCH_SECURITY_AUTHCZ_ADMIN_DN, List.of("CN=spock,OU=client,O=client,L=test, C=de")) + .build(); + ThreadContext tc = new ThreadContext(Settings.EMPTY); + tc + .putTransient( + OPENSEARCH_SECURITY_USER_INFO_THREAD_CONTEXT, + "CN=kirk,OU=client,O=client,L=test, C=de|backendrole1,backendrole2|role1,role2" + ); + String str = tc.getTransient(OPENSEARCH_SECURITY_USER_INFO_THREAD_CONTEXT); + User user = User.parse(str); + assertFalse(user.isAdminDn(settings)); + } + + @Test + public void testUserOrSettingsAreNullOrEmpty() { + Settings settings = Settings.EMPTY; + User user = User.parse("username|backend_role1|role1"); + assertFalse(user.isAdminDn(null)); + assertFalse(user.isAdminDn(settings)); + } } From 02cccc42eda796f3a712873b1b322bf91b64af70 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Thu, 16 Nov 2023 13:31:31 -0500 Subject: [PATCH 137/149] Update Gradle to 8.4 (#560) (#563) Signed-off-by: Andriy Redko --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 62076 -> 63721 bytes gradle/wrapper/gradle-wrapper.properties | 3 ++- gradlew | 22 +++++++++++++--------- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/build.gradle b/build.gradle index 7dc55f4e..9c5162bc 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ buildscript { plugins { id 'java-library' id 'maven-publish' - id 'com.diffplug.spotless' version '6.18.0' + id 'com.diffplug.spotless' version '6.22.0' } repositories { diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c1962a79e29d3e0ab67b14947c167a862655af9b..7f93135c49b765f8051ef9d0a6055ff8e46073d8 100644 GIT binary patch delta 37652 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 delta 36122 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 Ac-xW1# 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 Q>rd#Eb%7 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37aef8d3..3999f7f3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +distributionSha256Sum=3e1af3ae886920c3ac87f7a91f816c0c7c436f276a6eefdb3da152100fef72ae diff --git a/gradlew b/gradlew index aeb74cbb..1aa94a42 100755 --- a/gradlew +++ b/gradlew @@ -83,7 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -130,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -141,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -149,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -198,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ From a56599c5633b2a861ddd1e85ebf14043f582ef51 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Thu, 16 Nov 2023 10:45:45 -0800 Subject: [PATCH 138/149] Add Java 11/17/21 matrix for build, test and integration checks (#561) (#564) (cherry picked from commit aa6246dfd5e122ecdbe2f48b34a77385cbf6546d) Signed-off-by: Andriy Redko Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b339ad2f..61524b5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,7 @@ jobs: java: - 11 - 17 + - 21 name: Build and Test runs-on: ubuntu-latest container: @@ -55,6 +56,7 @@ jobs: java: - 11 - 17 + - 21 name: Build and Test runs-on: windows-latest From 8e2537b6c1cde03449f0818242a1a36089a69522 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Wed, 22 Nov 2023 09:57:00 -0800 Subject: [PATCH 139/149] Move get monitor and search monitor action / request / responses to common-utils (#566) (#567) * Add get monitor request/response * Remove status from response; add to interface * Add UT * Repeat for search monitor action --------- (cherry picked from commit 2ff995ba5a3cf68378c14f69746c1a492f6a1c47) Signed-off-by: Tyler Ohlsen Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] --- .../alerting/AlertingPluginInterface.kt | 49 +++++++ .../alerting/action/AlertingActions.kt | 11 ++ .../alerting/action/GetMonitorRequest.kt | 56 ++++++++ .../alerting/action/GetMonitorResponse.kt | 126 ++++++++++++++++++ .../alerting/action/SearchMonitorRequest.kt | 38 ++++++ .../alerting/AlertingPluginInterfaceTests.kt | 32 +++++ .../alerting/action/GetMonitorRequestTests.kt | 60 +++++++++ .../action/GetMonitorResponseTests.kt | 63 +++++++++ .../action/SearchMonitorRequestTests.kt | 33 +++++ 9 files changed, 468 insertions(+) create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/GetMonitorRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/GetMonitorResponse.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/SearchMonitorRequest.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/GetMonitorRequestTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/GetMonitorResponseTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/SearchMonitorRequestTests.kt diff --git a/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt index 5949582b..3ce81671 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/AlertingPluginInterface.kt @@ -4,6 +4,7 @@ */ package org.opensearch.commons.alerting +import org.opensearch.action.search.SearchResponse import org.opensearch.client.node.NodeClient import org.opensearch.commons.alerting.action.AcknowledgeAlertRequest import org.opensearch.commons.alerting.action.AcknowledgeAlertResponse @@ -17,6 +18,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.GetMonitorRequest +import org.opensearch.commons.alerting.action.GetMonitorResponse import org.opensearch.commons.alerting.action.GetWorkflowAlertsRequest import org.opensearch.commons.alerting.action.GetWorkflowAlertsResponse import org.opensearch.commons.alerting.action.GetWorkflowRequest @@ -26,6 +29,7 @@ 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.SearchMonitorRequest import org.opensearch.commons.alerting.action.SubscribeFindingsResponse import org.opensearch.commons.notifications.action.BaseResponse import org.opensearch.commons.utils.recreateObject @@ -288,6 +292,51 @@ object AlertingPluginInterface { ) } + /** + * Get Monitor interface. + * @param client Node client for making transport action + * @param request The request object + * @param listener The listener for getting response + */ + fun getMonitor( + client: NodeClient, + request: GetMonitorRequest, + listener: ActionListener + ) { + client.execute( + AlertingActions.GET_MONITOR_ACTION_TYPE, + request, + wrapActionListener(listener) { response -> + recreateObject(response) { + GetMonitorResponse( + it + ) + } + } + ) + } + + /** + * Search Monitors interface. + * @param client Node client for making transport action + * @param request The request object + * @param listener The listener for getting response + */ + fun searchMonitors( + client: NodeClient, + request: SearchMonitorRequest, + listener: ActionListener + ) { + client.execute( + AlertingActions.SEARCH_MONITORS_ACTION_TYPE, + request, + // we do not use the wrapActionListener in this case since there is no need + // to recreate any object or specially handle onResponse / onFailure. It is + // simply returning a SearchResponse. + listener + ) + } + @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 c2bae396..f4e68d73 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt @@ -5,6 +5,7 @@ package org.opensearch.commons.alerting.action import org.opensearch.action.ActionType +import org.opensearch.action.search.SearchResponse object AlertingActions { const val INDEX_MONITOR_ACTION_NAME = "cluster:admin/opendistro/alerting/monitor/write" @@ -18,6 +19,8 @@ object AlertingActions { 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" + const val GET_MONITOR_ACTION_NAME = "cluster:admin/opendistro/alerting/monitor/get" + const val SEARCH_MONITORS_ACTION_NAME = "cluster:admin/opendistro/alerting/monitor/search" @JvmField val INDEX_MONITOR_ACTION_TYPE = @@ -60,4 +63,12 @@ object AlertingActions { @JvmField val ACKNOWLEDGE_CHAINED_ALERTS_ACTION_TYPE = ActionType(ACKNOWLEDGE_CHAINED_ALERTS_ACTION_NAME, ::AcknowledgeAlertResponse) + + @JvmField + val GET_MONITOR_ACTION_TYPE = + ActionType(GET_MONITOR_ACTION_NAME, ::GetMonitorResponse) + + @JvmField + val SEARCH_MONITORS_ACTION_TYPE = + ActionType(SEARCH_MONITORS_ACTION_NAME, ::SearchResponse) } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetMonitorRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetMonitorRequest.kt new file mode 100644 index 00000000..6c1df281 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetMonitorRequest.kt @@ -0,0 +1,56 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.commons.alerting.action + +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionRequestValidationException +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput +import org.opensearch.rest.RestRequest +import org.opensearch.search.fetch.subphase.FetchSourceContext +import java.io.IOException + +class GetMonitorRequest : ActionRequest { + val monitorId: String + val version: Long + val method: RestRequest.Method + val srcContext: FetchSourceContext? + + constructor( + monitorId: String, + version: Long, + method: RestRequest.Method, + srcContext: FetchSourceContext? + ) : super() { + this.monitorId = monitorId + this.version = version + this.method = method + this.srcContext = srcContext + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readString(), // monitorId + sin.readLong(), // version + sin.readEnum(RestRequest.Method::class.java), // method + if (sin.readBoolean()) { + FetchSourceContext(sin) // srcContext + } else null + ) + + override fun validate(): ActionRequestValidationException? { + return null + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(monitorId) + out.writeLong(version) + out.writeEnum(method) + out.writeBoolean(srcContext != null) + srcContext?.writeTo(out) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetMonitorResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetMonitorResponse.kt new file mode 100644 index 00000000..49903853 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetMonitorResponse.kt @@ -0,0 +1,126 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.commons.alerting.action + +import org.opensearch.commons.alerting.model.Monitor +import org.opensearch.commons.alerting.util.IndexUtils.Companion._ID +import org.opensearch.commons.alerting.util.IndexUtils.Companion._PRIMARY_TERM +import org.opensearch.commons.alerting.util.IndexUtils.Companion._SEQ_NO +import org.opensearch.commons.alerting.util.IndexUtils.Companion._VERSION +import org.opensearch.commons.notifications.action.BaseResponse +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.ToXContentFragment +import org.opensearch.core.xcontent.XContentBuilder +import java.io.IOException + +class GetMonitorResponse : BaseResponse { + var id: String + var version: Long + var seqNo: Long + var primaryTerm: Long + var monitor: Monitor? + var associatedWorkflows: List? + + constructor( + id: String, + version: Long, + seqNo: Long, + primaryTerm: Long, + monitor: Monitor?, + associatedCompositeMonitors: List?, + ) : super() { + this.id = id + this.version = version + this.seqNo = seqNo + this.primaryTerm = primaryTerm + this.monitor = monitor + this.associatedWorkflows = associatedCompositeMonitors ?: emptyList() + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + id = sin.readString(), // id + version = sin.readLong(), // version + seqNo = sin.readLong(), // seqNo + primaryTerm = sin.readLong(), // primaryTerm + monitor = if (sin.readBoolean()) { + Monitor.readFrom(sin) // monitor + } else null, + associatedCompositeMonitors = sin.readList((AssociatedWorkflow)::readFrom), + ) + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(id) + out.writeLong(version) + out.writeLong(seqNo) + out.writeLong(primaryTerm) + if (monitor != null) { + out.writeBoolean(true) + monitor?.writeTo(out) + } else { + out.writeBoolean(false) + } + associatedWorkflows?.forEach { + it.writeTo(out) + } + } + + @Throws(IOException::class) + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .field(_ID, id) + .field(_VERSION, version) + .field(_SEQ_NO, seqNo) + .field(_PRIMARY_TERM, primaryTerm) + if (monitor != null) { + builder.field("monitor", monitor) + } + if (associatedWorkflows != null) { + builder.field("associated_workflows", associatedWorkflows!!.toTypedArray()) + } + return builder.endObject() + } + + class AssociatedWorkflow : ToXContentFragment { + val id: String + val name: String + + constructor(id: String, name: String) { + this.id = id + this.name = name + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params?): XContentBuilder { + builder.startObject() + .field("id", id) + .field("name", name) + .endObject() + return builder + } + + fun writeTo(out: StreamOutput) { + out.writeString(id) + out.writeString(name) + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readString(), + sin.readString() + ) + + companion object { + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): AssociatedWorkflow { + return AssociatedWorkflow(sin) + } + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/SearchMonitorRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/SearchMonitorRequest.kt new file mode 100644 index 00000000..003d3316 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/SearchMonitorRequest.kt @@ -0,0 +1,38 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.commons.alerting.action + +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionRequestValidationException +import org.opensearch.action.search.SearchRequest +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput +import java.io.IOException + +class SearchMonitorRequest : ActionRequest { + + val searchRequest: SearchRequest + + constructor( + searchRequest: SearchRequest + ) : super() { + this.searchRequest = searchRequest + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + searchRequest = SearchRequest(sin) + ) + + override fun validate(): ActionRequestValidationException? { + return null + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + searchRequest.writeTo(out) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt index 57670fc8..9b209032 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/AlertingPluginInterfaceTests.kt @@ -10,6 +10,7 @@ import org.mockito.Mockito import org.mockito.Mockito.mock import org.mockito.junit.jupiter.MockitoExtension import org.opensearch.action.ActionType +import org.opensearch.action.search.SearchResponse import org.opensearch.client.node.NodeClient import org.opensearch.common.settings.Settings import org.opensearch.commons.alerting.action.AcknowledgeAlertRequest @@ -23,6 +24,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.GetMonitorRequest +import org.opensearch.commons.alerting.action.GetMonitorResponse import org.opensearch.commons.alerting.action.GetWorkflowAlertsRequest import org.opensearch.commons.alerting.action.GetWorkflowAlertsResponse import org.opensearch.commons.alerting.action.IndexMonitorRequest @@ -30,6 +33,7 @@ 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.SearchMonitorRequest import org.opensearch.commons.alerting.action.SubscribeFindingsResponse import org.opensearch.commons.alerting.model.FindingDocument import org.opensearch.commons.alerting.model.FindingWithDocs @@ -250,4 +254,32 @@ internal class AlertingPluginInterfaceTests { AlertingPluginInterface.acknowledgeChainedAlerts(client, request, listener) Mockito.verify(listener, Mockito.times(1)).onResponse(ArgumentMatchers.eq(response)) } + + @Test + fun getMonitor() { + val request = mock(GetMonitorRequest::class.java) + val response = GetMonitorResponse("test-id", 1, 1, 1, null, null) + 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.getMonitor(client, request, listener) + Mockito.verify(listener, Mockito.times(1)).onResponse(ArgumentMatchers.eq(response)) + } + + @Test + fun searchMonitors() { + val request = mock(SearchMonitorRequest::class.java) + val response = mock(SearchResponse::class.java) + 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.searchMonitors(client, request, listener) + Mockito.verify(listener, Mockito.times(1)).onResponse(ArgumentMatchers.eq(response)) + } } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetMonitorRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetMonitorRequestTests.kt new file mode 100644 index 00000000..c916b993 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetMonitorRequestTests.kt @@ -0,0 +1,60 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.commons.alerting.action + +import org.opensearch.common.io.stream.BytesStreamOutput +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.rest.RestRequest +import org.opensearch.search.fetch.subphase.FetchSourceContext +import org.opensearch.test.OpenSearchTestCase + +class GetMonitorRequestTests : OpenSearchTestCase() { + + fun `test get monitor request`() { + + val req = GetMonitorRequest("1234", 1L, RestRequest.Method.GET, FetchSourceContext.FETCH_SOURCE) + assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = GetMonitorRequest(sin) + assertEquals("1234", newReq.monitorId) + assertEquals(1L, newReq.version) + assertEquals(RestRequest.Method.GET, newReq.method) + assertEquals(FetchSourceContext.FETCH_SOURCE, newReq.srcContext) + } + + fun `test get monitor request without src context`() { + + val req = GetMonitorRequest("1234", 1L, RestRequest.Method.GET, null) + assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = GetMonitorRequest(sin) + assertEquals("1234", newReq.monitorId) + assertEquals(1L, newReq.version) + assertEquals(RestRequest.Method.GET, newReq.method) + assertEquals(null, newReq.srcContext) + } + + fun `test head monitor request`() { + + val req = GetMonitorRequest("1234", 2L, RestRequest.Method.HEAD, FetchSourceContext.FETCH_SOURCE) + assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = GetMonitorRequest(sin) + assertEquals("1234", newReq.monitorId) + assertEquals(2L, newReq.version) + assertEquals(RestRequest.Method.HEAD, newReq.method) + assertEquals(FetchSourceContext.FETCH_SOURCE, newReq.srcContext) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetMonitorResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetMonitorResponseTests.kt new file mode 100644 index 00000000..d91c7471 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetMonitorResponseTests.kt @@ -0,0 +1,63 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.commons.alerting.action + +import org.opensearch.common.io.stream.BytesStreamOutput +import org.opensearch.commons.alerting.model.CronSchedule +import org.opensearch.commons.alerting.model.Monitor +import org.opensearch.commons.alerting.randomUser +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.test.OpenSearchTestCase +import java.time.Instant +import java.time.ZoneId + +class GetMonitorResponseTests : OpenSearchTestCase() { + + fun `test get monitor response`() { + val req = GetMonitorResponse("1234", 1L, 2L, 0L, null, null) + assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = GetMonitorResponse(sin) + assertEquals("1234", newReq.id) + assertEquals(1L, newReq.version) + assertEquals(null, newReq.monitor) + } + + fun `test get monitor response with monitor`() { + val cronExpression = "31 * * * *" // Run at minute 31. + val testInstance = Instant.ofEpochSecond(1538164858L) + + val cronSchedule = CronSchedule(cronExpression, ZoneId.of("Asia/Kolkata"), testInstance) + val monitor = Monitor( + id = "123", + version = 0L, + name = "test-monitor", + enabled = true, + schedule = cronSchedule, + lastUpdateTime = Instant.now(), + enabledTime = Instant.now(), + monitorType = Monitor.MonitorType.QUERY_LEVEL_MONITOR, + user = randomUser(), + schemaVersion = 0, + inputs = mutableListOf(), + triggers = mutableListOf(), + uiMetadata = mutableMapOf() + ) + val req = GetMonitorResponse("1234", 1L, 2L, 0L, monitor, null) + assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = GetMonitorResponse(sin) + assertEquals("1234", newReq.id) + assertEquals(1L, newReq.version) + assertNotNull(newReq.monitor) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/SearchMonitorRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/SearchMonitorRequestTests.kt new file mode 100644 index 00000000..169814ea --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/SearchMonitorRequestTests.kt @@ -0,0 +1,33 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.commons.alerting.action + +import org.opensearch.action.search.SearchRequest +import org.opensearch.common.io.stream.BytesStreamOutput +import org.opensearch.common.unit.TimeValue +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.search.builder.SearchSourceBuilder +import org.opensearch.test.OpenSearchTestCase +import org.opensearch.test.rest.OpenSearchRestTestCase +import java.util.concurrent.TimeUnit + +class SearchMonitorRequestTests : OpenSearchTestCase() { + + fun `test search monitors request`() { + val searchSourceBuilder = SearchSourceBuilder().from(0).size(100).timeout(TimeValue(60, TimeUnit.SECONDS)) + val searchRequest = SearchRequest().indices(OpenSearchRestTestCase.randomAlphaOfLength(10)).source(searchSourceBuilder) + val searchMonitorRequest = SearchMonitorRequest(searchRequest) + assertNotNull(searchMonitorRequest) + + val out = BytesStreamOutput() + searchMonitorRequest.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = SearchMonitorRequest(sin) + + assertNotNull(newReq.searchRequest) + assertEquals(1, newReq.searchRequest.indices().size) + } +} From a45dd4dc0eacff56e7c07516c33b9078617e45c8 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 12:42:13 -0800 Subject: [PATCH 140/149] [Backport 2.x] changed all usages of 'admin' as a password to something different (#583) Co-authored-by: github-actions[bot] Co-authored-by: Dennis Toepker --- .../org/opensearch/commons/rest/SecureRestClientBuilder.java | 2 +- src/test/java/org/opensearch/commons/rest/IntegrationTests.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opensearch/commons/rest/SecureRestClientBuilder.java b/src/main/java/org/opensearch/commons/rest/SecureRestClientBuilder.java index 1c747025..b88965b1 100644 --- a/src/main/java/org/opensearch/commons/rest/SecureRestClientBuilder.java +++ b/src/main/java/org/opensearch/commons/rest/SecureRestClientBuilder.java @@ -46,7 +46,7 @@ * * Other usage: * RestClient restClient = new SecureRestClientBuilder("localhost", 9200, false) - * .setUserPassword("admin", "admin") + * .setUserPassword("admin", "myStrongPassword123") * .setTrustCerts(trustStorePath) * .build(); * diff --git a/src/test/java/org/opensearch/commons/rest/IntegrationTests.java b/src/test/java/org/opensearch/commons/rest/IntegrationTests.java index 0c6b7cfe..4fc80ae9 100644 --- a/src/test/java/org/opensearch/commons/rest/IntegrationTests.java +++ b/src/test/java/org/opensearch/commons/rest/IntegrationTests.java @@ -38,7 +38,7 @@ private Request createSampleRequest() { @Test public void testCreateRestClientWithUser() throws Exception { - RestClient client = new SecureRestClientBuilder("localhost", 9200, true, "admin", "admin").build(); + RestClient client = new SecureRestClientBuilder("localhost", 9200, true, "admin", "myStrongPassword123").build(); Response response = client.performRequest(createSampleRequest()); String responseBody = EntityUtils.toString(response.getEntity()); assertEquals(200, response.getStatusLine().getStatusCode()); From 6a579cd6aa59862e2b0692b44155b6a0d1645c56 Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Tue, 6 Feb 2024 11:49:37 -0800 Subject: [PATCH 141/149] [Backport 2.x] Implemented cross-cluster monitor support #584 (#586) * Added clusters field to support cross cluster cluster metrics monitors. Signed-off-by: AWSHurneyt * Fixed writeTo. Signed-off-by: AWSHurneyt * Updated tests. Signed-off-by: AWSHurneyt * Updated tests. Signed-off-by: AWSHurneyt --------- Signed-off-by: AWSHurneyt --- .../commons/alerting/model/Alert.kt | 46 +++++++++--- .../alerting/model/ClusterMetricsInput.kt | 73 +++++++++++-------- .../opensearch/commons/alerting/AlertTests.kt | 2 + .../commons/alerting/TestHelpers.kt | 18 ++--- .../model/ClusterMetricsInputTests.kt | 10 +-- .../commons/alerting/model/XContentTests.kt | 9 ++- 6 files changed, 101 insertions(+), 57 deletions(-) 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 04df1b28..e4979407 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt @@ -43,6 +43,7 @@ data class Alert( val aggregationResultBucket: AggregationResultBucket? = null, val executionId: String? = null, val associatedAlertIds: List, + val clusters: List? = null, ) : Writeable, ToXContent { init { @@ -61,6 +62,7 @@ data class Alert( chainedAlertTrigger: ChainedAlertTrigger, workflow: Workflow, associatedAlertIds: List, + clusters: List? = null ) : this( monitorId = NO_ID, monitorName = "", @@ -82,7 +84,8 @@ data class Alert( executionId = executionId, workflowId = workflow.id, workflowName = workflow.name, - associatedAlertIds = associatedAlertIds + associatedAlertIds = associatedAlertIds, + clusters = clusters ) constructor( @@ -97,6 +100,7 @@ data class Alert( schemaVersion: Int = NO_SCHEMA_VERSION, executionId: String? = null, workflowId: String? = null, + clusters: List? = null ) : this( monitorId = monitor.id, monitorName = monitor.name, @@ -118,7 +122,8 @@ data class Alert( executionId = executionId, workflowId = workflowId ?: "", workflowName = "", - associatedAlertIds = emptyList() + associatedAlertIds = emptyList(), + clusters = clusters ) constructor( @@ -134,6 +139,7 @@ data class Alert( findingIds: List = emptyList(), executionId: String? = null, workflowId: String? = null, + clusters: List? = null ) : this( monitorId = monitor.id, monitorName = monitor.name, @@ -155,7 +161,8 @@ data class Alert( executionId = executionId, workflowId = workflowId ?: "", workflowName = "", - associatedAlertIds = emptyList() + associatedAlertIds = emptyList(), + clusters = clusters ) constructor( @@ -172,6 +179,7 @@ data class Alert( findingIds: List = emptyList(), executionId: String? = null, workflowId: String? = null, + clusters: List? = null ) : this( monitorId = monitor.id, monitorName = monitor.name, @@ -193,7 +201,8 @@ data class Alert( executionId = executionId, workflowId = workflowId ?: "", workflowName = "", - associatedAlertIds = emptyList() + associatedAlertIds = emptyList(), + clusters = clusters ) constructor( @@ -211,6 +220,7 @@ data class Alert( schemaVersion: Int = NO_SCHEMA_VERSION, executionId: String? = null, workflowId: String? = null, + clusters: List? = null ) : this( id = id, monitorId = monitor.id, @@ -233,7 +243,8 @@ data class Alert( executionId = executionId, workflowId = workflowId ?: "", workflowName = "", - associatedAlertIds = emptyList() + associatedAlertIds = emptyList(), + clusters = clusters ) constructor( @@ -248,6 +259,7 @@ data class Alert( schemaVersion: Int = NO_SCHEMA_VERSION, workflowId: String? = null, executionId: String?, + clusters: List? = null ) : this( id = id, monitorId = monitor.id, @@ -270,7 +282,8 @@ data class Alert( relatedDocIds = listOf(), workflowId = workflowId ?: "", executionId = executionId, - associatedAlertIds = emptyList() + associatedAlertIds = emptyList(), + clusters = clusters ) enum class State { @@ -311,7 +324,8 @@ data class Alert( actionExecutionResults = sin.readList(::ActionExecutionResult), aggregationResultBucket = if (sin.readBoolean()) AggregationResultBucket(sin) else null, executionId = sin.readOptionalString(), - associatedAlertIds = sin.readStringList() + associatedAlertIds = sin.readStringList(), + clusters = sin.readOptionalStringList() ) fun isAcknowledged(): Boolean = (state == State.ACKNOWLEDGED) @@ -349,6 +363,7 @@ data class Alert( } out.writeOptionalString(executionId) out.writeStringCollection(associatedAlertIds) + out.writeOptionalStringArray(clusters?.toTypedArray()) } companion object { @@ -379,6 +394,7 @@ data class Alert( 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 CLUSTERS_FIELD = "clusters" const val NO_ID = "" const val NO_VERSION = Versions.NOT_FOUND @@ -409,6 +425,7 @@ data class Alert( val actionExecutionResults: MutableList = mutableListOf() var aggAlertBucket: AggregationResultBucket? = null val associatedAlertIds = mutableListOf() + val clusters = mutableListOf() ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { val fieldName = xcp.currentName() @@ -475,6 +492,12 @@ data class Alert( AggregationResultBucket.parse(xcp) } } + CLUSTERS_FIELD -> { + ensureExpectedToken(XContentParser.Token.START_ARRAY, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + clusters.add(xcp.text()) + } + } } } @@ -503,7 +526,8 @@ data class Alert( executionId = executionId, workflowId = workflowId, workflowName = workflowName, - associatedAlertIds = associatedAlertIds + associatedAlertIds = associatedAlertIds, + clusters = if (clusters.size > 0) clusters else null ) } @@ -553,6 +577,9 @@ data class Alert( .optionalTimeField(END_TIME_FIELD, endTime) .optionalTimeField(ACKNOWLEDGED_TIME_FIELD, acknowledgedTime) aggregationResultBucket?.innerXContent(builder) + + if (!clusters.isNullOrEmpty()) builder.field(CLUSTERS_FIELD, clusters.toTypedArray()) + builder.endObject() return builder } @@ -576,7 +603,8 @@ data class Alert( BUCKET_KEYS to aggregationResultBucket?.bucketKeys?.joinToString(","), PARENTS_BUCKET_PATH to aggregationResultBucket?.parentBucketPath, FINDING_IDS to findingIds.joinToString(","), - RELATED_DOC_IDS to relatedDocIds.joinToString(",") + RELATED_DOC_IDS to relatedDocIds.joinToString(","), + CLUSTERS_FIELD to clusters?.joinToString(",") ) } } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt index 81432546..0c38f8a5 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt @@ -13,6 +13,7 @@ import org.opensearch.core.xcontent.XContentParser import org.opensearch.core.xcontent.XContentParserUtils import java.io.IOException import java.net.URI +import java.net.URISyntaxException val ILLEGAL_PATH_PARAMETER_CHARACTERS = arrayOf(':', '"', '+', '\\', '|', '?', '#', '>', '<', ' ') @@ -22,7 +23,8 @@ val ILLEGAL_PATH_PARAMETER_CHARACTERS = arrayOf(':', '"', '+', '\\', '|', '?', ' data class ClusterMetricsInput( var path: String, var pathParams: String = "", - var url: String + var url: String, + var clusters: List = listOf() ) : Input { val clusterMetricType: ClusterMetricType val constructedUri: URI @@ -43,11 +45,10 @@ data class ClusterMetricsInput( "Invalid URI constructed from the path and path_params inputs, or the url input." } - if (url.isNotEmpty() && validateFieldsNotEmpty()) { + if (url.isNotEmpty() && validateFieldsNotEmpty()) require(constructedUri == constructUrlFromInputs()) { "The provided URL and URI fields form different URLs." } - } require(constructedUri.host.lowercase() == SUPPORTED_HOST) { "Only host '$SUPPORTED_HOST' is supported." @@ -74,6 +75,7 @@ data class ClusterMetricsInput( .field(PATH_FIELD, path) .field(PATH_PARAMS_FIELD, pathParams) .field(URL_FIELD, url) + .field(CLUSTERS_FIELD, clusters) .endObject() .endObject() } @@ -87,6 +89,7 @@ data class ClusterMetricsInput( out.writeString(path) out.writeString(pathParams) out.writeString(url) + out.writeStringArray(clusters.toTypedArray()) } companion object { @@ -99,18 +102,19 @@ data class ClusterMetricsInput( const val PATH_PARAMS_FIELD = "path_params" const val URL_FIELD = "url" const val URI_FIELD = "uri" + const val CLUSTERS_FIELD = "clusters" val XCONTENT_REGISTRY = NamedXContentRegistry.Entry(Input::class.java, ParseField(URI_FIELD), CheckedFunction { parseInner(it) }) /** * This parse function uses [XContentParser] to parse JSON input and store corresponding fields to create a [ClusterMetricsInput] object */ - @JvmStatic - @Throws(IOException::class) + @JvmStatic @Throws(IOException::class) fun parseInner(xcp: XContentParser): ClusterMetricsInput { var path = "" var pathParams = "" var url = "" + val clusters = mutableListOf() XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) @@ -121,9 +125,17 @@ data class ClusterMetricsInput( PATH_FIELD -> path = xcp.text() PATH_PARAMS_FIELD -> pathParams = xcp.text() URL_FIELD -> url = xcp.text() + CLUSTERS_FIELD -> { + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_ARRAY, + xcp.currentToken(), + xcp + ) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) clusters.add(xcp.text()) + } } } - return ClusterMetricsInput(path, pathParams, url) + return ClusterMetricsInput(path, pathParams, url, clusters) } } @@ -163,20 +175,17 @@ data class ClusterMetricsInput( if (pathParams.isNotEmpty()) { pathParams = pathParams.trim('/') ILLEGAL_PATH_PARAMETER_CHARACTERS.forEach { character -> - if (pathParams.contains(character)) { + if (pathParams.contains(character)) throw IllegalArgumentException( - "The provided path parameters contain invalid characters or spaces. Please omit: " + "${ILLEGAL_PATH_PARAMETER_CHARACTERS.joinToString(" ")}" + "The provided path parameters contain invalid characters or spaces. Please omit: " + ILLEGAL_PATH_PARAMETER_CHARACTERS.joinToString(" ") ) - } } } - if (apiType.requiresPathParams && pathParams.isEmpty()) { + if (apiType.requiresPathParams && pathParams.isEmpty()) throw IllegalArgumentException("The API requires path parameters.") - } - if (!apiType.supportsPathParams && pathParams.isNotEmpty()) { + if (!apiType.supportsPathParams && pathParams.isNotEmpty()) throw IllegalArgumentException("The API does not use path parameters.") - } return pathParams } @@ -192,13 +201,11 @@ data class ClusterMetricsInput( ClusterMetricType.values() .filter { option -> option != ClusterMetricType.BLANK } .forEach { option -> - if (uriPath.startsWith(option.prependPath) || uriPath.startsWith(option.defaultPath)) { + if (uriPath.startsWith(option.prependPath) || uriPath.startsWith(option.defaultPath)) apiType = option - } } - if (apiType.isBlank()) { + if (apiType.isBlank()) throw IllegalArgumentException("The API could not be determined from the provided URI.") - } return apiType } @@ -207,12 +214,23 @@ data class ClusterMetricsInput( * @return The constructed [URI]. */ private fun constructUrlFromInputs(): URI { - val uriBuilder = URIBuilder() - .setScheme(SUPPORTED_SCHEME) - .setHost(SUPPORTED_HOST) - .setPort(SUPPORTED_PORT) - .setPath(path + pathParams) - return uriBuilder.build() + /** + * this try-catch block is required due to a httpcomponents 5.1.x library issue + * it auto encodes path params in the url. + */ + return try { + val formattedPath = if (path.startsWith("/") || path.isBlank()) path else "/$path" + val formattedPathParams = if (pathParams.startsWith("/") || pathParams.isBlank()) pathParams else "/$pathParams" + val uriBuilder = URIBuilder("$SUPPORTED_SCHEME://$SUPPORTED_HOST:$SUPPORTED_PORT$formattedPath$formattedPathParams") + uriBuilder.build() + } catch (ex: URISyntaxException) { + val uriBuilder = URIBuilder() + .setScheme(SUPPORTED_SCHEME) + .setHost(SUPPORTED_HOST) + .setPort(SUPPORTED_PORT) + .setPath(path + pathParams) + uriBuilder.build() + } } /** @@ -220,15 +238,12 @@ data class ClusterMetricsInput( * If [path] and [pathParams] are empty, populates them with values from [url]. */ private fun parseEmptyFields() { - if (pathParams.isEmpty()) { + if (pathParams.isEmpty()) pathParams = this.parsePathParams() - } - if (path.isEmpty()) { + if (path.isEmpty()) path = if (pathParams.isEmpty()) clusterMetricType.defaultPath else clusterMetricType.prependPath - } - if (url.isEmpty()) { + if (url.isEmpty()) url = constructedUri.toString() - } } /** diff --git a/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt index cf6a4947..8b4db7c4 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt @@ -23,6 +23,7 @@ class AlertTests { assertEquals(templateArgs[Alert.START_TIME_FIELD], alert.startTime.toEpochMilli(), "Template args start time does not") assertEquals(templateArgs[Alert.LAST_NOTIFICATION_TIME_FIELD], null, "Template args last notification time does not match") assertEquals(templateArgs[Alert.SEVERITY_FIELD], alert.severity, "Template args severity does not match") + assertEquals(templateArgs[Alert.CLUSTERS_FIELD], alert.clusters?.joinToString(","), "Template args clusters does not match") } @Test @@ -40,6 +41,7 @@ class AlertTests { assertEquals(templateArgs[Alert.START_TIME_FIELD], alert.startTime.toEpochMilli(), "Template args start time does not") assertEquals(templateArgs[Alert.LAST_NOTIFICATION_TIME_FIELD], null, "Template args last notification time does not match") assertEquals(templateArgs[Alert.SEVERITY_FIELD], alert.severity, "Template args severity does not match") + assertEquals(templateArgs[Alert.CLUSTERS_FIELD], alert.clusters?.joinToString(","), "Template args clusters does not match") assertEquals( templateArgs[Alert.BUCKET_KEYS], alert.aggregationResultBucket?.bucketKeys?.joinToString(","), diff --git a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt index 3889d1d6..b4337e7f 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt @@ -286,9 +286,9 @@ fun randomDocumentLevelTrigger( name = name, severity = severity, condition = condition, - actions = if (actions.isEmpty() && destinationId.isNotBlank()) { + actions = if (actions.isEmpty() && destinationId.isNotBlank()) (0..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { randomAction(destinationId = destinationId) } - } else actions + else actions ) } @@ -527,12 +527,11 @@ fun assertUserNull(monitor: Monitor) { fun randomAlert(monitor: Monitor = randomQueryLevelMonitor()): Alert { val trigger = randomQueryLevelTrigger() val actionExecutionResults = mutableListOf(randomActionExecutionResult(), randomActionExecutionResult()) + val clusterCount = (-1..5).random() + val clusters = if (clusterCount == -1) null else (0..clusterCount).map { "index-$it" } return Alert( - monitor, - trigger, - Instant.now().truncatedTo(ChronoUnit.MILLIS), - null, - actionExecutionResults = actionExecutionResults + monitor, trigger, Instant.now().truncatedTo(ChronoUnit.MILLIS), null, + actionExecutionResults = actionExecutionResults, clusters = clusters ) } @@ -562,10 +561,7 @@ fun randomAlertWithAggregationResultBucket(monitor: Monitor = randomBucketLevelM val trigger = randomBucketLevelTrigger() val actionExecutionResults = mutableListOf(randomActionExecutionResult(), randomActionExecutionResult()) return Alert( - monitor, - trigger, - Instant.now().truncatedTo(ChronoUnit.MILLIS), - null, + monitor, trigger, Instant.now().truncatedTo(ChronoUnit.MILLIS), null, actionExecutionResults = actionExecutionResults, aggregationResultBucket = AggregationResultBucket( "parent_bucket_path_1", diff --git a/src/test/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInputTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInputTests.kt index d9dcd1f3..9980d5db 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInputTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInputTests.kt @@ -89,7 +89,7 @@ class ClusterMetricsInputTests { @Test fun `test url field and URI component fields with path params create equal URI`() { // GIVEN - path = "/_cluster/health/" + path = "/_cluster/health" pathParams = "index1,index2,index3,index4,index5" url = "http://localhost:9200/_cluster/health/index1,index2,index3,index4,index5" @@ -205,7 +205,7 @@ class ClusterMetricsInputTests { @Test fun `test parsePathParams with path params as URI field`() { // GIVEN - path = "/_cluster/health/" + path = "/_cluster/health" pathParams = "index1,index2,index3,index4,index5" val testUrl = "http://localhost:9200/_cluster/health/index1,index2,index3,index4,index5" val clusterMetricsInput = ClusterMetricsInput(path, pathParams, url) @@ -268,7 +268,7 @@ class ClusterMetricsInputTests { // WHEN + THEN assertFailsWith( - "The provided path parameters contain invalid characters or spaces. Please omit: " + "${ILLEGAL_PATH_PARAMETER_CHARACTERS.joinToString(" ")}" + "The provided path parameters contain invalid characters or spaces. Please omit: " + ILLEGAL_PATH_PARAMETER_CHARACTERS.joinToString(" ") ) { clusterMetricsInput.parsePathParams() } @@ -427,9 +427,9 @@ class ClusterMetricsInputTests { @Test fun `test parseEmptyFields populates empty url field when path and path_params are provided`() { // GIVEN - path = "/_cluster/health/" + path = "/_cluster/health" pathParams = "index1,index2,index3,index4,index5" - val testUrl = "http://localhost:9200$path$pathParams" + val testUrl = "http://localhost:9200$path/$pathParams" // WHEN val clusterMetricsInput = ClusterMetricsInput(path, pathParams, url) 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 7c52ff42..e56d4aab 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt @@ -450,7 +450,8 @@ class XContentTests { errorMessage = "some error", lastNotificationTime = Instant.now(), workflowId = "", - executionId = "" + executionId = "", + clusters = listOf() ) assertEquals("Round tripping alert doesn't work", alert.triggerName, "NoOp trigger") } @@ -462,7 +463,8 @@ class XContentTests { "\"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}" + "\"start_time\":1601917224599,\"last_notification_time\":null,\"end_time\":null,\"acknowledged_time\":null," + + "\"clusters\":[\"cluster-1\",\"cluster-2\"]}" val parsedAlert = Alert.parse(parser(alertStr)) OpenSearchTestCase.assertNull(parsedAlert.monitorUser) } @@ -475,7 +477,8 @@ class XContentTests { "\"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}" + "\"start_time\":1601917224599,\"last_notification_time\":null,\"end_time\":null,\"acknowledged_time\":null," + + "\"clusters\":[\"cluster-1\",\"cluster-2\"]}" val parsedAlert = Alert.parse(parser(alertStr)) OpenSearchTestCase.assertNull(parsedAlert.monitorUser) } From fed683893f43274171f8615174f98d82d7b54f1f Mon Sep 17 00:00:00 2001 From: Joanne Wang Date: Tue, 6 Feb 2024 15:02:47 -0800 Subject: [PATCH 142/149] [Backport 2.x] Update dependency com.pinterest:ktlint to 0.47.1 and fix CVE-2023-6378 for common-utils (#588) * add logback-classic for CVE-2023-6378 Signed-off-by: Joanne Wang * updated com.pinterest:ktlint to v0.47.1 Signed-off-by: jowg-amazon * ran ./gradlew ktlintformat Signed-off-by: jowg-amazon --------- Signed-off-by: Joanne Wang Signed-off-by: jowg-amazon --- build.gradle | 11 ++- .../action/AcknowledgeChainedAlertRequest.kt | 4 +- .../alerting/action/AlertingActions.kt | 2 + .../alerting/action/DeleteWorkflowRequest.kt | 1 + .../alerting/action/GetMonitorRequest.kt | 4 +- .../alerting/action/GetMonitorResponse.kt | 8 +- .../action/GetWorkflowAlertsRequest.kt | 2 +- .../action/GetWorkflowAlertsResponse.kt | 1 + .../alerting/action/GetWorkflowResponse.kt | 7 +- .../alerting/action/IndexWorkflowRequest.kt | 22 +++-- .../BucketSelectorExtAggregator.kt | 4 +- .../commons/alerting/model/Alert.kt | 19 ++-- .../alerting/model/ChainedMonitorFindings.kt | 5 +- .../alerting/model/ClusterMetricsInput.kt | 30 ++++--- .../commons/alerting/model/CompositeInput.kt | 5 +- .../commons/alerting/model/Delegate.kt | 6 +- .../commons/alerting/model/Finding.kt | 2 +- .../commons/alerting/model/Monitor.kt | 18 ++-- .../commons/alerting/model/NoOpTrigger.kt | 8 +- .../commons/alerting/model/Sequence.kt | 2 +- .../commons/alerting/model/Workflow.kt | 8 +- .../commons/alerting/model/action/Action.kt | 4 +- .../opensearch/commons/alerting/AlertTests.kt | 9 +- .../commons/alerting/TestHelpers.kt | 27 ++++-- .../action/AcknowledgeAlertResponseTests.kt | 1 - .../action/DeleteMonitorRequestTests.kt | 1 - .../action/DeleteWorkflowRequestTests.kt | 1 - .../alerting/action/GetAlertsRequestTests.kt | 4 +- .../action/GetFindingsRequestTests.kt | 1 - .../action/GetFindingsResponseTests.kt | 1 - .../alerting/action/GetMonitorRequestTests.kt | 3 - .../action/GetWorkflowAlertsRequestTests.kt | 2 - .../action/GetWorkflowResponseTests.kt | 14 ++- .../action/IndexMonitorRequestTests.kt | 26 ++++-- .../action/IndexWorkflowRequestTests.kt | 90 +++++++++++++++---- .../action/PublishFindingsRequestTests.kt | 1 - .../commons/alerting/model/FindingTests.kt | 2 +- .../commons/alerting/model/XContentTests.kt | 6 +- .../NotificationsPluginInterfaceTests.kt | 6 +- .../CreateNotificationConfigRequestTests.kt | 3 + .../action/SendNotificationResponseTests.kt | 2 - .../UpdateNotificationConfigRequestTests.kt | 2 + .../model/NotificationConfigTests.kt | 1 - .../notifications/model/SmtpAccountTests.kt | 3 +- .../model/config/ConfigPropertiesTests.kt | 2 + 45 files changed, 264 insertions(+), 117 deletions(-) diff --git a/build.gradle b/build.gradle index 9c5162bc..fb8a5abe 100644 --- a/build.gradle +++ b/build.gradle @@ -66,7 +66,12 @@ apply plugin: 'opensearch.repositories' apply from: 'build-tools/opensearchplugin-coverage.gradle' configurations { - ktlint + ktlint { + resolutionStrategy { + force "ch.qos.logback:logback-classic:1.3.14" + force "ch.qos.logback:logback-core:1.3.14" + } + } } dependencies { @@ -86,7 +91,7 @@ dependencies { testImplementation "commons-validator:commons-validator:1.7" testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2' - ktlint "com.pinterest:ktlint:0.44.0" + ktlint "com.pinterest:ktlint:0.47.1" } test { @@ -226,4 +231,4 @@ task updateVersion { // Include the required files that needs to be updated with new Version ant.replaceregexp(file:'build.gradle', match: '"opensearch.version", "\\d.*"', replace: '"opensearch.version", "' + newVersion.tokenize('-')[0] + '-SNAPSHOT"', flags:'g', byline:true) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequest.kt index 2c482f26..81d1fef6 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/AcknowledgeChainedAlertRequest.kt @@ -19,7 +19,7 @@ class AcknowledgeChainedAlertRequest : ActionRequest { constructor( workflowId: String, - alertIds: List, + alertIds: List ) : super() { this.workflowId = workflowId this.alertIds = alertIds @@ -28,7 +28,7 @@ class AcknowledgeChainedAlertRequest : ActionRequest { @Throws(IOException::class) constructor(sin: StreamInput) : this( sin.readString(), // workflowId - Collections.unmodifiableList(sin.readStringList()), // alertIds + Collections.unmodifiableList(sin.readStringList()) // alertIds ) override fun validate(): ActionRequestValidationException? { 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 f4e68d73..f2ada6a5 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt @@ -29,6 +29,7 @@ object AlertingActions { @JvmField val INDEX_WORKFLOW_ACTION_TYPE = ActionType(INDEX_WORKFLOW_ACTION_NAME, ::IndexWorkflowResponse) + @JvmField val GET_ALERTS_ACTION_TYPE = ActionType(GET_ALERTS_ACTION_NAME, ::GetAlertsResponse) @@ -48,6 +49,7 @@ object AlertingActions { @JvmField val DELETE_WORKFLOW_ACTION_TYPE = ActionType(DELETE_WORKFLOW_ACTION_NAME, ::DeleteWorkflowResponse) + @JvmField val GET_FINDINGS_ACTION_TYPE = ActionType(GET_FINDINGS_ACTION_NAME, ::GetFindingsResponse) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequest.kt index 10189e89..a3907e83 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequest.kt @@ -9,6 +9,7 @@ import java.io.IOException class DeleteWorkflowRequest : ActionRequest { val workflowId: String + /** * Flag that indicates whether the delegate monitors should be deleted or not. * If the flag is set to true, Delegate monitors will be deleted only in the case when they are part of the specified workflow and no other. diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetMonitorRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetMonitorRequest.kt index 6c1df281..80676a04 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetMonitorRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetMonitorRequest.kt @@ -38,7 +38,9 @@ class GetMonitorRequest : ActionRequest { sin.readEnum(RestRequest.Method::class.java), // method if (sin.readBoolean()) { FetchSourceContext(sin) // srcContext - } else null + } else { + null + } ) override fun validate(): ActionRequestValidationException? { diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetMonitorResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetMonitorResponse.kt index 49903853..7984ed07 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetMonitorResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetMonitorResponse.kt @@ -32,7 +32,7 @@ class GetMonitorResponse : BaseResponse { seqNo: Long, primaryTerm: Long, monitor: Monitor?, - associatedCompositeMonitors: List?, + associatedCompositeMonitors: List? ) : super() { this.id = id this.version = version @@ -50,8 +50,10 @@ class GetMonitorResponse : BaseResponse { primaryTerm = sin.readLong(), // primaryTerm monitor = if (sin.readBoolean()) { Monitor.readFrom(sin) // monitor - } else null, - associatedCompositeMonitors = sin.readList((AssociatedWorkflow)::readFrom), + } else { + null + }, + associatedCompositeMonitors = sin.readList((AssociatedWorkflow)::readFrom) ) @Throws(IOException::class) 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 bfa80044..9d08fa96 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequest.kt @@ -27,7 +27,7 @@ class GetWorkflowAlertsRequest : ActionRequest { monitorIds: List? = null, workflowIds: List? = null, alertIds: List? = null, - getAssociatedAlerts: Boolean, + getAssociatedAlerts: Boolean ) : super() { this.table = table this.severityLevel = severityLevel diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsResponse.kt index 4856d747..5104f344 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsResponse.kt @@ -12,6 +12,7 @@ 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? diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponse.kt index 67bad9d0..ca6db115 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponse.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponse.kt @@ -51,7 +51,9 @@ class GetWorkflowResponse : BaseResponse { sin.readEnum(RestStatus::class.java), // RestStatus if (sin.readBoolean()) { Workflow.readFrom(sin) // monitor - } else null + } else { + null + } ) @Throws(IOException::class) @@ -76,8 +78,9 @@ class GetWorkflowResponse : BaseResponse { .field(_VERSION, version) .field(_SEQ_NO, seqNo) .field(_PRIMARY_TERM, primaryTerm) - if (workflow != null) + if (workflow != null) { builder.field("workflow", workflow) + } return builder.endObject() } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequest.kt index 6fe9c47b..1033e651 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequest.kt @@ -57,19 +57,22 @@ class IndexWorkflowRequest : ActionRequest { if (workflow.inputs.isEmpty()) { validationException = ValidateActions.addValidationError( - "Input list can not be empty.", validationException + "Input list can not be empty.", + validationException ) return validationException } if (workflow.inputs.size > 1) { validationException = ValidateActions.addValidationError( - "Input list can contain only one element.", validationException + "Input list can contain only one element.", + validationException ) return validationException } if (workflow.inputs[0] !is CompositeInput) { validationException = ValidateActions.addValidationError( - "When creating a workflow input must be CompositeInput", validationException + "When creating a workflow input must be CompositeInput", + validationException ) } val compositeInput = workflow.inputs[0] as CompositeInput @@ -77,7 +80,8 @@ class IndexWorkflowRequest : ActionRequest { if (monitorIds.isNullOrEmpty()) { validationException = ValidateActions.addValidationError( - "Delegates list can not be empty.", validationException + "Delegates list can not be empty.", + validationException ) // Break the flow because next checks are dependant on non-null monitorIds return validationException @@ -85,27 +89,29 @@ class IndexWorkflowRequest : ActionRequest { if (monitorIds.size > MAX_DELEGATE_SIZE) { validationException = ValidateActions.addValidationError( - "Delegates list can not be larger then $MAX_DELEGATE_SIZE.", validationException + "Delegates list can not be larger then $MAX_DELEGATE_SIZE.", + validationException ) } if (monitorIds.toSet().size != monitorIds.size) { validationException = ValidateActions.addValidationError( - "Duplicate delegates not allowed", validationException + "Duplicate delegates not allowed", + validationException ) } val delegates = compositeInput.sequence.delegates val orderSet = delegates.stream().filter { it.order > 0 }.map { it.order }.collect(Collectors.toSet()) if (orderSet.size != delegates.size) { validationException = ValidateActions.addValidationError( - "Sequence ordering of delegate monitor shouldn't contain duplicate order values", validationException + "Sequence ordering of delegate monitor shouldn't contain duplicate order values", + validationException ) } val monitorIdOrderMap: Map = delegates.associate { it.monitorId to it.order } delegates.forEach { if (it.chainedMonitorFindings != null) { - if (it.chainedMonitorFindings.monitorId != null) { if (monitorIdOrderMap.containsKey(it.chainedMonitorFindings.monitorId) == false) { validationException = ValidateActions.addValidationError( diff --git a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregator.kt b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregator.kt index 133ebc89..68017856 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregator.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/aggregation/bucketselectorext/BucketSelectorExtAggregator.kt @@ -33,7 +33,7 @@ class BucketSelectorExtAggregator : SiblingPipelineAggregator { script: Script, gapPolicy: BucketHelpers.GapPolicy, filter: BucketSelectorExtFilter?, - metadata: Map?, + metadata: Map? ) : super(name, bucketsPathsMap.values.toTypedArray(), metadata) { this.bucketsPathsMap = bucketsPathsMap this.parentBucketPath = parentBucketPath @@ -132,7 +132,7 @@ class BucketSelectorExtAggregator : SiblingPipelineAggregator { name(), parentBucketPath, selectedBucketsIndex, - originalAgg.metadata, + originalAgg.metadata ) } 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 e4979407..e435c866 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Alert.kt @@ -43,12 +43,14 @@ data class Alert( val aggregationResultBucket: AggregationResultBucket? = null, val executionId: String? = null, val associatedAlertIds: List, - val clusters: List? = null, + val clusters: List? = null ) : Writeable, ToXContent { init { - if (errorMessage != null) require(state == State.DELETED || state == State.ERROR || state == State.AUDIT) { - "Attempt to create an alert with an error in state: $state" + if (errorMessage != null) { + require(state == State.DELETED || state == State.ERROR || state == State.AUDIT) { + "Attempt to create an alert with an error in state: $state" + } } } @@ -308,7 +310,9 @@ data class Alert( monitorVersion = sin.readLong(), monitorUser = if (sin.readBoolean()) { User(sin) - } else null, + } else { + null + }, triggerId = sin.readString(), triggerName = sin.readString(), findingIds = sin.readStringList(), @@ -439,8 +443,11 @@ data class Alert( 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) + 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/ChainedMonitorFindings.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/ChainedMonitorFindings.kt index 92192eec..a1b6435a 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/ChainedMonitorFindings.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/ChainedMonitorFindings.kt @@ -17,7 +17,7 @@ import java.util.Collections // TODO - Remove the class and move the monitorId to Delegate (as a chainedMonitorId property) if this class won't be updated by adding new properties data class ChainedMonitorFindings( val monitorId: String? = null, - val monitorIds: List = emptyList(), // if monitorId field is non-null it would be given precendence for BWC + val monitorIds: List = emptyList() // if monitorId field is non-null it would be given precendence for BWC ) : BaseModel { init { @@ -75,8 +75,9 @@ data class ChainedMonitorFindings( when (fieldName) { MONITOR_ID_FIELD -> { - if (!xcp.currentToken().equals(XContentParser.Token.VALUE_NULL)) + if (!xcp.currentToken().equals(XContentParser.Token.VALUE_NULL)) { monitorId = xcp.text() + } } MONITOR_IDS_FIELD -> { diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt index 0c38f8a5..f834b435 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/ClusterMetricsInput.kt @@ -45,10 +45,11 @@ data class ClusterMetricsInput( "Invalid URI constructed from the path and path_params inputs, or the url input." } - if (url.isNotEmpty() && validateFieldsNotEmpty()) + if (url.isNotEmpty() && validateFieldsNotEmpty()) { require(constructedUri == constructUrlFromInputs()) { "The provided URL and URI fields form different URLs." } + } require(constructedUri.host.lowercase() == SUPPORTED_HOST) { "Only host '$SUPPORTED_HOST' is supported." @@ -109,7 +110,8 @@ data class ClusterMetricsInput( /** * This parse function uses [XContentParser] to parse JSON input and store corresponding fields to create a [ClusterMetricsInput] object */ - @JvmStatic @Throws(IOException::class) + @JvmStatic + @Throws(IOException::class) fun parseInner(xcp: XContentParser): ClusterMetricsInput { var path = "" var pathParams = "" @@ -175,17 +177,20 @@ data class ClusterMetricsInput( if (pathParams.isNotEmpty()) { pathParams = pathParams.trim('/') ILLEGAL_PATH_PARAMETER_CHARACTERS.forEach { character -> - if (pathParams.contains(character)) + if (pathParams.contains(character)) { throw IllegalArgumentException( "The provided path parameters contain invalid characters or spaces. Please omit: " + ILLEGAL_PATH_PARAMETER_CHARACTERS.joinToString(" ") ) + } } } - if (apiType.requiresPathParams && pathParams.isEmpty()) + if (apiType.requiresPathParams && pathParams.isEmpty()) { throw IllegalArgumentException("The API requires path parameters.") - if (!apiType.supportsPathParams && pathParams.isNotEmpty()) + } + if (!apiType.supportsPathParams && pathParams.isNotEmpty()) { throw IllegalArgumentException("The API does not use path parameters.") + } return pathParams } @@ -201,11 +206,13 @@ data class ClusterMetricsInput( ClusterMetricType.values() .filter { option -> option != ClusterMetricType.BLANK } .forEach { option -> - if (uriPath.startsWith(option.prependPath) || uriPath.startsWith(option.defaultPath)) + if (uriPath.startsWith(option.prependPath) || uriPath.startsWith(option.defaultPath)) { apiType = option + } } - if (apiType.isBlank()) + if (apiType.isBlank()) { throw IllegalArgumentException("The API could not be determined from the provided URI.") + } return apiType } @@ -238,12 +245,15 @@ data class ClusterMetricsInput( * If [path] and [pathParams] are empty, populates them with values from [url]. */ private fun parseEmptyFields() { - if (pathParams.isEmpty()) + if (pathParams.isEmpty()) { pathParams = this.parsePathParams() - if (path.isEmpty()) + } + if (path.isEmpty()) { path = if (pathParams.isEmpty()) clusterMetricType.defaultPath else clusterMetricType.prependPath - if (url.isEmpty()) + } + if (url.isEmpty()) { url = constructedUri.toString() + } } /** diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/CompositeInput.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/CompositeInput.kt index 7487232b..0f1e3e12 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/CompositeInput.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/CompositeInput.kt @@ -12,7 +12,7 @@ import org.opensearch.core.xcontent.XContentParserUtils import java.io.IOException data class CompositeInput( - val sequence: Sequence, + val sequence: Sequence ) : WorkflowInput { @Throws(IOException::class) constructor(sin: StreamInput) : this( @@ -53,7 +53,8 @@ data class CompositeInput( val XCONTENT_REGISTRY = NamedXContentRegistry.Entry( WorkflowInput::class.java, - ParseField(COMPOSITE_INPUT_FIELD), CheckedFunction { CompositeInput.parse(it) } + ParseField(COMPOSITE_INPUT_FIELD), + CheckedFunction { CompositeInput.parse(it) } ) @JvmStatic diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Delegate.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Delegate.kt index 7b36ed88..e32ae78d 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Delegate.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Delegate.kt @@ -42,13 +42,15 @@ data class Delegate( monitorId = sin.readString(), chainedMonitorFindings = if (sin.readBoolean()) { ChainedMonitorFindings(sin) - } else null, + } else { + null + } ) fun asTemplateArg(): Map { return mapOf( ORDER_FIELD to order, - MONITOR_ID_FIELD to monitorId, + MONITOR_ID_FIELD to monitorId ) } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt index 16bf14f8..d6436f8b 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Finding.kt @@ -27,7 +27,7 @@ class Finding( * Keeps the track of the workflow-monitor exact execution. * Used for filtering the data when chaining monitors in a workflow. */ - val executionId: String? = null, + val executionId: String? = null ) : Writeable, ToXContent { constructor( 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 1adfa958..b2099d93 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Monitor.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Monitor.kt @@ -97,7 +97,9 @@ data class Monitor( monitorType = sin.readEnum(MonitorType::class.java), user = if (sin.readBoolean()) { User(sin) - } else null, + } else { + null + }, schemaVersion = sin.readInt(), inputs = sin.readList((Input)::readFrom), triggers = sin.readList((Trigger)::readFrom), @@ -184,8 +186,11 @@ data class Monitor( // Outputting type with each Input so that the generic Input.readFrom() can read it out.writeVInt(inputs.size) inputs.forEach { - if (it is SearchInput) out.writeEnum(Input.Type.SEARCH_INPUT) - else out.writeEnum(Input.Type.DOCUMENT_LEVEL_INPUT) + if (it is SearchInput) { + out.writeEnum(Input.Type.SEARCH_INPUT) + } else { + out.writeEnum(Input.Type.DOCUMENT_LEVEL_INPUT) + } it.writeTo(out) } // Outputting type with each Trigger so that the generic Trigger.readFrom() can read it @@ -295,8 +300,11 @@ data class Monitor( ENABLED_TIME_FIELD -> enabledTime = xcp.instant() LAST_UPDATE_TIME_FIELD -> lastUpdateTime = xcp.instant() UI_METADATA_FIELD -> uiMetadata = xcp.map() - DATA_SOURCES_FIELD -> dataSources = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) DataSources() - else DataSources.parse(xcp) + DATA_SOURCES_FIELD -> dataSources = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) { + DataSources() + } else { + DataSources.parse(xcp) + } OWNER_FIELD -> owner = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) owner else xcp.text() else -> { xcp.skipChildren() 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 f0d08cbb..3ffacb6e 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/NoOpTrigger.kt @@ -17,7 +17,7 @@ data class NoOpTrigger( override val id: String = UUIDs.base64UUID(), override val name: String = "NoOp trigger", override val severity: String = "", - override val actions: List = listOf(), + override val actions: List = listOf() ) : Trigger { @Throws(IOException::class) @@ -48,11 +48,13 @@ data class NoOpTrigger( const val ID_FIELD = "id" const val NOOP_TRIGGER_FIELD = "noop_trigger" val XCONTENT_REGISTRY = NamedXContentRegistry.Entry( - Trigger::class.java, ParseField(NOOP_TRIGGER_FIELD), + Trigger::class.java, + ParseField(NOOP_TRIGGER_FIELD), CheckedFunction { parseInner(it) } ) - @JvmStatic @Throws(IOException::class) + @JvmStatic + @Throws(IOException::class) fun parseInner(xcp: XContentParser): NoOpTrigger { var id = UUIDs.base64UUID() if (xcp.currentToken() == XContentParser.Token.START_OBJECT) xcp.nextToken() diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Sequence.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Sequence.kt index 22d4683b..e1f6150d 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Sequence.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Sequence.kt @@ -21,7 +21,7 @@ data class Sequence( fun asTemplateArg(): Map { return mapOf( - DELEGATES_FIELD to delegates, + DELEGATES_FIELD to delegates ) } 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 2038edfb..d0e57e63 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/Workflow.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Workflow.kt @@ -37,7 +37,7 @@ data class Workflow( val inputs: List, val owner: String? = DEFAULT_OWNER, val triggers: List, - val auditDelegateMonitorAlerts: Boolean? = true, + val auditDelegateMonitorAlerts: Boolean? = true ) : ScheduledJob { override val type = WORKFLOW_TYPE @@ -67,7 +67,9 @@ data class Workflow( workflowType = sin.readEnum(WorkflowType::class.java), user = if (sin.readBoolean()) { User(sin) - } else null, + } else { + null + }, schemaVersion = sin.readInt(), inputs = sin.readList((WorkflowInput)::readFrom), owner = sin.readOptionalString(), @@ -101,7 +103,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) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/action/Action.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/action/Action.kt index cad8a864..4fa0c514 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/action/Action.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/action/Action.kt @@ -130,7 +130,9 @@ data class Action( NAME_FIELD -> name = xcp.textOrNull() DESTINATION_ID_FIELD -> destinationId = xcp.textOrNull() SUBJECT_TEMPLATE_FIELD -> { - subjectTemplate = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) null else { + subjectTemplate = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) { + null + } else { Script.parse(xcp, Script.DEFAULT_TEMPLATE_LANG) } } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt index 8b4db7c4..4a5f2346 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/AlertTests.kt @@ -50,7 +50,7 @@ class AlertTests { assertEquals( templateArgs[Alert.PARENTS_BUCKET_PATH], alert.aggregationResultBucket?.parentBucketPath, - "Template args parentBucketPath does not match", + "Template args parentBucketPath does not match" ) } @@ -66,8 +66,11 @@ class AlertTests { @Test fun `test alert in audit state`() { val auditAlert = Alert( - randomQueryLevelMonitor(), randomQueryLevelTrigger(), Instant.now().truncatedTo(ChronoUnit.MILLIS), - null, actionExecutionResults = listOf(randomActionExecutionResult()) + randomQueryLevelMonitor(), + randomQueryLevelTrigger(), + Instant.now().truncatedTo(ChronoUnit.MILLIS), + null, + actionExecutionResults = listOf(randomActionExecutionResult()) ) Assertions.assertFalse(auditAlert.isAcknowledged(), "Alert should not be in acknowledged state") } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt index b4337e7f..ca193224 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/TestHelpers.kt @@ -208,7 +208,7 @@ fun randomWorkflowWithDelegates( enabled: Boolean = Random().nextBoolean(), enabledTime: Instant? = if (enabled) Instant.now().truncatedTo(ChronoUnit.MILLIS) else null, lastUpdateTime: Instant = Instant.now().truncatedTo(ChronoUnit.MILLIS), - triggers: List = (1..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { randomChainedAlertTrigger() }, + triggers: List = (1..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { randomChainedAlertTrigger() } ): Workflow { return Workflow( name = name, workflowType = Workflow.WorkflowType.COMPOSITE, enabled = enabled, inputs = input, @@ -286,9 +286,11 @@ fun randomDocumentLevelTrigger( name = name, severity = severity, condition = condition, - actions = if (actions.isEmpty() && destinationId.isNotBlank()) + actions = if (actions.isEmpty() && destinationId.isNotBlank()) { (0..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { randomAction(destinationId = destinationId) } - else actions + } else { + actions + } ) } @@ -307,7 +309,9 @@ fun randomChainedAlertTrigger( condition = condition, actions = if (actions.isEmpty() && destinationId.isNotBlank()) { (0..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { randomAction(destinationId = destinationId) } - } else actions + } else { + actions + } ) } @@ -530,14 +534,18 @@ fun randomAlert(monitor: Monitor = randomQueryLevelMonitor()): Alert { val clusterCount = (-1..5).random() val clusters = if (clusterCount == -1) null else (0..clusterCount).map { "index-$it" } return Alert( - monitor, trigger, Instant.now().truncatedTo(ChronoUnit.MILLIS), null, - actionExecutionResults = actionExecutionResults, clusters = clusters + monitor, + trigger, + Instant.now().truncatedTo(ChronoUnit.MILLIS), + null, + actionExecutionResults = actionExecutionResults, + clusters = clusters ) } fun randomChainedAlert( workflow: Workflow = randomWorkflow(), - trigger: ChainedAlertTrigger = randomChainedAlertTrigger(), + trigger: ChainedAlertTrigger = randomChainedAlertTrigger() ): Alert { return Alert( startTime = Instant.now(), @@ -561,7 +569,10 @@ fun randomAlertWithAggregationResultBucket(monitor: Monitor = randomBucketLevelM val trigger = randomBucketLevelTrigger() val actionExecutionResults = mutableListOf(randomActionExecutionResult(), randomActionExecutionResult()) return Alert( - monitor, trigger, Instant.now().truncatedTo(ChronoUnit.MILLIS), null, + monitor, + trigger, + Instant.now().truncatedTo(ChronoUnit.MILLIS), + null, actionExecutionResults = actionExecutionResults, aggregationResultBucket = AggregationResultBucket( "parent_bucket_path_1", 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 5733c550..df9a083b 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/AcknowledgeAlertResponseTests.kt @@ -15,7 +15,6 @@ class AcknowledgeAlertResponseTests { @Test fun `test acknowledge alert response`() { - val acknowledged = mutableListOf( Alert( id = "1234", 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 38c89fc5..b307ec6c 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteMonitorRequestTests.kt @@ -11,7 +11,6 @@ class DeleteMonitorRequestTests { @Test fun `test delete monitor request`() { - val req = DeleteMonitorRequest("1234", WriteRequest.RefreshPolicy.IMMEDIATE) assertNotNull(req) assertEquals("1234", req.monitorId) diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequestTests.kt index 774be288..80fb24d4 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteWorkflowRequestTests.kt @@ -9,7 +9,6 @@ class DeleteWorkflowRequestTests { @Test fun `test delete workflow request`() { - val req = DeleteWorkflowRequest("1234", true) Assert.assertNotNull(req) Assert.assertEquals("1234", req.workflowId) 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 bf301f74..58f61550 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetAlertsRequestTests.kt @@ -13,7 +13,6 @@ internal class GetAlertsRequestTests { @Test fun `test get alerts request`() { - val table = Table("asc", "sortString", null, 1, 0, "") val req = GetAlertsRequest( @@ -24,7 +23,7 @@ internal class GetAlertsRequestTests { alertIndex = null, monitorIds = listOf("1", "2"), alertIds = listOf("alert1", "alert2"), - workflowIds = listOf("w1", "w2"), + workflowIds = listOf("w1", "w2") ) assertNotNull(req) @@ -47,7 +46,6 @@ internal class GetAlertsRequestTests { @Test fun `test get alerts request with filter`() { - val table = Table("asc", "sortString", null, 1, 0, "") val req = GetAlertsRequest(table, "1", "active", null, null) assertNotNull(req) diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt index 253f4708..ddd56d15 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt @@ -13,7 +13,6 @@ internal class GetFindingsRequestTests { @Test fun `test get findings request`() { - val table = Table("asc", "sortString", null, 1, 0, "") val req = GetFindingsRequest("2121", table, "1", "finding_index_name", listOf("1", "2")) diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt index 1a8b2bdb..0c30b640 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsResponseTests.kt @@ -15,7 +15,6 @@ internal class GetFindingsResponseTests { @Test fun `test get findings response`() { - // Alerting GetFindingsResponse mock #1 val finding1 = Finding( "1", diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetMonitorRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetMonitorRequestTests.kt index c916b993..7670650e 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetMonitorRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetMonitorRequestTests.kt @@ -14,7 +14,6 @@ import org.opensearch.test.OpenSearchTestCase class GetMonitorRequestTests : OpenSearchTestCase() { fun `test get monitor request`() { - val req = GetMonitorRequest("1234", 1L, RestRequest.Method.GET, FetchSourceContext.FETCH_SOURCE) assertNotNull(req) @@ -29,7 +28,6 @@ class GetMonitorRequestTests : OpenSearchTestCase() { } fun `test get monitor request without src context`() { - val req = GetMonitorRequest("1234", 1L, RestRequest.Method.GET, null) assertNotNull(req) @@ -44,7 +42,6 @@ class GetMonitorRequestTests : OpenSearchTestCase() { } fun `test head monitor request`() { - val req = GetMonitorRequest("1234", 2L, RestRequest.Method.HEAD, FetchSourceContext.FETCH_SOURCE) assertNotNull(req) 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 1cf50eec..425151cd 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowAlertsRequestTests.kt @@ -13,7 +13,6 @@ internal class GetWorkflowAlertsRequestTests { @Test fun `test get alerts request`() { - val table = Table("asc", "sortString", null, 1, 0, "") val req = GetWorkflowAlertsRequest( @@ -48,7 +47,6 @@ internal class GetWorkflowAlertsRequestTests { @Test fun `test get alerts request with custom alerts and associated alerts indices`() { - val table = Table("asc", "sortString", null, 1, 0, "") val req = GetWorkflowAlertsRequest( diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponseTests.kt index 8e78b61f..e21bb430 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetWorkflowResponseTests.kt @@ -20,7 +20,12 @@ class GetWorkflowResponseTests { fun testGetWorkflowResponse() { val workflow = randomWorkflow(auditDelegateMonitorAlerts = false) val response = GetWorkflowResponse( - id = "id", version = 1, seqNo = 1, primaryTerm = 1, status = RestStatus.OK, workflow = workflow + id = "id", + version = 1, + seqNo = 1, + primaryTerm = 1, + status = RestStatus.OK, + workflow = workflow ) val out = BytesStreamOutput() response.writeTo(out) @@ -50,7 +55,12 @@ class GetWorkflowResponseTests { triggers = listOf() ) val response = GetWorkflowResponse( - id = "id", version = 1, seqNo = 1, primaryTerm = 1, status = RestStatus.OK, workflow = workflow + id = "id", + version = 1, + seqNo = 1, + primaryTerm = 1, + status = RestStatus.OK, + workflow = workflow ) val out = BytesStreamOutput() response.writeTo(out) diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequestTests.kt index 20381c9b..6efd68b6 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexMonitorRequestTests.kt @@ -20,9 +20,12 @@ class IndexMonitorRequestTests { @Test fun `test index monitor post request`() { - val req = IndexMonitorRequest( - "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.POST, + "1234", + 1L, + 2L, + WriteRequest.RefreshPolicy.IMMEDIATE, + RestRequest.Method.POST, randomQueryLevelMonitor().copy(inputs = listOf(SearchInput(emptyList(), SearchSourceBuilder()))) ) Assertions.assertNotNull(req) @@ -41,7 +44,11 @@ class IndexMonitorRequestTests { @Test fun `test index bucket monitor post request`() { val req = IndexMonitorRequest( - "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.POST, + "1234", + 1L, + 2L, + WriteRequest.RefreshPolicy.IMMEDIATE, + RestRequest.Method.POST, randomBucketLevelMonitor() ) Assertions.assertNotNull(req) @@ -61,7 +68,11 @@ class IndexMonitorRequestTests { @Test fun `Index bucket monitor serialize and deserialize transport object should be equal`() { val bucketLevelMonitorRequest = IndexMonitorRequest( - "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.POST, + "1234", + 1L, + 2L, + WriteRequest.RefreshPolicy.IMMEDIATE, + RestRequest.Method.POST, randomBucketLevelMonitor() ) @@ -80,9 +91,12 @@ class IndexMonitorRequestTests { @Test fun `test index monitor put request`() { - val req = IndexMonitorRequest( - "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + "1234", + 1L, + 2L, + WriteRequest.RefreshPolicy.IMMEDIATE, + RestRequest.Method.PUT, randomQueryLevelMonitor().copy(inputs = listOf(SearchInput(emptyList(), SearchSourceBuilder()))) ) Assertions.assertNotNull(req) 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 58feffb2..600ac506 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexWorkflowRequestTests.kt @@ -29,9 +29,12 @@ class IndexWorkflowRequestTests { @Test fun `test index workflow post request`() { - val req = IndexWorkflowRequest( - "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.POST, + "1234", + 1L, + 2L, + WriteRequest.RefreshPolicy.IMMEDIATE, + RestRequest.Method.POST, randomWorkflow(auditDelegateMonitorAlerts = false) ) Assertions.assertNotNull(req) @@ -51,7 +54,11 @@ class IndexWorkflowRequestTests { @Test fun `test index composite workflow post request`() { val req = IndexWorkflowRequest( - "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.POST, + "1234", + 1L, + 2L, + WriteRequest.RefreshPolicy.IMMEDIATE, + RestRequest.Method.POST, randomWorkflow() ) Assertions.assertNotNull(req) @@ -71,7 +78,11 @@ class IndexWorkflowRequestTests { @Test fun `Index composite workflow serialize and deserialize transport object should be equal`() { val compositeWorkflowRequest = IndexWorkflowRequest( - "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.POST, + "1234", + 1L, + 2L, + WriteRequest.RefreshPolicy.IMMEDIATE, + RestRequest.Method.POST, randomWorkflow() ) @@ -89,9 +100,12 @@ class IndexWorkflowRequestTests { @Test fun `test index workflow put request`() { - val req = IndexWorkflowRequest( - "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + "1234", + 1L, + 2L, + WriteRequest.RefreshPolicy.IMMEDIATE, + RestRequest.Method.PUT, randomWorkflow() ) Assertions.assertNotNull(req) @@ -110,7 +124,11 @@ class IndexWorkflowRequestTests { @Test fun `test validate`() { val req = IndexWorkflowRequest( - "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + "1234", + 1L, + 2L, + WriteRequest.RefreshPolicy.IMMEDIATE, + RestRequest.Method.PUT, randomWorkflow(monitorIds = emptyList()) ) Assertions.assertNotNull(req) @@ -120,7 +138,11 @@ class IndexWorkflowRequestTests { Assert.assertTrue(validate!!.message!!.contains("Delegates list can not be empty.;")) // Duplicate delegate val req1 = IndexWorkflowRequest( - "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + "1234", + 1L, + 2L, + WriteRequest.RefreshPolicy.IMMEDIATE, + RestRequest.Method.PUT, randomWorkflow(monitorIds = listOf("1L", "1L", "2L")) ) validate = req1.validate() @@ -133,7 +155,11 @@ class IndexWorkflowRequestTests { Delegate(2, "monitor-3") ) val req2 = IndexWorkflowRequest( - "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + "1234", + 1L, + 2L, + WriteRequest.RefreshPolicy.IMMEDIATE, + RestRequest.Method.PUT, randomWorkflowWithDelegates( input = listOf(CompositeInput(Sequence(delegates = delegates))) ) @@ -148,7 +174,11 @@ class IndexWorkflowRequestTests { Delegate(3, "monitor-3", ChainedMonitorFindings("monitor-x")) ) val req3 = IndexWorkflowRequest( - "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + "1234", + 1L, + 2L, + WriteRequest.RefreshPolicy.IMMEDIATE, + RestRequest.Method.PUT, randomWorkflowWithDelegates( input = listOf(CompositeInput(Sequence(delegates = delegates))) ) @@ -163,7 +193,11 @@ class IndexWorkflowRequestTests { Delegate(2, "monitor-3", ChainedMonitorFindings("monitor-2")) ) val req4 = IndexWorkflowRequest( - "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + "1234", + 1L, + 2L, + WriteRequest.RefreshPolicy.IMMEDIATE, + RestRequest.Method.PUT, randomWorkflowWithDelegates( input = listOf(CompositeInput(Sequence(delegates = delegates))) ) @@ -177,7 +211,11 @@ class IndexWorkflowRequestTests { monitorsIds.add(UUID.randomUUID().toString()) } val req5 = IndexWorkflowRequest( - "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + "1234", + 1L, + 2L, + WriteRequest.RefreshPolicy.IMMEDIATE, + RestRequest.Method.PUT, randomWorkflow( monitorIds = monitorsIds ) @@ -187,7 +225,11 @@ class IndexWorkflowRequestTests { Assert.assertTrue(validate!!.message!!.contains("Delegates list can not be larger then 25.")) // Input list empty val req6 = IndexWorkflowRequest( - "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + "1234", + 1L, + 2L, + WriteRequest.RefreshPolicy.IMMEDIATE, + RestRequest.Method.PUT, randomWorkflowWithDelegates( input = emptyList() ) @@ -204,11 +246,15 @@ class IndexWorkflowRequestTests { delegates = listOf( Delegate(1, "monitor-1"), Delegate(2, "monitor-2"), - Delegate(3, "monitor-3", ChainedMonitorFindings(null, listOf("monitor-1", "monitor-2"))), + Delegate(3, "monitor-3", ChainedMonitorFindings(null, listOf("monitor-1", "monitor-2"))) ) val req7 = IndexWorkflowRequest( - "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + "1234", + 1L, + 2L, + WriteRequest.RefreshPolicy.IMMEDIATE, + RestRequest.Method.PUT, randomWorkflowWithDelegates( input = listOf(CompositeInput(Sequence(delegates = delegates))) ) @@ -216,7 +262,11 @@ class IndexWorkflowRequestTests { assertNull(req7.validate()) try { IndexWorkflowRequest( - "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + "1234", + 1L, + 2L, + WriteRequest.RefreshPolicy.IMMEDIATE, + RestRequest.Method.PUT, randomWorkflowWithDelegates( input = listOf(CompositeInput(Sequence(delegates = delegates)), CompositeInput(Sequence(delegates = delegates))) ) @@ -230,11 +280,15 @@ class IndexWorkflowRequestTests { delegates = listOf( Delegate(1, "monitor-1"), Delegate(3, "monitor-2"), - Delegate(2, "monitor-3", ChainedMonitorFindings(null, listOf("monitor-1", "monitor-2"))), + Delegate(2, "monitor-3", ChainedMonitorFindings(null, listOf("monitor-1", "monitor-2"))) ) val req8 = IndexWorkflowRequest( - "1234", 1L, 2L, WriteRequest.RefreshPolicy.IMMEDIATE, RestRequest.Method.PUT, + "1234", + 1L, + 2L, + WriteRequest.RefreshPolicy.IMMEDIATE, + RestRequest.Method.PUT, randomWorkflowWithDelegates( input = listOf(CompositeInput(Sequence(delegates = delegates))) ) diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/PublishFindingsRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/PublishFindingsRequestTests.kt index d29ef7c6..bbfc7793 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/PublishFindingsRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/PublishFindingsRequestTests.kt @@ -11,7 +11,6 @@ class PublishFindingsRequestTests { @Test fun `test delete monitor request`() { - val finding = randomFinding() val monitorId = "mid" val req = PublishFindingsRequest(monitorId, finding) diff --git a/src/test/kotlin/org/opensearch/commons/alerting/model/FindingTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/model/FindingTests.kt index 50615cb2..10579a41 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/FindingTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/FindingTests.kt @@ -24,7 +24,7 @@ internal class FindingTests { assertEquals( templateArgs[Finding.MONITOR_NAME_FIELD], finding.monitorName, - "Template args 'monitorName' field does not match:", + "Template args 'monitorName' field does not match:" ) assertEquals( templateArgs[Finding.QUERIES_FIELD], 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 e56d4aab..fc93529a 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt @@ -277,13 +277,15 @@ class XContentTests { val cmf1 = ChainedMonitorFindings(monitorId = "m1") val cmf1String = cmf1.toJsonString() Assertions.assertEquals( - ChainedMonitorFindings.parse(parser(cmf1String)), cmf1, + ChainedMonitorFindings.parse(parser(cmf1String)), + cmf1, "Round tripping chained monitor findings failed" ) val cmf2 = ChainedMonitorFindings(monitorIds = listOf("m1", "m2")) val cmf2String = cmf2.toJsonString() Assertions.assertEquals( - ChainedMonitorFindings.parse(parser(cmf2String)), cmf2, + ChainedMonitorFindings.parse(parser(cmf2String)), + cmf2, "Round tripping chained monitor findings failed" ) } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt index ae7d0f3f..dd97cfda 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt @@ -202,7 +202,11 @@ internal class NotificationsPluginInterfaceTests { }.whenever(client).execute(any(ActionType::class.java), any(), any()) NotificationsPluginInterface.sendNotification( - client, notificationInfo, channelMessage, listOf("channelId1", "channelId2"), listener + client, + notificationInfo, + channelMessage, + listOf("channelId1", "channelId2"), + listener ) verify(listener, times(1)).onResponse(eq(response)) } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt index 117375e7..73c446ac 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt @@ -125,6 +125,7 @@ internal class CreateNotificationConfigRequestTests { assertNull(recreatedObject.validate()) assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) } + @Test fun `Create config serialize and deserialize transport object should be equal microsoft teams`() { val configRequest = CreateNotificationConfigRequest( @@ -214,6 +215,7 @@ internal class CreateNotificationConfigRequestTests { assertNull(recreatedObject.validate()) assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) } + @Test fun `Create config serialize and deserialize using json object should be equal microsoft teams`() { val configRequest = CreateNotificationConfigRequest( @@ -309,6 +311,7 @@ internal class CreateNotificationConfigRequestTests { val recreatedObject = createObjectFromJsonString(jsonString) { CreateNotificationConfigRequest.parse(it) } assertEquals(config, recreatedObject.notificationConfig) } + @Test fun `Create config should deserialize json object using parser microsoft teams`() { val sampleMicrosoftTeams = MicrosoftTeams("https://domain.com/sample_microsoft_teams_url#1234567890") diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponseTests.kt index 4d828998..1de3c8fa 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponseTests.kt @@ -22,7 +22,6 @@ internal class SendNotificationResponseTests { @Test fun `Create response serialize and deserialize transport object should be equal`() { - val sampleEvent = getSampleEvent() val recreatedObject = recreateObject(sampleEvent) { SendNotificationResponse(it) } @@ -31,7 +30,6 @@ internal class SendNotificationResponseTests { @Test fun `Create response serialize and deserialize using json object should be equal`() { - val sampleEvent = getSampleEvent() val jsonString = getJsonString(sampleEvent) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt index e07d7747..fecd7710 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt @@ -119,6 +119,7 @@ internal class UpdateNotificationConfigRequestTests { assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) assertEquals("config_id", recreatedObject.configId) } + @Test fun `Update config serialize and deserialize transport object should be equal Microsoft Teams`() { val configRequest = UpdateNotificationConfigRequest("config_id", createMicrosoftTeamsContentConfigObject()) @@ -187,6 +188,7 @@ internal class UpdateNotificationConfigRequestTests { assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) assertEquals("config_id", recreatedObject.configId) } + @Test fun `Update config serialize and deserialize using json object should be equal microsoft Teams`() { val configRequest = UpdateNotificationConfigRequest("config_id", createMicrosoftTeamsContentConfigObject()) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt index 31791120..ee17777a 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt @@ -202,7 +202,6 @@ internal class NotificationConfigTests { } @Test - fun `Config should safely ignore unknown config type in json object`() { val sampleSlack = Slack("https://domain.com/sample_slack_url#1234567890") val sampleConfig = NotificationConfig( diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/SmtpAccountTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/SmtpAccountTests.kt index ffdf26c7..6617d1f6 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/SmtpAccountTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/SmtpAccountTests.kt @@ -71,7 +71,8 @@ internal class SmtpAccountTests { fun `SmtpAccount should safely ignore extra field in json object`() { val sampleSmtpAccount = SmtpAccount( "domain.com", - 1234, MethodType.START_TLS, + 1234, + MethodType.START_TLS, "from@domain.com" ) val jsonString = """ diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/config/ConfigPropertiesTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/config/ConfigPropertiesTests.kt index 881cf9d4..c3d20dee 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/config/ConfigPropertiesTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/config/ConfigPropertiesTests.kt @@ -76,6 +76,7 @@ internal class ConfigPropertiesTests { val recreatedObject = createObjectFromJsonString(jsonString) { createConfigData(ConfigType.WEBHOOK, it) } assertEquals(sampleWebhook, recreatedObject) } + @Test fun `Validate config data parse Microsoft Teams`() { val sampleMicrosoftTeams = MicrosoftTeams("https://domain.com/sample_url#1234567890") @@ -83,6 +84,7 @@ internal class ConfigPropertiesTests { val recreatedObject = createObjectFromJsonString(jsonString) { createConfigData(ConfigType.MICROSOFT_TEAMS, it) } assertEquals(sampleMicrosoftTeams, recreatedObject) } + @Test fun `Validate config data parse EmailGroup`() { val sampleEmailGroup = EmailGroup(listOf(EmailRecipient("email1@email.com"), EmailRecipient("email2@email.com"))) From 9959c3239796f6fee565dd6f47a51b395bd34264 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:34:27 -0800 Subject: [PATCH 143/149] Added release notes for 2.12.0 (#587) (#589) * added release notes for common-utils * update release notes --------- (cherry picked from commit cd8ea9b7225cb0039349658563407a407fdcd3b8) Signed-off-by: jowg-amazon Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] --- ...rch-common-utils.release-notes-2.12.0.0.md | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 release-notes/opensearch-common-utils.release-notes-2.12.0.0.md diff --git a/release-notes/opensearch-common-utils.release-notes-2.12.0.0.md b/release-notes/opensearch-common-utils.release-notes-2.12.0.0.md new file mode 100644 index 00000000..25aee069 --- /dev/null +++ b/release-notes/opensearch-common-utils.release-notes-2.12.0.0.md @@ -0,0 +1,25 @@ +## Version 2.12.0.0 2023-02-06 + +Compatible with OpenSearch 2.12.0 + +### Maintenance +* Increment version to 2.12.0-SNAPSHOT ([#545](https://github.com/opensearch-project/common-utils/pull/545)) +* Onboard prod jenkins docker image to github actions ([#557](https://github.com/opensearch-project/common-utils/pull/557)) +* Update Gradle to 8.4 ([#560](https://github.com/opensearch-project/common-utils/pull/560)) +* Add Java 11/17/21 matrix for build, test and integration checks ([#561](https://github.com/opensearch-project/common-utils/pull/561)) +* changed all usages of 'admin' as a password to something different ([#581](https://github.com/opensearch-project/common-utils/pull/581)) +* Update dependency com.pinterest:ktlint to 0.47.1 and fix CVE-2023-6378 ([#585](https://github.com/opensearch-project/common-utils/pull/585)) + +### Enhancement +* add 'fields' parameter in doc level query object. ([#546](https://github.com/opensearch-project/common-utils/pull/546)) +* add fields param in toxcontent() for doc level query ([#549](https://github.com/opensearch-project/common-utils/pull/549)) +* Add User.isAdminDn to User class ([#547](https://github.com/opensearch-project/common-utils/pull/547)) + +### Refactor +* Move get monitor and search monitor action / request / responses to common-utils ([#566](https://github.com/opensearch-project/common-utils/pull/566)) + +# Features +* Implemented cross-cluster monitor support ([#584](https://github.com/opensearch-project/common-utils/pull/584)) + +### Documentation +* Added 2.12.0.0 release notes ([#585](https://github.com/opensearch-project/common-utils/pull/585)) \ No newline at end of file From a0635a6f57f1e266e4beac55c382430396cfefda Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 11:28:03 -0800 Subject: [PATCH 144/149] add queryFieldNames field in Doc Level Queries (#582) (#597) * add queryFieldNames field in Doc Level Queries * add tests to verify queryFieldNames field in DocLevelQuery --------- (cherry picked from commit 75925dcdbc98e29c07e007676ea6c68ee7468dec) Signed-off-by: Surya Sashank Nistala Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] --- .../commons/alerting/model/DocLevelQuery.kt | 46 +++++++++++++++++-- .../commons/alerting/model/WriteableTests.kt | 14 ++++++ .../commons/alerting/model/XContentTests.kt | 12 +++++ 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt index ba3e4e4c..7c72b0ca 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/DocLevelQuery.kt @@ -16,7 +16,8 @@ data class DocLevelQuery( val name: String, val fields: List, val query: String, - val tags: List = mutableListOf() + val tags: List = mutableListOf(), + val queryFieldNames: List = mutableListOf() ) : BaseModel { init { @@ -33,7 +34,8 @@ data class DocLevelQuery( sin.readString(), // name sin.readStringList(), // fields sin.readString(), // query - sin.readStringList() // tags + sin.readStringList(), // tags, + sin.readStringList() // fieldsBeingQueried ) fun asTemplateArg(): Map { @@ -42,7 +44,8 @@ data class DocLevelQuery( NAME_FIELD to name, FIELDS_FIELD to fields, QUERY_FIELD to query, - TAGS_FIELD to tags + TAGS_FIELD to tags, + QUERY_FIELD_NAMES_FIELD to queryFieldNames ) } @@ -53,6 +56,7 @@ data class DocLevelQuery( out.writeStringCollection(fields) out.writeString(query) out.writeStringCollection(tags) + out.writeStringCollection(queryFieldNames) } override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { @@ -62,6 +66,7 @@ data class DocLevelQuery( .field(FIELDS_FIELD, fields.toTypedArray()) .field(QUERY_FIELD, query) .field(TAGS_FIELD, tags.toTypedArray()) + .field(QUERY_FIELD_NAMES_FIELD, queryFieldNames.toTypedArray()) .endObject() return builder } @@ -72,6 +77,7 @@ data class DocLevelQuery( const val FIELDS_FIELD = "fields" const val QUERY_FIELD = "query" const val TAGS_FIELD = "tags" + const val QUERY_FIELD_NAMES_FIELD = "query_field_names" const val NO_ID = "" val INVALID_CHARACTERS: List = listOf(" ", "[", "]", "{", "}", "(", ")") @@ -83,6 +89,7 @@ data class DocLevelQuery( lateinit var name: String val tags: MutableList = mutableListOf() val fields: MutableList = mutableListOf() + val queryFieldNames: MutableList = mutableListOf() XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { @@ -95,6 +102,7 @@ data class DocLevelQuery( name = xcp.text() validateQuery(name) } + QUERY_FIELD -> query = xcp.text() TAGS_FIELD -> { XContentParserUtils.ensureExpectedToken( @@ -108,6 +116,7 @@ data class DocLevelQuery( tags.add(tag) } } + FIELDS_FIELD -> { XContentParserUtils.ensureExpectedToken( XContentParser.Token.START_ARRAY, @@ -119,6 +128,18 @@ data class DocLevelQuery( fields.add(field) } } + + QUERY_FIELD_NAMES_FIELD -> { + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_ARRAY, + xcp.currentToken(), + xcp + ) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + val field = xcp.text() + queryFieldNames.add(field) + } + } } } @@ -127,7 +148,8 @@ data class DocLevelQuery( name = name, fields = fields, query = query, - tags = tags + tags = tags, + queryFieldNames = queryFieldNames ) } @@ -148,4 +170,20 @@ data class DocLevelQuery( } } } + + // constructor for java plugins' convenience to optionally avoid passing empty list for 'fieldsBeingQueried' field + constructor( + id: String, + name: String, + fields: MutableList, + query: String, + tags: MutableList + ) : this( + id = id, + name = name, + fields = fields, + query = query, + tags = tags, + queryFieldNames = emptyList() + ) } 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 a66d7222..e81e59bd 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/WriteableTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/WriteableTests.kt @@ -20,6 +20,7 @@ import org.opensearch.commons.alerting.randomUserEmpty import org.opensearch.commons.authuser.User import org.opensearch.core.common.io.stream.StreamInput import org.opensearch.search.builder.SearchSourceBuilder +import kotlin.test.assertTrue class WriteableTests { @@ -121,6 +122,19 @@ class WriteableTests { val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) val newDlq = DocLevelQuery.readFrom(sin) Assertions.assertEquals(dlq, newDlq, "Round tripping DocLevelQuery doesn't work") + assertTrue(newDlq.queryFieldNames.isEmpty()) + } + + @Test + fun `test doc-level query with query Field Names as stream`() { + val dlq = randomDocLevelQuery().copy(queryFieldNames = listOf("f1", "f2")) + val out = BytesStreamOutput() + dlq.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newDlq = DocLevelQuery.readFrom(sin) + assertTrue(newDlq.queryFieldNames.contains(dlq.queryFieldNames[0])) + assertTrue(newDlq.queryFieldNames.contains(dlq.queryFieldNames[1])) + Assertions.assertEquals(dlq, newDlq, "Round tripping DocLevelQuery doesn't work") } @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 fc93529a..065191fb 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/XContentTests.kt @@ -431,6 +431,18 @@ class XContentTests { ) } + @Test + fun `test doc level query toXcontent with query field names`() { + val dlq = DocLevelQuery("id", "name", listOf("f1", "f2"), "query", listOf("t1", "t2"), listOf("f1", "f2")) + val dlqString = dlq.toXContent(builder(), ToXContent.EMPTY_PARAMS).string() + val parsedDlq = DocLevelQuery.parse(parser(dlqString)) + Assertions.assertEquals( + dlq, + parsedDlq, + "Round tripping Doc level query doesn't work" + ) + } + @Test fun `test alert parsing`() { val alert = randomAlert() From 53f7dcc2b9f2af605342dc352b68c081de23db1e Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 10:30:30 -0800 Subject: [PATCH 145/149] Increment version to 2.13.0-SNAPSHOT (#591) Signed-off-by: opensearch-ci-bot Co-authored-by: opensearch-ci-bot --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index fb8a5abe..70bfd9ce 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "2.12.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.13.0-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") buildVersionQualifier = System.getProperty("build.version_qualifier", "") kotlin_version = System.getProperty("kotlin.version", "1.8.21") From d936c8f90911496969ce004222977ceb1d218e22 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Fri, 8 Mar 2024 17:55:34 -0800 Subject: [PATCH 146/149] Feature findings enhancemnt (#596) (#606) * added support for param in Finding API * added detectionType as param for Findings API enhancements * adding addiional params findingIds, startTime and endTime * fix klint errors --------- (cherry picked from commit 892c34fc1b048e43aeafdb16ad33be1a7f44651f) Signed-off-by: Riya Saxena Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] --- .../alerting/action/GetFindingsRequest.kt | 30 +++++++++++++++++-- .../action/GetFindingsRequestTests.kt | 12 +++++--- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequest.kt index 0004d4f1..8ad2ff82 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequest.kt @@ -6,6 +6,7 @@ import org.opensearch.commons.alerting.model.Table import org.opensearch.core.common.io.stream.StreamInput import org.opensearch.core.common.io.stream.StreamOutput import java.io.IOException +import java.time.Instant class GetFindingsRequest : ActionRequest { val findingId: String? @@ -13,19 +14,34 @@ class GetFindingsRequest : ActionRequest { val monitorId: String? val monitorIds: List? val findingIndex: String? + val severity: String? + val detectionType: String? + val findingIds: List? + val startTime: Instant? + val endTime: Instant? constructor( findingId: String?, table: Table, monitorId: String? = null, findingIndexName: String? = null, - monitorIds: List? = null + monitorIds: List? = null, + severity: String? = null, + detectionType: String? = null, + findingIds: List? = null, + startTime: Instant? = null, + endTime: Instant? = null ) : super() { this.findingId = findingId this.table = table this.monitorId = monitorId this.findingIndex = findingIndexName this.monitorIds = monitorIds + this.severity = severity + this.detectionType = detectionType + this.findingIds = findingIds + this.startTime = startTime + this.endTime = endTime } @Throws(IOException::class) @@ -34,7 +50,12 @@ class GetFindingsRequest : ActionRequest { table = Table.readFrom(sin), monitorId = sin.readOptionalString(), findingIndexName = sin.readOptionalString(), - monitorIds = sin.readOptionalStringList() + monitorIds = sin.readOptionalStringList(), + severity = sin.readOptionalString(), + detectionType = sin.readOptionalString(), + findingIds = sin.readOptionalStringList(), + startTime = sin.readOptionalInstant(), + endTime = sin.readOptionalInstant() ) override fun validate(): ActionRequestValidationException? { @@ -48,5 +69,10 @@ class GetFindingsRequest : ActionRequest { out.writeOptionalString(monitorId) out.writeOptionalString(findingIndex) out.writeOptionalStringCollection(monitorIds) + out.writeOptionalString(severity) + out.writeOptionalString(detectionType) + out.writeOptionalStringCollection(findingIds) + out.writeOptionalInstant(startTime) + out.writeOptionalInstant(endTime) } } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt index ddd56d15..67014d1b 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt @@ -8,14 +8,14 @@ import org.junit.jupiter.api.Test import org.opensearch.common.io.stream.BytesStreamOutput import org.opensearch.commons.alerting.model.Table import org.opensearch.core.common.io.stream.StreamInput +import java.time.Instant internal class GetFindingsRequestTests { @Test fun `test get findings request`() { val table = Table("asc", "sortString", null, 1, 0, "") - - val req = GetFindingsRequest("2121", table, "1", "finding_index_name", listOf("1", "2")) + val req = GetFindingsRequest("2121", table, "1", "finding_index_name", listOf("1", "2"), "severity", "detectionType", listOf("id1", "id2"), Instant.now(), Instant.now().plusSeconds(30000)) assertNotNull(req) val out = BytesStreamOutput() @@ -26,16 +26,20 @@ internal class GetFindingsRequestTests { assertEquals("1", newReq.monitorId) assertEquals("2121", newReq.findingId) assertEquals("finding_index_name", newReq.findingIndex) + assertEquals("severity", newReq.severity) + assertEquals("detectionType", newReq.detectionType) assertEquals(table, newReq.table) assertTrue(newReq.monitorIds!!.contains("1")) assertTrue(newReq.monitorIds!!.contains("2")) + assertTrue(newReq.findingIds!!.contains("id1")) + assertTrue(newReq.findingIds!!.contains("id2")) + assertTrue(newReq.startTime!! < newReq.endTime, "startTime less than endTime") } @Test fun `test validate returns null`() { val table = Table("asc", "sortString", null, 1, 0, "") - - val req = GetFindingsRequest("2121", table, "1", "active") + val req = GetFindingsRequest("2121", table, "1", "active", listOf("1", "2"), "severity", "detectionType", listOf("id1", "id2"), Instant.now(), Instant.now().plusSeconds(30000)) assertNotNull(req) assertNull(req.validate()) } From e6cd1391e8337120adf9f54ec7e9f7cfe31580dd Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Thu, 14 Mar 2024 09:34:52 -0700 Subject: [PATCH 147/149] fix findings API enhancemnts (#611) (#617) * fix findings API enhancemnts * fix klint errors * fix klint errors * fix klint errors --------- (cherry picked from commit 3f5421fc0f3d6b22bbccb8260962ef2ed22d4fdd) Signed-off-by: Riya Saxena Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] --- .../alerting/action/GetFindingsRequest.kt | 33 ++++--------------- .../action/GetFindingsRequestTests.kt | 13 +++----- 2 files changed, 11 insertions(+), 35 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequest.kt index 8ad2ff82..59dd3c39 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequest.kt @@ -5,8 +5,8 @@ import org.opensearch.action.ActionRequestValidationException import org.opensearch.commons.alerting.model.Table import org.opensearch.core.common.io.stream.StreamInput import org.opensearch.core.common.io.stream.StreamOutput +import org.opensearch.index.query.BoolQueryBuilder import java.io.IOException -import java.time.Instant class GetFindingsRequest : ActionRequest { val findingId: String? @@ -14,34 +14,21 @@ class GetFindingsRequest : ActionRequest { val monitorId: String? val monitorIds: List? val findingIndex: String? - val severity: String? - val detectionType: String? - val findingIds: List? - val startTime: Instant? - val endTime: Instant? - + val boolQueryBuilder: BoolQueryBuilder? constructor( findingId: String?, table: Table, monitorId: String? = null, findingIndexName: String? = null, monitorIds: List? = null, - severity: String? = null, - detectionType: String? = null, - findingIds: List? = null, - startTime: Instant? = null, - endTime: Instant? = null + boolQueryBuilder: BoolQueryBuilder? = null ) : super() { this.findingId = findingId this.table = table this.monitorId = monitorId this.findingIndex = findingIndexName this.monitorIds = monitorIds - this.severity = severity - this.detectionType = detectionType - this.findingIds = findingIds - this.startTime = startTime - this.endTime = endTime + this.boolQueryBuilder = boolQueryBuilder } @Throws(IOException::class) @@ -51,11 +38,7 @@ class GetFindingsRequest : ActionRequest { monitorId = sin.readOptionalString(), findingIndexName = sin.readOptionalString(), monitorIds = sin.readOptionalStringList(), - severity = sin.readOptionalString(), - detectionType = sin.readOptionalString(), - findingIds = sin.readOptionalStringList(), - startTime = sin.readOptionalInstant(), - endTime = sin.readOptionalInstant() + boolQueryBuilder = BoolQueryBuilder(sin) ) override fun validate(): ActionRequestValidationException? { @@ -69,10 +52,6 @@ class GetFindingsRequest : ActionRequest { out.writeOptionalString(monitorId) out.writeOptionalString(findingIndex) out.writeOptionalStringCollection(monitorIds) - out.writeOptionalString(severity) - out.writeOptionalString(detectionType) - out.writeOptionalStringCollection(findingIds) - out.writeOptionalInstant(startTime) - out.writeOptionalInstant(endTime) + boolQueryBuilder?.writeTo(out) } } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt index 67014d1b..238837cc 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/GetFindingsRequestTests.kt @@ -8,14 +8,15 @@ import org.junit.jupiter.api.Test import org.opensearch.common.io.stream.BytesStreamOutput import org.opensearch.commons.alerting.model.Table import org.opensearch.core.common.io.stream.StreamInput -import java.time.Instant +import org.opensearch.index.query.QueryBuilders internal class GetFindingsRequestTests { @Test fun `test get findings request`() { val table = Table("asc", "sortString", null, 1, 0, "") - val req = GetFindingsRequest("2121", table, "1", "finding_index_name", listOf("1", "2"), "severity", "detectionType", listOf("id1", "id2"), Instant.now(), Instant.now().plusSeconds(30000)) + val boolQueryBuilder = QueryBuilders.boolQuery() + val req = GetFindingsRequest("2121", table, "1", "finding_index_name", listOf("1", "2"), boolQueryBuilder) assertNotNull(req) val out = BytesStreamOutput() @@ -26,20 +27,16 @@ internal class GetFindingsRequestTests { assertEquals("1", newReq.monitorId) assertEquals("2121", newReq.findingId) assertEquals("finding_index_name", newReq.findingIndex) - assertEquals("severity", newReq.severity) - assertEquals("detectionType", newReq.detectionType) assertEquals(table, newReq.table) assertTrue(newReq.monitorIds!!.contains("1")) assertTrue(newReq.monitorIds!!.contains("2")) - assertTrue(newReq.findingIds!!.contains("id1")) - assertTrue(newReq.findingIds!!.contains("id2")) - assertTrue(newReq.startTime!! < newReq.endTime, "startTime less than endTime") } @Test fun `test validate returns null`() { val table = Table("asc", "sortString", null, 1, 0, "") - val req = GetFindingsRequest("2121", table, "1", "active", listOf("1", "2"), "severity", "detectionType", listOf("id1", "id2"), Instant.now(), Instant.now().plusSeconds(30000)) + val boolQueryBuilder = QueryBuilders.boolQuery() + val req = GetFindingsRequest("2121", table, "1", "active", listOf("1", "2"), boolQueryBuilder) assertNotNull(req) assertNull(req.validate()) } From 937bc4ecdf5ff07a1087b259c330c9b361db5c32 Mon Sep 17 00:00:00 2001 From: Riya <69919272+riysaxen-amzn@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:51:05 -0700 Subject: [PATCH 148/149] Added release notes for 2.13 (#623) * added release notes for 2.13 Signed-off-by: Riya Saxena * added release notes for 2.13 Signed-off-by: Riya Saxena --------- Signed-off-by: Riya Saxena --- ...search-common-utils.release-notes-2.13.0.0.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 release-notes/opensearch-common-utils.release-notes-2.13.0.0.md diff --git a/release-notes/opensearch-common-utils.release-notes-2.13.0.0.md b/release-notes/opensearch-common-utils.release-notes-2.13.0.0.md new file mode 100644 index 00000000..8aef8153 --- /dev/null +++ b/release-notes/opensearch-common-utils.release-notes-2.13.0.0.md @@ -0,0 +1,16 @@ +## Version 2.13.0.0 2023-03-21 + +Compatible with OpenSearch 2.13.0 + +### Maintenance +* Increment version to 2.13.0-SNAPSHOT ([#591](https://github.com/opensearch-project/common-utils/pull/591)) + +### Enhancement +* add queryFieldNames field in Doc Level Queries (#[582](https://github.com/opensearch-project/common-utils/pull/582)) (#[597](https://github.com/opensearch-project/common-utils/pull/597)) + +# Features +* fix findings API enhancemnts (#[611](https://github.com/opensearch-project/common-utils/pull/611)) (#[617](https://github.com/opensearch-project/common-utils/pull/617)) +* Feature findings enhancemnt (#[596](https://github.com/opensearch-project/common-utils/pull/596)) (#[606](https://github.com/opensearch-project/common-utils/pull/606)) + +### Documentation +* Added 2.13.0.0 release notes ([#622](https://github.com/opensearch-project/common-utils/pull/622)) \ No newline at end of file From 716ea5d7f823793e1755d3a147f1d1adc79f8f9b Mon Sep 17 00:00:00 2001 From: opensearch-ci-bot Date: Tue, 2 Apr 2024 23:02:15 +0000 Subject: [PATCH 149/149] Incremented version to 2.13.1 Signed-off-by: GitHub --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 70bfd9ce..241459b7 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { ext { opensearch_group = "org.opensearch" - opensearch_version = System.getProperty("opensearch.version", "2.13.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.13.1-SNAPSHOT") isSnapshot = "true" == System.getProperty("build.snapshot", "true") buildVersionQualifier = System.getProperty("build.version_qualifier", "") kotlin_version = System.getProperty("kotlin.version", "1.8.21")