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