Skip to main content

openstack_sdk_load_balancer/v2/pool/member/
create.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//
5//     http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12//
13// SPDX-License-Identifier: Apache-2.0
14//
15// WARNING: This file is automatically generated from OpenAPI schema using
16// `openstack-codegenerator`.
17
18//! This operation provisions a member and adds it to a pool by using the
19//! configuration that you define in the request object. After the API
20//! validates the request and starts the provisioning process, it returns a
21//! response object, which contains a unique ID.
22//!
23//! In the response, the member [provisioning status](#prov-status) is
24//! `ACTIVE`, `PENDING_CREATE`, or `ERROR`.
25//!
26//! If the status is `PENDING_CREATE`, issue GET
27//! `/v2/lbaas/pools/{pool_id}/members/{member_id}` to view the progress of the
28//! provisioning operation. When the member status changes to `ACTIVE`, the
29//! member is successfully provisioned and is ready for further configuration.
30//!
31//! If the API cannot fulfill the request due to insufficient data or data that
32//! is not valid, the service returns the HTTP `Bad Request (400)` response
33//! code with information about the failure in the response body. Validation
34//! errors require that you correct the error and submit the request again.
35//!
36//! At a minimum, you must specify these member attributes:
37//!
38//! Some attributes receive default values if you omit them from the request:
39//!
40//! If you omit the `subnet_id` parameter, the `vip_subnet_id` for the parent
41//! load balancer will be used for the member subnet UUID.
42//!
43//! The member `address` does not necessarily need to be a member of the
44//! `subnet_id` subnet. Members can be routable from the subnet specified
45//! either via the default route or by using `host_routes` defined on the
46//! subnet.
47//!
48//! Administrative users can specify a project ID that is different than their
49//! own to create members for other projects.
50//!
51//! `monitor_address` and/or `monitor_port` can be used to have the health
52//! monitor, if one is configured for the pool, connect to an alternate IP
53//! address and port when executing a health check on the member.
54//!
55//! To create a member, the load balancer must have an `ACTIVE` provisioning
56//! status.
57//!
58use derive_builder::Builder;
59use http::{HeaderMap, HeaderName, HeaderValue};
60
61use openstack_sdk_core::api::rest_endpoint_prelude::*;
62
63use serde::Deserialize;
64use serde::Serialize;
65use std::borrow::Cow;
66
67/// Defines mandatory and optional attributes of a POST request.
68#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
69#[builder(setter(strip_option))]
70pub struct Member<'a> {
71    /// The IP address of the resource.
72    #[serde()]
73    #[builder(setter(into))]
74    pub(crate) address: Cow<'a, str>,
75
76    /// The administrative state of the resource, which is up (`true`) or down
77    /// (`false`). Default is `true`.
78    #[serde(skip_serializing_if = "Option::is_none")]
79    #[builder(default, setter(into))]
80    pub(crate) admin_state_up: Option<bool>,
81
82    /// Is the member a backup? Backup members only receive traffic when all
83    /// non-backup members are down.
84    ///
85    /// **New in version 2.1**
86    #[serde(skip_serializing_if = "Option::is_none")]
87    #[builder(default, setter(into))]
88    pub(crate) backup: Option<bool>,
89
90    /// An alternate IP address used for health monitoring a backend member.
91    /// Default is `null` which monitors the member `address`.
92    #[serde(skip_serializing_if = "Option::is_none")]
93    #[builder(default, setter(into))]
94    pub(crate) monitor_address: Option<Cow<'a, str>>,
95
96    /// An alternate protocol port used for health monitoring a backend member.
97    /// Default is `null` which monitors the member `protocol_port`.
98    #[serde(skip_serializing_if = "Option::is_none")]
99    #[builder(default, setter(into))]
100    pub(crate) monitor_port: Option<i32>,
101
102    /// Human-readable name of the resource.
103    #[serde(skip_serializing_if = "Option::is_none")]
104    #[builder(default, setter(into))]
105    pub(crate) name: Option<Cow<'a, str>>,
106
107    /// The ID of the project owning this resource. (deprecated)
108    #[serde(skip_serializing_if = "Option::is_none")]
109    #[builder(default, setter(into))]
110    pub(crate) project_id: Option<Cow<'a, str>>,
111
112    /// The protocol port number for the resource.
113    #[serde()]
114    #[builder(setter(into))]
115    pub(crate) protocol_port: i32,
116
117    /// Request that an SR-IOV VF be used for the member network port. Defaults
118    /// to `false`.
119    ///
120    /// **New in version 2.29**
121    #[serde(skip_serializing_if = "Option::is_none")]
122    #[builder(default, setter(into))]
123    pub(crate) request_sriov: Option<bool>,
124
125    /// The subnet ID the member service is accessible from.
126    #[serde(skip_serializing_if = "Option::is_none")]
127    #[builder(default, setter(into))]
128    pub(crate) subnet_id: Option<Cow<'a, str>>,
129
130    /// A list of simple strings assigned to the resource.
131    ///
132    /// **New in version 2.5**
133    #[serde(skip_serializing_if = "Option::is_none")]
134    #[builder(default, setter(into))]
135    pub(crate) tags: Option<Vec<Cow<'a, str>>>,
136
137    #[serde(skip_serializing_if = "Option::is_none")]
138    #[builder(default, setter(into))]
139    pub(crate) tenant_id: Option<Cow<'a, str>>,
140
141    /// The weight of a member determines the portion of requests or
142    /// connections it services compared to the other members of the pool. For
143    /// example, a member with a weight of 10 receives five times as many
144    /// requests as a member with a weight of 2. A value of 0 means the member
145    /// does not receive new connections but continues to service existing
146    /// connections. A valid value is from `0` to `256`. Default is `1`.
147    #[serde(skip_serializing_if = "Option::is_none")]
148    #[builder(default, setter(into))]
149    pub(crate) weight: Option<i32>,
150}
151
152#[derive(Builder, Debug, Clone)]
153#[builder(setter(strip_option))]
154pub struct Request<'a> {
155    /// Defines mandatory and optional attributes of a POST request.
156    #[builder(setter(into))]
157    pub(crate) member: Member<'a>,
158
159    /// pool_id parameter for /v2/lbaas/pools/{pool_id}/members/{member_id} API
160    #[builder(default, setter(into))]
161    pool_id: Cow<'a, str>,
162
163    #[builder(setter(name = "_headers"), default, private)]
164    _headers: Option<HeaderMap>,
165}
166impl<'a> Request<'a> {
167    /// Create a builder for the endpoint.
168    pub fn builder() -> RequestBuilder<'a> {
169        RequestBuilder::default()
170    }
171}
172
173impl<'a> RequestBuilder<'a> {
174    /// Add a single header to the Member.
175    pub fn header<K, V>(&mut self, header_name: K, header_value: V) -> &mut Self
176    where
177        K: Into<HeaderName>,
178        V: Into<HeaderValue>,
179    {
180        self._headers
181            .get_or_insert(None)
182            .get_or_insert_with(HeaderMap::new)
183            .insert(header_name.into(), header_value.into());
184        self
185    }
186
187    /// Add multiple headers.
188    pub fn headers<I, T>(&mut self, iter: I) -> &mut Self
189    where
190        I: Iterator<Item = T>,
191        T: Into<(Option<HeaderName>, HeaderValue)>,
192    {
193        self._headers
194            .get_or_insert(None)
195            .get_or_insert_with(HeaderMap::new)
196            .extend(iter.map(Into::into));
197        self
198    }
199}
200
201impl RestEndpoint for Request<'_> {
202    fn method(&self) -> http::Method {
203        http::Method::POST
204    }
205
206    fn endpoint(&self) -> Cow<'static, str> {
207        format!(
208            "lbaas/pools/{pool_id}/members",
209            pool_id = self.pool_id.as_ref(),
210        )
211        .into()
212    }
213
214    fn parameters(&self) -> QueryParams<'_> {
215        QueryParams::default()
216    }
217
218    fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
219        let mut params = JsonBodyParams::default();
220
221        params.push("member", serde_json::to_value(&self.member)?);
222
223        params.into_body()
224    }
225
226    fn service_type(&self) -> ServiceType {
227        ServiceType::LoadBalancer
228    }
229
230    fn response_key(&self) -> Option<Cow<'static, str>> {
231        Some("member".into())
232    }
233
234    /// Returns headers to be set into the request
235    fn request_headers(&self) -> Option<&HeaderMap> {
236        self._headers.as_ref()
237    }
238
239    /// Returns required API version
240    fn api_version(&self) -> Option<ApiVersion> {
241        Some(ApiVersion::new(2, 0))
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248    use http::{HeaderName, HeaderValue};
249    use httpmock::MockServer;
250    #[cfg(feature = "sync")]
251    use openstack_sdk_core::api::Query;
252    use openstack_sdk_core::test::client::FakeOpenStackClient;
253    use openstack_sdk_core::types::ServiceType;
254    use serde_json::json;
255
256    #[test]
257    fn test_service_type() {
258        assert_eq!(
259            Request::builder()
260                .member(
261                    MemberBuilder::default()
262                        .address("foo")
263                        .protocol_port(123)
264                        .build()
265                        .unwrap()
266                )
267                .build()
268                .unwrap()
269                .service_type(),
270            ServiceType::LoadBalancer
271        );
272    }
273
274    #[test]
275    fn test_response_key() {
276        assert_eq!(
277            Request::builder()
278                .member(
279                    MemberBuilder::default()
280                        .address("foo")
281                        .protocol_port(123)
282                        .build()
283                        .unwrap()
284                )
285                .build()
286                .unwrap()
287                .response_key()
288                .unwrap(),
289            "member"
290        );
291    }
292
293    #[cfg(feature = "sync")]
294    #[test]
295    fn endpoint() {
296        let server = MockServer::start();
297        let client = FakeOpenStackClient::new(server.base_url());
298        let mock = server.mock(|when, then| {
299            when.method(httpmock::Method::POST).path(format!(
300                "/lbaas/pools/{pool_id}/members",
301                pool_id = "pool_id",
302            ));
303
304            then.status(200)
305                .header("content-type", "application/json")
306                .json_body(json!({ "member": {} }));
307        });
308
309        let endpoint = Request::builder()
310            .pool_id("pool_id")
311            .member(
312                MemberBuilder::default()
313                    .address("foo")
314                    .protocol_port(123)
315                    .build()
316                    .unwrap(),
317            )
318            .build()
319            .unwrap();
320        let _: serde_json::Value = endpoint.query(&client).unwrap();
321        mock.assert();
322    }
323
324    #[cfg(feature = "sync")]
325    #[test]
326    fn endpoint_headers() {
327        let server = MockServer::start();
328        let client = FakeOpenStackClient::new(server.base_url());
329        let mock = server.mock(|when, then| {
330            when.method(httpmock::Method::POST)
331                .path(format!(
332                    "/lbaas/pools/{pool_id}/members",
333                    pool_id = "pool_id",
334                ))
335                .header("foo", "bar")
336                .header("not_foo", "not_bar");
337            then.status(200)
338                .header("content-type", "application/json")
339                .json_body(json!({ "member": {} }));
340        });
341
342        let endpoint = Request::builder()
343            .pool_id("pool_id")
344            .member(
345                MemberBuilder::default()
346                    .address("foo")
347                    .protocol_port(123)
348                    .build()
349                    .unwrap(),
350            )
351            .headers(
352                [(
353                    Some(HeaderName::from_static("foo")),
354                    HeaderValue::from_static("bar"),
355                )]
356                .into_iter(),
357            )
358            .header(
359                HeaderName::from_static("not_foo"),
360                HeaderValue::from_static("not_bar"),
361            )
362            .build()
363            .unwrap();
364        let _: serde_json::Value = endpoint.query(&client).unwrap();
365        mock.assert();
366    }
367}