Merge lp:~matsubara/curtin/jenkins into lp:~curtin-dev/curtin/trunk

Proposed by Diogo Matsubara
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
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.

To post a comment you must log in.
Revision history for this message
Ryan Harper (raharper) 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.

review: Needs Fixing
Revision history for this message
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!

Revision history for this message
Ryan Harper (raharper) :
review: Approve
Revision history for this message
Scott Moser (smoser) :
review: Approve
lp:~matsubara/curtin/jenkins updated
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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

Subscribers

People subscribed via source and target branches