diff --git a/docs/resources/slo.md b/docs/resources/slo.md
index 4093e5b7..844968b3 100644
--- a/docs/resources/slo.md
+++ b/docs/resources/slo.md
@@ -145,67 +145,95 @@ resource "nobl9_slo" "this" {
### Required
-- `budgeting_method` (String) Method which will be use to calculate budget
-- `indicator` (Block Set, Min: 1, Max: 1) (see [below for nested schema](#nestedblock--indicator))
+- `budgeting_method` (String) Method which will be use to calculate budget.
- `name` (String) Unique name of the resource, must conform to the naming convention from [DNS RFC1123](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).
- `objective` (Block Set, Min: 1) [Objectives documentation](https://docs.nobl9.com/yaml-guide#objective) (see [below for nested schema](#nestedblock--objective))
- `project` (String) Name of the Nobl9 project the resource sits in, must conform to the naming convention from [DNS RFC1123](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).
-- `service` (String) Name of the service
+- `service` (String) Name of the service.
- `time_window` (Block Set, Min: 1, Max: 1) (see [below for nested schema](#nestedblock--time_window))
### Optional
-- `alert_policies` (List of String) Alert Policies attached to SLO
+- `alert_policies` (List of String) Alert Policies attached to SLO.
- `anomaly_config` (Block Set, Max: 1) Configuration for Anomalies. Currently supported Anomaly Type is NoData (see [below for nested schema](#nestedblock--anomaly_config))
- `attachment` (Block List, Max: 20) (see [below for nested schema](#nestedblock--attachment))
- `attachments` (Block List, Max: 20, Deprecated) (see [below for nested schema](#nestedblock--attachments))
-- `composite` (Block Set, Max: 1) [Composite SLO documentation](https://docs.nobl9.com/yaml-guide/#slo) (see [below for nested schema](#nestedblock--composite))
+- `composite` (Block Set, Max: 1, Deprecated) [Composite SLO documentation](https://docs.nobl9.com/yaml-guide/#slo) (see [below for nested schema](#nestedblock--composite))
- `description` (String) Optional description of the resource. Here, you can add details about who is responsible for the integration (team/owner) or the purpose of creating it.
- `display_name` (String) User-friendly display name of the resource.
+- `indicator` (Block Set, Max: 1) (see [below for nested schema](#nestedblock--indicator))
- `label` (Block List) [Labels](https://docs.nobl9.com/Features/labels/) containing a single key and a list of values. (see [below for nested schema](#nestedblock--label))
### Read-Only
- `id` (String) The ID of this resource.
-
-### Nested Schema for `indicator`
+
+### Nested Schema for `objective`
Required:
-- `name` (String) Name of the metric source (agent).
+- `target` (Number) Designated value.
+- `value` (Number) Value.
Optional:
-- `kind` (String) Kind of the metric source. One of {Agent, Direct}.
-- `project` (String) Name of the metric source project.
-
+- `composite` (Block Set) An assembly of objectives from different SLOs reflecting their combined performance. (see [below for nested schema](#nestedblock--objective--composite))
+- `count_metrics` (Block Set) Compares two time series, calculating the ratio of either good or bad values to the total number of values. Fill either the 'good' or 'bad' series, but not both. (see [below for nested schema](#nestedblock--objective--count_metrics))
+- `display_name` (String) Name to be displayed.
+- `name` (String) Objective's name. This field is computed if not provided.
+- `op` (String) Type of logical operation.
+- `primary` (Boolean) Is objective marked as primary.
+- `raw_metric` (Block Set) Raw data is used to compare objective values. (see [below for nested schema](#nestedblock--objective--raw_metric))
+- `time_slice_target` (Number) Designated value for slice.
-
-### Nested Schema for `objective`
+
+### Nested Schema for `objective.composite`
Required:
-- `display_name` (String) Name to be displayed
-- `target` (Number) Designated value
-- `value` (Number) Value
+- `max_delay` (String) Maximum time for your composite SLO to wait for data from objectives.
Optional:
-- `count_metrics` (Block Set) Compares two time series, calculating the ratio of either good or bad values to the total number of values. Fill either the 'good' or 'bad' series, but not both. (see [below for nested schema](#nestedblock--objective--count_metrics))
-- `name` (String) Objective's name. This field is computed if not provided.
-- `op` (String) Type of logical operation
-- `primary` (Boolean) Is objective marked as primary.
-- `raw_metric` (Block Set) Raw data is used to compare objective values. (see [below for nested schema](#nestedblock--objective--raw_metric))
-- `time_slice_target` (Number) Designated value for slice
+- `components` (Block Set) Objectives to be assembled in your composite SLO. (see [below for nested schema](#nestedblock--objective--composite--components))
+
+
+### Nested Schema for `objective.composite.components`
+
+Optional:
+
+- `objectives` (Block Set) An additional nesting for the components of your composite SLO. (see [below for nested schema](#nestedblock--objective--composite--components--objectives))
+
+
+### Nested Schema for `objective.composite.components.objectives`
+
+Optional:
+
+- `composite_objective` (Block List) Your composite SLO component. (see [below for nested schema](#nestedblock--objective--composite--components--objectives--composite_objective))
+
+
+### Nested Schema for `objective.composite.components.objectives.composite_objective`
+
+Required:
+
+- `objective` (String) SLO objective name.
+- `project` (String) Project name.
+- `slo` (String) SLO name.
+- `weight` (Number) Weights determine each component’s contribution to the composite SLO.
+- `when_delayed` (String) Defines how to treat missing component data on `max_delay` expiry.
+
+
+
+
### Nested Schema for `objective.count_metrics`
Required:
-- `incremental` (Boolean) Should the metrics be incrementing or not
-- `total` (Block Set, Min: 1) Configuration for metric source (see [below for nested schema](#nestedblock--objective--count_metrics--total))
+- `incremental` (Boolean) Should the metrics be incrementing or not.
+- `total` (Block Set, Min: 1) Configuration for metric source. (see [below for nested schema](#nestedblock--objective--count_metrics--total))
Optional:
@@ -1265,7 +1293,7 @@ Required:
Required:
-- `query` (Block Set, Min: 1) Configuration for metric source (see [below for nested schema](#nestedblock--objective--raw_metric--query))
+- `query` (Block Set, Min: 1) Configuration for metric source. (see [below for nested schema](#nestedblock--objective--raw_metric--query))
### Nested Schema for `objective.raw_metric.query`
@@ -1623,25 +1651,25 @@ Required:
Required:
-- `count` (Number) Count of the time unit
-- `unit` (String) Unit of time
+- `count` (Number) Count of the time unit.
+- `unit` (String) Unit of time.
Optional:
-- `calendar` (Block Set) Alert Policies attached to SLO (see [below for nested schema](#nestedblock--time_window--calendar))
-- `is_rolling` (Boolean) Is the window moving or not
+- `calendar` (Block Set) Alert Policies attached to SLO. (see [below for nested schema](#nestedblock--time_window--calendar))
+- `is_rolling` (Boolean) Is the window moving or not.
Read-Only:
-- `period` (Map of String) Period between start time and added count
+- `period` (Map of String) Period between start time and added count.
### Nested Schema for `time_window.calendar`
Required:
-- `start_time` (String) Date of the start
-- `time_zone` (String) Timezone name in IANA Time Zone Database
+- `start_time` (String) Date of the start.
+- `time_zone` (String) Timezone name in IANA Time Zone Database.
@@ -1675,7 +1703,7 @@ Required:
Required:
-- `url` (String) URL to the attachment
+- `url` (String) URL to the attachment.
Optional:
@@ -1687,7 +1715,7 @@ Optional:
Required:
-- `url` (String) URL to the attachment
+- `url` (String) URL to the attachment.
Optional:
@@ -1699,7 +1727,7 @@ Optional:
Required:
-- `target` (Number) Designated value
+- `target` (Number) Designated value.
Optional:
@@ -1710,11 +1738,24 @@ Optional:
Required:
-- `op` (String) Type of logical operation
+- `op` (String) Type of logical operation.
- `value` (Number) Burn rate value.
+
+### Nested Schema for `indicator`
+
+Required:
+
+- `name` (String) Name of the metric source (agent).
+
+Optional:
+
+- `kind` (String) Kind of the metric source. One of {Agent, Direct}.
+- `project` (String) Name of the metric source project.
+
+
### Nested Schema for `label`
diff --git a/nobl9/resource_slo.go b/nobl9/resource_slo.go
index 88612782..2dcb923a 100644
--- a/nobl9/resource_slo.go
+++ b/nobl9/resource_slo.go
@@ -84,19 +84,19 @@ func resourceObjective() *schema.Resource {
"bad": {
Type: schema.TypeSet,
Optional: true,
- Description: "Configuration for bad time series metrics. ",
+ Description: "Configuration for bad time series metrics.",
Elem: schemaMetricSpec(),
},
"total": {
Type: schema.TypeSet,
Required: true,
- Description: "Configuration for metric source",
+ Description: "Configuration for metric source.",
Elem: schemaMetricSpec(),
},
"incremental": {
Type: schema.TypeBool,
Required: true,
- Description: "Should the metrics be incrementing or not",
+ Description: "Should the metrics be incrementing or not.",
},
},
},
@@ -110,36 +110,42 @@ func resourceObjective() *schema.Resource {
"query": {
Type: schema.TypeSet,
Required: true,
- Description: "Configuration for metric source",
+ Description: "Configuration for metric source.",
Elem: schemaMetricSpec(),
},
},
},
},
+ "composite": {
+ Type: schema.TypeSet,
+ Optional: true,
+ Description: "An assembly of objectives from different SLOs reflecting their combined performance.",
+ Elem: resourceComposite(),
+ },
"display_name": {
Type: schema.TypeString,
- Required: true,
- Description: "Name to be displayed",
+ Optional: true,
+ Description: "Name to be displayed.",
},
"op": {
Type: schema.TypeString,
Optional: true,
- Description: "Type of logical operation",
+ Description: "Type of logical operation.",
},
"target": {
Type: schema.TypeFloat,
Required: true,
- Description: "Designated value",
+ Description: "Designated value.",
},
"time_slice_target": {
Type: schema.TypeFloat,
Optional: true,
- Description: "Designated value for slice",
+ Description: "Designated value for slice.",
},
"value": {
Type: schema.TypeFloat,
Required: true,
- Description: "Value",
+ Description: "Value.",
},
"name": {
Type: schema.TypeString,
@@ -157,6 +163,79 @@ func resourceObjective() *schema.Resource {
return res
}
+func resourceComposite() *schema.Resource {
+ return &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "max_delay": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Maximum time for your composite SLO to wait for data from objectives.",
+ },
+ "components": {
+ Type: schema.TypeSet,
+ Optional: true,
+ Description: "Objectives to be assembled in your composite SLO.",
+ Elem: resourceCompositeComponents(),
+ },
+ },
+ }
+}
+
+func resourceCompositeComponents() *schema.Resource {
+ return &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "objectives": {
+ Type: schema.TypeSet,
+ Optional: true,
+ Description: "An additional nesting for the components of your composite SLO.",
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "composite_objective": {
+ Type: schema.TypeList,
+ Optional: true,
+ Description: "Your composite SLO component.",
+ Elem: resourceCompositeObjective(),
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func resourceCompositeObjective() *schema.Resource {
+ return &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "project": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Project name.",
+ },
+ "slo": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "SLO name.",
+ },
+ "objective": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "SLO objective name.",
+ },
+ "weight": {
+ Type: schema.TypeFloat,
+ Required: true,
+ Description: "Weights determine each component’s contribution to the composite SLO.",
+ },
+ "when_delayed": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Defines how to treat missing component data on `max_delay` expiry.",
+ ValidateFunc: validation.StringInSlice(v1alphaSLO.WhenDelayedNames(), false),
+ },
+ },
+ }
+}
+
func schemaObjective() *schema.Schema {
return &schema.Schema{
Type: schema.TypeSet,
@@ -177,13 +256,14 @@ func schemaSLO() map[string]*schema.Schema {
Type: schema.TypeSet,
Optional: true,
Description: "[Composite SLO documentation](https://docs.nobl9.com/yaml-guide/#slo)",
+ Deprecated: "\"composite\" is deprecated, use \"objective.composite\" instead.",
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"target": {
Type: schema.TypeFloat,
Required: true,
- Description: "Designated value",
+ Description: "Designated value.",
},
"burn_rate_condition": {
Type: schema.TypeSet,
@@ -194,7 +274,7 @@ func schemaSLO() map[string]*schema.Schema {
"op": {
Type: schema.TypeString,
Required: true,
- Description: "Type of logical operation",
+ Description: "Type of logical operation.",
},
"value": {
Type: schema.TypeFloat,
@@ -210,16 +290,16 @@ func schemaSLO() map[string]*schema.Schema {
"budgeting_method": {
Type: schema.TypeString,
Required: true,
- Description: "Method which will be use to calculate budget",
+ Description: "Method which will be use to calculate budget.",
},
"service": {
Type: schema.TypeString,
Required: true,
- Description: "Name of the service",
+ Description: "Name of the service.",
},
"indicator": {
Type: schema.TypeSet,
- Required: true,
+ Optional: true,
Description: " ",
MaxItems: 1,
Elem: &schema.Resource{
@@ -254,18 +334,18 @@ func schemaSLO() map[string]*schema.Schema {
"calendar": {
Type: schema.TypeSet,
Optional: true,
- Description: "Alert Policies attached to SLO",
+ Description: "Alert Policies attached to SLO.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"start_time": {
Type: schema.TypeString,
Required: true,
- Description: "Date of the start",
+ Description: "Date of the start.",
},
"time_zone": {
Type: schema.TypeString,
Required: true,
- Description: "Timezone name in IANA Time Zone Database",
+ Description: "Timezone name in IANA Time Zone Database.",
},
},
},
@@ -273,23 +353,23 @@ func schemaSLO() map[string]*schema.Schema {
"count": {
Type: schema.TypeInt,
Required: true,
- Description: "Count of the time unit",
+ Description: "Count of the time unit.",
},
"is_rolling": {
Type: schema.TypeBool,
Optional: true,
- Description: "Is the window moving or not",
+ Description: "Is the window moving or not.",
},
"period": {
Type: schema.TypeMap,
Computed: true,
- Description: "Period between start time and added count",
+ Description: "Period between start time and added count.",
Elem: &schema.Schema{Type: schema.TypeString},
},
"unit": {
Type: schema.TypeString,
Required: true,
- Description: "Unit of time",
+ Description: "Unit of time.",
},
},
},
@@ -297,10 +377,10 @@ func schemaSLO() map[string]*schema.Schema {
"alert_policies": {
Type: schema.TypeList,
Optional: true,
- Description: "Alert Policies attached to SLO",
+ Description: "Alert Policies attached to SLO.",
Elem: &schema.Schema{
Type: schema.TypeString,
- Description: "Alert Policy",
+ Description: "Alert Policy.",
},
DiffSuppressFunc: diffSuppressListStringOrder("alert_policies"),
},
@@ -309,7 +389,7 @@ func schemaSLO() map[string]*schema.Schema {
Optional: true,
Description: "",
MaxItems: 20,
- Deprecated: "\"attachments\" argument is deprecated use \"attachment\" instead",
+ Deprecated: "\"attachments\" argument is deprecated use \"attachment\" instead.",
ConflictsWith: []string{"attachment"},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
@@ -322,7 +402,7 @@ func schemaSLO() map[string]*schema.Schema {
"url": {
Type: schema.TypeString,
Required: true,
- Description: "URL to the attachment",
+ Description: "URL to the attachment.",
},
},
},
@@ -343,7 +423,7 @@ func schemaSLO() map[string]*schema.Schema {
"url": {
Type: schema.TypeString,
Required: true,
- Description: "URL to the attachment",
+ Description: "URL to the attachment.",
},
},
},
@@ -489,6 +569,11 @@ func marshalSLO(d *schema.ResourceData) (*v1alphaSLO.SLO, diag.Diagnostics) {
if diags.HasError() {
return nil, diags
}
+ objectiveSpec, diags := marshalObjectives(d)
+ if diags.HasError() {
+ return nil, diags
+ }
+
slo := v1alphaSLO.New(
v1alphaSLO.Metadata{
Name: d.Get("name").(string),
@@ -501,8 +586,8 @@ func marshalSLO(d *schema.ResourceData) (*v1alphaSLO.SLO, diag.Diagnostics) {
Service: d.Get("service").(string),
BudgetingMethod: d.Get("budgeting_method").(string),
Indicator: marshalIndicator(d),
- Composite: marshalComposite(d),
- Objectives: marshalObjectives(d),
+ Composite: marshalCompositeDeprecated(d),
+ Objectives: objectiveSpec,
TimeWindows: marshalTimeWindows(d),
AlertPolicies: toStringSlice(d.Get("alert_policies").([]interface{})),
Attachments: marshalAttachments(attachments.([]interface{})),
@@ -511,7 +596,7 @@ func marshalSLO(d *schema.ResourceData) (*v1alphaSLO.SLO, diag.Diagnostics) {
return &slo, diags
}
-func marshalComposite(d *schema.ResourceData) *v1alphaSLO.Composite {
+func marshalCompositeDeprecated(d *schema.ResourceData) *v1alphaSLO.Composite {
compositeSet := d.Get("composite").(*schema.Set)
if compositeSet.Len() > 0 {
@@ -539,6 +624,48 @@ func marshalComposite(d *schema.ResourceData) *v1alphaSLO.Composite {
return nil
}
+func marshalComposite(sloObjective map[string]interface{}) (*v1alphaSLO.CompositeSpec, error) {
+ compositeSet := sloObjective["composite"].(*schema.Set)
+ if compositeSet.Len() == 0 {
+ return nil, nil
+ }
+ compositeMap := compositeSet.List()[0].(map[string]interface{})
+ maxDelay := compositeMap["max_delay"].(string)
+
+ componentsSet := compositeMap["components"].(*schema.Set)
+ resultObjectives := make([]v1alphaSLO.CompositeObjective, 0)
+ if len(componentsSet.List()) > 0 {
+ componentsMap := componentsSet.List()[0].(map[string]interface{})
+ objectivesSet := componentsMap["objectives"].(*schema.Set)
+ compositeObjectivesMap := objectivesSet.List()[0].(map[string]interface{})
+ compositeObjectives := compositeObjectivesMap["composite_objective"].([]interface{})
+
+ for _, compObjElems := range compositeObjectives {
+ compObj := compObjElems.(map[string]interface{})
+ whenDelayed, ok := compObj["when_delayed"].(string)
+ if !ok || whenDelayed == "" {
+ return nil, fmt.Errorf("when_delayed is required for composite objective")
+ }
+ whenDelayedParsed, err := v1alphaSLO.ParseWhenDelayed(whenDelayed)
+ if err != nil {
+ return nil, err
+ }
+
+ resultObjectives = append(resultObjectives, v1alphaSLO.CompositeObjective{
+ Project: compObj["project"].(string),
+ SLO: compObj["slo"].(string),
+ Objective: compObj["objective"].(string),
+ Weight: compObj["weight"].(float64),
+ WhenDelayed: whenDelayedParsed,
+ })
+ }
+ }
+ return &v1alphaSLO.CompositeSpec{
+ MaxDelay: maxDelay,
+ Components: v1alphaSLO.Components{Objectives: resultObjectives},
+ }, nil
+}
+
func marshalTimeWindows(d *schema.ResourceData) []v1alphaSLO.TimeWindow {
timeWindow := d.Get("time_window").(*schema.Set).List()[0].(map[string]interface{})
@@ -579,6 +706,9 @@ func marshalCalendar(c map[string]interface{}) *v1alphaSLO.Calendar {
}
func marshalIndicator(d *schema.ResourceData) *v1alphaSLO.Indicator {
+ if d.Get("indicator").(*schema.Set).Len() == 0 {
+ return nil
+ }
var resultIndicator v1alphaSLO.Indicator
indicator := d.Get("indicator").(*schema.Set).List()[0].(map[string]interface{})
kind, err := manifest.ParseKind(indicator["kind"].(string))
@@ -595,7 +725,7 @@ func marshalIndicator(d *schema.ResourceData) *v1alphaSLO.Indicator {
return &resultIndicator
}
-func marshalObjectives(d *schema.ResourceData) []v1alphaSLO.Objective {
+func marshalObjectives(d *schema.ResourceData) ([]v1alphaSLO.Objective, diag.Diagnostics) {
objectivesSchema := d.Get("objective").(*schema.Set).List()
objectives := make([]v1alphaSLO.Objective, len(objectivesSchema))
for i, o := range objectivesSchema {
@@ -609,6 +739,10 @@ func marshalObjectives(d *schema.ResourceData) []v1alphaSLO.Objective {
}
operator := objective["op"].(string)
primary := objective["primary"].(bool)
+ compositeSpec, err := marshalComposite(objective)
+ if err != nil {
+ return nil, diag.FromErr(err)
+ }
objectives[i] = v1alphaSLO.Objective{
ObjectiveBase: v1alphaSLO.ObjectiveBase{
@@ -622,10 +756,10 @@ func marshalObjectives(d *schema.ResourceData) []v1alphaSLO.Objective {
CountMetrics: marshalCountMetrics(objective),
RawMetric: marshalRawMetric(objective),
Primary: &primary,
+ Composite: compositeSpec,
}
}
-
- return objectives
+ return objectives, nil
}
func marshalRawMetric(metricRoot map[string]interface{}) *v1alphaSLO.RawMetricSpec {
@@ -750,7 +884,7 @@ func unmarshalSLO(d *schema.ResourceData, objects []v1alphaSLO.SLO) diag.Diagnos
err = unmarshalObjectives(d, spec)
diags = appendError(diags, err)
- err = unmarshalComposite(d, spec)
+ err = unmarshalCompositeDeprecated(d, spec)
diags = appendError(diags, err)
err = unmarshalAttachments(d, spec)
@@ -804,6 +938,9 @@ func getDeclaredAttachmentTag(d *schema.ResourceData) string {
}
func unmarshalIndicator(d *schema.ResourceData, spec v1alphaSLO.Spec) error {
+ if spec.Indicator == nil {
+ return nil
+ }
indicator := spec.Indicator
res := make(map[string]interface{})
metricSource := indicator.MetricSource
@@ -871,6 +1008,9 @@ func unmarshalObjectives(d *schema.ResourceData, spec v1alphaSLO.Spec) error {
objectiveTF["raw_metric"] = tfMetric
}
+ if objective.Composite != nil {
+ objectiveTF["composite"] = unmarshalComposite(objective.Composite)
+ }
objectivesTF[i] = objectiveTF
}
return d.Set("objective", schema.NewSet(objectiveHash, objectivesTF))
@@ -888,7 +1028,8 @@ func objectiveHash(objective interface{}) int {
)
return schema.HashString(indicator)
}
-func unmarshalComposite(d *schema.ResourceData, spec v1alphaSLO.Spec) error {
+
+func unmarshalCompositeDeprecated(d *schema.ResourceData, spec v1alphaSLO.Spec) error {
//nolint:staticcheck
if spec.Composite != nil {
//nolint:staticcheck
@@ -911,6 +1052,39 @@ func unmarshalComposite(d *schema.ResourceData, spec v1alphaSLO.Spec) error {
return nil
}
+func unmarshalComposite(compositeSpec *v1alphaSLO.CompositeSpec) *schema.Set {
+ if compositeSpec == nil {
+ return nil
+ }
+
+ composite := make(map[string]interface{})
+ composite["max_delay"] = compositeSpec.MaxDelay
+
+ compObjList := make([]interface{}, 0, len(compositeSpec.Components.Objectives))
+ for _, objective := range compositeSpec.Components.Objectives {
+ compositeObjective := make(map[string]interface{})
+ compositeObjective["project"] = objective.Project
+ compositeObjective["slo"] = objective.SLO
+ compositeObjective["objective"] = objective.Objective
+ compositeObjective["weight"] = objective.Weight
+ compositeObjective["when_delayed"] = objective.WhenDelayed.String()
+
+ compObjList = append(compObjList, compositeObjective)
+ }
+
+ objectives := make(map[string]interface{})
+ objectives["composite_objective"] = compObjList
+
+ components := make(map[string]interface{})
+ components["objectives"] = schema.NewSet(
+ schema.HashResource(resourceCompositeComponents()),
+ []interface{}{objectives},
+ )
+ composite["components"] = schema.NewSet(oneElementSet, []interface{}{components})
+
+ return schema.NewSet(schema.HashResource(resourceComposite()), []interface{}{composite})
+}
+
func unmarshalSLORawMetric(rawMetricSource *v1alphaSLO.RawMetricSpec) *schema.Set {
var rawMetricQuery *schema.Set
if rawMetricSource.MetricQuery != nil {
diff --git a/nobl9/resource_slo_test.go b/nobl9/resource_slo_test.go
index 9ab5102e..824018f9 100644
--- a/nobl9/resource_slo_test.go
+++ b/nobl9/resource_slo_test.go
@@ -1,6 +1,7 @@
package nobl9
import (
+ "fmt"
"regexp"
"strings"
"testing"
@@ -27,6 +28,8 @@ func TestAcc_Nobl9SLO(t *testing.T) {
{"test-cloudwatch-with-stat", testCloudWatchWithStat},
{"test-cloudwatch-with-stat-and-cross-account", testCloudWatchWithStatAndCrossAccount},
{"test-cloudwatch-with-bad-over-total", testCloudWatchWithBadOverTotal},
+ {"test-composite-occurrences-deprecated", testCompositeSLOOccurrencesDeprecated},
+ {"test-composite-time-slices-deprecated", testCompositeSLOTimeSlicesDeprecated},
{"test-composite-occurrences", testCompositeSLOOccurrences},
{"test-composite-time-slices", testCompositeSLOTimeSlices},
{"test-datadog", testDatadogSLO},
@@ -835,7 +838,7 @@ resource "nobl9_slo" ":name" {
return config
}
-func testCompositeSLOOccurrences(name string) string {
+func testCompositeSLOOccurrencesDeprecated(name string) string {
var serviceName = name + "-tf-service"
var agentName = name + "-tf-agent"
config :=
@@ -918,7 +921,7 @@ resource "nobl9_slo" ":name" {
return config
}
-func testCompositeSLOTimeSlices(name string) string {
+func testCompositeSLOTimeSlicesDeprecated(name string) string {
var serviceName = name + "-tf-service"
var agentName = name + "-tf-agent"
config :=
@@ -989,6 +992,142 @@ resource "nobl9_slo" ":name" {
return config
}
+func testCompositeSLOOccurrences(name string) string {
+ var serviceName = name + "-tf-service"
+ var agentName = name + "-tf-agent"
+ var sloDependencyName = name + "-dependency-slo-1"
+ config :=
+ testService(serviceName) +
+ testPrometheusAgent(agentName) +
+ testCompositeDependencySLO(serviceName, agentName, sloDependencyName) + `
+resource "nobl9_slo" ":name" {
+ name = ":name"
+ display_name = ":name"
+ project = ":project"
+ service = nobl9_service.:serviceName.name
+
+
+ depends_on = [nobl9_slo.:sloDependencyName]
+
+ label {
+ key = "team"
+ values = ["green","sapphire"]
+ }
+
+ label {
+ key = "env"
+ values = ["dev", "staging", "prod"]
+ }
+
+ budgeting_method = "Occurrences"
+
+ objective {
+ display_name = "obj1"
+ name = "tf-objective-1"
+ target = 0.7
+ value = 1
+ composite {
+ max_delay = "45m"
+ components {
+ objectives {
+ composite_objective {
+ project = ":project"
+ slo = ":sloDependencyName"
+ objective = "objective-1"
+ weight = 0.8
+ when_delayed = "CountAsGood"
+ }
+ composite_objective {
+ project = ":project"
+ slo = ":sloDependencyName"
+ objective = "objective-2"
+ weight = 1.0
+ when_delayed = "CountAsBad"
+ }
+ }
+ }
+ }
+ }
+
+ time_window {
+ count = 10
+ is_rolling = true
+ unit = "Minute"
+ }
+}
+`
+ config = strings.ReplaceAll(config, ":name", name)
+ config = strings.ReplaceAll(config, ":serviceName", serviceName)
+ config = strings.ReplaceAll(config, ":agentName", agentName)
+ config = strings.ReplaceAll(config, ":project", testProject)
+ config = strings.ReplaceAll(config, ":sloDependencyName", sloDependencyName)
+
+ return config
+}
+
+func testCompositeSLOTimeSlices(name string) string {
+ var serviceName = name + "-tf-service"
+ var agentName = name + "-tf-agent"
+ var sloDependencyName = name + "-dependency-slo-2"
+ config :=
+ testService(serviceName) +
+ testPrometheusAgent(agentName) +
+ testCompositeDependencySLO(serviceName, agentName, sloDependencyName) + `
+resource "nobl9_slo" ":name" {
+ name = ":name"
+ display_name = ":name"
+ project = ":project"
+ service = nobl9_service.:serviceName.name
+
+ depends_on = [nobl9_slo.:sloDependencyName]
+
+ budgeting_method = "Timeslices"
+
+ objective {
+ display_name = "obj1"
+ name = "tf-objective-1"
+ target = 0.7
+ value = 1
+ time_slice_target = 0.7
+ composite {
+ max_delay = "45m"
+ components {
+ objectives {
+ composite_objective {
+ project = ":project"
+ slo = ":sloDependencyName"
+ objective = "objective-1"
+ weight = 0.8
+ when_delayed = "CountAsGood"
+ }
+ composite_objective {
+ project = ":project"
+ slo = ":sloDependencyName"
+ objective = "objective-2"
+ weight = 1.0
+ when_delayed = "CountAsBad"
+ }
+ }
+ }
+ }
+ }
+
+ time_window {
+ count = 10
+ is_rolling = true
+ unit = "Minute"
+ }
+}
+`
+ config = strings.ReplaceAll(config, ":name", name)
+ config = strings.ReplaceAll(config, ":serviceName", serviceName)
+ config = strings.ReplaceAll(config, ":agentName", agentName)
+ config = strings.ReplaceAll(config, ":project", testProject)
+ config = strings.ReplaceAll(config, ":sloDependencyName", sloDependencyName)
+
+ return config
+}
+
func testDatadogSLO(name string) string {
var serviceName = name + "-tf-service"
var agentName = name + "-tf-agent"
@@ -998,7 +1137,7 @@ func testDatadogSLO(name string) string {
resource "nobl9_slo" ":name" {
name = ":name"
display_name = ":name"
- project = ":project"
+ project = ":project"
service = nobl9_service.:serviceName.name
label {
@@ -3194,3 +3333,57 @@ func testMoreThanOnePrimaryObjective(name string) string {
return config
}
+
+func testCompositeDependencySLO(serviceName, agentName, name string) string {
+ return fmt.Sprintf(`
+resource "nobl9_slo" "%s" {
+ name = "%s"
+ service = nobl9_service.%s.name
+ budgeting_method = "Occurrences"
+ project = "%s"
+
+ time_window {
+ unit = "Day"
+ count = 14
+ is_rolling = true
+ }
+
+ objective {
+ target = 0.999
+ value = 5
+ display_name = "Good"
+ name = "objective-1"
+ op = "lte"
+
+ raw_metric {
+ query {
+ prometheus {
+ promql = "server_requestMsec{host=\"*\",instance=\"143.146.168.125:9913\",job=\"nginx\"}"
+ }
+ }
+ }
+ }
+ objective {
+ target = 0.80
+ value = 2
+ display_name = "Moderate"
+ name = "objective-2"
+ op = "lte"
+
+ raw_metric {
+ query {
+ prometheus {
+ promql = "server_requestMsec{host=\"*\",instance=\"143.146.168.125:9913\",job=\"nginx\"}"
+ }
+ }
+ }
+ }
+
+ indicator {
+ name = nobl9_agent.%s.name
+ kind = "Agent"
+ project = "%s"
+ }
+}
+`, name, name, serviceName, testProject, agentName, testProject)
+}