diff --git a/.generator/schemas/v1/openapi.yaml b/.generator/schemas/v1/openapi.yaml index e4c949659..c13289329 100644 --- a/.generator/schemas/v1/openapi.yaml +++ b/.generator/schemas/v1/openapi.yaml @@ -11409,6 +11409,60 @@ components: example: UTC type: string type: object + SLOCountCondition: + description: 'A count-based SLI specification, composed of three parts: the + good events formula, the total events formula, + + and the involved queries.' + example: + good_events_formula: query1 - query2 + queries: + - data_source: metrics + name: query1 + query: sum:trace.servlet.request.success{*} by {env}.as_count() + - data_source: metrics + name: query2 + query: sum:trace.servlet.request.hits{*} by {env}.as_count() + total_events_formula: query2 + properties: + good_events_formula: + $ref: '#/components/schemas/SLOFormula' + queries: + example: + - data_source: metrics + name: query1 + query: sum:trace.servlet.request.hits{*} by {env}.as_count() + items: + $ref: '#/components/schemas/SLODataSourceQueryDefinition' + minItems: 1 + type: array + total_events_formula: + $ref: '#/components/schemas/SLOFormula' + required: + - good_events_formula + - total_events_formula + - queries + type: object + SLOCountSpec: + additionalProperties: false + description: A count-based SLI specification. + example: + count: + good_events_formula: query1 - query2 + queries: + - data_source: metrics + name: query1 + query: sum:trace.servlet.request.success{*} by {env}.as_count() + - data_source: metrics + name: query2 + query: sum:trace.servlet.request.hits{*} by {env}.as_count() + total_events_formula: query2 + properties: + count: + $ref: '#/components/schemas/SLOCountCondition' + required: + - count + type: object SLOCreator: description: The creator of the SLO nullable: true @@ -12295,8 +12349,6 @@ components: type: array timeframe: $ref: '#/components/schemas/SLOTimeframe' - type: - $ref: '#/components/schemas/SLOType' warning_threshold: description: 'The optional warning threshold such that when the service level indicator is @@ -12314,9 +12366,10 @@ components: type: object SLOSliSpec: description: A generic SLI specification. This is currently used for time-slice - SLOs only. + and count-based SLOs only. oneOf: - $ref: '#/components/schemas/SLOTimeSliceSpec' + - $ref: '#/components/schemas/SLOCountSpec' SLOState: description: State of the SLO. enum: @@ -13468,8 +13521,7 @@ components: - type type: object ServiceLevelObjectiveQuery: - description: 'A metric-based SLO. **Required if type is `metric`**. Note that - Datadog only allows the sum by aggregator + description: 'A metric-based SLO. Note that Datadog only allows the sum by aggregator to be used because this will sum up all request counts instead of averaging them, or taking the max or diff --git a/examples/v1_service-level-objectives_CreateSLO_512760759.rs b/examples/v1_service-level-objectives_CreateSLO_512760759.rs new file mode 100644 index 000000000..c165435ac --- /dev/null +++ b/examples/v1_service-level-objectives_CreateSLO_512760759.rs @@ -0,0 +1,61 @@ +// Create a new metric SLO object using sli_specification returns "OK" response +use datadog_api_client::datadog; +use datadog_api_client::datadogV1::api_service_level_objectives::ServiceLevelObjectivesAPI; +use datadog_api_client::datadogV1::model::FormulaAndFunctionMetricDataSource; +use datadog_api_client::datadogV1::model::FormulaAndFunctionMetricQueryDefinition; +use datadog_api_client::datadogV1::model::SLOCountCondition; +use datadog_api_client::datadogV1::model::SLOCountSpec; +use datadog_api_client::datadogV1::model::SLODataSourceQueryDefinition; +use datadog_api_client::datadogV1::model::SLOFormula; +use datadog_api_client::datadogV1::model::SLOSliSpec; +use datadog_api_client::datadogV1::model::SLOThreshold; +use datadog_api_client::datadogV1::model::SLOTimeframe; +use datadog_api_client::datadogV1::model::SLOType; +use datadog_api_client::datadogV1::model::ServiceLevelObjectiveRequest; + +#[tokio::main] +async fn main() { + let body = ServiceLevelObjectiveRequest::new( + "Example-Service-Level-Objective".to_string(), + vec![SLOThreshold::new(99.0, SLOTimeframe::SEVEN_DAYS) + .target_display("99.0".to_string()) + .warning(98.0 as f64) + .warning_display("98.0".to_string())], + SLOType::METRIC, + ) + .description(Some("Metric SLO using sli_specification".to_string())) + .sli_specification(SLOSliSpec::SLOCountSpec(Box::new(SLOCountSpec::new( + SLOCountCondition::new( + SLOFormula::new("query1".to_string()), + vec![ + SLODataSourceQueryDefinition::FormulaAndFunctionMetricQueryDefinition(Box::new( + FormulaAndFunctionMetricQueryDefinition::new( + FormulaAndFunctionMetricDataSource::METRICS, + "query1".to_string(), + "sum:httpservice.success{*}.as_count()".to_string(), + ), + )), + SLODataSourceQueryDefinition::FormulaAndFunctionMetricQueryDefinition(Box::new( + FormulaAndFunctionMetricQueryDefinition::new( + FormulaAndFunctionMetricDataSource::METRICS, + "query2".to_string(), + "sum:httpservice.hits{*}.as_count()".to_string(), + ), + )), + ], + SLOFormula::new("query2".to_string()), + ), + )))) + .tags(vec!["env:prod".to_string(), "type:count".to_string()]) + .target_threshold(99.0 as f64) + .timeframe(SLOTimeframe::SEVEN_DAYS) + .warning_threshold(98.0 as f64); + let configuration = datadog::Configuration::new(); + let api = ServiceLevelObjectivesAPI::with_config(configuration); + let resp = api.create_slo(body).await; + if let Ok(value) = resp { + println!("{:#?}", value); + } else { + println!("{:#?}", resp.unwrap_err()); + } +} diff --git a/src/datadogV1/model/mod.rs b/src/datadogV1/model/mod.rs index 7a29733b4..aab300617 100644 --- a/src/datadogV1/model/mod.rs +++ b/src/datadogV1/model/mod.rs @@ -1224,6 +1224,10 @@ pub mod model_slo_data_source_query_definition; pub use self::model_slo_data_source_query_definition::SLODataSourceQueryDefinition; pub mod model_slo_time_slice_interval; pub use self::model_slo_time_slice_interval::SLOTimeSliceInterval; +pub mod model_slo_count_spec; +pub use self::model_slo_count_spec::SLOCountSpec; +pub mod model_slo_count_condition; +pub use self::model_slo_count_condition::SLOCountCondition; pub mod model_slo_sli_spec; pub use self::model_slo_sli_spec::SLOSliSpec; pub mod model_slo_threshold; diff --git a/src/datadogV1/model/model_service_level_objective.rs b/src/datadogV1/model/model_service_level_objective.rs index 6f0a90228..821592135 100644 --- a/src/datadogV1/model/model_service_level_objective.rs +++ b/src/datadogV1/model/model_service_level_objective.rs @@ -62,12 +62,12 @@ pub struct ServiceLevelObjective { /// The name of the service level objective object. #[serde(rename = "name")] pub name: String, - /// A metric-based SLO. **Required if type is `metric`**. Note that Datadog only allows the sum by aggregator + /// A metric-based SLO. Note that Datadog only allows the sum by aggregator /// to be used because this will sum up all request counts instead of averaging them, or taking the max or /// min of all of those requests. #[serde(rename = "query")] pub query: Option, - /// A generic SLI specification. This is currently used for time-slice SLOs only. + /// A generic SLI specification. This is currently used for time-slice and count-based SLOs only. #[serde(rename = "sli_specification")] pub sli_specification: Option, /// A list of tags associated with this service level objective. diff --git a/src/datadogV1/model/model_service_level_objective_query.rs b/src/datadogV1/model/model_service_level_objective_query.rs index ef4e31f8f..b6d276962 100644 --- a/src/datadogV1/model/model_service_level_objective_query.rs +++ b/src/datadogV1/model/model_service_level_objective_query.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Deserializer, Serialize}; use serde_with::skip_serializing_none; use std::fmt::{self, Formatter}; -/// A metric-based SLO. **Required if type is `metric`**. Note that Datadog only allows the sum by aggregator +/// A metric-based SLO. Note that Datadog only allows the sum by aggregator /// to be used because this will sum up all request counts instead of averaging them, or taking the max or /// min of all of those requests. #[non_exhaustive] diff --git a/src/datadogV1/model/model_service_level_objective_request.rs b/src/datadogV1/model/model_service_level_objective_request.rs index 1a673bd88..407703aa4 100644 --- a/src/datadogV1/model/model_service_level_objective_request.rs +++ b/src/datadogV1/model/model_service_level_objective_request.rs @@ -36,12 +36,12 @@ pub struct ServiceLevelObjectiveRequest { /// The name of the service level objective object. #[serde(rename = "name")] pub name: String, - /// A metric-based SLO. **Required if type is `metric`**. Note that Datadog only allows the sum by aggregator + /// A metric-based SLO. Note that Datadog only allows the sum by aggregator /// to be used because this will sum up all request counts instead of averaging them, or taking the max or /// min of all of those requests. #[serde(rename = "query")] pub query: Option, - /// A generic SLI specification. This is currently used for time-slice SLOs only. + /// A generic SLI specification. This is currently used for time-slice and count-based SLOs only. #[serde(rename = "sli_specification")] pub sli_specification: Option, /// A list of tags associated with this service level objective. diff --git a/src/datadogV1/model/model_slo_count_condition.rs b/src/datadogV1/model/model_slo_count_condition.rs new file mode 100644 index 000000000..1fc6ba63f --- /dev/null +++ b/src/datadogV1/model/model_slo_count_condition.rs @@ -0,0 +1,122 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. +use serde::de::{Error, MapAccess, Visitor}; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_with::skip_serializing_none; +use std::fmt::{self, Formatter}; + +/// A count-based SLI specification, composed of three parts: the good events formula, the total events formula, +/// and the involved queries. +#[non_exhaustive] +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct SLOCountCondition { + /// A formula that specifies how to combine the results of multiple queries. + #[serde(rename = "good_events_formula")] + pub good_events_formula: crate::datadogV1::model::SLOFormula, + #[serde(rename = "queries")] + pub queries: Vec, + /// A formula that specifies how to combine the results of multiple queries. + #[serde(rename = "total_events_formula")] + pub total_events_formula: crate::datadogV1::model::SLOFormula, + #[serde(flatten)] + pub additional_properties: std::collections::BTreeMap, + #[serde(skip)] + #[serde(default)] + pub(crate) _unparsed: bool, +} + +impl SLOCountCondition { + pub fn new( + good_events_formula: crate::datadogV1::model::SLOFormula, + queries: Vec, + total_events_formula: crate::datadogV1::model::SLOFormula, + ) -> SLOCountCondition { + SLOCountCondition { + good_events_formula, + queries, + total_events_formula, + additional_properties: std::collections::BTreeMap::new(), + _unparsed: false, + } + } + + pub fn additional_properties( + mut self, + value: std::collections::BTreeMap, + ) -> Self { + self.additional_properties = value; + self + } +} + +impl<'de> Deserialize<'de> for SLOCountCondition { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct SLOCountConditionVisitor; + impl<'a> Visitor<'a> for SLOCountConditionVisitor { + type Value = SLOCountCondition; + + fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("a mapping") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'a>, + { + let mut good_events_formula: Option = None; + let mut queries: Option< + Vec, + > = None; + let mut total_events_formula: Option = None; + let mut additional_properties: std::collections::BTreeMap< + String, + serde_json::Value, + > = std::collections::BTreeMap::new(); + let mut _unparsed = false; + + while let Some((k, v)) = map.next_entry::()? { + match k.as_str() { + "good_events_formula" => { + good_events_formula = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "queries" => { + queries = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "total_events_formula" => { + total_events_formula = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + &_ => { + if let Ok(value) = serde_json::from_value(v.clone()) { + additional_properties.insert(k, value); + } + } + } + } + let good_events_formula = good_events_formula + .ok_or_else(|| M::Error::missing_field("good_events_formula"))?; + let queries = queries.ok_or_else(|| M::Error::missing_field("queries"))?; + let total_events_formula = total_events_formula + .ok_or_else(|| M::Error::missing_field("total_events_formula"))?; + + let content = SLOCountCondition { + good_events_formula, + queries, + total_events_formula, + additional_properties, + _unparsed, + }; + + Ok(content) + } + } + + deserializer.deserialize_any(SLOCountConditionVisitor) + } +} diff --git a/src/datadogV1/model/model_slo_count_spec.rs b/src/datadogV1/model/model_slo_count_spec.rs new file mode 100644 index 000000000..87bff9fb0 --- /dev/null +++ b/src/datadogV1/model/model_slo_count_spec.rs @@ -0,0 +1,74 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. +use serde::de::{Error, MapAccess, Visitor}; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_with::skip_serializing_none; +use std::fmt::{self, Formatter}; + +/// A count-based SLI specification. +#[non_exhaustive] +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct SLOCountSpec { + /// A count-based SLI specification, composed of three parts: the good events formula, the total events formula, + /// and the involved queries. + #[serde(rename = "count")] + pub count: crate::datadogV1::model::SLOCountCondition, + #[serde(skip)] + #[serde(default)] + pub(crate) _unparsed: bool, +} + +impl SLOCountSpec { + pub fn new(count: crate::datadogV1::model::SLOCountCondition) -> SLOCountSpec { + SLOCountSpec { + count, + _unparsed: false, + } + } +} + +impl<'de> Deserialize<'de> for SLOCountSpec { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct SLOCountSpecVisitor; + impl<'a> Visitor<'a> for SLOCountSpecVisitor { + type Value = SLOCountSpec; + + fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("a mapping") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'a>, + { + let mut count: Option = None; + let mut _unparsed = false; + + while let Some((k, v)) = map.next_entry::()? { + match k.as_str() { + "count" => { + count = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + &_ => { + return Err(serde::de::Error::custom( + "Additional properties not allowed", + )); + } + } + } + let count = count.ok_or_else(|| M::Error::missing_field("count"))?; + + let content = SLOCountSpec { count, _unparsed }; + + Ok(content) + } + } + + deserializer.deserialize_any(SLOCountSpecVisitor) + } +} diff --git a/src/datadogV1/model/model_slo_response_data.rs b/src/datadogV1/model/model_slo_response_data.rs index 56b1e9ac5..35f54aaf4 100644 --- a/src/datadogV1/model/model_slo_response_data.rs +++ b/src/datadogV1/model/model_slo_response_data.rs @@ -65,12 +65,12 @@ pub struct SLOResponseData { /// The name of the service level objective object. #[serde(rename = "name")] pub name: Option, - /// A metric-based SLO. **Required if type is `metric`**. Note that Datadog only allows the sum by aggregator + /// A metric-based SLO. Note that Datadog only allows the sum by aggregator /// to be used because this will sum up all request counts instead of averaging them, or taking the max or /// min of all of those requests. #[serde(rename = "query")] pub query: Option, - /// A generic SLI specification. This is currently used for time-slice SLOs only. + /// A generic SLI specification. This is currently used for time-slice and count-based SLOs only. #[serde(rename = "sli_specification")] pub sli_specification: Option, /// A list of tags associated with this service level objective. @@ -90,9 +90,6 @@ pub struct SLOResponseData { /// or updating SLOs. It is only used when querying SLO history over custom timeframes. #[serde(rename = "timeframe")] pub timeframe: Option, - /// The type of the service level objective. - #[serde(rename = "type")] - pub type_: Option, /// The optional warning threshold such that when the service level indicator is /// below this value for the given threshold, but above the target threshold, the /// objective appears in a "warning" state. This value must be greater than the target @@ -125,7 +122,6 @@ impl SLOResponseData { target_threshold: None, thresholds: None, timeframe: None, - type_: None, warning_threshold: None, additional_properties: std::collections::BTreeMap::new(), _unparsed: false, @@ -212,11 +208,6 @@ impl SLOResponseData { self } - pub fn type_(mut self, value: crate::datadogV1::model::SLOType) -> Self { - self.type_ = Some(value); - self - } - pub fn warning_threshold(mut self, value: f64) -> Self { self.warning_threshold = Some(value); self @@ -270,7 +261,6 @@ impl<'de> Deserialize<'de> for SLOResponseData { let mut target_threshold: Option = None; let mut thresholds: Option> = None; let mut timeframe: Option = None; - let mut type_: Option = None; let mut warning_threshold: Option = None; let mut additional_properties: std::collections::BTreeMap< String, @@ -400,20 +390,6 @@ impl<'de> Deserialize<'de> for SLOResponseData { } } } - "type" => { - if v.is_null() { - continue; - } - type_ = Some(serde_json::from_value(v).map_err(M::Error::custom)?); - if let Some(ref _type_) = type_ { - match _type_ { - crate::datadogV1::model::SLOType::UnparsedObject(_type_) => { - _unparsed = true; - } - _ => {} - } - } - } "warning_threshold" => { if v.is_null() { continue; @@ -446,7 +422,6 @@ impl<'de> Deserialize<'de> for SLOResponseData { target_threshold, thresholds, timeframe, - type_, warning_threshold, additional_properties, _unparsed, diff --git a/src/datadogV1/model/model_slo_sli_spec.rs b/src/datadogV1/model/model_slo_sli_spec.rs index 12687266a..342e81bde 100644 --- a/src/datadogV1/model/model_slo_sli_spec.rs +++ b/src/datadogV1/model/model_slo_sli_spec.rs @@ -3,12 +3,13 @@ // Copyright 2019-Present Datadog, Inc. use serde::{Deserialize, Deserializer, Serialize}; -/// A generic SLI specification. This is currently used for time-slice SLOs only. +/// A generic SLI specification. This is currently used for time-slice and count-based SLOs only. #[non_exhaustive] #[derive(Clone, Debug, PartialEq, Serialize)] #[serde(untagged)] pub enum SLOSliSpec { SLOTimeSliceSpec(Box), + SLOCountSpec(Box), UnparsedObject(crate::datadog::UnparsedObject), } @@ -25,6 +26,13 @@ impl<'de> Deserialize<'de> for SLOSliSpec { return Ok(SLOSliSpec::SLOTimeSliceSpec(_v)); } } + if let Ok(_v) = + serde_json::from_value::>(value.clone()) + { + if !_v._unparsed { + return Ok(SLOSliSpec::SLOCountSpec(_v)); + } + } return Ok(SLOSliSpec::UnparsedObject(crate::datadog::UnparsedObject { value, diff --git a/tests/scenarios/features/v1/service_level_objectives.feature b/tests/scenarios/features/v1/service_level_objectives.feature index c5534618a..3fba149b2 100644 --- a/tests/scenarios/features/v1/service_level_objectives.feature +++ b/tests/scenarios/features/v1/service_level_objectives.feature @@ -48,6 +48,30 @@ Feature: Service Level Objectives When the request is sent Then the response status is 200 OK + @team:DataDog/slo-app + Scenario: Create a metric SLO with both sli_specification and query returns "Bad Request" response + Given new "CreateSLO" request + And body with value {"type":"metric","description":"Invalid SLO with both sli_specification and query","name":"{{ unique }}","sli_specification":{"count":{"good_events_formula":{"formula":"query1"},"total_events_formula":{"formula":"query2"},"queries":[{"data_source":"metrics","name":"query1","query":"sum:httpservice.success{*}.as_count()"},{"data_source":"metrics","name":"query2","query":"sum:httpservice.hits{*}.as_count()"}]}},"query":{"numerator":"sum:httpservice.success{*}.as_count()","denominator":"sum:httpservice.hits{*}.as_count()"},"tags":["env:prod"],"thresholds":[{"target":99.0,"target_display":"99.0","timeframe":"7d","warning":98,"warning_display":"98.0"}],"timeframe":"7d","target_threshold":99.0,"warning_threshold":98} + When the request is sent + Then the response status is 400 Bad Request + + @team:DataDog/slo-app + Scenario: Create a new metric SLO object using sli_specification returns "OK" response + Given new "CreateSLO" request + And body with value {"type":"metric","description":"Metric SLO using sli_specification","name":"{{ unique }}","sli_specification":{"count":{"good_events_formula":{"formula":"query1"},"total_events_formula":{"formula":"query2"},"queries":[{"data_source":"metrics","name":"query1","query":"sum:httpservice.success{*}.as_count()"},{"data_source":"metrics","name":"query2","query":"sum:httpservice.hits{*}.as_count()"}]}},"tags":["env:prod","type:count"],"thresholds":[{"target":99.0,"target_display":"99.0","timeframe":"7d","warning":98,"warning_display":"98.0"}],"timeframe":"7d","target_threshold":99.0,"warning_threshold":98} + When the request is sent + Then the response status is 200 OK + And the response "data[0].timeframe" is equal to "7d" + And the response "data[0].target_threshold" is equal to 99.0 + And the response "data[0].warning_threshold" is equal to 98.0 + And the response "data[0]" has field "sli_specification" + And the response "data[0].sli_specification" has field "count" + And the response "data[0].sli_specification.count" has field "good_events_formula" + And the response "data[0].sli_specification.count" has field "total_events_formula" + And the response "data[0].sli_specification.count" has field "queries" + And the response "data[0].sli_specification.count.queries" has length 2 + And the response "data[0]" does not have field "query" + @team:DataDog/slo-app Scenario: Create a time-slice SLO object returns "OK" response Given new "CreateSLO" request @@ -74,6 +98,8 @@ Feature: Service Level Objectives And the response "data[0].timeframe" is equal to "7d" And the response "data[0].target_threshold" is equal to 97.0 And the response "data[0].warning_threshold" is equal to 98.0 + And the response "data[0]" has field "query" + And the response "data[0]" does not have field "sli_specification" @generated @skip @team:DataDog/slo-app Scenario: Delete an SLO returns "Conflict" response