Skip to content

Commit 7e8b376

Browse files
Merge pull request #698 from SumoLogic/terraform-support-for-metric-anomaly-monitors
Adding terraform support for metric anomaly monitors
2 parents 95123da + e441133 commit 7e8b376

File tree

4 files changed

+168
-3
lines changed

4 files changed

+168
-3
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
DEPRECATIONS:
33
* resource_sumologic_ingest_budget : Deprecated in favour of `resource_sumologic_ingest_budget_v2`.
44

5+
## 2.31.6 (Unreleased)
6+
ENHANCEMENTS:
7+
* Add support for MetricsAnomalyCondition to Monitor resource (GH-698)
8+
59
## 2.31.5 (October 04, 2024)
610
ENHANCEMENTS:
711
* Added *index_id* attribute to sumologic_scheduled_view. (GH-691)

sumologic/resource_sumologic_monitors_library_monitor.go

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,14 @@ func getMonitorBaseSchema() map[string]*schema.Schema {
172172
Schema: logsAnomalyTriggerConditionSchema,
173173
},
174174
},
175+
metricsAnomalyConditionFieldName: {
176+
Type: schema.TypeList,
177+
MaxItems: 1,
178+
Optional: true,
179+
Elem: &schema.Resource{
180+
Schema: metricsAnomalyTriggerConditionSchema,
181+
},
182+
},
175183
},
176184
},
177185
},
@@ -446,6 +454,7 @@ var (
446454
"trigger_conditions.0.slo_sli_condition",
447455
"trigger_conditions.0.slo_burn_rate_condition",
448456
fmt.Sprintf("trigger_conditions.0.%s", logsAnomalyConditionFieldName),
457+
fmt.Sprintf("trigger_conditions.0.%s", metricsAnomalyConditionFieldName),
449458
}
450459
logStaticConditionCriticalOrWarningAtleastOneKeys = []string{
451460
"trigger_conditions.0.logs_static_condition.0.warning",
@@ -651,6 +660,34 @@ var logsAnomalyTriggerConditionSchema = map[string]*schema.Schema{
651660
}),
652661
}
653662

663+
var metricsAnomalyTriggerConditionSchema = map[string]*schema.Schema{
664+
"direction": {
665+
Type: schema.TypeString,
666+
Optional: true,
667+
Default: "Both",
668+
ValidateFunc: validation.StringInSlice([]string{"Both", "Up", "Down"}, false),
669+
},
670+
"anomaly_detector_type": {
671+
Type: schema.TypeString,
672+
Required: true,
673+
ValidateFunc: validation.StringInSlice([]string{"Cluster"}, false),
674+
},
675+
"critical": nested(false, schemaMap{
676+
"sensitivity": {
677+
Type: schema.TypeFloat,
678+
Optional: true,
679+
Default: 0.5,
680+
ValidateFunc: validation.FloatBetween(0.1, 1.0),
681+
},
682+
"min_anomaly_count": {
683+
Type: schema.TypeInt,
684+
Optional: true,
685+
Default: 1,
686+
},
687+
"time_range": &timeRangeWithAllowedValuesSchema,
688+
}),
689+
}
690+
654691
func getBurnRateSchema(triggerType string) *schema.Schema {
655692
burnRateThresholdConflict := fmt.Sprintf("trigger_conditions.0.slo_burn_rate_condition.0.%s.0.burn_rate_threshold", triggerType)
656693
timeRangeConflict := fmt.Sprintf("trigger_conditions.0.slo_burn_rate_condition.0.%s.0.time_range", triggerType)
@@ -1142,6 +1179,9 @@ func triggerConditionsBlockToJson(block map[string]interface{}) []TriggerConditi
11421179
if sc, ok := fromSingletonArray(block, logsAnomalyConditionFieldName); ok {
11431180
conditions = append(conditions, logsAnomalyConditionBlockToJson(sc)...)
11441181
}
1182+
if sc, ok := fromSingletonArray(block, metricsAnomalyConditionFieldName); ok {
1183+
conditions = append(conditions, metricsAnomalyConditionBlockToJson(sc)...)
1184+
}
11451185

11461186
return conditions
11471187
}
@@ -1266,6 +1306,21 @@ func logsAnomalyConditionBlockToJson(block map[string]interface{}) []TriggerCond
12661306
return base.cloneReadingFromNestedBlocks(block)
12671307
}
12681308

