Merge lp:~roadmr/checkbox/1260507-config-files into lp:checkbox

Proposed by Daniel Manrique
Status: Merged
Approved by: Zygmunt Krynicki
Approved revision: 2629
Merged at revision: 2631
Proposed branch: lp:~roadmr/checkbox/1260507-config-files
Merge into: lp:checkbox
Diff against target: 448 lines (+173/-75)
3 files modified
checkbox-old/debian/changelog (+3/-1)
checkbox-old/scripts/network (+130/-50)
checkbox-old/scripts/virtualization (+40/-24)
To merge this branch: bzr merge lp:~roadmr/checkbox/1260507-config-files
Reviewer Review Type Date Requested Status
Zygmunt Krynicki (community) Approve
Daniel Manrique (community) Needs Resubmitting
Review via email: mp+203850@code.launchpad.net

Commit message

scripts/network and scripts/virtualization can now be configured via environment variables, in addition to the existing config file mechanism and command-line parameters.

This is most useful with canonical-certification-server or other plainbox-based testing tools (i.e. in the absence of checkbox proper). In this case all configuration is centralized in the plainbox config file (/etc/xdg/plainbox.conf). These variables can be defined in the [environment] section with the familiar config file syntax.

For virtualization, the KVM_IMAGE and KVM_TIMEOUT variables are picked up.

For network, it's TEST_TARGET_FTP, TEST_TARGET_IPERF, TEST_USER and TEST_PASS. Separate variables are used for iperf and ftp to mimic the existing config file behavior (but note that a single --target command-line parameter is used in all cases).

Across the board, the preference order is command line -> environment -> config file.

Description of the change

scripts/network and scripts/virtualization can now be configured via environment variables, in addition to the existing config file mechanism and command-line parameters.

This is most useful with canonical-certification-server or other plainbox-based testing tools (i.e. in the absence of checkbox proper). In this case all configuration is centralized in the plainbox config file (/etc/xdg/plainbox.conf). These variables can be defined in the [environment] section with the familiar config file syntax.

For virtualization, the KVM_IMAGE and KVM_TIMEOUT variables are picked up.

For network, it's TEST_TARGET_FTP, TEST_TARGET_IPERF, TEST_USER and TEST_PASS. Separate variables are used for iperf and ftp to mimic the existing config file behavior (but note that a single --target command-line parameter is used in all cases).

Across the board, the preference order is command line -> environment -> config file.

To post a comment you must log in.
Revision history for this message
Zygmunt Krynicki (zyga) wrote :

-checkbox (0.17.4) UNRELEASED; urgency=low
6 +checkbox (0.17.4ubuntu1) UNRELEASED; urgency=low

I'm sure you didn't want to send that change

review: Needs Fixing
2628. By Daniel Manrique

scripts/virtualization: refactoring of option management.

The logic and the preference sequence are a bit clearer.

It also adds support for environment variables to specify configuration
values (image and timout for KVM testing).

Note that the behavior when more than one configuration mechanism is
specified has changed; previously, command-line values would only be
used if they weren't defined in the config file. Now, command-line
always overrides environment variable, which in turn always overrides
config file.

2629. By Daniel Manrique

Pep8 fixes, save for a couple lines I'm too afraid to touch.

Revision history for this message
Daniel Manrique (roadmr) wrote :

Ah, the wonders of dch.

Fixed! thanks!

review: Needs Resubmitting
Revision history for this message
Zygmunt Krynicki (zyga) wrote :

+1 thanks

review: Approve
Revision history for this message
Jeff Lane  (bladernr) wrote :

