]> git.ipfire.org Git - nitsi.git/blame - src/nitsi/test.py
Rework setting handling
[nitsi.git] / src / nitsi / test.py
CommitLineData
5e7f6db7
JS
1#!/usr/bin/python3
2
6632e137 3import configparser
5e7f6db7 4import libvirt
6632e137 5import logging
5e7f6db7 6import os
6c352a80
JS
7import time
8
b560f31a
JS
9from . import recipe
10from . import virtual_environ
1795f5e8 11from . import settings
4ac093dc 12from . import cmd
5e7f6db7 13
1ed8ca9f 14logger = logging.getLogger("nitsi.test")
5e7f6db7 15
61b44c10
JS
16
17class TestException(Exception):
18 def __init__(self, message):
19 self.message = message
20
ee227ea1 21class Test():
7aa9213e 22 def __init__(self, log_path, dir=None, recipe_file=None, settings_file=None, settings=None):
978854bf 23 # init settings var
7aa9213e 24 self.settings = settings
978854bf 25
ab5b8849 26 self.log_path = log_path
5e7f6db7 27
ab5b8849
JS
28 # Init all vars with None
29 self.settings_file = None
30 self.recipe_file = None
31 self.path = None
5e7f6db7 32
ab5b8849
JS
33 # We need at least a path to a recipe file or a dir to a test
34 if not dir and not recipe:
35 raise TestException("Did not get a path to a test or to a recipe file")
36
37 # We cannot decide which to use when we get both
38 if (dir and recipe_file) or (dir and settings_file):
39 raise TestException("Get dir and path to recipe or settings file")
40
41 if dir:
42 try:
43 if not os.path.isabs(dir):
44 self.path = os.path.abspath(dir)
45 except BaseException as e:
46 logger.error("Could not get absolute path")
47 raise e
48
49 logger.debug("Path of this test is: {}".format(self.path))
50
51 self.recipe_file = "{}/recipe".format(self.path)
52 self.settings_file = "{}/settings".format(self.path)
53
b4936764 54
aadbafc3
JS
55 # We can also go on without a settings file
56 if self.settings_file:
57 if not os.path.isfile(self.settings_file):
58 logger.error("No such file: {}".format(self.settings_file))
59 raise TestException("No settings file found")
60
e8da2bf3
JS
61 # os.path.isfile fails if self.recipe_file is None so we cannot use an and statement
62 if self.recipe_file:
63 if not os.path.isfile(self.recipe_file):
aadbafc3
JS
64 logger.error("No such file: {}".format(self.recipe_file))
65 raise TestException("No recipe file found")
e8da2bf3
JS
66 else:
67 logger.error("No such file: {}".format(self.recipe_file))
68 raise TestException("No recipe file found")
5e7f6db7 69
fea304c4
JS
70 if recipe_file:
71 if not os.path.isabs(recipe_file):
72 self.recipe_file = os.path.abspath(recipe_file)
73 else:
74 self.recipe_file = recipe_file
75
76 if settings_file:
77 if not os.path.isabs(settings_file):
78 self.settings_file = os.path.abspath(settings_file)
79 else:
80 self.settings_file = settings_file
81
5e7f6db7 82
ab5b8849
JS
83 # Init logging
84 if dir:
2468cf99
JS
85 self.log = logger.getChild(os.path.basename(self.path))
86 # We get a recipe when we get here
87 else:
ab5b8849 88 self.log = logger.getChild(os.path.basename(self.recipe_file))
978854bf 89
7aa9213e 90 # Parse config and settings:
ab5b8849 91 if self.settings_file:
7aa9213e 92 self.settings.set_config_values_from_file(self.settings_file, type="settings-file")#
129c0b0f 93
7aa9213e
JS
94 # Check settings
95 self.settings.check_config_values()
5e7f6db7 96
3dac9881 97 def virtual_environ_setup_stage_1(self):
7aa9213e 98 self.virtual_environ = virtual_environ.VirtualEnviron(self.settings.get_config_value("virtual_environ_path"))
5e7f6db7
JS
99
100 self.virtual_networks = self.virtual_environ.get_networks()
101
102 self.virtual_machines = self.virtual_environ.get_machines()
103
3dac9881 104 def virtual_environ_setup_stage_2(self):
fbaed8c3
JS
105 # built up which machines which are used in our recipe
106 used_machines = []
107
108 for line in self.recipe.recipe:
109 if not line[0] in used_machines:
110 used_machines.append(line[0])
111
112 self.log.debug("Machines used in this recipe {}".format(used_machines))
113
114 self.used_machine_names = used_machines
115
116 for machine in self.used_machine_names:
117 if not machine in self.virtual_environ.machine_names:
118 raise TestException("{} is listed as machine in the recipe, but the virtual environmet does not have such a machine".format(machine))
119
120
5e7f6db7 121 def virtual_environ_start(self):
3fa89b7c
JS
122 for name in self.virtual_environ.network_names:
123 self.virtual_networks[name].define()
124 self.virtual_networks[name].start()
5e7f6db7 125
fbaed8c3 126 for name in self.used_machine_names:
3fa89b7c
JS
127 self.virtual_machines[name].define()
128 self.virtual_machines[name].create_snapshot()
8428a452 129 # We can only copy files when we know which and to which dir
7aa9213e
JS
130 if self.settings.get_config_value("copy_from") and self.settings.get_config_value("copy_to"):
131 self.virtual_machines[name].copy_in(self.settings.get_config_value("copy_from"), self.settings.get_config_value("copy_to"))
3fa89b7c 132 self.virtual_machines[name].start()
5e7f6db7 133
6c352a80
JS
134 # Time to which all serial output log entries are relativ
135 log_start_time = time.time()
136
fc35cba1
JS
137 # Number of chars of the longest machine name
138 longest_machine_name = self.virtual_environ.longest_machine_name
139
25572214 140 self.log.info("Try to intialize the serial connection, connect and login on all machines")
fbaed8c3 141 for name in self.used_machine_names:
25572214
JS
142 self.log.info("Try to initialize the serial connection connect and login on {}".format(name))
143 self.virtual_machines[name].serial_init(log_file="{}/test.log".format(self.log_path),
fc35cba1
JS
144 log_start_time=log_start_time,
145 longest_machine_name=longest_machine_name)
25572214 146 self.virtual_machines[name].serial_connect()
5e7f6db7 147
3fa89b7c 148 def load_recipe(self):
2fa4467d 149 self.log.info("Going to load the recipe")
3fa89b7c 150 try:
5945ce2a
JS
151 self.recipe = recipe.Recipe(self.recipe_file,
152 fallback_machines=self.virtual_environ.machine_names)
153
4bc54b45
JS
154 for line in self.recipe.recipe:
155 self.log.debug(line)
2fa4467d
JS
156
157 self.log.debug("This was the recipe")
4bc54b45 158 except BaseException as e:
3fa89b7c 159 self.log.error("Failed to load recipe")
4bc54b45 160 raise e
3fa89b7c 161
35aab3f6 162 # This functions tries to handle an error of the test (eg. when 'echo "Hello World"' failed)
4ac093dc
JS
163 # in an interactive way
164 # returns False when the test should exit right now, and True when the test should go on
165 def interactive_error_handling(self):
7aa9213e 166 if not self.settings.get_config_value("interactive_error_handling"):
4ac093dc
JS
167 return False
168
169 _cmd = cmd.CMD(intro="You are droppped into an interative debugging shell because of the previous errors",
170 help={"exit": "Exit the test rigth now",
171 "continue": "Continues the test without any error handling, so do not expect that the test succeeds.",
172 "debug": "Disconnects from the serial console and prints the devices to manually connect to the virtual machines." \
173 "This is useful when you can fix th error with some manual commands. Please disconnect from the serial consoles and " \
174 "choose 'exit or 'continue' when you are done"})
175
176 command = _cmd.get_input(valid_commands=["continue", "exit", "debug"])
177
178 if command == "continue":
179 # The test should go on but we do not any debugging, so we return True
180 return True
181 elif command == "exit":
182 # The test should exit right now (normal behaviour)
183 return False
184
185 # If we get here we are in debugging mode
186 # Disconnect from the serial console:
187
188 for name in self.used_machine_names:
189 _cmd.print_to_cmd("Disconnect from the serial console of {}".format(name))
190 self.virtual_machines[name].serial_disconnect()
191
192 # Print the serial device for each machine
193 for name in self.used_machine_names:
194 device = self.virtual_machines[name].get_serial_device()
195 _cmd.print_to_cmd("Serial device of {} is {}".format(name, device))
196
197 _cmd.print_to_cmd("You can now connect to all serial devices, and send custom commands to the virtual machines." \
198 "Please type 'continue' or 'exit' when you disconnected from als serial devices and want to go on.")
199
200 command = _cmd.get_input(valid_commands=["continue", "exit"])
201
202 if command == "exit":
203 return False
204
205 # We should continue whit the test
206 # Reconnect to the serial devices
207
208 for name in self.used_machine_names:
209 self.log.info("Try to reconnect to {}".format(name))
210 self.virtual_machines[name].serial_connect()
211
212 return True
213
3fa89b7c
JS
214 def run_recipe(self):
215 for line in self.recipe.recipe:
216 return_value = self.virtual_machines[line[0]].cmd(line[2])
bce7d520
JS
217 self.log.debug("Return value is: {}".format(return_value))
218 if return_value != "0" and line[1] == "":
4ac093dc
JS
219 err_msg = "Failed to execute command '{}' on {}, return code: {}".format(line[2],line[0], return_value)
220 # Try to handle this error in an interactive way, if we cannot go on
221 # raise an exception and exit
222 if not self.interactive_error_handling():
223 raise TestException(err_msg)
224
bce7d520 225 elif return_value == "0" and line[1] == "!":
4ac093dc
JS
226 err_msg = "Succeded to execute command '{}' on {}, return code: {}".format(line[2],line[0],return_value)
227 self.log.error(err_msg)
228 # Try to handle this error in an interactive way, if we cannot go on
229 # raise an exception and exit
230 if not self.interactive_error_handling():
231 raise TestException(err_msg)
bce7d520
JS
232 else:
233 self.log.debug("Command '{}' on {} returned with: {}".format(line[2],line[0],return_value))
3fa89b7c
JS
234
235 def virtual_environ_stop(self):
fbaed8c3 236 for name in self.used_machine_names:
f9178ad3
JS
237 # We just catch exception here to avoid
238 # that we stop the cleanup process if only one command fails
239 try:
240 self.virtual_machines[name].shutdown()
241 self.virtual_machines[name].revert_snapshot()
242 self.virtual_machines[name].undefine()
243 except BaseException as e:
244 self.log.exception(e)
3fa89b7c
JS
245
246 for name in self.virtual_environ.network_names:
f9178ad3
JS
247 try:
248 self.virtual_networks[name].undefine()
249 except BaseException as e:
250 self.log.exception(e)
251