openstack_sdk/api/compute/v2/server/
create_219.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//! Creates a server.
19//!
20//! The progress of this operation depends on the location of the requested
21//! image, network I/O, host load, selected flavor, and other factors.
22//!
23//! To check the progress of the request, make a `GET /servers/{id}` request.
24//! This call returns a progress attribute, which is a percentage value from 0
25//! to 100.
26//!
27//! The `Location` header returns the full URL to the newly created server and
28//! is available as a `self` and `bookmark` link in the server representation.
29//!
30//! When you create a server, the response shows only the server ID, its links,
31//! and the admin password. You can get additional attributes through
32//! subsequent `GET` requests on the server.
33//!
34//! Include the `block_device_mapping_v2` parameter in the create request body
35//! to boot a server from a volume.
36//!
37//! Include the `key_name` parameter in the create request body to add a
38//! keypair to the server when you create it. To create a keypair, make a
39//! [create keypair](https://docs.openstack.org/api-ref/compute/#create-or-import-keypair)
40//! request.
41//!
42//! **Preconditions**
43//!
44//! **Asynchronous postconditions**
45//!
46//! **Troubleshooting**
47//!
48//! Normal response codes: 202
49//!
50//! Error response codes: badRequest(400), unauthorized(401), forbidden(403),
51//! itemNotFound(404), conflict(409)
52//!
53use derive_builder::Builder;
54use http::{HeaderMap, HeaderName, HeaderValue};
55
56use crate::api::rest_endpoint_prelude::*;
57
58use serde::Deserialize;
59use serde::Serialize;
60use serde_json::Value;
61use std::borrow::Cow;
62use std::collections::BTreeMap;
63
64#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
65#[builder(setter(strip_option))]
66pub struct Networks<'a> {
67    /// Schedule the server on a host in the network specified with this
68    /// parameter and a cidr (`os:scheduler_hints.cidr`). It is available when
69    /// `SimpleCIDRAffinityFilter` is available on cloud side.
70    #[serde(skip_serializing_if = "Option::is_none")]
71    #[builder(default, setter(into))]
72    pub(crate) fixed_ip: Option<Cow<'a, str>>,
73
74    #[serde(skip_serializing_if = "Option::is_none")]
75    #[builder(default, setter(into))]
76    pub(crate) port: Option<Option<Cow<'a, str>>>,
77
78    #[serde(skip_serializing_if = "Option::is_none")]
79    #[builder(default, setter(into))]
80    pub(crate) uuid: Option<Cow<'a, str>>,
81}
82
83#[derive(Debug, Deserialize, Clone, Serialize)]
84pub enum OsDcfDiskConfig {
85    #[serde(rename = "AUTO")]
86    Auto,
87    #[serde(rename = "MANUAL")]
88    Manual,
89}
90
91#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
92#[builder(setter(strip_option))]
93pub struct Personality<'a> {
94    #[serde(skip_serializing_if = "Option::is_none")]
95    #[builder(default, setter(into))]
96    pub(crate) contents: Option<Cow<'a, str>>,
97
98    #[serde(skip_serializing_if = "Option::is_none")]
99    #[builder(default, setter(into))]
100    pub(crate) path: Option<Cow<'a, str>>,
101}
102
103#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
104#[builder(setter(strip_option))]
105pub struct BlockDeviceMapping<'a> {
106    #[serde(skip_serializing_if = "Option::is_none")]
107    #[builder(default, setter(into))]
108    pub(crate) connection_info: Option<Cow<'a, str>>,
109
110    /// Indicates whether a config drive enables metadata injection. The
111    /// config_drive setting provides information about a drive that the
112    /// instance can mount at boot time. The instance reads files from the
113    /// drive to get information that is normally available through the
114    /// metadata service. This metadata is different from the user data. Not
115    /// all cloud providers enable the `config_drive`. Read more in the
116    /// [OpenStack End User Guide](https://docs.openstack.org/nova/latest/user/config-drive.html).
117    #[serde(skip_serializing_if = "Option::is_none")]
118    #[builder(default, setter(into))]
119    pub(crate) delete_on_termination: Option<bool>,
120
121    #[serde(skip_serializing_if = "Option::is_none")]
122    #[builder(default, setter(into))]
123    pub(crate) device_name: Option<Cow<'a, str>>,
124
125    #[serde(skip_serializing_if = "Option::is_none")]
126    #[builder(default, setter(into))]
127    pub(crate) no_device: Option<Value>,
128
129    #[serde(skip_serializing_if = "Option::is_none")]
130    #[builder(default, setter(into))]
131    pub(crate) snapshot_id: Option<Cow<'a, str>>,
132
133    #[serde(skip_serializing_if = "Option::is_none")]
134    #[builder(default, setter(into))]
135    pub(crate) virtual_name: Option<Cow<'a, str>>,
136
137    #[serde(skip_serializing_if = "Option::is_none")]
138    #[builder(default, setter(into))]
139    pub(crate) volume_id: Option<Cow<'a, str>>,
140
141    #[serde(skip_serializing_if = "Option::is_none")]
142    #[builder(default, setter(into))]
143    pub(crate) volume_size: Option<i32>,
144}
145
146#[derive(Debug, Deserialize, Clone, Serialize)]
147pub enum SourceType {
148    #[serde(rename = "blank")]
149    Blank,
150    #[serde(rename = "image")]
151    Image,
152    #[serde(rename = "snapshot")]
153    Snapshot,
154    #[serde(rename = "volume")]
155    Volume,
156}
157
158#[derive(Debug, Deserialize, Clone, Serialize)]
159pub enum DestinationType {
160    #[serde(rename = "local")]
161    Local,
162    #[serde(rename = "volume")]
163    Volume,
164}
165
166#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
167#[builder(setter(strip_option))]
168pub struct BlockDeviceMappingV2<'a> {
169    #[serde(skip_serializing_if = "Option::is_none")]
170    #[builder(default, setter(into))]
171    pub(crate) boot_index: Option<Option<Cow<'a, str>>>,
172
173    #[serde(skip_serializing_if = "Option::is_none")]
174    #[builder(default, setter(into))]
175    pub(crate) connection_info: Option<Cow<'a, str>>,
176
177    #[serde(skip_serializing_if = "Option::is_none")]
178    #[builder(default, setter(into))]
179    pub(crate) delete_on_termination: Option<bool>,
180
181    #[serde(skip_serializing_if = "Option::is_none")]
182    #[builder(default)]
183    pub(crate) destination_type: Option<DestinationType>,
184
185    #[serde(skip_serializing_if = "Option::is_none")]
186    #[builder(default, setter(into))]
187    pub(crate) device_name: Option<Cow<'a, str>>,
188
189    #[serde(skip_serializing_if = "Option::is_none")]
190    #[builder(default, setter(into))]
191    pub(crate) device_type: Option<Cow<'a, str>>,
192
193    #[serde(skip_serializing_if = "Option::is_none")]
194    #[builder(default, setter(into))]
195    pub(crate) disk_bus: Option<Cow<'a, str>>,
196
197    #[serde(skip_serializing_if = "Option::is_none")]
198    #[builder(default, setter(into))]
199    pub(crate) guest_format: Option<Cow<'a, str>>,
200
201    #[serde(skip_serializing_if = "Option::is_none")]
202    #[builder(default, setter(into))]
203    pub(crate) image_id: Option<Cow<'a, str>>,
204
205    #[serde(skip_serializing_if = "Option::is_none")]
206    #[builder(default, setter(into))]
207    pub(crate) no_device: Option<Value>,
208
209    #[serde(skip_serializing_if = "Option::is_none")]
210    #[builder(default, setter(into))]
211    pub(crate) snapshot_id: Option<Cow<'a, str>>,
212
213    #[serde(skip_serializing_if = "Option::is_none")]
214    #[builder(default)]
215    pub(crate) source_type: Option<SourceType>,
216
217    #[serde(skip_serializing_if = "Option::is_none")]
218    #[builder(default, setter(into))]
219    pub(crate) uuid: Option<Cow<'a, str>>,
220
221    #[serde(skip_serializing_if = "Option::is_none")]
222    #[builder(default, setter(into))]
223    pub(crate) virtual_name: Option<Cow<'a, str>>,
224
225    #[serde(skip_serializing_if = "Option::is_none")]
226    #[builder(default, setter(into))]
227    pub(crate) volume_id: Option<Cow<'a, str>>,
228
229    #[serde(skip_serializing_if = "Option::is_none")]
230    #[builder(default, setter(into))]
231    pub(crate) volume_size: Option<i32>,
232}
233
234#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
235#[builder(setter(strip_option))]
236pub struct SecurityGroups<'a> {
237    /// A target cell name. Schedule the server in a host in the cell
238    /// specified. It is available when `TargetCellFilter` is available on
239    /// cloud side that is cell v1 environment.
240    #[serde(skip_serializing_if = "Option::is_none")]
241    #[builder(default, setter(into))]
242    pub(crate) name: Option<Cow<'a, str>>,
243}
244
245/// A `server` object.
246#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
247#[builder(setter(strip_option))]
248pub struct Server<'a> {
249    /// IPv4 address that should be used to access this server.
250    #[serde(rename = "accessIPv4", skip_serializing_if = "Option::is_none")]
251    #[builder(default, setter(into))]
252    pub(crate) access_ipv4: Option<Cow<'a, str>>,
253
254    /// IPv6 address that should be used to access this server.
255    #[serde(rename = "accessIPv6", skip_serializing_if = "Option::is_none")]
256    #[builder(default, setter(into))]
257    pub(crate) access_ipv6: Option<Cow<'a, str>>,
258
259    /// The administrative password of the server. If you omit this parameter,
260    /// the operation generates a new password.
261    #[serde(rename = "adminPass", skip_serializing_if = "Option::is_none")]
262    #[builder(default, setter(into))]
263    pub(crate) admin_pass: Option<Cow<'a, str>>,
264
265    /// A target cell name. Schedule the server in a host in the cell
266    /// specified. It is available when `TargetCellFilter` is available on
267    /// cloud side that is cell v1 environment.
268    #[serde(skip_serializing_if = "Option::is_none")]
269    #[builder(default, setter(into))]
270    pub(crate) availability_zone: Option<Cow<'a, str>>,
271
272    #[serde(skip_serializing_if = "Option::is_none")]
273    #[builder(default, setter(into))]
274    pub(crate) block_device_mapping: Option<Vec<BlockDeviceMapping<'a>>>,
275
276    /// Enables fine grained control of the block device mapping for an
277    /// instance. This is typically used for booting servers from volumes. An
278    /// example format would look as follows:
279    ///
280    /// > ```text
281    /// > "block_device_mapping_v2": [{
282    /// >     "boot_index": "0",
283    /// >     "uuid": "ac408821-c95a-448f-9292-73986c790911",
284    /// >     "source_type": "image",
285    /// >     "volume_size": "25",
286    /// >     "destination_type": "volume",
287    /// >     "delete_on_termination": true,
288    /// >     "tag": "disk1",
289    /// >     "disk_bus": "scsi"}]
290    /// >
291    /// > ```
292    ///
293    /// In microversion 2.32, `tag` is an optional string attribute that can be
294    /// used to assign a tag to the block device. This tag is then exposed to
295    /// the guest in the metadata API and the config drive and is associated to
296    /// hardware metadata for that block device, such as bus (ex: SCSI), bus
297    /// address (ex: 1:0:2:0), and serial.
298    ///
299    /// A bug has caused the `tag` attribute to no longer be accepted starting
300    /// with version 2.33. It has been restored in version 2.42.
301    #[serde(skip_serializing_if = "Option::is_none")]
302    #[builder(default, setter(into))]
303    pub(crate) block_device_mapping_v2: Option<Vec<BlockDeviceMappingV2<'a>>>,
304
305    /// Indicates whether a config drive enables metadata injection. The
306    /// config_drive setting provides information about a drive that the
307    /// instance can mount at boot time. The instance reads files from the
308    /// drive to get information that is normally available through the
309    /// metadata service. This metadata is different from the user data. Not
310    /// all cloud providers enable the `config_drive`. Read more in the
311    /// [OpenStack End User Guide](https://docs.openstack.org/nova/latest/user/config-drive.html).
312    #[serde(skip_serializing_if = "Option::is_none")]
313    #[builder(default, setter(into))]
314    pub(crate) config_drive: Option<bool>,
315
316    /// A free form description of the server. Limited to 255 characters in
317    /// length. Before microversion 2.19 this was set to the server name.
318    ///
319    /// **New in version 2.19**
320    #[serde(skip_serializing_if = "Option::is_none")]
321    #[builder(default, setter(into))]
322    pub(crate) description: Option<Option<Cow<'a, str>>>,
323
324    /// The flavor reference, as an ID (including a UUID) or full URL, for the
325    /// flavor for your server instance.
326    #[serde(rename = "flavorRef")]
327    #[builder(setter(into))]
328    pub(crate) flavor_ref: Cow<'a, str>,
329
330    /// The UUID of the image to use for your server instance. This is not
331    /// required in case of boot from volume. In all other cases it is required
332    /// and must be a valid UUID otherwise API will return 400.
333    #[serde(rename = "imageRef", skip_serializing_if = "Option::is_none")]
334    #[builder(default, setter(into))]
335    pub(crate) image_ref: Option<Cow<'a, str>>,
336
337    /// A target cell name. Schedule the server in a host in the cell
338    /// specified. It is available when `TargetCellFilter` is available on
339    /// cloud side that is cell v1 environment.
340    #[serde(skip_serializing_if = "Option::is_none")]
341    #[builder(default, setter(into))]
342    pub(crate) key_name: Option<Cow<'a, str>>,
343
344    #[serde(skip_serializing_if = "Option::is_none")]
345    #[builder(default, setter(into))]
346    pub(crate) max_count: Option<i32>,
347
348    /// Metadata key and value pairs. The maximum size of the metadata key and
349    /// value is 255 bytes each.
350    #[serde(skip_serializing_if = "Option::is_none")]
351    #[builder(default, private, setter(into, name = "_metadata"))]
352    pub(crate) metadata: Option<BTreeMap<Cow<'a, str>, Cow<'a, str>>>,
353
354    #[serde(skip_serializing_if = "Option::is_none")]
355    #[builder(default, setter(into))]
356    pub(crate) min_count: Option<i32>,
357
358    /// A target cell name. Schedule the server in a host in the cell
359    /// specified. It is available when `TargetCellFilter` is available on
360    /// cloud side that is cell v1 environment.
361    #[serde()]
362    #[builder(setter(into))]
363    pub(crate) name: Cow<'a, str>,
364
365    /// A list of `network` object. Required parameter when there are multiple
366    /// networks defined for the tenant. When you do not specify the networks
367    /// parameter, the server attaches to the only network created for the
368    /// current tenant. Optionally, you can create one or more NICs on the
369    /// server. To provision the server instance with a NIC for a network,
370    /// specify the UUID of the network in the `uuid` attribute in a `networks`
371    /// object. To provision the server instance with a NIC for an already
372    /// existing port, specify the port-id in the `port` attribute in a
373    /// `networks` object.
374    ///
375    /// If multiple networks are defined, the order in which they appear in the
376    /// guest operating system will not necessarily reflect the order in which
377    /// they are given in the server boot request. Guests should therefore not
378    /// depend on device order to deduce any information about their network
379    /// devices. Instead, device role tags should be used: introduced in 2.32,
380    /// broken in 2.37, and re-introduced and fixed in 2.42, the `tag` is an
381    /// optional, string attribute that can be used to assign a tag to a
382    /// virtual network interface. This tag is then exposed to the guest in the
383    /// metadata API and the config drive and is associated to hardware
384    /// metadata for that network interface, such as bus (ex: PCI), bus address
385    /// (ex: 0000:00:02.0), and MAC address.
386    ///
387    /// A bug has caused the `tag` attribute to no longer be accepted starting
388    /// with version 2.37. Therefore, network interfaces could only be tagged
389    /// in versions 2.32 to 2.36 inclusively. Version 2.42 has restored the
390    /// `tag` attribute.
391    ///
392    /// Starting with microversion 2.37, this field is required and the special
393    /// string values *auto* and *none* can be specified for networks. *auto*
394    /// tells the Compute service to use a network that is available to the
395    /// project, if one exists. If one does not exist, the Compute service will
396    /// attempt to automatically allocate a network for the project (if
397    /// possible). *none* tells the Compute service to not allocate a network
398    /// for the instance. The *auto* and *none* values cannot be used with any
399    /// other network values, including other network uuids, ports, fixed IPs
400    /// or device tags. These are requested as strings for the networks value,
401    /// not in a list. See the associated example.
402    #[serde(skip_serializing_if = "Option::is_none")]
403    #[builder(default, setter(into))]
404    pub(crate) networks: Option<Vec<Networks<'a>>>,
405
406    /// Controls how the API partitions the disk when you create, rebuild, or
407    /// resize servers. A server inherits the `OS-DCF:diskConfig` value from
408    /// the image from which it was created, and an image inherits the
409    /// `OS-DCF:diskConfig` value from the server from which it was created. To
410    /// override the inherited setting, you can include this attribute in the
411    /// request body of a server create, rebuild, or resize request. If the
412    /// `OS-DCF:diskConfig` value for an image is `MANUAL`, you cannot create a
413    /// server from that image and set its `OS-DCF:diskConfig` value to `AUTO`.
414    /// A valid value is:
415    ///
416    /// - `AUTO`. The API builds the server with a single partition the size of
417    ///   the target flavor disk. The API automatically adjusts the file system
418    ///   to fit the entire partition.
419    /// - `MANUAL`. The API builds the server by using whatever partition
420    ///   scheme and file system is in the source image. If the target flavor
421    ///   disk is larger, the API does not partition the remaining disk space.
422    #[serde(rename = "OS-DCF:diskConfig", skip_serializing_if = "Option::is_none")]
423    #[builder(default)]
424    pub(crate) os_dcf_disk_config: Option<OsDcfDiskConfig>,
425
426    /// The file path and contents, text only, to inject into the server at
427    /// launch. The maximum size of the file path data is 255 bytes. The
428    /// maximum limit is the number of allowed bytes in the decoded, rather
429    /// than encoded, data.
430    ///
431    /// **Available until version 2.56**
432    #[serde(skip_serializing_if = "Option::is_none")]
433    #[builder(default, setter(into))]
434    pub(crate) personality: Option<Vec<Personality<'a>>>,
435
436    /// Indicates whether a config drive enables metadata injection. The
437    /// config_drive setting provides information about a drive that the
438    /// instance can mount at boot time. The instance reads files from the
439    /// drive to get information that is normally available through the
440    /// metadata service. This metadata is different from the user data. Not
441    /// all cloud providers enable the `config_drive`. Read more in the
442    /// [OpenStack End User Guide](https://docs.openstack.org/nova/latest/user/config-drive.html).
443    #[serde(skip_serializing_if = "Option::is_none")]
444    #[builder(default, setter(into))]
445    pub(crate) return_reservation_id: Option<bool>,
446
447    /// One or more security groups. Specify the name of the security group in
448    /// the `name` attribute. If you omit this attribute, the API creates the
449    /// server in the `default` security group. Requested security groups are
450    /// not applied to pre-existing ports.
451    #[serde(skip_serializing_if = "Option::is_none")]
452    #[builder(default, setter(into))]
453    pub(crate) security_groups: Option<Vec<SecurityGroups<'a>>>,
454
455    /// Configuration information or scripts to use upon launch. Must be Base64
456    /// encoded. Restricted to 65535 bytes.
457    ///
458    /// Note
459    ///
460    /// The `null` value allowed in Nova legacy v2 API, but due to the strict
461    /// input validation, it isn’t allowed in Nova v2.1 API.
462    #[serde(skip_serializing_if = "Option::is_none")]
463    #[builder(default, setter(into))]
464    pub(crate) user_data: Option<Cow<'a, str>>,
465}
466
467impl<'a> ServerBuilder<'a> {
468    /// Metadata key and value pairs. The maximum size of the metadata key and
469    /// value is 255 bytes each.
470    pub fn metadata<I, K, V>(&mut self, iter: I) -> &mut Self
471    where
472        I: Iterator<Item = (K, V)>,
473        K: Into<Cow<'a, str>>,
474        V: Into<Cow<'a, str>>,
475    {
476        self.metadata
477            .get_or_insert(None)
478            .get_or_insert_with(BTreeMap::new)
479            .extend(iter.map(|(k, v)| (k.into(), v.into())));
480        self
481    }
482}
483
484/// The dictionary of data to send to the scheduler. Alternatively, you can
485/// specify `OS-SCH-HNT:scheduler_hints` as the key in the request body.
486///
487/// Note
488///
489/// This is a top-level key in the request body, not part of the server portion
490/// of the request body.
491///
492/// There are a few caveats with scheduler hints:
493///
494/// - The request validation schema is per hint. For example, some require a
495///   single string value, and some accept a list of values.
496/// - Hints are only used based on the cloud scheduler configuration, which
497///   varies per deployment.
498/// - Hints are pluggable per deployment, meaning that a cloud can have custom
499///   hints which may not be available in another cloud.
500///
501/// For these reasons, it is important to consult each cloud’s user
502/// documentation to know what is available for scheduler hints.
503#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
504#[builder(setter(strip_option))]
505pub struct OsSchedulerHints<'a> {
506    /// Schedule the server on a host in the network specified with this
507    /// parameter and a cidr (`os:scheduler_hints.cidr`). It is available when
508    /// `SimpleCIDRAffinityFilter` is available on cloud side.
509    #[serde(skip_serializing_if = "Option::is_none")]
510    #[builder(default, setter(into))]
511    pub(crate) build_near_host_ip: Option<Cow<'a, str>>,
512
513    /// Schedule the server on a host in the network specified with an IP
514    /// address (`os:scheduler_hints:build_near_host_ip`) and this parameter.
515    /// If `os:scheduler_hints:build_near_host_ip` is specified and this
516    /// parameter is omitted, `/24` is used. It is available when
517    /// `SimpleCIDRAffinityFilter` is available on cloud side.
518    #[serde(skip_serializing_if = "Option::is_none")]
519    #[builder(default, setter(into))]
520    pub(crate) cidr: Option<Cow<'a, str>>,
521
522    /// A list of cell routes or a cell route (string). Schedule the server in
523    /// a cell that is not specified. It is available when
524    /// `DifferentCellFilter` is available on cloud side that is cell v1
525    /// environment.
526    #[serde(skip_serializing_if = "Option::is_none")]
527    #[builder(default, setter(into))]
528    pub(crate) different_cell: Option<Vec<Cow<'a, str>>>,
529
530    /// A list of server UUIDs or a server UUID. Schedule the server on a
531    /// different host from a set of servers. It is available when
532    /// `DifferentHostFilter` is available on cloud side.
533    #[serde(skip_serializing_if = "Option::is_none")]
534    #[builder(default, setter(into))]
535    pub(crate) different_host: Option<Vec<Cow<'a, str>>>,
536
537    /// The server group UUID. Schedule the server according to a policy of the
538    /// server group (`anti-affinity`, `affinity`, `soft-anti-affinity` or
539    /// `soft-affinity`). It is available when `ServerGroupAffinityFilter`,
540    /// `ServerGroupAntiAffinityFilter`, `ServerGroupSoftAntiAffinityWeigher`,
541    /// `ServerGroupSoftAffinityWeigher` are available on cloud side.
542    #[serde(skip_serializing_if = "Option::is_none")]
543    #[builder(default, setter(into))]
544    pub(crate) group: Option<Cow<'a, str>>,
545
546    /// Schedule the server by using a custom filter in JSON format. For
547    /// example:
548    ///
549    /// ```text
550    /// "query": "[\">=\",\"$free_ram_mb\",1024]"
551    ///
552    /// ```
553    ///
554    /// It is available when `JsonFilter` is available on cloud side.
555    #[serde(skip_serializing_if = "Option::is_none")]
556    #[builder(default, setter(into))]
557    pub(crate) query: Option<Value>,
558
559    /// A list of server UUIDs or a server UUID. Schedule the server on the
560    /// same host as another server in a set of servers. It is available when
561    /// `SameHostFilter` is available on cloud side.
562    #[serde(skip_serializing_if = "Option::is_none")]
563    #[builder(default, setter(into))]
564    pub(crate) same_host: Option<Vec<Cow<'a, str>>>,
565
566    /// A target cell name. Schedule the server in a host in the cell
567    /// specified. It is available when `TargetCellFilter` is available on
568    /// cloud side that is cell v1 environment.
569    #[serde(skip_serializing_if = "Option::is_none")]
570    #[builder(default, setter(into))]
571    pub(crate) target_cell: Option<Cow<'a, str>>,
572
573    #[builder(setter(name = "_properties"), default, private)]
574    #[serde(flatten)]
575    _properties: BTreeMap<Cow<'a, str>, Value>,
576}
577
578impl<'a> OsSchedulerHintsBuilder<'a> {
579    pub fn properties<I, K, V>(&mut self, iter: I) -> &mut Self
580    where
581        I: Iterator<Item = (K, V)>,
582        K: Into<Cow<'a, str>>,
583        V: Into<Value>,
584    {
585        self._properties
586            .get_or_insert_with(BTreeMap::new)
587            .extend(iter.map(|(k, v)| (k.into(), v.into())));
588        self
589    }
590}
591
592#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
593#[builder(setter(strip_option))]
594pub struct OsSchHntSchedulerHints<'a> {
595    /// Schedule the server on a host in the network specified with this
596    /// parameter and a cidr (`os:scheduler_hints.cidr`). It is available when
597    /// `SimpleCIDRAffinityFilter` is available on cloud side.
598    #[serde(skip_serializing_if = "Option::is_none")]
599    #[builder(default, setter(into))]
600    pub(crate) build_near_host_ip: Option<Cow<'a, str>>,
601
602    /// Schedule the server on a host in the network specified with an IP
603    /// address (`os:scheduler_hints:build_near_host_ip`) and this parameter.
604    /// If `os:scheduler_hints:build_near_host_ip` is specified and this
605    /// parameter is omitted, `/24` is used. It is available when
606    /// `SimpleCIDRAffinityFilter` is available on cloud side.
607    #[serde(skip_serializing_if = "Option::is_none")]
608    #[builder(default, setter(into))]
609    pub(crate) cidr: Option<Cow<'a, str>>,
610
611    /// A list of cell routes or a cell route (string). Schedule the server in
612    /// a cell that is not specified. It is available when
613    /// `DifferentCellFilter` is available on cloud side that is cell v1
614    /// environment.
615    #[serde(skip_serializing_if = "Option::is_none")]
616    #[builder(default, setter(into))]
617    pub(crate) different_cell: Option<Vec<Cow<'a, str>>>,
618
619    /// A list of server UUIDs or a server UUID. Schedule the server on a
620    /// different host from a set of servers. It is available when
621    /// `DifferentHostFilter` is available on cloud side.
622    #[serde(skip_serializing_if = "Option::is_none")]
623    #[builder(default, setter(into))]
624    pub(crate) different_host: Option<Vec<Cow<'a, str>>>,
625
626    /// The server group UUID. Schedule the server according to a policy of the
627    /// server group (`anti-affinity`, `affinity`, `soft-anti-affinity` or
628    /// `soft-affinity`). It is available when `ServerGroupAffinityFilter`,
629    /// `ServerGroupAntiAffinityFilter`, `ServerGroupSoftAntiAffinityWeigher`,
630    /// `ServerGroupSoftAffinityWeigher` are available on cloud side.
631    #[serde(skip_serializing_if = "Option::is_none")]
632    #[builder(default, setter(into))]
633    pub(crate) group: Option<Cow<'a, str>>,
634
635    /// Schedule the server by using a custom filter in JSON format. For
636    /// example:
637    ///
638    /// ```text
639    /// "query": "[\">=\",\"$free_ram_mb\",1024]"
640    ///
641    /// ```
642    ///
643    /// It is available when `JsonFilter` is available on cloud side.
644    #[serde(skip_serializing_if = "Option::is_none")]
645    #[builder(default, setter(into))]
646    pub(crate) query: Option<Value>,
647
648    /// A list of server UUIDs or a server UUID. Schedule the server on the
649    /// same host as another server in a set of servers. It is available when
650    /// `SameHostFilter` is available on cloud side.
651    #[serde(skip_serializing_if = "Option::is_none")]
652    #[builder(default, setter(into))]
653    pub(crate) same_host: Option<Vec<Cow<'a, str>>>,
654
655    /// A target cell name. Schedule the server in a host in the cell
656    /// specified. It is available when `TargetCellFilter` is available on
657    /// cloud side that is cell v1 environment.
658    #[serde(skip_serializing_if = "Option::is_none")]
659    #[builder(default, setter(into))]
660    pub(crate) target_cell: Option<Cow<'a, str>>,
661
662    #[builder(setter(name = "_properties"), default, private)]
663    #[serde(flatten)]
664    _properties: BTreeMap<Cow<'a, str>, Value>,
665}
666
667impl<'a> OsSchHntSchedulerHintsBuilder<'a> {
668    pub fn properties<I, K, V>(&mut self, iter: I) -> &mut Self
669    where
670        I: Iterator<Item = (K, V)>,
671        K: Into<Cow<'a, str>>,
672        V: Into<Value>,
673    {
674        self._properties
675            .get_or_insert_with(BTreeMap::new)
676            .extend(iter.map(|(k, v)| (k.into(), v.into())));
677        self
678    }
679}
680
681#[derive(Builder, Debug, Clone)]
682#[builder(setter(strip_option))]
683pub struct Request<'a> {
684    #[builder(default, setter(into))]
685    pub(crate) os_sch_hnt_scheduler_hints: Option<OsSchHntSchedulerHints<'a>>,
686
687    /// The dictionary of data to send to the scheduler. Alternatively, you can
688    /// specify `OS-SCH-HNT:scheduler_hints` as the key in the request body.
689    ///
690    /// Note
691    ///
692    /// This is a top-level key in the request body, not part of the server
693    /// portion of the request body.
694    ///
695    /// There are a few caveats with scheduler hints:
696    ///
697    /// - The request validation schema is per hint. For example, some require
698    ///   a single string value, and some accept a list of values.
699    /// - Hints are only used based on the cloud scheduler configuration, which
700    ///   varies per deployment.
701    /// - Hints are pluggable per deployment, meaning that a cloud can have
702    ///   custom hints which may not be available in another cloud.
703    ///
704    /// For these reasons, it is important to consult each cloud’s user
705    /// documentation to know what is available for scheduler hints.
706    #[builder(default, setter(into))]
707    pub(crate) os_scheduler_hints: Option<OsSchedulerHints<'a>>,
708
709    /// A `server` object.
710    #[builder(setter(into))]
711    pub(crate) server: Server<'a>,
712
713    #[builder(setter(name = "_headers"), default, private)]
714    _headers: Option<HeaderMap>,
715}
716impl<'a> Request<'a> {
717    /// Create a builder for the endpoint.
718    pub fn builder() -> RequestBuilder<'a> {
719        RequestBuilder::default()
720    }
721}
722
723impl RequestBuilder<'_> {
724    /// Add a single header to the Server.
725    pub fn header(&mut self, header_name: &'static str, header_value: &'static str) -> &mut Self
726where {
727        self._headers
728            .get_or_insert(None)
729            .get_or_insert_with(HeaderMap::new)
730            .insert(header_name, HeaderValue::from_static(header_value));
731        self
732    }
733
734    /// Add multiple headers.
735    pub fn headers<I, T>(&mut self, iter: I) -> &mut Self
736    where
737        I: Iterator<Item = T>,
738        T: Into<(Option<HeaderName>, HeaderValue)>,
739    {
740        self._headers
741            .get_or_insert(None)
742            .get_or_insert_with(HeaderMap::new)
743            .extend(iter.map(Into::into));
744        self
745    }
746}
747
748impl RestEndpoint for Request<'_> {
749    fn method(&self) -> http::Method {
750        http::Method::POST
751    }
752
753    fn endpoint(&self) -> Cow<'static, str> {
754        "servers".to_string().into()
755    }
756
757    fn parameters(&self) -> QueryParams {
758        QueryParams::default()
759    }
760
761    fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
762        let mut params = JsonBodyParams::default();
763
764        params.push("server", serde_json::to_value(&self.server)?);
765        if let Some(val) = &self.os_scheduler_hints {
766            params.push("os:scheduler_hints", serde_json::to_value(val)?);
767        }
768        if let Some(val) = &self.os_sch_hnt_scheduler_hints {
769            params.push("OS-SCH-HNT:scheduler_hints", serde_json::to_value(val)?);
770        }
771
772        params.into_body()
773    }
774
775    fn service_type(&self) -> ServiceType {
776        ServiceType::Compute
777    }
778
779    fn response_key(&self) -> Option<Cow<'static, str>> {
780        Some("server".into())
781    }
782
783    /// Returns headers to be set into the request
784    fn request_headers(&self) -> Option<&HeaderMap> {
785        self._headers.as_ref()
786    }
787
788    /// Returns required API version
789    fn api_version(&self) -> Option<ApiVersion> {
790        Some(ApiVersion::new(2, 19))
791    }
792}
793
794#[cfg(test)]
795mod tests {
796    use super::*;
797    #[cfg(feature = "sync")]
798    use crate::api::Query;
799    use crate::test::client::FakeOpenStackClient;
800    use crate::types::ServiceType;
801    use http::{HeaderName, HeaderValue};
802    use httpmock::MockServer;
803    use serde_json::json;
804
805    #[test]
806    fn test_service_type() {
807        assert_eq!(
808            Request::builder()
809                .server(
810                    ServerBuilder::default()
811                        .flavor_ref("foo")
812                        .name("foo")
813                        .build()
814                        .unwrap()
815                )
816                .build()
817                .unwrap()
818                .service_type(),
819            ServiceType::Compute
820        );
821    }
822
823    #[test]
824    fn test_response_key() {
825        assert_eq!(
826            Request::builder()
827                .server(
828                    ServerBuilder::default()
829                        .flavor_ref("foo")
830                        .name("foo")
831                        .build()
832                        .unwrap()
833                )
834                .build()
835                .unwrap()
836                .response_key()
837                .unwrap(),
838            "server"
839        );
840    }
841
842    #[cfg(feature = "sync")]
843    #[test]
844    fn endpoint() {
845        let server = MockServer::start();
846        let client = FakeOpenStackClient::new(server.base_url());
847        let mock = server.mock(|when, then| {
848            when.method(httpmock::Method::POST)
849                .path("/servers".to_string());
850
851            then.status(200)
852                .header("content-type", "application/json")
853                .json_body(json!({ "server": {} }));
854        });
855
856        let endpoint = Request::builder()
857            .server(
858                ServerBuilder::default()
859                    .flavor_ref("foo")
860                    .name("foo")
861                    .build()
862                    .unwrap(),
863            )
864            .build()
865            .unwrap();
866        let _: serde_json::Value = endpoint.query(&client).unwrap();
867        mock.assert();
868    }
869
870    #[cfg(feature = "sync")]
871    #[test]
872    fn endpoint_headers() {
873        let server = MockServer::start();
874        let client = FakeOpenStackClient::new(server.base_url());
875        let mock = server.mock(|when, then| {
876            when.method(httpmock::Method::POST)
877                .path("/servers".to_string())
878                .header("foo", "bar")
879                .header("not_foo", "not_bar");
880            then.status(200)
881                .header("content-type", "application/json")
882                .json_body(json!({ "server": {} }));
883        });
884
885        let endpoint = Request::builder()
886            .server(
887                ServerBuilder::default()
888                    .flavor_ref("foo")
889                    .name("foo")
890                    .build()
891                    .unwrap(),
892            )
893            .headers(
894                [(
895                    Some(HeaderName::from_static("foo")),
896                    HeaderValue::from_static("bar"),
897                )]
898                .into_iter(),
899            )
900            .header("not_foo", "not_bar")
901            .build()
902            .unwrap();
903        let _: serde_json::Value = endpoint.query(&client).unwrap();
904        mock.assert();
905    }
906}