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}