Skip to main content

openstack_cli_network/v2/port/
set.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//! Set Port command
19//!
20//! Wraps invoking of the `v2.0/ports/{port_id}` with `PUT` method
21
22use clap::Args;
23use eyre::WrapErr;
24use tracing::info;
25
26use openstack_cli_core::cli::CliArgs;
27use openstack_cli_core::error::OpenStackCliError;
28use openstack_cli_core::output::OutputProcessor;
29use openstack_sdk::AsyncOpenStack;
30
31use clap::ValueEnum;
32use openstack_cli_core::common::parse_key_val;
33use openstack_sdk::api::QueryAsync;
34use openstack_sdk::api::find;
35use openstack_sdk::api::network::v2::port::find;
36use openstack_sdk::api::network::v2::port::set;
37use openstack_types::network::v2::port::response;
38use serde_json::Value;
39use std::collections::BTreeMap;
40
41/// Updates a port.
42///
43/// You can update information for a port, such as its symbolic name and
44/// associated IPs. When you update IPs for a port, any previously associated
45/// IPs are removed, returned to the respective subnet allocation pools, and
46/// replaced by the IPs in the request body. Therefore, this operation replaces
47/// the `fixed_ip` attribute when you specify it in the request body. If the
48/// updated IP addresses are not valid or are already in use, the operation
49/// fails and the existing IP addresses are not removed from the port.
50///
51/// When you update security groups for a port and the operation succeeds, any
52/// associated security groups are removed and replaced by the security groups
53/// in the request body. Therefore, this operation replaces the
54/// `security_groups` attribute when you specify it in the request body. If the
55/// security groups are not valid, the operation fails and the existing
56/// security groups are not removed from the port.
57///
58/// When you update `binding:profile` of a port with null it is treated as {}
59/// in the response.
60///
61/// The `binding:vnic_type` attribute can be updated on unbound ports only. If
62/// the port is already bound, the update operation of the attribute returns
63/// the `Conflict (409)` response code.
64///
65/// Only admins and users with a specific role can update the data plane status
66/// (default role: `data_plane_integrator`).
67///
68/// Normal response codes: 200
69///
70/// Error response codes: 400, 401, 403, 404, 409, 412
71#[derive(Args)]
72#[command(about = "Update port")]
73pub struct PortCommand {
74    /// Request Query parameters
75    #[command(flatten)]
76    query: QueryParameters,
77
78    /// Path parameters
79    #[command(flatten)]
80    path: PathParameters,
81
82    /// A `port` object.
83    #[command(flatten)]
84    port: Port,
85}
86
87/// Query parameters
88#[derive(Args)]
89struct QueryParameters {}
90
91/// Path parameters
92#[derive(Args)]
93struct PathParameters {
94    /// port_id parameter for /v2.0/ports/{port_id}/add_allowed_address_pairs
95    /// API
96    #[arg(
97        help_heading = "Path parameters",
98        id = "path_param_id",
99        value_name = "ID"
100    )]
101    id: String,
102}
103
104#[derive(Clone, Eq, Ord, PartialEq, PartialOrd, ValueEnum)]
105enum BindingVnicType {
106    AcceleratorDirect,
107    AcceleratorDirectPhysical,
108    Baremetal,
109    Direct,
110    DirectPhysical,
111    Macvtap,
112    Normal,
113    RemoteManaged,
114    SmartNic,
115    Vdpa,
116    VirtioForwarder,
117}
118
119#[derive(Clone, Eq, Ord, PartialEq, PartialOrd, ValueEnum)]
120enum DataPlaneStatus {
121    Active,
122    Down,
123}
124
125#[derive(Clone, Eq, Ord, PartialEq, PartialOrd, ValueEnum)]
126enum NumaAffinityPolicy {
127    Legacy,
128    Preferred,
129    Required,
130    Socket,
131}
132
133/// Port Body data
134#[derive(Args, Clone)]
135struct Port {
136    /// The administrative state of the resource, which is up (`true`) or down
137    /// (`false`). Default is `true`.
138    #[arg(action=clap::ArgAction::Set, help_heading = "Body parameters", long)]
139    admin_state_up: Option<bool>,
140
141    /// A set of zero or more allowed address pair objects each where address
142    /// pair object contains an `ip_address` and `mac_address`. While the
143    /// `ip_address` is required, the `mac_address` will be taken from the port
144    /// if not specified. The value of `ip_address` can be an IP Address or a
145    /// CIDR (if supported by the underlying extension plugin). A server
146    /// connected to the port can send a packet with source address which
147    /// matches one of the specified allowed address pairs.
148    ///
149    /// Parameter is an array, may be provided multiple times.
150    #[arg(action=clap::ArgAction::Append, help_heading = "Body parameters", long, value_name="JSON", value_parser=openstack_cli_core::common::parse_json)]
151    allowed_address_pairs: Option<Vec<Value>>,
152
153    /// The ID of the host where the port resides. The default is an empty
154    /// string.
155    #[arg(help_heading = "Body parameters", long)]
156    binding_host_id: Option<String>,
157
158    /// A dictionary that enables the application running on the specific host
159    /// to pass and receive vif port information specific to the networking
160    /// back-end. This field is only meant for machine-machine communication
161    /// for compute services like Nova, Ironic or Zun to pass information to a
162    /// Neutron back-end. It should not be used by multiple services
163    /// concurrently or by cloud end users. The existing counterexamples
164    /// (`capabilities: [switchdev]` for Open vSwitch hardware offload and
165    /// `trusted=true` for Trusted Virtual Functions) are due to be cleaned up.
166    /// The networking API does not define a specific format of this field. The
167    /// default is an empty dictionary. If you update it with null then it is
168    /// treated like {} in the response. Since the port-mac-address-override
169    /// extension the `device_mac_address` field of the binding:profile can be
170    /// used to provide the MAC address of the physical device a
171    /// direct-physical port is being bound to. If provided, then the
172    /// `mac_address` field of the port resource will be updated to the MAC
173    /// from the active binding.
174    #[arg(help_heading = "Body parameters", long, value_name="key=value", value_parser=parse_key_val::<String, Value>)]
175    binding_profile: Option<Vec<(String, Value)>>,
176
177    /// The type of vNIC which this port should be attached to. This is used to
178    /// determine which mechanism driver(s) to be used to bind the port. The
179    /// valid values are `normal`, `macvtap`, `direct`, `baremetal`,
180    /// `direct-physical`, `virtio-forwarder`, `smart-nic`, `vdpa`,
181    /// `accelerator-direct`, `accelerator-direct-physical` and
182    /// `remote-managed`. What type of vNIC is actually available depends on
183    /// deployments. The default is `normal`.
184    #[arg(help_heading = "Body parameters", long)]
185    binding_vnic_type: Option<BindingVnicType>,
186
187    /// Status of the underlying data plane of a port.
188    #[arg(help_heading = "Body parameters", long)]
189    data_plane_status: Option<DataPlaneStatus>,
190
191    /// A human-readable description for the resource. Default is an empty
192    /// string.
193    #[arg(help_heading = "Body parameters", long)]
194    description: Option<String>,
195
196    /// The ID of the device that uses this port. For example, a server
197    /// instance or a logical router.
198    #[arg(help_heading = "Body parameters", long)]
199    device_id: Option<String>,
200
201    /// The entity type that uses this port. For example, `compute:nova`
202    /// (server instance), `network:dhcp` (DHCP agent) or
203    /// `network:router_interface` (router interface).
204    #[arg(help_heading = "Body parameters", long)]
205    device_owner: Option<String>,
206
207    /// A valid DNS domain.
208    #[arg(help_heading = "Body parameters", long)]
209    dns_domain: Option<String>,
210
211    /// A valid DNS name.
212    #[arg(help_heading = "Body parameters", long)]
213    dns_name: Option<String>,
214
215    /// A set of zero or more extra DHCP option pairs. An option pair consists
216    /// of an option value and name.
217    ///
218    /// Parameter is an array, may be provided multiple times.
219    #[arg(action=clap::ArgAction::Append, help_heading = "Body parameters", long, value_name="JSON", value_parser=openstack_cli_core::common::parse_json)]
220    extra_dhcp_opts: Option<Vec<Value>>,
221
222    /// The IP addresses for the port. If you would like to assign multiple IP
223    /// addresses for the port, specify multiple entries in this field. Each
224    /// entry consists of IP address (`ip_address`) and the subnet ID from
225    /// which the IP address is assigned (`subnet_id`).
226    ///
227    /// - If you specify both a subnet ID and an IP address, OpenStack
228    ///   Networking tries to allocate the IP address on that subnet to the
229    ///   port.
230    /// - If you specify only a subnet ID, OpenStack Networking allocates an
231    ///   available IP from that subnet to the port.
232    /// - If you specify only an IP address, OpenStack Networking tries to
233    ///   allocate the IP address if the address is a valid IP for any of the
234    ///   subnets on the specified network.
235    ///
236    /// Parameter is an array, may be provided multiple times.
237    #[arg(action=clap::ArgAction::Append, help_heading = "Body parameters", long, value_name="JSON", value_parser=openstack_cli_core::common::parse_json)]
238    fixed_ips: Option<Vec<Value>>,
239
240    /// Admin-only. A dict, at the top level keyed by mechanism driver aliases
241    /// (as defined in setup.cfg). To following values can be used to control
242    /// Open vSwitch’s Userspace Tx packet steering feature:
243    ///
244    /// - `{"openvswitch": {"other_config": {"tx-steering": "hash"}}}`
245    /// - `{"openvswitch": {"other_config": {"tx-steering": "thread"}}}`
246    ///
247    /// If omitted the default is defined by Open vSwitch. The field cannot be
248    /// longer than 4095 characters.
249    #[arg(help_heading = "Body parameters", long, value_name="key=value", value_parser=parse_key_val::<String, Value>)]
250    hints: Option<Vec<(String, Value)>>,
251
252    /// The MAC address of the port. By default, only administrative users and
253    /// users with advsvc role can change this value.
254    #[arg(help_heading = "Body parameters", long)]
255    mac_address: Option<String>,
256
257    /// Human-readable name of the resource. Default is an empty string.
258    #[arg(help_heading = "Body parameters", long)]
259    name: Option<String>,
260
261    /// The port NUMA affinity policy requested during the virtual machine
262    /// scheduling. Values: `None`, `required`, `preferred` or `legacy`.
263    #[arg(help_heading = "Body parameters", long)]
264    numa_affinity_policy: Option<NumaAffinityPolicy>,
265
266    /// The port security status. A valid value is enabled (`true`) or disabled
267    /// (`false`). If port security is enabled for the port, security group
268    /// rules and anti-spoofing rules are applied to the traffic on the port.
269    /// If disabled, no such rules are applied.
270    #[arg(action=clap::ArgAction::Set, help_heading = "Body parameters", long)]
271    port_security_enabled: Option<bool>,
272
273    /// QoS policy associated with the port.
274    #[arg(help_heading = "Body parameters", long)]
275    qos_policy_id: Option<String>,
276
277    /// Set explicit NULL for the qos_policy_id
278    #[arg(help_heading = "Body parameters", long, action = clap::ArgAction::SetTrue, conflicts_with = "qos_policy_id")]
279    no_qos_policy_id: bool,
280
281    /// The IDs of security groups applied to the port.
282    ///
283    /// Parameter is an array, may be provided multiple times.
284    #[arg(action=clap::ArgAction::Append, help_heading = "Body parameters", long)]
285    security_groups: Option<Vec<String>>,
286}
287
288impl PortCommand {
289    /// Perform command action
290    pub async fn take_action<C: CliArgs>(
291        &self,
292        parsed_args: &C,
293        client: &mut AsyncOpenStack,
294    ) -> Result<(), OpenStackCliError> {
295        info!("Set Port");
296
297        let op = OutputProcessor::from_args(parsed_args, Some("network.port"), Some("set"));
298        op.validate_args(parsed_args)?;
299
300        let mut find_builder = find::Request::builder();
301
302        find_builder.id(&self.path.id);
303
304        let find_ep = find_builder
305            .build()
306            .map_err(|x| OpenStackCliError::EndpointBuild(x.to_string()))?;
307        let find_data: serde_json::Value = find(find_ep).query_async(client).await?;
308
309        let mut ep_builder = set::Request::builder();
310
311        let resource_id = find_data["id"]
312            .as_str()
313            .ok_or_else(|| eyre::eyre!("resource ID must be a string"))?
314            .to_string();
315        ep_builder.id(resource_id.clone());
316
317        // Set body parameters
318        // Set Request.port data
319        let args = &self.port;
320        let mut port_builder = set::PortBuilder::default();
321        if let Some(val) = &args.admin_state_up {
322            port_builder.admin_state_up(*val);
323        }
324
325        if let Some(val) = &args.allowed_address_pairs {
326            let allowed_address_pairs_builder: Vec<set::AllowedAddressPairs> = val
327                .iter()
328                .flat_map(|v| serde_json::from_value::<set::AllowedAddressPairs>(v.to_owned()))
329                .collect::<Vec<set::AllowedAddressPairs>>();
330            port_builder.allowed_address_pairs(allowed_address_pairs_builder);
331        }
332
333        if let Some(val) = &args.binding_host_id {
334            port_builder.binding_host_id(val);
335        }
336
337        if let Some(val) = &args.binding_profile {
338            port_builder.binding_profile(val.iter().cloned());
339        }
340
341        if let Some(val) = &args.binding_vnic_type {
342            let tmp = match val {
343                BindingVnicType::AcceleratorDirect => set::BindingVnicType::AcceleratorDirect,
344                BindingVnicType::AcceleratorDirectPhysical => {
345                    set::BindingVnicType::AcceleratorDirectPhysical
346                }
347                BindingVnicType::Baremetal => set::BindingVnicType::Baremetal,
348                BindingVnicType::Direct => set::BindingVnicType::Direct,
349                BindingVnicType::DirectPhysical => set::BindingVnicType::DirectPhysical,
350                BindingVnicType::Macvtap => set::BindingVnicType::Macvtap,
351                BindingVnicType::Normal => set::BindingVnicType::Normal,
352                BindingVnicType::RemoteManaged => set::BindingVnicType::RemoteManaged,
353                BindingVnicType::SmartNic => set::BindingVnicType::SmartNic,
354                BindingVnicType::Vdpa => set::BindingVnicType::Vdpa,
355                BindingVnicType::VirtioForwarder => set::BindingVnicType::VirtioForwarder,
356            };
357            port_builder.binding_vnic_type(tmp);
358        }
359
360        if let Some(val) = &args.data_plane_status {
361            let tmp = match val {
362                DataPlaneStatus::Active => set::DataPlaneStatus::Active,
363                DataPlaneStatus::Down => set::DataPlaneStatus::Down,
364            };
365            port_builder.data_plane_status(tmp);
366        }
367
368        if let Some(val) = &args.description {
369            port_builder.description(val);
370        }
371
372        if let Some(val) = &args.device_id {
373            port_builder.device_id(val);
374        }
375
376        if let Some(val) = &args.device_owner {
377            port_builder.device_owner(val);
378        }
379
380        if let Some(val) = &args.dns_domain {
381            port_builder.dns_domain(val);
382        }
383
384        if let Some(val) = &args.dns_name {
385            port_builder.dns_name(val);
386        }
387
388        if let Some(val) = &args.extra_dhcp_opts {
389            port_builder.extra_dhcp_opts(
390                val.iter()
391                    .map(|v| {
392                        v.as_object()
393                            .ok_or_else(|| {
394                                eyre::eyre!("extra_dhcp_opts must be a valid json object")
395                            })
396                            .and_then(|obj| {
397                                obj.into_iter()
398                                    .map(|(k, val)| -> eyre::Result<_> {
399                                        Ok((
400                                            k.into(),
401                                            serde_json::from_value::<Value>(val.clone())?,
402                                        ))
403                                    })
404                                    .collect::<Result<BTreeMap<_, _>, _>>()
405                            })
406                    })
407                    .collect::<Result<Vec<_>, _>>()?,
408            );
409        }
410
411        if let Some(val) = &args.fixed_ips {
412            let fixed_ips_builder: Vec<set::FixedIps> = val
413                .iter()
414                .flat_map(|v| serde_json::from_value::<set::FixedIps>(v.to_owned()))
415                .collect::<Vec<set::FixedIps>>();
416            port_builder.fixed_ips(fixed_ips_builder);
417        }
418
419        if let Some(val) = &args.hints {
420            port_builder.hints(val.iter().cloned());
421        }
422
423        if let Some(val) = &args.mac_address {
424            port_builder.mac_address(val);
425        }
426
427        if let Some(val) = &args.name {
428            port_builder.name(val);
429        }
430
431        if let Some(val) = &args.numa_affinity_policy {
432            let tmp = match val {
433                NumaAffinityPolicy::Legacy => set::NumaAffinityPolicy::Legacy,
434                NumaAffinityPolicy::Preferred => set::NumaAffinityPolicy::Preferred,
435                NumaAffinityPolicy::Required => set::NumaAffinityPolicy::Required,
436                NumaAffinityPolicy::Socket => set::NumaAffinityPolicy::Socket,
437            };
438            port_builder.numa_affinity_policy(tmp);
439        }
440
441        if let Some(val) = &args.port_security_enabled {
442            port_builder.port_security_enabled(*val);
443        }
444
445        if let Some(val) = &args.qos_policy_id {
446            port_builder.qos_policy_id(Some(val.into()));
447        } else if args.no_qos_policy_id {
448            port_builder.qos_policy_id(None);
449        }
450
451        if let Some(val) = &args.security_groups {
452            port_builder.security_groups(val.iter().map(Into::into).collect::<Vec<_>>());
453        }
454
455        ep_builder.port(
456            port_builder
457                .build()
458                .wrap_err("error preparing the request data")?,
459        );
460
461        let ep = ep_builder
462            .build()
463            .map_err(|x| OpenStackCliError::EndpointBuild(x.to_string()))?;
464
465        let data: serde_json::Value = ep.query_async(client).await?;
466
467        op.output_single::<response::set::PortResponse>(data.clone())?;
468        // Show command specific hints
469        op.show_command_hint()?;
470        Ok(())
471    }
472}