use std::sync::Arc;
use serde::{Deserialize, Serialize};
use serde_json as json;
use super::account::{Account, AccountStatus};
use crate::{
client::Client,
keypair::KeypairResource,
lite::BillingResource,
os::OsResource,
plan::{Billing, PlanResource},
};
pub struct VirtualMachineOptions {
pub plan: PlanResource,
pub keypair: KeypairResource,
pub os: OsResource,
pub billing: Billing,
pub use_credit_card: bool,
pub promocode: Option<String>,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub enum VirtualMachineStatus {
Active,
Pending,
Suspended,
}
impl VirtualMachineStatus {
pub fn as_str(&self) -> &'static str {
match self {
Self::Active => "Active",
Self::Pending => "Pending",
Self::Suspended => "Suspended",
}
}
}
pub struct VirtualMachine {
client: Arc<Client>,
}
impl VirtualMachine {
pub fn new(client: Arc<Client>) -> Self {
Self { client }
}
pub async fn list(&self) -> Result<Vec<VirtualMachineResource>, crate::Error> {
let mut vms: Vec<VirtualMachineResource> = Vec::new();
let account = Account::new(Arc::clone(&self.client));
let accounts = account.list().await?;
for account in accounts {
if account.status == AccountStatus::Terminated {
continue;
}
let vm = self.get(account.id).await?;
vms.push(vm);
}
Ok(vms)
}
pub async fn list_with_status(
&self,
status: VirtualMachineStatus,
) -> Result<Vec<VirtualMachineResource>, crate::Error> {
let mut vms: Vec<VirtualMachineResource> = Vec::new();
let status = match status {
VirtualMachineStatus::Active => AccountStatus::Active,
VirtualMachineStatus::Pending => AccountStatus::Pending,
VirtualMachineStatus::Suspended => AccountStatus::Suspended,
};
let account = Account::new(Arc::clone(&self.client));
let accounts = account.list_with_status(status).await?;
for account in accounts {
if account.status == AccountStatus::Terminated {
continue;
}
let vm = self.get(account.id).await?;
vms.push(vm);
}
Ok(vms)
}
pub async fn get(&self, id: u32) -> Result<VirtualMachineResource, crate::Error> {
let response = self
.client
.get(&format!("/accounts/{}/vm-details", id))
.await?;
let response: VirtualMachineResource = json::from_value(response)?;
let response = response.with_client(Arc::clone(&self.client));
let response = response.change_id(id);
Ok(response)
}
pub async fn create(
&self,
name: String,
description: Option<String>,
username: String,
password: String,
opts: &VirtualMachineOptions,
) -> Result<BillingResource, crate::Error> {
let use_cc = match opts.use_credit_card {
true => "yes",
false => "no",
};
let body = json::json!({
"ssh_and_console_user": &username,
"console_password": &password,
"vm_name": &name,
"description": &description,
"product_id": opts.plan.id,
"select_os": &opts.os.name,
"keypair_id": opts.keypair.id,
"cycle": opts.billing.cycle,
"pay_invoice_with_cc": use_cc,
"promocode": opts.promocode,
});
let response = self.client.post("", body).await?;
let response: BillingResource = json::from_value(response)?;
Ok(response)
}
pub async fn delete(&self, id: u32) -> Result<(), crate::Error> {
self.client.delete(&format!("/{}", id)).await?;
Ok(())
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct VirtualMachineResource {
#[serde(rename = "vmid")]
pub id: u32,
pub name: String,
pub status: String,
pub uptime: i32,
pub maxdisk: i64,
pub maxmem: i64,
pub mem: i32,
pub cpus: i32,
#[serde(skip)]
client: Arc<Client>,
}
impl VirtualMachineResource {
fn with_client(mut self, client: Arc<Client>) -> Self {
self.client = client;
self
}
fn change_id(mut self, id: u32) -> Self {
self.id = id;
self
}
pub async fn change_keypair(&self, keypair_id: u32) -> Result<(), crate::Error> {
let body = json::json!({ "keypair_id": keypair_id });
self.client
.put_with_body(&format!("/accounts/{}/keypair", self.id), body)
.await?;
Ok(())
}
pub async fn change_name(&self, name: &str) -> Result<(), crate::Error> {
let body = json::json!({ "name": name });
self.client
.put_with_body(&format!("/accounts/{}/change-vm-name", self.id), body)
.await?;
Ok(())
}
pub async fn change_plan(&self, plan_id: u32) -> Result<BillingResource, crate::Error> {
let body = json::json!({ "new_product_id": plan_id });
let response = self
.client
.post(&format!("/accounts/{}/change-package", self.id), body)
.await?;
let response: BillingResource = json::from_value(response)?;
Ok(response)
}
pub async fn change_storage(&self, size: u32) -> Result<BillingResource, crate::Error> {
let body = json::json!({ "disk_size": size });
let response = self
.client
.put_with_body(&format!("/accounts/{}/storage", self.id), body)
.await?;
let response: BillingResource = json::from_value(response)?;
Ok(response)
}
pub async fn start(&self) -> Result<(), crate::Error> {
self.change_state("start").await?;
Ok(())
}
pub async fn suspend(&self) -> Result<(), crate::Error> {
self.change_state("suspend").await?;
Ok(())
}
pub async fn resume(&self) -> Result<(), crate::Error> {
self.change_state("resume").await?;
Ok(())
}
pub async fn reset(&self) -> Result<(), crate::Error> {
self.change_state("reset").await?;
Ok(())
}
pub async fn shutdown(&self) -> Result<(), crate::Error> {
self.change_state("shutdown").await?;
Ok(())
}
pub async fn stop(&self) -> Result<(), crate::Error> {
self.change_state("stop").await?;
Ok(())
}
pub async fn rebuild(&self, os: &OsResource) -> Result<(), crate::Error> {
let body = json::json!({ "name": os.name });
self.client
.put_with_body(&format!("/accounts/{}/rebuild", self.id), body)
.await?;
Ok(())
}
async fn change_state(&self, state: &str) -> Result<(), crate::Error> {
self.client
.put(&format!("/accounts/{}/vm-state/{}", self.id, state))
.await?;
Ok(())
}
}