Skip to content
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 @@ -3,11 +3,14 @@ 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.commons.alerting.model.DocLevelMonitorInput
import org.opensearch.commons.alerting.model.Monitor
import org.opensearch.commons.alerting.util.IndexPatternUtils
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.Locale

class IndexMonitorRequest : ActionRequest {
val monitorId: String
Expand Down Expand Up @@ -48,9 +51,22 @@ class IndexMonitorRequest : ActionRequest {
)

override fun validate(): ActionRequestValidationException? {
if (isDocLevelMonitor() && hasDocLeveMonitorInput()) {
val docLevelMonitorInput = monitor.inputs[0] as DocLevelMonitorInput
if (docLevelMonitorInput.indices.stream().anyMatch { IndexPatternUtils.containsPatternSyntax(it) }) {
val actionValidationException = ActionRequestValidationException()
actionValidationException.addValidationError("Index patterns are not supported for doc level monitors.")
return actionValidationException
}
}
return null
}

private fun hasDocLeveMonitorInput() = monitor.inputs.isNotEmpty() && monitor.inputs[0] is DocLevelMonitorInput

private fun isDocLevelMonitor() =
monitor.monitorType.isNotBlank() && Monitor.MonitorType.valueOf(this.monitor.monitorType.uppercase(Locale.ROOT)) == Monitor.MonitorType.DOC_LEVEL_MONITOR

@Throws(IOException::class)
override fun writeTo(out: StreamOutput) {
out.writeString(monitorId)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.opensearch.commons.alerting.util

/**
* Validates that index patterns, wildcards and regex are not used in index names.
*/
object IndexPatternUtils {
private val PATTERN_SPECIAL_CHARS = setOf(
'*', // wildcard for any number of characters
'?', // wildcard for single character
'+', // one or more quantifier
'[', // character class start
']', // character class end
'(', // group start
')', // group end
'{', // range quantifier start
'}', // range quantifier end
'|', // OR operator
'\\', // escape character
'.', // any character
'^', // start anchor/negation in character class
'$' // end anchor
)

fun containsPatternSyntax(indexName: String): Boolean {
if (indexName.isEmpty() || indexName == "_all") {
return true
}

// Check for date math expression <...>
if (indexName.startsWith("<") && indexName.endsWith(">")) {
return true
}

var i = 0
while (i < indexName.length) {
when (val currentChar = indexName[i]) {
'\\' -> i += 2 // Skip escaped character
in PATTERN_SPECIAL_CHARS -> return true
else -> i++
}
}
return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ fun randomDocumentLevelMonitor(
inputs: List<Input> = listOf(DocLevelMonitorInput("description", listOf("index"), emptyList())),
schedule: Schedule = IntervalSchedule(interval = 5, unit = ChronoUnit.MINUTES),
enabled: Boolean = Random().nextBoolean(),
triggers: List<Trigger> = (1..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { randomQueryLevelTrigger() },
triggers: List<Trigger> = (1..RandomNumbers.randomIntBetween(Random(), 0, 10)).map { randomDocumentLevelTrigger() },
enabledTime: Instant? = if (enabled) Instant.now().truncatedTo(ChronoUnit.MILLIS) else null,
lastUpdateTime: Instant = Instant.now().truncatedTo(ChronoUnit.MILLIS),
withMetadata: Boolean = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ package org.opensearch.commons.alerting.action

import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.opensearch.action.ActionRequestValidationException
import org.opensearch.action.support.WriteRequest
import org.opensearch.common.io.stream.BytesStreamOutput
import org.opensearch.common.settings.Settings
import org.opensearch.commons.alerting.model.DocLevelMonitorInput
import org.opensearch.commons.alerting.model.Monitor
import org.opensearch.commons.alerting.model.SearchInput
import org.opensearch.commons.alerting.randomBucketLevelMonitor
import org.opensearch.commons.alerting.randomDocumentLevelMonitor
import org.opensearch.commons.alerting.randomQueryLevelMonitor
import org.opensearch.commons.utils.recreateObject
import org.opensearch.core.common.io.stream.NamedWriteableAwareStreamInput
Expand Down Expand Up @@ -80,7 +84,10 @@ class IndexMonitorRequestTests {
recreateObject(bucketLevelMonitorRequest) { IndexMonitorRequest(it) }
}

val recreatedObject = recreateObject(bucketLevelMonitorRequest, NamedWriteableRegistry(SearchModule(Settings.EMPTY, emptyList()).namedWriteables)) { 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)
Expand Down Expand Up @@ -111,4 +118,111 @@ class IndexMonitorRequestTests {
Assertions.assertEquals(RestRequest.Method.PUT, newReq.method)
Assertions.assertNotNull(newReq.monitor)
}

@Test
fun `test doc level monitor with valid index name`() {
val monitor = randomDocumentLevelMonitor().copy(
inputs = listOf(DocLevelMonitorInput(indices = listOf("valid-index"), queries = emptyList())),
triggers = emptyList()
)
val req = IndexMonitorRequest(
"1234",
1L,
2L,
WriteRequest.RefreshPolicy.IMMEDIATE,
RestRequest.Method.POST,
monitor
)

val validationException = req.validate()
Assertions.assertNull(validationException)
}

@Test
fun `test doc level monitor with wildcard index pattern`() {
val monitor = randomDocumentLevelMonitor().copy(
inputs = listOf(DocLevelMonitorInput(indices = listOf("valid, test*", "test*"), queries = emptyList()))
)
val req = IndexMonitorRequest(
"1234",
1L,
2L,
WriteRequest.RefreshPolicy.IMMEDIATE,
RestRequest.Method.POST,
monitor
)

val validationException = req.validate()
Assertions.assertNotNull(validationException)
Assertions.assertTrue(validationException is ActionRequestValidationException)
Assertions.assertTrue(
validationException!!.validationErrors().contains("Index patterns are not supported for doc level monitors.")
?: false
)
}

@Test
fun `test doc level monitor with regex index pattern`() {
val monitor = randomDocumentLevelMonitor().copy(
inputs = listOf(DocLevelMonitorInput(indices = listOf("test[0-9]+"), queries = emptyList())),
triggers = emptyList()
)
val req = IndexMonitorRequest(
"1234",
1L,
2L,
WriteRequest.RefreshPolicy.IMMEDIATE,
RestRequest.Method.POST,
monitor
)

val validationException = req.validate()
Assertions.assertNotNull(validationException)
Assertions.assertTrue(validationException is ActionRequestValidationException)
Assertions.assertTrue(
validationException!!.validationErrors().contains("Index patterns are not supported for doc level monitors.")
)
}

@Test
fun `test doc level monitor with date math index pattern`() {
val monitor = randomDocumentLevelMonitor().copy(
inputs = listOf(DocLevelMonitorInput(indices = listOf("<test-{now/d}>"), queries = emptyList())),
triggers = emptyList()
)
val req = IndexMonitorRequest(
"1234",
1L,
2L,
WriteRequest.RefreshPolicy.IMMEDIATE,
RestRequest.Method.POST,
monitor
)

val validationException = req.validate()
Assertions.assertNotNull(validationException)
Assertions.assertTrue(validationException is ActionRequestValidationException)
Assertions.assertTrue(
validationException!!.validationErrors().contains("Index patterns are not supported for doc level monitors.")
)
}

@Test
fun `test non-doc level monitor with index pattern`() {
val monitor = randomQueryLevelMonitor().copy(
inputs = listOf(SearchInput(listOf("test*"), SearchSourceBuilder())),
monitorType = Monitor.MonitorType.QUERY_LEVEL_MONITOR.name
)
val req = IndexMonitorRequest(
"1234",
1L,
2L,
WriteRequest.RefreshPolicy.IMMEDIATE,
RestRequest.Method.POST,
monitor
)

val validationException = req.validate()
Assertions.assertNull(validationException)
}
}
Loading