Merge lp:~nuclearbob/utah/smoketest-setup-changes into lp:utah

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
Reviewer Review Type Date Requested Status
Joe Talbott (community) Approve
Review via email: mp+135232@code.launchpad.net

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
Joe Talbott (joetalbott) wrote :

This looks good to me.

review: Approve
Revision history for this message
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>

Subscribers

People subscribed via source and target branches