Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Add logged-on User details to the Monitor and Destination #255

Merged
merged 8 commits into from
Sep 25, 2020
3 changes: 3 additions & 0 deletions alerting/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ dependencies {
compile project(":alerting-notification")

testImplementation "org.jetbrains.kotlin:kotlin-test:${kotlin_version}"
testImplementation "org.apache.commons:commons-lang3:3.11"
testImplementation "org.apache.commons:commons-text:1.9"

}

javadoc.enabled = false // turn off javadoc as it barfs on Kotlin code
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,7 @@ class GetMonitorRequest : ActionRequest {
out.writeString(monitorId)
out.writeLong(version)
out.writeEnum(method)
if (srcContext != null) {
out.writeBoolean(true)
srcContext?.writeTo(out)
} else {
out.writeBoolean(false)
}
out.writeBoolean(srcContext != null)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is nice!

srcContext?.writeTo(out)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ import com.amazon.opendistroforelasticsearch.alerting.core.model.Input
import com.amazon.opendistroforelasticsearch.alerting.core.model.Schedule
import com.amazon.opendistroforelasticsearch.alerting.core.model.ScheduledJob
import com.amazon.opendistroforelasticsearch.alerting.core.model.SearchInput
import com.amazon.opendistroforelasticsearch.alerting.core.model.User
import com.amazon.opendistroforelasticsearch.alerting.elasticapi.instant
import com.amazon.opendistroforelasticsearch.alerting.elasticapi.optionalTimeField
import com.amazon.opendistroforelasticsearch.alerting.elasticapi.optionalUserField
import com.amazon.opendistroforelasticsearch.alerting.settings.AlertingSettings.Companion.MONITOR_MAX_INPUTS
import com.amazon.opendistroforelasticsearch.alerting.settings.AlertingSettings.Companion.MONITOR_MAX_TRIGGERS
import com.amazon.opendistroforelasticsearch.alerting.util.IndexUtils.Companion.NO_SCHEMA_VERSION
Expand Down Expand Up @@ -52,6 +54,7 @@ data class Monitor(
override val schedule: Schedule,
override val lastUpdateTime: Instant,
override val enabledTime: Instant?,
val user: User?,
val schemaVersion: Int = NO_SCHEMA_VERSION,
val inputs: List<Input>,
val triggers: List<Trigger>,
Expand All @@ -77,17 +80,20 @@ data class Monitor(

@Throws(IOException::class)
constructor(sin: StreamInput): this(
sin.readString(), // id
sin.readLong(), // version
sin.readString(), // name
sin.readBoolean(), // enabled
Schedule.readFrom(sin),
sin.readInstant(), // lastUpdateTime
sin.readOptionalInstant(), // enabledTime
sin.readInt(), // schemaVersion
sin.readList(::SearchInput), // inputs
sin.readList(::Trigger), // triggers
suppressWarning(sin.readMap()) // uiMetadata
id = sin.readString(),
version = sin.readLong(),
name = sin.readString(),
enabled = sin.readBoolean(),
schedule = Schedule.readFrom(sin),
lastUpdateTime = sin.readInstant(),
enabledTime = sin.readOptionalInstant(),
user = if (sin.readBoolean()) {
User(sin)
} else null,
schemaVersion = sin.readInt(),
inputs = sin.readList(::SearchInput),
triggers = sin.readList(::Trigger),
uiMetadata = suppressWarning(sin.readMap())
)
fun toXContent(builder: XContentBuilder): XContentBuilder {
return toXContent(builder, ToXContent.EMPTY_PARAMS)
Expand All @@ -104,6 +110,7 @@ data class Monitor(
builder.field(TYPE_FIELD, type)
.field(SCHEMA_VERSION_FIELD, schemaVersion)
.field(NAME_FIELD, name)
.optionalUserField(USER_FIELD, user)
.field(ENABLED_FIELD, enabled)
.optionalTimeField(ENABLED_TIME_FIELD, enabledTime)
.field(SCHEDULE_FIELD, schedule)
Expand All @@ -118,7 +125,7 @@ data class Monitor(
override fun fromDocument(id: String, version: Long): Monitor = copy(id = id, version = version)

@Throws(IOException::class)
fun writeTo(out: StreamOutput) {
override fun writeTo(out: StreamOutput) {
out.writeString(id)
out.writeLong(version)
out.writeString(name)
Expand All @@ -131,6 +138,8 @@ data class Monitor(
schedule.writeTo(out)
out.writeInstant(lastUpdateTime)
out.writeOptionalInstant(enabledTime)
out.writeBoolean(user != null)
user?.writeTo(out)
out.writeInt(schemaVersion)
out.writeCollection(inputs)
out.writeCollection(triggers)
Expand All @@ -142,6 +151,7 @@ data class Monitor(
const val TYPE_FIELD = "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"
Expand All @@ -163,6 +173,7 @@ data class Monitor(
@Throws(IOException::class)
fun parse(xcp: XContentParser, id: String = NO_ID, version: Long = NO_VERSION): Monitor {
lateinit var name: String
var user: User? = null
lateinit var schedule: Schedule
var lastUpdateTime: Instant? = null
var enabledTime: Instant? = null
Expand All @@ -180,6 +191,7 @@ data class Monitor(
when (fieldName) {
SCHEMA_VERSION_FIELD -> schemaVersion = xcp.intValue()
NAME_FIELD -> name = xcp.text()
USER_FIELD -> user = User.parse(xcp)
ENABLED_FIELD -> enabled = xcp.booleanValue()
SCHEDULE_FIELD -> schedule = Schedule.parse(xcp)
INPUTS_FIELD -> {
Expand Down Expand Up @@ -215,6 +227,7 @@ data class Monitor(
requireNotNull(schedule) { "Monitor schedule is null" },
lastUpdateTime ?: Instant.now(),
enabledTime,
user,
schemaVersion,
inputs.toList(),
triggers.toList(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import com.amazon.opendistroforelasticsearch.alerting.destination.response.Desti
import com.amazon.opendistroforelasticsearch.alerting.elasticapi.convertToMap
import com.amazon.opendistroforelasticsearch.alerting.elasticapi.instant
import com.amazon.opendistroforelasticsearch.alerting.elasticapi.optionalTimeField
import com.amazon.opendistroforelasticsearch.alerting.core.model.User
import com.amazon.opendistroforelasticsearch.alerting.elasticapi.optionalUserField
import com.amazon.opendistroforelasticsearch.alerting.model.destination.email.Email
import com.amazon.opendistroforelasticsearch.alerting.util.DestinationType
import com.amazon.opendistroforelasticsearch.alerting.util.IndexUtils.Companion.NO_SCHEMA_VERSION
Expand All @@ -48,6 +50,7 @@ data class Destination(
val schemaVersion: Int = NO_SCHEMA_VERSION,
val type: DestinationType,
val name: String,
val user: User?,
val lastUpdateTime: Instant,
val chime: Chime?,
val slack: Slack?,
Expand All @@ -60,6 +63,7 @@ data class Destination(
if (params.paramAsBoolean("with_type", false)) builder.startObject(DESTINATION)
builder.field(TYPE_FIELD, type.value)
.field(NAME_FIELD, name)
.optionalUserField(USER_FIELD, user)
.field(SCHEMA_VERSION, schemaVersion)
.optionalTimeField(LAST_UPDATE_TIME_FIELD, lastUpdateTime)
.field(type.value, constructResponseForDestinationType(type))
Expand All @@ -78,37 +82,24 @@ data class Destination(
out.writeInt(schemaVersion)
out.writeEnum(type)
out.writeString(name)
out.writeBoolean(user != null)
user?.writeTo(out)
out.writeInstant(lastUpdateTime)
if (chime != null) {
out.writeBoolean(true)
chime.writeTo(out)
} else {
out.writeBoolean(false)
}
if (slack != null) {
out.writeBoolean(true)
slack.writeTo(out)
} else {
out.writeBoolean(false)
}
if (customWebhook != null) {
out.writeBoolean(true)
customWebhook.writeTo(out)
} else {
out.writeBoolean(false)
}
if (email != null) {
out.writeBoolean(true)
email.writeTo(out)
} else {
out.writeBoolean(false)
}
out.writeBoolean(chime != null)
chime?.writeTo(out)
out.writeBoolean(slack != null)
slack?.writeTo(out)
out.writeBoolean(customWebhook != null)
customWebhook?.writeTo(out)
out.writeBoolean(email != null)
email?.writeTo(out)
}

companion object {
const val DESTINATION = "destination"
const val TYPE_FIELD = "type"
const val NAME_FIELD = "name"
const val USER_FIELD = "user"
const val NO_ID = ""
const val NO_VERSION = 1L
const val SCHEMA_VERSION = "schema_version"
Expand All @@ -128,6 +119,7 @@ data class Destination(
@Throws(IOException::class)
fun parse(xcp: XContentParser, id: String = NO_ID, version: Long = NO_VERSION): Destination {
lateinit var name: String
var user: User? = null
lateinit var type: String
var slack: Slack? = null
var chime: Chime? = null
Expand All @@ -143,6 +135,7 @@ data class Destination(

when (fieldName) {
NAME_FIELD -> name = xcp.text()
USER_FIELD -> user = User.parse(xcp)
TYPE_FIELD -> {
type = xcp.text()
val allowedTypes = DestinationType.values().map { it.value }
Expand Down Expand Up @@ -179,6 +172,7 @@ data class Destination(
schemaVersion,
DestinationType.valueOf(type.toUpperCase(Locale.ROOT)),
requireNotNull(name) { "Destination name is null" },
user,
lastUpdateTime ?: Instant.now(),
chime,
slack,
Expand All @@ -190,16 +184,19 @@ data class Destination(
@Throws(IOException::class)
fun readFrom(sin: StreamInput): Destination {
return Destination(
sin.readString(), // id
sin.readLong(), // version
sin.readInt(), // schemaVersion
sin.readEnum(DestinationType::class.java), // type
sin.readString(), // name
sin.readInstant(), // lastUpdateTime
Chime.readFrom(sin), // chime
Slack.readFrom(sin), // slack
CustomWebhook.readFrom(sin), // customWebhook
Email.readFrom(sin) // email
id = sin.readString(),
version = sin.readLong(),
schemaVersion = sin.readInt(),
type = sin.readEnum(DestinationType::class.java),
name = sin.readString(),
user = if (sin.readBoolean()) {
User(sin)
} else null,
lastUpdateTime = sin.readInstant(),
chime = Chime.readFrom(sin),
slack = Slack.readFrom(sin),
customWebhook = CustomWebhook.readFrom(sin),
email = Email.readFrom(sin)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ abstract class AlertingRestTestCase : ODFERestTestCase() {
return Destination(
type = DestinationType.TEST_ACTION,
name = "test",
user = randomUser(),
lastUpdateTime = Instant.now(),
chime = null,
slack = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ import com.amazon.opendistroforelasticsearch.alerting.model.ActionRunResult
import com.amazon.opendistroforelasticsearch.alerting.model.InputRunResults
import com.amazon.opendistroforelasticsearch.alerting.model.MonitorRunResult
import com.amazon.opendistroforelasticsearch.alerting.model.TriggerRunResult
import com.amazon.opendistroforelasticsearch.alerting.core.model.User
import com.amazon.opendistroforelasticsearch.alerting.model.action.Throttle
import org.apache.commons.text.RandomStringGenerator
import com.amazon.opendistroforelasticsearch.alerting.model.destination.email.EmailAccount
import com.amazon.opendistroforelasticsearch.alerting.model.destination.email.EmailEntry
import com.amazon.opendistroforelasticsearch.alerting.model.destination.email.EmailGroup
Expand Down Expand Up @@ -61,6 +63,7 @@ import java.time.temporal.ChronoUnit

fun randomMonitor(
name: String = ESRestTestCase.randomAlphaOfLength(10),
user: User = randomUser(),
inputs: List<Input> = listOf(SearchInput(emptyList(), SearchSourceBuilder().query(QueryBuilders.matchAllQuery()))),
schedule: Schedule = IntervalSchedule(interval = 5, unit = ChronoUnit.MINUTES),
enabled: Boolean = ESTestCase.randomBoolean(),
Expand All @@ -71,7 +74,23 @@ fun randomMonitor(
): Monitor {
return Monitor(name = name, enabled = enabled, inputs = inputs, schedule = schedule, triggers = triggers,
enabledTime = enabledTime, lastUpdateTime = lastUpdateTime,
uiMetadata = if (withMetadata) mapOf("foo" to "bar") else mapOf())
user = user, uiMetadata = if (withMetadata) mapOf("foo" to "bar") else mapOf())
}

// Monitor of older versions without security.
fun randomMonitorWithoutUser(
name: String = ESRestTestCase.randomAlphaOfLength(10),
inputs: List<Input> = listOf(SearchInput(emptyList(), SearchSourceBuilder().query(QueryBuilders.matchAllQuery()))),
schedule: Schedule = IntervalSchedule(interval = 5, unit = ChronoUnit.MINUTES),
enabled: Boolean = ESTestCase.randomBoolean(),
triggers: List<Trigger> = (1..randomInt(10)).map { randomTrigger() },
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, enabled = enabled, inputs = inputs, schedule = schedule, triggers = triggers,
enabledTime = enabledTime, lastUpdateTime = lastUpdateTime,
user = null, uiMetadata = if (withMetadata) mapOf("foo" to "bar") else mapOf())
}

fun randomTrigger(
Expand Down Expand Up @@ -201,6 +220,18 @@ fun Monitor.toJsonString(): String {
return this.toXContent(builder).string()
}

fun randomUser(): User {
val ranStrGen = RandomStringGenerator.Builder().build()
val randomUser = ranStrGen.generate(5)
val bckEndRole1 = ranStrGen.generate(10)
val bckEndRole2 = ranStrGen.generate(10)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are already provided helper methods for generating random values, e.g. ESRestTestCase.randomAlphaOfLength(10)

return User(randomUser, listOf(bckEndRole1, bckEndRole2), listOf("all_access"), listOf("test_attr=test"))
}

fun randomUserEmpty(): User {
return User("", listOf(), listOf(), listOf())
}

fun EmailAccount.toJsonString(): String {
val builder = XContentFactory.jsonBuilder()
return this.toXContent(builder).string()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package com.amazon.opendistroforelasticsearch.alerting.action

import com.amazon.opendistroforelasticsearch.alerting.core.model.CronSchedule
import com.amazon.opendistroforelasticsearch.alerting.model.Monitor
import com.amazon.opendistroforelasticsearch.alerting.randomUser
import org.elasticsearch.common.io.stream.BytesStreamOutput
import org.elasticsearch.common.io.stream.StreamInput
import org.elasticsearch.rest.RestStatus
Expand Down Expand Up @@ -47,7 +48,7 @@ class GetMonitorResponseTests : ESTestCase() {
val cronSchedule = CronSchedule(cronExpression, ZoneId.of("Asia/Kolkata"), testInstance)
val req = GetMonitorResponse("1234", 1L, 2L, 0L, RestStatus.OK,
Monitor("123", 0L, "test-monitor", true, cronSchedule, Instant.now(),
Instant.now(), 0, mutableListOf(), mutableListOf(), mutableMapOf()))
Instant.now(), randomUser(), 0, mutableListOf(), mutableListOf(), mutableMapOf()))
assertNotNull(req)

val out = BytesStreamOutput()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package com.amazon.opendistroforelasticsearch.alerting.action

import com.amazon.opendistroforelasticsearch.alerting.model.destination.Chime
import com.amazon.opendistroforelasticsearch.alerting.model.destination.Destination
import com.amazon.opendistroforelasticsearch.alerting.randomUser
import com.amazon.opendistroforelasticsearch.alerting.util.DestinationType
import org.elasticsearch.action.support.WriteRequest
import org.elasticsearch.common.io.stream.BytesStreamOutput
Expand All @@ -41,6 +42,7 @@ class IndexDestinationRequestTests : ESTestCase() {
1,
DestinationType.CHIME,
"TestChimeDest",
randomUser(),
Instant.now(),
Chime("test.com"),
null,
Expand Down Expand Up @@ -77,6 +79,7 @@ class IndexDestinationRequestTests : ESTestCase() {
1,
DestinationType.CHIME,
"TestChimeDest",
randomUser(),
Instant.now(),
Chime("test.com"),
null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package com.amazon.opendistroforelasticsearch.alerting.action

import com.amazon.opendistroforelasticsearch.alerting.model.destination.Chime
import com.amazon.opendistroforelasticsearch.alerting.model.destination.Destination
import com.amazon.opendistroforelasticsearch.alerting.randomUser
import com.amazon.opendistroforelasticsearch.alerting.util.DestinationType
import org.elasticsearch.common.io.stream.BytesStreamOutput
import org.elasticsearch.common.io.stream.StreamInput
Expand All @@ -30,8 +31,7 @@ class IndexDestinationResponseTests : ESTestCase() {

val req = IndexDestinationResponse("1234", 0L, 1L, 2L, RestStatus.CREATED,
Destination("1234", 0L, 1, DestinationType.CHIME, "TestChimeDest",
Instant.now(), Chime("test.com"), null, null, null))

randomUser(), Instant.now(), Chime("test.com"), null, null, null))
assertNotNull(req)

val out = BytesStreamOutput()
Expand Down
Loading