apisix_admin_client/models/
admin_upstream_requests.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use strum_macros::{Display, EnumString};
4use crate::models::generate_identifier;
5use crate::{Result};
6use crate::models::common::ApisixTimeout;
7
8#[serde_with::skip_serializing_none]
9#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub struct UpstreamBuilder {
11    pub id: Option<String>,
12    pub retries: Option<i32>,
13    pub retry_timeout: Option<i32>,
14    pub timeout: Option<ApisixTimeout>,
15    pub nodes: Option<Value>,
16    pub service_name: Option<String>,
17    pub discovery_type: Option<String>,
18    #[serde(rename = "type")]
19    pub type_field: Option<UpstreamType>,
20    pub name: Option<String>,
21    pub desc: Option<String>,
22    pub scheme: Option<UpstreamSchema>,
23}
24
25impl UpstreamBuilder {
26    pub fn new() -> Self {
27        UpstreamRequest::default().into()
28    }
29
30    /// Upstream ID
31    /// TODO validate id See [generate_identifier]
32    pub fn with_id(mut self, id: String) -> Self {
33        self.id = Some(id);
34        self
35    }
36
37    /// Load balancing algorithm to be used, and the default value is roundrobin.
38    /// See [UpstreamType]
39    pub fn with_u_type(mut self, u_type: UpstreamType) -> Self {
40        self.type_field = Some(u_type);
41        self
42    }
43
44    /// IP addresses (with optional ports) of the Upstream nodes represented as a hash table or an array.
45    /// In the hash table, the key is the IP address and the value is the weight of the node for the load balancing algorithm.
46    /// For hash table case, if the key is IPv6 address with port, then the IPv6 address must be quoted with square brackets.
47    /// In the array, each item is a hash table with keys host, weight, and the optional port and priority (defaults to 0).
48    /// Nodes with lower priority are used only when all nodes with a higher priority are tried and are unavailable.
49    /// Empty nodes are treated as placeholders and clients trying to access this Upstream will receive a 502 response.
50    ///
51    /// Restrictions: can not be used with `service_name`
52    ///
53    /// Example: `192.168.1.100:80`, `[::1]:80`
54    pub fn with_nodes(mut self, nodes: Value) -> Self {
55        self.nodes = Some(nodes);
56        self
57    }
58
59    /// Service name used for service discovery
60    ///
61    /// Restrictions: can not be used with `nodes`
62    pub fn with_service_name(mut self, service_name: String) -> Self {
63        self.service_name = Some(service_name);
64        self.discovery_type = Some("eureka".to_string()); //default
65        self.nodes = None; //reset nodes when service name is used
66        self
67    }
68
69    /// The type of service discovery to be used. The default value is eureka.
70    /// Required when `service_name` is defined
71    pub fn with_discovery_type(mut self, discovery_type: String) -> Self {
72        self.discovery_type = Some(discovery_type);
73        self
74    }
75
76    /// Sets the number of retries while passing the request to Upstream using the underlying Nginx mechanism.
77    /// Set according to the number of available backend nodes by default.
78    /// Setting this to 0 disables retry.
79    pub fn with_retries(mut self, retries: i32) -> Self {
80        self.retries = Some(retries);
81        self
82    }
83
84    /// Timeout to continue with retries. Setting this to 0 disables the retry timeout.
85    pub fn with_retry_timeout(mut self, retry_timeout: i32) -> Self {
86        self.retry_timeout = Some(retry_timeout);
87        self
88    }
89
90    /// Sets the timeout (in seconds) for connecting to,
91    /// and sending and receiving messages to and from the Upstream.
92    ///
93    /// Example: {"connect": 0.5,"send": 0.5,"read": 0.5}
94    pub fn with_timeout(mut self, timeout: ApisixTimeout) -> Self {
95        self.timeout = Some(timeout);
96        self
97    }
98
99    /// Identifier for the Upstream
100    pub fn with_name(mut self, name: String) -> Self {
101        self.name = Some(name);
102        self
103    }
104
105    /// Description of usage scenarios
106    pub fn with_desc(mut self, desc: String) -> Self {
107        self.desc = Some(desc);
108        self
109    }
110
111    /// The scheme used when communicating with the Upstream.
112    /// For an L7 proxy, this value can be one of http, https, grpc, grpcs.
113    /// For an L4 proxy, this value could be one of tcp, udp, tls.
114    /// Defaults to http.
115    pub fn with_schema(mut self, scheme: UpstreamSchema) -> Self {
116        self.scheme = Some(scheme);
117        self
118    }
119
120    pub fn build(&self) -> Result<UpstreamRequest> {
121        Ok(UpstreamRequest {
122            id: self.id.clone(),
123            retries: self.retries,
124            retry_timeout: self.retry_timeout,
125            timeout: self.timeout.clone(),
126            nodes: self.nodes.clone(),
127            service_name: self.service_name.clone(),
128            discovery_type: self.discovery_type.clone(),
129            type_field: self.type_field.clone(),
130            name: self.name.clone(),
131            desc: self.desc.clone(),
132            scheme: self.scheme.clone(),
133        })
134    }
135
136}
137
138// TODO: health checks => Configures the parameters for the health check.
139#[serde_with::skip_serializing_none]
140#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
141pub struct UpstreamRequest {
142    pub id: Option<String>,
143    pub retries: Option<i32>,
144    pub retry_timeout: Option<i32>,
145    pub timeout: Option<ApisixTimeout>,
146    pub nodes: Option<Value>,
147    pub service_name: Option<String>,
148    pub discovery_type: Option<String>,
149    #[serde(rename = "type")]
150    pub type_field: Option<UpstreamType>,
151    pub name: Option<String>,
152    pub desc: Option<String>,
153    pub scheme: Option<UpstreamSchema>,
154}
155
156impl Default for UpstreamRequest {
157    fn default() -> Self {
158        let nodes = r#"
159        {
160            "localhost:9000": 1
161        }"#;
162        let nodes = serde_json::from_str(nodes).unwrap();
163        UpstreamRequest {
164            id: Some(generate_identifier()),
165            retries: Some(0_i32), //disabled by default
166            retry_timeout: Some(0_i32),
167            timeout: None,
168            nodes: Some(nodes),
169            service_name: None,
170            discovery_type: None,
171            type_field: None,
172            name: None,
173            desc: None,
174            scheme: Some(UpstreamSchema::http),
175        }
176    }
177}
178
179impl From<UpstreamRequest> for UpstreamBuilder {
180    fn from(upstream: UpstreamRequest) -> Self {
181        UpstreamBuilder {
182            id: upstream.id,
183            retries: upstream.retries,
184            retry_timeout: upstream.retry_timeout,
185            timeout: upstream.timeout,
186            nodes: upstream.nodes,
187            service_name: upstream.service_name,
188            discovery_type: upstream.discovery_type,
189            type_field: upstream.type_field,
190            name: upstream.name,
191            desc: upstream.desc,
192            scheme: upstream.scheme,
193        }
194    }
195}
196
197#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Display, EnumString)]
198#[allow(non_camel_case_types)]
199#[strum(ascii_case_insensitive)]
200#[non_exhaustive]
201pub enum UpstreamType {
202    roundrobin,
203    chash,
204    ewma,
205    least_conn,
206}
207
208#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Display, EnumString)]
209#[allow(non_camel_case_types)]
210#[strum(ascii_case_insensitive)]
211#[non_exhaustive]
212pub enum UpstreamTypeChashAuxiliary {
213    vars,
214    header,
215    cookie,
216    consumer,
217}
218
219impl From<String> for UpstreamTypeChashAuxiliary {
220    fn from(value: String) -> Self {
221        match value.to_uppercase().as_str() {
222            "VARS" => UpstreamTypeChashAuxiliary::vars,
223            "HEADER" => UpstreamTypeChashAuxiliary::header,
224            "COOKIE" => UpstreamTypeChashAuxiliary::cookie,
225            "CONSUMER" => UpstreamTypeChashAuxiliary::consumer,
226            _ => UpstreamTypeChashAuxiliary::vars
227        }
228    }
229}
230
231#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Display, EnumString)]
232#[allow(non_camel_case_types)]
233#[strum(ascii_case_insensitive)]
234#[non_exhaustive]
235pub enum UpstreamSchema {
236    http,
237    https,
238    grpc,
239    grpcs,
240    tcp,
241    udp,
242    tls
243}
244
245// region: tests
246#[cfg(test)]
247mod tests {
248    use serde_json::{to_string, to_string_pretty};
249    use super::*;
250    use tracing::{error, info};
251    use tracing_test::traced_test;
252    use crate::models::admin_upstream_requests::UpstreamType;
253
254    #[traced_test]
255    #[tokio::test]
256    async fn test_generate_upstream_request() {
257        let nodes = r#"
258        {
259            "localhost:9000": 1
260        }"#;
261        let nodes = serde_json::from_str(nodes).unwrap();
262
263        let upstream_req = UpstreamBuilder::new()
264            .with_id("test_upstream".to_string())
265            .with_name("Test Upstream".to_string())
266            .with_desc("Test Upstream Description".to_string())
267            .with_schema(UpstreamSchema::https)
268            .with_u_type(UpstreamType::roundrobin)
269            .with_nodes(nodes)
270            .with_retries(3)
271            .with_retry_timeout(5)
272            .with_timeout(ApisixTimeout { connect: Some(0.5), send: Some(0.5), read: Some(0.5) })
273            .build().unwrap();
274        info!("Upstream Request: {:?}", to_string(&upstream_req));
275        assert!(true)
276    }
277}
278// endregion: tests