From 5d8cf2df8a12eb7791967406734a2267f9f0fb68 Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Tue, 3 Dec 2024 04:42:54 +0800 Subject: [PATCH 1/4] feat: add TProxy.GetInfo --- tproxy/rpc/proto/tproxy_rpc.proto | 12 +++++++++++ tproxy/src/main_service.rs | 35 ++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/tproxy/rpc/proto/tproxy_rpc.proto b/tproxy/rpc/proto/tproxy_rpc.proto index f42b2ab07..f4eb4e266 100644 --- a/tproxy/rpc/proto/tproxy_rpc.proto +++ b/tproxy/rpc/proto/tproxy_rpc.proto @@ -71,6 +71,16 @@ message AcmeInfoResponse { repeated bytes hist_keys = 2; } +// Get HostInfo for associated instance id. +message GetInfoRequest { + string id = 1; +} + +message GetInfoResponse { + bool found = 1; + optional HostInfo info = 2; +} + service Tproxy { // Register a new proxied CVM. rpc RegisterCvm(RegisterCvmRequest) returns (RegisterCvmResponse) {} @@ -78,4 +88,6 @@ service Tproxy { rpc List(google.protobuf.Empty) returns (ListResponse) {} // List all ACME account URIs and the public key history of the certificates for the Content Addressable HTTPS. rpc AcmeInfo(google.protobuf.Empty) returns (AcmeInfoResponse) {} + // Find Proxied HostInfo by instance ID + rpc GetInfo(GetInfoRequest) returns (GetInfoResponse) {} } diff --git a/tproxy/src/main_service.rs b/tproxy/src/main_service.rs index 3f54723ab..3e8f238fe 100644 --- a/tproxy/src/main_service.rs +++ b/tproxy/src/main_service.rs @@ -16,7 +16,8 @@ use serde::{Deserialize, Serialize}; use tproxy_rpc::{ tproxy_server::{TproxyRpc, TproxyServer}, AcmeInfoResponse, HostInfo as PbHostInfo, ListResponse, RegisterCvmRequest, - RegisterCvmResponse, TappdConfig, WireGuardConfig, + RegisterCvmResponse, TappdConfig, WireGuardConfig, GetInfoRequest, + GetInfoResponse, }; use tracing::{debug, error, info}; @@ -368,6 +369,38 @@ impl TproxyRpc for RpcHandler { Ok(ListResponse { hosts }) } + async fn get_info(self, request: GetInfoRequest) -> Result { + let state = self.state.lock(); + let base_domain = &state.config.proxy.base_domain; + let handshakes = state.latest_handshakes(None)?; + + if let Some(instance) = state.state.instances.get(&request.id) { + let host_info = PbHostInfo { + id: instance.id.clone(), + ip: instance.ip.to_string(), + app_id: instance.app_id.clone(), + base_domain: base_domain.clone(), + port: state.config.proxy.listen_port as u32, + latest_handshake: { + let (ts, _) = handshakes + .get(&instance.public_key) + .copied() + .unwrap_or_default(); + ts + }, + }; + Ok(GetInfoResponse { + found: true, + info: Some(host_info), + }) + } else { + Ok(GetInfoResponse { + found: false, + info: None, + }) + } + } + async fn acme_info(self) -> Result { let state = self.state.lock(); let workdir = WorkDir::new(&state.config.certbot.workdir); From a59c28453b325a22ecc14bf77c72fb568494f9f3 Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Tue, 3 Dec 2024 22:25:06 +0800 Subject: [PATCH 2/4] feat: add Teepod.GetInfo & Teepod.ResizeVm --- teepod/rpc/proto/teepod_rpc.proto | 22 ++++++++++++++ teepod/src/app.rs | 49 ++++++++++++++++++++++++++++++- teepod/src/main_service.rs | 44 ++++++++++++++++++++++++++- 3 files changed, 113 insertions(+), 2 deletions(-) diff --git a/teepod/rpc/proto/teepod_rpc.proto b/teepod/rpc/proto/teepod_rpc.proto index c5ac9ff2b..b90d9e2b9 100644 --- a/teepod/rpc/proto/teepod_rpc.proto +++ b/teepod/rpc/proto/teepod_rpc.proto @@ -91,6 +91,22 @@ message PublicKeyResponse { bytes public_key = 1; } +message GetInfoResponse { + bool found = 1; + optional VmInfo info = 2; +} + +message ResizeVmRequest { + // Unique identifier for the VM + string id = 1; + // Number of vCPUs + optional uint32 vcpu = 2; + // Memory in MB + optional uint32 memory = 3; + // Disk size in GB + optional uint32 disk_size = 4; +} + // Service definition for Teepod service Teepod { // RPC to create a VM @@ -111,4 +127,10 @@ service Teepod { // Get Env encrypt public key rpc GetAppEnvEncryptPubKey(AppId) returns (PublicKeyResponse); + + // Get VM info by ID + rpc GetInfo(Id) returns (GetInfoResponse); + + // RPC to resize a VM + rpc ResizeVm(ResizeVmRequest) returns (google.protobuf.Empty); } diff --git a/teepod/src/app.rs b/teepod/src/app.rs index c951bca23..542b3697a 100644 --- a/teepod/src/app.rs +++ b/teepod/src/app.rs @@ -27,7 +27,7 @@ use std::net::IpAddr; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use teepod_rpc as pb; -use tracing::error; +use tracing::{error, Instrument}; mod id_pool; @@ -302,4 +302,51 @@ impl App { }) .collect()) } + + pub fn get_vm(&self, id: &str) -> Option { + let state = self.state.lock().unwrap(); + let vm = state.monitor.iter_vms().find(|vm| vm.info().manifest.id == id)?; + let info = vm.info(); + let gw = &self.config.gateway; + + Some(pb::VmInfo { + id: info.manifest.id, + name: info.manifest.name.clone(), + status: info.status.to_string(), + uptime: info.uptime, + configuration: Some(pb::VmConfiguration { + name: info.manifest.name, + image: info.manifest.image, + compose_file: { + let workdir = VmWorkDir::new(&info.workdir); + fs::read_to_string(workdir.app_compose_path()).unwrap_or_default() + }, + encrypted_env: { + let workdir = VmWorkDir::new(&info.workdir); + fs::read(workdir.encrypted_env_path()).unwrap_or_default() + }, + vcpu: info.manifest.vcpu, + memory: info.manifest.memory, + disk_size: info.manifest.disk_size, + ports: info + .manifest + .port_map + .into_iter() + .map(|pm| pb::PortMapping { + protocol: pm.protocol.as_str().into(), + host_port: pm.from as u32, + vm_port: pm.to as u32, + }) + .collect(), + }), + app_url: info.instance_id.as_ref().map(|id| { + format!( + "https://{id}-{}.{}:{}", + gw.tappd_port, gw.base_domain, gw.port + ) + }), + app_id: info.manifest.app_id, + instance_id: info.instance_id, + }) + } } diff --git a/teepod/src/main_service.rs b/teepod/src/main_service.rs index 7f9340ad8..5fb6f8c3c 100644 --- a/teepod/src/main_service.rs +++ b/teepod/src/main_service.rs @@ -9,7 +9,7 @@ use ra_rpc::{Attestation, RpcCall}; use teepod_rpc::teepod_server::{TeepodRpc, TeepodServer}; use teepod_rpc::{ AppId, Id, ImageInfo as RpcImageInfo, ImageListResponse, PublicKeyResponse, StatusResponse, - UpgradeAppRequest, VmConfiguration, + UpgradeAppRequest, VmConfiguration, GetInfoResponse, ResizeVmRequest, }; use tracing::warn; @@ -241,6 +241,48 @@ impl TeepodRpc for RpcHandler { public_key: response.public_key, }) } + + async fn get_info(self, request: Id) -> Result { + if let Some(vm) = self.app.get_vm(&request.id) { + Ok(GetInfoResponse { + found: true, + info: Some(vm), + }) + } else { + Ok(GetInfoResponse { + found: false, + info: None, + }) + } + } + + async fn resize_vm(self, request: ResizeVmRequest) -> Result<()> { + let vm = self.app + .get_vm(&request.id) + .ok_or_else(|| anyhow::anyhow!("vm not found: {}", request.id))?; + if vm.status != "stopped" { + return Err(anyhow::anyhow!("vm should be stopped before resize: {}", request.id)); + } + let work_dir = self.app.config.run_path.join(&request.id); + let vm_work_dir = VmWorkDir::new(&work_dir); + let mut manifest = vm_work_dir + .manifest() + .context("failed to read manifest")?; + if let Some(vcpu) = request.vcpu { + manifest.vcpu = vcpu; + } + if let Some(memory) = request.memory { + manifest.memory = memory; + } + if let Some(disk_size) = request.disk_size { + manifest.disk_size = disk_size; + } + vm_work_dir + .put_manifest(&manifest) + .context("failed to update manifest")?; + self.app.load_vm(work_dir).context("Failed to load VM")?; + Ok(()) + } } impl RpcCall for RpcHandler { From fb8865c92de5c363ca0101a3e92b2a2d3a21e3ab Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Wed, 4 Dec 2024 12:22:15 +0800 Subject: [PATCH 3/4] refactor: add VmMonitor.get_vm and VmInfo.to_pb --- teepod/src/app.rs | 83 ++--------------------------------------------- teepod/src/vm.rs | 51 ++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 81 deletions(-) diff --git a/teepod/src/app.rs b/teepod/src/app.rs index 542b3697a..ef3577d75 100644 --- a/teepod/src/app.rs +++ b/teepod/src/app.rs @@ -240,45 +240,7 @@ impl App { infos .into_iter() - .map(|info| pb::VmInfo { - id: info.manifest.id, - name: info.manifest.name.clone(), - status: info.status.to_string(), - uptime: info.uptime, - configuration: Some(pb::VmConfiguration { - name: info.manifest.name, - image: info.manifest.image, - compose_file: { - let workdir = VmWorkDir::new(&info.workdir); - fs::read_to_string(workdir.app_compose_path()).unwrap_or_default() - }, - encrypted_env: { - let workdir = VmWorkDir::new(&info.workdir); - fs::read(workdir.encrypted_env_path()).unwrap_or_default() - }, - vcpu: info.manifest.vcpu, - memory: info.manifest.memory, - disk_size: info.manifest.disk_size, - ports: info - .manifest - .port_map - .into_iter() - .map(|pm| pb::PortMapping { - protocol: pm.protocol.as_str().into(), - host_port: pm.from as u32, - vm_port: pm.to as u32, - }) - .collect(), - }), - app_url: info.instance_id.as_ref().map(|id| { - format!( - "https://{id}-{}.{}:{}", - gw.tappd_port, gw.base_domain, gw.port - ) - }), - app_id: info.manifest.app_id, - instance_id: info.instance_id, - }) + .map(|info| info.to_pb(gw)) .collect() } @@ -305,48 +267,9 @@ impl App { pub fn get_vm(&self, id: &str) -> Option { let state = self.state.lock().unwrap(); - let vm = state.monitor.iter_vms().find(|vm| vm.info().manifest.id == id)?; + let vm = state.monitor.get_vm(id)?; let info = vm.info(); let gw = &self.config.gateway; - - Some(pb::VmInfo { - id: info.manifest.id, - name: info.manifest.name.clone(), - status: info.status.to_string(), - uptime: info.uptime, - configuration: Some(pb::VmConfiguration { - name: info.manifest.name, - image: info.manifest.image, - compose_file: { - let workdir = VmWorkDir::new(&info.workdir); - fs::read_to_string(workdir.app_compose_path()).unwrap_or_default() - }, - encrypted_env: { - let workdir = VmWorkDir::new(&info.workdir); - fs::read(workdir.encrypted_env_path()).unwrap_or_default() - }, - vcpu: info.manifest.vcpu, - memory: info.manifest.memory, - disk_size: info.manifest.disk_size, - ports: info - .manifest - .port_map - .into_iter() - .map(|pm| pb::PortMapping { - protocol: pm.protocol.as_str().into(), - host_port: pm.from as u32, - vm_port: pm.to as u32, - }) - .collect(), - }), - app_url: info.instance_id.as_ref().map(|id| { - format!( - "https://{id}-{}.{}:{}", - gw.tappd_port, gw.base_domain, gw.port - ) - }), - app_id: info.manifest.app_id, - instance_id: info.instance_id, - }) + Some(info.to_pb(gw)) } } diff --git a/teepod/src/vm.rs b/teepod/src/vm.rs index ab820132f..b9f1efc9b 100644 --- a/teepod/src/vm.rs +++ b/teepod/src/vm.rs @@ -83,7 +83,9 @@ pub(crate) mod image { } pub(crate) mod run { - use crate::app::Manifest; + use crate::app::{Manifest, VmWorkDir}; + use crate::config::GatewayConfig; + use teepod_rpc as pb; pub use super::image::Image; pub use super::qemu::{TdxConfig, VmConfig}; @@ -253,6 +255,49 @@ pub(crate) mod run { pub instance_id: Option, } + impl VmInfo { + pub fn to_pb(&self, gw: &GatewayConfig) -> pb::VmInfo { + let workdir = VmWorkDir::new(&self.workdir); + pb::VmInfo { + id: self.manifest.id.as_str().into(), + name: self.manifest.name.as_str().into(), + status: self.status.into(), + uptime: self.uptime.as_str().into(), + configuration: Some(pb::VmConfiguration { + name: self.manifest.name.as_str().into(), + image: self.manifest.image.as_str().into(), + compose_file: { + fs::read_to_string(workdir.app_compose_path()).unwrap_or_default() + }, + encrypted_env: { + fs::read(workdir.encrypted_env_path()).unwrap_or_default() + }, + vcpu: self.manifest.vcpu, + memory: self.manifest.memory, + disk_size: self.manifest.disk_size, + ports: self + .manifest + .port_map + .iter() + .map(|pm| pb::PortMapping { + protocol: pm.protocol.as_str().into(), + host_port: pm.from as u32, + vm_port: pm.to as u32, + }) + .collect(), + }), + app_url: self.instance_id.as_ref().map(|id| { + format!( + "https://{id}-{}.{}:{}", + gw.tappd_port, gw.base_domain, gw.port + ) + }), + app_id: self.manifest.app_id.as_str().into(), + instance_id: self.instance_id.as_deref().map(Into::into), + } + } + } + pub struct VmMonitor { qemu_bin: PathBuf, vms: BTreeMap, @@ -317,6 +362,10 @@ pub(crate) mod run { pub fn iter_vms(&self) -> impl Iterator { self.vms.values() } + + pub fn get_vm(&self, id: &str) -> Option<&VmInstance> { + self.vms.get(id) + } } } From 1618938c0c34aa0449806ccc753e56b66b20f954 Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Wed, 4 Dec 2024 12:27:35 +0800 Subject: [PATCH 4/4] docs: comment on the resize disk size behavior --- teepod/src/main_service.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/teepod/src/main_service.rs b/teepod/src/main_service.rs index 5fb6f8c3c..91c231adf 100644 --- a/teepod/src/main_service.rs +++ b/teepod/src/main_service.rs @@ -275,6 +275,7 @@ impl TeepodRpc for RpcHandler { manifest.memory = memory; } if let Some(disk_size) = request.disk_size { + // it only updates the manifesta and does NOT affect the real storage alloc at this time. manifest.disk_size = disk_size; } vm_work_dir