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}