detee-cli/src/sgx/deploy.rs

178 lines
6.3 KiB
Rust

use crate::config::Config;
use crate::name_generator::random_app_name;
use crate::sgx::grpc_brain::{get_app_node_list, new_app};
use crate::sgx::grpc_dtpm::{dtpm_client, set_config_pb, upload_files_pb};
use crate::sgx::utils::{
calculate_nanocredits_for_app, fetch_config, hratls_url_and_mr_enclave_from_app_id,
};
use crate::sgx::{
append_app_id_list, package_entry_from_name, AppDeployResponse, Error, PackageElement,
};
use crate::snp;
use detee_shared::app_proto::{AppNodeFilters, AppNodeListResp, AppResource, NewAppReq};
use detee_shared::sgx::pb::dtpm_proto::DtpmSetConfigReq;
use serde::{Deserialize, Serialize};
use serde_default_utils::*;
use tokio_retry::strategy::FixedInterval;
use tokio_retry::Retry;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Reqwest {
#[serde(default = "random_app_name")]
pub app_name: String,
pub package_name: String,
pub vcpus: u32,
pub memory_mib: u32,
pub disk_size_mib: u32,
pub port: Vec<u32>,
#[serde(default = "default_u64::<1>")]
pub hours: u64,
#[serde(default)]
pub price: u64,
#[serde(default)]
pub location: String,
pub envs: Vec<String>,
pub args: Vec<String>,
}
impl Reqwest {
pub async fn deploy(self) -> Result<AppDeployResponse, Error> {
let mut req = self.get_cheapest_offer().await?;
let PackageElement { package_url, mr_enclave, launch_config_url, .. } =
package_entry_from_name(&self.package_name).expect("Unknown package name");
let AppResource { vcpus, memory_mib, disk_size_mib, .. } =
req.resource.clone().unwrap_or_default();
req.public_package_mr_enclave = Some(mr_enclave.to_vec());
req.admin_pubkey = Config::get_detee_wallet()?;
req.hratls_pubkey = Config::get_hratls_pubkey_hex()?;
req.package_url = package_url;
eprintln!(
"Node {} can offer the app at {} nanocredits for {} hours. Spec: {vcpus} vCPUs, {memory_mib} MiB mem, {disk_size_mib} MiB disk.",
&req.node_pubkey, req.locked_nano, self.hours
);
let new_app_res = new_app(req).await?;
if !new_app_res.error.is_empty() {
return Err(Error::Deployment(new_app_res.error));
}
tokio::time::sleep(tokio::time::Duration::from_millis(2500)).await;
let (hratls_uri, mr_enclave) =
hratls_url_and_mr_enclave_from_app_id(&new_app_res.app_id).await?;
let mr_enclave = mr_enclave.expect("App contract does not have a mr_enclave");
log::info!("hratls uri: {hratls_uri} mr_enclave: {mr_enclave:?}");
if new_app_res.error.is_empty() {
let launch_config = fetch_config(&launch_config_url).await?;
eprint!("Deploying...");
let dtpm_client = Retry::spawn(FixedInterval::from_millis(1000).take(30), || {
log::debug!("retrying attestation and launch config update");
eprint!(".");
dtpm_client(&hratls_uri, &mr_enclave)
})
.await?;
println!("");
upload_files_pb(launch_config.filesystems.clone(), &dtpm_client).await?;
let config_data = Some(launch_config.into());
log::trace!("Decoded the configuration... {:?}", config_data);
let req = DtpmSetConfigReq { config_data, ..Default::default() };
set_config_pb(req, &dtpm_client).await?;
append_app_id_list(&new_app_res.app_id, &self.app_name)?;
Ok((new_app_res, self.app_name).into())
} else {
Err(Error::Deployment(new_app_res.error))
}
}
pub async fn get_cheapest_offer(&self) -> Result<NewAppReq, Error> {
let location = snp::Location::from(self.location.as_str());
let app_node_filter = AppNodeFilters {
vcpus: self.vcpus,
memory_mib: self.memory_mib,
storage_mib: self.disk_size_mib,
country: location.country.clone().unwrap_or_default(),
region: location.region.clone().unwrap_or_default(),
city: location.city.clone().unwrap_or_default(),
ip: location.node_ip.clone().unwrap_or_default(),
node_pubkey: String::new(),
free_ports: (self.port.len() + 1) as u32,
};
let node_list = get_app_node_list(app_node_filter).await?;
let mut node_iter = node_list.iter();
let mut final_req =
self.calculate_app_request(node_iter.next().ok_or(Error::NoValidNodeFound)?);
while let Some(node) = node_iter.next() {
let new_app_req = self.calculate_app_request(node);
if new_app_req.locked_nano < final_req.locked_nano {
final_req = new_app_req;
}
}
Ok(final_req)
}
fn calculate_app_request(&self, node: &AppNodeListResp) -> NewAppReq {
let node_mem_per_cpu = node.memory_mib / node.vcpus;
let node_disk_per_cpu = node.disk_mib / node.vcpus;
let mut req_vcpus = self.vcpus;
if req_vcpus < self.memory_mib.div_ceil(node_mem_per_cpu as u32) {
req_vcpus = self.memory_mib.div_ceil(node_mem_per_cpu as u32);
}
if req_vcpus < self.disk_size_mib.div_ceil(node_disk_per_cpu as u32) {
req_vcpus = self.disk_size_mib.div_ceil(node_disk_per_cpu as u32);
}
let req_mem_mib = req_vcpus * node_mem_per_cpu as u32;
let req_disk_mib = req_vcpus * node_disk_per_cpu as u32;
let nano_credits = calculate_nanocredits_for_app(
req_vcpus,
req_mem_mib,
req_disk_mib,
self.hours,
node.price,
);
let resource = Some(AppResource {
vcpus: req_vcpus,
memory_mib: req_mem_mib,
disk_size_mib: req_disk_mib,
ports: self.port.clone(),
});
let new_app_req = NewAppReq {
node_pubkey: node.node_pubkey.clone(),
resource,
app_id: "".to_string(),
price_per_unit: node.price,
locked_nano: nano_credits,
app_name: self.app_name.clone(),
..Default::default()
};
log::debug!(
"Node {} can offer the app at {} nanocredits for {} hours. Spec: {} vCPUs, {} MiB mem, {} MiB disk.",
node.ip, nano_credits, self.hours, req_vcpus, req_mem_mib, req_disk_mib
);
new_app_req
}
}