openstack_sdk/api/network/v2/subnet/
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//! Creates a subnet on a network.
19//!
20//! OpenStack Networking does not try to derive the correct IP version from the
21//! CIDR. If you do not specify the `gateway_ip` attribute, OpenStack
22//! Networking allocates an address from the CIDR for the gateway for the
23//! subnet.
24//!
25//! To specify a subnet without a gateway, set the `gateway_ip` attribute to
26//! `null` in the request body. If you do not specify the `allocation_pools`
27//! attribute, OpenStack Networking automatically allocates pools for covering
28//! all IP addresses in the CIDR, excluding the address reserved for the subnet
29//! gateway. Otherwise, you can explicitly specify allocation pools as shown in
30//! the following example.
31//!
32//! When you specify both the `allocation_pools` and `gateway_ip` attributes,
33//! you must ensure that the gateway IP does not overlap with the allocation
34//! pools; otherwise, the call returns the `Conflict (409)` response code.
35//!
36//! A subnet can have one or more name servers and host routes. Hosts in this
37//! subnet use the name servers. Devices with IP addresses from this subnet,
38//! not including the local subnet route, use the host routes.
39//!
40//! Specify the `ipv6_ra_mode` and `ipv6_address_mode` attributes to create
41//! subnets that support IPv6 configurations, such as stateless address
42//! autoconfiguration (SLAAC), DHCPv6 stateful, and DHCPv6 stateless
43//! configurations.
44//!
45//! A subnet can optionally be associated with a network segment when it is
46//! created by specifying the `segment_id` of a valid segment on the specified
47//! network. A network with subnets associated in this way is called a routed
48//! network. On any given network, all of the subnets must be associated with
49//! segments or none of them can be. Neutron enforces this invariant.
50//! Currently, routed networks are only supported for provider networks.
51//!
52//! Normal response codes: 201
53//!
54//! Error response codes: 400, 401, 403, 404, 409
55//!
56use 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(Builder, Debug, Deserialize, Clone, Serialize)]
66#[builder(setter(strip_option))]
67pub struct AllocationPools<'a> {
68    #[serde(skip_serializing_if = "Option::is_none")]
69    #[builder(default, setter(into))]
70    pub(crate) end: Option<Cow<'a, str>>,
71
72    #[serde(skip_serializing_if = "Option::is_none")]
73    #[builder(default, setter(into))]
74    pub(crate) start: Option<Cow<'a, str>>,
75}
76
77#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
78#[builder(setter(strip_option))]
79pub struct HostRoutes<'a> {
80    #[serde(skip_serializing_if = "Option::is_none")]
81    #[builder(default, setter(into))]
82    pub(crate) destination: Option<Cow<'a, str>>,
83
84    #[serde(skip_serializing_if = "Option::is_none")]
85    #[builder(default, setter(into))]
86    pub(crate) nexthop: Option<Cow<'a, str>>,
87}
88
89#[derive(Debug, Deserialize, Clone, Serialize)]
90pub enum Ipv6AddressMode {
91    #[serde(rename = "dhcpv6-stateful")]
92    Dhcpv6Stateful,
93    #[serde(rename = "dhcpv6-stateless")]
94    Dhcpv6Stateless,
95    #[serde(rename = "slaac")]
96    Slaac,
97}
98
99#[derive(Debug, Deserialize, Clone, Serialize)]
100pub enum Ipv6RaMode {
101    #[serde(rename = "dhcpv6-stateful")]
102    Dhcpv6Stateful,
103    #[serde(rename = "dhcpv6-stateless")]
104    Dhcpv6Stateless,
105    #[serde(rename = "slaac")]
106    Slaac,
107}
108
109/// A `subnet` object.
110#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
111#[builder(setter(strip_option))]
112pub struct Subnet<'a> {
113    /// Allocation pools with `start` and `end` IP addresses for this subnet.
114    /// If allocation_pools are not specified, OpenStack Networking
115    /// automatically allocates pools for covering all IP addresses in the
116    /// CIDR, excluding the address reserved for the subnet gateway by default.
117    #[serde(skip_serializing_if = "Option::is_none")]
118    #[builder(default, setter(into))]
119    pub(crate) allocation_pools: Option<Vec<AllocationPools<'a>>>,
120
121    /// The CIDR of the subnet.
122    #[serde(skip_serializing_if = "Option::is_none")]
123    #[builder(default, setter(into))]
124    pub(crate) cidr: Option<Option<Cow<'a, str>>>,
125
126    /// A human-readable description for the resource. Default is an empty
127    /// string.
128    #[serde(skip_serializing_if = "Option::is_none")]
129    #[builder(default, setter(into))]
130    pub(crate) description: Option<Cow<'a, str>>,
131
132    /// List of dns name servers associated with the subnet. Default is an
133    /// empty list.
134    #[serde(skip_serializing_if = "Option::is_none")]
135    #[builder(default, setter(into))]
136    pub(crate) dns_nameservers: Option<Vec<Cow<'a, str>>>,
137
138    /// Whether to publish DNS records for IPs from this subnet. Default is
139    /// `false`.
140    #[serde(skip_serializing_if = "Option::is_none")]
141    #[builder(default, setter(into))]
142    pub(crate) dns_publish_fixed_ip: Option<bool>,
143
144    /// Indicates whether dhcp is enabled or disabled for the subnet. Default
145    /// is `true`.
146    #[serde(skip_serializing_if = "Option::is_none")]
147    #[builder(default, setter(into))]
148    pub(crate) enable_dhcp: Option<bool>,
149
150    /// Gateway IP of this subnet. If the value is `null` that implies no
151    /// gateway is associated with the subnet. If the gateway_ip is not
152    /// specified, OpenStack Networking allocates an address from the CIDR for
153    /// the gateway for the subnet by default.
154    #[serde(skip_serializing_if = "Option::is_none")]
155    #[builder(default, setter(into))]
156    pub(crate) gateway_ip: Option<Option<Cow<'a, str>>>,
157
158    /// Additional routes for the subnet. A list of dictionaries with
159    /// `destination` and `nexthop` parameters. Default value is an empty list.
160    #[serde(skip_serializing_if = "Option::is_none")]
161    #[builder(default, setter(into))]
162    pub(crate) host_routes: Option<Vec<HostRoutes<'a>>>,
163
164    /// The IP protocol version. Value is `4` or `6`.
165    #[serde()]
166    #[builder(setter(into))]
167    pub(crate) ip_version: i32,
168
169    /// The IPv6 address modes specifies mechanisms for assigning IP addresses.
170    /// Value is `slaac`, `dhcpv6-stateful`, `dhcpv6-stateless`.
171    #[serde(skip_serializing_if = "Option::is_none")]
172    #[builder(default)]
173    pub(crate) ipv6_address_mode: Option<Ipv6AddressMode>,
174
175    /// The IPv6 router advertisement specifies whether the networking service
176    /// should transmit ICMPv6 packets, for a subnet. Value is `slaac`,
177    /// `dhcpv6-stateful`, `dhcpv6-stateless`.
178    #[serde(skip_serializing_if = "Option::is_none")]
179    #[builder(default)]
180    pub(crate) ipv6_ra_mode: Option<Ipv6RaMode>,
181
182    /// Human-readable name of the resource. Default is an empty string.
183    #[serde(skip_serializing_if = "Option::is_none")]
184    #[builder(default, setter(into))]
185    pub(crate) name: Option<Cow<'a, str>>,
186
187    /// The ID of the network to which the subnet belongs.
188    #[serde()]
189    #[builder(setter(into))]
190    pub(crate) network_id: Cow<'a, str>,
191
192    /// The prefix length to use for subnet allocation from a subnet pool. If
193    /// not specified, the `default_prefixlen` value of the subnet pool will be
194    /// used.
195    #[serde(skip_serializing_if = "Option::is_none")]
196    #[builder(default, setter(into))]
197    pub(crate) prefixlen: Option<u32>,
198
199    /// The ID of a network segment the subnet is associated with. It is
200    /// available when `segment` extension is enabled.
201    #[serde(skip_serializing_if = "Option::is_none")]
202    #[builder(default, setter(into))]
203    pub(crate) segment_id: Option<Option<Cow<'a, str>>>,
204
205    /// The service types associated with the subnet.
206    #[serde(skip_serializing_if = "Option::is_none")]
207    #[builder(default, setter(into))]
208    pub(crate) service_types: Option<Vec<Cow<'a, str>>>,
209
210    /// The ID of the subnet pool associated with the subnet.
211    #[serde(skip_serializing_if = "Option::is_none")]
212    #[builder(default, setter(into))]
213    pub(crate) subnetpool_id: Option<Option<Cow<'a, str>>>,
214
215    /// The ID of the project that owns the resource. Only administrative and
216    /// users with advsvc role can specify a project ID other than their own.
217    /// You cannot change this value through authorization policies.
218    #[serde(skip_serializing_if = "Option::is_none")]
219    #[builder(default, setter(into))]
220    pub(crate) tenant_id: Option<Cow<'a, str>>,
221
222    /// Whether to allocate this subnet from the default subnet pool.
223    #[serde(skip_serializing_if = "Option::is_none")]
224    #[builder(default, setter(into))]
225    pub(crate) use_default_subnetpool: Option<bool>,
226}
227
228#[derive(Builder, Debug, Clone)]
229#[builder(setter(strip_option))]
230pub struct Request<'a> {
231    /// A `subnet` object.
232    #[builder(setter(into))]
233    pub(crate) subnet: Subnet<'a>,
234
235    #[builder(setter(name = "_headers"), default, private)]
236    _headers: Option<HeaderMap>,
237}
238impl<'a> Request<'a> {
239    /// Create a builder for the endpoint.
240    pub fn builder() -> RequestBuilder<'a> {
241        RequestBuilder::default()
242    }
243}
244
245impl<'a> RequestBuilder<'a> {
246    /// Add a single header to the Subnet.
247    pub fn header<K, V>(&mut self, header_name: K, header_value: V) -> &mut Self
248    where
249        K: Into<HeaderName>,
250        V: Into<HeaderValue>,
251    {
252        self._headers
253            .get_or_insert(None)
254            .get_or_insert_with(HeaderMap::new)
255            .insert(header_name.into(), header_value.into());
256        self
257    }
258
259    /// Add multiple headers.
260    pub fn headers<I, T>(&mut self, iter: I) -> &mut Self
261    where
262        I: Iterator<Item = T>,
263        T: Into<(Option<HeaderName>, HeaderValue)>,
264    {
265        self._headers
266            .get_or_insert(None)
267            .get_or_insert_with(HeaderMap::new)
268            .extend(iter.map(Into::into));
269        self
270    }
271}
272
273impl RestEndpoint for Request<'_> {
274    fn method(&self) -> http::Method {
275        http::Method::POST
276    }
277
278    fn endpoint(&self) -> Cow<'static, str> {
279        "subnets".to_string().into()
280    }
281
282    fn parameters(&self) -> QueryParams<'_> {
283        QueryParams::default()
284    }
285
286    fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
287        let mut params = JsonBodyParams::default();
288
289        params.push("subnet", serde_json::to_value(&self.subnet)?);
290
291        params.into_body()
292    }
293
294    fn service_type(&self) -> ServiceType {
295        ServiceType::Network
296    }
297
298    fn response_key(&self) -> Option<Cow<'static, str>> {
299        Some("subnet".into())
300    }
301
302    /// Returns headers to be set into the request
303    fn request_headers(&self) -> Option<&HeaderMap> {
304        self._headers.as_ref()
305    }
306
307    /// Returns required API version
308    fn api_version(&self) -> Option<ApiVersion> {
309        Some(ApiVersion::new(2, 0))
310    }
311}
312
313#[cfg(test)]
314mod tests {
315    use super::*;
316    #[cfg(feature = "sync")]
317    use crate::api::Query;
318    use crate::test::client::FakeOpenStackClient;
319    use crate::types::ServiceType;
320    use http::{HeaderName, HeaderValue};
321    use httpmock::MockServer;
322    use serde_json::json;
323
324    #[test]
325    fn test_service_type() {
326        assert_eq!(
327            Request::builder()
328                .subnet(
329                    SubnetBuilder::default()
330                        .ip_version(123)
331                        .network_id("foo")
332                        .build()
333                        .unwrap()
334                )
335                .build()
336                .unwrap()
337                .service_type(),
338            ServiceType::Network
339        );
340    }
341
342    #[test]
343    fn test_response_key() {
344        assert_eq!(
345            Request::builder()
346                .subnet(
347                    SubnetBuilder::default()
348                        .ip_version(123)
349                        .network_id("foo")
350                        .build()
351                        .unwrap()
352                )
353                .build()
354                .unwrap()
355                .response_key()
356                .unwrap(),
357            "subnet"
358        );
359    }
360
361    #[cfg(feature = "sync")]
362    #[test]
363    fn endpoint() {
364        let server = MockServer::start();
365        let client = FakeOpenStackClient::new(server.base_url());
366        let mock = server.mock(|when, then| {
367            when.method(httpmock::Method::POST)
368                .path("/subnets".to_string());
369
370            then.status(200)
371                .header("content-type", "application/json")
372                .json_body(json!({ "subnet": {} }));
373        });
374
375        let endpoint = Request::builder()
376            .subnet(
377                SubnetBuilder::default()
378                    .ip_version(123)
379                    .network_id("foo")
380                    .build()
381                    .unwrap(),
382            )
383            .build()
384            .unwrap();
385        let _: serde_json::Value = endpoint.query(&client).unwrap();
386        mock.assert();
387    }
388
389    #[cfg(feature = "sync")]
390    #[test]
391    fn endpoint_headers() {
392        let server = MockServer::start();
393        let client = FakeOpenStackClient::new(server.base_url());
394        let mock = server.mock(|when, then| {
395            when.method(httpmock::Method::POST)
396                .path("/subnets".to_string())
397                .header("foo", "bar")
398                .header("not_foo", "not_bar");
399            then.status(200)
400                .header("content-type", "application/json")
401                .json_body(json!({ "subnet": {} }));
402        });
403
404        let endpoint = Request::builder()
405            .subnet(
406                SubnetBuilder::default()
407                    .ip_version(123)
408                    .network_id("foo")
409                    .build()
410                    .unwrap(),
411            )
412            .headers(
413                [(
414                    Some(HeaderName::from_static("foo")),
415                    HeaderValue::from_static("bar"),
416                )]
417                .into_iter(),
418            )
419            .header(
420                HeaderName::from_static("not_foo"),
421                HeaderValue::from_static("not_bar"),
422            )
423            .build()
424            .unwrap();
425        let _: serde_json::Value = endpoint.query(&client).unwrap();
426        mock.assert();
427    }
428}