Skip to content

Commit 270c674

Browse files
Add MQL based alerts (#4157) (#2651)
Signed-off-by: Modular Magician <[email protected]>
1 parent df55dc7 commit 270c674

File tree

4 files changed

+323
-4
lines changed

4 files changed

+323
-4
lines changed

.changelog/4157.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
monitoring: Added Monitoring Query Language based alerting for `google_monitoring_alert_policy`
3+
```

google-beta/resource_monitoring_alert_policy.go

Lines changed: 213 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,71 @@ condition to be triggered.`,
237237
Optional: true,
238238
Description: `The percentage of time series that
239239
must fail the predicate for the
240+
condition to be triggered.`,
241+
},
242+
},
243+
},
244+
},
245+
},
246+
},
247+
},
248+
"condition_monitoring_query_language": {
249+
Type: schema.TypeList,
250+
Optional: true,
251+
Description: `A Monitoring Query Language query that outputs a boolean stream`,
252+
MaxItems: 1,
253+
Elem: &schema.Resource{
254+
Schema: map[string]*schema.Schema{
255+
"duration": {
256+
Type: schema.TypeString,
257+
Required: true,
258+
Description: `The amount of time that a time series must
259+
violate the threshold to be considered
260+
failing. Currently, only values that are a
261+
multiple of a minute--e.g., 0, 60, 120, or
262+
300 seconds--are supported. If an invalid
263+
value is given, an error will be returned.
264+
When choosing a duration, it is useful to
265+
keep in mind the frequency of the underlying
266+
time series data (which may also be affected
267+
by any alignments specified in the
268+
aggregations field); a good duration is long
269+
enough so that a single outlier does not
270+
generate spurious alerts, but short enough
271+
that unhealthy states are detected and
272+
alerted on quickly.`,
273+
},
274+
"query": {
275+
Type: schema.TypeString,
276+
Required: true,
277+
Description: `Monitoring Query Language query that outputs a boolean stream.`,
278+
},
279+
"trigger": {
280+
Type: schema.TypeList,
281+
Optional: true,
282+
Description: `The number/percent of time series for which
283+
the comparison must hold in order for the
284+
condition to trigger. If unspecified, then
285+
the condition will trigger if the comparison
286+
is true for any of the time series that have
287+
been identified by filter and aggregations,
288+
or by the ratio, if denominator_filter and
289+
denominator_aggregations are specified.`,
290+
MaxItems: 1,
291+
Elem: &schema.Resource{
292+
Schema: map[string]*schema.Schema{
293+
"count": {
294+
Type: schema.TypeInt,
295+
Optional: true,
296+
Description: `The absolute number of time series
297+
that must fail the predicate for the
298+
condition to be triggered.`,
299+
},
300+
"percent": {
301+
Type: schema.TypeFloat,
302+
Optional: true,
303+
Description: `The percentage of time series that
304+
must fail the predicate for the
240305
condition to be triggered.`,
241306
},
242307
},
@@ -1123,10 +1188,11 @@ func flattenMonitoringAlertPolicyConditions(v interface{}, d *schema.ResourceDat
11231188
continue
11241189
}
11251190
transformed = append(transformed, map[string]interface{}{
1126-
"condition_absent": flattenMonitoringAlertPolicyConditionsConditionAbsent(original["conditionAbsent"], d, config),
1127-
"name": flattenMonitoringAlertPolicyConditionsName(original["name"], d, config),
1128-
"condition_threshold": flattenMonitoringAlertPolicyConditionsConditionThreshold(original["conditionThreshold"], d, config),
1129-
"display_name": flattenMonitoringAlertPolicyConditionsDisplayName(original["displayName"], d, config),
1191+
"condition_absent": flattenMonitoringAlertPolicyConditionsConditionAbsent(original["conditionAbsent"], d, config),
1192+
"name": flattenMonitoringAlertPolicyConditionsName(original["name"], d, config),
1193+
"condition_monitoring_query_language": flattenMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguage(original["conditionMonitoringQueryLanguage"], d, config),
1194+
"condition_threshold": flattenMonitoringAlertPolicyConditionsConditionThreshold(original["conditionThreshold"], d, config),
1195+
"display_name": flattenMonitoringAlertPolicyConditionsDisplayName(original["displayName"], d, config),
11301196
})
11311197
}
11321198
return transformed
@@ -1235,6 +1301,67 @@ func flattenMonitoringAlertPolicyConditionsName(v interface{}, d *schema.Resourc
12351301
return v
12361302
}
12371303

1304+
func flattenMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguage(v interface{}, d *schema.ResourceData, config *Config) interface{} {
1305+
if v == nil {
1306+
return nil
1307+
}
1308+
original := v.(map[string]interface{})
1309+
if len(original) == 0 {
1310+
return nil
1311+
}
1312+
transformed := make(map[string]interface{})
1313+
transformed["query"] =
1314+
flattenMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguageQuery(original["query"], d, config)
1315+
transformed["duration"] =
1316+
flattenMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguageDuration(original["duration"], d, config)
1317+
transformed["trigger"] =
1318+
flattenMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguageTrigger(original["trigger"], d, config)
1319+
return []interface{}{transformed}
1320+
}
1321+
func flattenMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguageQuery(v interface{}, d *schema.ResourceData, config *Config) interface{} {
1322+
return v
1323+
}
1324+
1325+
func flattenMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguageDuration(v interface{}, d *schema.ResourceData, config *Config) interface{} {
1326+
return v
1327+
}
1328+
1329+
func flattenMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguageTrigger(v interface{}, d *schema.ResourceData, config *Config) interface{} {
1330+
if v == nil {
1331+
return nil
1332+
}
1333+
original := v.(map[string]interface{})
1334+
if len(original) == 0 {
1335+
return nil
1336+
}
1337+
transformed := make(map[string]interface{})
1338+
transformed["percent"] =
1339+
flattenMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguageTriggerPercent(original["percent"], d, config)
1340+
transformed["count"] =
1341+
flattenMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguageTriggerCount(original["count"], d, config)
1342+
return []interface{}{transformed}
1343+
}
1344+
func flattenMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguageTriggerPercent(v interface{}, d *schema.ResourceData, config *Config) interface{} {
1345+
return v
1346+
}
1347+
1348+
func flattenMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguageTriggerCount(v interface{}, d *schema.ResourceData, config *Config) interface{} {
1349+
// Handles the string fixed64 format
1350+
if strVal, ok := v.(string); ok {
1351+
if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil {
1352+
return intVal
1353+
}
1354+
}
1355+
1356+
// number values are represented as float64
1357+
if floatVal, ok := v.(float64); ok {
1358+
intVal := int(floatVal)
1359+
return intVal
1360+
}
1361+
1362+
return v // let terraform core handle it otherwise
1363+
}
1364+
12381365
func flattenMonitoringAlertPolicyConditionsConditionThreshold(v interface{}, d *schema.ResourceData, config *Config) interface{} {
12391366
if v == nil {
12401367
return nil
@@ -1463,6 +1590,13 @@ func expandMonitoringAlertPolicyConditions(v interface{}, d TerraformResourceDat
14631590
transformed["name"] = transformedName
14641591
}
14651592

1593+
transformedConditionMonitoringQueryLanguage, err := expandMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguage(original["condition_monitoring_query_language"], d, config)
1594+
if err != nil {
1595+
return nil, err
1596+
} else if val := reflect.ValueOf(transformedConditionMonitoringQueryLanguage); val.IsValid() && !isEmptyValue(val) {
1597+
transformed["conditionMonitoringQueryLanguage"] = transformedConditionMonitoringQueryLanguage
1598+
}
1599+
14661600
transformedConditionThreshold, err := expandMonitoringAlertPolicyConditionsConditionThreshold(original["condition_threshold"], d, config)
14671601
if err != nil {
14681602
return nil, err
@@ -1627,6 +1761,81 @@ func expandMonitoringAlertPolicyConditionsName(v interface{}, d TerraformResourc
16271761
return v, nil
16281762
}
16291763

1764+
func expandMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguage(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
1765+
l := v.([]interface{})
1766+
if len(l) == 0 || l[0] == nil {
1767+
return nil, nil
1768+
}
1769+
raw := l[0]
1770+
original := raw.(map[string]interface{})
1771+
transformed := make(map[string]interface{})
1772+
1773+
transformedQuery, err := expandMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguageQuery(original["query"], d, config)
1774+
if err != nil {
1775+
return nil, err
1776+
} else if val := reflect.ValueOf(transformedQuery); val.IsValid() && !isEmptyValue(val) {
1777+
transformed["query"] = transformedQuery
1778+
}
1779+
1780+
transformedDuration, err := expandMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguageDuration(original["duration"], d, config)
1781+
if err != nil {
1782+
return nil, err
1783+
} else if val := reflect.ValueOf(transformedDuration); val.IsValid() && !isEmptyValue(val) {
1784+
transformed["duration"] = transformedDuration
1785+
}
1786+
1787+
transformedTrigger, err := expandMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguageTrigger(original["trigger"], d, config)
1788+
if err != nil {
1789+
return nil, err
1790+
} else if val := reflect.ValueOf(transformedTrigger); val.IsValid() && !isEmptyValue(val) {
1791+
transformed["trigger"] = transformedTrigger
1792+
}
1793+
1794+
return transformed, nil
1795+
}
1796+
1797+
func expandMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguageQuery(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
1798+
return v, nil
1799+
}
1800+
1801+
func expandMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguageDuration(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
1802+
return v, nil
1803+
}
1804+
1805+
func expandMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguageTrigger(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
1806+
l := v.([]interface{})
1807+
if len(l) == 0 || l[0] == nil {
1808+
return nil, nil
1809+
}
1810+
raw := l[0]
1811+
original := raw.(map[string]interface{})
1812+
transformed := make(map[string]interface{})
1813+
1814+
transformedPercent, err := expandMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguageTriggerPercent(original["percent"], d, config)
1815+
if err != nil {
1816+
return nil, err
1817+
} else if val := reflect.ValueOf(transformedPercent); val.IsValid() && !isEmptyValue(val) {
1818+
transformed["percent"] = transformedPercent
1819+
}
1820+
1821+
transformedCount, err := expandMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguageTriggerCount(original["count"], d, config)
1822+
if err != nil {
1823+
return nil, err
1824+
} else if val := reflect.ValueOf(transformedCount); val.IsValid() && !isEmptyValue(val) {
1825+
transformed["count"] = transformedCount
1826+
}
1827+
1828+
return transformed, nil
1829+
}
1830+
1831+
func expandMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguageTriggerPercent(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
1832+
return v, nil
1833+
}
1834+
1835+
func expandMonitoringAlertPolicyConditionsConditionMonitoringQueryLanguageTriggerCount(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
1836+
return v, nil
1837+
}
1838+
16301839
func expandMonitoringAlertPolicyConditionsConditionThreshold(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
16311840
l := v.([]interface{})
16321841
if len(l) == 0 || l[0] == nil {

google-beta/resource_monitoring_alert_policy_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ func TestAccMonitoringAlertPolicy(t *testing.T) {
1616
"basic": testAccMonitoringAlertPolicy_basic,
1717
"full": testAccMonitoringAlertPolicy_full,
1818
"update": testAccMonitoringAlertPolicy_update,
19+
"mql": testAccMonitoringAlertPolicy_mql,
1920
}
2021

2122
for name, tc := range testCases {
@@ -110,6 +111,28 @@ func testAccMonitoringAlertPolicy_full(t *testing.T) {
110111
})
111112
}
112113

114+
func testAccMonitoringAlertPolicy_mql(t *testing.T) {
115+
116+
alertName := fmt.Sprintf("tf-test-%s", randString(t, 10))
117+
conditionName := fmt.Sprintf("tf-test-%s", randString(t, 10))
118+
119+
vcrTest(t, resource.TestCase{
120+
PreCheck: func() { testAccPreCheck(t) },
121+
Providers: testAccProviders,
122+
CheckDestroy: testAccCheckAlertPolicyDestroyProducer(t),
123+
Steps: []resource.TestStep{
124+
{
125+
Config: testAccMonitoringAlertPolicy_mqlCfg(alertName, conditionName),
126+
},
127+
{
128+
ResourceName: "google_monitoring_alert_policy.mql",
129+
ImportState: true,
130+
ImportStateVerify: true,
131+
},
132+
},
133+
})
134+
}
135+
113136
func testAccCheckAlertPolicyDestroyProducer(t *testing.T) func(s *terraform.State) error {
114137
return func(s *terraform.State) error {
115138
config := googleProviderConfig(t)
@@ -226,3 +249,31 @@ resource "google_monitoring_alert_policy" "full" {
226249
}
227250
`, alertName, conditionName1, conditionName2)
228251
}
252+
253+
func testAccMonitoringAlertPolicy_mqlCfg(alertName, conditionName string) string {
254+
return fmt.Sprintf(`
255+
resource "google_monitoring_alert_policy" "mql" {
256+
display_name = "%s"
257+
combiner = "OR"
258+
enabled = true
259+
260+
conditions {
261+
display_name = "%s"
262+
263+
condition_monitoring_query_language {
264+
query = "fetch gce_instance::compute.googleapis.com/instance/cpu/utilization | align mean_aligner() | window 5m | condition value.utilization > .15 '10^2.%%'"
265+
duration = "60s"
266+
267+
trigger {
268+
count = 2
269+
}
270+
}
271+
}
272+
273+
documentation {
274+
content = "test content"
275+
mime_type = "text/markdown"
276+
}
277+
}
278+
`, alertName, conditionName)
279+
}

website/docs/r/monitoring_alert_policy.html.markdown

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ The `conditions` block supports:
104104
the condition is created as part of a new or updated alerting
105105
policy.
106106

107+
* `condition_monitoring_query_language` -
108+
(Optional)
109+
A Monitoring Query Language query that outputs a boolean stream
110+
Structure is documented below.
111+
107112
* `condition_threshold` -
108113
(Optional)
109114
A condition that compares a time series against a
@@ -259,6 +264,57 @@ The `aggregations` block supports:
259264

260265
The `trigger` block supports:
261266

267+
* `percent` -
268+
(Optional)
269+
The percentage of time series that
270+
must fail the predicate for the
271+
condition to be triggered.
272+
273+
* `count` -
274+
(Optional)
275+
The absolute number of time series
276+
that must fail the predicate for the
277+
condition to be triggered.
278+
279+
The `condition_monitoring_query_language` block supports:
280+
281+
* `query` -
282+
(Required)
283+
Monitoring Query Language query that outputs a boolean stream.
284+
285+
* `duration` -
286+
(Required)
287+
The amount of time that a time series must
288+
violate the threshold to be considered
289+
failing. Currently, only values that are a
290+
multiple of a minute--e.g., 0, 60, 120, or
291+
300 seconds--are supported. If an invalid
292+
value is given, an error will be returned.
293+
When choosing a duration, it is useful to
294+
keep in mind the frequency of the underlying
295+
time series data (which may also be affected
296+
by any alignments specified in the
297+
aggregations field); a good duration is long
298+
enough so that a single outlier does not
299+
generate spurious alerts, but short enough
300+
that unhealthy states are detected and
301+
alerted on quickly.
302+
303+
* `trigger` -
304+
(Optional)
305+
The number/percent of time series for which
306+
the comparison must hold in order for the
307+
condition to trigger. If unspecified, then
308+
the condition will trigger if the comparison
309+
is true for any of the time series that have
310+
been identified by filter and aggregations,
311+
or by the ratio, if denominator_filter and
312+
denominator_aggregations are specified.
313+
Structure is documented below.
314+
315+
316+
The `trigger` block supports:
317+
262318
* `percent` -
263319
(Optional)
264320
The percentage of time series that

0 commit comments

Comments
 (0)