diff --git a/azurerm/internal/services/applicationinsights/parse/application_insights.go b/azurerm/internal/services/applicationinsights/parse/application_insights.go new file mode 100644 index 000000000000..0953801e91f2 --- /dev/null +++ b/azurerm/internal/services/applicationinsights/parse/application_insights.go @@ -0,0 +1,59 @@ +package parse + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type ApplicationInsightsId struct { + ResourceGroup string + Name string +} + +func ApplicationInsightsID(input string) (*ApplicationInsightsId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, fmt.Errorf("parsing Application Insights ID %q: %+v", input, err) + } + + appId := ApplicationInsightsId{ + ResourceGroup: id.ResourceGroup, + } + + if appId.Name, err = id.PopSegment("components"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &appId, nil +} + +type ApplicationInsightsWebTestId struct { + ResourceGroup string + Name string +} + +func ApplicationInsightsWebTestID(input string) (*ApplicationInsightsWebTestId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, fmt.Errorf("parsing Application Insights Web Test ID %q: %+v", input, err) + } + + testid := ApplicationInsightsWebTestId{ + ResourceGroup: id.ResourceGroup, + } + + if testid.Name, err = id.PopSegment("webtests"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &testid, nil +} diff --git a/azurerm/internal/services/applicationinsights/parse/application_insights_test.go b/azurerm/internal/services/applicationinsights/parse/application_insights_test.go new file mode 100644 index 000000000000..f34c57d9fbb1 --- /dev/null +++ b/azurerm/internal/services/applicationinsights/parse/application_insights_test.go @@ -0,0 +1,139 @@ +package parse + +import ( + "testing" +) + +func TestApplicationInsightsID(t *testing.T) { + testData := []struct { + Name string + Input string + Error bool + Expect *ApplicationInsightsId + }{ + { + Name: "Empty", + Input: "", + Error: true, + }, + { + Name: "No Resource Groups Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000", + Error: true, + }, + { + Name: "No Resource Groups Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Error: true, + }, + { + Name: "No Provider", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers", + Error: true, + }, + { + Name: "No component", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/microsoft.insights/components", + Error: true, + }, + { + Name: "Correct", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/microsoft.insights/components/appinsights1", + Expect: &ApplicationInsightsId{ + ResourceGroup: "group1", + Name: "appinsights1", + }, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Name) + + actual, err := ApplicationInsightsID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get") + } + + if actual.ResourceGroup != v.Expect.ResourceGroup { + t.Fatalf("Expected %q but got %q for Resource Group", v.Expect.ResourceGroup, actual.ResourceGroup) + } + + if actual.Name != v.Expect.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expect.Name, actual.Name) + } + } +} + +func TestApplicationInsightsWebTestID(t *testing.T) { + testData := []struct { + Name string + Input string + Error bool + Expect *ApplicationInsightsWebTestId + }{ + { + Name: "Empty", + Input: "", + Error: true, + }, + { + Name: "No Resource Groups Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000", + Error: true, + }, + { + Name: "No Resource Groups Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Error: true, + }, + { + Name: "No Provider", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers", + Error: true, + }, + { + Name: "No webtest", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/microsoft.insights/webtests", + Error: true, + }, + { + Name: "Correct", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/microsoft.insights/webtests/test1", + Expect: &ApplicationInsightsWebTestId{ + ResourceGroup: "group1", + Name: "test1", + }, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Name) + + actual, err := ApplicationInsightsWebTestID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get") + } + + if actual.ResourceGroup != v.Expect.ResourceGroup { + t.Fatalf("Expected %q but got %q for Resource Group", v.Expect.ResourceGroup, actual.ResourceGroup) + } + + if actual.Name != v.Expect.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expect.Name, actual.Name) + } + } +} diff --git a/azurerm/internal/services/applicationinsights/validate/application_insights.go b/azurerm/internal/services/applicationinsights/validate/application_insights.go new file mode 100644 index 000000000000..2722004664db --- /dev/null +++ b/azurerm/internal/services/applicationinsights/validate/application_insights.go @@ -0,0 +1,37 @@ +package validate + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/applicationinsights/parse" +) + +func ApplicationInsightsID(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) + return + } + + if _, err := parse.ApplicationInsightsID(v); err != nil { + errors = append(errors, fmt.Errorf("parsing %q as a resource id: %v", k, err)) + return + } + + return warnings, errors +} + +func ApplicationInsightsWebTestID(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) + return + } + + if _, err := parse.ApplicationInsightsWebTestID(v); err != nil { + errors = append(errors, fmt.Errorf("parsing %q as a resource id: %v", k, err)) + return + } + + return warnings, errors +} diff --git a/azurerm/internal/services/monitor/monitor_metric_alert_resource.go b/azurerm/internal/services/monitor/monitor_metric_alert_resource.go index bf1431e60fc2..3d97784d7f50 100644 --- a/azurerm/internal/services/monitor/monitor_metric_alert_resource.go +++ b/azurerm/internal/services/monitor/monitor_metric_alert_resource.go @@ -7,6 +7,7 @@ import ( "time" "github.com/Azure/azure-sdk-for-go/services/preview/monitor/mgmt/2019-06-01/insights" + "github.com/Azure/go-autorest/autorest/date" "github.com/hashicorp/go-azure-helpers/response" "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" @@ -15,6 +16,8 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/location" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/applicationinsights/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" @@ -48,16 +51,10 @@ func resourceArmMonitorMetricAlert() *schema.Resource { "resource_group_name": azure.SchemaResourceGroupName(), - // TODO: Multiple resource IDs (Remove MaxItems) support is missing in SDK - // Issue to track: https://github.com/Azure/azure-sdk-for-go/issues/2920 - // But to prevent potential state migration in the future, let's stick to use Set now - - // lintignore:S018 "scopes": { Type: schema.TypeSet, Required: true, MinItems: 1, - MaxItems: 1, Elem: &schema.Schema{ Type: schema.TypeString, ValidateFunc: azure.ValidateResourceID, @@ -65,10 +62,28 @@ func resourceArmMonitorMetricAlert() *schema.Resource { Set: schema.HashString, }, + "target_resource_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: `The resource type (e.g. Microsoft.Compute/virtualMachines) of the target resource. Required when using subscription, resource group scope or multiple scopes.`, + }, + + "target_resource_location": { + Type: schema.TypeString, + Optional: true, + Computed: true, + StateFunc: location.StateFunc, + DiffSuppressFunc: location.DiffSuppressFunc, + Description: `The location of the target resource. Required when using subscription, resource group scope or multiple scopes.`, + }, + + // static criteria "criteria": { - Type: schema.TypeList, - Required: true, - MinItems: 1, + Type: schema.TypeSet, + Optional: true, + MinItems: 1, + ExactlyOneOf: []string{"criteria", "dynamic_criteria", "application_insights_web_test_location_availability_criteria"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "metric_namespace": { @@ -92,6 +107,35 @@ func resourceArmMonitorMetricAlert() *schema.Resource { "Total", }, false), }, + "dimension": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "operator": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "Include", + "Exclude", + }, false), + }, + "values": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, "operator": { Type: schema.TypeString, Required: true, @@ -108,6 +152,40 @@ func resourceArmMonitorMetricAlert() *schema.Resource { Type: schema.TypeFloat, Required: true, }, + }, + }, + }, + + "dynamic_criteria": { + Type: schema.TypeSet, + Optional: true, + MinItems: 1, + // Curently, it allows to define only one dynamic criteria in one metric alert. + MaxItems: 1, + ExactlyOneOf: []string{"criteria", "dynamic_criteria", "application_insights_web_test_location_availability_criteria"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "metric_namespace": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "metric_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "aggregation": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "Average", + "Count", + "Minimum", + "Maximum", + "Total", + }, false), + }, "dimension": { Type: schema.TypeList, Optional: true, @@ -137,6 +215,71 @@ func resourceArmMonitorMetricAlert() *schema.Resource { }, }, }, + "operator": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(insights.DynamicThresholdOperatorLessThan), + string(insights.DynamicThresholdOperatorGreaterThan), + string(insights.DynamicThresholdOperatorGreaterOrLessThan), + }, false), + }, + "alert_sensitivity": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(insights.Low), + string(insights.Medium), + string(insights.High), + }, false), + }, + + "evaluation_total_count": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(1), + Default: 4, + }, + + "evaluation_failure_count": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(1), + Default: 4, + }, + + "ignore_data_before": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.IsRFC3339Time, + }, + }, + }, + }, + + "application_insights_web_test_location_availability_criteria": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + MaxItems: 1, + ExactlyOneOf: []string{"criteria", "dynamic_criteria", "application_insights_web_test_location_availability_criteria"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "web_test_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.ApplicationInsightsWebTestID, + }, + "component_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.ApplicationInsightsID, + }, + "failed_location_count": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, }, }, }, @@ -249,24 +392,31 @@ func resourceArmMonitorMetricAlertCreateUpdate(d *schema.ResourceData, meta inte severity := d.Get("severity").(int) frequency := d.Get("frequency").(string) windowSize := d.Get("window_size").(string) - criteriaRaw := d.Get("criteria").([]interface{}) actionRaw := d.Get("action").(*schema.Set).List() + targetResourceType := d.Get("target_resource_type").(string) + targetResourceLocation := d.Get("target_resource_location").(string) t := d.Get("tags").(map[string]interface{}) expandedTags := tags.Expand(t) + criteria, err := expandMonitorMetricAlertCriteria(d) + if err != nil { + return fmt.Errorf(`Expanding criteria: %+v`, err) + } parameters := insights.MetricAlertResource{ Location: utils.String(azure.NormalizeLocation("Global")), MetricAlertProperties: &insights.MetricAlertProperties{ - Enabled: utils.Bool(enabled), - AutoMitigate: utils.Bool(autoMitigate), - Description: utils.String(description), - Severity: utils.Int32(int32(severity)), - EvaluationFrequency: utils.String(frequency), - WindowSize: utils.String(windowSize), - Scopes: utils.ExpandStringSlice(scopesRaw), - Criteria: expandMonitorMetricAlertCriteria(criteriaRaw), - Actions: expandMonitorMetricAlertAction(actionRaw), + Enabled: utils.Bool(enabled), + AutoMitigate: utils.Bool(autoMitigate), + Description: utils.String(description), + Severity: utils.Int32(int32(severity)), + EvaluationFrequency: utils.String(frequency), + WindowSize: utils.String(windowSize), + Scopes: utils.ExpandStringSlice(scopesRaw), + Criteria: criteria, + Actions: expandMonitorMetricAlertAction(actionRaw), + TargetResourceType: utils.String(targetResourceType), + TargetResourceRegion: utils.String(targetResourceLocation), }, Tags: expandedTags, } @@ -321,12 +471,40 @@ func resourceArmMonitorMetricAlertRead(d *schema.ResourceData, meta interface{}) if err := d.Set("scopes", utils.FlattenStringSlice(alert.Scopes)); err != nil { return fmt.Errorf("Error setting `scopes`: %+v", err) } - if err := d.Set("criteria", flattenMonitorMetricAlertCriteria(alert.Criteria)); err != nil { - return fmt.Errorf("Error setting `criteria`: %+v", err) + + // Determine the correct criteria schema to set + var criteriaSchema string + switch c := alert.Criteria.(type) { + case insights.MetricAlertSingleResourceMultipleMetricCriteria: + criteriaSchema = "criteria" + case insights.MetricAlertMultipleResourceMultipleMetricCriteria: + if c.AllOf == nil || len(*c.AllOf) == 0 { + return fmt.Errorf("nil or empty contained criteria of MultipleResourceMultipleMetricCriteria") + } + // `MinItems` defined in schema guaranteed there is at least one element. + switch (*c.AllOf)[0].(type) { + case insights.DynamicMetricCriteria: + criteriaSchema = "dynamic_criteria" + case insights.MetricCriteria: + criteriaSchema = "criteria" + } + case insights.WebtestLocationAvailabilityCriteria: + criteriaSchema = "application_insights_web_test_location_availability_criteria" + default: + return fmt.Errorf("Unknown criteria type") } + + monitorMetricAlertCriteria := flattenMonitorMetricAlertCriteria(alert.Criteria) + + if err := d.Set(criteriaSchema, monitorMetricAlertCriteria); err != nil { + return fmt.Errorf("failed setting `%s`: %+v", criteriaSchema, err) + } + if err := d.Set("action", flattenMonitorMetricAlertAction(alert.Actions)); err != nil { return fmt.Errorf("Error setting `action`: %+v", err) } + d.Set("target_resource_type", alert.TargetResourceType) + d.Set("target_resource_location", alert.TargetResourceRegion) } return tags.FlattenAndSet(d, resp.Tags) } @@ -352,37 +530,98 @@ func resourceArmMonitorMetricAlertDelete(d *schema.ResourceData, meta interface{ return nil } -func expandMonitorMetricAlertCriteria(input []interface{}) *insights.MetricAlertSingleResourceMultipleMetricCriteria { - criteria := make([]insights.MetricCriteria, 0) +func expandMonitorMetricAlertCriteria(d *schema.ResourceData) (insights.BasicMetricAlertCriteria, error) { + switch { + case d.Get("criteria").(*schema.Set).Len() != 0: + return expandMonitorMetricAlertMetricCriteria(d.Get("criteria").(*schema.Set).List()), nil + case d.Get("dynamic_criteria").(*schema.Set).Len() != 0: + return expandMonitorMetricAlertDynamicMetricCriteria(d.Get("dynamic_criteria").(*schema.Set).List()), nil + case len(d.Get("application_insights_web_test_location_availability_criteria").([]interface{})) != 0: + return expandMonitorMetricAlertWebtestLocAvailCriteria(d.Get("application_insights_web_test_location_availability_criteria").([]interface{})), nil + default: + // Guaranteed by schema `AtLeastOne` constraint + return nil, fmt.Errorf("unknown criteria type") + } +} + +func expandMonitorMetricAlertMetricCriteria(input []interface{}) insights.BasicMetricAlertCriteria { + criteria := make([]insights.BasicMultiMetricCriteria, 0) for i, item := range input { v := item.(map[string]interface{}) - - dimensions := make([]insights.MetricDimension, 0) - for _, dimension := range v["dimension"].([]interface{}) { - dVal := dimension.(map[string]interface{}) - dimensions = append(dimensions, insights.MetricDimension{ - Name: utils.String(dVal["name"].(string)), - Operator: utils.String(dVal["operator"].(string)), - Values: utils.ExpandStringSlice(dVal["values"].([]interface{})), - }) - } - + dimensions := expandMonitorMetricAlertMultiMetricCriteriaDimension(v["dimension"].([]interface{})) criteria = append(criteria, insights.MetricCriteria{ Name: utils.String(fmt.Sprintf("Metric%d", i+1)), MetricNamespace: utils.String(v["metric_namespace"].(string)), MetricName: utils.String(v["metric_name"].(string)), TimeAggregation: v["aggregation"].(string), + Dimensions: &dimensions, Operator: insights.Operator(v["operator"].(string)), Threshold: utils.Float(v["threshold"].(float64)), - Dimensions: &dimensions, }) } - return &insights.MetricAlertSingleResourceMultipleMetricCriteria{ + return &insights.MetricAlertMultipleResourceMultipleMetricCriteria{ + AllOf: &criteria, + OdataType: insights.OdataTypeMicrosoftAzureMonitorMultipleResourceMultipleMetricCriteria, + } +} +func expandMonitorMetricAlertDynamicMetricCriteria(input []interface{}) insights.BasicMetricAlertCriteria { + criteria := make([]insights.BasicMultiMetricCriteria, 0) + for i, item := range input { + v := item.(map[string]interface{}) + dimensions := expandMonitorMetricAlertMultiMetricCriteriaDimension(v["dimension"].([]interface{})) + var ignoreDataBefore *date.Time + if v := v["ignore_data_before"].(string); v != "" { + // Guaranteed in schema validation func. + t, _ := time.Parse(time.RFC3339, v) + ignoreDataBefore = &date.Time{Time: t} + } + criteria = append(criteria, insights.DynamicMetricCriteria{ + Name: utils.String(fmt.Sprintf("Metric%d", i+1)), + MetricNamespace: utils.String(v["metric_namespace"].(string)), + MetricName: utils.String(v["metric_name"].(string)), + TimeAggregation: v["aggregation"].(string), + Dimensions: &dimensions, + Operator: insights.DynamicThresholdOperator(v["operator"].(string)), + AlertSensitivity: insights.DynamicThresholdSensitivity(v["alert_sensitivity"].(string)), + FailingPeriods: &insights.DynamicThresholdFailingPeriods{ + NumberOfEvaluationPeriods: utils.Float(float64(v["evaluation_total_count"].(int))), + MinFailingPeriodsToAlert: utils.Float(float64(v["evaluation_failure_count"].(int))), + }, + IgnoreDataBefore: ignoreDataBefore, + }) + } + return &insights.MetricAlertMultipleResourceMultipleMetricCriteria{ AllOf: &criteria, - OdataType: insights.OdataTypeMicrosoftAzureMonitorSingleResourceMultipleMetricCriteria, + OdataType: insights.OdataTypeMicrosoftAzureMonitorMultipleResourceMultipleMetricCriteria, } } +func expandMonitorMetricAlertWebtestLocAvailCriteria(input []interface{}) insights.BasicMetricAlertCriteria { + if len(input) == 0 { + return nil + } + v := input[0].(map[string]interface{}) + return &insights.WebtestLocationAvailabilityCriteria{ + WebTestID: utils.String(v["web_test_id"].(string)), + ComponentID: utils.String(v["component_id"].(string)), + FailedLocationCount: utils.Float(float64(v["failed_location_count"].(int))), + OdataType: insights.OdataTypeMicrosoftAzureMonitorWebtestLocationAvailabilityCriteria, + } +} + +func expandMonitorMetricAlertMultiMetricCriteriaDimension(input []interface{}) []insights.MetricDimension { + result := make([]insights.MetricDimension, 0) + for _, dimension := range input { + dVal := dimension.(map[string]interface{}) + result = append(result, insights.MetricDimension{ + Name: utils.String(dVal["name"].(string)), + Operator: utils.String(dVal["operator"].(string)), + Values: utils.ExpandStringSlice(dVal["values"].([]interface{})), + }) + } + return result +} + func expandMonitorMetricAlertAction(input []interface{}) *[]insights.MetricAlertAction { actions := make([]insights.MetricAlertAction, 0) for _, item := range input { @@ -404,36 +643,108 @@ func expandMonitorMetricAlertAction(input []interface{}) *[]insights.MetricAlert return &actions } -func flattenMonitorMetricAlertCriteria(input insights.BasicMetricAlertCriteria) (result []interface{}) { - result = make([]interface{}, 0) - if input == nil { - return +func flattenMonitorMetricAlertCriteria(input insights.BasicMetricAlertCriteria) []interface{} { + switch criteria := input.(type) { + case insights.MetricAlertSingleResourceMultipleMetricCriteria: + // As service is gonna deprecate data type of `MetricAlertSingleResourceMultipleMetricCriteria`, + // we use the same function to handle both single/multiple ResourceMultipleMetricCriteria cases. + // This is possible because their `AllOf` member implement the same interface: `MultiMetricCriteria`. + if criteria.AllOf == nil { + return nil + } + l := make([]insights.BasicMultiMetricCriteria, len(*criteria.AllOf)) + for i, c := range *criteria.AllOf { + l[i] = c + } + return flattenMonitorMetricAlertMultiMetricCriteria(&l) + case insights.MetricAlertMultipleResourceMultipleMetricCriteria: + return flattenMonitorMetricAlertMultiMetricCriteria(criteria.AllOf) + case insights.WebtestLocationAvailabilityCriteria: + return flattenMonitorMetricAlertWebtestLocAvailCriteria(&criteria) + default: + return nil } - criteria, ok := input.AsMetricAlertSingleResourceMultipleMetricCriteria() - if !ok || criteria == nil || criteria.AllOf == nil { - return +} + +func flattenMonitorMetricAlertMultiMetricCriteria(input *[]insights.BasicMultiMetricCriteria) []interface{} { + if input == nil { + return nil } - for _, metric := range *criteria.AllOf { + result := make([]interface{}, 0) + + for _, criteria := range *input { v := make(map[string]interface{}) + var ( + metricName string + metricNamespace string + timeAggregation interface{} + dimensions []insights.MetricDimension + ) + + switch criteria := criteria.(type) { + case insights.MetricCriteria: + if criteria.MetricName != nil { + metricName = *criteria.MetricName + } + if criteria.MetricNamespace != nil { + metricNamespace = *criteria.MetricNamespace + } + timeAggregation = criteria.TimeAggregation + if criteria.Dimensions != nil { + dimensions = *criteria.Dimensions + } - if metric.MetricNamespace != nil { - v["metric_namespace"] = *metric.MetricNamespace - } - if metric.MetricName != nil { - v["metric_name"] = *metric.MetricName - } - if aggr, ok := metric.TimeAggregation.(string); ok { - v["aggregation"] = aggr - } + // MetricCriteria specific properties + v["operator"] = string(criteria.Operator) - v["operator"] = string(metric.Operator) + threshold := 0.0 + if criteria.Threshold != nil { + threshold = *criteria.Threshold + } + v["threshold"] = threshold + case insights.DynamicMetricCriteria: + if criteria.MetricName != nil { + metricName = *criteria.MetricName + } + if criteria.MetricNamespace != nil { + metricNamespace = *criteria.MetricNamespace + } + timeAggregation = criteria.TimeAggregation + if criteria.Dimensions != nil { + dimensions = *criteria.Dimensions + } + // DynamicMetricCriteria specific properties + v["operator"] = string(criteria.Operator) + v["alert_sensitivity"] = string(criteria.AlertSensitivity) + var ( + nEvl = 1 + nFailEvl = 1 + ) + if period := criteria.FailingPeriods; period != nil { + if period.NumberOfEvaluationPeriods != nil { + nEvl = int(*period.NumberOfEvaluationPeriods) + } + if period.MinFailingPeriodsToAlert != nil { + nFailEvl = int(*period.MinFailingPeriodsToAlert) + } + } + v["evaluation_total_count"] = nEvl + v["evaluation_failure_count"] = nFailEvl - if metric.Threshold != nil { - v["threshold"] = *metric.Threshold + ignoreDataBefore := "" + if criteria.IgnoreDataBefore != nil { + ignoreDataBefore = criteria.IgnoreDataBefore.Format(time.RFC3339) + } + v["ignore_data_before"] = ignoreDataBefore } - if metric.Dimensions != nil { + + // Common properties + v["metric_name"] = metricName + v["metric_namespace"] = metricNamespace + v["aggregation"] = timeAggregation + if dimensions != nil { dimResult := make([]map[string]interface{}, 0) - for _, dimension := range *metric.Dimensions { + for _, dimension := range dimensions { dVal := make(map[string]interface{}) if dimension.Name != nil { dVal["name"] = *dimension.Name @@ -449,10 +760,37 @@ func flattenMonitorMetricAlertCriteria(input insights.BasicMetricAlertCriteria) result = append(result, v) } - return result } +func flattenMonitorMetricAlertWebtestLocAvailCriteria(input *insights.WebtestLocationAvailabilityCriteria) []interface{} { + if input == nil { + return nil + } + webtestID := "" + if input.WebTestID != nil { + webtestID = *input.WebTestID + } + + componentID := "" + if input.ComponentID != nil { + componentID = *input.ComponentID + } + + failedLocationCount := 0 + if input.FailedLocationCount != nil { + failedLocationCount = int(*input.FailedLocationCount) + } + + return []interface{}{ + map[string]interface{}{ + "web_test_id": webtestID, + "component_id": componentID, + "failed_location_count": failedLocationCount, + }, + } +} + func flattenMonitorMetricAlertAction(input *[]insights.MetricAlertAction) (result []interface{}) { result = make([]interface{}, 0) if input == nil { diff --git a/azurerm/internal/services/monitor/tests/monitor_metric_alert_resource_test.go b/azurerm/internal/services/monitor/tests/monitor_metric_alert_resource_test.go index 8200d8e4198c..a25129464889 100644 --- a/azurerm/internal/services/monitor/tests/monitor_metric_alert_resource_test.go +++ b/azurerm/internal/services/monitor/tests/monitor_metric_alert_resource_test.go @@ -23,15 +23,6 @@ func TestAccAzureRMMonitorMetricAlert_basic(t *testing.T) { Config: testAccAzureRMMonitorMetricAlert_basic(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMMonitorMetricAlertExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "enabled", "true"), - resource.TestCheckResourceAttr(data.ResourceName, "scopes.#", "1"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.#", "1"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.metric_namespace", "Microsoft.Storage/storageAccounts"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.metric_name", "UsedCapacity"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.aggregation", "Average"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.operator", "GreaterThan"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.threshold", "55.5"), - resource.TestCheckResourceAttr(data.ResourceName, "action.#", "0"), ), }, data.ImportStep(), @@ -73,35 +64,6 @@ func TestAccAzureRMMonitorMetricAlert_complete(t *testing.T) { Config: testAccAzureRMMonitorMetricAlert_complete(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMMonitorMetricAlertExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "enabled", "true"), - resource.TestCheckResourceAttr(data.ResourceName, "auto_mitigate", "false"), - resource.TestCheckResourceAttr(data.ResourceName, "severity", "4"), - resource.TestCheckResourceAttr(data.ResourceName, "description", "This is a complete metric alert resource."), - resource.TestCheckResourceAttr(data.ResourceName, "frequency", "PT30M"), - resource.TestCheckResourceAttr(data.ResourceName, "window_size", "PT12H"), - resource.TestCheckResourceAttr(data.ResourceName, "scopes.#", "1"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.#", "2"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.metric_namespace", "Microsoft.Storage/storageAccounts"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.metric_name", "Transactions"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.aggregation", "Maximum"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.operator", "Equals"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.threshold", "99"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.dimension.#", "2"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.dimension.0.name", "GeoType"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.dimension.0.operator", "Include"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.dimension.0.values.#", "1"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.dimension.0.values.0", "*"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.dimension.1.name", "ApiName"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.dimension.1.operator", "Include"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.dimension.1.values.#", "1"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.dimension.1.values.0", "*"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.1.metric_namespace", "Microsoft.Storage/storageAccounts"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.1.metric_name", "UsedCapacity"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.1.aggregation", "Total"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.1.operator", "GreaterThanOrEqual"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.1.threshold", "66.6"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.1.dimension.#", "0"), - resource.TestCheckResourceAttr(data.ResourceName, "action.#", "2"), ), }, data.ImportStep(), @@ -121,83 +83,127 @@ func TestAccAzureRMMonitorMetricAlert_basicAndCompleteUpdate(t *testing.T) { Config: testAccAzureRMMonitorMetricAlert_basic(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMMonitorMetricAlertExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "enabled", "true"), - resource.TestCheckResourceAttr(data.ResourceName, "auto_mitigate", "true"), - resource.TestCheckResourceAttr(data.ResourceName, "severity", "3"), - resource.TestCheckResourceAttr(data.ResourceName, "description", ""), - resource.TestCheckResourceAttr(data.ResourceName, "frequency", "PT1M"), - resource.TestCheckResourceAttr(data.ResourceName, "window_size", "PT5M"), - resource.TestCheckResourceAttr(data.ResourceName, "scopes.#", "1"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.#", "1"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.metric_namespace", "Microsoft.Storage/storageAccounts"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.metric_name", "UsedCapacity"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.aggregation", "Average"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.operator", "GreaterThan"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.threshold", "55.5"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.dimension.#", "0"), - resource.TestCheckResourceAttr(data.ResourceName, "action.#", "0"), ), }, { Config: testAccAzureRMMonitorMetricAlert_complete(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMMonitorMetricAlertExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "enabled", "true"), - resource.TestCheckResourceAttr(data.ResourceName, "auto_mitigate", "false"), - resource.TestCheckResourceAttr(data.ResourceName, "severity", "4"), - resource.TestCheckResourceAttr(data.ResourceName, "description", "This is a complete metric alert resource."), - resource.TestCheckResourceAttr(data.ResourceName, "frequency", "PT30M"), - resource.TestCheckResourceAttr(data.ResourceName, "window_size", "PT12H"), - resource.TestCheckResourceAttr(data.ResourceName, "scopes.#", "1"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.#", "2"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.metric_namespace", "Microsoft.Storage/storageAccounts"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.metric_name", "Transactions"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.aggregation", "Maximum"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.operator", "Equals"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.threshold", "99"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.dimension.#", "2"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.dimension.0.name", "GeoType"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.dimension.0.operator", "Include"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.dimension.0.values.#", "1"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.dimension.0.values.0", "*"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.dimension.1.name", "ApiName"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.dimension.1.operator", "Include"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.dimension.1.values.#", "1"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.dimension.1.values.0", "*"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.1.metric_namespace", "Microsoft.Storage/storageAccounts"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.1.metric_name", "UsedCapacity"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.1.aggregation", "Total"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.1.operator", "GreaterThanOrEqual"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.1.threshold", "66.6"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.1.dimension.#", "0"), - resource.TestCheckResourceAttr(data.ResourceName, "action.#", "2"), ), }, { Config: testAccAzureRMMonitorMetricAlert_basic(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMMonitorMetricAlertExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "enabled", "true"), - resource.TestCheckResourceAttr(data.ResourceName, "auto_mitigate", "true"), - resource.TestCheckResourceAttr(data.ResourceName, "severity", "3"), - resource.TestCheckResourceAttr(data.ResourceName, "description", ""), - resource.TestCheckResourceAttr(data.ResourceName, "frequency", "PT1M"), - resource.TestCheckResourceAttr(data.ResourceName, "window_size", "PT5M"), - resource.TestCheckResourceAttr(data.ResourceName, "scopes.#", "1"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.#", "1"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.metric_namespace", "Microsoft.Storage/storageAccounts"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.metric_name", "UsedCapacity"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.aggregation", "Average"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.operator", "GreaterThan"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.threshold", "55.5"), - resource.TestCheckResourceAttr(data.ResourceName, "criteria.0.dimension.#", "0"), - resource.TestCheckResourceAttr(data.ResourceName, "action.#", "0"), ), }, }, }) } +func TestAccAzureRMMonitorMetricAlert_multiScope(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_monitor_metric_alert", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMMonitorMetricAlertDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMMonitorMetricAlert_multiScope(data, 2), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMonitorMetricAlertExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMMonitorMetricAlert_multiScope(data, 1), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMonitorMetricAlertExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMMonitorMetricAlert_multiScope(data, 2), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMonitorMetricAlertExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMMonitorMetricAlert_applicationInsightsWebTest(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_monitor_metric_alert", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMMonitorMetricAlertDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMMonitorMetricAlert_applicationInsightsWebTest(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMonitorMetricAlertExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func testCheckAzureRMMonitorMetricAlertDestroy(s *terraform.State) error { + conn := acceptance.AzureProvider.Meta().(*clients.Client).Monitor.MetricAlertsClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_monitor_metric_alert" { + continue + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := conn.Get(ctx, resourceGroup, name) + if err != nil { + return nil + } + if resp.StatusCode != http.StatusNotFound { + return fmt.Errorf("Metric alert still exists:\n%#v", resp) + } + } + return nil +} + +func testCheckAzureRMMonitorMetricAlertExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acceptance.AzureProvider.Meta().(*clients.Client).Monitor.MetricAlertsClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + name := rs.Primary.Attributes["name"] + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for Metric Alert Instance: %s", name) + } + + resp, err := conn.Get(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("Bad: Get on monitorMetricAlertsClient: %+v", err) + } + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: Metric Alert Instance %q (resource group: %q) does not exist", name, resourceGroup) + } + + return nil + } +} + func testAccAzureRMMonitorMetricAlert_basic(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { @@ -229,6 +235,8 @@ resource "azurerm_monitor_metric_alert" "test" { operator = "GreaterThan" threshold = 55.5 } + + window_size = "PT1H" } `, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger) } @@ -250,6 +258,7 @@ resource "azurerm_monitor_metric_alert" "import" { operator = "GreaterThan" threshold = 55.5 } + window_size = "PT1H" } `, template) } @@ -299,8 +308,8 @@ resource "azurerm_monitor_metric_alert" "test" { criteria { metric_namespace = "Microsoft.Storage/storageAccounts" metric_name = "Transactions" - aggregation = "Maximum" - operator = "Equals" + aggregation = "Total" + operator = "GreaterThan" threshold = 99 dimension { @@ -308,20 +317,6 @@ resource "azurerm_monitor_metric_alert" "test" { operator = "Include" values = ["*"] } - - dimension { - name = "ApiName" - operator = "Include" - values = ["*"] - } - } - - criteria { - metric_namespace = "Microsoft.Storage/storageAccounts" - metric_name = "UsedCapacity" - aggregation = "Total" - operator = "GreaterThanOrEqual" - threshold = 66.6 } action { @@ -335,53 +330,158 @@ resource "azurerm_monitor_metric_alert" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomString) } -func testCheckAzureRMMonitorMetricAlertDestroy(s *terraform.State) error { - conn := acceptance.AzureProvider.Meta().(*clients.Client).Monitor.MetricAlertsClient - ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext - for _, rs := range s.RootModule().Resources { - if rs.Type != "azurerm_monitor_metric_alert" { - continue - } +func testAccAzureRMMonitorMetricAlert_multiVMTemplate(data acceptance.TestData, count int) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} - name := rs.Primary.Attributes["name"] - resourceGroup := rs.Primary.Attributes["resource_group_name"] +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} - resp, err := conn.Get(ctx, resourceGroup, name) - if err != nil { - return nil - } - if resp.StatusCode != http.StatusNotFound { - return fmt.Errorf("Metric alert still exists:\n%#v", resp) - } - } - return nil +resource "azurerm_virtual_network" "test" { + name = "acctestnw-%[1]d" + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name } -func testCheckAzureRMMonitorMetricAlertExists(resourceName string) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := acceptance.AzureProvider.Meta().(*clients.Client).Monitor.MetricAlertsClient - ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext +resource "azurerm_subnet" "test" { + count = %[3]d + name = "internal-${count.index}" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefix = "10.0.${count.index}.0/24" +} - // Ensure we have enough information in state to look up in API - rs, ok := s.RootModule().Resources[resourceName] - if !ok { - return fmt.Errorf("Not found: %s", resourceName) - } +resource "azurerm_network_interface" "test" { + count = %[3]d + name = "acctestnic-${count.index}" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + ip_configuration { + name = "internal" + subnet_id = azurerm_subnet.test[count.index].id + private_ip_address_allocation = "Dynamic" + } +} - name := rs.Primary.Attributes["name"] - resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] - if !hasResourceGroup { - return fmt.Errorf("Bad: no resource group found in state for Metric Alert Instance: %s", name) - } +resource "azurerm_linux_virtual_machine" "test" { + count = %[3]d + name = "acctestVM-${count.index}" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + size = "Standard_F2" + admin_username = "adminuser" + admin_password = "P@$$w0rd1234!" + disable_password_authentication = false + network_interface_ids = [ + azurerm_network_interface.test[count.index].id, + ] + + os_disk { + caching = "ReadWrite" + storage_account_type = "Standard_LRS" + } - resp, err := conn.Get(ctx, resourceGroup, name) - if err != nil { - return fmt.Errorf("Bad: Get on monitorMetricAlertsClient: %+v", err) - } - if resp.StatusCode == http.StatusNotFound { - return fmt.Errorf("Bad: Metric Alert Instance %q (resource group: %q) does not exist", name, resourceGroup) - } + source_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "16.04-LTS" + version = "latest" + } +} +`, data.RandomInteger, data.Locations.Primary, count) +} - return nil - } +func testAccAzureRMMonitorMetricAlert_multiScope(data acceptance.TestData, count int) string { + return fmt.Sprintf(` +%s + +resource "azurerm_monitor_metric_alert" "test" { + name = "acctestMetricAlert-%d" + resource_group_name = azurerm_resource_group.test.name + scopes = azurerm_linux_virtual_machine.test.*.id + dynamic_criteria { + metric_namespace = "Microsoft.Compute/virtualMachines" + metric_name = "Network In" + aggregation = "Total" + + operator = "GreaterOrLessThan" + alert_sensitivity = "Medium" + } + window_size = "PT1H" + frequency = "PT5M" + target_resource_type = "Microsoft.Compute/virtualMachines" + target_resource_location = "%s" +} +`, testAccAzureRMMonitorMetricAlert_multiVMTemplate(data, count), data.RandomInteger, data.Locations.Primary) +} + +func testAccAzureRMMonitorMetricAlert_applicationInsightsWebTestTemplate(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_application_insights" "test" { + name = "acctestAppInsight-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + application_type = "web" +} + +resource "azurerm_application_insights_web_test" "test" { + name = "acctestAppInsight-webtest-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + application_insights_id = azurerm_application_insights.test.id + kind = "ping" + frequency = 300 + timeout = 60 + enabled = true + geo_locations = ["us-tx-sn1-azr", "us-il-ch1-azr"] + + configuration = < + + + + +XML + lifecycle { + ignore_changes = [tags] + } +} +`, data.RandomInteger, data.Locations.Primary) +} + +func testAccAzureRMMonitorMetricAlert_applicationInsightsWebTest(data acceptance.TestData) string { + template := testAccAzureRMMonitorMetricAlert_applicationInsightsWebTestTemplate(data) + return fmt.Sprintf(` +%s + +resource "azurerm_monitor_metric_alert" "test" { + name = "acctestMetricAlert-%d" + resource_group_name = azurerm_resource_group.test.name + scopes = [ + azurerm_application_insights.test.id, + azurerm_application_insights_web_test.test.id, + ] + application_insights_web_test_location_availability_criteria { + web_test_id = azurerm_application_insights_web_test.test.id + component_id = azurerm_application_insights.test.id + failed_location_count = 2 + } + window_size = "PT15M" + frequency = "PT1M" +} +`, template, data.RandomInteger) } diff --git a/website/docs/r/monitor_metric_alert.html.markdown b/website/docs/r/monitor_metric_alert.html.markdown index 76f087058bab..102022d8c277 100644 --- a/website/docs/r/monitor_metric_alert.html.markdown +++ b/website/docs/r/monitor_metric_alert.html.markdown @@ -70,13 +70,32 @@ The following arguments are supported: * `name` - (Required) The name of the Metric Alert. Changing this forces a new resource to be created. * `resource_group_name` - (Required) The name of the resource group in which to create the Metric Alert instance. * `scopes` - (Required) A set of strings of resource IDs at which the metric criteria should be applied. -* `criteria` - (Required) One or more `criteria` blocks as defined below. +* `criteria` - (Optional) One or more (static) `criteria` blocks as defined below. + +-> **NOTE** One of either `criteria`, `dynamic_criteria` or `application_insights_web_test_location_availability_criteria` must be specified. + +* `dynamic_criteria` - (Optional) A `dynamic_criteria` block as defined below. + +-> **NOTE** One of either `criteria`, `dynamic_criteria` or `application_insights_web_test_location_availability_criteria` must be specified. + +* `application_insights_web_test_location_availability_criteria` - (Optional) A `application_insights_web_test_location_availability_criteria` block as defined below. + +-> **NOTE** One of either `criteria`, `dynamic_criteria` or `application_insights_web_test_location_availability_criteria` must be specified. + * `action` - (Optional) One or more `action` blocks as defined below. * `enabled` - (Optional) Should this Metric Alert be enabled? Defaults to `true`. * `auto_mitigate` - (Optional) Should the alerts in this Metric Alert be auto resolved? Defaults to `true`. * `description` - (Optional) The description of this Metric Alert. * `frequency` - (Optional) The evaluation frequency of this Metric Alert, represented in ISO 8601 duration format. Possible values are `PT1M`, `PT5M`, `PT15M`, `PT30M` and `PT1H`. Defaults to `PT1M`. * `severity` - (Optional) The severity of this Metric Alert. Possible values are `0`, `1`, `2`, `3` and `4`. Defaults to `3`. +* `target_resource_type` - (Optional) The resource type (e.g. `Microsoft.Compute/virtualMachines`) of the target resource. + +-> This is Required when using a Subscription as scope, a Resource Group as scope or Multiple Scopes. + +* `target_resource_location` - (Optional) The location of the target resource. + +-> This is Required when using a Subscription as scope, a Resource Group as scope or Multiple Scopes. + * `window_size` - (Optional) The period of time that is used to monitor alert activity, represented in ISO 8601 duration format. This value must be greater than `frequency`. Possible values are `PT1M`, `PT5M`, `PT15M`, `PT30M`, `PT1H`, `PT6H`, `PT12H` and `P1D`. Defaults to `PT5M`. * `tags` - (Optional) A mapping of tags to assign to the resource. @@ -100,6 +119,28 @@ A `criteria` block supports the following: --- +A `dynamic_criteria` block supports the following: + +* `metric_namespace` - (Required) One of the metric namespaces to be monitored. +* `metric_name` - (Required) One of the metric names to be monitored. +* `aggregation` - (Required) The statistic that runs over the metric values. Possible values are `Average`, `Count`, `Minimum`, `Maximum` and `Total`. +* `operator` - (Required) The criteria operator. Possible values are `LessThan`, `GreaterThan` and `GreaterOrLessThan`. +* `alert_sensitivity` - (Required) The extent of deviation required to trigger an alert. Possible values are `Low`, `Medium` and `High`. +* `dimension` - (Optional) One or more `dimension` blocks as defined below. +* `evaluation_total_count` - (Optional) The number of aggregated lookback points. The lookback time window is calculated based on the aggregation granularity (`window_size`) and the selected number of aggregated points. +* `evaluation_failure_count` - (Optional) The number of violations to trigger an alert. Should be smaller or equal to `evaluation_total_count`. +* `ignore_data_before` - (Optional) The [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) date from which to start learning the metric historical data and calculate the dynamic thresholds. + +--- + +A `application_insights_web_test_location_availability_criteria` block supports the following: + +* `web_test_id` - (Required) The ID of the Application Insights Web Test. +* `component_id` - (Required) The ID of the Application Insights Resource. +* `failed_location_count` - (Required) The number of failed locations. + +--- + A `dimension` block supports the following: * `name` - (Required) One of the dimension names.