upcloud_sdk/resources/
server.rs

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/// Operations for managing cloud servers.
13///
14/// This trait provides methods for creating, modifying, and managing cloud servers
15/// through the UpCloud API.
16#[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// TODO: Add test for get_server
157#[tokio::test]
158async fn test_get_server() {}
159
160// TODO: Add test for create_server
161#[tokio::test]
162async fn test_create_server() {}