1309+
func metricsAnomalyConditionBlockToJson(block map[string]interface{}) []TriggerCondition {
1310+
base := TriggerCondition{
1311+
Direction: block["direction"].(string),
1312+
AnomalyDetectorType: block["anomaly_detector_type"].(string),
1313+
DetectionMethod: metricsAnomalyConditionDetectionMethod,
1314+
}
1315+
// metric anomaly condition does not have 'alert' and 'resolution' objects. Here we generate empty blocks
1316+
// for reading to work
1317+
if subBlock, ok := fromSingletonArray(block, "critical"); ok {
1318+
subBlock["alert"] = toSingletonArray(map[string]interface{}{})
1319+
subBlock["resolution"] = toSingletonArray(map[string]interface{}{})
1320+
}
1321+
return base.cloneReadingFromNestedBlocks(block)
1322+
}
1323+
12691324
// TriggerCondition JSON model to 'trigger_conditions' block
12701325
func jsonToTriggerConditionsBlock(conditions []TriggerCondition) map[string]interface{} {
12711326
missingDataConditions := make([]TriggerCondition, 0)
@@ -1294,6 +1349,9 @@ func jsonToTriggerConditionsBlock(conditions []TriggerCondition) map[string]inte
12941349
triggerConditionsBlock[sloBurnRateConditionFieldName] = toSingletonArray(jsonToSloBurnRateConditionBlock(dataConditions))
12951350
case logsAnomalyConditionDetectionMethod:
12961351
triggerConditionsBlock[logsAnomalyConditionFieldName] = toSingletonArray(jsonToLogsAnomalyConditionBlock(dataConditions))
1352+
case metricsAnomalyConditionDetectionMethod:
1353+
triggerConditionsBlock[metricsAnomalyConditionFieldName] = toSingletonArray(jsonToMetricsAnomalyConditionBlock(dataConditions))
1354+
12971355
}
12981356
}
12991357
if len(missingDataConditions) > 0 {
@@ -1580,6 +1638,36 @@ func jsonToLogsAnomalyConditionBlock(conditions []TriggerCondition) map[string]i
15801638
return block
15811639
}
15821640

