datadog_api_client/datadogV2/api/
api_spa.rs

1// Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License.
2// This product includes software developed at Datadog (https://www.datadoghq.com/).
3// Copyright 2019-Present Datadog, Inc.
4use crate::datadog;
5use log::warn;
6use reqwest::header::{HeaderMap, HeaderValue};
7use serde::{Deserialize, Serialize};
8
9/// GetSPARecommendationsError is a struct for typed errors of method [`SpaAPI::get_spa_recommendations`]
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(untagged)]
12pub enum GetSPARecommendationsError {
13    APIErrorResponse(crate::datadogV2::model::APIErrorResponse),
14    UnknownValue(serde_json::Value),
15}
16
17/// SPA (Spark Pod Autosizing) API. Provides resource recommendations and cost insights to help optimize Spark job configurations.
18#[derive(Debug, Clone)]
19pub struct SpaAPI {
20    config: datadog::Configuration,
21    client: reqwest_middleware::ClientWithMiddleware,
22}
23
24impl Default for SpaAPI {
25    fn default() -> Self {
26        Self::with_config(datadog::Configuration::default())
27    }
28}
29
30impl SpaAPI {
31    pub fn new() -> Self {
32        Self::default()
33    }
34    pub fn with_config(config: datadog::Configuration) -> Self {
35        let mut reqwest_client_builder = reqwest::Client::builder();
36
37        if let Some(proxy_url) = &config.proxy_url {
38            let proxy = reqwest::Proxy::all(proxy_url).expect("Failed to parse proxy URL");
39            reqwest_client_builder = reqwest_client_builder.proxy(proxy);
40        }
41
42        let mut middleware_client_builder =
43            reqwest_middleware::ClientBuilder::new(reqwest_client_builder.build().unwrap());
44
45        if config.enable_retry {
46            struct RetryableStatus;
47            impl reqwest_retry::RetryableStrategy for RetryableStatus {
48                fn handle(
49                    &self,
50                    res: &Result<reqwest::Response, reqwest_middleware::Error>,
51                ) -> Option<reqwest_retry::Retryable> {
52                    match res {
53                        Ok(success) => reqwest_retry::default_on_request_success(success),
54                        Err(_) => None,
55                    }
56                }
57            }
58            let backoff_policy = reqwest_retry::policies::ExponentialBackoff::builder()
59                .build_with_max_retries(config.max_retries);
60
61            let retry_middleware =
62                reqwest_retry::RetryTransientMiddleware::new_with_policy_and_strategy(
63                    backoff_policy,
64                    RetryableStatus,
65                );
66
67            middleware_client_builder = middleware_client_builder.with(retry_middleware);
68        }
69
70        let client = middleware_client_builder.build();
71
72        Self { config, client }
73    }
74
75    pub fn with_client_and_config(
76        config: datadog::Configuration,
77        client: reqwest_middleware::ClientWithMiddleware,
78    ) -> Self {
79        Self { config, client }
80    }
81
82    /// Retrieve resource recommendations for a Spark job. The caller (Spark Gateway or DJM UI) provides a service name and shard identifier, and SPA returns structured recommendations for driver and executor resources.
83    pub async fn get_spa_recommendations(
84        &self,
85        shard: String,
86        service: String,
87    ) -> Result<
88        crate::datadogV2::model::RecommendationDocument,
89        datadog::Error<GetSPARecommendationsError>,
90    > {
91        match self
92            .get_spa_recommendations_with_http_info(shard, service)
93            .await
94        {
95            Ok(response_content) => {
96                if let Some(e) = response_content.entity {
97                    Ok(e)
98                } else {
99                    Err(datadog::Error::Serde(serde::de::Error::custom(
100                        "response content was None",
101                    )))
102                }
103            }
104            Err(err) => Err(err),
105        }
106    }
107
108    /// Retrieve resource recommendations for a Spark job. The caller (Spark Gateway or DJM UI) provides a service name and shard identifier, and SPA returns structured recommendations for driver and executor resources.
109    pub async fn get_spa_recommendations_with_http_info(
110        &self,
111        shard: String,
112        service: String,
113    ) -> Result<
114        datadog::ResponseContent<crate::datadogV2::model::RecommendationDocument>,
115        datadog::Error<GetSPARecommendationsError>,
116    > {
117        let local_configuration = &self.config;
118        let operation_id = "v2.get_spa_recommendations";
119        if local_configuration.is_unstable_operation_enabled(operation_id) {
120            warn!("Using unstable operation {operation_id}");
121        } else {
122            let local_error = datadog::UnstableOperationDisabledError {
123                msg: "Operation 'v2.get_spa_recommendations' is not enabled".to_string(),
124            };
125            return Err(datadog::Error::UnstableOperationDisabledError(local_error));
126        }
127
128        let local_client = &self.client;
129
130        let local_uri_str = format!(
131            "{}/api/v2/spa/recommendations/{service}/{shard}",
132            local_configuration.get_operation_host(operation_id),
133            shard = datadog::urlencode(shard),
134            service = datadog::urlencode(service)
135        );
136        let mut local_req_builder =
137            local_client.request(reqwest::Method::GET, local_uri_str.as_str());
138
139        // build headers
140        let mut headers = HeaderMap::new();
141        headers.insert("Accept", HeaderValue::from_static("application/json"));
142
143        // build user agent
144        match HeaderValue::from_str(local_configuration.user_agent.as_str()) {
145            Ok(user_agent) => headers.insert(reqwest::header::USER_AGENT, user_agent),
146            Err(e) => {
147                log::warn!("Failed to parse user agent header: {e}, falling back to default");
148                headers.insert(
149                    reqwest::header::USER_AGENT,
150                    HeaderValue::from_static(datadog::DEFAULT_USER_AGENT.as_str()),
151                )
152            }
153        };
154
155        // build auth
156        if let Some(local_key) = local_configuration.auth_keys.get("apiKeyAuth") {
157            headers.insert(
158                "DD-API-KEY",
159                HeaderValue::from_str(local_key.key.as_str())
160                    .expect("failed to parse DD-API-KEY header"),
161            );
162        };
163        if let Some(local_key) = local_configuration.auth_keys.get("appKeyAuth") {
164            headers.insert(
165                "DD-APPLICATION-KEY",
166                HeaderValue::from_str(local_key.key.as_str())
167                    .expect("failed to parse DD-APPLICATION-KEY header"),
168            );
169        };
170
171        local_req_builder = local_req_builder.headers(headers);
172        let local_req = local_req_builder.build()?;
173        log::debug!("request content: {:?}", local_req.body());
174        let local_resp = local_client.execute(local_req).await?;
175
176        let local_status = local_resp.status();
177        let local_content = local_resp.text().await?;
178        log::debug!("response content: {}", local_content);
179
180        if !local_status.is_client_error() && !local_status.is_server_error() {
181            match serde_json::from_str::<crate::datadogV2::model::RecommendationDocument>(
182                &local_content,
183            ) {
184                Ok(e) => {
185                    return Ok(datadog::ResponseContent {
186                        status: local_status,
187                        content: local_content,
188                        entity: Some(e),
189                    })
190                }
191                Err(e) => return Err(datadog::Error::Serde(e)),
192            };
193        } else {
194            let local_entity: Option<GetSPARecommendationsError> =
195                serde_json::from_str(&local_content).ok();
196            let local_error = datadog::ResponseContent {
197                status: local_status,
198                content: local_content,
199                entity: local_entity,
200            };
201            Err(datadog::Error::ResponseError(local_error))
202        }
203    }
204}