openstack/network/
protocol.rs

1// Copyright 2018 Dmitry Tantsur <divius.inside@gmail.com>
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! JSON structures and protocol bits for the Network API.
16
17#![allow(non_snake_case)]
18#![allow(missing_docs)]
19
20use std::marker::PhantomData;
21use std::net;
22use std::ops::Not;
23
24use chrono::{DateTime, FixedOffset};
25use osauth::common::empty_as_default;
26use serde::{Deserialize, Deserializer, Serialize, Serializer};
27use serde_json::Value;
28
29use super::super::common::{NetworkRef, SecurityGroupRef};
30use super::super::Result;
31use crate::session::Session;
32
33protocol_enum! {
34    #[doc = "IP protocol version."]
35    enum IpVersion: u8 {
36        V4 = 4,
37        V6 = 6
38    }
39}
40
41protocol_enum! {
42    #[doc = "Network IP protocol."]
43    enum NetworkProtocol {
44        TCP = "tcp",
45        UDP = "udp"
46    }
47}
48
49protocol_enum! {
50    #[doc = "Possible network statuses."]
51    enum NetworkStatus {
52        Active = "ACTIVE",
53        Down = "DOWN",
54        Building = "BUILD",
55        Error = "ERROR"
56    }
57}
58
59protocol_enum! {
60    #[doc = "Available sort keys."]
61    enum NetworkSortKey {
62        CreatedAt = "created_at",
63        Id = "id",
64        Name = "name",
65        UpdatedAt = "updated_at"
66    }
67}
68
69protocol_enum! {
70    #[doc = "Possible floating IP statuses."]
71    enum FloatingIpStatus {
72        Active = "ACTIVE",
73        Down = "DOWN",
74        Error = "ERROR"
75    }
76}
77
78protocol_enum! {
79    #[doc = "Available sort keys."]
80    enum FloatingIpSortKey {
81        FixedIpAddress = "fixed_ip_address",
82        FloatingIpAddress = "floating_ip_address",
83        FloatingNetworkId = "floating_network_id",
84        Id = "id",
85        RouterId = "router_id",
86        Status = "status"
87    }
88}
89
90impl Default for NetworkSortKey {
91    fn default() -> NetworkSortKey {
92        NetworkSortKey::CreatedAt
93    }
94}
95
96protocol_enum! {
97    #[doc = "Available sort keys."]
98    enum PortSortKey {
99        AdminStateUp = "admin_state_up",
100        DeviceId = "device_id",
101        DeviceOwner = "device_owner",
102        Id = "id",
103        MacAddress = "mac_address",
104        Name = "name",
105        NetworkId = "network_id",
106        Status = "status"
107    }
108}
109
110protocol_enum! {
111    #[doc = "Available sort keys."]
112    enum RouterSortKey {
113        AdminStateUp = "admin_state_up",
114        FlavorId = "flavor_id",
115        Id = "id",
116        Name = "name",
117        ProjectId = "project_id",
118        Status = "status"
119    }
120}
121
122protocol_enum! {
123    #[doc = "Possible router statuses."]
124    enum RouterStatus {
125        Active = "ACTIVE",
126        Allocating = "ALLOCATING",
127        Error = "ERROR"
128    }
129}
130
131protocol_enum! {
132    #[doc = "Available sort keys."]
133    enum SubnetSortKey {
134        Cidr = "cidr",
135        DhcpEnabled = "enable_dhcp",
136        GatewayIp = "gateway_ip",
137        Id = "id",
138        IpVersion = "ip_version",
139        Ipv6AddressMode = "ipv6_address_mode",
140        Ipv6RouterAdvertisementMode = "ipv6_ra_mode",
141        Name = "name",
142        NetworkId = "network_id"
143    }
144}
145
146protocol_enum! {
147    #[doc = "IPv6 modes for assigning IP addresses."]
148    enum Ipv6Mode {
149        DhcpStateful = "dhcpv6-stateful",
150        DhcpStateless = "dhcpv6-stateless",
151        Slaac = "slaac"
152    }
153}
154
155/// An network.
156#[derive(Debug, Clone, Deserialize, Serialize)]
157pub struct Network {
158    pub admin_state_up: bool,
159    #[serde(default, skip_serializing)]
160    pub availability_zones: Vec<String>,
161    #[serde(default, skip_serializing)]
162    pub created_at: Option<DateTime<FixedOffset>>,
163    #[serde(
164        deserialize_with = "empty_as_default",
165        default,
166        skip_serializing_if = "Option::is_none"
167    )]
168    pub description: Option<String>,
169    #[serde(
170        deserialize_with = "empty_as_default",
171        default,
172        skip_serializing_if = "Option::is_none"
173    )]
174    pub dns_domain: Option<String>,
175    #[serde(rename = "router:external", skip_serializing_if = "Option::is_none")]
176    pub external: Option<bool>,
177    #[serde(skip_serializing)]
178    pub id: String,
179    #[serde(default, skip_serializing_if = "Option::is_none")]
180    pub is_default: Option<bool>,
181    #[serde(default, skip_serializing)]
182    pub l2_adjacency: Option<bool>,
183    #[serde(default, skip_serializing_if = "Option::is_none")]
184    pub mtu: Option<u32>,
185    #[serde(
186        deserialize_with = "empty_as_default",
187        skip_serializing_if = "Option::is_none"
188    )]
189    pub name: Option<String>,
190    #[serde(default, skip_serializing_if = "Option::is_none")]
191    pub port_security_enabled: Option<bool>,
192    #[serde(default, skip_serializing_if = "Option::is_none")]
193    pub project_id: Option<String>,
194    #[serde(default, skip_serializing_if = "Not::not")]
195    pub shared: bool,
196    #[serde(skip_serializing)]
197    pub status: NetworkStatus,
198    // #[serde(skip_serializing)]
199    // pub subnets: Vec<String>,
200    #[serde(default, skip_serializing)]
201    pub updated_at: Option<DateTime<FixedOffset>>,
202    #[serde(default, skip_serializing_if = "Option::is_none")]
203    pub vlan_transparent: Option<bool>,
204}
205
206impl Default for Network {
207    fn default() -> Network {
208        Network {
209            admin_state_up: true,
210            availability_zones: Vec::new(),
211            created_at: None,
212            description: None,
213            dns_domain: None,
214            external: None,
215            id: String::new(),
216            is_default: None,
217            l2_adjacency: None,
218            mtu: None,
219            name: None,
220            port_security_enabled: None,
221            project_id: None,
222            shared: false,
223            status: NetworkStatus::Active,
224            // subnets: Vec::new(),
225            updated_at: None,
226            vlan_transparent: None,
227        }
228    }
229}
230
231/// A network.
232#[derive(Debug, Clone, Default, Serialize)]
233pub struct NetworkUpdate {
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub admin_state_up: Option<bool>,
236    #[serde(rename = "router:external", skip_serializing_if = "Option::is_none")]
237    pub external: Option<bool>,
238    #[serde(skip_serializing_if = "Option::is_none")]
239    pub description: Option<String>,
240    #[serde(skip_serializing_if = "Option::is_none")]
241    pub dns_domain: Option<String>,
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub is_default: Option<bool>,
244    #[serde(skip_serializing_if = "Option::is_none")]
245    pub mtu: Option<u32>,
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub name: Option<String>,
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub port_security_enabled: Option<bool>,
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub shared: Option<bool>,
252}
253
254/// A network.
255#[derive(Debug, Clone, Deserialize, Serialize)]
256pub struct NetworkRoot {
257    pub network: Network,
258}
259
260/// A network.
261#[derive(Debug, Clone, Serialize)]
262pub struct NetworkUpdateRoot {
263    pub network: NetworkUpdate,
264}
265
266/// A list of networks.
267#[derive(Debug, Clone, Deserialize)]
268pub struct NetworksRoot {
269    pub networks: Vec<Network>,
270}
271
272/// An extra DHCP option.
273#[derive(Debug, Clone, Deserialize, Serialize)]
274pub struct PortExtraDhcpOption {
275    /// IP protocol version (if required).
276    #[serde(default, skip_serializing_if = "Option::is_none")]
277    pub ip_version: Option<IpVersion>,
278    /// Option name.
279    #[serde(rename = "opt_name")]
280    pub name: String,
281    /// Option value.
282    #[serde(rename = "opt_value")]
283    pub value: String,
284    #[doc(hidden)]
285    #[serde(skip)]
286    pub __nonexhaustive: PhantomData<()>,
287}
288
289impl PortExtraDhcpOption {
290    /// Create a new DHCP option.
291    pub fn new<S1, S2>(name: S1, value: S2) -> PortExtraDhcpOption
292    where
293        S1: Into<String>,
294        S2: Into<String>,
295    {
296        PortExtraDhcpOption {
297            ip_version: None,
298            name: name.into(),
299            value: value.into(),
300            __nonexhaustive: PhantomData,
301        }
302    }
303
304    /// Create a new DHCP option with an IP version.
305    pub fn new_with_ip_version<S1, S2>(
306        name: S1,
307        value: S2,
308        ip_version: IpVersion,
309    ) -> PortExtraDhcpOption
310    where
311        S1: Into<String>,
312        S2: Into<String>,
313    {
314        PortExtraDhcpOption {
315            ip_version: Some(ip_version),
316            name: name.into(),
317            value: value.into(),
318            __nonexhaustive: PhantomData,
319        }
320    }
321}
322
323/// A port's IP address.
324#[derive(Debug, Clone, Deserialize, Serialize)]
325pub struct FixedIp {
326    #[serde(skip_serializing_if = "::std::net::IpAddr::is_unspecified")]
327    pub ip_address: net::IpAddr,
328    #[serde(skip_serializing_if = "String::is_empty")]
329    pub subnet_id: String,
330}
331
332#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Ord, PartialOrd, Hash)]
333pub struct MacAddress(macaddr::MacAddr6);
334
335impl MacAddress {
336    pub fn is_nil(&self) -> bool {
337        self.0.is_nil()
338    }
339}
340
341impl std::fmt::Display for MacAddress {
342    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
343        write!(f, "{}", self.0)
344    }
345}
346
347impl std::ops::Deref for MacAddress {
348    type Target = macaddr::MacAddr6;
349
350    fn deref(&self) -> &Self::Target {
351        &self.0
352    }
353}
354
355impl std::str::FromStr for MacAddress {
356    type Err = macaddr::ParseError;
357
358    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
359        Ok(Self(s.parse::<macaddr::MacAddr6>()?))
360    }
361}
362
363impl Serialize for MacAddress {
364    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
365    where
366        S: Serializer,
367    {
368        serializer.serialize_str(&self.to_string())
369    }
370}
371
372impl<'de> Deserialize<'de> for MacAddress {
373    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
374    where
375        D: Deserializer<'de>,
376    {
377        let s: String = Deserialize::deserialize(deserializer)?;
378        s.parse().map_err(serde::de::Error::custom)
379    }
380}
381
382/// A port's IP address.
383#[derive(Debug, Clone, Deserialize, Serialize, Copy)]
384pub struct AllowedAddressPair {
385    pub ip_address: net::IpAddr,
386    #[serde(skip_serializing_if = "Option::is_none")]
387    pub mac_address: Option<MacAddress>,
388}
389
390/// A port.
391#[derive(Debug, Clone, Deserialize, Serialize)]
392pub struct Port {
393    pub admin_state_up: bool,
394    #[serde(skip_serializing_if = "Vec::is_empty", default)]
395    pub allowed_address_pairs: Vec<AllowedAddressPair>,
396    #[serde(default, skip_serializing)]
397    pub created_at: Option<DateTime<FixedOffset>>,
398    #[serde(
399        deserialize_with = "empty_as_default",
400        default,
401        skip_serializing_if = "Option::is_none"
402    )]
403    pub description: Option<String>,
404    #[serde(
405        deserialize_with = "empty_as_default",
406        default,
407        skip_serializing_if = "Option::is_none"
408    )]
409    pub device_id: Option<String>,
410    #[serde(
411        deserialize_with = "empty_as_default",
412        default,
413        skip_serializing_if = "Option::is_none"
414    )]
415    pub device_owner: Option<String>,
416    #[serde(
417        deserialize_with = "empty_as_default",
418        default,
419        skip_serializing_if = "Option::is_none"
420    )]
421    pub dns_domain: Option<String>,
422    #[serde(
423        deserialize_with = "empty_as_default",
424        default,
425        skip_serializing_if = "Option::is_none"
426    )]
427    pub dns_name: Option<String>,
428    #[serde(default, skip_serializing_if = "Vec::is_empty")]
429    pub extra_dhcp_opts: Vec<PortExtraDhcpOption>,
430    #[serde(default, skip_serializing_if = "Vec::is_empty")]
431    pub fixed_ips: Vec<FixedIp>,
432    #[serde(skip_serializing)]
433    pub id: String,
434    #[serde(skip_serializing_if = "MacAddress::is_nil")]
435    pub mac_address: MacAddress,
436    #[serde(
437        deserialize_with = "empty_as_default",
438        skip_serializing_if = "Option::is_none"
439    )]
440    pub name: Option<String>,
441    pub network_id: String,
442    #[serde(default, skip_serializing_if = "Option::is_none")]
443    pub project_id: Option<String>,
444    #[serde(default, skip_serializing_if = "Vec::is_empty")]
445    pub security_groups: Vec<SecurityGroupRef>,
446    #[serde(skip_serializing)]
447    pub status: NetworkStatus,
448    #[serde(default, skip_serializing)]
449    pub updated_at: Option<DateTime<FixedOffset>>,
450}
451
452/// A port.
453#[derive(Debug, Clone, Serialize, Default)]
454pub struct PortUpdate {
455    #[serde(skip_serializing_if = "Option::is_none")]
456    pub admin_state_up: Option<bool>,
457    #[serde(skip_serializing_if = "Option::is_none")]
458    pub description: Option<String>,
459    #[serde(skip_serializing_if = "Option::is_none")]
460    pub device_id: Option<String>,
461    #[serde(skip_serializing_if = "Option::is_none")]
462    pub device_owner: Option<String>,
463    #[serde(skip_serializing_if = "Option::is_none")]
464    pub dns_domain: Option<String>,
465    #[serde(skip_serializing_if = "Option::is_none")]
466    pub dns_name: Option<String>,
467    #[serde(skip_serializing_if = "Option::is_none")]
468    pub extra_dhcp_opts: Option<Vec<PortExtraDhcpOption>>,
469    #[serde(skip_serializing_if = "Option::is_none")]
470    pub fixed_ips: Option<Vec<FixedIp>>,
471    #[serde(skip_serializing_if = "Option::is_none")]
472    pub mac_address: Option<MacAddress>,
473    #[serde(skip_serializing_if = "Option::is_none")]
474    pub name: Option<String>,
475    #[serde(skip_serializing_if = "Option::is_none")]
476    pub security_groups: Option<Vec<SecurityGroupRef>>,
477}
478
479/// A port.
480#[derive(Debug, Clone, Deserialize, Serialize)]
481pub struct PortRoot {
482    pub port: Port,
483}
484
485/// A port update.
486#[derive(Debug, Clone, Serialize)]
487pub struct PortUpdateRoot {
488    pub port: PortUpdate,
489}
490
491/// A list of ports.
492#[derive(Debug, Clone, Deserialize)]
493pub struct PortsRoot {
494    pub ports: Vec<Port>,
495}
496
497protocol_enum! {
498    #[doc = "Allowed conntrack helpers as defined [here](https://opendev.org/openstack/neutron/src/branch/master/neutron/conf/extensions/conntrack_helper.py)"]
499    enum Helper {
500        Amanda = "amanda",
501        FTP = "ftp",
502        H323 = "h323",
503        IRC = "irc",
504        NetbiosNS = "netbios-ns",
505        PPTP = "pptp",
506        SANE = "sane",
507        SIP = "sip",
508        SNMP = "snmp",
509        TFTP = "tftp"
510    }
511}
512
513/// ConntrackHelper object.
514/// See [here](https://home.regit.org/netfilter-en/secure-use-of-helpers/) for in-depth info about
515/// conntrack helpers.
516#[derive(Debug, Clone, Copy, Deserialize, PartialEq)]
517pub struct ConntrackHelper {
518    /// Conntrack Helper
519    pub helper: Helper,
520    /// Network IP protocol.
521    pub protocol: NetworkProtocol,
522    /// TCP or UDP port
523    pub port: u16,
524}
525
526/// External gateway information.
527#[non_exhaustive]
528#[derive(Debug, Clone, Deserialize, Serialize)]
529pub struct ExternalGateway {
530    /// External network.
531    pub network_id: NetworkRef,
532    /// Whether to enable source NAT.
533    #[serde(skip_serializing_if = "Option::is_none")]
534    pub enable_snat: Option<bool>,
535    /// A list of external fixed IPs.
536    #[serde(skip_serializing_if = "Vec::is_empty")]
537    pub external_fixed_ips: Vec<FixedIp>,
538}
539
540impl ExternalGateway {
541    /// Create a new external gateway.
542    pub fn new<N: Into<NetworkRef>>(external_network: N) -> ExternalGateway {
543        ExternalGateway {
544            network_id: external_network.into(),
545            enable_snat: None,
546            external_fixed_ips: Vec::new(),
547        }
548    }
549
550    pub(crate) async fn into_verified(self, session: &Session) -> Result<Self> {
551        Ok(ExternalGateway {
552            network_id: self.network_id.into_verified(session).await?,
553            ..self
554        })
555    }
556}
557
558/// Routes.
559#[derive(Debug, Serialize)]
560pub struct Routes {
561    pub routes: Vec<HostRoute>,
562}
563
564/// A router.
565#[derive(Debug, Clone, Deserialize, Serialize)]
566pub struct Router {
567    pub admin_state_up: bool,
568    #[serde(default, skip_serializing)]
569    pub availability_zone_hints: Vec<String>,
570    #[serde(default, skip_serializing)]
571    pub availability_zones: Vec<String>,
572    #[serde(default, skip_serializing)]
573    pub conntrack_helpers: Vec<ConntrackHelper>,
574    #[serde(default, skip_serializing)]
575    pub created_at: Option<DateTime<FixedOffset>>,
576    #[serde(
577        deserialize_with = "empty_as_default",
578        default,
579        skip_serializing_if = "Option::is_none"
580    )]
581    pub description: Option<String>,
582    #[serde(skip_serializing_if = "Option::is_none")]
583    pub distributed: Option<bool>,
584    #[serde(
585        default,
586        skip_serializing_if = "Option::is_none",
587        rename = "external_gateway_info"
588    )]
589    pub external_gateway: Option<ExternalGateway>,
590    #[serde(skip_serializing_if = "Option::is_none")]
591    pub flavor_id: Option<String>,
592    #[serde(skip_serializing_if = "Option::is_none")]
593    pub ha: Option<bool>,
594    #[serde(skip_serializing)]
595    pub id: String,
596    #[serde(
597        deserialize_with = "empty_as_default",
598        skip_serializing_if = "Option::is_none"
599    )]
600    pub name: Option<String>,
601    #[serde(default, skip_serializing_if = "Option::is_none")]
602    pub project_id: Option<String>,
603    #[serde(default, skip_serializing)]
604    pub revision_number: Option<u32>,
605    #[serde(skip_serializing_if = "Option::is_none")]
606    pub routes: Option<Vec<HostRoute>>,
607    #[serde(skip_serializing_if = "Option::is_none")]
608    pub service_type_id: Option<String>,
609    #[serde(skip_serializing)]
610    pub status: RouterStatus,
611    #[serde(skip_serializing)]
612    pub tags: Option<Vec<String>>,
613    #[serde(default, skip_serializing)]
614    pub updated_at: Option<DateTime<FixedOffset>>,
615}
616
617impl Default for Router {
618    fn default() -> Router {
619        Router {
620            admin_state_up: true,
621            availability_zones: vec![],
622            availability_zone_hints: vec![],
623            created_at: None,
624            conntrack_helpers: vec![],
625            description: None,
626            distributed: None,
627            external_gateway: None,
628            flavor_id: None,
629            ha: None,
630            id: String::new(),
631            name: None,
632            project_id: None,
633            revision_number: None,
634            routes: None,
635            service_type_id: None,
636            status: RouterStatus::Active,
637            tags: None,
638            updated_at: None,
639        }
640    }
641}
642
643impl Router {
644    pub(crate) async fn into_verified(self, session: &Session) -> Result<Self> {
645        Ok(Router {
646            external_gateway: match self.external_gateway {
647                Some(gw) => Some(gw.into_verified(session).await?),
648                None => None,
649            },
650            ..self
651        })
652    }
653}
654
655/// A router.
656#[derive(Debug, Clone, Deserialize, Serialize)]
657pub struct RouterRoot {
658    pub router: Router,
659}
660
661/// A Router.
662#[derive(Debug, Clone, Default, Serialize)]
663pub struct RouterUpdate {
664    #[serde(skip_serializing_if = "Option::is_none")]
665    pub admin_state_up: Option<bool>,
666    #[serde(skip_serializing_if = "Option::is_none")]
667    pub description: Option<String>,
668    #[serde(skip_serializing_if = "Option::is_none")]
669    pub distributed: Option<bool>,
670    #[serde(
671        default,
672        skip_serializing_if = "Option::is_none",
673        rename = "external_gateway_info"
674    )]
675    pub external_gateway: Option<ExternalGateway>,
676    #[serde(skip_serializing_if = "Option::is_none")]
677    pub ha: Option<bool>,
678    #[serde(skip_serializing_if = "Option::is_none")]
679    pub name: Option<String>,
680    #[serde(skip_serializing_if = "Option::is_none")]
681    pub routes: Option<Vec<HostRoute>>,
682}
683
684/// A router.
685#[derive(Debug, Clone, Serialize)]
686pub struct RouterUpdateRoot {
687    pub router: RouterUpdate,
688}
689
690/// A list of routers.
691#[derive(Debug, Clone, Deserialize)]
692pub struct RoutersRoot {
693    pub routers: Vec<Router>,
694}
695
696/// An allocation pool.
697#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
698pub struct AllocationPool {
699    /// Start IP address.
700    pub start: net::IpAddr,
701    /// End IP address.
702    pub end: net::IpAddr,
703}
704
705/// A host route.
706#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
707pub struct HostRoute {
708    /// Destination network.
709    pub destination: ipnet::IpNet,
710    /// Next hop address.
711    #[serde(rename = "nexthop")]
712    pub next_hop: net::IpAddr,
713}
714
715/// A subnet.
716#[derive(Debug, Clone, Deserialize, Serialize)]
717pub struct Subnet {
718    #[serde(default, skip_serializing_if = "Vec::is_empty")]
719    pub allocation_pools: Vec<AllocationPool>,
720    pub cidr: ipnet::IpNet,
721    #[serde(default, skip_serializing)]
722    pub created_at: Option<DateTime<FixedOffset>>,
723    #[serde(
724        deserialize_with = "empty_as_default",
725        default,
726        skip_serializing_if = "Option::is_none"
727    )]
728    pub description: Option<String>,
729    #[serde(rename = "enable_dhcp")]
730    pub dhcp_enabled: bool,
731    #[serde(default, skip_serializing_if = "Vec::is_empty")]
732    pub dns_nameservers: Vec<String>,
733    #[serde(default, skip_serializing_if = "Option::is_none")]
734    pub gateway_ip: Option<net::IpAddr>,
735    #[serde(default, skip_serializing_if = "Vec::is_empty")]
736    pub host_routes: Vec<HostRoute>,
737    #[serde(skip_serializing)]
738    pub id: String,
739    pub ip_version: IpVersion,
740    #[serde(default, skip_serializing_if = "Option::is_none")]
741    pub ipv6_address_mode: Option<Ipv6Mode>,
742    #[serde(
743        default,
744        rename = "ipv6_ra_mode",
745        skip_serializing_if = "Option::is_none"
746    )]
747    pub ipv6_router_advertisement_mode: Option<Ipv6Mode>,
748    #[serde(
749        deserialize_with = "empty_as_default",
750        skip_serializing_if = "Option::is_none"
751    )]
752    pub name: Option<String>,
753    pub network_id: String,
754    #[serde(default, skip_serializing_if = "Option::is_none")]
755    pub project_id: Option<String>,
756    #[serde(default, skip_serializing)]
757    pub updated_at: Option<DateTime<FixedOffset>>,
758}
759
760impl Subnet {
761    pub(crate) fn empty(cidr: ipnet::IpNet) -> Subnet {
762        Subnet {
763            allocation_pools: Vec::new(),
764            cidr,
765            created_at: None,
766            description: None,
767            dhcp_enabled: true,
768            dns_nameservers: Vec::new(),
769            gateway_ip: None,
770            host_routes: Vec::new(),
771            id: String::new(),
772            ip_version: match cidr {
773                ipnet::IpNet::V4(..) => IpVersion::V4,
774                ipnet::IpNet::V6(..) => IpVersion::V6,
775            },
776            ipv6_address_mode: None,
777            ipv6_router_advertisement_mode: None,
778            name: None,
779            network_id: String::new(),
780            project_id: None,
781            updated_at: None,
782        }
783    }
784}
785
786/// A subnet.
787#[derive(Debug, Clone, Serialize, Default)]
788pub struct SubnetUpdate {
789    #[serde(skip_serializing_if = "Option::is_none")]
790    pub allocation_pools: Option<Vec<AllocationPool>>,
791    #[serde(skip_serializing_if = "Option::is_none")]
792    pub description: Option<String>,
793    #[serde(rename = "enable_dhcp", skip_serializing_if = "Option::is_none")]
794    pub dhcp_enabled: Option<bool>,
795    #[serde(skip_serializing_if = "Option::is_none")]
796    pub dns_nameservers: Option<Vec<String>>,
797    #[serde(skip_serializing_if = "Option::is_none")]
798    pub gateway_ip: Option<net::IpAddr>,
799    #[serde(skip_serializing_if = "Option::is_none")]
800    pub host_routes: Option<Vec<HostRoute>>,
801    #[serde(skip_serializing_if = "Option::is_none")]
802    pub name: Option<String>,
803}
804
805/// A subnet.
806#[derive(Debug, Clone, Deserialize, Serialize)]
807pub struct SubnetRoot {
808    pub subnet: Subnet,
809}
810
811/// A subnet.
812#[derive(Debug, Clone, Serialize)]
813pub struct SubnetUpdateRoot {
814    pub subnet: SubnetUpdate,
815}
816
817/// A list of subnets.
818#[derive(Debug, Clone, Deserialize)]
819pub struct SubnetsRoot {
820    pub subnets: Vec<Subnet>,
821}
822
823#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
824pub struct PortForwarding {
825    /// TCP or UDP port used by floating IP.
826    pub external_port: u16,
827    /// Fixed IP address of internal port.
828    pub internal_ip_address: net::IpAddr,
829    /// TCP or UDP port used by internal port.
830    pub internal_port: u16,
831    /// Network IP protocol.
832    pub protocol: String,
833}
834
835/// A floating IP.
836#[derive(Debug, Clone, Deserialize, Serialize)]
837pub struct FloatingIp {
838    #[serde(default, skip_serializing)]
839    pub created_at: Option<DateTime<FixedOffset>>,
840    #[serde(
841        deserialize_with = "empty_as_default",
842        default,
843        skip_serializing_if = "Option::is_none"
844    )]
845    pub description: Option<String>,
846    #[serde(
847        deserialize_with = "empty_as_default",
848        default,
849        skip_serializing_if = "Option::is_none"
850    )]
851    pub dns_domain: Option<String>,
852    #[serde(
853        deserialize_with = "empty_as_default",
854        default,
855        skip_serializing_if = "Option::is_none"
856    )]
857    pub dns_name: Option<String>,
858    #[serde(default, skip_serializing_if = "Option::is_none")]
859    pub fixed_ip_address: Option<net::IpAddr>,
860    #[serde(skip_serializing_if = "::std::net::IpAddr::is_unspecified")]
861    pub floating_ip_address: net::IpAddr,
862    pub floating_network_id: String,
863    #[serde(skip_serializing)]
864    pub id: String,
865    #[serde(default)]
866    pub port_id: Option<String>,
867    #[serde(default, skip_serializing)]
868    pub port_forwardings: Vec<PortForwarding>,
869    #[serde(default, skip_serializing)]
870    pub router_id: Option<String>,
871    #[serde(skip_serializing)]
872    pub status: FloatingIpStatus,
873    #[serde(skip_deserializing, skip_serializing_if = "Option::is_none")]
874    pub subnet_id: Option<String>,
875    #[serde(default, skip_serializing)]
876    pub updated_at: Option<DateTime<FixedOffset>>,
877}
878
879/// A port.
880#[derive(Debug, Clone, Serialize, Default)]
881pub struct FloatingIpUpdate {
882    #[serde(skip_serializing_if = "Option::is_none")]
883    pub description: Option<String>,
884    #[serde(skip_serializing_if = "Option::is_none")]
885    pub fixed_ip_address: Option<net::IpAddr>,
886    #[serde(skip_serializing_if = "Option::is_none")]
887    pub port_id: Option<Value>,
888}
889
890/// A floating IP.
891#[derive(Debug, Clone, Deserialize, Serialize)]
892pub struct FloatingIpRoot {
893    pub floatingip: FloatingIp,
894}
895
896/// A floating IP.
897#[derive(Debug, Clone, Serialize)]
898pub struct FloatingIpUpdateRoot {
899    pub floatingip: FloatingIpUpdate,
900}
901
902/// Floating IPs.
903#[derive(Debug, Clone, Deserialize)]
904pub struct FloatingIpsRoot {
905    pub floatingips: Vec<FloatingIp>,
906}
907
908#[cfg(test)]
909mod test {
910    use super::*;
911
912    #[test]
913    fn test_parse_macaddr() {
914        // Test that a JSON deserialisation of MAC addresses work
915        let a: AllowedAddressPair = serde_json::from_value(
916            serde_json::json!({"ip_address":"0.0.0.0", "mac_address":"ab:aa:aa:aa:aa:aa"}),
917        )
918        .expect("Could not parse this JSON");
919        assert_eq!(
920            a.mac_address.expect("MAC address is missing").to_string(),
921            "AB:AA:AA:AA:AA:AA"
922        );
923
924        // Test that a JSON serialisation of MAC addresses work
925        assert_eq!(
926            serde_json::to_value(&a)
927                .expect("Could not serialize")
928                .get("mac_address")
929                .expect("No mac_address")
930                .as_str()
931                .expect("No string found"),
932            "AB:AA:AA:AA:AA:AA"
933        );
934
935        // Test that missing MAC addresses are parsed as None
936        let a: AllowedAddressPair =
937            serde_json::from_value(serde_json::json!({"ip_address":"0.0.0.0"}))
938                .expect("Cannot parse this JSON");
939        assert_eq!(a.mac_address, None);
940    }
941}