Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport 2.x] Adding Alerting Comments system indices and Security ITs (#1659) #1686

Merged
merged 1 commit into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import org.opensearch.alerting.action.SearchEmailGroupAction
import org.opensearch.alerting.alerts.AlertIndices
import org.opensearch.alerting.alerts.AlertIndices.Companion.ALL_ALERT_INDEX_PATTERN
import org.opensearch.alerting.comments.CommentsIndices
import org.opensearch.alerting.comments.CommentsIndices.Companion.ALL_COMMENTS_INDEX_PATTERN
import org.opensearch.alerting.core.JobSweeper
import org.opensearch.alerting.core.ScheduledJobIndices
import org.opensearch.alerting.core.action.node.ScheduledJobsStatsAction
Expand Down Expand Up @@ -443,7 +444,8 @@ internal class AlertingPlugin : PainlessExtension, ActionPlugin, ScriptPlugin, R
override fun getSystemIndexDescriptors(settings: Settings): Collection<SystemIndexDescriptor> {
return listOf(
SystemIndexDescriptor(ALL_ALERT_INDEX_PATTERN, "Alerting Plugin system index pattern"),
SystemIndexDescriptor(SCHEDULED_JOBS_INDEX, "Alerting Plugin Configuration index")
SystemIndexDescriptor(SCHEDULED_JOBS_INDEX, "Alerting Plugin Configuration index"),
SystemIndexDescriptor(ALL_COMMENTS_INDEX_PATTERN, "Alerting Comments system index pattern")
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import org.opensearch.commons.alerting.action.AlertingActions
val ALL_ACCESS_ROLE = "all_access"
val READALL_AND_MONITOR_ROLE = "readall_and_monitor"
val ALERTING_FULL_ACCESS_ROLE = "alerting_full_access"
val ALERTING_ACK_ALERTS_ROLE = "alerting_ack_alerts"
val ALERTING_READ_ONLY_ACCESS = "alerting_read_access"
val ALERTING_NO_ACCESS_ROLE = "no_access"
val ALERTING_GET_EMAIL_ACCOUNT_ACCESS = "alerting_get_email_account_access"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ 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.Comment
import org.opensearch.commons.alerting.model.Comment.Companion.COMMENT_CONTENT_FIELD
import org.opensearch.commons.alerting.model.DocLevelMonitorInput
import org.opensearch.commons.alerting.model.DocLevelQuery
import org.opensearch.commons.alerting.model.DocumentLevelTrigger
Expand All @@ -66,6 +67,7 @@ import org.opensearch.core.xcontent.XContentBuilder
import org.opensearch.core.xcontent.XContentParser
import org.opensearch.core.xcontent.XContentParserUtils
import org.opensearch.search.SearchModule
import org.opensearch.search.builder.SearchSourceBuilder
import java.net.URLEncoder
import java.nio.file.Files
import java.time.Instant
Expand Down Expand Up @@ -537,40 +539,6 @@ abstract class AlertingRestTestCase : ODFERestTestCase() {
return alert.copy(id = alertJson["_id"] as String, version = (alertJson["_version"] as Int).toLong())
}

protected fun createAlertComment(alertId: String, content: String): Comment {
val createRequestBody = jsonBuilder()
.startObject()
.field(Comment.COMMENT_CONTENT_FIELD, content)
.endObject()
.string()

val createResponse = client().makeRequest(
"POST",
"$COMMENTS_BASE_URI/$alertId",
StringEntity(createRequestBody, APPLICATION_JSON)
)

assertEquals("Unable to create a new alert", RestStatus.CREATED, createResponse.restStatus())

val responseBody = createResponse.asMap()
val commentId = responseBody["_id"] as String
assertNotEquals("response is missing Id", Comment.NO_ID, commentId)

val comment = responseBody["comment"] as Map<*, *>

return Comment(
id = commentId,
entityId = comment["entity_id"] as String,
entityType = comment["entity_type"] as String,
content = comment["content"] as String,
createdTime = Instant.ofEpochMilli(comment["created_time"] as Long),
lastUpdatedTime = if (comment["last_updated_time"] != null) {
Instant.ofEpochMilli(comment["last_updated_time"] as Long)
} else null,
user = comment["user"]?.let { User(it as String, emptyList(), emptyList(), emptyList()) }
)
}

protected fun createRandomMonitor(refresh: Boolean = false, withMetadata: Boolean = false): Monitor {
val monitor = randomQueryLevelMonitor(withMetadata = withMetadata)
val monitorId = createMonitor(monitor, refresh).id
Expand Down Expand Up @@ -1913,4 +1881,97 @@ abstract class AlertingRestTestCase : ODFERestTestCase() {
}

protected fun Workflow.relativeUrl() = "$WORKFLOW_ALERTING_BASE_URI/$id"

protected fun createAlertComment(alertId: String, content: String, client: RestClient): Comment {
val createRequestBody = jsonBuilder()
.startObject()
.field(COMMENT_CONTENT_FIELD, content)
.endObject()
.string()

val createResponse = client.makeRequest(
"POST",
"$COMMENTS_BASE_URI/$alertId",
StringEntity(createRequestBody, APPLICATION_JSON)
)

assertEquals("Unable to create a new comment", RestStatus.CREATED, createResponse.restStatus())

val responseBody = createResponse.asMap()
val commentId = responseBody["_id"] as String
assertNotEquals("response is missing Id", Comment.NO_ID, commentId)

val comment = responseBody["comment"] as Map<*, *>

return Comment(
id = commentId,
entityId = comment["entity_id"] as String,
entityType = comment["entity_type"] as String,
content = comment["content"] as String,
createdTime = Instant.ofEpochMilli(comment["created_time"] as Long),
lastUpdatedTime = if (comment["last_updated_time"] != null) {
Instant.ofEpochMilli(comment["last_updated_time"] as Long)
} else null,
user = comment["user"]?.let { User(it as String, emptyList(), emptyList(), emptyList()) }
)
}

protected fun updateAlertComment(commentId: String, content: String, client: RestClient): Comment {
val updateRequestBody = jsonBuilder()
.startObject()
.field(COMMENT_CONTENT_FIELD, content)
.endObject()
.string()

val updateResponse = client.makeRequest(
"PUT",
"$COMMENTS_BASE_URI/$commentId",
StringEntity(updateRequestBody, APPLICATION_JSON)
)

assertEquals("Update comment failed", RestStatus.OK, updateResponse.restStatus())

val updateResponseBody = updateResponse.asMap()

val comment = updateResponseBody["comment"] as Map<*, *>

return Comment(
id = commentId,
entityId = comment["entity_id"] as String,
entityType = comment["entity_type"] as String,
content = comment["content"] as String,
createdTime = Instant.ofEpochMilli(comment["created_time"] as Long),
lastUpdatedTime = if (comment["last_updated_time"] != null) {
Instant.ofEpochMilli(comment["last_updated_time"] as Long)
} else null,
user = comment["user"]?.let { User(it as String, emptyList(), emptyList(), emptyList()) }
)
}

protected fun searchAlertComments(query: SearchSourceBuilder, client: RestClient): XContentParser {
val searchResponse = client.makeRequest(
"GET",
"$COMMENTS_BASE_URI/_search",
StringEntity(query.toString(), APPLICATION_JSON)
)

val xcp = createParser(XContentType.JSON.xContent(), searchResponse.entity.content)

return xcp
}

// returns the ID of the delete comment
protected fun deleteAlertComment(commentId: String, client: RestClient): String {
val deleteResponse = client.makeRequest(
"DELETE",
"$COMMENTS_BASE_URI/$commentId"
)

assertEquals("Delete comment failed", RestStatus.OK, deleteResponse.restStatus())

val deleteResponseBody = deleteResponse.asMap()
val deletedCommentId = deleteResponseBody["_id"] as String

return deletedCommentId
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,13 @@

package org.opensearch.alerting.resthandler

import org.apache.http.entity.ContentType
import org.apache.http.entity.StringEntity
import org.opensearch.alerting.AlertingPlugin.Companion.COMMENTS_BASE_URI
import org.opensearch.alerting.AlertingRestTestCase
import org.opensearch.alerting.makeRequest
import org.opensearch.alerting.randomAlert
import org.opensearch.alerting.settings.AlertingSettings.Companion.ALERTING_COMMENTS_ENABLED
import org.opensearch.common.xcontent.XContentFactory
import org.opensearch.common.xcontent.XContentType
import org.opensearch.commons.alerting.model.Alert
import org.opensearch.commons.alerting.model.Comment.Companion.COMMENT_CONTENT_FIELD
import org.opensearch.commons.alerting.util.string
import org.opensearch.core.rest.RestStatus
import org.opensearch.index.query.QueryBuilders
import org.opensearch.search.builder.SearchSourceBuilder
import org.opensearch.test.OpenSearchTestCase
import org.opensearch.test.junit.annotations.TestLogging
import java.util.concurrent.TimeUnit

@TestLogging("level:DEBUG", reason = "Debug for tests.")
@Suppress("UNCHECKED_CAST")
Expand All @@ -36,7 +25,7 @@ class AlertingCommentsRestApiIT : AlertingRestTestCase() {
val alertId = alert.id
val commentContent = "test comment"

val comment = createAlertComment(alertId, commentContent)
val comment = createAlertComment(alertId, commentContent, client())

assertEquals("Comment does not have correct content", commentContent, comment.content)
assertEquals("Comment does not have correct alert ID", alertId, comment.entityId)
Expand All @@ -50,27 +39,11 @@ class AlertingCommentsRestApiIT : AlertingRestTestCase() {
val alertId = alert.id
val commentContent = "test comment"

val commentId = createAlertComment(alertId, commentContent).id
val commentId = createAlertComment(alertId, commentContent, client()).id

val updateContent = "updated comment"
val updateRequestBody = XContentFactory.jsonBuilder()
.startObject()
.field(COMMENT_CONTENT_FIELD, updateContent)
.endObject()
.string()
val actualContent = updateAlertComment(commentId, updateContent, client()).content

val updateResponse = client().makeRequest(
"PUT",
"$COMMENTS_BASE_URI/$commentId",
StringEntity(updateRequestBody, ContentType.APPLICATION_JSON)
)

assertEquals("Update comment failed", RestStatus.OK, updateResponse.restStatus())

val updateResponseBody = updateResponse.asMap()

val comment = updateResponseBody["comment"] as Map<*, *>
val actualContent = comment["content"] as String
assertEquals("Comment does not have correct content after update", updateContent, actualContent)
}

Expand All @@ -82,20 +55,11 @@ class AlertingCommentsRestApiIT : AlertingRestTestCase() {
val alertId = alert.id
val commentContent = "test comment"

createAlertComment(alertId, commentContent)
createAlertComment(alertId, commentContent, client())

OpenSearchTestCase.waitUntil({
return@waitUntil false
}, 3, TimeUnit.SECONDS)
val search = SearchSourceBuilder().query(QueryBuilders.matchAllQuery())
val xcp = searchAlertComments(search, client())

val search = SearchSourceBuilder().query(QueryBuilders.matchAllQuery()).toString()
val searchResponse = client().makeRequest(
"GET",
"$COMMENTS_BASE_URI/_search",
StringEntity(search, ContentType.APPLICATION_JSON)
)

val xcp = createParser(XContentType.JSON.xContent(), searchResponse.entity.content)
val hits = xcp.map()["hits"]!! as Map<String, Map<String, Any>>
logger.info("hits: $hits")
val numberDocsFound = hits["total"]?.get("value")
Expand All @@ -116,21 +80,10 @@ class AlertingCommentsRestApiIT : AlertingRestTestCase() {
val alertId = alert.id
val commentContent = "test comment"

val commentId = createAlertComment(alertId, commentContent).id
OpenSearchTestCase.waitUntil({
return@waitUntil false
}, 3, TimeUnit.SECONDS)

val deleteResponse = client().makeRequest(
"DELETE",
"$COMMENTS_BASE_URI/$commentId"
)

assertEquals("Delete comment failed", RestStatus.OK, deleteResponse.restStatus())
val commentId = createAlertComment(alertId, commentContent, client()).id

val deleteResponseBody = deleteResponse.asMap()
val deletedCommentId = deleteAlertComment(commentId, client())

val deletedCommentId = deleteResponseBody["_id"] as String
assertEquals("Deleted Comment ID does not match Comment ID in delete request", commentId, deletedCommentId)
}

Expand Down
Loading
Loading