Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions teepod/rpc/proto/teepod_rpc.proto
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand All@@ -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);
}
56 changes: 13 additions & 43 deletions teepod/src/app.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -193,54 +193,13 @@ impl App {
let mut infos = self
.lock()
.iter_vms()
.map(|vm| vm.merge_info(&vms, &self.work_dir(&vm.manifest.id)))
.map(|vm| vm.merge_info(vms.get(&vm.manifest.id), &self.work_dir(&vm.manifest.id)))
.collect::<Vec<_>>();

infos.sort_by(|a, b| a.manifest.created_at_ms.cmp(&b.manifest.created_at_ms));
let gw = &self.config.gateway;

let lst = 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,
})
.collect();
let lst = infos.into_iter().map(|info| info.to_pb(gw)).collect();
Ok(lst)
}

Expand All@@ -260,6 +219,17 @@ impl App {
})
.collect())
}

pub async fn get_vm(&self, id: &str) -> Result<Option<pb::VmInfo>> {
let proc_state = self.supervisor.info(id).await?;
let Some(cfg) = self.lock().get(id) else {
return Ok(None);
};
let info = cfg
.merge_info(proc_state.as_ref(), &self.work_dir(id))
.to_pb(&self.config.gateway);
Ok(Some(info))
}
}

pub(crate) struct AppState {
Expand Down
58 changes: 50 additions & 8 deletions teepod/src/app/qemu.rs
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
//! QEMU related code
use crate::{
app::Manifest,
config::{GatewayConfig, Networking},
};
use std::{
collections::HashMap,
ops::Deref,
path::{Path, PathBuf},
process::Command,
time::{Duration, SystemTime},
};

use crate::{app::Manifest, config::Networking};

use super::image::Image;
use anyhow::{Context, Result};
use bon::Builder;
use fs_err as fs;
use serde::{Deserialize, Serialize};
use supervisor_client::supervisor::{ProcessConfig, ProcessInfo};
use teepod_rpc as pb;

#[derive(Debug, Deserialize)]
pub struct InstanceInfo {
Expand DownExpand Up@@ -68,13 +70,53 @@ fn create_hd(
Ok(())
}

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),
}
}
}

impl VmConfig {
pub fn merge_info(&self, states: &HashMap<String, ProcessInfo>, workdir: &VmWorkDir) -> VmInfo {
pub fn merge_info(&self, proc_state: Option<&ProcessInfo>, workdir: &VmWorkDir) -> VmInfo {
fn truncate(d: Duration) -> Duration {
Duration::from_secs(d.as_secs())
}
let info = states.get(&self.manifest.id);
let is_running = match &info {
let is_running = match proc_state {
Some(info) => info.state.status.is_running(),
None => false,
};
Expand All@@ -96,8 +138,8 @@ impl VmConfig {
}
}
}
let uptime = display_ts(info.and_then(|info| info.state.started_at.as_ref()));
let exited_at = display_ts(info.and_then(|info| info.state.stopped_at.as_ref()));
let uptime = display_ts(proc_state.and_then(|info| info.state.started_at.as_ref()));
let exited_at = display_ts(proc_state.and_then(|info| info.state.stopped_at.as_ref()));
let instance_id = workdir.instance_info().ok().map(|info| info.instance_id);
VmInfo {
manifest: self.manifest.clone(),
Expand Down
53 changes: 51 additions & 2 deletions teepod/src/main_service.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,8 +8,8 @@ use ra_rpc::client::RaClient;
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,
AppId, GetInfoResponse, Id, ImageInfo as RpcImageInfo, ImageListResponse, PublicKeyResponse,
ResizeVmRequest, StatusResponse, UpgradeAppRequest, VmConfiguration,
};
use tracing::warn;

Expand DownExpand Up@@ -252,6 +252,55 @@ impl TeepodRpc for RpcHandler {
public_key: response.public_key,
})
}

async fn get_info(self, request: Id) -> Result<GetInfoResponse> {
if let Some(vm) = self.app.get_vm(&request.id).await? {
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)
.await?
.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 {
// it only updates the manifesta and does NOT affect the real storage alloc at this time.
manifest.disk_size = disk_size;
Comment thread
Leechael marked this conversation as resolved.
}
vm_work_dir
.put_manifest(&manifest)
.context("failed to update manifest")?;
self.app
.load_vm(work_dir)
.await
.context("Failed to load VM")?;
Ok(())
}
}

impl RpcCall<App> for RpcHandler {
Expand Down
Loading