185 lines
6.8 KiB
Rust
185 lines
6.8 KiB
Rust
use super::{
|
|
grpc::{self, proto},
|
|
injector, Distro, Dtrfs, Error, VmSshArgs, DEFAULT_ARCHLINUX, DEFAULT_DTRFS,
|
|
};
|
|
use crate::config::Config;
|
|
use crate::utils::block_on;
|
|
use log::{debug, info};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub enum IPv4Config {
|
|
PublishPorts(Vec<u32>),
|
|
PublicIPv4,
|
|
}
|
|
|
|
// TODO: push this out of snp module
|
|
#[derive(Serialize, Deserialize, Default)]
|
|
pub struct Location {
|
|
pub node_ip: Option<String>,
|
|
pub country: Option<String>,
|
|
pub region: Option<String>,
|
|
pub city: Option<String>,
|
|
}
|
|
|
|
impl From<&str> for Location {
|
|
fn from(s: &str) -> Self {
|
|
match s {
|
|
"Canada" => Self { country: Some("CA".to_string()), ..Default::default() },
|
|
"Montreal" => Self { city: Some("Montréal".to_string()), ..Default::default() },
|
|
"Vancouver" => Self { city: Some("Vancouver".to_string()), ..Default::default() },
|
|
"US" => Self { country: Some("US".to_string()), ..Default::default() },
|
|
"California" => Self { country: Some("US".to_string()), ..Default::default() },
|
|
"France" => Self { country: Some("FR".to_string()), ..Default::default() },
|
|
"GB" => Self { country: Some("GB".to_string()), ..Default::default() },
|
|
"Random" => Self { ..Default::default() },
|
|
"DE" => Self { country: Some("DE".to_string()), ..Default::default() },
|
|
_ => Self { city: Some("Vancouver".to_string()), ..Default::default() },
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct Request {
|
|
pub hostname: String,
|
|
pub hours: u32,
|
|
// price per unit per minute
|
|
pub price: u64,
|
|
pub location: Location,
|
|
pub ipv4: IPv4Config,
|
|
pub public_ipv6: bool,
|
|
pub vcpus: u32,
|
|
pub memory_mb: u32,
|
|
pub disk_size_gb: u32,
|
|
pub dtrfs: Option<Dtrfs>,
|
|
pub distro: Option<Distro>,
|
|
}
|
|
|
|
impl Request {
|
|
pub fn load_from_yaml(config_path: &str) -> Result<VmSshArgs, Error> {
|
|
let new_vm_config = Self::load_from_file(config_path)?;
|
|
new_vm_config.deploy()
|
|
}
|
|
|
|
fn load_from_file(config_path: &str) -> Result<Self, Error> {
|
|
let content = std::fs::read_to_string(config_path)?;
|
|
let new_vm_config: Self = serde_yaml::from_str(&content)?;
|
|
Ok(new_vm_config)
|
|
}
|
|
|
|
pub fn deploy(&self) -> Result<VmSshArgs, Error> {
|
|
let (node_ip, new_vm_resp) = self.send_vm_request()?;
|
|
info!("Got confirmation from the node {node_ip} that VM started.");
|
|
debug!("IPs and ports assigned by node are: {new_vm_resp:#?}");
|
|
if !new_vm_resp.error.is_empty() {
|
|
return Err(Error::Node(new_vm_resp.error));
|
|
}
|
|
let (kernel_sha, dtrfs_sha) = match self.dtrfs.clone() {
|
|
Some(dtrfs) => (dtrfs.kernel_sha, dtrfs.dtrfs_sha),
|
|
None => (DEFAULT_DTRFS.kernel_sha.clone(), DEFAULT_DTRFS.dtrfs_sha.clone()),
|
|
};
|
|
let args = new_vm_resp.args.ok_or(Error::NoMeasurement)?;
|
|
let measurement_args = injector::Args {
|
|
uuid: new_vm_resp.uuid.clone(),
|
|
vcpus: self.vcpus,
|
|
kernel: kernel_sha,
|
|
initrd: dtrfs_sha,
|
|
args: args.clone(),
|
|
};
|
|
let measurement = measurement_args.get_measurement()?;
|
|
let (template_url, template_sha) = match &self.distro {
|
|
Some(distro) => (distro.template_url.clone(), distro.template_sha.clone()),
|
|
None => {
|
|
(DEFAULT_ARCHLINUX.template_url.clone(), DEFAULT_ARCHLINUX.template_sha.clone())
|
|
}
|
|
};
|
|
let mut ssh_args = injector::execute(
|
|
measurement,
|
|
args.dtrfs_api_endpoint.clone(),
|
|
Some((&template_url, &template_sha)),
|
|
&self.hostname,
|
|
)?;
|
|
ssh_args.uuid = new_vm_resp.uuid;
|
|
ssh_args.hostname = self.hostname.clone();
|
|
let _ = super::append_uuid_list(&ssh_args.uuid, &ssh_args.hostname);
|
|
Ok(ssh_args)
|
|
}
|
|
|
|
// returns node IP and data regarding the new VM
|
|
fn send_vm_request(&self) -> Result<(String, proto::NewVmResp), Error> {
|
|
let admin_pubkey = Config::get_detee_wallet()?;
|
|
let node = self.get_node()?;
|
|
let (extra_ports, public_ipv4): (Vec<u32>, bool) = match &self.ipv4 {
|
|
IPv4Config::PublishPorts(vec) => (vec.to_vec(), false),
|
|
IPv4Config::PublicIPv4 => (Vec::new(), true),
|
|
};
|
|
let (kernel_url, kernel_sha, dtrfs_url, dtrfs_sha) = match self.dtrfs.clone() {
|
|
Some(dtrfs) => (dtrfs.kernel_url, dtrfs.kernel_sha, dtrfs.dtrfs_url, dtrfs.dtrfs_sha),
|
|
None => (
|
|
DEFAULT_DTRFS.kernel_url.clone(),
|
|
DEFAULT_DTRFS.kernel_sha.clone(),
|
|
DEFAULT_DTRFS.dtrfs_url.clone(),
|
|
DEFAULT_DTRFS.dtrfs_sha.clone(),
|
|
),
|
|
};
|
|
let locked_nano = super::calculate_nanolp(
|
|
self.vcpus,
|
|
self.memory_mb,
|
|
self.disk_size_gb,
|
|
public_ipv4,
|
|
self.hours,
|
|
self.price,
|
|
);
|
|
let brain_req = proto::NewVmReq {
|
|
uuid: String::new(),
|
|
hostname: self.hostname.clone(),
|
|
admin_pubkey,
|
|
node_pubkey: node.node_pubkey,
|
|
extra_ports,
|
|
public_ipv4,
|
|
public_ipv6: self.public_ipv6,
|
|
disk_size_gb: self.disk_size_gb,
|
|
vcpus: self.vcpus,
|
|
memory_mb: self.memory_mb,
|
|
kernel_url,
|
|
kernel_sha,
|
|
dtrfs_url,
|
|
dtrfs_sha,
|
|
price_per_unit: self.price,
|
|
locked_nano,
|
|
};
|
|
let new_vm_resp = block_on(grpc::create_vm(brain_req))?;
|
|
if !new_vm_resp.error.is_empty() {
|
|
return Err(Error::Node(new_vm_resp.error));
|
|
}
|
|
Ok((node.ip, new_vm_resp))
|
|
}
|
|
|
|
pub fn get_node(&self) -> Result<proto::VmNodeListResp, Error> {
|
|
let (free_ports, offers_ipv4) = match &self.ipv4 {
|
|
IPv4Config::PublishPorts(vec) => (vec.len() as u32, false),
|
|
IPv4Config::PublicIPv4 => (0, true),
|
|
};
|
|
let filters = proto::VmNodeFilters {
|
|
free_ports,
|
|
offers_ipv4,
|
|
offers_ipv6: self.public_ipv6,
|
|
vcpus: self.vcpus,
|
|
memory_mb: self.memory_mb,
|
|
storage_gb: self.disk_size_gb,
|
|
country: self.location.country.clone().unwrap_or_default(),
|
|
region: self.location.region.clone().unwrap_or_default(),
|
|
city: self.location.city.clone().unwrap_or_default(),
|
|
ip: self.location.node_ip.clone().unwrap_or_default(),
|
|
node_pubkey: String::new(),
|
|
};
|
|
match block_on(grpc::get_one_node(filters)) {
|
|
Ok(node) => Ok(node),
|
|
Err(e) => {
|
|
log::error!("Coult not get node from brain: {e:?}");
|
|
Err(Error::NoValidNodeFound)
|
|
}
|
|
}
|
|
}
|
|
}
|