openstack/compute/
servers.rs

1// Copyright 2017 Dmitry Tantsur <divius.inside@gmail.com>
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Server management via Compute API.
16
17use std::collections::HashMap;
18use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
19use std::time::Duration;
20
21use async_trait::async_trait;
22use chrono::{DateTime, FixedOffset};
23use futures::stream::{Stream, TryStreamExt};
24use osauth::common::IdAndName;
25use serde::Serialize;
26
27use super::super::common::{
28    FlavorRef, ImageRef, KeyPairRef, NetworkRef, PortRef, ProjectRef, Refresh, ResourceIterator,
29    ResourceQuery, UserRef, VolumeRef,
30};
31#[cfg(feature = "image")]
32use super::super::image::Image;
33use super::super::session::Session;
34use super::super::utils::{unit_to_null, Query};
35use super::super::waiter::{DeletionWaiter, Waiter};
36use super::super::{Error, ErrorKind, Result, Sort};
37use super::{api, protocol, BlockDevice, KeyPair};
38
39/// A query to server list.
40#[derive(Clone, Debug)]
41pub struct ServerQuery {
42    session: Session,
43    query: Query,
44    can_paginate: bool,
45}
46
47/// A detailed query to server list.
48///
49/// Is constructed from a `ServerQuery`.
50#[derive(Clone, Debug)]
51pub struct DetailedServerQuery {
52    inner: ServerQuery,
53}
54
55/// Structure representing a single server.
56#[derive(Clone, Debug)]
57pub struct Server {
58    session: Session,
59    inner: protocol::Server,
60}
61
62/// Structure representing a summary of a single server.
63#[derive(Clone, Debug)]
64pub struct ServerSummary {
65    session: Session,
66    inner: IdAndName,
67}
68
69/// Waiter for server status to change.
70#[derive(Debug)]
71pub struct ServerStatusWaiter<'server> {
72    server: &'server mut Server,
73    target: protocol::ServerStatus,
74}
75
76/// A virtual NIC of a new server.
77#[derive(Clone, Debug)]
78pub enum ServerNIC {
79    /// A NIC from the given network.
80    FromNetwork(NetworkRef),
81    /// A NIC with the given port.
82    WithPort(PortRef),
83    /// A NIC with the given fixed IP.
84    WithFixedIp(Ipv4Addr),
85}
86
87/// A request to create a server.
88#[derive(Debug)]
89pub struct NewServer {
90    session: Session,
91    flavor: FlavorRef,
92    image: Option<ImageRef>,
93    keypair: Option<KeyPairRef>,
94    metadata: HashMap<String, String>,
95    name: String,
96    nics: Vec<ServerNIC>,
97    block_devices: Vec<BlockDevice>,
98    user_data: Option<String>,
99    config_drive: Option<bool>,
100    availability_zone: Option<String>,
101}
102
103/// Waiter for server to be created.
104#[derive(Debug)]
105pub struct ServerCreationWaiter {
106    server: Server,
107}
108
109#[async_trait]
110impl Refresh for Server {
111    /// Refresh the server.
112    async fn refresh(&mut self) -> Result<()> {
113        self.inner = api::get_server_by_id(&self.session, &self.inner.id).await?;
114        Ok(())
115    }
116}
117
118impl Server {
119    /// Create a new Server object.
120    pub(crate) fn new(session: Session, inner: protocol::Server) -> Result<Server> {
121        Ok(Server { session, inner })
122    }
123
124    /// Load a Server object.
125    pub(crate) async fn load<Id: AsRef<str>>(session: Session, id: Id) -> Result<Server> {
126        let inner = api::get_server(&session, id).await?;
127        Server::new(session, inner)
128    }
129
130    transparent_property! {
131        #[doc = "IPv4 address to access the server (if provided)."]
132        access_ipv4: Option<Ipv4Addr>
133    }
134
135    transparent_property! {
136        #[doc = "IPv6 address to access the server (if provided)."]
137        access_ipv6: Option<Ipv6Addr>
138    }
139
140    transparent_property! {
141        #[doc = "Addresses (floating and fixed) associated with the server."]
142        addresses: ref HashMap<String, Vec<protocol::ServerAddress>>
143    }
144
145    transparent_property! {
146        #[doc = "Availability zone."]
147        availability_zone: ref String
148    }
149
150    transparent_property! {
151        #[doc = "Creation date and time."]
152        created_at: DateTime<FixedOffset>
153    }
154
155    transparent_property! {
156        #[doc = "Server description."]
157        description: ref Option<String>
158    }
159
160    /// Identifier of the flavor used to create this server.
161    ///
162    /// This is only known in old API versions, and the flavor is not guaranteed to exist any more.
163    #[inline]
164    pub fn flavor_id(&self) -> Option<&String> {
165        match self.inner.flavor {
166            protocol::AnyFlavor::Old(ref flavor) => Some(&flavor.id),
167            _ => None,
168        }
169    }
170
171    /// Flavor used to create this server.
172    ///
173    /// It may not possible to reconstruct a real Flavor object out of a Server, so this call
174    /// returns the corresponding information instead.
175    #[inline]
176    pub async fn flavor(&self) -> Result<protocol::ServerFlavor> {
177        match self.inner.flavor {
178            protocol::AnyFlavor::Old(ref flavor) => {
179                let flavor = api::get_flavor(&self.session, &flavor.id).await?;
180                Ok(protocol::ServerFlavor {
181                    ephemeral_size: flavor.ephemeral,
182                    extra_specs: flavor.extra_specs,
183                    original_name: flavor.name,
184                    ram_size: flavor.ram,
185                    root_size: flavor.disk,
186                    swap_size: flavor.swap,
187                    vcpu_count: flavor.vcpus,
188                })
189            }
190            protocol::AnyFlavor::New(ref flavor) => Ok(flavor.clone()),
191        }
192    }
193
194    /// Find a floating IP, if it exists.
195    ///
196    /// If multiple floating IPs exist, the first is returned.
197    pub fn floating_ip(&self) -> Option<IpAddr> {
198        self.inner
199            .addresses
200            .values()
201            .flat_map(|l| l.iter())
202            .filter(|a| a.addr_type == Some(protocol::AddressType::Floating))
203            .map(|a| a.addr)
204            .next()
205    }
206
207    transparent_property! {
208        #[doc = "Whether the server was created with a config drive."]
209        has_config_drive: bool
210    }
211
212    /// Whether the server has an image.
213    ///
214    /// May return `false` if the server was created from a volume.
215    #[inline]
216    pub fn has_image(&self) -> bool {
217        self.inner.image.is_some()
218    }
219
220    transparent_property! {
221        #[doc = "Server unique ID."]
222        id: ref String
223    }
224
225    /// Fetch the associated image.
226    ///
227    /// Fails with `ResourceNotFound` if the server does not have an image.
228    #[cfg(feature = "image")]
229    pub async fn image(&self) -> Result<Image> {
230        match self.inner.image {
231            Some(ref image) => Image::new(self.session.clone(), &image.id).await,
232            None => Err(Error::new(
233                ErrorKind::ResourceNotFound,
234                "No image associated with server",
235            )),
236        }
237    }
238
239    /// Get a reference to the image.
240    ///
241    /// May be None if the server was created from a volume.
242    pub fn image_id(&self) -> Option<&String> {
243        match self.inner.image {
244            Some(ref image) => Some(&image.id),
245            None => None,
246        }
247    }
248
249    transparent_property! {
250        #[doc = "Instance name."]
251        instance_name: ref Option<String>
252    }
253
254    /// Fetch the key pair used for the server.
255    pub async fn key_pair(&self) -> Result<KeyPair> {
256        match self.inner.key_pair_name {
257            Some(ref key_pair) => KeyPair::new(self.session.clone(), key_pair).await,
258            None => Err(Error::new(
259                ErrorKind::ResourceNotFound,
260                "No key pair associated with server",
261            )),
262        }
263    }
264
265    transparent_property! {
266        #[doc = "Name of a key pair used with this server (if any)."]
267        key_pair_name: ref Option<String>
268    }
269
270    transparent_property! {
271        #[doc = "Server name."]
272        name: ref String
273    }
274
275    transparent_property! {
276        #[doc = "Metadata associated with the server."]
277        metadata: ref HashMap<String, String>
278    }
279
280    transparent_property! {
281        #[doc = "Server power state."]
282        power_state: protocol::ServerPowerState
283    }
284
285    transparent_property! {
286        #[doc = "Server status."]
287        status: protocol::ServerStatus
288    }
289
290    transparent_property! {
291        #[doc = "Last update date and time."]
292        updated_at: DateTime<FixedOffset>
293    }
294
295    /// Run an action on the server.
296    pub async fn action(&mut self, action: ServerAction) -> Result<()> {
297        api::server_action(&self.session, &self.inner.id, action).await
298    }
299
300    /// Delete the server.
301    pub async fn delete(self) -> Result<DeletionWaiter<Server>> {
302        api::delete_server(&self.session, &self.inner.id).await?;
303        Ok(DeletionWaiter::new(
304            self,
305            Duration::new(120, 0),
306            Duration::new(1, 0),
307        ))
308    }
309
310    /// Get the console output as a string.
311    ///
312    /// Length is the number of lines to fetch from the end of console log.
313    /// All lines will be returned if this is not specified.
314    pub async fn get_console_output(&self, length: Option<u64>) -> Result<String> {
315        let action = ServerAction::GetConsoleOutput { length };
316        let result: protocol::GetConsoleOutput =
317            api::server_action_with_result(&self.session, &self.inner.id, action).await?;
318        Ok(result.output)
319    }
320
321    /// Reboot the server.
322    pub async fn reboot(
323        &mut self,
324        reboot_type: protocol::RebootType,
325    ) -> Result<ServerStatusWaiter<'_>> {
326        let _ = self.action(ServerAction::Reboot { reboot_type }).await?;
327        Ok(ServerStatusWaiter {
328            server: self,
329            target: protocol::ServerStatus::Active,
330        })
331    }
332
333    /// Start the server, optionally wait for it to be active.
334    pub async fn start(&mut self) -> Result<ServerStatusWaiter<'_>> {
335        let _ = self.action(ServerAction::Start).await?;
336        Ok(ServerStatusWaiter {
337            server: self,
338            target: protocol::ServerStatus::Active,
339        })
340    }
341
342    /// Stop the server, optionally wait for it to be powered off.
343    pub async fn stop(&mut self) -> Result<ServerStatusWaiter<'_>> {
344        let _ = self.action(ServerAction::Stop).await?;
345        Ok(ServerStatusWaiter {
346            server: self,
347            target: protocol::ServerStatus::ShutOff,
348        })
349    }
350}
351
352/// An action to perform on a server.
353#[derive(Clone, Debug, Serialize)]
354#[non_exhaustive]
355pub enum ServerAction {
356    /// Adds a security group to a server.
357    #[serde(rename = "addSecurityGroup")]
358    AddSecurityGroup {
359        /// The security group name.
360        name: String,
361    },
362    /// Changes the administrative password for a server.
363    #[serde(rename = "changePassword")]
364    ChangePassword {
365        /// The administrative password for the server.
366        admin_pass: String,
367    },
368    /// Confirms a pending resize action for a server.
369    #[serde(rename = "confirmResize", serialize_with = "unit_to_null")]
370    ConfirmResize,
371    /// Creates a back up of a server.
372    #[serde(rename = "createBackup")]
373    CreateBackup {
374        /// The name of the image to be backed up.
375        name: String,
376        /// The type of the backup, for example, daily.
377        backup_type: String,
378        /// The rotation of the back up image, the oldest image will be removed when image count exceed the rotation count.
379        rotation: u16,
380        /// Metadata key and value pairs for the image.
381        #[serde(skip_serializing_if = "Option::is_none")]
382        metadata: Option<HashMap<String, String>>,
383    },
384    /// Creates an image from a server.
385    #[serde(rename = "createImage")]
386    CreateImage {
387        /// The display name of an Image.
388        name: String,
389        /// Metadata key and value pairs for the image.
390        #[serde(skip_serializing_if = "Option::is_none")]
391        metadata: Option<HashMap<String, String>>,
392    },
393    /// Force-deletes a server before deferred cleanup.
394    #[serde(rename = "forceDelete", serialize_with = "unit_to_null")]
395    ForceDelete,
396    /// Shows console output for a server.
397    #[serde(rename = "os-getConsoleOutput")]
398    #[doc(hidden)]
399    GetConsoleOutput {
400        /// The number of lines to fetch from the end of console log. All lines will be returned if this is not specified.
401        #[serde(skip_serializing_if = "Option::is_none")]
402        length: Option<u64>,
403    },
404    /// Pauses a server. Changes its status to PAUSED.
405    #[serde(rename = "pause", serialize_with = "unit_to_null")]
406    Pause,
407    /// Reboots a server.
408    #[serde(rename = "reboot")]
409    Reboot {
410        /// The type of the reboot action.
411        #[serde(rename = "type")]
412        reboot_type: protocol::RebootType,
413    },
414    /// Removes a security group from a server.
415    #[serde(rename = "removeSecurityGroup")]
416    RemoveSecurityGroup {
417        /// The security group name.
418        name: String,
419    },
420    /// Puts a server in rescue mode and changes its status to RESCUE.
421    #[serde(rename = "rescue")]
422    Rescue {
423        /// The password for the rescued instance.
424        #[serde(rename = "adminPass", skip_serializing_if = "Option::is_none")]
425        admin_pass: Option<String>,
426        /// The image reference to use to rescue your server instance.
427        #[serde(skip_serializing_if = "Option::is_none")]
428        rescue_image_ref: Option<String>,
429    },
430    /// Resizes a server.
431    #[serde(rename = "resize")]
432    Resize {
433        /// The flavor ID for resizing the server.
434        #[serde(rename = "flavorRef")]
435        flavor_ref: String,
436        /// Controls how the API partitions the disk when you create, rebuild, or resize servers.
437        #[serde(rename = "OS-DCF:diskConfig")]
438        disk_config: String,
439    },
440    /// Restores a previously soft-deleted server instance.
441    #[serde(rename = "restore", serialize_with = "unit_to_null")]
442    Restore,
443    /// Resumes a suspended server and changes its status to ACTIVE.
444    #[serde(rename = "resume", serialize_with = "unit_to_null")]
445    Resume,
446    /// Cancels and reverts a pending resize action for a server.
447    #[serde(rename = "revertResize", serialize_with = "unit_to_null")]
448    RevertResize,
449    /// Shelves a server.
450    #[serde(rename = "shelve", serialize_with = "unit_to_null")]
451    Shelve,
452    /// Shelf-offloads, or removes, a shelved server.
453    #[serde(rename = "shelveOffload", serialize_with = "unit_to_null")]
454    ShelveOffload,
455    /// Starts a stopped server.
456    #[serde(rename = "os-start", serialize_with = "unit_to_null")]
457    Start,
458    /// Stops a running server.
459    #[serde(rename = "os-stop", serialize_with = "unit_to_null")]
460    Stop,
461    /// Suspends a server and changes its status to SUSPENDED.
462    #[serde(rename = "suspend", serialize_with = "unit_to_null")]
463    Suspend,
464    /// Trigger a crash dump in a server.
465    #[serde(rename = "trigger_crash_dump", serialize_with = "unit_to_null")]
466    TriggerCrashDump,
467    /// Unlocks a locked server.
468    #[serde(rename = "unlock", serialize_with = "unit_to_null")]
469    Unlock,
470    /// Unpauses a paused server and changes its status to ACTIVE.
471    #[serde(rename = "unpause", serialize_with = "unit_to_null")]
472    Unpause,
473    /// Unrescues a server. Changes status to ACTIVE.
474    #[serde(rename = "unrescue", serialize_with = "unit_to_null")]
475    Unrescue,
476}
477
478#[async_trait]
479impl<'server> Waiter<(), Error> for ServerStatusWaiter<'server> {
480    fn default_wait_timeout(&self) -> Option<Duration> {
481        // TODO(dtantsur): vary depending on target?
482        Some(Duration::new(600, 0))
483    }
484
485    fn default_delay(&self) -> Duration {
486        Duration::new(1, 0)
487    }
488
489    fn timeout_error(&self) -> Error {
490        Error::new(
491            ErrorKind::OperationTimedOut,
492            format!(
493                "Timeout waiting for server {} to reach state {}",
494                self.server.id(),
495                self.target
496            ),
497        )
498    }
499
500    async fn poll(&mut self) -> Result<Option<()>> {
501        self.server.refresh().await?;
502        if self.server.status() == self.target {
503            debug!("Server {} reached state {}", self.server.id(), self.target);
504            Ok(Some(()))
505        } else if self.server.status() == protocol::ServerStatus::Error {
506            debug!(
507                "Failed to move server {} to {} - status is ERROR",
508                self.server.id(),
509                self.target
510            );
511            Err(Error::new(
512                ErrorKind::OperationFailed,
513                format!("Server {} got into ERROR state", self.server.id()),
514            ))
515        } else {
516            trace!(
517                "Still waiting for server {} to get to state {}, current is {}",
518                self.server.id(),
519                self.target,
520                self.server.status()
521            );
522            Ok(None)
523        }
524    }
525}
526
527impl<'server> ServerStatusWaiter<'server> {
528    /// Current state of the server.
529    pub fn current_state(&self) -> &Server {
530        self.server
531    }
532}
533
534impl ServerSummary {
535    transparent_property! {
536        #[doc = "Server unique ID."]
537        id: ref String
538    }
539
540    transparent_property! {
541        #[doc = "Server name."]
542        name: ref String
543    }
544
545    /// Get details.
546    pub async fn details(&self) -> Result<Server> {
547        Server::load(self.session.clone(), &self.inner.id).await
548    }
549
550    /// Delete the server.
551    pub async fn delete(self) -> Result<()> {
552        // TODO(dtantsur): implement wait
553        api::delete_server(&self.session, &self.inner.id).await
554    }
555}
556
557impl ServerQuery {
558    pub(crate) fn new(session: Session) -> ServerQuery {
559        ServerQuery {
560            session,
561            query: Query::new(),
562            can_paginate: true,
563        }
564    }
565
566    /// Add marker to the request.
567    ///
568    /// Using this disables automatic pagination.
569    pub fn with_marker<T: Into<String>>(mut self, marker: T) -> Self {
570        self.can_paginate = false;
571        self.query.push_str("marker", marker);
572        self
573    }
574
575    /// Add limit to the request.
576    ///
577    /// Using this disables automatic pagination.
578    pub fn with_limit(mut self, limit: usize) -> Self {
579        self.can_paginate = false;
580        self.query.push("limit", limit);
581        self
582    }
583
584    /// Add sorting to the request.
585    pub fn sort_by(mut self, sort: Sort<protocol::ServerSortKey>) -> Self {
586        let (field, direction) = sort.into();
587        self.query.push_str("sort_key", field);
588        self.query.push("sort_dir", direction);
589        self
590    }
591
592    /// Add all tenants to the request.
593    pub fn all_tenants(mut self) -> Self {
594        self.query.push("all_tenants", true);
595        self
596    }
597
598    query_filter! {
599        #[doc = "Filter by IPv4 address that should be used to access the server."]
600        set_access_ip_v4, with_access_ip_v4 -> access_ip_v4: Ipv4Addr
601    }
602
603    query_filter! {
604        #[doc = "Filter by IPv6 address that should be used to access the server."]
605        set_access_ip_v6, with_access_ip_v6 -> access_ip_v6: Ipv6Addr
606    }
607
608    query_filter! {
609        #[doc = "Filter by availability zone."]
610        set_availability_zone, with_availability_zone -> availability_zone: String
611    }
612
613    query_filter! {
614        #[doc = "Filter by flavor."]
615        set_flavor, with_flavor -> flavor: FlavorRef
616    }
617
618    query_filter! {
619        #[doc = "Filter by host name."]
620        set_hostname, with_hostname -> hostname: String
621    }
622
623    query_filter! {
624        #[doc = "Filter by image used to build the server."]
625        set_image, with_image -> image: ImageRef
626    }
627
628    query_filter! {
629        #[doc = "Filter by an IPv4 address."]
630        set_ip_v4, with_ip_v4 -> ip: Ipv4Addr
631    }
632
633    query_filter! {
634        #[doc = "Filter by an IPv6 address."]
635        set_ip_v6, with_ip_v6 -> ip6: Ipv6Addr
636    }
637
638    query_filter! {
639        #[doc = "Filter by name."]
640        set_name, with_name -> name: String
641    }
642
643    query_filter! {
644        #[doc = "Filter by project (also commonly known as tenant)."]
645        set_project, with_project -> project_id: ProjectRef
646    }
647
648    query_filter! {
649        #[doc = "Filter by server status."]
650        set_status, with_status -> status: protocol::ServerStatus
651    }
652
653    query_filter! {
654        #[doc = "Filter by user."]
655        set_user, with_user -> user_id: UserRef
656    }
657
658    /// Convert this query into a detailed query.
659    ///
660    /// Detailed queries return full `Server` objects instead of just `ServerSummary`.
661    #[inline]
662    pub fn detailed(self) -> DetailedServerQuery {
663        DetailedServerQuery { inner: self }
664    }
665
666    /// Convert this query into a stream executing the request.
667    ///
668    /// This stream yields only `ServerSummary` objects, containing
669    /// IDs and names. Use `detailed().into_stream()` for full `Server` objects.
670    ///
671    /// Returns a `TryStream`, which is a stream with each `next`
672    /// call returning a `Result`.
673    ///
674    /// Note that no requests are done until you start iterating.
675    #[inline]
676    pub fn into_stream(self) -> impl Stream<Item = Result<ServerSummary>> {
677        debug!("Fetching servers with {:?}", self.query);
678        ResourceIterator::new(self).into_stream()
679    }
680
681    /// Execute this request and return all results.
682    ///
683    /// A convenience shortcut for `self.into_stream().try_collect().await`.
684    #[inline]
685    pub async fn all(self) -> Result<Vec<ServerSummary>> {
686        self.into_stream().try_collect().await
687    }
688
689    /// Return one and exactly one result.
690    ///
691    /// Fails with `ResourceNotFound` if the query produces no results and
692    /// with `TooManyItems` if the query produces more than one result.
693    pub async fn one(mut self) -> Result<ServerSummary> {
694        debug!("Fetching one server with {:?}", self.query);
695        if self.can_paginate {
696            // We need only one result. We fetch maximum two to be able
697            // to check if the query yieled more than one result.
698            self.query.push("limit", 2);
699        }
700
701        ResourceIterator::new(self).one().await
702    }
703}
704
705#[async_trait]
706impl ResourceQuery for ServerQuery {
707    type Item = ServerSummary;
708
709    const DEFAULT_LIMIT: usize = 100;
710
711    async fn can_paginate(&self) -> Result<bool> {
712        Ok(self.can_paginate)
713    }
714
715    fn extract_marker(&self, resource: &Self::Item) -> String {
716        resource.id().clone()
717    }
718
719    async fn fetch_chunk(
720        &self,
721        limit: Option<usize>,
722        marker: Option<String>,
723    ) -> Result<Vec<Self::Item>> {
724        let query = self.query.with_marker_and_limit(limit, marker);
725        Ok(api::list_servers(&self.session, &query)
726            .await?
727            .into_iter()
728            .map(|srv| ServerSummary {
729                session: self.session.clone(),
730                inner: srv,
731            })
732            .collect())
733    }
734}
735
736impl DetailedServerQuery {
737    /// Convert this query into a stream executing the request.
738    ///
739    /// This stream yields full `Server` objects.
740    ///
741    /// Returns a `TryStream`, which is a stream with each `next`
742    /// call returning a `Result`.
743    ///
744    /// Note that no requests are done until you start iterating.
745    pub fn into_stream(self) -> impl Stream<Item = Result<Server>> {
746        debug!("Fetching server details with {:?}", self.inner.query);
747        ResourceIterator::new(self).into_stream()
748    }
749
750    /// Execute this request and return all results.
751    ///
752    /// A convenience shortcut for `self.into_stream().try_collect().await`.
753    #[inline]
754    pub async fn all(self) -> Result<Vec<Server>> {
755        self.into_stream().try_collect().await
756    }
757}
758
759#[async_trait]
760impl ResourceQuery for DetailedServerQuery {
761    type Item = Server;
762
763    const DEFAULT_LIMIT: usize = 50;
764
765    async fn can_paginate(&self) -> Result<bool> {
766        Ok(self.inner.can_paginate)
767    }
768
769    fn extract_marker(&self, resource: &Self::Item) -> String {
770        resource.id().clone()
771    }
772
773    async fn fetch_chunk(
774        &self,
775        limit: Option<usize>,
776        marker: Option<String>,
777    ) -> Result<Vec<Self::Item>> {
778        let query = self.inner.query.with_marker_and_limit(limit, marker);
779        let servers = api::list_servers_detail(&self.inner.session, &query).await?;
780        let mut result = Vec::with_capacity(servers.len());
781        for srv in servers {
782            result.push(Server::new(self.inner.session.clone(), srv)?);
783        }
784        Ok(result)
785    }
786}
787
788impl From<DetailedServerQuery> for ServerQuery {
789    fn from(value: DetailedServerQuery) -> ServerQuery {
790        value.inner
791    }
792}
793
794impl From<ServerQuery> for DetailedServerQuery {
795    fn from(value: ServerQuery) -> DetailedServerQuery {
796        value.detailed()
797    }
798}
799
800async fn convert_networks(
801    session: &Session,
802    networks: Vec<ServerNIC>,
803) -> Result<Vec<protocol::ServerNetwork>> {
804    let mut result = Vec::with_capacity(networks.len());
805    for item in networks {
806        result.push(match item {
807            ServerNIC::FromNetwork(n) => protocol::ServerNetwork::Network {
808                uuid: n.into_verified(session).await?.into(),
809            },
810            ServerNIC::WithPort(p) => protocol::ServerNetwork::Port {
811                port: p.into_verified(session).await?.into(),
812            },
813            ServerNIC::WithFixedIp(ip) => protocol::ServerNetwork::FixedIp { fixed_ip: ip },
814        });
815    }
816    Ok(result)
817}
818
819impl NewServer {
820    /// Start creating a server.
821    pub(crate) fn new(session: Session, name: String, flavor: FlavorRef) -> NewServer {
822        NewServer {
823            session,
824            flavor,
825            image: None,
826            keypair: None,
827            metadata: HashMap::new(),
828            name,
829            nics: Vec::new(),
830            block_devices: Vec::new(),
831            user_data: None,
832            config_drive: None,
833            availability_zone: None,
834        }
835    }
836
837    /// Request creation of the server.
838    pub async fn create(self) -> Result<ServerCreationWaiter> {
839        let mut block_devices = Vec::with_capacity(self.block_devices.len());
840        for bd in self.block_devices {
841            block_devices.push(bd.into_verified(&self.session).await?);
842        }
843
844        let request = protocol::ServerCreate {
845            block_devices,
846            flavorRef: self.flavor.into_verified(&self.session).await?.into(),
847            imageRef: match self.image {
848                Some(img) => Some(img.into_verified(&self.session).await?.into()),
849                None => None,
850            },
851            key_name: match self.keypair {
852                Some(item) => Some(item.into_verified(&self.session).await?.into()),
853                None => None,
854            },
855            metadata: self.metadata,
856            name: self.name,
857            networks: convert_networks(&self.session, self.nics).await?,
858            user_data: self.user_data,
859            config_drive: self.config_drive,
860            availability_zone: self.availability_zone,
861        };
862
863        let server_ref = api::create_server(&self.session, request).await?;
864        Ok(ServerCreationWaiter {
865            server: Server::load(self.session, server_ref.id).await?,
866        })
867    }
868
869    /// Add a virtual NIC with given fixed IP to the new server.
870    #[inline]
871    pub fn add_fixed_ip(&mut self, fixed_ip: Ipv4Addr) {
872        self.nics.push(ServerNIC::WithFixedIp(fixed_ip));
873    }
874
875    /// Add a virtual NIC from this network to the new server.
876    #[inline]
877    pub fn add_network<N>(&mut self, network: N)
878    where
879        N: Into<NetworkRef>,
880    {
881        self.nics.push(ServerNIC::FromNetwork(network.into()));
882    }
883
884    /// Add a virtual NIC with this port to the new server.
885    #[inline]
886    pub fn add_port<P>(&mut self, port: P)
887    where
888        P: Into<PortRef>,
889    {
890        self.nics.push(ServerNIC::WithPort(port.into()));
891    }
892
893    /// Metadata assigned to this server.
894    #[inline]
895    pub fn metadata(&mut self) -> &mut HashMap<String, String> {
896        &mut self.metadata
897    }
898
899    /// NICs to attach to this server.
900    #[inline]
901    pub fn nics(&mut self) -> &mut Vec<ServerNIC> {
902        &mut self.nics
903    }
904
905    /// Block devices attached to the server.
906    #[inline]
907    pub fn block_devices(&mut self) -> &mut Vec<BlockDevice> {
908        &mut self.block_devices
909    }
910
911    /// Use this image as a source for the new server.
912    pub fn set_image<I>(&mut self, image: I)
913    where
914        I: Into<ImageRef>,
915    {
916        self.image = Some(image.into());
917    }
918
919    /// Use this key pair for the new server.
920    pub fn set_keypair<K>(&mut self, keypair: K)
921    where
922        K: Into<KeyPairRef>,
923    {
924        self.keypair = Some(keypair.into());
925    }
926
927    /// Use this availability_zone for the new server.
928    pub fn set_availability_zone<A>(&mut self, availability_zone: A)
929    where
930        A: Into<String>,
931    {
932        self.availability_zone = Some(availability_zone.into());
933    }
934
935    /// Add a block device to attach to the server.
936    #[inline]
937    pub fn with_block_device(mut self, block_device: BlockDevice) -> Self {
938        self.block_devices.push(block_device);
939        self
940    }
941
942    /// Add a volume to boot from.
943    #[inline]
944    pub fn with_boot_volume<V>(self, volume: V) -> Self
945    where
946        V: Into<VolumeRef>,
947    {
948        self.with_block_device(BlockDevice::from_volume(volume, true))
949    }
950
951    /// Add a virtual NIC with given fixed IP to the new server.
952    #[inline]
953    pub fn with_fixed_ip(mut self, fixed_ip: Ipv4Addr) -> NewServer {
954        self.add_fixed_ip(fixed_ip);
955        self
956    }
957
958    /// Use this image as a source for the new server.
959    #[inline]
960    pub fn with_image<I>(mut self, image: I) -> NewServer
961    where
962        I: Into<ImageRef>,
963    {
964        self.set_image(image);
965        self
966    }
967
968    /// Use this key pair for the new server.
969    #[inline]
970    pub fn with_keypair<K>(mut self, keypair: K) -> NewServer
971    where
972        K: Into<KeyPairRef>,
973    {
974        self.set_keypair(keypair);
975        self
976    }
977
978    /// Use this availability zone for the new server.
979    #[inline]
980    pub fn with_availability_zone<K>(mut self, availability_zone: K) -> NewServer
981    where
982        K: Into<String>,
983    {
984        self.set_availability_zone(availability_zone);
985        self
986    }
987
988    /// Add an arbitrary key/value metadata pair.
989    pub fn with_metadata<S1, S2>(mut self, key: S1, value: S2) -> NewServer
990    where
991        S1: Into<String>,
992        S2: Into<String>,
993    {
994        let _ = self.metadata.insert(key.into(), value.into());
995        self
996    }
997
998    /// Add a virtual NIC from this network to the new server.
999    #[inline]
1000    pub fn with_network<N>(mut self, network: N) -> NewServer
1001    where
1002        N: Into<NetworkRef>,
1003    {
1004        self.add_network(network);
1005        self
1006    }
1007
1008    /// Create a volume to boot from from an image.
1009    #[inline]
1010    pub fn with_new_boot_volume<I>(self, image: I, size_gib: u32) -> Self
1011    where
1012        I: Into<ImageRef>,
1013    {
1014        self.with_block_device(BlockDevice::from_new_volume(image, size_gib, true))
1015    }
1016
1017    /// Add a virtual NIC with this port to the new server.
1018    #[inline]
1019    pub fn with_port<P>(mut self, port: P) -> NewServer
1020    where
1021        P: Into<PortRef>,
1022    {
1023        self.add_port(port);
1024        self
1025    }
1026
1027    creation_field! {
1028        #[doc = "Use this user-data for the new server."]
1029        set_user_data, with_user_data -> user_data: optional String
1030    }
1031
1032    creation_field! {
1033        #[doc = "Enable/disable config-drive for the new server."]
1034        set_config_drive, with_config_drive -> config_drive: optional bool
1035    }
1036}
1037
1038#[async_trait]
1039impl Waiter<Server, Error> for ServerCreationWaiter {
1040    fn default_wait_timeout(&self) -> Option<Duration> {
1041        Some(Duration::new(1800, 0))
1042    }
1043
1044    fn default_delay(&self) -> Duration {
1045        Duration::new(5, 0)
1046    }
1047
1048    fn timeout_error(&self) -> Error {
1049        Error::new(
1050            ErrorKind::OperationTimedOut,
1051            format!(
1052                "Timeout waiting for server {} to become ACTIVE",
1053                self.server.id()
1054            ),
1055        )
1056    }
1057
1058    async fn poll(&mut self) -> Result<Option<Server>> {
1059        self.server.refresh().await?;
1060        if self.server.status() == protocol::ServerStatus::Active {
1061            debug!("Server {} successfully created", self.server.id());
1062            // TODO(dtantsur): get rid of clone?
1063            Ok(Some(self.server.clone()))
1064        } else if self.server.status() == protocol::ServerStatus::Error {
1065            debug!(
1066                "Failed create server {} - status is ERROR",
1067                self.server.id()
1068            );
1069            Err(Error::new(
1070                ErrorKind::OperationFailed,
1071                format!("Server {} got into ERROR state", self.server.id()),
1072            ))
1073        } else {
1074            trace!(
1075                "Still waiting for server {} to become ACTIVE, current is {}",
1076                self.server.id(),
1077                self.server.status()
1078            );
1079            Ok(None)
1080        }
1081    }
1082}
1083
1084impl ServerCreationWaiter {
1085    /// Current state of the waiter.
1086    pub fn current_state(&self) -> &Server {
1087        &self.server
1088    }
1089}
1090
1091#[cfg(test)]
1092mod test {
1093    use super::*;
1094
1095    #[test]
1096    fn test_action_json() {
1097        assert_eq!(
1098            serde_json::to_string(&ServerAction::Start).unwrap(),
1099            "{\"os-start\":null}"
1100        );
1101        assert_eq!(
1102            serde_json::to_string(&ServerAction::Stop).unwrap(),
1103            "{\"os-stop\":null}"
1104        );
1105        assert_eq!(
1106            serde_json::to_string(&ServerAction::Reboot {
1107                reboot_type: protocol::RebootType::Hard
1108            })
1109            .unwrap(),
1110            "{\"reboot\":{\"type\":\"HARD\"}}"
1111        );
1112        assert_eq!(
1113            serde_json::to_string(&ServerAction::CreateImage {
1114                name: "new-image".to_string(),
1115                metadata: None,
1116            })
1117            .unwrap(),
1118            r#"{"createImage":{"name":"new-image"}}"#
1119        );
1120        assert_eq!(
1121            serde_json::to_string(&ServerAction::CreateImage {
1122                name: "new-image".to_string(),
1123                metadata: Some(HashMap::from([("tag".into(), "foo".into())])),
1124            })
1125            .unwrap(),
1126            r#"{"createImage":{"name":"new-image","metadata":{"tag":"foo"}}}"#
1127        );
1128    }
1129}