Heh... so...
1: If I read that right, now the configs for virtualization and network will live in /etc/xdg/plainbox.conf. Did you add the default configs there? (I don't see it in this diff)
2: Personally, I think the separate files is a bit more intuitive as it says (Hey Tester, edit these files here for network and virtualization tests)
3: /etc/xdg/plainbox.conf is harder to find than /etc/checkbox.d was but that's a minor quibble
4: What's the order of precedence for environment variables now? they don't override command line, yes, but do pre-set env vars override the ones read from plainbox.conf? (e.g. if I have TARGET set already via the shell, will running plainbox overwrite TARGET with whatever is in plainbox.conf?)
5: has this been tested with the contents of the network.cfg and virtualization.cfg included in plainbox.conf?
6: the pep8 fixes made the diff rather hard to read, it's hard to tell what is related to this fix and what is pep8 adding spaces or doing something that doesn't really affect the script.

Revision history for this message
Daniel Manrique (roadmr) wrote :
Download full text (3.4 KiB)

>Heh... so...
>1: If I read that right, now the configs for virtualization and network will live in /etc/xdg/plainbox.conf. Did you add the default configs there? (I don't see it in this diff)

We don't create plainbox.conf by default in any of our packages, but this is a good point. I'll see if it makes sense to ship this file in one of the packages (I'm thinking plainbox-provider-checkbox).

>2: Personally, I think the separate files is a bit more intuitive as it says (Hey Tester, edit these files here for network and virtualization tests)

I agree in principle, we decided against having script-specific config files because it felt a bit kludgy, but we can also try to think of a way to do this. The naive way is to just have the scripts search in /etc/xdg as well and have plainbox-provider-checkbox install the existing configs in there, but that's just relocating and doesn't feel like it solves the issue entirely.

>3: /etc/xdg/plainbox.conf is harder to find than /etc/checkbox.d was but that's a minor quibble

that's just because the tool was called checkbox :D. If the new tool is called canonical-certification-server the equivalent would be some sort of /etc/canonical-certification or some other such.

>4: What's the order of precedence for environment variables now? they don't override command line, yes, but do pre-set env vars override the ones read from plainbox.conf? (e.g. if I have TARGET set already via the shell, will running plainbox overwrite TARGET with whatever is in plainbox.conf?)

PLainbox's env-setting magic will always give precedence to the actual environment, so in your example, no, whatever you set in the shell for TARGET would be authoritative.

>5: has this been tested with the contents of the network.cfg and virtualization.cfg included in plainbox.conf?

I didn't :/ I tested with some dummy values, I checked all possible sequences (setting env in the shell, setting env in the config file, using command-line) and in all cases it picked up the correct values. But again, I didn't just translate the existing values to equivalents in plainbox.conf :( sorry, my bad.

>6: the pep8 fixes made the diff rather hard to read, it's hard to tell what is related to this fix and what is pep8 adding spaces or doing something that doesn't really affect the script.

Yes, that's always the case when reading the complete diff on launchpad :( The pep8 fixes are in a separate commit though, so you could bzr branch and review locally, or check each individual commit in the branch (https://code.launchpad.net/~roadmr/checkbox/1260507-config-files). The meat is in commits 2626 and 2628; 2627 is just documentation and licensing stuff, while 2629 are the pep fixes.

OK, actions that will result from this comment:

- Find a good place/way to create an example/prepopulated plainbox.conf.
- Retest this mechanism with the existing values from checkbox.d to ensure they work and make sense as they did before.

We need to decide:
- Do we want to somehow split again these config files? If so, how to do it without simply relocating them, which would be a bit kludgy?
- Do we want to somehow place these files in a more-intuitive config directory that refer...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'checkbox-old/debian/changelog'
2--- checkbox-old/debian/changelog 2014-01-28 02:06:31 +0000
3+++ checkbox-old/debian/changelog 2014-01-30 14:28:16 +0000
4@@ -19,11 +19,13 @@
5 PCI subclass "2", like newer NVidia GPUs (LP: #1270139)
6 * checkbox/scripts/audio_settings: Place/read active_profiles in an
7 absolute, known-writable location (LP: #1271707)
8+ * scripts/virtualization, scripts/network: Use specific environment
9+ variables for configuration, if available (LP: #1260507)
10
11 [ Zygmunt Krynicki ]
12 * Incremented changelog
13
14- -- Zygmunt Krynicki <zygmunt.krynicki@canonical.com> Tue, 28 Jan 2014 03:06:27 +0100
15+ -- Daniel Manrique <roadmr@ubuntu.com> Wed, 29 Jan 2014 17:07:13 -0500
16
17 checkbox (0.17.2) trusty; urgency=low
18
19
20=== modified file 'checkbox-old/scripts/network'
21--- checkbox-old/scripts/network 2014-01-15 22:21:57 +0000
22+++ checkbox-old/scripts/network 2014-01-30 14:28:16 +0000
23@@ -1,9 +1,10 @@
24 #!/usr/bin/env python3
25 """
26-Copyright (C) 2012 Canonical Ltd.
27+Copyright (C) 2012-2014 Canonical Ltd.
28
29 Authors
30 Jeff Marcom <jeff.marcom@canonical.com>
31+ Daniel Manrique <roadmr@ubuntu.com>
32
33 This program is free software: you can redistribute it and/or modify
34 it under the terms of the GNU General Public License version 3,
35@@ -267,6 +268,7 @@
36 print("Failed sending file via ftp")
37 return 1
38
39+
40 class StressPerformanceTest:
41
42 def __init__(self, interface, target):
43@@ -281,7 +283,7 @@
44 ping_cmd = 'ping -I {} {}'.format(self.interface, self.target)
45 ping = subprocess.Popen(shlex.split(ping_cmd), stdout=subprocess.PIPE)
46 iperf.communicate()
47-
48+
49 ping.terminate()
50 (out, err) = ping.communicate()
51
52@@ -301,9 +303,10 @@
53 if 'unreachable' in line.lower():
54 print(line)
55 result = 1
56-
57+
58 return result
59-
60+
61+
62 class Interface(socket.socket):
63 """
64 Simple class that provides network interface information.
65@@ -367,10 +370,81 @@
66 return self._read_data("device/label")
67
68
69+def get_test_parameters(args, environ, config_filename):
70+ # Decide the actual values for test parameters, which can come
71+ # from one of three possible sources: a config file, command-line
72+ # arguments, or environment variables.
73+ # - If command-line args were given, they take precedence
74+ # - Next come environment variables, if set.
75+ # - Last, values in the config file are used if present.
76+
77+ params = {"test_target_ftp": None,
78+ "test_user": None,
79+ "test_pass": None,
80+ "test_target_iperf": None}
81+
82+ #First (try to) load values from config file
83+ config = configparser.SafeConfigParser()
84+
85+ try:
86+ with open(config_filename) as config_file:
87+ config.readfp(config_file)
88+ params["test_target_ftp"] = config.get("FTP", "Target")
89+ params["test_user"] = config.get("FTP", "User")
90+ params["test_pass"] = config.get("FTP", "Pass")
91+ params["test_target_iperf"] = config.get("IPERF", "Target")
92+ except FileNotFoundError as err:
93+ pass # No biggie, we can still get configs from elsewhere
94+
95+ # Next see if we have environment variables to override the config file
96+ # "partial" overrides are not allowed; if an env variable is missing,
97+ # we won't use this at all.
98+ if all([param.upper() in os.environ for param in params.keys()]):
99+ for key in params.keys():
100+ params[key] = os.environ[key.upper()]
101+
102+ # Finally, see if we have the command-line arguments that are the ultimate
103+ # override. Again, we will only override if we have all of them.
104+ if args.target and args.username and args.password:
105+ params["test_target_ftp"] = args.target
106+ params["test_user"] = args.username
107+ params["test_pass"] = args.password
108+ params["test_target_iperf"] = args.target
109+
110+ return params
111+
112+
113 def interface_test(args):
114 if not "test_type" in vars(args):
115 return
116
117+ # Determine whether to use the default or user-supplied config
118+ # file name.
119+ DEFAULT_CFG = "/etc/checkbox.d/network.cfg"
120+ if not "config" in vars(args):
121+ config_filename = DEFAULT_CFG
122+ else:
123+ config_filename = args.config
124+
125+ # Get the actual test data from one of three possible sources
126+ test_parameters = get_test_parameters(args, os.environ, config_filename)
127+
128+ test_user = test_parameters["test_user"]
129+ test_pass = test_parameters["test_pass"]
130+ if (args.test_type.lower() == "iperf" or
131+ args.test_type.lower() == "stress"):
132+ test_target = test_parameters["test_target_iperf"]
133+ else:
134+ test_target = test_parameters["test_target_ftp"]
135+
136+ # Validate that we got reasonable values
137+ if "example.com" in test_target:
138+ # Default values found in config file
139+ logging.error("Please supply target via: %s", config_filename)
140+ sys.exit(1)
141+
142+ # Testing begins here!
143+ #
144 # Check and make sure that interface is indeed connected
145 try:
146 cmd = "ip link set dev %s up" % args.interface
147@@ -382,37 +456,6 @@
148 # Give interface enough time to get DHCP address
149 time.sleep(10)
150
151- # Open Network config file
152- DEFAULT_CFG = "/etc/checkbox.d/network.cfg"
153- if not "config" in vars(args):
154- config_file = DEFAULT_CFG
155- else:
156- config_file = args.config
157-
158- config = configparser.SafeConfigParser()
159- config.readfp(open(config_file))
160-
161- # Set default network config options
162- test_target = args.target
163- test_user = args.username
164- test_pass = args.password
165-
166- if test_target is None:
167- # Set FTP parameters based on config file
168- test_target = config.get("FTP", "Target")
169- test_user = config.get("FTP", "User")
170- test_pass = config.get("FTP", "Pass")
171-
172- if (args.test_type.lower() == "iperf" or
173- args.test_type.lower() == "stress"):
174- test_target = config.get("IPERF", "Target")
175-
176- if "example.com" in test_target:
177- # Default values found in config file
178- logging.error("Please supply target via: %s", config_file)
179- sys.exit(1)
180-
181-
182 result = 0
183 # Stop all other interfaces
184 extra_interfaces = \
185@@ -443,9 +486,10 @@
186 result = iperf_benchmark.run()
187
188 elif args.test_type.lower() == "stress":
189- stress_benchmark = StressPerformanceTest(args.interface, test_target)
190+ stress_benchmark = StressPerformanceTest(args.interface,
191+ test_target)
192 result = stress_benchmark.run()
193-
194+
195 for iface in extra_interfaces:
196 logging.debug("Restoring interface:%s", iface)
197 try:
198@@ -457,6 +501,7 @@
199
200 return result
201
202+
203 def interface_info(args):
204
205 info_set = ""
206@@ -476,17 +521,53 @@
207
208 def main():
209
210- intro_message = "Network module\n\nThis script provides benchmarking " \
211- + "and information for a specified network interface.\n\n\n" \
212- + "Example NIC information usage:\nnetwork info -i eth0 --max-speed " \
213- + "\n\nFor running ftp benchmark test: \nnetwork test -i eth0 -t ftp " \
214- + "--target 192.168.0.1 --username USERID --password PASSW0RD " \
215- + "--filesize-2\n\nPlease note that this script can use configuration " \
216- + "values supplied via a config file.\nExample config file:\n[FTP]\n" \
217- + "Target: 192.168.1.23\nUser: FTPUser\nPass:PassW0Rd\n" \
218- + "[IPERF]\nTarget: 192.168.1.45\n**NOTE**\nDefault config location " \
219- + "is /etc/checkbox.d/network.cfg"
220-
221+ intro_message = """
222+Network module
223+
224+This script provides benchmarking and information for a specified network
225+interface.
226+
227+Example NIC information usage:
228+network info -i eth0 --max-speed
229+
230+For running ftp benchmark test:
231+network test -i eth0 -t ftp
232+--target 192.168.0.1 --username USERID --password PASSW0RD
233+--filesize-2
234+
235+Configuration
236+=============
237+
238+Configuration can be supplied in three different ways, with the following
239+priorities:
240+
241+1- Command-line parameters (see above).
242+2- Environment variables (example will follow).
243+3- Configuration file (example will follow).
244+ Default config location is /etc/checkbox.d/network.cfg
245+
246+Environment variables
247+=====================
248+ALL environment variables must be defined, even if empty, for them to be
249+picked up. The variables are:
250+TEST_TARGET_FTP
251+TEST_USER
252+TEST_PASS
253+TEST_TARGET_IPERF
254+
255+example config file
256+===================
257+[FTP]
258+
259+Target: 192.168.1.23
260+User: FTPUser
261+Pass:PassW0Rd
262+
263+[IPERF]
264+Target: 192.168.1.45
265+**NOTE**
266+
267+"""
268
269 parser = ArgumentParser(
270 description=intro_message, formatter_class=RawTextHelpFormatter)
271@@ -502,7 +583,7 @@
272 test_parser.add_argument(
273 '-i', '--interface', type=str, required=True)
274 test_parser.add_argument(
275- '-t', '--test_type', type=str,
276+ '-t', '--test_type', type=str,
277 choices=("ftp", "iperf", "stress"), default="ftp",
278 help=("[FTP *Default*]"))
279 test_parser.add_argument('--target', type=str)
280@@ -518,7 +599,6 @@
281 default="/etc/checkbox.d/network.cfg",
282 help="Supply config file for target/host network parameters")
283
284-
285 # Sub info options
286 info_parser.add_argument(
287 '-i', '--interface', type=str, required=True)
288
289=== modified file 'checkbox-old/scripts/virtualization'
290--- checkbox-old/scripts/virtualization 2013-12-12 19:35:38 +0000
291+++ checkbox-old/scripts/virtualization 2014-01-30 14:28:16 +0000
292@@ -3,10 +3,11 @@
293 """
294 Script to test virtualization functionality
295
296-Copyright (C) 2013 Canonical Ltd.
297+Copyright (C) 2013, 2014 Canonical Ltd.
298
299 Authors
300 Jeff Marcom <jeff.marcom@canonical.com>
301+ Daniel Manrique <roadmr@ubuntu.com>
302
303 This program is free software: you can redistribute it and/or modify
304 it under the terms of the GNU General Public License version 3,
305@@ -31,9 +32,9 @@
306 import shlex
307 import signal
308 from subprocess import (
309- Popen,
310- PIPE,
311- CalledProcessError,
312+ Popen,
313+ PIPE,
314+ CalledProcessError,
315 check_output,
316 call
317 )
318@@ -43,6 +44,8 @@
319 import time
320 import urllib.request
321
322+DEFAULT_TIMEOUT = 500
323+
324
325 class XENTest(object):
326 pass
327@@ -106,19 +109,18 @@
328 netrange = "10.0.0.0/8"
329 image_ip = "10.0.0.1"
330 hostfwd = "tcp::2222-:22"
331- cloud_disk = ""
332+ cloud_disk = ""
333
334 # Should we attach the cloud config disk
335 if os.path.isfile("seed.iso"):
336 logging.debug("Attaching Cloud config disk")
337 cloud_disk = "-drive file=seed.iso,if=virtio"
338
339-
340 if re.match("(arm.*|aarch64)", self.arch):
341- uname = check_output(['uname','-r'], universal_newlines=True)
342+ uname = check_output(['uname', '-r'], universal_newlines=True)
343 cloud_disk = cloud_disk.replace("virtio", "sd")
344- params = 'qemu-system-arm -machine vexpress-a15 -cpu cortex-a15 -enable-kvm -m {} -kernel /boot/vmlinuz -append "console=ttyAMA0 earlyprintk=serial root=/dev/mmcblk0 ro rootfstype=ext4" -serial stdio -dtb /lib/firmware/{}/device-tree/vexpress-v2p-ca15-tc1.dtb -initrd /boot/initrd.img -net nic -net user,net={},host={},hostfwd={} -drive file={},if=sd,cache=writeback {} -display none -nographic'.format(uname, "256", netrange,image_ip, hostfwd, data_disk, cloud_disk)
345- else:
346+ params = 'qemu-system-arm -machine vexpress-a15 -cpu cortex-a15 -enable-kvm -m {} -kernel /boot/vmlinuz -append "console=ttyAMA0 earlyprintk=serial root=/dev/mmcblk0 ro rootfstype=ext4" -serial stdio -dtb /lib/firmware/{}/device-tree/vexpress-v2p-ca15-tc1.dtb -initrd /boot/initrd.img -net nic -net user,net={},host={},hostfwd={} -drive file={},if=sd,cache=writeback {} -display none -nographic'.format(uname, "256", netrange, image_ip, hostfwd, data_disk, cloud_disk)
347+ else:
348 params = \
349 '''
350 qemu-system-x86_64 -machine accel=kvm:tcg -m {0} -net nic
351@@ -185,13 +187,12 @@
352 # Create Data ISO hosting user & meta cloud config data
353 try:
354 iso_build = check_output(
355- ['genisoimage', '-output', 'seed.iso', '-volid',
356- 'cidata', '-joliet', '-rock', 'user-data', 'meta-data'],
357+ ['genisoimage', '-output', 'seed.iso', '-volid',
358+ 'cidata', '-joliet', '-rock', 'user-data', 'meta-data'],
359 universal_newlines=True)
360 except CalledProcessError as exception:
361 logging.exception("Cloud data disk creation failed")
362
363-
364 def start(self):
365 logging.debug('Starting KVM Test')
366 status = 1
367@@ -246,6 +247,8 @@
368 image = ""
369 timeout = ""
370
371+ # Configuration data can come from three sources.
372+ # Lowest priority is the config file.
373 config_file = DEFAULT_CFG
374 config = configparser.SafeConfigParser()
375
376@@ -254,22 +257,35 @@
377 except IOError:
378 logging.warn("No config file found")
379 else:
380- try:
381+ try:
382 timeout = config.getfloat("KVM", "timeout")
383- except ValueError:
384+ except ValueError:
385 logging.warning('Invalid or Empty timeout in config file. '
386- 'Falling back to default')
387+ 'Falling back to default')
388 except configparser.NoSectionError as e:
389 logging.exception(e)
390-
391+
392 try:
393 image = config.get("KVM", "image")
394 except configparser.NoSectionError:
395 logging.exception('Invalid or Empty image in config file.')
396
397- if timeout == "":
398+ # Next in priority are environment variables.
399+ if 'KVM_TIMEOUT' in os.environ:
400+ try:
401+ timeout = float(os.environ['KVM_TIMEOUT'])
402+ except ValueError as err:
403+ logging.warning("TIMEOUT env variable: %s" % err)
404+ timeout = DEFAULT_TIMEOUT
405+ if 'KVM_IMAGE' in os.environ:
406+ image = os.environ['KVM_IMAGE']
407+
408+ # Finally, highest-priority are command line arguments.
409+ if args.timeout:
410 timeout = args.timeout
411- if image == "":
412+ elif not timeout:
413+ timeout = DEFAULT_TIMEOUT
414+ if args.image:
415 image = args.image
416
417 kvm_test = KVMTest(image, timeout)
418@@ -294,24 +310,24 @@
419 kvm_test_parser.add_argument(
420 '-i', '--image', type=str, default=None)
421 kvm_test_parser.add_argument(
422- '-t', '--timeout', type=int, default=500)
423+ '-t', '--timeout', type=int)
424 kvm_test_parser.add_argument(
425 '--debug', action="store_true")
426 kvm_test_parser.set_defaults(func=test_kvm)
427
428 args = parser.parse_args()
429-
430+
431 try:
432 if args.debug:
433 logging.basicConfig(level=logging.DEBUG)
434 except AttributeError:
435- pass #avoids exception when trying to run without specifying 'kvm'
436-
437- #to check if not len(sys.argv) > 1
438+ pass # avoids exception when trying to run without specifying 'kvm'
439+
440+ # to check if not len(sys.argv) > 1
441 if len(vars(args)) == 0:
442 parser.print_help()
443 return False
444-
445+
446 args.func(args)
447
448 if __name__ == "__main__":

Subscribers

People subscribed via source and target branches