1use 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#[derive(Clone, Debug)]
41pub struct ServerQuery {
42 session: Session,
43 query: Query,
44 can_paginate: bool,
45}
46
47#[derive(Clone, Debug)]
51pub struct DetailedServerQuery {
52 inner: ServerQuery,
53}
54
55#[derive(Clone, Debug)]
57pub struct Server {
58 session: Session,
59 inner: protocol::Server,
60}
61
62#[derive(Clone, Debug)]
64pub struct ServerSummary {
65 session: Session,
66 inner: IdAndName,
67}
68
69#[derive(Debug)]
71pub struct ServerStatusWaiter<'server> {
72 server: &'server mut Server,
73 target: protocol::ServerStatus,
74}
75
76#[derive(Clone, Debug)]
78pub enum ServerNIC {
79 FromNetwork(NetworkRef),
81 WithPort(PortRef),
83 WithFixedIp(Ipv4Addr),
85}
86
87#[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#[derive(Debug)]
105pub struct ServerCreationWaiter {
106 server: Server,
107}
108
109#[async_trait]
110impl Refresh for Server {
111 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 pub(crate) fn new(session: Session, inner: protocol::Server) -> Result<Server> {
121 Ok(Server { session, inner })
122 }
123
124 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 #[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 #[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 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 #[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 #[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 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 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 pub async fn action(&mut self, action: ServerAction) -> Result<()> {
297 api::server_action(&self.session, &self.inner.id, action).await
298 }
299
300 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 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 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 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 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#[derive(Clone, Debug, Serialize)]
354#[non_exhaustive]
355pub enum ServerAction {
356 #[serde(rename = "addSecurityGroup")]
358 AddSecurityGroup {
359 name: String,
361 },
362 #[serde(rename = "changePassword")]
364 ChangePassword {
365 admin_pass: String,
367 },
368 #[serde(rename = "confirmResize", serialize_with = "unit_to_null")]
370 ConfirmResize,
371 #[serde(rename = "createBackup")]
373 CreateBackup {
374 name: String,
376 backup_type: String,
378 rotation: u16,
380 #[serde(skip_serializing_if = "Option::is_none")]
382 metadata: Option<HashMap<String, String>>,
383 },
384 #[serde(rename = "createImage")]
386 CreateImage {
387 name: String,
389 #[serde(skip_serializing_if = "Option::is_none")]
391 metadata: Option<HashMap<String, String>>,
392 },
393 #[serde(rename = "forceDelete", serialize_with = "unit_to_null")]
395 ForceDelete,
396 #[serde(rename = "os-getConsoleOutput")]
398 #[doc(hidden)]
399 GetConsoleOutput {
400 #[serde(skip_serializing_if = "Option::is_none")]
402 length: Option<u64>,
403 },
404 #[serde(rename = "pause", serialize_with = "unit_to_null")]
406 Pause,
407 #[serde(rename = "reboot")]
409 Reboot {
410 #[serde(rename = "type")]
412 reboot_type: protocol::RebootType,
413 },
414 #[serde(rename = "removeSecurityGroup")]
416 RemoveSecurityGroup {
417 name: String,
419 },
420 #[serde(rename = "rescue")]
422 Rescue {
423 #[serde(rename = "adminPass", skip_serializing_if = "Option::is_none")]
425 admin_pass: Option<String>,
426 #[serde(skip_serializing_if = "Option::is_none")]
428 rescue_image_ref: Option<String>,
429 },
430 #[serde(rename = "resize")]
432 Resize {
433 #[serde(rename = "flavorRef")]
435 flavor_ref: String,
436 #[serde(rename = "OS-DCF:diskConfig")]
438 disk_config: String,
439 },
440 #[serde(rename = "restore", serialize_with = "unit_to_null")]
442 Restore,
443 #[serde(rename = "resume", serialize_with = "unit_to_null")]
445 Resume,
446 #[serde(rename = "revertResize", serialize_with = "unit_to_null")]
448 RevertResize,
449 #[serde(rename = "shelve", serialize_with = "unit_to_null")]
451 Shelve,
452 #[serde(rename = "shelveOffload", serialize_with = "unit_to_null")]
454 ShelveOffload,
455 #[serde(rename = "os-start", serialize_with = "unit_to_null")]
457 Start,
458 #[serde(rename = "os-stop", serialize_with = "unit_to_null")]
460 Stop,
461 #[serde(rename = "suspend", serialize_with = "unit_to_null")]
463 Suspend,
464 #[serde(rename = "trigger_crash_dump", serialize_with = "unit_to_null")]
466 TriggerCrashDump,
467 #[serde(rename = "unlock", serialize_with = "unit_to_null")]
469 Unlock,
470 #[serde(rename = "unpause", serialize_with = "unit_to_null")]
472 Unpause,
473 #[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 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 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 pub async fn details(&self) -> Result<Server> {
547 Server::load(self.session.clone(), &self.inner.id).await
548 }
549
550 pub async fn delete(self) -> Result<()> {
552 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 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 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 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 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 #[inline]
662 pub fn detailed(self) -> DetailedServerQuery {
663 DetailedServerQuery { inner: self }
664 }
665
666 #[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 #[inline]
685 pub async fn all(self) -> Result<Vec<ServerSummary>> {
686 self.into_stream().try_collect().await
687 }
688
689 pub async fn one(mut self) -> Result<ServerSummary> {
694 debug!("Fetching one server with {:?}", self.query);
695 if self.can_paginate {
696 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 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 #[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 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 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 #[inline]
871 pub fn add_fixed_ip(&mut self, fixed_ip: Ipv4Addr) {
872 self.nics.push(ServerNIC::WithFixedIp(fixed_ip));
873 }
874
875 #[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 #[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 #[inline]
895 pub fn metadata(&mut self) -> &mut HashMap<String, String> {
896 &mut self.metadata
897 }
898
899 #[inline]
901 pub fn nics(&mut self) -> &mut Vec<ServerNIC> {
902 &mut self.nics
903 }
904
905 #[inline]
907 pub fn block_devices(&mut self) -> &mut Vec<BlockDevice> {
908 &mut self.block_devices
909 }
910
911 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 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 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 #[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 #[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 #[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 #[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 #[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 #[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 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 #[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 #[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 #[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 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 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}