From 0c6a7266a5730fecd6af335817b360b090a14ef2 Mon Sep 17 00:00:00 2001 From: The Magician Date: Tue, 12 May 2020 13:14:14 -0400 Subject: [PATCH] Added support for request-based SLIs in monitoring SLO (#3491) (#6353) * initial slo resource * whitespace and update mask * add exactly X of to slo descriptions * unit tests Signed-off-by: Modular Magician --- .changelog/3491.txt | 3 + google/resource_app_engine_domain_mapping.go | 3 +- google/resource_kms_crypto_key.go | 3 +- google/resource_monitoring_slo.go | 407 +++++++++++++++++- .../resource_monitoring_slo_generated_test.go | 58 +++ google/resource_monitoring_slo_test.go | 154 ++++++- website/docs/r/monitoring_slo.html.markdown | 147 ++++++- 7 files changed, 760 insertions(+), 15 deletions(-) create mode 100644 .changelog/3491.txt diff --git a/.changelog/3491.txt b/.changelog/3491.txt new file mode 100644 index 00000000000..1a9cb7a071e --- /dev/null +++ b/.changelog/3491.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +monitoring: Added `request_based` SLI support to `google_monitoring_slo` +``` diff --git a/google/resource_app_engine_domain_mapping.go b/google/resource_app_engine_domain_mapping.go index d6a298ce6bd..78d605c1ec0 100644 --- a/google/resource_app_engine_domain_mapping.go +++ b/google/resource_app_engine_domain_mapping.go @@ -293,7 +293,8 @@ func resourceAppEngineDomainMappingUpdate(d *schema.ResourceData, meta interface updateMask := []string{} if d.HasChange("ssl_settings") { - updateMask = append(updateMask, "ssl_settings.certificate_id,ssl_settings.ssl_management_type") + updateMask = append(updateMask, "ssl_settings.certificate_id", + "ssl_settings.ssl_management_type") } // updateMask is a URL parameter but not present in the schema, so replaceVars // won't set it diff --git a/google/resource_kms_crypto_key.go b/google/resource_kms_crypto_key.go index 6c63ce83e28..bec4f71d8b0 100644 --- a/google/resource_kms_crypto_key.go +++ b/google/resource_kms_crypto_key.go @@ -272,7 +272,8 @@ func resourceKMSCryptoKeyUpdate(d *schema.ResourceData, meta interface{}) error } if d.HasChange("rotation_period") { - updateMask = append(updateMask, "rotationPeriod,nextRotationTime") + updateMask = append(updateMask, "rotationPeriod", + "nextRotationTime") } if d.HasChange("version_template") { diff --git a/google/resource_monitoring_slo.go b/google/resource_monitoring_slo.go index 47613077e8a..d141fee0a55 100644 --- a/google/resource_monitoring_slo.go +++ b/google/resource_monitoring_slo.go @@ -18,6 +18,7 @@ import ( "fmt" "log" "reflect" + "strconv" "strings" "time" @@ -88,12 +89,15 @@ Must be between 1 to 30 days, inclusive.`, }, "basic_sli": { Type: schema.TypeList, - Required: true, + Optional: true, Description: `Basic Service-Level Indicator (SLI) on a well-known service type. Performance will be computed on the basis of pre-defined metrics. SLIs are used to measure and calculate the quality of the Service's -performance with respect to a single aspect of service quality.`, +performance with respect to a single aspect of service quality. + +Exactly one of the following must be set: +'basic_sli', 'request_based_sli'`, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -158,6 +162,138 @@ field will result in an error.`, }, }, }, + ExactlyOneOf: []string{"basic_sli", "request_based_sli"}, + }, + "request_based_sli": { + Type: schema.TypeList, + Optional: true, + Description: `A request-based SLI defines a SLI for which atomic units of +service are counted directly. + +A SLI describes a good service. +It is used to measure and calculate the quality of the Service's +performance with respect to a single aspect of service quality. +Exactly one of the following must be set: +'basic_sli', 'request_based_sli'`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "distribution_cut": { + Type: schema.TypeList, + Optional: true, + Description: `Used when good_service is defined by a count of values aggregated in a +Distribution that fall into a good range. The total_service is the +total count of all values aggregated in the Distribution. +Defines a distribution TimeSeries filter and thresholds used for +measuring good service and total service. + +Exactly one of 'distribution_cut' or 'good_total_ratio' can be set.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "distribution_filter": { + Type: schema.TypeString, + Required: true, + Description: `A TimeSeries [monitoring filter](https://cloud.google.com/monitoring/api/v3/filters) +aggregating values to quantify the good service provided. + +Must have ValueType = DISTRIBUTION and +MetricKind = DELTA or MetricKind = CUMULATIVE.`, + }, + "range": { + Type: schema.TypeList, + Required: true, + Description: `Range of numerical values. The computed good_service +will be the count of values x in the Distribution such +that range.min <= x < range.max. inclusive of min and +exclusive of max. Open ranges can be defined by setting +just one of min or max.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max": { + Type: schema.TypeInt, + Optional: true, + Description: `max value for the range (inclusive). If not given, +will be set to "infinity", defining an open range +">= range.min"`, + AtLeastOneOf: []string{"request_based_sli.0.distribution_cut.0.range.0.min", "request_based_sli.0.distribution_cut.0.range.0.max"}, + }, + "min": { + Type: schema.TypeInt, + Optional: true, + Description: `Min value for the range (inclusive). If not given, +will be set to "-infinity", defining an open range +"< range.max"`, + AtLeastOneOf: []string{"request_based_sli.0.distribution_cut.0.range.0.min", "request_based_sli.0.distribution_cut.0.range.0.max"}, + }, + }, + }, + }, + }, + }, + ExactlyOneOf: []string{"request_based_sli.0.good_total_ratio", "request_based_sli.0.distribution_cut"}, + }, + "good_total_ratio": { + Type: schema.TypeList, + Optional: true, + Description: `A means to compute a ratio of 'good_service' to 'total_service'. +Defines computing this ratio with two TimeSeries [monitoring filters](https://cloud.google.com/monitoring/api/v3/filters) +Must specify exactly two of good, bad, and total service filters. +The relationship good_service + bad_service = total_service +will be assumed. + +Exactly one of 'distribution_cut' or 'good_total_ratio' can be set.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "bad_service_filter": { + Type: schema.TypeString, + Optional: true, + Description: `A TimeSeries [monitoring filter](https://cloud.google.com/monitoring/api/v3/filters) +quantifying bad service provided, either demanded service that +was not provided or demanded service that was of inadequate +quality. + +Must have ValueType = DOUBLE or ValueType = INT64 and +must have MetricKind = DELTA or MetricKind = CUMULATIVE. + +Exactly two of 'good_service_filter','bad_service_filter','total_service_filter' +must be set (good + bad = total is assumed).`, + AtLeastOneOf: []string{"request_based_sli.0.good_total_ratio.0.good_service_filter", "request_based_sli.0.good_total_ratio.0.bad_service_filter", "request_based_sli.0.good_total_ratio.0.total_service_filter"}, + }, + "good_service_filter": { + Type: schema.TypeString, + Optional: true, + Description: `A TimeSeries [monitoring filter](https://cloud.google.com/monitoring/api/v3/filters) +quantifying good service provided. +Must have ValueType = DOUBLE or ValueType = INT64 and +must have MetricKind = DELTA or MetricKind = CUMULATIVE. + +Exactly two of 'good_service_filter','bad_service_filter','total_service_filter' +must be set (good + bad = total is assumed).`, + AtLeastOneOf: []string{"request_based_sli.0.good_total_ratio.0.good_service_filter", "request_based_sli.0.good_total_ratio.0.bad_service_filter", "request_based_sli.0.good_total_ratio.0.total_service_filter"}, + }, + "total_service_filter": { + Type: schema.TypeString, + Optional: true, + Description: `A TimeSeries [monitoring filter](https://cloud.google.com/monitoring/api/v3/filters) +quantifying total demanded service. + +Must have ValueType = DOUBLE or ValueType = INT64 and +must have MetricKind = DELTA or MetricKind = CUMULATIVE. + +Exactly two of 'good_service_filter','bad_service_filter','total_service_filter' +must be set (good + bad = total is assumed).`, + AtLeastOneOf: []string{"request_based_sli.0.good_total_ratio.0.good_service_filter", "request_based_sli.0.good_total_ratio.0.bad_service_filter", "request_based_sli.0.good_total_ratio.0.total_service_filter"}, + }, + }, + }, + ExactlyOneOf: []string{"request_based_sli.0.good_total_ratio", "request_based_sli.0.distribution_cut"}, + }, + }, + }, + ExactlyOneOf: []string{"basic_sli", "request_based_sli"}, }, "slo_id": { @@ -400,7 +536,16 @@ func resourceMonitoringSloUpdate(d *schema.ResourceData, meta interface{}) error } if d.HasChange("basic_sli") { - updateMask = append(updateMask, "serviceLevelIndicator") + updateMask = append(updateMask, "serviceLevelIndicator.basicSli") + } + + if d.HasChange("request_based_sli") { + updateMask = append(updateMask, "serviceLevelIndicator.requestBased.goodTotalRatio.badServiceFilter", + "serviceLevelIndicator.requestBased.goodTotalRatio.goodServiceFilter", + "serviceLevelIndicator.requestBased.goodTotalRatio.totalServiceFilter", + "serviceLevelIndicator.requestBased.distributionCut.range.min", + "serviceLevelIndicator.requestBased.distributionCut.range.max", + "serviceLevelIndicator.requestBased.distributionCut.distributionFilter") } // updateMask is a URL parameter but not present in the schema, so replaceVars // won't set it @@ -503,6 +648,8 @@ func flattenMonitoringSloServiceLevelIndicator(v interface{}, d *schema.Resource transformed := make(map[string]interface{}) transformed["basic_sli"] = flattenMonitoringSloServiceLevelIndicatorBasicSli(original["basicSli"], d, config) + transformed["request_based_sli"] = + flattenMonitoringSloServiceLevelIndicatorRequestBasedSli(original["requestBased"], d, config) return []interface{}{transformed} } func flattenMonitoringSloServiceLevelIndicatorBasicSli(v interface{}, d *schema.ResourceData, config *Config) interface{} { @@ -562,6 +709,118 @@ func flattenMonitoringSloServiceLevelIndicatorBasicSliLatencyThreshold(v interfa return v } +func flattenMonitoringSloServiceLevelIndicatorRequestBasedSli(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["good_total_ratio"] = + flattenMonitoringSloServiceLevelIndicatorRequestBasedSliGoodTotalRatio(original["goodTotalRatio"], d, config) + transformed["distribution_cut"] = + flattenMonitoringSloServiceLevelIndicatorRequestBasedSliDistributionCut(original["distributionCut"], d, config) + return []interface{}{transformed} +} +func flattenMonitoringSloServiceLevelIndicatorRequestBasedSliGoodTotalRatio(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["good_service_filter"] = + flattenMonitoringSloServiceLevelIndicatorRequestBasedSliGoodTotalRatioGoodServiceFilter(original["goodServiceFilter"], d, config) + transformed["bad_service_filter"] = + flattenMonitoringSloServiceLevelIndicatorRequestBasedSliGoodTotalRatioBadServiceFilter(original["badServiceFilter"], d, config) + transformed["total_service_filter"] = + flattenMonitoringSloServiceLevelIndicatorRequestBasedSliGoodTotalRatioTotalServiceFilter(original["totalServiceFilter"], d, config) + return []interface{}{transformed} +} +func flattenMonitoringSloServiceLevelIndicatorRequestBasedSliGoodTotalRatioGoodServiceFilter(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenMonitoringSloServiceLevelIndicatorRequestBasedSliGoodTotalRatioBadServiceFilter(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenMonitoringSloServiceLevelIndicatorRequestBasedSliGoodTotalRatioTotalServiceFilter(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenMonitoringSloServiceLevelIndicatorRequestBasedSliDistributionCut(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["distribution_filter"] = + flattenMonitoringSloServiceLevelIndicatorRequestBasedSliDistributionCutDistributionFilter(original["distributionFilter"], d, config) + transformed["range"] = + flattenMonitoringSloServiceLevelIndicatorRequestBasedSliDistributionCutRange(original["range"], d, config) + return []interface{}{transformed} +} +func flattenMonitoringSloServiceLevelIndicatorRequestBasedSliDistributionCutDistributionFilter(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenMonitoringSloServiceLevelIndicatorRequestBasedSliDistributionCutRange(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["min"] = + flattenMonitoringSloServiceLevelIndicatorRequestBasedSliDistributionCutRangeMin(original["min"], d, config) + transformed["max"] = + flattenMonitoringSloServiceLevelIndicatorRequestBasedSliDistributionCutRangeMax(original["max"], d, config) + return []interface{}{transformed} +} +func flattenMonitoringSloServiceLevelIndicatorRequestBasedSliDistributionCutRangeMin(v interface{}, d *schema.ResourceData, config *Config) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil { + return intVal + } + } + + // number values are represented as float64 + if floatVal, ok := v.(float64); ok { + intVal := int(floatVal) + return intVal + } + + return v // let terraform core handle it otherwise +} + +func flattenMonitoringSloServiceLevelIndicatorRequestBasedSliDistributionCutRangeMax(v interface{}, d *schema.ResourceData, config *Config) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil { + return intVal + } + } + + // number values are represented as float64 + if floatVal, ok := v.(float64); ok { + intVal := int(floatVal) + return intVal + } + + return v // let terraform core handle it otherwise +} + func flattenMonitoringSloSloId(v interface{}, d *schema.ResourceData, config *Config) interface{} { if v == nil { return v @@ -604,6 +863,13 @@ func expandMonitoringSloServiceLevelIndicator(v interface{}, d TerraformResource transformed["basicSli"] = transformedBasicSli } + transformedRequestBasedSli, err := expandMonitoringSloServiceLevelIndicatorRequestBasedSli(d.Get("request_based_sli"), d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedRequestBasedSli); val.IsValid() && !isEmptyValue(val) { + transformed["requestBased"] = transformedRequestBasedSli + } + return transformed, nil } @@ -685,6 +951,141 @@ func expandMonitoringSloServiceLevelIndicatorBasicSliLatencyThreshold(v interfac return v, nil } +func expandMonitoringSloServiceLevelIndicatorRequestBasedSli(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedGoodTotalRatio, err := expandMonitoringSloServiceLevelIndicatorRequestBasedSliGoodTotalRatio(original["good_total_ratio"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedGoodTotalRatio); val.IsValid() && !isEmptyValue(val) { + transformed["goodTotalRatio"] = transformedGoodTotalRatio + } + + transformedDistributionCut, err := expandMonitoringSloServiceLevelIndicatorRequestBasedSliDistributionCut(original["distribution_cut"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDistributionCut); val.IsValid() && !isEmptyValue(val) { + transformed["distributionCut"] = transformedDistributionCut + } + + return transformed, nil +} + +func expandMonitoringSloServiceLevelIndicatorRequestBasedSliGoodTotalRatio(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedGoodServiceFilter, err := expandMonitoringSloServiceLevelIndicatorRequestBasedSliGoodTotalRatioGoodServiceFilter(original["good_service_filter"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedGoodServiceFilter); val.IsValid() && !isEmptyValue(val) { + transformed["goodServiceFilter"] = transformedGoodServiceFilter + } + + transformedBadServiceFilter, err := expandMonitoringSloServiceLevelIndicatorRequestBasedSliGoodTotalRatioBadServiceFilter(original["bad_service_filter"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedBadServiceFilter); val.IsValid() && !isEmptyValue(val) { + transformed["badServiceFilter"] = transformedBadServiceFilter + } + + transformedTotalServiceFilter, err := expandMonitoringSloServiceLevelIndicatorRequestBasedSliGoodTotalRatioTotalServiceFilter(original["total_service_filter"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedTotalServiceFilter); val.IsValid() && !isEmptyValue(val) { + transformed["totalServiceFilter"] = transformedTotalServiceFilter + } + + return transformed, nil +} + +func expandMonitoringSloServiceLevelIndicatorRequestBasedSliGoodTotalRatioGoodServiceFilter(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandMonitoringSloServiceLevelIndicatorRequestBasedSliGoodTotalRatioBadServiceFilter(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandMonitoringSloServiceLevelIndicatorRequestBasedSliGoodTotalRatioTotalServiceFilter(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandMonitoringSloServiceLevelIndicatorRequestBasedSliDistributionCut(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedDistributionFilter, err := expandMonitoringSloServiceLevelIndicatorRequestBasedSliDistributionCutDistributionFilter(original["distribution_filter"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDistributionFilter); val.IsValid() && !isEmptyValue(val) { + transformed["distributionFilter"] = transformedDistributionFilter + } + + transformedRange, err := expandMonitoringSloServiceLevelIndicatorRequestBasedSliDistributionCutRange(original["range"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedRange); val.IsValid() && !isEmptyValue(val) { + transformed["range"] = transformedRange + } + + return transformed, nil +} + +func expandMonitoringSloServiceLevelIndicatorRequestBasedSliDistributionCutDistributionFilter(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandMonitoringSloServiceLevelIndicatorRequestBasedSliDistributionCutRange(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedMin, err := expandMonitoringSloServiceLevelIndicatorRequestBasedSliDistributionCutRangeMin(original["min"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedMin); val.IsValid() && !isEmptyValue(val) { + transformed["min"] = transformedMin + } + + transformedMax, err := expandMonitoringSloServiceLevelIndicatorRequestBasedSliDistributionCutRangeMax(original["max"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedMax); val.IsValid() && !isEmptyValue(val) { + transformed["max"] = transformedMax + } + + return transformed, nil +} + +func expandMonitoringSloServiceLevelIndicatorRequestBasedSliDistributionCutRangeMin(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandMonitoringSloServiceLevelIndicatorRequestBasedSliDistributionCutRangeMax(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + func expandMonitoringSloSloId(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { return v, nil } diff --git a/google/resource_monitoring_slo_generated_test.go b/google/resource_monitoring_slo_generated_test.go index 858bb189cd4..3f4d242d57a 100644 --- a/google/resource_monitoring_slo_generated_test.go +++ b/google/resource_monitoring_slo_generated_test.go @@ -72,6 +72,64 @@ resource "google_monitoring_slo" "appeng_slo" { `, context) } +func TestAccMonitoringSlo_monitoringSloRequestBasedExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "project": getTestProjectFromEnv(), + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckMonitoringSloDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccMonitoringSlo_monitoringSloRequestBasedExample(context), + }, + { + ResourceName: "google_monitoring_slo.request_based_slo", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"service"}, + }, + }, + }) +} + +func testAccMonitoringSlo_monitoringSloRequestBasedExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_monitoring_custom_service" "customsrv" { + service_id = "tf-test-custom-srv%{random_suffix}" + display_name = "My Custom Service" +} + +resource "google_monitoring_slo" "request_based_slo" { + service = google_monitoring_custom_service.customsrv.service_id + slo_id = "tf-test-consumed-api-slo%{random_suffix}" + display_name = "Terraform Test SLO with request based SLI (good total ratio)" + + goal = 0.9 + rolling_period_days = 30 + + request_based_sli { + distribution_cut { + distribution_filter = join(" AND ", [ + "metric.type=\"serviceruntime.googleapis.com/api/request_latencies\"", + "resource.type=\"consumed_api\"", + "resource.label.\"project_id\"=\"%{project}\"", + ]) + + range { + max = 10 + } + } + } +} +`, context) +} + func testAccCheckMonitoringSloDestroyProducer(t *testing.T) func(s *terraform.State) error { return func(s *terraform.State) error { for name, rs := range s.RootModule().Resources { diff --git a/google/resource_monitoring_slo_test.go b/google/resource_monitoring_slo_test.go index bb5ff5affbf..491cbd93cbb 100644 --- a/google/resource_monitoring_slo_test.go +++ b/google/resource_monitoring_slo_test.go @@ -69,7 +69,7 @@ func getTestResourceMonitoringSloId(res string, s *terraform.State) (string, err return "", fmt.Errorf("slo_id not set on resource %s", res) } -func TestAccMonitoringSlo_update(t *testing.T) { +func TestAccMonitoringSlo_basic(t *testing.T) { t.Parallel() var generatedId string @@ -90,7 +90,7 @@ func TestAccMonitoringSlo_update(t *testing.T) { ImportStateVerifyIgnore: []string{"service"}, }, { - Config: testAccMonitoringSlo_update(), + Config: testAccMonitoringSlo_basicUpdate(), Check: testCheckMonitoringSloIdAfterUpdate("google_monitoring_slo.primary", &generatedId), }, { @@ -104,6 +104,53 @@ func TestAccMonitoringSlo_update(t *testing.T) { }) } +func TestAccMonitoringSlo_requestBased(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "project": getTestProjectFromEnv(), + "random_suffix": randString(t, 10), + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckMonitoringSloDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccMonitoringSlo_requestBasedDistribution(context), + }, + { + ResourceName: "google_monitoring_slo.request_based_slo", + ImportState: true, + ImportStateVerify: true, + // Ignore input-only field for import + ImportStateVerifyIgnore: []string{"service"}, + }, + { + Config: testAccMonitoringSlo_requestBasedGoodBadRatio(context), + }, + { + ResourceName: "google_monitoring_slo.request_based_slo", + ImportState: true, + ImportStateVerify: true, + // Ignore input-only field for import + ImportStateVerifyIgnore: []string{"service"}, + }, + { + Config: testAccMonitoringSlo_requestBasedGoodTotalRatio(context), + }, + { + ResourceName: "google_monitoring_slo.request_based_slo", + ImportState: true, + ImportStateVerify: true, + // Ignore input-only field for import + ImportStateVerifyIgnore: []string{"service"}, + }, + }, + }) +} + func testAccMonitoringSlo_basic() string { return ` data "google_monitoring_app_engine_service" "ae" { @@ -125,7 +172,7 @@ resource "google_monitoring_slo" "primary" { ` } -func testAccMonitoringSlo_update() string { +func testAccMonitoringSlo_basicUpdate() string { return ` data "google_monitoring_app_engine_service" "ae" { module_id = "default" @@ -146,3 +193,104 @@ resource "google_monitoring_slo" "primary" { } ` } + +func testAccMonitoringSlo_requestBasedDistribution(context map[string]interface{}) string { + return Nprintf(` +resource "google_monitoring_custom_service" "srv" { + service_id = "tf-test-custom-srv%{random_suffix}" + display_name = "My Custom Service" +} + +resource "google_monitoring_slo" "request_based_slo" { + service = google_monitoring_custom_service.srv.service_id + slo_id = "tf-test-consumed-api-slo%{random_suffix}" + display_name = "Terraform Test SLO with request based SLI" + + goal = 0.9 + rolling_period_days = 30 + + request_based_sli { + distribution_cut { + distribution_filter = join(" AND ", [ + "metric.type=\"serviceruntime.googleapis.com/api/request_latencies\"", + "resource.type=\"consumed_api\"", + "resource.label.\"project_id\"=\"%{project}\"", + ]) + + range { + max = 10 + } + } + } +} +`, context) +} + +func testAccMonitoringSlo_requestBasedGoodTotalRatio(context map[string]interface{}) string { + return Nprintf(` +resource "google_monitoring_custom_service" "srv" { + service_id = "tf-test-custom-srv%{random_suffix}" + display_name = "My Custom Service" +} + +resource "google_monitoring_slo" "request_based_slo" { + service = google_monitoring_custom_service.srv.service_id + slo_id = "tf-test-consumed-api-slo%{random_suffix}" + display_name = "Terraform Test SLO with request based SLI (good total ratio)" + + goal = 0.9 + rolling_period_days = 30 + + request_based_sli { + good_total_ratio { + good_service_filter = join(" AND ", [ + "metric.type=\"serviceruntime.googleapis.com/api/request_count\"", + "resource.type=\"consumed_api\"", + "resource.label.\"project_id\"=\"%{project}\"", + "metric.label.\"response_code\"=\"200\"", + ]) + total_service_filter = join(" AND ", [ + "metric.type=\"serviceruntime.googleapis.com/api/request_count\"", + "resource.type=\"consumed_api\"", + "resource.label.\"project_id\"=\"%{project}\"", + ]) + } + } +} +`, context) +} + +func testAccMonitoringSlo_requestBasedGoodBadRatio(context map[string]interface{}) string { + return Nprintf(` +resource "google_monitoring_custom_service" "srv" { + service_id = "tf-test-custom-srv%{random_suffix}" + display_name = "My Custom Service" +} + +resource "google_monitoring_slo" "request_based_slo" { + service = google_monitoring_custom_service.srv.service_id + slo_id = "tf-test-consumed-api-slo%{random_suffix}" + display_name = "Terraform Test SLO with request based SLI (good total ratio)" + + goal = 0.9 + rolling_period_days = 30 + + request_based_sli { + good_total_ratio { + good_service_filter = join(" AND ", [ + "metric.type=\"serviceruntime.googleapis.com/api/request_count\"", + "resource.type=\"consumed_api\"", + "resource.label.\"project_id\"=\"%{project}\"", + "metric.label.\"response_code\"=\"200\"", + ]) + bad_service_filter = join(" AND ", [ + "metric.type=\"serviceruntime.googleapis.com/api/request_count\"", + "resource.type=\"consumed_api\"", + "resource.label.\"project_id\"=\"%{project}\"", + "metric.label.\"response_code\"=\"400\"", + ]) + } + } +} +`, context) +} diff --git a/website/docs/r/monitoring_slo.html.markdown b/website/docs/r/monitoring_slo.html.markdown index 8a925b80712..6938ed651c2 100644 --- a/website/docs/r/monitoring_slo.html.markdown +++ b/website/docs/r/monitoring_slo.html.markdown @@ -68,6 +68,43 @@ resource "google_monitoring_slo" "appeng_slo" { } } ``` + +## Example Usage - Monitoring Slo Request Based + + +```hcl +resource "google_monitoring_custom_service" "customsrv" { + service_id = "custom-srv" + display_name = "My Custom Service" +} + +resource "google_monitoring_slo" "request_based_slo" { + service = google_monitoring_custom_service.customsrv.service_id + slo_id = "consumed-api-slo" + display_name = "Terraform Test SLO with request based SLI (good total ratio)" + + goal = 0.9 + rolling_period_days = 30 + + request_based_sli { + distribution_cut { + distribution_filter = join(" AND ", [ + "metric.type=\"serviceruntime.googleapis.com/api/request_latencies\"", + "resource.type=\"consumed_api\"", + "resource.label.\"project_id\"=\"my-project-name\"", + ]) + + range { + max = 10 + } + } + } +} +``` ## Argument Reference @@ -79,13 +116,6 @@ The following arguments are supported: The fraction of service that must be good in order for this objective to be met. 0 < goal <= 0.999 -* `basic_sli` - - (Required) - Basic Service-Level Indicator (SLI) on a well-known service type. - Performance will be computed on the basis of pre-defined metrics. - SLIs are used to measure and calculate the quality of the Service's - performance with respect to a single aspect of service quality. Structure is documented below. - * `service` - (Required) ID of the service to which this SLO belongs. @@ -114,6 +144,25 @@ The following arguments are supported: * `FORTNIGHT` * `MONTH` +* `basic_sli` - + (Optional) + Basic Service-Level Indicator (SLI) on a well-known service type. + Performance will be computed on the basis of pre-defined metrics. + SLIs are used to measure and calculate the quality of the Service's + performance with respect to a single aspect of service quality. + Exactly one of the following must be set: + `basic_sli`, `request_based_sli` Structure is documented below. + +* `request_based_sli` - + (Optional) + A request-based SLI defines a SLI for which atomic units of + service are counted directly. + A SLI describes a good service. + It is used to measure and calculate the quality of the Service's + performance with respect to a single aspect of service quality. + Exactly one of the following must be set: + `basic_sli`, `request_based_sli` Structure is documented below. + * `slo_id` - (Optional) The id to use for this ServiceLevelObjective. If omitted, an id will be generated instead. @@ -165,6 +214,90 @@ The `latency` block supports: Good service is defined to be the count of requests made to this service that return in no more than threshold. +The `request_based_sli` block supports: + +* `good_total_ratio` - + (Optional) + A means to compute a ratio of `good_service` to `total_service`. + Defines computing this ratio with two TimeSeries [monitoring filters](https://cloud.google.com/monitoring/api/v3/filters) + Must specify exactly two of good, bad, and total service filters. + The relationship good_service + bad_service = total_service + will be assumed. + Exactly one of `distribution_cut` or `good_total_ratio` can be set. Structure is documented below. + +* `distribution_cut` - + (Optional) + Used when good_service is defined by a count of values aggregated in a + Distribution that fall into a good range. The total_service is the + total count of all values aggregated in the Distribution. + Defines a distribution TimeSeries filter and thresholds used for + measuring good service and total service. + Exactly one of `distribution_cut` or `good_total_ratio` can be set. Structure is documented below. + + +The `good_total_ratio` block supports: + +* `good_service_filter` - + (Optional) + A TimeSeries [monitoring filter](https://cloud.google.com/monitoring/api/v3/filters) + quantifying good service provided. + Must have ValueType = DOUBLE or ValueType = INT64 and + must have MetricKind = DELTA or MetricKind = CUMULATIVE. + Exactly two of `good_service_filter`,`bad_service_filter`,`total_service_filter` + must be set (good + bad = total is assumed). + +* `bad_service_filter` - + (Optional) + A TimeSeries [monitoring filter](https://cloud.google.com/monitoring/api/v3/filters) + quantifying bad service provided, either demanded service that + was not provided or demanded service that was of inadequate + quality. + Must have ValueType = DOUBLE or ValueType = INT64 and + must have MetricKind = DELTA or MetricKind = CUMULATIVE. + Exactly two of `good_service_filter`,`bad_service_filter`,`total_service_filter` + must be set (good + bad = total is assumed). + +* `total_service_filter` - + (Optional) + A TimeSeries [monitoring filter](https://cloud.google.com/monitoring/api/v3/filters) + quantifying total demanded service. + Must have ValueType = DOUBLE or ValueType = INT64 and + must have MetricKind = DELTA or MetricKind = CUMULATIVE. + Exactly two of `good_service_filter`,`bad_service_filter`,`total_service_filter` + must be set (good + bad = total is assumed). + +The `distribution_cut` block supports: + +* `distribution_filter` - + (Required) + A TimeSeries [monitoring filter](https://cloud.google.com/monitoring/api/v3/filters) + aggregating values to quantify the good service provided. + Must have ValueType = DISTRIBUTION and + MetricKind = DELTA or MetricKind = CUMULATIVE. + +* `range` - + (Required) + Range of numerical values. The computed good_service + will be the count of values x in the Distribution such + that range.min <= x < range.max. inclusive of min and + exclusive of max. Open ranges can be defined by setting + just one of min or max. Structure is documented below. + + +The `range` block supports: + +* `min` - + (Optional) + Min value for the range (inclusive). If not given, + will be set to "-infinity", defining an open range + "< range.max" + +* `max` - + (Optional) + max value for the range (inclusive). If not given, + will be set to "infinity", defining an open range + ">= range.min" + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are exported: