Merge lp:~matsubara/curtin/jenkins into lp:~curtin-dev/curtin/trunk
- jenkins
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 270 |
Proposed branch: | lp:~matsubara/curtin/jenkins |
Merge into: | lp:~curtin-dev/curtin/trunk |
Diff against target: |
422 lines (+86/-62) 3 files modified
tests/vmtests/__init__.py (+57/-33) tests/vmtests/test_bonding.py (+15/-15) tests/vmtests/test_network.py (+14/-14) |
To merge this branch: | bzr merge lp:~matsubara/curtin/jenkins |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Scott Moser (community) | Approve | ||
Ryan Harper (community) | Approve | ||
Review via email: mp+270587@code.launchpad.net |
Commit message
Add a logger to save logs to file and print output to console.
Description of the change
Hi, this branch adds a logger to the vmtests so test run logs as well the serial.log files generated by different kvm invocations will be kept for a post mortem analysis. This is specially useful for tests run under the jenkins instance I'm setting up for curtin CI. It also keeps the functionality of print()ing the output of tests to the console and it sets a pattern to be used for future test additions to the vmtests.
Diogo Matsubara (matsubara) wrote : | # |
> Looks fine, please do add something so subsequent and/or parallel runs of
> vmtest don't write to the same file.
>
> The rest is fine.
Hi Ryan,
I initially used the FileHandler mode='a' so the log would always be appended but then the log file was growing too much (and my initial intention was to have it collected by each jenkins job after the run). I like the idea of putting a date in the name so it'll be kept separate. Fixed.
Thanks for the review!
Ryan Harper (raharper) : | # |
Scott Moser (smoser) : | # |
- 270. By Ryan Harper
-
Add a logger to save logs to file and print output to console.
Use logger to capture vmtest logs, serial.log files generated during test runs.
Tests will continue to produce output to stderr for capture under test
harnesses like Jenkins
Preview Diff
1 | === modified file 'tests/vmtests/__init__.py' |
2 | --- tests/vmtests/__init__.py 2015-08-31 20:58:03 +0000 |
3 | +++ tests/vmtests/__init__.py 2015-09-10 13:04:01 +0000 |
4 | @@ -1,5 +1,7 @@ |
5 | import ast |
6 | +import datetime |
7 | import hashlib |
8 | +import logging |
9 | import os |
10 | import re |
11 | import shutil |
12 | @@ -12,6 +14,26 @@ |
13 | DEVNULL = open(os.devnull, 'w') |
14 | |
15 | |
16 | +logger = logging.getLogger(__name__) |
17 | +logger.setLevel(logging.DEBUG) |
18 | +formatter = logging.Formatter( |
19 | + '%(asctime)s - %(name)s - %(levelname)s - %(message)s') |
20 | +# Configure logging module to save output to disk and present it on |
21 | +# sys.stderr |
22 | +now = datetime.datetime.now().isoformat() |
23 | +fh = logging.FileHandler( |
24 | + '/tmp/{}-vmtest.log'.format(now), mode='w', encoding='utf-8') |
25 | +fh.setLevel(logging.DEBUG) |
26 | +fh.setFormatter(formatter) |
27 | + |
28 | +ch = logging.StreamHandler() |
29 | +ch.setLevel(logging.DEBUG) |
30 | +ch.setFormatter(formatter) |
31 | + |
32 | +logger.addHandler(fh) |
33 | +logger.addHandler(ch) |
34 | + |
35 | + |
36 | class ImageStore: |
37 | def __init__(self, base_dir): |
38 | self.base_dir = base_dir |
39 | @@ -20,19 +42,19 @@ |
40 | |
41 | def get_image(self, repo, release, arch): |
42 | # query sstream for root image |
43 | - print('Query simplestreams for root image: ' |
44 | - 'release={release} arch={arch}'.format(release=release, |
45 | - arch=arch)) |
46 | + logger.debug( |
47 | + 'Query simplestreams for root image: ' |
48 | + 'release={release} arch={arch}'.format(release=release, arch=arch)) |
49 | cmd = ["tools/usquery", "--max=1", repo, "release=%s" % release, |
50 | "krel=%s" % release, "arch=%s" % arch, |
51 | "item_name=root-image.gz"] |
52 | - print(" ".join(cmd)) |
53 | + logger.debug(" ".join(cmd)) |
54 | out = subprocess.check_output(cmd) |
55 | - print(out) |
56 | + logger.debug(out) |
57 | sstream_data = ast.literal_eval(bytes.decode(out)) |
58 | |
59 | # Check if we already have the image |
60 | - print('Checking cache for image') |
61 | + logger.debug('Checking cache for image') |
62 | checksum = "" |
63 | release_dir = os.path.join(self.base_dir, repo, release, arch, |
64 | sstream_data['version_name']) |
65 | @@ -50,13 +72,13 @@ |
66 | not os.path.exists(p("root-image-kernel")) or \ |
67 | not os.path.exists(p("root-image-initrd")): |
68 | # clear dir, download, and extract image |
69 | - print('Image not found, downloading...') |
70 | + logger.debug('Image not found, downloading...') |
71 | shutil.rmtree(release_dir, ignore_errors=True) |
72 | os.makedirs(release_dir) |
73 | subprocess.check_call( |
74 | ["wget", "-c", sstream_data['item_url'], "-O", |
75 | p("root-image.gz")]) |
76 | - print('Converting image format') |
77 | + logger.debug('Converting image format') |
78 | subprocess.check_call(["tools/maas2roottar", p("root-image.gz")]) |
79 | return (p("root-image"), p("root-image-kernel"), |
80 | p("root-image-initrd")) |
81 | @@ -80,21 +102,21 @@ |
82 | os.mkdir(self.mnt) |
83 | |
84 | # create target disk |
85 | - print('Creating target disk') |
86 | + logger.debug('Creating target disk') |
87 | self.target_disk = os.path.join(self.tmpdir, "install_disk.img") |
88 | subprocess.check_call(["qemu-img", "create", "-f", "qcow2", |
89 | self.target_disk, "10G"], |
90 | stdout=DEVNULL, stderr=subprocess.STDOUT) |
91 | |
92 | # create seed.img for installed system's cloud init |
93 | - print('Creating seed disk') |
94 | + logger.debug('Creating seed disk') |
95 | self.seed_disk = os.path.join(self.tmpdir, "seed.img") |
96 | subprocess.check_call(["cloud-localds", self.seed_disk, |
97 | user_data_file, meta_data_file], |
98 | stdout=DEVNULL, stderr=subprocess.STDOUT) |
99 | |
100 | # create output disk, mount ro |
101 | - print('Creating output disk') |
102 | + logger.debug('Creating output disk') |
103 | self.output_disk = os.path.join(self.tmpdir, "output_disk.img") |
104 | subprocess.check_call(["qemu-img", "create", "-f", "raw", |
105 | self.output_disk, "10M"], |
106 | @@ -103,7 +125,7 @@ |
107 | stdout=DEVNULL, stderr=subprocess.STDOUT) |
108 | |
109 | def mount_output_disk(self): |
110 | - print('Mounting output disk') |
111 | + logger.debug('Mounting output disk') |
112 | subprocess.check_call(["fuseext2", "-o", "rw+", self.output_disk, |
113 | self.mnt], |
114 | stdout=DEVNULL, stderr=subprocess.STDOUT) |
115 | @@ -116,14 +138,14 @@ |
116 | class VMBaseClass: |
117 | @classmethod |
118 | def setUpClass(self): |
119 | - print('Acquiring boot image') |
120 | + logger.debug('Acquiring boot image') |
121 | # get boot img |
122 | image_store = ImageStore(IMAGE_DIR) |
123 | (boot_img, boot_kernel, boot_initrd) = image_store.get_image( |
124 | self.repo, self.release, self.arch) |
125 | |
126 | # set up tempdir |
127 | - print('Setting up tempdir') |
128 | + logger.debug('Setting up tempdir') |
129 | self.td = TempDir(self.user_data) |
130 | |
131 | # create launch cmd |
132 | @@ -133,7 +155,7 @@ |
133 | |
134 | # check for network configuration |
135 | self.network_state = curtin_net.parse_net_config(self.conf_file) |
136 | - print(self.network_state) |
137 | + logger.debug("Network state: {}".format(self.network_state)) |
138 | |
139 | # build -n arg list with macaddrs from net_config physical config |
140 | macs = [] |
141 | @@ -141,7 +163,7 @@ |
142 | if self.network_state: |
143 | interfaces = self.network_state.get('interfaces') |
144 | for ifname in interfaces: |
145 | - print(ifname) |
146 | + logger.debug("Interface name: {}".format(ifname)) |
147 | iface = interfaces.get(ifname) |
148 | hwaddr = iface.get('mac_address') |
149 | if hwaddr: |
150 | @@ -166,31 +188,32 @@ |
151 | |
152 | # run vm with installer |
153 | try: |
154 | - print('Running curtin installer') |
155 | - print('{}'.format(" ".join(cmd))) |
156 | + logger.debug('Running curtin installer') |
157 | + logger.debug('{}'.format(" ".join(cmd))) |
158 | subprocess.check_call(cmd, timeout=self.install_timeout, |
159 | stdout=DEVNULL, stderr=subprocess.STDOUT) |
160 | except subprocess.TimeoutExpired: |
161 | - print('Curting installer failed') |
162 | + logger.debug('Curtin installer failed') |
163 | raise |
164 | finally: |
165 | if os.path.exists('serial.log'): |
166 | - with open('serial.log', 'r') as l: |
167 | - print('Serial console output:\n{}'.format(l.read())) |
168 | + with open('serial.log', 'r', encoding='utf-8') as l: |
169 | + logger.debug( |
170 | + u'Serial console output:\n{}'.format(l.read())) |
171 | |
172 | - print('') |
173 | - print('Checking curtin install output for errors') |
174 | + logger.debug('') |
175 | + logger.debug('Checking curtin install output for errors') |
176 | with open('serial.log') as l: |
177 | install_log = l.read() |
178 | errors = re.findall('\[.*\]\ cloud-init.*:.*Installation\ failed', |
179 | install_log) |
180 | if len(errors) > 0: |
181 | for e in errors: |
182 | - print(e) |
183 | - print('Errors during curtin installer') |
184 | + logger.debug(e) |
185 | + logger.debug('Errors during curtin installer') |
186 | raise Exception('Errors during curtin installer') |
187 | else: |
188 | - print('Install OK') |
189 | + logger.debug('Install OK') |
190 | |
191 | # drop the size parameter if present in extra_disks |
192 | extra_disks = [x if ":" not in x else x.split(':')[0] |
193 | @@ -207,26 +230,27 @@ |
194 | |
195 | # run vm with installed system, fail if timeout expires |
196 | try: |
197 | - print('Booting target image') |
198 | - print('{}'.format(" ".join(cmd))) |
199 | + logger.debug('Booting target image') |
200 | + logger.debug('{}'.format(" ".join(cmd))) |
201 | subprocess.check_call(cmd, timeout=self.boot_timeout, |
202 | stdout=DEVNULL, stderr=subprocess.STDOUT) |
203 | except subprocess.TimeoutExpired: |
204 | - print('Booting after install failed') |
205 | + logger.debug('Booting after install failed') |
206 | raise |
207 | finally: |
208 | serial_log = os.path.join(self.td.tmpdir, 'serial.log') |
209 | if os.path.exists(serial_log): |
210 | - with open(serial_log, 'r') as l: |
211 | - print('Serial console output:\n{}'.format(l.read())) |
212 | + with open(serial_log, 'r', encoding='utf-8') as l: |
213 | + logger.debug( |
214 | + u'Serial console output:\n{}'.format(l.read())) |
215 | |
216 | # mount output disk |
217 | self.td.mount_output_disk() |
218 | - print('Ready for testcases') |
219 | + logger.debug('Ready for testcases') |
220 | |
221 | @classmethod |
222 | def tearDownClass(self): |
223 | - print('Removing launch logfile') |
224 | + logger.debug('Removing launch logfile') |
225 | subprocess.call(["fusermount", "-u", self.td.mnt]) |
226 | # remove launch logfile |
227 | if os.path.exists("./serial.log"): |
228 | |
229 | === modified file 'tests/vmtests/test_bonding.py' |
230 | --- tests/vmtests/test_bonding.py 2015-09-04 18:44:08 +0000 |
231 | +++ tests/vmtests/test_bonding.py 2015-09-10 13:04:01 +0000 |
232 | @@ -1,4 +1,4 @@ |
233 | -from . import VMBaseClass |
234 | +from . import VMBaseClass, logger |
235 | from unittest import TestCase |
236 | |
237 | import ipaddress |
238 | @@ -84,13 +84,13 @@ |
239 | def test_ifenslave_installed(self): |
240 | with open(os.path.join(self.td.mnt, "ifenslave_installed")) as fp: |
241 | status = fp.read().strip() |
242 | - print('ifenslave installed: {}'.format(status)) |
243 | + logger.debug('ifenslave installed: {}'.format(status)) |
244 | self.assertEqual('install ok installed', status) |
245 | |
246 | def test_etc_network_interfaces(self): |
247 | with open(os.path.join(self.td.mnt, "interfaces")) as fp: |
248 | eni = fp.read() |
249 | - print('etc/network/interfaces:\n{}'.format(eni)) |
250 | + logger.debug('etc/network/interfaces:\n{}'.format(eni)) |
251 | |
252 | expected_eni = self.get_expected_etc_network_interfaces() |
253 | eni_lines = eni.split('\n') |
254 | @@ -100,20 +100,20 @@ |
255 | def test_ifconfig_output(self): |
256 | '''check ifconfig output with test input''' |
257 | network_state = self.get_network_state() |
258 | - print('expected_network_state:\n{}'.format( |
259 | + logger.debug('expected_network_state:\n{}'.format( |
260 | yaml.dump(network_state, default_flow_style=False, indent=4))) |
261 | |
262 | with open(os.path.join(self.td.mnt, "ifconfig_a")) as fp: |
263 | ifconfig_a = fp.read() |
264 | - print('ifconfig -a:\n{}'.format(ifconfig_a)) |
265 | + logger.debug('ifconfig -a:\n{}'.format(ifconfig_a)) |
266 | |
267 | ifconfig_dict = ifconfig_to_dict(ifconfig_a) |
268 | - print('parsed ifcfg dict:\n{}'.format( |
269 | + logger.debug('parsed ifcfg dict:\n{}'.format( |
270 | yaml.dump(ifconfig_dict, default_flow_style=False, indent=4))) |
271 | |
272 | with open(os.path.join(self.td.mnt, "ip_route_show")) as fp: |
273 | ip_route_show = fp.read() |
274 | - print("ip route show:\n{}".format(ip_route_show)) |
275 | + logger.debug("ip route show:\n{}".format(ip_route_show)) |
276 | for line in [line for line in ip_route_show.split('\n') |
277 | if 'src' in line]: |
278 | m = re.search(r'^(?P<network>\S+)\sdev\s' + |
279 | @@ -122,11 +122,11 @@ |
280 | r'\s+src\s(?P<src_ip>\S+)', |
281 | line) |
282 | route_info = m.groupdict('') |
283 | - print(route_info) |
284 | + logger.debug(route_info) |
285 | |
286 | with open(os.path.join(self.td.mnt, "route_n")) as fp: |
287 | route_n = fp.read() |
288 | - print("route -n:\n{}".format(route_n)) |
289 | + logger.debug("route -n:\n{}".format(route_n)) |
290 | |
291 | interfaces = network_state.get('interfaces') |
292 | for iface in interfaces.values(): |
293 | @@ -149,8 +149,8 @@ |
294 | route_n) |
295 | |
296 | def check_interface(self, iface, ifconfig, route_n): |
297 | - print('testing iface:\n{}\n\nifconfig:\n{}'.format( |
298 | - iface, ifconfig)) |
299 | + logger.debug( |
300 | + 'testing iface:\n{}\n\nifconfig:\n{}'.format(iface, ifconfig)) |
301 | subnets = iface.get('subnets', {}) |
302 | if subnets and iface['index'] != 0: |
303 | ifname = "{name}:{index}".format(**iface) |
304 | @@ -158,8 +158,8 @@ |
305 | ifname = "{name}".format(**iface) |
306 | |
307 | # initial check, do we have the correct iface ? |
308 | - print('ifname={}'.format(ifname)) |
309 | - print("ifconfig['interface']={}".format(ifconfig['interface'])) |
310 | + logger.debug('ifname={}'.format(ifname)) |
311 | + logger.debug("ifconfig['interface']={}".format(ifconfig['interface'])) |
312 | self.assertEqual(ifname, ifconfig['interface']) |
313 | |
314 | # check physical interface attributes |
315 | @@ -205,12 +205,12 @@ |
316 | gw_ip = subnet['gateway'] |
317 | gateways = [line for line in route_n.split('\n') |
318 | if 'UG' in line and gw_ip in line] |
319 | - print('matching gateways:\n{}'.format(gateways)) |
320 | + logger.debug('matching gateways:\n{}'.format(gateways)) |
321 | self.assertEqual(len(gateways), 1) |
322 | [gateways] = gateways |
323 | (dest, gw, genmask, flags, metric, ref, use, iface) = \ |
324 | gateways.split() |
325 | - print('expected gw:{} found gw:{}'.format(gw_ip, gw)) |
326 | + logger.debug('expected gw:{} found gw:{}'.format(gw_ip, gw)) |
327 | self.assertEqual(gw_ip, gw) |
328 | |
329 | |
330 | |
331 | === modified file 'tests/vmtests/test_network.py' |
332 | --- tests/vmtests/test_network.py 2015-08-27 21:46:55 +0000 |
333 | +++ tests/vmtests/test_network.py 2015-09-10 13:04:01 +0000 |
334 | @@ -1,4 +1,4 @@ |
335 | -from . import VMBaseClass |
336 | +from . import VMBaseClass, logger |
337 | from unittest import TestCase |
338 | |
339 | import ipaddress |
340 | @@ -81,7 +81,7 @@ |
341 | def test_etc_network_interfaces(self): |
342 | with open(os.path.join(self.td.mnt, "interfaces")) as fp: |
343 | eni = fp.read() |
344 | - print('etc/network/interfaces:\n{}'.format(eni)) |
345 | + logger.debug('etc/network/interfaces:\n{}'.format(eni)) |
346 | |
347 | expected_eni = self.get_expected_etc_network_interfaces() |
348 | eni_lines = eni.split('\n') |
349 | @@ -91,20 +91,20 @@ |
350 | def test_ifconfig_output(self): |
351 | '''check ifconfig output with test input''' |
352 | network_state = self.get_network_state() |
353 | - print('expected_network_state:\n{}'.format( |
354 | + logger.debug('expected_network_state:\n{}'.format( |
355 | yaml.dump(network_state, default_flow_style=False, indent=4))) |
356 | |
357 | with open(os.path.join(self.td.mnt, "ifconfig_a")) as fp: |
358 | ifconfig_a = fp.read() |
359 | - print('ifconfig -a:\n{}'.format(ifconfig_a)) |
360 | + logger.debug('ifconfig -a:\n{}'.format(ifconfig_a)) |
361 | |
362 | ifconfig_dict = ifconfig_to_dict(ifconfig_a) |
363 | - print('parsed ifcfg dict:\n{}'.format( |
364 | + logger.debug('parsed ifcfg dict:\n{}'.format( |
365 | yaml.dump(ifconfig_dict, default_flow_style=False, indent=4))) |
366 | |
367 | with open(os.path.join(self.td.mnt, "ip_route_show")) as fp: |
368 | ip_route_show = fp.read() |
369 | - print("ip route show:\n{}".format(ip_route_show)) |
370 | + logger.debug("ip route show:\n{}".format(ip_route_show)) |
371 | for line in [line for line in ip_route_show.split('\n') |
372 | if 'src' in line]: |
373 | m = re.search(r'^(?P<network>\S+)\sdev\s' + |
374 | @@ -113,11 +113,11 @@ |
375 | r'\s+src\s(?P<src_ip>\S+)', |
376 | line) |
377 | route_info = m.groupdict('') |
378 | - print(route_info) |
379 | + logger.debug(route_info) |
380 | |
381 | with open(os.path.join(self.td.mnt, "route_n")) as fp: |
382 | route_n = fp.read() |
383 | - print("route -n:\n{}".format(route_n)) |
384 | + logger.debug("route -n:\n{}".format(route_n)) |
385 | |
386 | interfaces = network_state.get('interfaces') |
387 | for iface in interfaces.values(): |
388 | @@ -140,8 +140,8 @@ |
389 | route_n) |
390 | |
391 | def check_interface(self, iface, ifconfig, route_n): |
392 | - print('testing iface:\n{}\n\nifconfig:\n{}'.format( |
393 | - iface, ifconfig)) |
394 | + logger.debug( |
395 | + 'testing iface:\n{}\n\nifconfig:\n{}'.format(iface, ifconfig)) |
396 | subnets = iface.get('subnets', {}) |
397 | if subnets and iface['index'] != 0: |
398 | ifname = "{name}:{index}".format(**iface) |
399 | @@ -149,8 +149,8 @@ |
400 | ifname = "{name}".format(**iface) |
401 | |
402 | # initial check, do we have the correct iface ? |
403 | - print('ifname={}'.format(ifname)) |
404 | - print("ifconfig['interface']={}".format(ifconfig['interface'])) |
405 | + logger.debug('ifname={}'.format(ifname)) |
406 | + logger.debug("ifconfig['interface']={}".format(ifconfig['interface'])) |
407 | self.assertEqual(ifname, ifconfig['interface']) |
408 | |
409 | # check physical interface attributes |
410 | @@ -194,12 +194,12 @@ |
411 | gw_ip = subnet['gateway'] |
412 | gateways = [line for line in route_n.split('\n') |
413 | if 'UG' in line and gw_ip in line] |
414 | - print('matching gateways:\n{}'.format(gateways)) |
415 | + logger.debug('matching gateways:\n{}'.format(gateways)) |
416 | self.assertEqual(len(gateways), 1) |
417 | [gateways] = gateways |
418 | (dest, gw, genmask, flags, metric, ref, use, iface) = \ |
419 | gateways.split() |
420 | - print('expected gw:{} found gw:{}'.format(gw_ip, gw)) |
421 | + logger.debug('expected gw:{} found gw:{}'.format(gw_ip, gw)) |
422 | self.assertEqual(gw_ip, gw) |
423 | |
424 |
Looks fine, please do add something so subsequent and/or parallel runs of vmtest don't write to the same file.
The rest is fine.