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}