1use derive_builder::Builder;
57use http::{HeaderMap, HeaderName, HeaderValue};
58
59use crate::api::rest_endpoint_prelude::*;
60
61use serde::Deserialize;
62use serde::Serialize;
63use std::borrow::Cow;
64
65#[derive(Debug, Deserialize, Clone, Serialize)]
66pub enum Protocol {
67 #[serde(rename = "HTTP")]
68 Http,
69 #[serde(rename = "HTTPS")]
70 Https,
71 #[serde(rename = "PROXY")]
72 Proxy,
73 #[serde(rename = "PROXYV2")]
74 Proxyv2,
75 #[serde(rename = "SCTP")]
76 Sctp,
77 #[serde(rename = "TCP")]
78 Tcp,
79 #[serde(rename = "UDP")]
80 Udp,
81}
82
83#[derive(Debug, Deserialize, Clone, Serialize)]
84pub enum LbAlgorithm {
85 #[serde(rename = "LEAST_CONNECTIONS")]
86 LeastConnections,
87 #[serde(rename = "ROUND_ROBIN")]
88 RoundRobin,
89 #[serde(rename = "SOURCE_IP")]
90 SourceIp,
91 #[serde(rename = "SOURCE_IP_PORT")]
92 SourceIpPort,
93}
94
95#[derive(Debug, Deserialize, Clone, Serialize)]
96pub enum Type {
97 #[serde(rename = "APP_COOKIE")]
98 AppCookie,
99 #[serde(rename = "HTTP_COOKIE")]
100 HttpCookie,
101 #[serde(rename = "SOURCE_IP")]
102 SourceIp,
103}
104
105#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
109#[builder(setter(strip_option))]
110pub struct SessionPersistence<'a> {
111 #[serde(skip_serializing_if = "Option::is_none")]
112 #[builder(default, setter(into))]
113 pub(crate) cookie_name: Option<Cow<'a, str>>,
114
115 #[serde(skip_serializing_if = "Option::is_none")]
116 #[builder(default, setter(into))]
117 pub(crate) persistence_granularity: Option<Cow<'a, str>>,
118
119 #[serde(skip_serializing_if = "Option::is_none")]
120 #[builder(default, setter(into))]
121 pub(crate) persistence_timeout: Option<i32>,
122
123 #[serde(rename = "type")]
124 #[builder()]
125 pub(crate) _type: Type,
126}
127
128#[derive(Debug, Deserialize, Clone, Serialize)]
129pub enum HealthmonitorType {
130 #[serde(rename = "HTTP")]
131 Http,
132 #[serde(rename = "HTTPS")]
133 Https,
134 #[serde(rename = "PING")]
135 Ping,
136 #[serde(rename = "SCTP")]
137 Sctp,
138 #[serde(rename = "TCP")]
139 Tcp,
140 #[serde(rename = "TLS-HELLO")]
141 TlsHello,
142 #[serde(rename = "UDP-CONNECT")]
143 UdpConnect,
144}
145
146#[derive(Debug, Deserialize, Clone, Serialize)]
147pub enum HttpMethod {
148 #[serde(rename = "CONNECT")]
149 Connect,
150 #[serde(rename = "DELETE")]
151 Delete,
152 #[serde(rename = "GET")]
153 Get,
154 #[serde(rename = "HEAD")]
155 Head,
156 #[serde(rename = "OPTIONS")]
157 Options,
158 #[serde(rename = "PATCH")]
159 Patch,
160 #[serde(rename = "POST")]
161 Post,
162 #[serde(rename = "PUT")]
163 Put,
164 #[serde(rename = "TRACE")]
165 Trace,
166}
167
168#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
170#[builder(setter(strip_option))]
171pub struct Healthmonitor<'a> {
172 #[serde(skip_serializing_if = "Option::is_none")]
173 #[builder(default, setter(into))]
174 pub(crate) admin_state_up: Option<bool>,
175
176 #[serde()]
177 #[builder(setter(into))]
178 pub(crate) delay: i32,
179
180 #[serde(skip_serializing_if = "Option::is_none")]
181 #[builder(default, setter(into))]
182 pub(crate) domain_name: Option<Cow<'a, str>>,
183
184 #[serde(skip_serializing_if = "Option::is_none")]
185 #[builder(default, setter(into))]
186 pub(crate) expected_codes: Option<Cow<'a, str>>,
187
188 #[serde(skip_serializing_if = "Option::is_none")]
189 #[builder(default)]
190 pub(crate) http_method: Option<HttpMethod>,
191
192 #[serde(skip_serializing_if = "Option::is_none")]
193 #[builder(default, setter(into))]
194 pub(crate) http_version: Option<f32>,
195
196 #[serde()]
197 #[builder(setter(into))]
198 pub(crate) max_retries: i32,
199
200 #[serde(skip_serializing_if = "Option::is_none")]
201 #[builder(default, setter(into))]
202 pub(crate) max_retries_down: Option<i32>,
203
204 #[serde(skip_serializing_if = "Option::is_none")]
205 #[builder(default, setter(into))]
206 pub(crate) name: Option<Cow<'a, str>>,
207
208 #[serde(skip_serializing_if = "Option::is_none")]
212 #[builder(default, setter(into))]
213 pub(crate) tags: Option<Vec<Cow<'a, str>>>,
214
215 #[serde()]
216 #[builder(setter(into))]
217 pub(crate) timeout: i32,
218
219 #[serde(rename = "type")]
220 #[builder()]
221 pub(crate) _type: HealthmonitorType,
222
223 #[serde(skip_serializing_if = "Option::is_none")]
224 #[builder(default, setter(into))]
225 pub(crate) url_path: Option<Cow<'a, str>>,
226}
227
228#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
230#[builder(setter(strip_option))]
231pub struct Members<'a> {
232 #[serde()]
233 #[builder(setter(into))]
234 pub(crate) address: Cow<'a, str>,
235
236 #[serde(skip_serializing_if = "Option::is_none")]
237 #[builder(default, setter(into))]
238 pub(crate) admin_state_up: Option<bool>,
239
240 #[serde(skip_serializing_if = "Option::is_none")]
241 #[builder(default, setter(into))]
242 pub(crate) backup: Option<bool>,
243
244 #[serde(skip_serializing_if = "Option::is_none")]
245 #[builder(default, setter(into))]
246 pub(crate) monitor_address: Option<Cow<'a, str>>,
247
248 #[serde(skip_serializing_if = "Option::is_none")]
249 #[builder(default, setter(into))]
250 pub(crate) monitor_port: Option<i32>,
251
252 #[serde(skip_serializing_if = "Option::is_none")]
253 #[builder(default, setter(into))]
254 pub(crate) name: Option<Cow<'a, str>>,
255
256 #[serde()]
257 #[builder(setter(into))]
258 pub(crate) protocol_port: i32,
259
260 #[serde(skip_serializing_if = "Option::is_none")]
261 #[builder(default, setter(into))]
262 pub(crate) request_sriov: Option<bool>,
263
264 #[serde(skip_serializing_if = "Option::is_none")]
265 #[builder(default, setter(into))]
266 pub(crate) subnet_id: Option<Cow<'a, str>>,
267
268 #[serde(skip_serializing_if = "Option::is_none")]
269 #[builder(default, setter(into))]
270 pub(crate) tags: Option<Vec<Cow<'a, str>>>,
271
272 #[serde(skip_serializing_if = "Option::is_none")]
273 #[builder(default, setter(into))]
274 pub(crate) weight: Option<i32>,
275}
276
277#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
279#[builder(setter(strip_option))]
280pub struct Pool<'a> {
281 #[serde(skip_serializing_if = "Option::is_none")]
284 #[builder(default, setter(into))]
285 pub(crate) admin_state_up: Option<bool>,
286
287 #[serde(skip_serializing_if = "Option::is_none")]
291 #[builder(default, setter(into))]
292 pub(crate) alpn_protocols: Option<Vec<Cow<'a, str>>>,
293
294 #[serde(skip_serializing_if = "Option::is_none")]
301 #[builder(default, setter(into))]
302 pub(crate) ca_tls_container_ref: Option<Cow<'a, str>>,
303
304 #[serde(skip_serializing_if = "Option::is_none")]
309 #[builder(default, setter(into))]
310 pub(crate) crl_container_ref: Option<Cow<'a, str>>,
311
312 #[serde(skip_serializing_if = "Option::is_none")]
314 #[builder(default, setter(into))]
315 pub(crate) description: Option<Cow<'a, str>>,
316
317 #[serde(skip_serializing_if = "Option::is_none")]
319 #[builder(default, setter(into))]
320 pub(crate) healthmonitor: Option<Healthmonitor<'a>>,
321
322 #[serde()]
325 #[builder()]
326 pub(crate) lb_algorithm: LbAlgorithm,
327
328 #[serde(skip_serializing_if = "Option::is_none")]
333 #[builder(default, setter(into))]
334 pub(crate) listener_id: Option<Cow<'a, str>>,
335
336 #[serde(skip_serializing_if = "Option::is_none")]
339 #[builder(default, setter(into))]
340 pub(crate) loadbalancer_id: Option<Cow<'a, str>>,
341
342 #[serde(skip_serializing_if = "Option::is_none")]
343 #[builder(default, setter(into))]
344 pub(crate) members: Option<Vec<Members<'a>>>,
345
346 #[serde(skip_serializing_if = "Option::is_none")]
348 #[builder(default, setter(into))]
349 pub(crate) name: Option<Cow<'a, str>>,
350
351 #[serde(skip_serializing_if = "Option::is_none")]
353 #[builder(default, setter(into))]
354 pub(crate) project_id: Option<Cow<'a, str>>,
355
356 #[serde()]
359 #[builder()]
360 pub(crate) protocol: Protocol,
361
362 #[serde(skip_serializing_if = "Option::is_none")]
366 #[builder(default, setter(into))]
367 pub(crate) session_persistence: Option<SessionPersistence<'a>>,
368
369 #[serde(skip_serializing_if = "Option::is_none")]
370 #[builder(default, setter(into))]
371 pub(crate) tags: Option<Vec<Cow<'a, str>>>,
372
373 #[serde(skip_serializing_if = "Option::is_none")]
374 #[builder(default, setter(into))]
375 pub(crate) tenant_id: Option<Cow<'a, str>>,
376
377 #[serde(skip_serializing_if = "Option::is_none")]
382 #[builder(default, setter(into))]
383 pub(crate) tls_ciphers: Option<Cow<'a, str>>,
384
385 #[serde(skip_serializing_if = "Option::is_none")]
393 #[builder(default, setter(into))]
394 pub(crate) tls_container_ref: Option<Cow<'a, str>>,
395
396 #[serde(skip_serializing_if = "Option::is_none")]
401 #[builder(default, setter(into))]
402 pub(crate) tls_enabled: Option<bool>,
403
404 #[serde(skip_serializing_if = "Option::is_none")]
409 #[builder(default, setter(into))]
410 pub(crate) tls_versions: Option<Vec<Cow<'a, str>>>,
411}
412
413#[derive(Builder, Debug, Clone)]
414#[builder(setter(strip_option))]
415pub struct Request<'a> {
416 #[builder(setter(into))]
418 pub(crate) pool: Pool<'a>,
419
420 #[builder(setter(name = "_headers"), default, private)]
421 _headers: Option<HeaderMap>,
422}
423impl<'a> Request<'a> {
424 pub fn builder() -> RequestBuilder<'a> {
426 RequestBuilder::default()
427 }
428}
429
430impl RequestBuilder<'_> {
431 pub fn header(&mut self, header_name: &'static str, header_value: &'static str) -> &mut Self
433where {
434 self._headers
435 .get_or_insert(None)
436 .get_or_insert_with(HeaderMap::new)
437 .insert(header_name, HeaderValue::from_static(header_value));
438 self
439 }
440
441 pub fn headers<I, T>(&mut self, iter: I) -> &mut Self
443 where
444 I: Iterator<Item = T>,
445 T: Into<(Option<HeaderName>, HeaderValue)>,
446 {
447 self._headers
448 .get_or_insert(None)
449 .get_or_insert_with(HeaderMap::new)
450 .extend(iter.map(Into::into));
451 self
452 }
453}
454
455impl RestEndpoint for Request<'_> {
456 fn method(&self) -> http::Method {
457 http::Method::POST
458 }
459
460 fn endpoint(&self) -> Cow<'static, str> {
461 "lbaas/pools".to_string().into()
462 }
463
464 fn parameters(&self) -> QueryParams {
465 QueryParams::default()
466 }
467
468 fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
469 let mut params = JsonBodyParams::default();
470
471 params.push("pool", serde_json::to_value(&self.pool)?);
472
473 params.into_body()
474 }
475
476 fn service_type(&self) -> ServiceType {
477 ServiceType::LoadBalancer
478 }
479
480 fn response_key(&self) -> Option<Cow<'static, str>> {
481 Some("pool".into())
482 }
483
484 fn request_headers(&self) -> Option<&HeaderMap> {
486 self._headers.as_ref()
487 }
488
489 fn api_version(&self) -> Option<ApiVersion> {
491 Some(ApiVersion::new(2, 0))
492 }
493}
494
495#[cfg(test)]
496mod tests {
497 use super::*;
498 #[cfg(feature = "sync")]
499 use crate::api::Query;
500 use crate::test::client::FakeOpenStackClient;
501 use crate::types::ServiceType;
502 use http::{HeaderName, HeaderValue};
503 use httpmock::MockServer;
504 use serde_json::json;
505
506 #[test]
507 fn test_service_type() {
508 assert_eq!(
509 Request::builder()
510 .pool(
511 PoolBuilder::default()
512 .lb_algorithm(LbAlgorithm::LeastConnections)
513 .protocol(Protocol::Http)
514 .build()
515 .unwrap()
516 )
517 .build()
518 .unwrap()
519 .service_type(),
520 ServiceType::LoadBalancer
521 );
522 }
523
524 #[test]
525 fn test_response_key() {
526 assert_eq!(
527 Request::builder()
528 .pool(
529 PoolBuilder::default()
530 .lb_algorithm(LbAlgorithm::LeastConnections)
531 .protocol(Protocol::Http)
532 .build()
533 .unwrap()
534 )
535 .build()
536 .unwrap()
537 .response_key()
538 .unwrap(),
539 "pool"
540 );
541 }
542
543 #[cfg(feature = "sync")]
544 #[test]
545 fn endpoint() {
546 let server = MockServer::start();
547 let client = FakeOpenStackClient::new(server.base_url());
548 let mock = server.mock(|when, then| {
549 when.method(httpmock::Method::POST)
550 .path("/lbaas/pools".to_string());
551
552 then.status(200)
553 .header("content-type", "application/json")
554 .json_body(json!({ "pool": {} }));
555 });
556
557 let endpoint = Request::builder()
558 .pool(
559 PoolBuilder::default()
560 .lb_algorithm(LbAlgorithm::LeastConnections)
561 .protocol(Protocol::Http)
562 .build()
563 .unwrap(),
564 )
565 .build()
566 .unwrap();
567 let _: serde_json::Value = endpoint.query(&client).unwrap();
568 mock.assert();
569 }
570
571 #[cfg(feature = "sync")]
572 #[test]
573 fn endpoint_headers() {
574 let server = MockServer::start();
575 let client = FakeOpenStackClient::new(server.base_url());
576 let mock = server.mock(|when, then| {
577 when.method(httpmock::Method::POST)
578 .path("/lbaas/pools".to_string())
579 .header("foo", "bar")
580 .header("not_foo", "not_bar");
581 then.status(200)
582 .header("content-type", "application/json")
583 .json_body(json!({ "pool": {} }));
584 });
585
586 let endpoint = Request::builder()
587 .pool(
588 PoolBuilder::default()
589 .lb_algorithm(LbAlgorithm::LeastConnections)
590 .protocol(Protocol::Http)
591 .build()
592 .unwrap(),
593 )
594 .headers(
595 [(
596 Some(HeaderName::from_static("foo")),
597 HeaderValue::from_static("bar"),
598 )]
599 .into_iter(),
600 )
601 .header("not_foo", "not_bar")
602 .build()
603 .unwrap();
604 let _: serde_json::Value = endpoint.query(&client).unwrap();
605 mock.assert();
606 }
607}