use std::{
net::Ipv4Addr,
path::{Path, PathBuf},
};
use chrono::{DateTime, Utc};
use getset::{Getters, MutGetters, Setters};
use serde::{Deserialize, Serialize};
use crate::config::{Group, Service};
#[derive(Debug, Clone, Getters, Setters, MutGetters, Serialize, Deserialize)]
#[getset(
get = "pub with_prefix",
set = "pub with_prefix",
get_mut = "pub with_prefix"
)]
pub struct MicroVmState {
pid: Option<u32>,
created_at: DateTime<Utc>,
modified_at: DateTime<Utc>,
service: Service,
group: Group,
rootfs_path: PathBuf,
status: MicroVmStatus,
metrics: MicroVmMetrics,
group_ip: Option<Ipv4Addr>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum MicroVmStatus {
Unstarted,
Starting,
Started,
Stopping,
Stopped {
exit_code: i32,
},
Failed {
error: String,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MicroVmMetrics {
cpu_usage: f32,
memory_usage: u64,
disk_read_bytes: u64,
disk_write_bytes: u64,
total_disk_read_bytes: u64,
total_disk_write_bytes: u64,
}
impl MicroVmState {
pub fn new(
service: Service,
group: Group,
group_ip: Option<Ipv4Addr>,
rootfs_path: impl AsRef<Path>,
) -> Self {
Self {
pid: None,
created_at: Utc::now(),
modified_at: Utc::now(),
service,
group,
rootfs_path: rootfs_path.as_ref().to_path_buf(),
status: MicroVmStatus::Unstarted,
metrics: MicroVmMetrics::new(),
group_ip,
}
}
pub async fn save<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
let serialized = serde_json::to_string(self)?;
tokio::fs::write(path, serialized).await?;
Ok(())
}
pub async fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let data = tokio::fs::read_to_string(path).await?;
let state = serde_json::from_str(&data)?;
Ok(state)
}
}
impl MicroVmMetrics {
pub fn new() -> Self {
Self {
cpu_usage: 0.0,
memory_usage: 0,
disk_read_bytes: 0,
disk_write_bytes: 0,
total_disk_read_bytes: 0,
total_disk_write_bytes: 0,
}
}
pub fn set_cpu_usage(&mut self, usage: f32) {
self.cpu_usage = usage;
}
pub fn set_memory_usage(&mut self, usage: u64) {
self.memory_usage = usage;
}
pub fn set_disk_read_bytes(&mut self, bytes: u64) {
self.disk_read_bytes = bytes;
}
pub fn set_disk_write_bytes(&mut self, bytes: u64) {
self.disk_write_bytes = bytes;
}
pub fn set_total_disk_read_bytes(&mut self, bytes: u64) {
self.total_disk_read_bytes = bytes;
}
pub fn set_total_disk_write_bytes(&mut self, bytes: u64) {
self.total_disk_write_bytes = bytes;
}
pub fn get_cpu_usage(&self) -> f32 {
self.cpu_usage
}
pub fn get_memory_usage(&self) -> u64 {
self.memory_usage
}
pub fn get_disk_read_bytes(&self) -> u64 {
self.disk_read_bytes
}
pub fn get_disk_write_bytes(&self) -> u64 {
self.disk_write_bytes
}
pub fn get_total_disk_read_bytes(&self) -> u64 {
self.total_disk_read_bytes
}
pub fn get_total_disk_write_bytes(&self) -> u64 {
self.total_disk_write_bytes
}
}
impl Default for MicroVmMetrics {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::NamedTempFile;
#[tokio::test]
async fn test_save_and_load_microvm_state() -> anyhow::Result<()> {
let temp_dir = tempfile::tempdir()?;
let state_file = NamedTempFile::new()?;
let state = MicroVmState::new(
Service::default(),
Group::builder().name("test-group").build(),
None,
temp_dir.path(),
);
state.save(state_file.path()).await?;
let loaded_state = MicroVmState::load(state_file.path()).await?;
assert_eq!(state.get_pid(), loaded_state.get_pid());
assert_eq!(state.get_created_at(), loaded_state.get_created_at());
assert_eq!(state.get_modified_at(), loaded_state.get_modified_at());
assert_eq!(state.get_service(), loaded_state.get_service());
assert_eq!(state.get_rootfs_path(), loaded_state.get_rootfs_path());
assert_eq!(state.get_status(), loaded_state.get_status());
assert_eq!(state.get_metrics(), loaded_state.get_metrics());
assert_eq!(state.get_group_ip(), loaded_state.get_group_ip());
Ok(())
}
}