Skip to content

Commit 497dea8

Browse files
PPL Alerting: Models (#1955)
* PPL Alerting Models Signed-off-by: Dennis Toepker <[email protected]> * correcting readFrom in PPLTriggerRunResult Signed-off-by: Dennis Toepker <[email protected]> * adding minutes to duration fields, changing name from PPLMonitor to PPLSQLMonitor Signed-off-by: Dennis Toepker <[email protected]> * adjusting comment positions Signed-off-by: Dennis Toepker <[email protected]> * adding description field to monitor Signed-off-by: Dennis Toepker <[email protected]> * adding description javadoc Signed-off-by: Dennis Toepker <[email protected]> * various refactors Signed-off-by: Dennis Toepker <[email protected]> * making alert initiate its own parsing pointer Signed-off-by: Dennis Toepker <[email protected]> * adding number of results value validations Signed-off-by: Dennis Toepker <[email protected]> * adding full stops to error messages Signed-off-by: Dennis Toepker <[email protected]> * adding another missing full stop Signed-off-by: Dennis Toepker <[email protected]> * renaming customer exposed field name back to ppl_monitor Signed-off-by: Dennis Toepker <[email protected]> * renaming PPL to PPL_SQL in certain places Signed-off-by: Dennis Toepker <[email protected]> --------- Signed-off-by: Dennis Toepker <[email protected]> Co-authored-by: Dennis Toepker <[email protected]>
1 parent e96d07f commit 497dea8

File tree

14 files changed

+2218
-1
lines changed

14 files changed

+2218
-1
lines changed
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.alerting.modelv2
7+
8+
import org.opensearch.alerting.core.util.nonOptionalTimeField
9+
import org.opensearch.alerting.modelv2.TriggerV2.Severity
10+
import org.opensearch.common.lucene.uid.Versions
11+
import org.opensearch.commons.alerting.util.instant
12+
import org.opensearch.commons.alerting.util.optionalUserField
13+
import org.opensearch.commons.authuser.User
14+
import org.opensearch.core.common.io.stream.StreamInput
15+
import org.opensearch.core.common.io.stream.StreamOutput
16+
import org.opensearch.core.common.io.stream.Writeable
17+
import org.opensearch.core.xcontent.ToXContent
18+
import org.opensearch.core.xcontent.XContentBuilder
19+
import org.opensearch.core.xcontent.XContentParser
20+
import org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken
21+
import java.io.IOException
22+
import java.time.Instant
23+
24+
/**
25+
* Alert generated by Alerting V2
26+
* An alert is created when a Trigger's trigger conditions are met.
27+
*
28+
* @property id Alert ID. Defaults to [NO_ID].
29+
* @property version Version number of the Alert. Defaults to [NO_VERSION].
30+
* @property schemaVersion Version of the alerting-alerts index schema when this Alert was indexed. Defaults to [NO_SCHEMA_VERSION].
31+
* @property monitorId ID of the Monitor that generated this Alert.
32+
* @property monitorName Name of the Monitor that generated this Alert.
33+
* @property monitorVersion Version of the Monitor at the time it generated this Alert.
34+
* @property triggerId ID of the Trigger in the Monitor that generated this alert.
35+
* @property triggerName Name of the trigger in the Monitor that generated this alert.
36+
* @property queryResults Results from the Monitor's query that caused the Trigger to fire.
37+
* @property triggeredTime Timestamp for when the Alert was generated.
38+
* @property errorMessage Optional error message if there were issues during Trigger execution.
39+
* Null indicates no errors occurred.
40+
* @property severity Severity level of the alert (e.g., "HIGH", "MEDIUM", "LOW").
41+
* @property executionId Optional ID for the Monitor execution that generated this Alert.
42+
*
43+
* @see MonitorV2 For the monitor that generates alerts
44+
* @see TriggerV2 For the trigger conditions that create alerts
45+
*
46+
* Lifecycle:
47+
* 1. AlertV2 is generated when a TriggerV2's condition is met. The TriggerV2 fires and forgets the AlertV2.
48+
* 2. AlertV2 is stored in the alerts index. AlertV2s are stateless. (e.g. they are never ACTIVE or COMPLETED)
49+
* 3. AlertV2 is soft deleted after its expire duration (determined by its trigger), and archived in an alert history index
50+
* 4. Based on the alert v2 history retention period, the AlertV2 is permanently deleted
51+
*/
52+
data class AlertV2(
53+
val id: String = NO_ID,
54+
val version: Long = NO_VERSION,
55+
val schemaVersion: Int = NO_SCHEMA_VERSION,
56+
val monitorId: String,
57+
val monitorName: String,
58+
val monitorVersion: Long,
59+
val monitorUser: User?,
60+
val triggerId: String,
61+
val triggerName: String,
62+
val query: String,
63+
val queryResults: Map<String, Any>,
64+
val triggeredTime: Instant,
65+
val errorMessage: String? = null,
66+
val severity: Severity,
67+
val executionId: String? = null
68+
) : Writeable, ToXContent {
69+
@Throws(IOException::class)
70+
constructor(sin: StreamInput) : this(
71+
id = sin.readString(),
72+
version = sin.readLong(),
73+
schemaVersion = sin.readInt(),
74+
monitorId = sin.readString(),
75+
monitorName = sin.readString(),
76+
monitorVersion = sin.readLong(),
77+
monitorUser = if (sin.readBoolean()) {
78+
User(sin)
79+
} else {
80+
null
81+
},
82+
triggerId = sin.readString(),
83+
triggerName = sin.readString(),
84+
query = sin.readString(),
85+
queryResults = sin.readMap(),
86+
triggeredTime = sin.readInstant(),
87+
errorMessage = sin.readOptionalString(),
88+
severity = sin.readEnum(Severity::class.java),
89+
executionId = sin.readOptionalString()
90+
)
91+
92+
@Throws(IOException::class)
93+
override fun writeTo(out: StreamOutput) {
94+
out.writeString(id)
95+
out.writeLong(version)
96+
out.writeInt(schemaVersion)
97+
out.writeString(monitorId)
98+
out.writeString(monitorName)
99+
out.writeLong(monitorVersion)
100+
out.writeBoolean(monitorUser != null)
101+
monitorUser?.writeTo(out)
102+
out.writeString(triggerId)
103+
out.writeString(triggerName)
104+
out.writeString(query)
105+
out.writeMap(queryResults)
106+
out.writeInstant(triggeredTime)
107+
out.writeOptionalString(errorMessage)
108+
out.writeEnum(severity)
109+
out.writeOptionalString(executionId)
110+
}
111+
112+
override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder {
113+
return createXContentBuilder(builder, false)
114+
}
115+
116+
fun toXContentWithUser(builder: XContentBuilder): XContentBuilder {
117+
return createXContentBuilder(builder, true)
118+
}
119+
120+
private fun createXContentBuilder(builder: XContentBuilder, withUser: Boolean): XContentBuilder {
121+
builder.startObject()
122+
.field(ALERT_V2_ID_FIELD, id)
123+
.field(ALERT_V2_VERSION_FIELD, version)
124+
.field(MONITOR_V2_ID_FIELD, monitorId)
125+
.field(SCHEMA_VERSION_FIELD, schemaVersion)
126+
.field(MONITOR_V2_VERSION_FIELD, monitorVersion)
127+
.field(MONITOR_V2_NAME_FIELD, monitorName)
128+
.field(EXECUTION_ID_FIELD, executionId)
129+
.field(TRIGGER_V2_ID_FIELD, triggerId)
130+
.field(TRIGGER_V2_NAME_FIELD, triggerName)
131+
.field(QUERY_FIELD, query)
132+
.field(QUERY_RESULTS_FIELD, queryResults)
133+
.field(ERROR_MESSAGE_FIELD, errorMessage)
134+
.field(SEVERITY_FIELD, severity.value)
135+
.nonOptionalTimeField(TRIGGERED_TIME_FIELD, triggeredTime)
136+
137+
if (withUser) {
138+
builder.optionalUserField(MONITOR_V2_USER_FIELD, monitorUser)
139+
}
140+
141+
builder.endObject()
142+
143+
return builder
144+
}
145+
146+
fun asTemplateArg(): Map<String, Any?> {
147+
return mapOf(
148+
ALERT_V2_ID_FIELD to id,
149+
ALERT_V2_VERSION_FIELD to version,
150+
ERROR_MESSAGE_FIELD to errorMessage,
151+
EXECUTION_ID_FIELD to executionId,
152+
SEVERITY_FIELD to severity.value
153+
)
154+
}
155+
156+
companion object {
157+
const val ALERT_V2_ID_FIELD = "id"
158+
const val ALERT_V2_VERSION_FIELD = "version"
159+
const val MONITOR_V2_ID_FIELD = "monitor_v2_id"
160+
const val MONITOR_V2_VERSION_FIELD = "monitor_v2_version"
161+
const val MONITOR_V2_NAME_FIELD = "monitor_v2_name"
162+
const val MONITOR_V2_USER_FIELD = "monitor_v2_user"
163+
const val TRIGGER_V2_ID_FIELD = "trigger_v2_id"
164+
const val TRIGGER_V2_NAME_FIELD = "trigger_v2_name"
165+
const val TRIGGERED_TIME_FIELD = "triggered_time"
166+
const val QUERY_FIELD = "query"
167+
const val QUERY_RESULTS_FIELD = "query_results"
168+
const val ERROR_MESSAGE_FIELD = "error_message"
169+
const val EXECUTION_ID_FIELD = "execution_id"
170+
const val SEVERITY_FIELD = "severity"
171+
const val SCHEMA_VERSION_FIELD = "schema_version"
172+
173+
const val NO_ID = ""
174+
const val NO_VERSION = Versions.NOT_FOUND
175+
const val NO_SCHEMA_VERSION = 0
176+
177+
@JvmStatic
178+
@JvmOverloads
179+
@Throws(IOException::class)
180+
fun parse(xcp: XContentParser, id: String = NO_ID, version: Long = NO_VERSION): AlertV2 {
181+
var schemaVersion = NO_SCHEMA_VERSION
182+
lateinit var monitorId: String
183+
lateinit var monitorName: String
184+
var monitorVersion: Long = Versions.NOT_FOUND
185+
var monitorUser: User? = null
186+
lateinit var triggerId: String
187+
lateinit var triggerName: String
188+
lateinit var query: String
189+
var queryResults: Map<String, Any> = mapOf()
190+
lateinit var severity: Severity
191+
var triggeredTime: Instant? = null
192+
var errorMessage: String? = null
193+
var executionId: String? = null
194+
195+
ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.nextToken(), xcp)
196+
while (xcp.nextToken() != XContentParser.Token.END_OBJECT) {
197+
val fieldName = xcp.currentName()
198+
xcp.nextToken()
199+
200+
when (fieldName) {
201+
MONITOR_V2_ID_FIELD -> monitorId = xcp.text()
202+
SCHEMA_VERSION_FIELD -> schemaVersion = xcp.intValue()
203+
MONITOR_V2_NAME_FIELD -> monitorName = xcp.text()
204+
MONITOR_V2_VERSION_FIELD -> monitorVersion = xcp.longValue()
205+
MONITOR_V2_USER_FIELD ->
206+
monitorUser = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) {
207+
null
208+
} else {
209+
User.parse(xcp)
210+
}
211+
TRIGGER_V2_ID_FIELD -> triggerId = xcp.text()
212+
TRIGGER_V2_NAME_FIELD -> triggerName = xcp.text()
213+
QUERY_FIELD -> query = xcp.text()
214+
QUERY_RESULTS_FIELD -> queryResults = xcp.map()
215+
TRIGGERED_TIME_FIELD -> triggeredTime = xcp.instant()
216+
ERROR_MESSAGE_FIELD -> errorMessage = xcp.textOrNull()
217+
EXECUTION_ID_FIELD -> executionId = xcp.textOrNull()
218+
TriggerV2.SEVERITY_FIELD -> {
219+
val input = xcp.text()
220+
val enumMatchResult = Severity.enumFromString(input)
221+
?: throw IllegalArgumentException(
222+
"Invalid value for ${TriggerV2.SEVERITY_FIELD}: $input. " +
223+
"Supported values are ${Severity.entries.map { it.value }}"
224+
)
225+
severity = enumMatchResult
226+
}
227+
}
228+
}
229+
230+
return AlertV2(
231+
id = id,
232+
version = version,
233+
schemaVersion = schemaVersion,
234+
monitorId = requireNotNull(monitorId),
235+
monitorName = requireNotNull(monitorName),
236+
monitorVersion = monitorVersion,
237+
monitorUser = monitorUser,
238+
triggerId = requireNotNull(triggerId),
239+
triggerName = requireNotNull(triggerName),
240+
query = requireNotNull(query),
241+
queryResults = requireNotNull(queryResults),
242+
triggeredTime = requireNotNull(triggeredTime),
243+
errorMessage = errorMessage,
244+
severity = severity,
245+
executionId = executionId
246+
)
247+
}
248+
249+
@JvmStatic
250+
@Throws(IOException::class)
251+
fun readFrom(sin: StreamInput): AlertV2 {
252+
return AlertV2(sin)
253+
}
254+
}
255+
}

0 commit comments

Comments
 (0)