Skip to content

Commit

Permalink
Added support for request-based SLIs in monitoring SLO (GoogleCloudPl…
Browse files Browse the repository at this point in the history
…atform#3491)

* initial slo resource

* whitespace and update mask

* add exactly X of to slo descriptions

* unit tests
  • Loading branch information
emilymye authored and Nathan Klish committed May 18, 2020
1 parent c18009f commit 3124f56
Show file tree
Hide file tree
Showing 12 changed files with 524 additions and 54 deletions.
138 changes: 136 additions & 2 deletions products/monitoring/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1043,13 +1043,18 @@ objects:
properties:
- !ruby/object:Api::Type::NestedObject
name: basicSli
required: true
exactly_one_of:
- service_level_indicator.0.basic_sli
- service_level_indicator.0.request_based_sli
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.
Exactly one of the following must be set:
`basic_sli`, `request_based_sli`
properties:
- !ruby/object:Api::Type::Array
name: method
Expand Down Expand Up @@ -1094,6 +1099,135 @@ objects:
A duration string, e.g. 10s.
Good service is defined to be the count of requests made to
this service that return in no more than threshold.
- !ruby/object:Api::Type::NestedObject
name: requestBasedSli
api_name: 'requestBased'
exactly_one_of:
- service_level_indicator.0.basic_sli
- service_level_indicator.0.request_based_sli
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`
# If adding properties to requestBasedSli, remember to add to the
# custom updateMask fields in property overrides.
properties:
- !ruby/object:Api::Type::NestedObject
name: goodTotalRatio
exactly_one_of:
- service_level_indicator.0.request_based_sli.0.good_total_ratio
- service_level_indicator.0.request_based_sli.0.distribution_cut
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.
properties:
- !ruby/object:Api::Type::String
name: goodServiceFilter
at_least_one_of:
- service_level_indicator.0.request_based_sli.0.good_total_ratio.0.good_service_filter
- service_level_indicator.0.request_based_sli.0.good_total_ratio.0.bad_service_filter
- service_level_indicator.0.request_based_sli.0.good_total_ratio.0.total_service_filter
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).
- !ruby/object:Api::Type::String
name: badServiceFilter
at_least_one_of:
- service_level_indicator.0.request_based_sli.0.good_total_ratio.0.good_service_filter
- service_level_indicator.0.request_based_sli.0.good_total_ratio.0.bad_service_filter
- service_level_indicator.0.request_based_sli.0.good_total_ratio.0.total_service_filter
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).
- !ruby/object:Api::Type::String
name: totalServiceFilter
at_least_one_of:
- service_level_indicator.0.request_based_sli.0.good_total_ratio.0.good_service_filter
- service_level_indicator.0.request_based_sli.0.good_total_ratio.0.bad_service_filter
- service_level_indicator.0.request_based_sli.0.good_total_ratio.0.total_service_filter
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).
- !ruby/object:Api::Type::NestedObject
name: distributionCut
exactly_one_of:
- service_level_indicator.0.request_based_sli.0.good_total_ratio
- service_level_indicator.0.request_based_sli.0.distribution_cut
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.
properties:
- !ruby/object:Api::Type::String
name: distributionFilter
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.
- !ruby/object:Api::Type::NestedObject
name: range
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.
properties:
- !ruby/object:Api::Type::Integer
name: min
at_least_one_of:
- service_level_indicator.0.request_based_sli.0.distribution_cut.0.range.0.min
- service_level_indicator.0.request_based_sli.0.distribution_cut.0.range.0.max
description: |
Min value for the range (inclusive). If not given,
will be set to "-infinity", defining an open range
"< range.max"
- !ruby/object:Api::Type::Integer
name: max
at_least_one_of:
- service_level_indicator.0.request_based_sli.0.distribution_cut.0.range.0.min
- service_level_indicator.0.request_based_sli.0.distribution_cut.0.range.0.max
description: |
max value for the range (inclusive). If not given,
will be set to "infinity", defining an open range
">= range.min"
- !ruby/object:Api::Resource
name: UptimeCheckConfig
Expand Down Expand Up @@ -1319,4 +1453,4 @@ objects:
required: true
description: Values for all of the labels listed in the associated
monitored resource descriptor. For example, Compute Engine VM instances use
the labels "project_id", "instance_id", and "zone".
the labels "project_id", "instance_id", and "zone".
17 changes: 17 additions & 0 deletions products/monitoring/terraform.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,14 @@ overrides: !ruby/object:Overrides::ResourceOverrides
primary_resource_id: "appeng_slo"
vars:
slo_id: "ae-slo"
- !ruby/object:Provider::Terraform::Examples
name: "monitoring_slo_request_based"
primary_resource_id: "request_based_slo"
test_env_vars:
project: :PROJECT_NAME
vars:
service_id: "custom-srv"
slo_id: "consumed-api-slo"
properties:
rollingPeriodDays: !ruby/object:Overrides::Terraform::PropertyOverride
api_name: rollingPeriod
Expand All @@ -164,6 +172,15 @@ overrides: !ruby/object:Overrides::ResourceOverrides
is_set: true
serviceLevelIndicator.basicSli.version: !ruby/object:Overrides::Terraform::PropertyOverride
is_set: true
serviceLevelIndicator.requestBasedSli: !ruby/object:Overrides::Terraform::PropertyOverride
# Force update all nested fields to allow for unsetting values.
update_mask_fields:
- "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"
custom_code: !ruby/object:Provider::Terraform::CustomCode
constants: templates/terraform/constants/monitoring_slo.go.erb
custom_import: templates/terraform/custom_import/self_link_as_name.erb
Expand Down
47 changes: 36 additions & 11 deletions provider/terraform.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,23 +100,48 @@ def force_new?(property, resource)
force_new?(property.parent, resource))))
end

# Returns the property for a given Terraform field path (e.g.
# Returns tuples of (fieldName, list of update masks) for
# top-level updatable fields. Schema path refers to a given Terraform
# field name (e.g. d.GetChange('fieldName)')
def get_property_update_masks_groups(properties, mask_prefix: '')
mask_groups = []
properties.each do |prop|
if prop.flatten_object
mask_groups += get_property_update_masks_groups(
prop.properties, mask_prefix: "#{prop.api_name}."
)
elsif prop.update_mask_fields
mask_groups << [prop.name.underscore, prop.update_mask_fields]
else
mask_groups << [prop.name.underscore, [mask_prefix + prop.api_name]]
end
end
mask_groups
end

# Returns an updated path for a given Terraform field path (e.g.
# 'a_field', 'parent_field.0.child_name'). Returns nil if the property
# is not included in the resource's properties.
def property_for_schema_path(schema_path, resource)
# is not included in the resource's properties and removes keys that have
# been flattened
# TODO(emilymye): Change format of input for
# xactly_one_of/at_least_one_of/etc to use camelcase, MM properities and
# convert to snake in this method
def get_property_schema_path(schema_path, resource)
nested_props = resource.properties
prop = nil

schema_path.split('.').each_with_index do |pname, i|
next if i.odd?

pname = pname.camelize(:lower)
prop = nested_props.find { |p| p.name == pname }
break if prop.nil?
path_tkns = schema_path.split('.0.').map do |pname|
camel_pname = pname.camelize(:lower)
prop = nested_props.find { |p| p.name == camel_pname }
return nil if prop.nil?

nested_props = prop.nested_properties || []
prop.flatten_object ? nil : pname.underscore
end
if path_tkns.empty? || path_tkns[-1].nil?
nil
else
path_tkns.compact.join('.0.')
end
prop
end

# Transforms a format string with field markers to a regex string with
Expand Down
2 changes: 1 addition & 1 deletion spec/compiler_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@

it { is_expected.to be_instance_of Api::Product }
it { is_expected.to have_attributes(api_name: 'myproduct') }
it { is_expected.to have_attribute_of_length(objects: 4) }
it { is_expected.to have_attribute_of_length(objects: 5) }
end

context 'should only accept product' do
Expand Down
37 changes: 37 additions & 0 deletions spec/data/good-file.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,43 @@ objects:
- !ruby/object:Api::Type::String
name: 'nv-prop1'
description: 'the first property in my namevalues'
- !ruby/object:Api::Resource
name: 'ResourceWithTerraformOverride'
kind: 'terraform#resourceWithTerraformOverride'
base_url: 'resourceWithTerraformOverride'
description: 'a description'
properties:
- !ruby/object:Api::Type::String
name: 'stringOne'
description: 'a string property (depth 0)'
- !ruby/object:Api::Type::NestedObject
name: 'objectOne'
description: 'a NestedObject property (depth 0)'
properties:
- !ruby/object:Api::Type::String
name: 'objectOneString'
description: 'a string property (depth 1)'
- !ruby/object:Api::Type::NestedObject
name: 'objectOneFlattenedObject'
description: 'a nested NestedObject (depth 1)'
properties:
- !ruby/object:Api::Type::Integer
name: 'objectOneNestedNestedInteger'
description: 'a nested integer (depth 2)'
- !ruby/object:Api::Type::NestedObject
name: 'objectTwoFlattened'
description: 'a NestedObject property that is flattened (depth 0)'
properties:
- !ruby/object:Api::Type::String
name: 'objectTwoString'
description: 'a nested string (depth 1)'
- !ruby/object:Api::Type::NestedObject
name: 'objectTwoNestedObject'
description: 'a nested NestedObject (depth 1)'
properties:
- !ruby/object:Api::Type::String
name: 'objectTwoNestedNestedString'
description: 'a nested String (depth 2)'
- !ruby/object:Api::Resource
name: 'TerraformImportIdTest'
description: 'Used for spec/provider_terraform_import_spec'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@
# limitations under the License.

--- !ruby/object:Provider::Terraform::Config
overrides: !ruby/object:Provider::ResourceOverrides
AnotherResource: !ruby/object:Provider::Terraform::ResourceOverride
description: '{{description}} bar'
overrides: !ruby/object:Overrides::ResourceOverrides
ResourceWithTerraformOverride: !ruby/object:Overrides::Terraform::ResourceOverride
properties:
property1: !ruby/object:Provider::Terraform::PropertyOverride
description: 'foo'
nested-property.property1: !ruby/object:Provider::Terraform::PropertyOverride
description: 'bar'
array-property.property1: !ruby/object:Provider::Terraform::PropertyOverride
description: 'baz'
objectTwoFlattened: !ruby/object:Overrides::Terraform::PropertyOverride
flatten_object: true
objectTwoFlattened.objectTwoNestedObject: !ruby/object:Overrides::Terraform::PropertyOverride
update_mask_fields:
- 'overrideFoo'
- 'nested.overrideBar'
10 changes: 10 additions & 0 deletions spec/data/terraform-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,13 @@

--- !ruby/object:Provider::Terraform::Config
overrides: !ruby/object:Overrides::ResourceOverrides
ResourceWithTerraformOverride: !ruby/object:Overrides::Terraform::ResourceOverride
properties:
objectOne.objectOneFlattenedObject: !ruby/object:Overrides::Terraform::PropertyOverride
flatten_object: true
objectTwoFlattened: !ruby/object:Overrides::Terraform::PropertyOverride
flatten_object: true
objectTwoFlattened.objectTwoString: !ruby/object:Overrides::Terraform::PropertyOverride
update_mask_fields:
- 'overrideFoo'
- 'nested.overrideBar'
Loading

0 comments on commit 3124f56

Please sign in to comment.