Merge lp:~nuclearbob/utah/smoketest-setup-changes into lp:utah
- smoketest-setup-changes
- Merge into dev
Proposed by
Max Brustkern
Status: | Merged |
---|---|
Approved by: | Joe Talbott |
Approved revision: | 29 |
Merge reported by: | Max Brustkern |
Merged at revision: | not available |
Proposed branch: | lp:~nuclearbob/utah/smoketest-setup-changes |
Merge into: | lp:utah |
Diff against target: |
451 lines (+427/-0) 5 files modified
README (+37/-0) commandblock.tmpl (+11/-0) default.xml.tmpl (+82/-0) setup-jobs.py (+240/-0) smoke.xml.tmpl (+57/-0) |
To merge this branch: | bzr merge lp:~nuclearbob/utah/smoketest-setup-changes |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Joe Talbott (community) | Approve | ||
Review via email:
|
Commit message
Description of the change
This branch adds better error handling to the smoke test log collection and changes job names.
To post a comment you must log in.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Max Brustkern (nuclearbob) wrote : | # |
Okay, it looks like I accidentally proposed this for merging in lp:utah, which we don't want. I'm going to close this and push the changes.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file 'README' |
2 | --- README 1970-01-01 00:00:00 +0000 |
3 | +++ README 2012-11-20 20:10:30 +0000 |
4 | @@ -0,0 +1,37 @@ |
5 | +This branch provides code for automated management of jenkins jobs from smoke |
6 | +testing branches. It currently supports two branch types. |
7 | + |
8 | +Branches with scripts directories, i.e.: |
9 | +lp:~javier.collado/ubuntu-test-cases/desktop/ |
10 | +For these, a job wil be created for each executable script in the scripts |
11 | +directory. |
12 | + |
13 | +Branches without scripts directories, i.e.: |
14 | +lp:~ubuntu-server-dev/utah/server-tests-quantal/ |
15 | +For these, a job will be created for each runlist in the runlists directory, |
16 | +using a preseed if one exists in the preseeds directory, falling back to the |
17 | +default preseed, if it exists. |
18 | + |
19 | +The main argument for the setup-jobs.py script is the local path to the branch |
20 | +(or branches) to use. Multiple paths can be provided, and will be processed |
21 | +separately. A current limitation is that the branches need to be checked out |
22 | +on the machine running the tests (i.e. aldebaran.) I would like to support |
23 | +remote branches eventually, but I think changes to the scripts in script-type |
24 | +branches will be necessary to facilitate this. Alternately, the job scripts |
25 | +could branch the branches themselves. For now, I run against checked out |
26 | +branches in /var/cache/utah on aldebaran. |
27 | + |
28 | +The script will try to guess the job type from the name of the branch, or you |
29 | +can pass in a job type with the -t argument. It will download images from |
30 | +imageurl (-i) which defaults to the path we use in the lab. the -j argument |
31 | +to supply a jenkins instance defaults to the 1SS instance. The -s argument |
32 | +for series will default to the current testing series as defined in python's |
33 | +distro_info. username and password are specific to the jenkins instance |
34 | +in use. |
35 | + |
36 | +For each image, a default job will be created from default.xml.tmpl. This job |
37 | +will download the image, perform static validation tests, and then run the |
38 | +default script/runlist if one exists. All other jobs (created from |
39 | +smoke.xml.tmpl) are triggered when the default job succeeds. Both jobs have |
40 | +their utah command populated by commandblock.tmpl. Log collection can also be |
41 | +setup here. |
42 | |
43 | === added file 'commandblock.tmpl' |
44 | --- commandblock.tmpl 1970-01-01 00:00:00 +0000 |
45 | +++ commandblock.tmpl 2012-11-20 20:10:30 +0000 |
46 | @@ -0,0 +1,11 @@ |
47 | + <hudson.tasks.Shell> |
48 | + <command>echo -n "Media Info: " > media_info |
49 | +bsdtar -x -f {isopath} -O \./.disk/info >> media_info |
50 | +echo >> media_info |
51 | +cat media_info |
52 | +set +e |
53 | +sudo -u utah -i {command} -x /etc/utah/bridged-network-vm.xml >log |
54 | +RETCODE=$? |
55 | +/usr/share/utah/examples/utah_logs.sh</command> |
56 | +exit $RETCODE |
57 | + </hudson.tasks.Shell> |
58 | |
59 | === added file 'default.xml.tmpl' |
60 | --- default.xml.tmpl 1970-01-01 00:00:00 +0000 |
61 | +++ default.xml.tmpl 2012-11-20 20:10:30 +0000 |
62 | @@ -0,0 +1,82 @@ |
63 | +<?xml version='1.0' encoding='UTF-8'?> |
64 | +<project> |
65 | + <actions/> |
66 | + <description>Download the latest {series} {installtype} {arch} image, validate it, and initiate smoke tests |
67 | +Automatically created as part of UTAH smoke testing</description> |
68 | + <keepDependencies>false</keepDependencies> |
69 | + <properties> |
70 | + <hudson.queueSorter.PrioritySorterJobProperty> |
71 | + <priority>100</priority> |
72 | + </hudson.queueSorter.PrioritySorterJobProperty> |
73 | + <hudson.plugins.throttleconcurrents.ThrottleJobProperty> |
74 | + <maxConcurrentPerNode>0</maxConcurrentPerNode> |
75 | + <maxConcurrentTotal>0</maxConcurrentTotal> |
76 | + <throttleEnabled>false</throttleEnabled> |
77 | + <throttleOption>project</throttleOption> |
78 | + </hudson.plugins.throttleconcurrents.ThrottleJobProperty> |
79 | + </properties> |
80 | + <scm class="hudson.scm.NullSCM"/> |
81 | + <assignedNode>aldebaran</assignedNode> |
82 | + <canRoam>false</canRoam> |
83 | + <disabled>false</disabled> |
84 | + <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding> |
85 | + <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding> |
86 | + <triggers class="vector"> |
87 | + <com.redfin.hudson.UrlChangeTrigger> |
88 | + <spec></spec> |
89 | + <url>{imageurl}/{series}/SHA256SUMS.{series}-{installtype}</url> |
90 | + </com.redfin.hudson.UrlChangeTrigger> |
91 | + </triggers> |
92 | + <concurrentBuild>false</concurrentBuild> |
93 | + <builders> |
94 | + <hudson.tasks.Shell> |
95 | + <command># Get rid of this once iso-download is in the stable package |
96 | +#sudo /var/lib/utah/fix-iso-perms.sh</command> |
97 | + </hudson.tasks.Shell> |
98 | + <hudson.tasks.Shell> |
99 | + <command>TMPISO=/tmp/utah-{series}-{installtype}-{arch}.iso |
100 | +wget -q {imageurl}/{series}/{series}-{installtype}-{arch}.iso -O $TMPISO |
101 | +chmod 644 $TMPISO |
102 | +sudo -u utah -i /usr/share/utah/isotest/iso_static_validation.py --name $TMPISO |
103 | +sudo -u utah -i cp $TMPISO {isopath} |
104 | +rm $TMPISO</command> |
105 | + </hudson.tasks.Shell> |
106 | + <hudson.tasks.Shell> |
107 | + <command>rm -rf *</command> |
108 | + </hudson.tasks.Shell> |
109 | +{commandblock} |
110 | + </builders> |
111 | + <publishers> |
112 | + <hudson.tasks.BuildTrigger> |
113 | + <childProjects>{jobs}</childProjects> |
114 | + <threshold> |
115 | + <name>SUCCESS</name> |
116 | + <ordinal>0</ordinal> |
117 | + <color>BLUE</color> |
118 | + </threshold> |
119 | + </hudson.tasks.BuildTrigger> |
120 | + <hudson.plugins.descriptionsetter.DescriptionSetterPublisher> |
121 | + <regexp>Media Info:.* \(([\d.]+)\)</regexp> |
122 | + <regexpForFailed>Media Info:.* \(([\d.]+)\)</regexpForFailed> |
123 | + <description>\1</description> |
124 | + <descriptionForFailed>\1</descriptionForFailed> |
125 | + <setForMatrix>false</setForMatrix> |
126 | + </hudson.plugins.descriptionsetter.DescriptionSetterPublisher> |
127 | + <hudson.plugins.build__publisher.BuildPublisher> |
128 | + <publishUnstableBuilds>true</publishUnstableBuilds> |
129 | + <publishFailedBuilds>true</publishFailedBuilds> |
130 | + <postActions class="vector"/> |
131 | + </hudson.plugins.build__publisher.BuildPublisher> |
132 | + <hudson.tasks.ArtifactArchiver> |
133 | + <artifacts>**/*</artifacts> |
134 | + <latestOnly>false</latestOnly> |
135 | + </hudson.tasks.ArtifactArchiver> |
136 | + </publishers> |
137 | + <buildWrappers> |
138 | + <EnvInjectBuildWrapper> |
139 | + <info> |
140 | + <loadFilesFromMaster>false</loadFilesFromMaster> |
141 | + </info> |
142 | + </EnvInjectBuildWrapper> |
143 | + </buildWrappers> |
144 | +</project> |
145 | |
146 | === added file 'setup-jobs.py' |
147 | --- setup-jobs.py 1970-01-01 00:00:00 +0000 |
148 | +++ setup-jobs.py 2012-11-20 20:10:30 +0000 |
149 | @@ -0,0 +1,240 @@ |
150 | +#!/usr/bin/python |
151 | +""" |
152 | +Update jenkins jobs for smoke testing |
153 | +Draws heavily from setup-jenkins-utah.py from |
154 | +lp:~ubuntu-server-iso-testing-dev/+junk/jenkins-qa-lab |
155 | +""" |
156 | +# TODO: fix static validation for raring in utah |
157 | +# TODO: customize local branch directory |
158 | +# TODO: add support for remote branches |
159 | +# TODO: get serial console logs |
160 | + |
161 | + |
162 | +import argparse |
163 | +import jenkins |
164 | +import logging |
165 | +import os |
166 | +import sys |
167 | + |
168 | +from distro_info import UbuntuDistroInfo |
169 | + |
170 | +DEV_SERIES = UbuntuDistroInfo().devel() |
171 | +ARCHES = ['i386', 'amd64'] |
172 | +INSTALLTYPES = ['desktop', 'server', 'mini', 'alternate'] |
173 | +ISODIR = os.path.join('/', 'var', 'cache', 'utah', 'iso') |
174 | + |
175 | + |
176 | +def get_parser(): |
177 | + parser = argparse.ArgumentParser( |
178 | + description=('Update smoke testing jenkins jobs.'), |
179 | + epilog=("For example:\n" |
180 | + "\t%(prog)s /path/to/branch -s " + DEV_SERIES + |
181 | + " -u user -p pass -j http://jenkins:8080" |
182 | + " -i http://10.98.0.1/iso/\n"), |
183 | + formatter_class=argparse.RawDescriptionHelpFormatter) |
184 | + parser.add_argument("branches", nargs='+', |
185 | + help="Path(s) to branch(es) to process") |
186 | + parser.add_argument("-t", "--type", help="Image type to use" |
187 | + " (if not supplied, guessed from branch name)") |
188 | + parser.add_argument("-d", "--debug", action="store_true", |
189 | + help="Enable debugging") |
190 | + parser.add_argument("-n", "--dryrun", action="store_true", |
191 | + help="Dry run mode. Don't execute jenkins commands") |
192 | + parser.add_argument("-s", "--series", default=DEV_SERIES, |
193 | + help="series of Ubuntu to download (default=%s)" % |
194 | + DEV_SERIES) |
195 | + parser.add_argument("-u", "--username", |
196 | + help="username to use when logging into Jenkins") |
197 | + parser.add_argument("-p", "--password", |
198 | + help="username to use when logging into Jenkins") |
199 | + parser.add_argument("-j", "--jenkins", default="http://10.189.74.2:8080/", |
200 | + help="URL of jenkins instance to configure test in") |
201 | + parser.add_argument("-i", "--imageurl", default="http://10.98.0.1/iso/", |
202 | + help="URL where images are located") |
203 | + return parser |
204 | + |
205 | + |
206 | +def update_jenkins(instance, dryrun, jobname, config): |
207 | + if not instance.job_exists(jobname): |
208 | + logging.debug("Creating Jenkins Job %s ", jobname) |
209 | + if dryrun: |
210 | + return True, '' |
211 | + else: |
212 | + try: |
213 | + instance.create_job(jobname, config) |
214 | + return True, '' |
215 | + except Exception as err: |
216 | + return False, str(err) |
217 | + else: |
218 | + logging.debug("Reconfiguring Jenkins Job %s ", jobname) |
219 | + if dryrun: |
220 | + return True, '' |
221 | + else: |
222 | + try: |
223 | + instance.reconfig_job(jobname, config) |
224 | + return True, '' |
225 | + except Exception as err: |
226 | + return False, str(err) |
227 | + |
228 | + |
229 | +def main(): |
230 | + args = get_parser().parse_args() |
231 | + if args.debug: |
232 | + logging.basicConfig(level=logging.DEBUG) |
233 | + else: |
234 | + logging.basicConfig(level=logging.INFO) |
235 | + |
236 | + logging.debug('Attempting to login to jenkins at ' + args.jenkins + '...') |
237 | + # Check to see if authentication information provided |
238 | + if args.username is not None: |
239 | + logging.debug('...with authentication as user ' + args.username) |
240 | + instance = jenkins.Jenkins(args.jenkins, |
241 | + username=args.username, |
242 | + password=args.password) |
243 | + else: |
244 | + logging.debug('...without authentication') |
245 | + instance = jenkins.Jenkins(args.jenkins) |
246 | + |
247 | + logging.info('Series: ' + args.series) |
248 | + logging.debug('Image directory: ' + ISODIR) |
249 | + with open('commandblock.tmpl') as template: |
250 | + commandblock = template.read() |
251 | + failures = [] |
252 | + for branch in args.branches: |
253 | + branchname = os.path.basename(os.path.realpath(branch.rstrip('/'))) |
254 | + installtype = None |
255 | + if args.type is None: |
256 | + logging.debug('Guessing installtype from branch') |
257 | + for checktype in INSTALLTYPES: |
258 | + if checktype in branchname: |
259 | + installtype = checktype |
260 | + break |
261 | + else: |
262 | + logging.debug('Using passed installtype: ' + args.type) |
263 | + installtype = args.type |
264 | + if installtype not in INSTALLTYPES: |
265 | + logging.error(installtype + ' is not a valid install type') |
266 | + logging.error('Skipping branch ' + branch) |
267 | + continue |
268 | + logging.info('Branch: ' + branch) |
269 | + logging.info('Type: ' + installtype) |
270 | + logging.info('Architectures to process: ' + ' '.join(ARCHES)) |
271 | + |
272 | + commands = [] |
273 | + scriptdir = os.path.join(branch, 'scripts') |
274 | + runlistdir = os.path.join(branch, 'runlists') |
275 | + isopath = ('{isoroot}/{series}-{installtype}-{arch}.iso' |
276 | + .format(isoroot=ISODIR, |
277 | + series=args.series, |
278 | + installtype=installtype, |
279 | + arch='{arch}')) |
280 | + if os.path.isdir(scriptdir): |
281 | + logging.debug('Processing scripts directory: ' + scriptdir) |
282 | + testdir = os.path.join('/', 'var', 'cache', 'utah', branchname, |
283 | + 'scripts') |
284 | + logging.debug('Test directory: ' + testdir) |
285 | + scripts = os.listdir(scriptdir) |
286 | + logging.debug('All scripts: ' + ' '.join(scripts)) |
287 | + for script in list(scripts): |
288 | + if os.access(os.path.join(branch, 'scripts', script), |
289 | + os.X_OK): |
290 | + logging.debug(script + ' is executable, using') |
291 | + scriptname = os.path.splitext(script)[0] |
292 | + command = ('$({testdir}/{test} -i {isopath})'.format( |
293 | + testdir=testdir, |
294 | + test=script, |
295 | + isopath=isopath)) |
296 | + commands.append([scriptname, commandblock.format( |
297 | + command=command, isopath=isopath)]) |
298 | + elif os.path.isdir(runlistdir): |
299 | + logging.debug('Processing runlists directory: ' + runlistdir) |
300 | + testdir = os.path.join('/', 'var', 'cache', 'utah', branchname) |
301 | + logging.debug('Test directory: ' + testdir) |
302 | + runlists = os.listdir(runlistdir) |
303 | + logging.debug('All runlists: ' + ' '.join(runlists)) |
304 | + for runlist in list(runlists): |
305 | + logging.debug('Processing runlist ' + runlist) |
306 | + runlistname = os.path.splitext(runlist)[0] |
307 | + if os.path.isfile(os.path.join(branch, 'preseeds', runlistname |
308 | + + '.preseed')): |
309 | + logging.debug('Found preseed for runlist') |
310 | + preseed_path = os.path.join(testdir, 'preseeds', |
311 | + runlistname + '.preseed') |
312 | + preseedblock = ('-p ' + preseed_path) |
313 | + elif os.path.isfile(os.path.join(branch, 'preseeds', |
314 | + 'default.preseed')): |
315 | + logging.debug('Using default preseed from branch') |
316 | + preseedblock = '-p ' + os.path.join(testdir, 'preseeds', |
317 | + 'default.preseed') |
318 | + else: |
319 | + logging.debug('No preseed found, using UTAH default') |
320 | + preseedblock = '' |
321 | + runlist_path = os.path.join(testdir, 'runlists', runlist) |
322 | + command = ('run_utah_tests.py {preseedblock} -i {isopath} ' |
323 | + '{runlist}' |
324 | + .format(preseedblock=preseedblock, |
325 | + isopath=isopath, |
326 | + runlist=runlist_path)) |
327 | + commands.append([runlistname, commandblock.format( |
328 | + command=command, isopath=isopath)]) |
329 | + else: |
330 | + logging.error(branch + ' does not contain a scripts directory ' |
331 | + 'or a runlists directory; skipping') |
332 | + continue |
333 | + |
334 | + for arch in ARCHES: |
335 | + logging.info('Processing architecture ' + arch) |
336 | + jobs = [] |
337 | + defaultjob = '' |
338 | + for name, commandblock in commands: |
339 | + if name == 'default': |
340 | + logging.info('Adding default runlist to default job') |
341 | + defaultjob = commandblock.format(arch=arch) |
342 | + continue |
343 | + jobname = '-'.join((args.series, installtype, arch, 'smoke', |
344 | + name)) |
345 | + logging.info('Processing job ' + jobname) |
346 | + archcommandblock = commandblock.format(arch=arch) |
347 | + with open('smoke.xml.tmpl') as template: |
348 | + smokexml = (template.read() |
349 | + .format(name=name, |
350 | + series=args.series, |
351 | + installtype=installtype, |
352 | + arch=arch, |
353 | + commandblock=archcommandblock)) |
354 | + result, text = update_jenkins(instance, args.dryrun, jobname, |
355 | + smokexml) |
356 | + if result: |
357 | + jobs.append(jobname) |
358 | + else: |
359 | + logging.debug(jobname + ' failed to configure: ' + text) |
360 | + failures.append(jobname) |
361 | + jobname = '-'.join((args.series, installtype, arch, 'smoke', |
362 | + 'default')) |
363 | + logging.info('Processing job ' + jobname) |
364 | + with open('default.xml.tmpl') as template: |
365 | + defaultxml = (template.read() |
366 | + .format(series=args.series, |
367 | + installtype=installtype, |
368 | + arch=arch, |
369 | + isopath=isopath.format(arch=arch), |
370 | + jobs=','.join(jobs), |
371 | + imageurl=args.imageurl, |
372 | + commandblock=defaultjob)) |
373 | + result, text = update_jenkins(instance, args.dryrun, jobname, |
374 | + defaultxml) |
375 | + if not result: |
376 | + logging.debug(jobname + ' failed to configure: ' + text) |
377 | + failures.append(jobname) |
378 | + |
379 | + if len(failures) == 0: |
380 | + logging.info('All jobs updated successfully') |
381 | + sys.exit(0) |
382 | + else: |
383 | + logging.info('These jobs failed to upadte:') |
384 | + for job in failures: |
385 | + logging.info(job) |
386 | + sys.exit(1) |
387 | + |
388 | +if __name__ == '__main__': |
389 | + main() |
390 | |
391 | === added file 'smoke.xml.tmpl' |
392 | --- smoke.xml.tmpl 1970-01-01 00:00:00 +0000 |
393 | +++ smoke.xml.tmpl 2012-11-20 20:10:30 +0000 |
394 | @@ -0,0 +1,57 @@ |
395 | +<?xml version='1.0' encoding='UTF-8'?> |
396 | +<project> |
397 | + <actions/> |
398 | + <description>Run the {name} test on the latest {series} {installtype} {arch} image |
399 | +Automatically created as part of UTAH smoke testing</description> |
400 | + <keepDependencies>false</keepDependencies> |
401 | + <properties> |
402 | + <hudson.queueSorter.PrioritySorterJobProperty> |
403 | + <priority>100</priority> |
404 | + </hudson.queueSorter.PrioritySorterJobProperty> |
405 | + <hudson.plugins.throttleconcurrents.ThrottleJobProperty> |
406 | + <maxConcurrentPerNode>0</maxConcurrentPerNode> |
407 | + <maxConcurrentTotal>0</maxConcurrentTotal> |
408 | + <throttleEnabled>false</throttleEnabled> |
409 | + <throttleOption>project</throttleOption> |
410 | + </hudson.plugins.throttleconcurrents.ThrottleJobProperty> |
411 | + </properties> |
412 | + <scm class="hudson.scm.NullSCM"/> |
413 | + <assignedNode>aldebaran</assignedNode> |
414 | + <canRoam>false</canRoam> |
415 | + <disabled>false</disabled> |
416 | + <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding> |
417 | + <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding> |
418 | + <triggers class="vector"/> |
419 | + <concurrentBuild>false</concurrentBuild> |
420 | + <builders> |
421 | + <hudson.tasks.Shell> |
422 | + <command>rm -rf *</command> |
423 | + </hudson.tasks.Shell> |
424 | +{commandblock} |
425 | + </builders> |
426 | + <publishers> |
427 | + <hudson.plugins.descriptionsetter.DescriptionSetterPublisher> |
428 | + <regexp>Media Info:.* \(([\d.]+)\)</regexp> |
429 | + <regexpForFailed>Media Info:.* \(([\d.]+)\)</regexpForFailed> |
430 | + <description>\1</description> |
431 | + <descriptionForFailed>\1</descriptionForFailed> |
432 | + <setForMatrix>false</setForMatrix> |
433 | + </hudson.plugins.descriptionsetter.DescriptionSetterPublisher> |
434 | + <hudson.plugins.build__publisher.BuildPublisher> |
435 | + <publishUnstableBuilds>true</publishUnstableBuilds> |
436 | + <publishFailedBuilds>true</publishFailedBuilds> |
437 | + <postActions class="vector"/> |
438 | + </hudson.plugins.build__publisher.BuildPublisher> |
439 | + <hudson.tasks.ArtifactArchiver> |
440 | + <artifacts>**/*</artifacts> |
441 | + <latestOnly>false</latestOnly> |
442 | + </hudson.tasks.ArtifactArchiver> |
443 | + </publishers> |
444 | + <buildWrappers> |
445 | + <EnvInjectBuildWrapper> |
446 | + <info> |
447 | + <loadFilesFromMaster>false</loadFilesFromMaster> |
448 | + </info> |
449 | + </EnvInjectBuildWrapper> |
450 | + </buildWrappers> |
451 | +</project> |
This looks good to me.