1641+
func jsonToMetricsAnomalyConditionBlock(conditions []TriggerCondition) map[string]interface{} {
1642+
block := map[string]interface{}{}
1643+
1644+
block["direction"] = conditions[0].Direction
1645+
block["anomaly_detector_type"] = conditions[0].AnomalyDetectorType
1646+
1647+
var criticalDict = dict{}
1648+
block["critical"] = toSingletonArray(criticalDict)
1649+
1650+
var hasCritical = false
1651+
for _, condition := range conditions {
1652+
switch condition.TriggerType {
1653+
case "Critical":
1654+
hasCritical = true
1655+
criticalDict["sensitivity"] = condition.Sensitivity
1656+
criticalDict["min_anomaly_count"] = condition.MinAnomalyCount
1657+
criticalDict["time_range"] = condition.PositiveTimeRange()
1658+
case "ResolvedCritical":
1659+
hasCritical = true
1660+
criticalDict["sensitivity"] = condition.Sensitivity
1661+
criticalDict["min_anomaly_count"] = condition.MinAnomalyCount
1662+
criticalDict["time_range"] = condition.PositiveTimeRange()
1663+
}
1664+
}
1665+
if !hasCritical {
1666+
delete(block, "critical")
1667+
}
1668+
return block
1669+
}
1670+
15831671
func getAlertBlock(condition TriggerCondition) dict {
15841672
var alert = dict{}
15851673
burnRates := make([]interface{}, len(condition.BurnRates))
@@ -1622,6 +1710,7 @@ const metricsMissingDataConditionFieldName = "metrics_missing_data_condition"
16221710
const sloSLIConditionFieldName = "slo_sli_condition"
16231711
const sloBurnRateConditionFieldName = "slo_burn_rate_condition"
16241712
const logsAnomalyConditionFieldName = "logs_anomaly_condition"
1713+
const metricsAnomalyConditionFieldName = "metrics_anomaly_condition"
16251714

16261715
const logsStaticConditionDetectionMethod = "LogsStaticCondition"
16271716
const metricsStaticConditionDetectionMethod = "MetricsStaticCondition"
@@ -1632,6 +1721,7 @@ const metricsMissingDataConditionDetectionMethod = "MetricsMissingDataCondition"
16321721
const sloSLIConditionDetectionMethod = "SloSliCondition"
16331722
const sloBurnRateConditionDetectionMethod = "SloBurnRateCondition"
16341723
const logsAnomalyConditionDetectionMethod = "LogsAnomalyCondition"
1724+
const metricsAnomalyConditionDetectionMethod = "MetricsAnomalyCondition"
16351725

16361726
func getQueries(d *schema.ResourceData) []MonitorQuery {
16371727
rawQueries := d.Get("queries").([]interface{})
@@ -1817,7 +1907,8 @@ func (base TriggerCondition) cloneReadingFromNestedBlocks(block map[string]inter
18171907
resolvedCriticalCondition.OccurrenceType = ""
18181908
}
18191909

1820-
if criticalCondition.DetectionMethod == logsAnomalyConditionDetectionMethod {
1910+
if (criticalCondition.DetectionMethod == logsAnomalyConditionDetectionMethod) ||
1911+
(criticalCondition.DetectionMethod == metricsAnomalyConditionDetectionMethod) {
18211912
criticalCondition.MinAnomalyCount = critical["min_anomaly_count"].(int)
18221913
criticalCondition.Sensitivity = critical["sensitivity"].(float64)
18231914
resolvedCriticalCondition.MinAnomalyCount = criticalCondition.MinAnomalyCount

sumologic/resource_sumologic_monitors_library_monitor_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1434,6 +1434,16 @@ var exampleLogsAnomalyTriggerConditionBlock = `
14341434
}
14351435
}`
14361436

1437+
var exampleMetricsAnomalyTriggerConditionBlock = `
1438+
metrics_anomaly_condition {
1439+
anomaly_detector_type = "Cluster"
1440+
critical {
1441+
sensitivity = 0.5
1442+
min_anomaly_count = 5
1443+
time_range = "-1h"
1444+
}
1445+
}`
1446+
14371447
func exampleLogsStaticMonitor(testName string) string {
14381448
query := "error | timeslice 1m | count as field by _timeslice"
14391449
return exampleMonitorWithTriggerCondition(testName, "Logs", query,
@@ -1501,6 +1511,17 @@ func exampleLogsAnomalyMonitor(testName string) string {
15011511
)
15021512
}
15031513

1514+
func exampleMetricsAnomalyMonitor(testName string) string {
1515+
query := "service=auth api=login metric=HTTP_5XX_Count | avg"
1516+
return exampleMonitorWithTriggerCondition(
1517+
testName,
1518+
"Metrics",
1519+
query,
1520+
exampleMetricsAnomalyTriggerConditionBlock,
1521+
[]string{"Critical", "ResolvedCritical"},
1522+
)
1523+
}
1524+
15041525
var allExampleMonitors = []func(testName string) string{
15051526
exampleLogsStaticMonitor,
15061527
exampleLogsStaticMonitorWithResolutionWindow,
@@ -1513,6 +1534,7 @@ var allExampleMonitors = []func(testName string) string{
15131534
exampleSloSliMonitor,
15141535
exampleSloBurnRateMonitor,
15151536
exampleLogsAnomalyMonitor,
1537+
exampleMetricsAnomalyMonitor,
15161538
}
15171539

15181540
func testAccSumologicMonitorsLibraryMonitorWithInvalidTriggerCondition(testName string, triggerCondition string) string {

website/docs/r/monitor.html.markdown

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,46 @@ resource "sumologic_monitor" "tf_example_anomaly_monitor" {
375375
}
376376
```
377377

378+
## Example Metrics Anomaly Monitor
379+
```hcl
380+
resource "sumologic_monitor" "tf_example_metrics_anomaly_monitor" {
381+
name = "Example Metrics Anomaly Monitor"
382+
description = "example metrics anomaly monitor"
383+
type = "MonitorsLibraryMonitor"
384+
monitor_type = "Metrics"
385+
is_disabled = false
386+
387+
queries {
388+
row_id = "A"
389+
query = "service=auth api=login metric=HTTP_5XX_Count | avg"
390+
}
391+
392+
trigger_conditions {
393+
metrics_anomaly_condition {
394+
anomaly_detector_type = "Cluster"
395+
critical {
396+
sensitivity = 0.4
397+
min_anomaly_count = 9
398+
time_range = "-3h"
399+
}
400+
}
401+
}
402+
403+
notifications {
404+
notification {
405+
connection_type = "Email"
406+
recipients = [
407+
408+
]
409+
subject = "Monitor Alert: {{TriggerType}} on {{Name}}"
410+
time_zone = "PST"
411+
message_body = "Triggered {{TriggerType}} Alert on {{Name}}: {{QueryURL}}"
412+
}
413+
run_for_trigger_types = ["Critical", "ResolvedCritical"]
414+
}
415+
}
416+
```
417+
378418
## Monitor Folders
379419

380420
NOTE: Monitor folders are considered a different resource from Library content folders. See [sumologic_monitor_folder][2] for more details.
@@ -480,7 +520,8 @@ A `trigger_conditions` block contains one or more subblocks of the following typ
480520
- `metrics_missing_data_condition`
481521
- `slo_sli_condition`
482522
- `slo_burn_rate_condition`
483-
- `log_anomaly_condition`
523+
- `logs_anomaly_condition`
524+
- `metrics_anomaly_condition`
484525

485526
Subblocks should be limited to at most 1 missing data condition and at most 1 static / outlier condition.
486527

@@ -571,14 +612,21 @@ Here is a summary of arguments for each condition type (fields which are not mar
571612
- `burn_rate_threshold` (Required): The burn rate percentage threshold.
572613
- `time_range` (Required): The relative time range for the burn rate percentage evaluation. Accepted format: Optional `-` sign followed by `<number>` followed by a `<time_unit>` character: `s` for seconds, `m` for minutes, `h` for hours, `d` for days. Examples: `30m`, `-12h`.
573614

574-
#### log_anomaly_condition
615+
#### logs_anomaly_condition
575616
- `field`: The name of the field that the trigger condition will alert on. The trigger could compare the value of specified field with the threshold. If field is not specified, monitor would default to result count instead.
576617
- `anomaly_detector_type`: The type of anomaly model that will be used for evaluating this monitor. Possible values are: `Cluster`.
577618
- `critical`
578619
- `sensitivity`: The triggering sensitivity of the anomaly model used for this monitor.
579620
- `min_anomaly_count` (Required) : The minimum number of anomalies required to exist in the current time range for the condition to trigger.
580621
- `time_range` (Required) : The relative time range for anomaly evaluation. Accepted format: Optional `-` sign followed by `<number>` followed by a `<time_unit>` character: `s` for seconds, `m` for minutes, `h` for hours, `d` for days. Examples: `30m`, `-12h`.
581622

623+
#### metrics_anomaly_condition
624+
- `anomaly_detector_type`: The type of anomaly model that will be used for evaluating this monitor. Possible values are: `Cluster`.
625+
- `critical`
626+
- `sensitivity`: The triggering sensitivity of the anomaly model used for this monitor.
627+
- `min_anomaly_count` (Required) : The minimum number of anomalies required to exist in the current time range for the condition to trigger.
628+
- `time_range` (Required) : The relative time range for anomaly evaluation. Accepted format: Optional `-` sign followed by `<number>` followed by a `<time_unit>` character: `s` for seconds, `m` for minutes, `h` for hours, `d` for days. Examples: `30m`, `-12h`.
629+
582630
## The `triggers` block
583631
The `triggers` block is deprecated. Please use `trigger_conditions` to specify notification conditions.
584632

0 commit comments

Comments
 (0)