]>
git.ipfire.org Git - nitsi.git/blob - test.py
11 import xml
.etree
.ElementTree
as ET
18 def __init__(self
, log_level
):
19 self
.log_level
= log_level
21 def debug(self
, string
):
22 if self
.log_level
>= 4:
23 print("DEBUG: {}".format(string
))
25 def error(self
, string
):
26 print("ERROR: {}".format(string
))
29 def __init__(self
, uri
):
32 self
.connection
= None
34 def get_domain_from_name(self
, name
):
35 dom
= self
.con
.lookupByName(name
)
43 if self
.connection
== None:
45 self
.connection
= libvirt
.open(self
.uri
)
46 except BaseException
as error
:
47 self
.log
.error("Could not connect to: {}".format(self
.uri
))
49 self
.log
.debug("Connected to: {}".format(self
.uri
))
50 return self
.connection
52 return self
.connection
56 def __init__(self
, vm_xml_file
, snapshot_xml_file
, image
, root_uid
, username
, password
):
58 self
.con
= libvirt_con("qemu:///system")
60 with
open(vm_xml_file
) as fobj
:
61 self
.vm_xml
= fobj
.read()
62 except FileNotFoundError
as error
:
63 self
.log
.error("No such file: {}".format(vm_xml_file
))
66 with
open(snapshot_xml_file
) as fobj
:
67 self
.snapshot_xml
= fobj
.read()
68 except FileNotFoundError
as error
:
69 self
.log
.error("No such file: {}".format(snapshot_xml_file
))
73 if not os
.path
.isfile(self
.image
):
74 self
.log
.error("No such file: {}".format(self
.image
))
76 self
.root_uid
= root_uid
78 self
.username
= username
79 self
.password
= password
82 self
.dom
= self
.con
.con
.defineXML(self
.vm_xml
)
84 self
.log
.error("Could not define VM")
88 if self
.dom
.create() < 0:
89 self
.log
.error("Could not start VM")
94 if self
.dom
.shutdown() < 0:
95 self
.log
.error("Could not shutdown VM")
98 self
.log
.error("Domain is not running")
103 def create_snapshot(self
):
105 self
.snapshot
= self
.dom
.snapshotCreateXML(self
.snapshot_xml
)
107 if not self
.snapshot
:
108 self
.log
.error("Could not create snapshot")
111 def revert_snapshot(self
):
112 self
.dom
.revertToSnapshot(self
.snapshot
)
113 self
.snapshot
.delete()
115 def is_running(self
):
117 state
, reason
= self
.dom
.state()
119 if state
== libvirt
.VIR_DOMAIN_RUNNING
:
124 def get_serial_device(self
):
126 if not self
.is_running():
129 xml_root
= ET
.fromstring(self
.dom
.XMLDesc(0))
131 elem
= xml_root
.find("./devices/serial/source")
132 return elem
.get("path")
134 def check_is_booted_up(self
):
135 serial_con
= connection(self
.get_serial_device())
137 serial_con
.write("\n")
138 # This will block till the domain is booted up
145 self
.serial_con
= connection(self
.get_serial_device(), username
=self
.username
)
146 self
.serial_con
.login(self
.password
)
147 except BaseException
as e
:
148 self
.log
.error("Could not connect to the domain via serial console")
151 return self
.serial_con
.command(cmd
)
155 def __init__(self
, device
, username
=None):
157 self
.back_at_prompt_pattern
= None
158 self
.username
= username
160 self
.con
= serial
.Serial(device
)
162 def read(self
, size
=1):
163 if len(self
.buffer) >= size
:
164 # throw away first size bytes in buffer
165 data
= self
.buffer[:size
]
166 # Set the buffer to the non used bytes
167 self
.buffer = self
.buffer[size
:]
171 # Set the size to the value we have to read now
172 size
= size
- len(self
.buffer)
173 # Set the buffer empty
175 return data
+ self
.con
.read(size
)
177 def peek(self
, size
=1):
178 if len(self
.buffer) <= size
:
179 self
.buffer += self
.con
.read(size
=size
- len(self
.buffer))
181 return self
.buffer[:size
]
184 self
.log
.debug(self
.buffer)
185 self
.buffer = self
.buffer + self
.con
.read(self
.con
.in_waiting
)
186 if b
"\n" in self
.buffer:
187 size
= self
.buffer.index(b
"\n") + 1
188 self
.log
.debug("We have a whole line in the buffer")
189 self
.log
.debug(self
.buffer)
190 self
.log
.debug("We split at {}".format(size
))
191 data
= self
.buffer[:size
]
192 self
.buffer = self
.buffer[size
:]
194 self
.log
.debug(self
.buffer)
199 return data
+ self
.con
.readline()
201 def back_at_prompt(self
):
206 # We need to use self.in_waiting because with self.con.in_waiting we get
207 # not the complete string
208 size
= len(self
.buffer) + self
.in_waiting
209 data
= self
.peek(size
)
212 if self
.back_at_prompt_pattern
== None:
213 #self.back_at_prompt_pattern = r"^\[{}@.+\]#".format(self.username)
214 self
.back_at_prompt_pattern
= re
.compile(r
"^\[{}@.+\]#".format(self
.username
), re
.MULTILINE
)
216 if self
.back_at_prompt_pattern
.search(data
.decode()):
221 def log_console_line(self
, line
):
222 self
.log
.debug("Get in function log_console_line()")
223 sys
.stdout
.write(line
)
226 def in_waiting(self
):
227 in_waiting_before
= 0
230 while in_waiting_before
!= self
.con
.in_waiting
:
231 in_waiting_before
= self
.con
.in_waiting
234 return self
.con
.in_waiting
236 def line_in_buffer(self
):
237 if b
"\n" in self
.buffer:
242 def readline2(self
, pattern
=None):
246 pattern
= re
.compile(pattern
)
249 char
= self
.con
.read(1)
250 string
= string
+ char
.decode("utf-8")
251 string2
= string2
+ char
253 print(char
.decode("utf-8"), end
="")
256 if pattern
and pattern
.match(string
):
259 return {"string" : string
, "return-code" : 1}
265 return {"return-code" : 0}
267 def check_logged_in(self
, username
):
268 pattern
= "^\[" + username
+ "@.+\]#"
269 data
= self
.readline(pattern
=pattern
)
270 if data
["return-code"] == 1:
271 print("We are logged in")
274 print("We are not logged in")
277 def print_lines_in_buffer(self
):
279 self
.log
.debug("Fill buffer ...")
280 self
.peek(len(self
.buffer) + self
.in_waiting
)
281 self
.log
.debug("Current buffer length: {}".format(len(self
.buffer)))
282 if self
.line_in_buffer() == True:
283 while self
.line_in_buffer() == True:
284 data
= self
.readline()
285 self
.log_console_line(data
.decode())
287 self
.log
.debug("We have printed all lines in the buffer")
290 def login(self
, password
):
291 if self
.username
== None:
292 self
.log
.error("Username cannot be blank")
295 self
.print_lines_in_buffer()
297 # Hit enter to see what we get
298 self
.con
.write(b
'\n')
299 # We get two new lines \r\n ?
300 data
= self
.readline()
301 self
.log_console_line(data
.decode())
303 self
.print_lines_in_buffer()
305 if self
.back_at_prompt():
306 self
.log
.debug("We are already logged in.")
309 # Read all line till we get login:
312 if not data
.decode() == "l":
313 self
.log
.debug("We get no l at the start")
314 self
.log_console_line(self
.readline().decode())
316 # We need to use self.in_waiting because with self.con.in_waiting we get
317 # not the complete string
318 size
= len(self
.buffer) + self
.in_waiting
319 data
= self
.peek(size
)
321 pattern
= r
"^.*login: "
322 pattern
= re
.compile(pattern
)
324 if pattern
.search(data
.decode()):
327 self
.log
.debug("The pattern does not match")
328 self
.log_console_line(self
.readline().decode())
331 string
= "{}\n".format(self
.username
)
332 self
.con
.write(string
.encode())
334 # read the login out of the buffer
335 data
= self
.readline()
336 self
.log
.debug("This is the login:{}".format(data
))
337 self
.log_console_line(data
.decode())
339 # We need to wait her till we get the full string "Password:"
340 #This is useless but self.in_waiting will wait the correct amount of time
341 size
= self
.in_waiting
343 string
= "{}\n".format(password
)
344 self
.con
.write(string
.encode())
346 # Print the 'Password:' line
347 data
= self
.readline()
348 self
.log_console_line(data
.decode())
350 while not self
.back_at_prompt():
351 # This will fail if the login failed so we need to look for the failed keyword
352 data
= self
.readline()
353 self
.log_console_line(data
.decode())
357 def write(self
, string
):
358 self
.log
.debug(string
)
359 self
.con
.write(string
.encode())
362 def command(self
, command
):
363 self
.write("{}\n".format(command
))
365 # We need to read out the prompt for this command first
366 # If we do not do this we will break the loop immediately
367 # because the prompt for this command is still in the buffer
368 data
= self
.readline()
369 self
.log_console_line(data
.decode())
371 while not self
.back_at_prompt():
372 data
= self
.readline()
373 self
.log_console_line(data
.decode())
376 # A class which define and undefine a virtual network based on an xml file
378 def __init__(self
, network_xml_file
):
380 self
.con
= libvirt_con("qemu:///system")
382 with
open(network_xml_file
) as fobj
:
383 self
.network_xml
= fobj
.read()
384 except FileNotFoundError
as error
:
385 self
.log
.error("No such file: {}".format(vm_xml_file
))
388 self
.network
= self
.con
.con
.networkDefineXML(self
.network_xml
)
391 self
.log
.error("Failed to define virtual network")
394 self
.network
.create()
397 self
.network
.destroy()
401 class RecipeExeption(Exception):
406 # Should read the test, check if the syntax are valid
407 # and return tuples with the ( host, command ) structure
409 def __init__(self
, path
):
411 self
.recipe_file
= path
414 if not os
.path
.isfile(self
.recipe_file
):
415 self
.log
.error("No such file: {}".format(self
.recipe_file
))
418 with
open(self
.recipe_file
) as fobj
:
419 self
.raw_recipe
= fobj
.readlines()
420 except FileNotFoundError
as error
:
421 self
.log
.error("No such file: {}".format(vm_xml_file
))
433 for line
in self
.raw_recipe
:
434 raw_line
= line
.split(":")
435 if len(raw_line
) < 2:
436 self
.log
.error("Error parsing the recipe in line {}".format(i
))
439 raw_line
= raw_line
[0].strip().split(" ")
440 if len(raw_line
) == 0:
441 self
.log
.error("Failed to parse the recipe in line {}".format(i
))
443 elif len(raw_line
) == 1:
444 if raw_line
[0] == "":
445 self
.log
.error("Failed to parse the recipe in line {}".format(i
))
447 machine
= raw_line
[0]
449 elif len(raw_line
) == 2:
450 machine
= raw_line
[0]
453 self
._recipe
.append((machine
.strip(), extra
.strip(), cmd
.strip()))
458 def __init__(self
, path
):
461 self
.path
= os
.path
.abspath(path
)
462 except BaseException
as e
:
463 self
.log
.error("Could not get absolute path")
465 self
.log
.debug(self
.path
)
467 self
.settings_file
= "{}/settings".format(self
.path
)
468 if not os
.path
.isfile(self
.settings_file
):
469 self
.log
.error("No such file: {}".format(self
.settings_file
))
471 self
.recipe_file
= "{}/recipe".format(self
.path
)
472 if not os
.path
.isfile(self
.recipe_file
):
473 self
.log
.error("No such file: {}".format(self
.recipe_file
))
475 def read_settings(self
):
476 self
.config
= configparser
.ConfigParser()
477 self
.config
.read(self
.settings_file
)
478 self
.name
= self
.config
["DEFAULT"]["Name"]
479 self
.description
= self
.config
["DEFAULT"]["Description"]
481 self
.virtual_environ_name
= self
.config
["VIRTUAL_ENVIRONMENT"]["Name"]
482 self
.virtual_environ_path
= self
.config
["VIRTUAL_ENVIRONMENT"]["Path"]
483 self
.virtual_environ_path
= os
.path
.normpath(self
.path
+ "/" + self
.virtual_environ_path
)
485 def virtual_environ_setup(self
):
486 self
.virtual_environ
= virtual_environ(self
.virtual_environ_path
)
488 self
.virtual_networks
= self
.virtual_environ
.get_networks()
490 self
.virtual_machines
= self
.virtual_environ
.get_machines()
492 def virtual_environ_start(self
):
493 for name
in self
.virtual_environ
.network_names
:
494 self
.virtual_networks
[name
].define()
495 self
.virtual_networks
[name
].start()
497 for name
in self
.virtual_environ
.machine_names
:
498 self
.virtual_machines
[name
].define()
499 self
.virtual_machines
[name
].create_snapshot()
500 self
.virtual_machines
[name
].start()
502 self
.log
.debug("Try to login on all machines")
503 for name
in self
.virtual_environ
.machine_names
:
504 self
.virtual_machines
[name
].login()
506 def load_recipe(self
):
508 self
.recipe
= recipe(self
.recipe_file
)
509 except BaseException
:
510 self
.log
.error("Failed to load recipe")
513 def run_recipe(self
):
514 for line
in self
.recipe
.recipe
:
515 return_value
= self
.virtual_machines
[line
[0]].cmd(line
[2])
516 if not return_value
and line
[1] == "":
517 self
.log
.error("Failed to execute command '{}' on {}".format(line
[2],line
[0]))
519 elif return_value
== True and line
[1] == "!":
520 self
.log
.error("Succeded to execute command '{}' on {}".format(line
[2],line
[0]))
523 def virtual_environ_stop(self
):
524 for name
in self
.virtual_environ
.machine_names
:
525 self
.virtual_machines
[name
].shutdown()
526 self
.virtual_machines
[name
].revert_snapshot()
527 self
.virtual_machines
[name
].undefine()
529 for name
in self
.virtual_environ
.network_names
:
530 self
.virtual_networks
[name
].undefine()
533 # Should return all vms and networks in a list
534 # and should provide the path to the necessary xml files
535 class virtual_environ():
536 def __init__(self
, path
):
539 self
.path
= os
.path
.abspath(path
)
540 except BaseException
as e
:
541 self
.log
.error("Could not get absolute path")
543 self
.log
.debug(self
.path
)
545 self
.settings_file
= "{}/settings".format(self
.path
)
546 if not os
.path
.isfile(self
.settings_file
):
547 self
.log
.error("No such file: {}".format(self
.settings_file
))
549 self
.log
.debug(self
.settings_file
)
550 self
.config
= configparser
.ConfigParser()
551 self
.config
.read(self
.settings_file
)
552 self
.name
= self
.config
["DEFAULT"]["name"]
553 self
.machines_string
= self
.config
["DEFAULT"]["machines"]
554 self
.networks_string
= self
.config
["DEFAULT"]["networks"]
557 for machine
in self
.machines_string
.split(","):
558 self
.machines
.append(machine
.strip())
561 for network
in self
.networks_string
.split(","):
562 self
.networks
.append(network
.strip())
564 self
.log
.debug(self
.machines
)
565 self
.log
.debug(self
.networks
)
567 def get_networks(self
):
569 for _network
in self
.networks
:
570 self
.log
.debug(_network
)
571 networks
.setdefault(_network
, network(os
.path
.normpath(self
.path
+ "/" + self
.config
[_network
]["xml_file"])))
574 def get_machines(self
):
576 for _machine
in self
.machines
:
577 self
.log
.debug(_machine
)
578 machines
.setdefault(_machine
, vm(
579 os
.path
.normpath(self
.path
+ "/" + self
.config
[_machine
]["xml_file"]),
580 os
.path
.normpath(self
.path
+ "/" + self
.config
[_machine
]["snapshot_xml_file"]),
581 self
.config
[_machine
]["image"],
582 self
.config
[_machine
]["root_uid"],
583 self
.config
[_machine
]["username"],
584 self
.config
[_machine
]["password"]))
589 def machine_names(self
):
593 def network_names(self
):
597 if __name__
== "__main__":
600 parser
= argparse
.ArgumentParser()
602 parser
.add_argument("-d", "--directory", dest
="dir")
604 args
= parser
.parse_args()
606 currenttest
= test(args
.dir)
607 currenttest
.read_settings()
608 currenttest
.virtual_environ_setup()
609 currenttest
.load_recipe()
610 currenttest
.virtual_environ_start()
611 currenttest
.run_recipe()
612 currenttest
.virtual_environ_stop()