]>
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:
311 # We need to use self.in_waiting because with self.con.in_waiting we get
312 # not the complete string
313 size
= len(self
.buffer) + self
.in_waiting
314 data
= self
.peek(size
)
316 pattern
= r
"^.*login: "
317 pattern
= re
.compile(pattern
)
319 if pattern
.search(data
.decode()):
322 self
.log
.debug("The pattern does not match")
323 self
.log_console_line(self
.readline().decode())
326 string
= "{}\n".format(self
.username
)
327 self
.con
.write(string
.encode())
329 # read the login out of the buffer
330 data
= self
.readline()
331 self
.log
.debug("This is the login:{}".format(data
))
332 self
.log_console_line(data
.decode())
334 # We need to wait her till we get the full string "Password:"
335 #This is useless but self.in_waiting will wait the correct amount of time
336 size
= self
.in_waiting
338 string
= "{}\n".format(password
)
339 self
.con
.write(string
.encode())
341 # Print the 'Password:' line
342 data
= self
.readline()
343 self
.log_console_line(data
.decode())
345 while not self
.back_at_prompt():
346 # This will fail if the login failed so we need to look for the failed keyword
347 data
= self
.readline()
348 self
.log_console_line(data
.decode())
352 def write(self
, string
):
353 self
.log
.debug(string
)
354 self
.con
.write(string
.encode())
357 def command(self
, command
):
358 self
.write("{}; echo \"END: $?\"\n".format(command
))
360 # We need to read out the prompt for this command first
361 # If we do not do this we will break the loop immediately
362 # because the prompt for this command is still in the buffer
363 data
= self
.readline()
364 self
.log_console_line(data
.decode())
366 while not self
.back_at_prompt():
367 data
= self
.readline()
368 self
.log_console_line(data
.decode())
370 # We saved our exit code in data (the last line)
371 self
.log
.debug(data
.decode())
372 data
= data
.decode().replace("END: ", "")
374 self
.log
.debug(data
.strip())
378 # A class which define and undefine a virtual network based on an xml file
380 def __init__(self
, network_xml_file
):
382 self
.con
= libvirt_con("qemu:///system")
384 with
open(network_xml_file
) as fobj
:
385 self
.network_xml
= fobj
.read()
386 except FileNotFoundError
as error
:
387 self
.log
.error("No such file: {}".format(vm_xml_file
))
390 self
.network
= self
.con
.con
.networkDefineXML(self
.network_xml
)
393 self
.log
.error("Failed to define virtual network")
396 self
.network
.create()
399 self
.network
.destroy()
403 class RecipeExeption(Exception):
408 # Should read the test, check if the syntax are valid
409 # and return tuples with the ( host, command ) structure
411 def __init__(self
, path
, circle
=[]):
413 self
.recipe_file
= path
414 self
.path
= os
.path
.dirname(self
.recipe_file
)
415 self
.log
.debug("Path of recipe is: {}".format(self
.recipe_file
))
417 self
._machines
= None
419 self
.in_recursion
= True
421 self
.in_recursion
= False
424 self
.log
.debug(circle
)
425 self
.log
.debug(self
.circle
)
427 if not os
.path
.isfile(self
.recipe_file
):
428 self
.log
.error("No such file: {}".format(self
.recipe_file
))
431 with
open(self
.recipe_file
) as fobj
:
432 self
.raw_recipe
= fobj
.readlines()
433 except FileNotFoundError
as error
:
434 self
.log
.error("No such file: {}".format(vm_xml_file
))
445 if not self
._machines
:
447 for line
in self
._recipe
:
448 if line
[0] != "all" and line
[0] not in self
._machines
:
449 self
._machines
.append(line
[0])
451 return self
._machines
456 for line
in self
.raw_recipe
:
457 raw_line
= line
.split(":")
458 if len(raw_line
) < 2:
459 self
.log
.error("Error parsing the recipe in line {}".format(i
))
461 cmd
= raw_line
[1].strip()
462 raw_line
= raw_line
[0].strip().split(" ")
463 if len(raw_line
) == 0:
464 self
.log
.error("Failed to parse the recipe in line {}".format(i
))
467 if raw_line
[0].strip() == "":
468 self
.log
.error("Failed to parse the recipe in line {}".format(i
))
471 machine
= raw_line
[0].strip()
473 if len(raw_line
) == 2:
474 extra
= raw_line
[1].strip()
478 # We could get a machine here or a include statement
479 if machine
== "include":
481 path
= os
.path
.normpath(self
.path
+ "/" + path
)
482 path
= path
+ "/recipe"
483 if path
in self
.circle
:
484 self
.log
.error("Detect import loop!")
486 self
.circle
.append(path
)
487 recipe_to_include
= recipe(path
, circle
=self
.circle
)
489 if machine
== "include":
490 self
._recipe
.extend(recipe_to_include
.recipe
)
492 # Support also something like 'alice,bob: echo'
493 machines
= machine
.split(",")
494 for machine
in machines
:
495 self
._recipe
.append((machine
.strip(), extra
.strip(), cmd
.strip()))
498 if not self
.in_recursion
:
500 for line
in self
._recipe
:
502 tmp_recipe
.append(line
)
504 for machine
in self
.machines
:
505 tmp_recipe
.append((machine
.strip(), line
[1], line
[2]))
507 self
._recipe
= tmp_recipe
512 def __init__(self
, path
):
515 self
.path
= os
.path
.abspath(path
)
516 except BaseException
as e
:
517 self
.log
.error("Could not get absolute path")
519 self
.log
.debug(self
.path
)
521 self
.settings_file
= "{}/settings".format(self
.path
)
522 if not os
.path
.isfile(self
.settings_file
):
523 self
.log
.error("No such file: {}".format(self
.settings_file
))
525 self
.recipe_file
= "{}/recipe".format(self
.path
)
526 if not os
.path
.isfile(self
.recipe_file
):
527 self
.log
.error("No such file: {}".format(self
.recipe_file
))
529 def read_settings(self
):
530 self
.config
= configparser
.ConfigParser()
531 self
.config
.read(self
.settings_file
)
532 self
.name
= self
.config
["DEFAULT"]["Name"]
533 self
.description
= self
.config
["DEFAULT"]["Description"]
535 self
.virtual_environ_name
= self
.config
["VIRTUAL_ENVIRONMENT"]["Name"]
536 self
.virtual_environ_path
= self
.config
["VIRTUAL_ENVIRONMENT"]["Path"]
537 self
.virtual_environ_path
= os
.path
.normpath(self
.path
+ "/" + self
.virtual_environ_path
)
539 def virtual_environ_setup(self
):
540 self
.virtual_environ
= virtual_environ(self
.virtual_environ_path
)
542 self
.virtual_networks
= self
.virtual_environ
.get_networks()
544 self
.virtual_machines
= self
.virtual_environ
.get_machines()
546 def virtual_environ_start(self
):
547 for name
in self
.virtual_environ
.network_names
:
548 self
.virtual_networks
[name
].define()
549 self
.virtual_networks
[name
].start()
551 for name
in self
.virtual_environ
.machine_names
:
552 self
.virtual_machines
[name
].define()
553 self
.virtual_machines
[name
].create_snapshot()
554 self
.virtual_machines
[name
].start()
556 self
.log
.debug("Try to login on all machines")
557 for name
in self
.virtual_environ
.machine_names
:
558 self
.virtual_machines
[name
].login()
560 def load_recipe(self
):
562 self
.recipe
= recipe(self
.recipe_file
)
563 except BaseException
:
564 self
.log
.error("Failed to load recipe")
567 def run_recipe(self
):
568 for line
in self
.recipe
.recipe
:
569 return_value
= self
.virtual_machines
[line
[0]].cmd(line
[2])
570 self
.log
.debug("Return value is: {}".format(return_value
))
571 if return_value
!= "0" and line
[1] == "":
572 self
.log
.error("Failed to execute command '{}' on {}, return code: {}".format(line
[2],line
[0], return_value
))
574 elif return_value
== "0" and line
[1] == "!":
575 self
.log
.error("Succeded to execute command '{}' on {}, return code: {}".format(line
[2],line
[0],return_value
))
578 self
.log
.debug("Command '{}' on {} returned with: {}".format(line
[2],line
[0],return_value
))
580 def virtual_environ_stop(self
):
581 for name
in self
.virtual_environ
.machine_names
:
582 self
.virtual_machines
[name
].shutdown()
583 self
.virtual_machines
[name
].revert_snapshot()
584 self
.virtual_machines
[name
].undefine()
586 for name
in self
.virtual_environ
.network_names
:
587 self
.virtual_networks
[name
].undefine()
590 # Should return all vms and networks in a list
591 # and should provide the path to the necessary xml files
592 class virtual_environ():
593 def __init__(self
, path
):
596 self
.path
= os
.path
.abspath(path
)
597 except BaseException
as e
:
598 self
.log
.error("Could not get absolute path")
600 self
.log
.debug(self
.path
)
602 self
.settings_file
= "{}/settings".format(self
.path
)
603 if not os
.path
.isfile(self
.settings_file
):
604 self
.log
.error("No such file: {}".format(self
.settings_file
))
606 self
.log
.debug(self
.settings_file
)
607 self
.config
= configparser
.ConfigParser()
608 self
.config
.read(self
.settings_file
)
609 self
.name
= self
.config
["DEFAULT"]["name"]
610 self
.machines_string
= self
.config
["DEFAULT"]["machines"]
611 self
.networks_string
= self
.config
["DEFAULT"]["networks"]
614 for machine
in self
.machines_string
.split(","):
615 self
.machines
.append(machine
.strip())
618 for network
in self
.networks_string
.split(","):
619 self
.networks
.append(network
.strip())
621 self
.log
.debug(self
.machines
)
622 self
.log
.debug(self
.networks
)
624 def get_networks(self
):
626 for _network
in self
.networks
:
627 self
.log
.debug(_network
)
628 networks
.setdefault(_network
, network(os
.path
.normpath(self
.path
+ "/" + self
.config
[_network
]["xml_file"])))
631 def get_machines(self
):
633 for _machine
in self
.machines
:
634 self
.log
.debug(_machine
)
635 machines
.setdefault(_machine
, vm(
636 os
.path
.normpath(self
.path
+ "/" + self
.config
[_machine
]["xml_file"]),
637 os
.path
.normpath(self
.path
+ "/" + self
.config
[_machine
]["snapshot_xml_file"]),
638 self
.config
[_machine
]["image"],
639 self
.config
[_machine
]["root_uid"],
640 self
.config
[_machine
]["username"],
641 self
.config
[_machine
]["password"]))
646 def machine_names(self
):
650 def network_names(self
):
654 if __name__
== "__main__":
657 parser
= argparse
.ArgumentParser()
659 parser
.add_argument("-d", "--directory", dest
="dir")
661 args
= parser
.parse_args()
663 currenttest
= test(args
.dir)
664 currenttest
.read_settings()
665 currenttest
.virtual_environ_setup()
666 currenttest
.load_recipe()
668 currenttest
.virtual_environ_start()
669 currenttest
.run_recipe()
670 except BaseException
as e
:
673 currenttest
.virtual_environ_stop()