emu_cli/
vm.rs

1use super::{
2    config_storage::XDGConfigStorage,
3    supervisor::{PidSupervisor, SystemdSupervisor},
4    traits::{ConfigStorageHandler, SupervisorHandler, Supervisors},
5};
6use crate::config::Configuration;
7use anyhow::Result;
8use serde::{de::Visitor, Deserialize, Serialize};
9use std::{fmt::Display, path::PathBuf, sync::Arc};
10
11#[derive(Debug, Clone, Default, PartialEq, Eq)]
12pub struct VM {
13    name: String,
14    cdrom: Option<PathBuf>,
15    extra_disk: Option<PathBuf>,
16    config: Configuration,
17    headless: bool,
18    supervisor: Supervisors,
19}
20
21impl std::hash::Hash for VM {
22    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
23        self.name.hash(state)
24    }
25}
26
27impl Display for VM {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        f.write_str(&self.name())
30    }
31}
32
33impl From<String> for VM {
34    fn from(value: String) -> Self {
35        Self::new(value, Arc::new(Box::new(XDGConfigStorage::default())))
36    }
37}
38
39impl VM {
40    pub fn new(name: String, storage: Arc<Box<dyn ConfigStorageHandler>>) -> Self {
41        let mut obj = Self {
42            name,
43            ..Default::default()
44        };
45
46        if SystemdSupervisor::default().storage().exists(&obj) {
47            obj.supervisor = Supervisors::Systemd;
48        }
49
50        obj.load_config(storage);
51        obj
52    }
53
54    pub fn set_headless(&mut self, headless: bool) {
55        self.headless = headless
56    }
57
58    pub fn headless(&self) -> bool {
59        self.headless
60    }
61
62    pub fn name(&self) -> String {
63        self.name.clone()
64    }
65
66    pub fn cdrom(&self) -> Option<PathBuf> {
67        self.cdrom.clone()
68    }
69
70    pub fn set_cdrom(&mut self, cdrom: PathBuf) {
71        self.cdrom = Some(cdrom)
72    }
73
74    pub fn extra_disk(&self) -> Option<PathBuf> {
75        self.cdrom.clone()
76    }
77
78    pub fn set_extra_disk(&mut self, extra_disk: PathBuf) {
79        self.extra_disk = Some(extra_disk)
80    }
81
82    pub fn config(&self) -> Configuration {
83        self.config.clone()
84    }
85
86    pub fn supervisor(&self) -> Arc<Box<dyn SupervisorHandler>> {
87        match self.supervisor {
88            Supervisors::Systemd => Arc::new(Box::new(SystemdSupervisor::default())),
89            _ => Arc::new(Box::new(PidSupervisor::default())),
90        }
91    }
92
93    pub fn load_config(&mut self, storage: Arc<Box<dyn ConfigStorageHandler>>) {
94        self.config = Configuration::from_file(storage.config_path(self));
95    }
96
97    pub fn save_config(&mut self, storage: Arc<Box<dyn ConfigStorageHandler>>) -> Result<()> {
98        self.config.to_file(storage.config_path(self))
99    }
100
101    pub fn set_config(&mut self, config: Configuration) {
102        self.config = config;
103    }
104}
105
106impl Serialize for VM {
107    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
108    where
109        S: serde::Serializer,
110    {
111        serializer.serialize_str(&self.name)
112    }
113}
114
115struct VMVisitor;
116
117impl Visitor<'_> for VMVisitor {
118    type Value = VM;
119
120    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
121        formatter.write_str("expecting a vm name")
122    }
123
124    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> {
125        Ok(v.to_string().into())
126    }
127}
128
129impl<'de> Deserialize<'de> for VM {
130    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
131    where
132        D: serde::Deserializer<'de>,
133    {
134        deserializer.deserialize_str(VMVisitor)
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use crate::config_storage::XDGConfigStorage;
142    use anyhow::Result;
143    use std::sync::Arc;
144    use tempfile::tempdir;
145
146    #[test]
147    fn test_into() -> Result<()> {
148        let vm1: VM = "vm1".to_string().into();
149        assert_eq!(vm1.name(), "vm1".to_string());
150        Ok(())
151    }
152
153    #[test]
154    fn test_serde() -> Result<()> {
155        let vm1: VM = "vm1".to_string().into();
156        assert_eq!(serde_json::to_string(&vm1)?, "\"vm1\"");
157        let vm1: VM = serde_json::from_str("\"vm1\"")?;
158        assert_eq!(vm1.name(), "vm1".to_string());
159        Ok(())
160    }
161
162    #[test]
163    fn test_vm_operations() -> Result<()> {
164        let dir = tempdir()?;
165        let base_path = dir.into_path();
166        let storage: Arc<Box<dyn ConfigStorageHandler>> =
167            Arc::new(Box::new(XDGConfigStorage::new(base_path)));
168
169        let mut vm = VM::new("vm1".to_string(), storage.clone());
170        storage.create(&vm)?;
171
172        assert!(!vm.supervisor().supervised());
173        assert!(!vm.supervisor().is_active(&vm)?);
174        let mut config = vm.config();
175        config.machine.ssh_port = 2000;
176        vm.set_config(config.clone());
177        vm.save_config(storage.clone())?;
178        vm.load_config(storage.clone());
179        assert_eq!(vm.config(), config);
180
181        vm.set_cdrom(PathBuf::from("/cdrom"));
182        assert_eq!(vm.cdrom(), Some(PathBuf::from("/cdrom")));
183        vm.set_extra_disk(PathBuf::from("/cdrom"));
184        assert_eq!(vm.extra_disk(), Some(PathBuf::from("/cdrom")));
185        vm.set_headless(true);
186        assert!(vm.headless());
187
188        Ok(())
189    }
190}