]>
git.ipfire.org Git - nitsi.git/blob - test.py
6 import xml
.etree
.ElementTree
as ET
15 def __init__(self
, log_level
):
16 self
.log_level
= log_level
18 def debug(self
, string
):
19 if self
.log_level
>= 4:
20 print("DEBUG: {}".format(string
))
22 def error(self
, string
):
23 print("ERROR: {}".format(string
))
26 def __init__(self
, uri
):
29 self
.connection
= None
31 def get_domain_from_name(self
, name
):
32 dom
= self
.con
.lookupByName(name
)
40 if self
.connection
== None:
42 self
.connection
= libvirt
.open(self
.uri
)
43 except BaseException
as error
:
44 self
.log
.error("Could not connect to: {}".format(self
.uri
))
46 self
.log
.debug("Connected to: {}".format(self
.uri
))
47 return self
.connection
49 return self
.connection
53 def __init__(self
, vm_xml_file
, snapshot_xml_file
, image
, root_uid
, username
, password
):
55 self
.con
= libvirt_con("qemu:///system")
57 with
open(vm_xml_file
) as fobj
:
58 self
.vm_xml
= fobj
.read()
59 except FileNotFoundError
as error
:
60 self
.log
.error("No such file: {}".format(vm_xml_file
))
63 with
open(snapshot_xml_file
) as fobj
:
64 self
.snapshot_xml
= fobj
.read()
65 except FileNotFoundError
as error
:
66 self
.log
.error("No such file: {}".format(snapshot_xml_file
))
70 if not os
.path
.isfile(self
.image
):
71 self
.log
.error("No such file: {}".format(self
.image
))
73 self
.root_uid
= root_uid
74 self
.disk
= disk(image
)
76 self
.username
= username
77 self
.password
= password
80 self
.dom
= self
.con
.con
.defineXML(self
.vm_xml
)
82 self
.log
.error("Could not define VM")
86 if self
.dom
.create() < 0:
87 self
.log
.error("Could not start VM")
92 if self
.dom
.shutdown() < 0:
93 self
.log
.error("Could not shutdown VM")
96 self
.log
.error("Domain is not running")
101 def create_snapshot(self
):
103 self
.snapshot
= self
.dom
.snapshotCreateXML(self
.snapshot_xml
)
105 if not self
.snapshot
:
106 self
.log
.error("Could not create snapshot")
109 def revert_snapshot(self
):
110 self
.dom
.revertToSnapshot(self
.snapshot
)
111 self
.snapshot
.delete()
113 def is_running(self
):
115 state
, reason
= self
.dom
.state()
117 if state
== libvirt
.VIR_DOMAIN_RUNNING
:
122 def get_serial_device(self
):
124 if not self
.is_running():
127 xml_root
= ET
.fromstring(self
.dom
.XMLDesc(0))
129 elem
= xml_root
.find("./devices/serial/source")
130 return elem
.get("path")
132 def check_is_booted_up(self
):
133 serial_con
= serial_connection(self
.get_serial_device())
135 serial_con
.write("\n")
136 # This will block till the domain is booted up
143 self
.serial_con
= serial_connection(self
.get_serial_device(), username
=self
.username
)
144 self
.serial_con
.login(self
.password
)
145 except BaseException
as e
:
146 self
.log
.error("Could not connect to the domain via serial console")
149 return self
.serial_con
.command(cmd
)
151 def copy_in(self
, fr
, to
):
153 self
.disk
.mount(self
.root_uid
, "/")
154 self
.disk
.copy_in(fr
, to
)
155 except BaseException
as e
:
158 self
.disk
.umount("/")
163 # A class which define and undefine a virtual network based on an xml file
165 def __init__(self
, network_xml_file
):
167 self
.con
= libvirt_con("qemu:///system")
169 with
open(network_xml_file
) as fobj
:
170 self
.network_xml
= fobj
.read()
171 except FileNotFoundError
as error
:
172 self
.log
.error("No such file: {}".format(vm_xml_file
))
175 self
.network
= self
.con
.con
.networkDefineXML(self
.network_xml
)
178 self
.log
.error("Failed to define virtual network")
181 self
.network
.create()
184 self
.network
.destroy()
188 class RecipeExeption(Exception):
193 # Should read the test, check if the syntax are valid
194 # and return tuples with the ( host, command ) structure
196 def __init__(self
, path
, circle
=[]):
198 self
.recipe_file
= path
199 self
.path
= os
.path
.dirname(self
.recipe_file
)
200 self
.log
.debug("Path of recipe is: {}".format(self
.recipe_file
))
202 self
._machines
= None
204 self
.in_recursion
= True
206 self
.in_recursion
= False
209 self
.log
.debug(circle
)
210 self
.log
.debug(self
.circle
)
212 if not os
.path
.isfile(self
.recipe_file
):
213 self
.log
.error("No such file: {}".format(self
.recipe_file
))
216 with
open(self
.recipe_file
) as fobj
:
217 self
.raw_recipe
= fobj
.readlines()
218 except FileNotFoundError
as error
:
219 self
.log
.error("No such file: {}".format(vm_xml_file
))
230 if not self
._machines
:
232 for line
in self
._recipe
:
233 if line
[0] != "all" and line
[0] not in self
._machines
:
234 self
._machines
.append(line
[0])
236 return self
._machines
241 for line
in self
.raw_recipe
:
242 raw_line
= line
.split(":")
243 if len(raw_line
) < 2:
244 self
.log
.error("Error parsing the recipe in line {}".format(i
))
246 cmd
= raw_line
[1].strip()
247 raw_line
= raw_line
[0].strip().split(" ")
248 if len(raw_line
) == 0:
249 self
.log
.error("Failed to parse the recipe in line {}".format(i
))
252 if raw_line
[0].strip() == "":
253 self
.log
.error("Failed to parse the recipe in line {}".format(i
))
256 machine
= raw_line
[0].strip()
258 if len(raw_line
) == 2:
259 extra
= raw_line
[1].strip()
263 # We could get a machine here or a include statement
264 if machine
== "include":
266 path
= os
.path
.normpath(self
.path
+ "/" + path
)
267 path
= path
+ "/recipe"
268 if path
in self
.circle
:
269 self
.log
.error("Detect import loop!")
271 self
.circle
.append(path
)
272 recipe_to_include
= recipe(path
, circle
=self
.circle
)
274 if machine
== "include":
275 self
._recipe
.extend(recipe_to_include
.recipe
)
277 # Support also something like 'alice,bob: echo'
278 machines
= machine
.split(",")
279 for machine
in machines
:
280 self
._recipe
.append((machine
.strip(), extra
.strip(), cmd
.strip()))
283 if not self
.in_recursion
:
285 for line
in self
._recipe
:
287 tmp_recipe
.append(line
)
289 for machine
in self
.machines
:
290 tmp_recipe
.append((machine
.strip(), line
[1], line
[2]))
292 self
._recipe
= tmp_recipe
297 def __init__(self
, path
):
300 self
.path
= os
.path
.abspath(path
)
301 except BaseException
as e
:
302 self
.log
.error("Could not get absolute path")
304 self
.log
.debug(self
.path
)
306 self
.settings_file
= "{}/settings".format(self
.path
)
307 if not os
.path
.isfile(self
.settings_file
):
308 self
.log
.error("No such file: {}".format(self
.settings_file
))
310 self
.recipe_file
= "{}/recipe".format(self
.path
)
311 if not os
.path
.isfile(self
.recipe_file
):
312 self
.log
.error("No such file: {}".format(self
.recipe_file
))
314 def read_settings(self
):
315 self
.config
= configparser
.ConfigParser()
316 self
.config
.read(self
.settings_file
)
317 self
.name
= self
.config
["DEFAULT"]["Name"]
318 self
.description
= self
.config
["DEFAULT"]["Description"]
319 self
.copy_to
= self
.config
["DEFAULT"]["Copy_to"]
320 self
.copy_from
= self
.config
["DEFAULT"]["Copy_from"]
321 self
.copy_from
= self
.copy_from
.split(",")
324 for file in self
.copy_from
:
326 file = os
.path
.normpath(self
.path
+ "/" + file)
331 self
.virtual_environ_name
= self
.config
["VIRTUAL_ENVIRONMENT"]["Name"]
332 self
.virtual_environ_path
= self
.config
["VIRTUAL_ENVIRONMENT"]["Path"]
333 self
.virtual_environ_path
= os
.path
.normpath(self
.path
+ "/" + self
.virtual_environ_path
)
335 def virtual_environ_setup(self
):
336 self
.virtual_environ
= virtual_environ(self
.virtual_environ_path
)
338 self
.virtual_networks
= self
.virtual_environ
.get_networks()
340 self
.virtual_machines
= self
.virtual_environ
.get_machines()
342 def virtual_environ_start(self
):
343 for name
in self
.virtual_environ
.network_names
:
344 self
.virtual_networks
[name
].define()
345 self
.virtual_networks
[name
].start()
347 for name
in self
.virtual_environ
.machine_names
:
348 self
.virtual_machines
[name
].define()
349 self
.virtual_machines
[name
].create_snapshot()
350 self
.virtual_machines
[name
].copy_in(self
.copy_from
, self
.copy_to
)
351 self
.virtual_machines
[name
].start()
353 self
.log
.debug("Try to login on all machines")
354 for name
in self
.virtual_environ
.machine_names
:
355 self
.virtual_machines
[name
].login()
357 def load_recipe(self
):
359 self
.recipe
= recipe(self
.recipe_file
)
360 except BaseException
:
361 self
.log
.error("Failed to load recipe")
364 def run_recipe(self
):
365 for line
in self
.recipe
.recipe
:
366 return_value
= self
.virtual_machines
[line
[0]].cmd(line
[2])
367 self
.log
.debug("Return value is: {}".format(return_value
))
368 if return_value
!= "0" and line
[1] == "":
369 self
.log
.error("Failed to execute command '{}' on {}, return code: {}".format(line
[2],line
[0], return_value
))
371 elif return_value
== "0" and line
[1] == "!":
372 self
.log
.error("Succeded to execute command '{}' on {}, return code: {}".format(line
[2],line
[0],return_value
))
375 self
.log
.debug("Command '{}' on {} returned with: {}".format(line
[2],line
[0],return_value
))
377 def virtual_environ_stop(self
):
378 for name
in self
.virtual_environ
.machine_names
:
379 self
.virtual_machines
[name
].shutdown()
380 self
.virtual_machines
[name
].revert_snapshot()
381 self
.virtual_machines
[name
].undefine()
383 for name
in self
.virtual_environ
.network_names
:
384 self
.virtual_networks
[name
].undefine()
387 # Should return all vms and networks in a list
388 # and should provide the path to the necessary xml files
389 class virtual_environ():
390 def __init__(self
, path
):
393 self
.path
= os
.path
.abspath(path
)
394 except BaseException
as e
:
395 self
.log
.error("Could not get absolute path")
397 self
.log
.debug(self
.path
)
399 self
.settings_file
= "{}/settings".format(self
.path
)
400 if not os
.path
.isfile(self
.settings_file
):
401 self
.log
.error("No such file: {}".format(self
.settings_file
))
403 self
.log
.debug(self
.settings_file
)
404 self
.config
= configparser
.ConfigParser()
405 self
.config
.read(self
.settings_file
)
406 self
.name
= self
.config
["DEFAULT"]["name"]
407 self
.machines_string
= self
.config
["DEFAULT"]["machines"]
408 self
.networks_string
= self
.config
["DEFAULT"]["networks"]
411 for machine
in self
.machines_string
.split(","):
412 self
.machines
.append(machine
.strip())
415 for network
in self
.networks_string
.split(","):
416 self
.networks
.append(network
.strip())
418 self
.log
.debug(self
.machines
)
419 self
.log
.debug(self
.networks
)
421 def get_networks(self
):
423 for _network
in self
.networks
:
424 self
.log
.debug(_network
)
425 networks
.setdefault(_network
, network(os
.path
.normpath(self
.path
+ "/" + self
.config
[_network
]["xml_file"])))
428 def get_machines(self
):
430 for _machine
in self
.machines
:
431 self
.log
.debug(_machine
)
432 machines
.setdefault(_machine
, machine(
433 os
.path
.normpath(self
.path
+ "/" + self
.config
[_machine
]["xml_file"]),
434 os
.path
.normpath(self
.path
+ "/" + self
.config
[_machine
]["snapshot_xml_file"]),
435 self
.config
[_machine
]["image"],
436 self
.config
[_machine
]["root_uid"],
437 self
.config
[_machine
]["username"],
438 self
.config
[_machine
]["password"]))
443 def machine_names(self
):
447 def network_names(self
):
451 if __name__
== "__main__":
454 parser
= argparse
.ArgumentParser()
456 parser
.add_argument("-d", "--directory", dest
="dir")
458 args
= parser
.parse_args()
460 currenttest
= test(args
.dir)
461 currenttest
.read_settings()
462 currenttest
.virtual_environ_setup()
463 currenttest
.load_recipe()
465 currenttest
.virtual_environ_start()
466 currenttest
.run_recipe()
467 except BaseException
as e
:
470 currenttest
.virtual_environ_stop()