1use async_trait::async_trait;
2
3use crate::{
4 error::Error,
5 types::server::*,
6 types::common::LabelFilter,
7 client::Client,
8};
9
10use tokio::time::{sleep, Duration};
11
12#[async_trait]
17pub trait ServerOperations {
18 async fn list_servers(&self) -> Result<ServerList, Error>;
19 async fn get_server(&self, uuid: &str) -> Result<ServerDetails, Error>;
20 async fn create_server(&self, request: &CreateServerRequest) -> Result<ServerDetails, Error>;
21 async fn start_server(&self, uuid: &str, request: &StartServerRequest) -> Result<StartServerResponse, Error>;
22 async fn stop_server(&self, uuid: &str, request: &StopServerRequest) -> Result<StopServerResponse, Error>;
23 async fn restart_server(&self, uuid: &str, request: &RestartServerRequest) -> Result<RestartServerResponse, Error>;
24 async fn modify_server(&self, uuid: &str, request: &ModifyServerRequest) -> Result<ModifyServerResponse, Error>;
25 async fn delete_server(&self, uuid: &str) -> Result<(), Error>;
26 async fn delete_server_and_storages(&self, uuid: &str, delete_backups: bool) -> Result<(), Error>;
27 async fn list_servers_by_labels(&self, filter: &LabelFilter) -> Result<ServerList, Error>;
28 async fn wait_for_server_state(
29 &self,
30 uuid: &str,
31 desired_state: Option<&ServerState>,
32 undesired_state: Option<&ServerState>,
33 timeout: Duration,
34 ) -> Result<ServerDetails, Error>;
35}
36
37#[async_trait]
38impl ServerOperations for Client {
39 async fn list_servers(&self) -> Result<ServerList, Error> {
40 let response = self.get("/server").await?;
41 let details: GetServerResponse = serde_json::from_str(&response)?;
42 Ok(details.servers)
43 }
44
45 async fn list_servers_by_labels(&self, filter: &LabelFilter) -> Result<ServerList, Error> {
46 let query = filter.to_query_params();
47 let path = if query.is_empty() {
48 "/server".to_string()
49 } else {
50 format!("/server?{}", query)
51 };
52
53 let response = self.get(&path).await?;
54 let details: GetServerResponse = serde_json::from_str(&response)?;
55 Ok(details.servers)
56 }
57
58 async fn get_server(&self, uuid: &str) -> Result<ServerDetails, Error> {
59 let response = self.get(&format!("/server/{}", uuid)).await?;
60 let details: GetServerDetailsResponse = serde_json::from_str(&response)?;
61 Ok(details.server)
62 }
63
64 async fn create_server(&self, request: &CreateServerRequest) -> Result<ServerDetails, Error> {
65 let response = self.post("/server", Some(request)).await?;
66 let create_response: CreateServerResponse = serde_json::from_str(&response)?;
67 Ok(create_response.server)
68 }
69
70 async fn start_server(&self, uuid: &str, request: &StartServerRequest) -> Result<StartServerResponse, Error> {
71 let response = self.post(&format!("/server/{}/start", uuid), Some(request)).await?;
72 Ok(serde_json::from_str(&response)?)
73 }
74
75 async fn stop_server(&self, uuid: &str, request: &StopServerRequest) -> Result<StopServerResponse, Error> {
76 let response = self.post(&format!("/server/{}/stop", uuid), Some(request)).await?;
77 Ok(serde_json::from_str(&response)?)
78 }
79
80 async fn restart_server(&self, uuid: &str, request: &RestartServerRequest) -> Result<RestartServerResponse, Error> {
81 let response = self.post(&format!("/server/{}/restart", uuid), Some(request)).await?;
82 Ok(serde_json::from_str(&response)?)
83 }
84
85 async fn modify_server(&self, uuid: &str, request: &ModifyServerRequest) -> Result<ModifyServerResponse, Error> {
86 let response = self.put(&format!("/server/{}", uuid), Some(request)).await?;
87 Ok(serde_json::from_str(&response)?)
88 }
89
90 async fn delete_server(&self, uuid: &str) -> Result<(), Error> {
91 self.delete(&format!("/server/{}", uuid)).await?;
92 Ok(())
93 }
94
95 async fn delete_server_and_storages(&self, uuid: &str, delete_backups: bool) -> Result<(), Error> {
96 let path = if delete_backups {
97 format!("/server/{}/?storages=1&backups=delete", uuid)
98 } else {
99 format!("/server/{}/?storages=1", uuid)
100 };
101 self.delete(&path).await?;
102 Ok(())
103 }
104
105 async fn wait_for_server_state(
106 &self,
107 uuid: &str,
108 desired_state: Option<&ServerState>,
109 undesired_state: Option<&ServerState>,
110 timeout: Duration,
111 ) -> Result<ServerDetails, Error> {
112 let start = std::time::Instant::now();
113
114 loop {
115 if start.elapsed() > timeout {
116 return Err(Error::Timeout);
117 }
118
119 let res = self.get_server(uuid).await?;
120
121 match (desired_state, undesired_state) {
122 (Some(desired), _) if res.server.state == desired.as_str() => return Ok(res),
123 (_, Some(undesired)) if res.server.state != undesired.as_str() => return Ok(res),
124 _ => {
125 sleep(Duration::from_secs(5)).await;
126 continue;
127 }
128 }
129 }
130 }
131}
132
133#[tokio::test]
134async fn test_list_servers() {
135 use crate::config;
136
137 let mut server = mockito::Server::new_async().await;
138 let url = server.url();
139 let _mock = server.mock("GET", "/1.3/server")
140 .with_status(200)
141 .with_body(r#"{"servers":{"server":[{"core_number":"1","hostname":"fi.example.com","labels":{"label":[{"key":"env","value":"prod"}]},"license":0,"memory_amount":"2048","plan":"1xCPU-2GB","plan_ivp4_bytes":"34253332","plan_ipv6_bytes":"0","state":"started","tags":{"tag":["PROD","CentOS"]},"title":"Helsinki server","uuid":"00798b85-efdc-41ca-8021-f6ef457b8531","zone":"fi-hel1"},{"core_number":"1","hostname":"uk.example.com","labels":{"label":[]},"license":0,"memory_amount":"512","plan":"custom","state":"stopped","tags":{"tag":["DEV","Ubuntu"]},"title":"London server","uuid":"009d64ef-31d1-4684-a26b-c86c955cbf46","zone":"uk-lon1"}]}}"#)
142 .create();
143
144 let client = Client::with_config(
145 config::Config::new("foo", "bar")
146 .with_base_url(url)
147 ).unwrap();
148
149
150 let result = client.list_servers().await.unwrap();
151 assert_eq!(result.server.len(), 2);
152 assert_eq!(result.server[0].uuid, "00798b85-efdc-41ca-8021-f6ef457b8531");
153 assert_eq!(result.server[0].title, "Helsinki server");
154}
155
156#[tokio::test]
158async fn test_get_server() {}
159
160#[tokio::test]
162async fn test_create_server() {}