@@ -17,12 +17,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
1717*/
1818
1919use std:: fmt:: Display ;
20- use std:: path:: PathBuf ;
20+ use std:: path:: { Path , PathBuf } ;
2121
2222use serde:: { Deserialize , Serialize } ;
2323
24- use crate :: configuration:: Disk ;
25- use crate :: configuration:: Template ;
24+ use crate :: error:: ConfigurationError ;
25+
26+ use super :: Disk ;
27+ use super :: Template ;
2628
2729/// Domain configuration
2830#[ derive( Debug , Clone , Default , Eq , PartialEq , Ord , PartialOrd , Hash , Serialize , Deserialize ) ]
@@ -40,6 +42,8 @@ pub struct Domain {
4042 /// List of disks for the domain
4143 /// This is the list of disks that are used by the domain.
4244 disks : Vec < Disk > ,
45+ /// List of disk snapshots for the domain
46+ snapshots : Vec < Disk > ,
4347 /// Packer templates for the domain
4448 templates : Option < Template > ,
4549}
@@ -50,17 +54,96 @@ impl Domain {
5054 path : PathBuf ,
5155 configuration_file : Option < PathBuf > ,
5256 disks : Vec < Disk > ,
57+ disks_snapshots : Vec < Disk > ,
5358 templates : Option < Template > ,
5459 ) -> Self {
5560 Self {
5661 name,
5762 path,
5863 configuration_file,
5964 disks,
65+ snapshots : disks_snapshots,
6066 templates,
6167 }
6268 }
6369
70+ pub fn new_empty ( name : String , path : PathBuf ) -> Self {
71+ Self {
72+ name,
73+ path,
74+ configuration_file : None ,
75+ disks : vec ! [ ] ,
76+ snapshots : vec ! [ ] ,
77+ templates : None ,
78+ }
79+ }
80+
81+ fn parse_configuration_file ( & self ) -> Result < Option < PathBuf > , ConfigurationError > {
82+ let path = self . get_configuration_file_path ( ) ;
83+
84+ // check if the file exists and is not empty
85+ if path. exists ( ) && path. is_file ( ) {
86+ let metadata = path. metadata ( ) . map_err ( ConfigurationError :: Parsing ) ?;
87+
88+ if metadata. len ( ) > 0 {
89+ return Ok ( Some ( path) ) ;
90+ }
91+ }
92+
93+ Ok ( None )
94+ }
95+
96+ fn parse_disks_in_directory ( & self , path : & Path ) -> Result < Vec < Disk > , ConfigurationError > {
97+ let mut disks = vec ! [ ] ;
98+
99+ if !path. exists ( ) {
100+ return Ok ( disks) ;
101+ }
102+
103+ // iterate over the disks directory, and parse each disk
104+ for entry in path. read_dir ( ) . map_err ( ConfigurationError :: Parsing ) ? {
105+ let entry = entry. map_err ( ConfigurationError :: Parsing ) ?;
106+ let path = entry. path ( ) ;
107+
108+ if path. is_file ( ) {
109+ let disk = Disk :: try_from ( & path) . map_err ( ConfigurationError :: Parsing ) ?;
110+ disks. push ( disk) ;
111+ }
112+ }
113+
114+ Ok ( disks)
115+ }
116+
117+ fn parse_templates_in_directory (
118+ & self ,
119+ path : & PathBuf ,
120+ ) -> Result < Option < Template > , ConfigurationError > {
121+ if path. exists ( ) {
122+ let template = Template :: try_from ( path) . map_err ( ConfigurationError :: Parsing ) ?;
123+ return Ok ( Some ( template) ) ;
124+ }
125+
126+ Ok ( None )
127+ }
128+
129+ fn parse_disks ( & self ) -> Result < Vec < Disk > , ConfigurationError > {
130+ let disks_path = self . get_disks_path ( ) ;
131+
132+ self . parse_disks_in_directory ( & disks_path)
133+ }
134+
135+ fn parse_snapshots ( & self ) -> Result < Vec < Disk > , ConfigurationError > {
136+ let snapshots_path = self . get_snapshots_path ( ) ;
137+
138+ self . parse_disks_in_directory ( & snapshots_path)
139+ }
140+
141+ fn parse_templates ( & self ) -> Result < Option < Template > , ConfigurationError > {
142+ let templates_path = self . get_templates_path ( ) ;
143+
144+ self . parse_templates_in_directory ( & templates_path)
145+ }
146+
64147 /// Get the domain name
65148 /// This is the name of the domain that is used to create
66149 /// the domain in libvirt.
@@ -98,6 +181,10 @@ impl Domain {
98181 self . configuration_file . as_ref ( )
99182 }
100183
184+ pub fn get_configuration_file_path ( & self ) -> PathBuf {
185+ self . path . join ( "config.xml" )
186+ }
187+
101188 /// Get the disks for the domain
102189 /// This is the list of disks that are used by the domain.
103190 /// It is used to create the domain in libvirt.
@@ -109,6 +196,24 @@ impl Domain {
109196 & self . disks
110197 }
111198
199+ pub fn get_disks_path ( & self ) -> PathBuf {
200+ self . path . join ( "disks" )
201+ }
202+
203+ /// Get the disk snapshots for the domain
204+ /// This is the list of disk snapshots that are used by the domain.
205+ ///
206+ /// # Returns
207+ ///
208+ /// * `&Vec<Disk>` - The list of disk snapshots for the domain.
209+ pub fn get_snapshots ( & self ) -> & Vec < Disk > {
210+ & self . snapshots
211+ }
212+
213+ pub fn get_snapshots_path ( & self ) -> PathBuf {
214+ self . get_disks_path ( ) . join ( "snapshots" )
215+ }
216+
112217 /// Get the Packer templates for the domain
113218 /// It is used to create the domain disk image.
114219 ///
@@ -120,20 +225,85 @@ impl Domain {
120225 pub fn get_templates ( & self ) -> Option < & Template > {
121226 self . templates . as_ref ( )
122227 }
228+
229+ pub fn get_templates_path ( & self ) -> PathBuf {
230+ self . path . join ( "templates" )
231+ }
123232}
124233
125234impl Display for Domain {
126235 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
127236 write ! (
128237 f,
129- "Domain({{ name: {}, path: {}, disks: {:?} }})" ,
238+ "Domain({{ name: {}, path: {}, disks: {:?}, snapshots: {:?} }})" ,
130239 self . name,
131240 self . path. display( ) ,
132- self . disks
241+ self . disks,
242+ self . snapshots,
133243 )
134244 }
135245}
136246
247+ impl TryFrom < & PathBuf > for Domain {
248+ type Error = ConfigurationError ;
249+
250+ /// Try to create a Domain from a path.
251+ ///
252+ /// The given path should be the path to the domain directory configuration.
253+ fn try_from ( path : & PathBuf ) -> Result < Self , Self :: Error > {
254+ // Check if the file exists
255+ path. try_exists ( ) . map_err ( ConfigurationError :: Parsing ) ?;
256+
257+ // Check if the file is a regular file
258+ if path. is_file ( ) {
259+ return Err ( ConfigurationError :: Parsing ( std:: io:: Error :: new (
260+ std:: io:: ErrorKind :: InvalidInput ,
261+ format ! ( "Not a directory: {}" , path. display( ) ) ,
262+ ) ) ) ;
263+ }
264+
265+ // Check if the file is not empty
266+ let metadata = path. metadata ( ) . map_err ( ConfigurationError :: Parsing ) ?;
267+ metadata. len ( ) . eq ( & 0 ) . then ( || {
268+ ConfigurationError :: Parsing ( std:: io:: Error :: new (
269+ std:: io:: ErrorKind :: InvalidInput ,
270+ format ! ( "File is empty: {}" , path. display( ) ) ,
271+ ) )
272+ } ) ;
273+
274+ let name = path
275+ . file_name ( )
276+ . ok_or_else ( || {
277+ std:: io:: Error :: new (
278+ std:: io:: ErrorKind :: InvalidData ,
279+ format ! ( "No file name for path: {}" , path. display( ) ) ,
280+ )
281+ } )
282+ . map_err ( ConfigurationError :: Parsing ) ?
283+ . to_string_lossy ( )
284+ . to_string ( ) ;
285+
286+ let domain = Domain :: new_empty ( name. clone ( ) , path. clone ( ) ) ;
287+
288+ let config_file = domain. parse_configuration_file ( ) ?;
289+
290+ let disks = domain. parse_disks ( ) ?;
291+
292+ let snapshots = domain. parse_snapshots ( ) ?;
293+
294+ let templates = domain. parse_templates ( ) ?;
295+
296+ Ok ( Domain {
297+ name,
298+ path : path. clone ( ) ,
299+ configuration_file : config_file,
300+ disks,
301+ snapshots,
302+ templates,
303+ } )
304+ }
305+ }
306+
137307#[ cfg( test) ]
138308mod tests {
139309
@@ -153,6 +323,7 @@ mod tests {
153323 PathBuf :: from ( "/xenith/domains/test_domain" ) ,
154324 Some ( PathBuf :: from ( "/xenith/domains/test_domain/config.xml" ) ) ,
155325 vec ! [ disk. clone( ) ] ,
326+ vec ! [ ] ,
156327 None ,
157328 ) ;
158329
@@ -166,6 +337,7 @@ mod tests {
166337 Some ( & PathBuf :: from( "/xenith/domains/test_domain/config.xml" ) )
167338 ) ;
168339 assert_eq ! ( domain. get_disks( ) , & vec![ disk] ) ;
340+ assert_eq ! ( domain. get_snapshots( ) , & vec![ ] ) ;
169341 assert ! ( domain. get_templates( ) . is_none( ) ) ;
170342 }
171343}
0 commit comments