Merge lp:~pfalcon/linaro-ci-dashboard/kernel_chain into lp:linaro-ci-dashboard

Proposed by Paul Sokolovsky
Status: Merged
Merged at revision: 68
Proposed branch: lp:~pfalcon/linaro-ci-dashboard/kernel_chain
Merge into: lp:linaro-ci-dashboard
Diff against target: 457 lines (+238/-13)
15 files modified
config_template/jenkins-jobs/AndroidLoop.xml (+1/-1)
config_template/jenkins-jobs/HwpackLoop.xml (+1/-1)
config_template/jenkins-jobs/IntegrationLoop.xml (+6/-1)
config_template/jenkins-jobs/KernelLoop.xml (+1/-1)
dashboard/frontend/hwpack_loop/models/hwpack_loop.py (+1/-1)
dashboard/frontend/kernel_build/models/kernel_loop.py (+20/-0)
dashboard/frontend/kernel_build/tests/__init__.py (+2/-0)
dashboard/frontend/kernel_build/tests/test_kernel_chaining.py (+70/-0)
dashboard/frontend/models/loop.py (+20/-2)
dashboard/frontend/models/loop_build.py (+38/-0)
dashboard/frontend/models/loop_reference.py (+1/-0)
dashboard/jenkinsserver/models/jenkins_server.py (+36/-5)
dashboard/jenkinsserver/tests/test_jenkins_server.py (+35/-0)
dashboard/lib/jenkins_dashboard.py (+2/-1)
dashboard/settings.py (+4/-0)
To merge this branch: bzr merge lp:~pfalcon/linaro-ci-dashboard/kernel_chain
Reviewer Review Type Date Requested Status
Stevan Radaković Approve
Linaro Infrastructure Pending
Review via email: mp+125646@code.launchpad.net

Description of the change

This is preliminary merge proposal for Kernel->Hwpack loop chaining implementation. This so far lacks tests, on which I'm working in the meantime, but I'd like to collect any early feedback. As usual with more or less big changesets, it may be more comfortable to review the changes commit by commit, instead of aggregated diff.

Otherwise, these changes already underwent some refactoring comparing to the initial implementation I coded, and should be pretty architecturally clean IMHO. The main change is to introduce Loop method process_build_results() which is called at the end of build to process build outcome and prepare build results which can be used by downsteam builds, and to add LoopBuild API which process_build_results() can use to query artifacts of a build (because artifacts is the only outcome of a build in Jenkins, so any "build results" would be produced by analyzing artifacts). Some other convenience changes (like providing default implementation of Loop.preconfigure()) were also introduced.

To post a comment you must log in.
69. By Paul Sokolovsky

[merge] Merge from trunk.

70. By Paul Sokolovsky

Typo fix.

Revision history for this message
Stevan Radaković (stevanr) wrote :

Nice work Paul, thanks!!

I have no minor things to note, architecture looks pretty good.
One thing I'd like to see though are tests. There aren't any for one single method we implemented here.

review: Needs Fixing
Revision history for this message
Milo Casagrande (milo) wrote :

Hi Paul!

Some comments follow inline.

On Fri, Sep 21, 2012 at 10:34 AM, Paul Sokolovsky
<email address hidden> wrote:
> https://code.launchpad.net/~pfalcon/linaro-ci-dashboard/kernel_chain/+merge/125646
> Your team Linaro Infrastructure is requested to review the proposed merge of lp:~pfalcon/linaro-ci-dashboard/kernel_chain into lp:linaro-ci-dashboard.
>
> === modified file 'config_template/jenkins-jobs/AndroidLoop.xml'
> --- config_template/jenkins-jobs/AndroidLoop.xml 2012-09-18 15:08:05 +0000
> +++ config_template/jenkins-jobs/AndroidLoop.xml 2012-09-21 08:33:27 +0000
> @@ -178,7 +178,7 @@
> </builders>
> <publishers>
> <hudson.tasks.ArtifactArchiver>
> - <artifacts>build/out/target/*/*/*.img.bz2,build/out/target/*/*/*.tar.bz2,build/out/target/*/*/MD5SUMS,build/out/*.tar.bz2,build/out/*.xml,build/out/*_config,build/out/lava-job-info,build/out/linaro_kernel_build_cmds.sh,build/out/linaro_android_build_cmds.sh,build/out/**/*EULA*</artifacts>
> + <artifacts>build/out/target/*/*/*.img.bz2,build/out/target/*/*/*.tar.bz2,build/out/target/*/*/MD5SUMS,build/out/*.tar.bz2,build/out/*.xml,build/out/*_config,build/out/lava-job-info,build/out/linaro_kernel_build_cmds.sh,build/out/linaro_android_build_cmds.sh,build/out/**/*EULA*,MANIFEST</artifacts>

Is it possible to be more greedy here? Like "*.img.*", so to avoid
file suffix, or there might be other file types that we do not want?
Minor suggestion though...

> @models.permalink
> def get_detail_url(self):
> return 'KernelLoopDetail', (), {'slug': self.name}
> @@ -135,3 +141,17 @@
> @models.permalink
> def get_update_url(self):
> return 'KernelLoopUpdate', (), {'slug': self.name}
> +
> + def process_build_results(self, loop_build):
> + """This method gets called with list of artifact files from a build.
> + Its responsibility is to categorize artifacts, and prepare result data
> + to be passed to downstream loop in chain. If needed, this method can
> + access content of artifacts (not just list of them).
> + """
> + files = loop_build.list_artifacts()
> + kernel_debs = filter(lambda fname: "linux-image" in fname, files)
> + if len(kernel_debs) == 1:
> + return {"kernel_deb_url": loop_build.get_artifact_url(kernel_debs[0])}
> + else:
> + self.log.warning("No single kernel deb found among: %s", files)
> + return {}

This is more a personal thing than anything else: why not having only
a single exit point? Just using a single return statement.

Overall it looks good and clean, maybe a couple of tests wouldn't hurt.
Thanks!

--
Milo Casagrande
Infrastructure Engineer
Linaro.org <www.linaro.org> │ Open source software for ARM SoCs

71. By Paul Sokolovsky

preconfigure(): Check that argument is not None before iterating over it.

72. By Paul Sokolovsky

Factor out variable with Jenkins URL to ease debugging.

This regulalrly needs to be dumped to find out what's wrong with Jenkins.

73. By Paul Sokolovsky

Add integration test for for artifact querying methods of JenkinsServer.

This required making IntegrationLoop to export artifacts (also exposes
shell code injection with loop build scripts). TODO: Provide special Loop
just for integration testing.

74. By Paul Sokolovsky

Update another reference self.get_build_info() -> self.update_build_info()

75. By Paul Sokolovsky

self.remote_number can be None during tests, so print it as string.

76. By Paul Sokolovsky

Add high-level unit test for kernel => hwpack loop chaining.

77. By Paul Sokolovsky

[merge] Merge from trunk.

Revision history for this message
Paul Sokolovsky (pfalcon) wrote :

Milo:

> Is it possible to be more greedy here? Like "*.img.*", so to avoid
file suffix, or there might be other file types that we do not want?
Minor suggestion though...

Well, if you looked carefully, what I did to that line was adding "MANIFEST" file import. The original line likely was added by yourself while working on Android loop. But as it apparently was taken from a build config as set up be me, here's reply to the very question:

What about "1.img.something.completely.different" file? So yes, it's grounded conservatism imho. Of course, if there're good reasons to change that, we can.

>This is more a personal thing than anything else: why not having only
a single exit point? Just using a single return statement.

Single statement of course would be possible, but that would program transformation skewing the original idea of the code. It's because 2 branches have different semantics: one executes if data is valid and returns expected processed data, while another executes when data invalid and returns "data invalid" token. To make the point more clear, in the next incarnation of this function, return in "data invalid" branch may become raise.

Revision history for this message
Paul Sokolovsky (pfalcon) wrote :

Ok, so I finally get to complete tests for this. 2 tests were added:

1. Integrational test for new JenkinsServer methods to query artifacts.
2. Unit tests which mocks JenkinsServer methods and assures proper data flow between upstream and downstream tests in chain.

Revision history for this message
Stevan Radaković (stevanr) wrote :

Looks good to me Paul, thanks!
Approve +1

review: Approve
Revision history for this message
Milo Casagrande (milo) wrote :

Hi Paul,

thanks for fixing this, and also for the previous comments.
It looks good to me, I only have some errors when running the tests, but overall all the tests pass (total number is 96). Is that normal?

This is the kind of Python errors I see:
https://pastebin.linaro.org/991/

Revision history for this message
Stevan Radaković (stevanr) wrote :

Strange I don't get such errors. Let's check tomorrow with Paul.

Revision history for this message
Paul Sokolovsky (pfalcon) wrote :

Milo:

Here's my story: I didn't do work on ci-dashboard for a while, so when I restarted with work on these tested, I was greeted with the need to upgrade jenkins.war. I did, and then on first "python dashboard/manage.py test" run I got errors similar to those you quoted, except that in my case there were more errors "JenkinsException: create[testjob_integration] failed" and no "JenkinsException: build[2] does not exist for job test". I almost prepared to scratch my head and blame spurious jenkins upgrades that we have so far, but then I decided just to rerun the testsuite. Those errors were gone. There was another one though:

====
FAIL: test_login_teams (django_openid_auth.tests.test_views.RelyingPartyTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/pymodules/python2.7/django_openid_auth/tests/test_views.py", line 359, in test_login_teams
    self.assertTrue(group in user.groups.all())
AssertionError: False is not true
====

But that's known by all of us (?) heizenbug, except that it has much lower oscillation frequency (i.e. I have it stable, including with the trunk now even with several reruns, tomorrow it may be gone, day after appear again).

So, answering your question: it's "normal" (in quotes), in the sense that it's matter of fact that it does happen for more than one developer. It's not normal in other senses of this word, and we should take measures to investigate and fix those non-deterministic failures. First measure would be to use well-known jenkins version, instead of non-deterministic one - I proposed that some time ago, but didn't make that change at that time, so now it seems time has come.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'config_template/jenkins-jobs/AndroidLoop.xml'
--- config_template/jenkins-jobs/AndroidLoop.xml 2012-09-18 15:08:05 +0000
+++ config_template/jenkins-jobs/AndroidLoop.xml 2012-10-04 15:43:21 +0000
@@ -178,7 +178,7 @@
178 </builders>178 </builders>
179 <publishers>179 <publishers>
180 <hudson.tasks.ArtifactArchiver>180 <hudson.tasks.ArtifactArchiver>
181 <artifacts>build/out/target/*/*/*.img.bz2,build/out/target/*/*/*.tar.bz2,build/out/target/*/*/MD5SUMS,build/out/*.tar.bz2,build/out/*.xml,build/out/*_config,build/out/lava-job-info,build/out/linaro_kernel_build_cmds.sh,build/out/linaro_android_build_cmds.sh,build/out/**/*EULA*</artifacts>181 <artifacts>build/out/target/*/*/*.img.bz2,build/out/target/*/*/*.tar.bz2,build/out/target/*/*/MD5SUMS,build/out/*.tar.bz2,build/out/*.xml,build/out/*_config,build/out/lava-job-info,build/out/linaro_kernel_build_cmds.sh,build/out/linaro_android_build_cmds.sh,build/out/**/*EULA*,MANIFEST</artifacts>
182 <latestOnly>false</latestOnly>182 <latestOnly>false</latestOnly>
183 </hudson.tasks.ArtifactArchiver>183 </hudson.tasks.ArtifactArchiver>
184 <hudson.plugins.logparser.LogParserPublisher>184 <hudson.plugins.logparser.LogParserPublisher>
185185
=== modified file 'config_template/jenkins-jobs/HwpackLoop.xml'
--- config_template/jenkins-jobs/HwpackLoop.xml 2012-09-18 14:44:17 +0000
+++ config_template/jenkins-jobs/HwpackLoop.xml 2012-10-04 15:43:21 +0000
@@ -19,7 +19,7 @@
19 </builders>19 </builders>
20 <publishers>20 <publishers>
21 <hudson.tasks.ArtifactArchiver>21 <hudson.tasks.ArtifactArchiver>
22 <artifacts>*.tar.gz</artifacts>22 <artifacts>*.tar.gz,MANIFEST</artifacts>
23 <latestOnly>false</latestOnly>23 <latestOnly>false</latestOnly>
24 </hudson.tasks.ArtifactArchiver>24 </hudson.tasks.ArtifactArchiver>
25 </publishers>25 </publishers>
2626
=== modified file 'config_template/jenkins-jobs/IntegrationLoop.xml'
--- config_template/jenkins-jobs/IntegrationLoop.xml 2012-09-17 10:27:00 +0000
+++ config_template/jenkins-jobs/IntegrationLoop.xml 2012-10-04 15:43:21 +0000
@@ -17,6 +17,11 @@
17 <command>{build_script}</command>17 <command>{build_script}</command>
18 </hudson.tasks.Shell>18 </hudson.tasks.Shell>
19 </builders>19 </builders>
20 <publishers/>20 <publishers>
21 <hudson.tasks.ArtifactArchiver>
22 <artifacts>*.result,MANIFEST</artifacts>
23 <latestOnly>false</latestOnly>
24 </hudson.tasks.ArtifactArchiver>
25 </publishers>
21 <buildWrappers/>26 <buildWrappers/>
22</project>27</project>
2328
=== modified file 'config_template/jenkins-jobs/KernelLoop.xml'
--- config_template/jenkins-jobs/KernelLoop.xml 2012-09-18 13:29:37 +0000
+++ config_template/jenkins-jobs/KernelLoop.xml 2012-10-04 15:43:21 +0000
@@ -19,7 +19,7 @@
19 </builders>19 </builders>
20 <publishers>20 <publishers>
21 <hudson.tasks.ArtifactArchiver>21 <hudson.tasks.ArtifactArchiver>
22 <artifacts>*.deb</artifacts>22 <artifacts>*.deb,MANIFEST</artifacts>
23 <latestOnly>false</latestOnly>23 <latestOnly>false</latestOnly>
24 </hudson.tasks.ArtifactArchiver>24 </hudson.tasks.ArtifactArchiver>
25 </publishers>25 </publishers>
2626
=== modified file 'dashboard/frontend/hwpack_loop/models/hwpack_loop.py'
--- dashboard/frontend/hwpack_loop/models/hwpack_loop.py 2012-09-17 13:00:42 +0000
+++ dashboard/frontend/hwpack_loop/models/hwpack_loop.py 2012-10-04 15:43:21 +0000
@@ -32,7 +32,7 @@
3232
33 # Version of hwpack tarball to build (passed directly to33 # Version of hwpack tarball to build (passed directly to
34 # linaro-hwpack-create)34 # linaro-hwpack-create)
35 hwpack_build_version = models.CharField(max_length=15)35 hwpack_build_version = models.CharField(max_length=15, default="1")
3636
37 # URL of kernel deb package to use for hwpack creation37 # URL of kernel deb package to use for hwpack creation
38 kernel_deb_url = models.CharField(max_length=255, default="", blank=True)38 kernel_deb_url = models.CharField(max_length=255, default="", blank=True)
3939
=== modified file 'dashboard/frontend/kernel_build/models/kernel_loop.py'
--- dashboard/frontend/kernel_build/models/kernel_loop.py 2012-09-12 12:48:46 +0000
+++ dashboard/frontend/kernel_build/models/kernel_loop.py 2012-10-04 15:43:21 +0000
@@ -19,6 +19,7 @@
19from django.db import models19from django.db import models
20from frontend.models.loop import Loop20from frontend.models.loop import Loop
21from jenkinsserver.models.jenkins_server import JenkinsServer21from jenkinsserver.models.jenkins_server import JenkinsServer
22from frontend.models.loop_reference import HWPACK_LOOP
2223
2324
24LLT_URL = (r'git://git.linaro.org/kernel/linux-linaro-tracking.git',25LLT_URL = (r'git://git.linaro.org/kernel/linux-linaro-tracking.git',
@@ -124,6 +125,11 @@
124 # TODO: Log error.125 # TODO: Log error.
125 pass126 pass
126127
128 @staticmethod
129 def can_chain_into():
130 chain = [HWPACK_LOOP]
131 return chain
132
127 @models.permalink133 @models.permalink
128 def get_detail_url(self):134 def get_detail_url(self):
129 return 'KernelLoopDetail', (), {'slug': self.name}135 return 'KernelLoopDetail', (), {'slug': self.name}
@@ -135,3 +141,17 @@
135 @models.permalink141 @models.permalink
136 def get_update_url(self):142 def get_update_url(self):
137 return 'KernelLoopUpdate', (), {'slug': self.name}143 return 'KernelLoopUpdate', (), {'slug': self.name}
144
145 def process_build_results(self, loop_build):
146 """This method gets called with list of artifact files from a build.
147 Its responsibility is to categorize artifacts, and prepare result data
148 to be passed to downstream loop in chain. If needed, this method can
149 access content of artifacts (not just list of them).
150 """
151 files = loop_build.list_artifacts()
152 kernel_debs = filter(lambda fname: "linux-image" in fname, files)
153 if len(kernel_debs) == 1:
154 return {"kernel_deb_url": loop_build.get_artifact_url(kernel_debs[0])}
155 else:
156 self.log.warning("No single kernel deb found among: %s", files)
157 return {}
138158
=== modified file 'dashboard/frontend/kernel_build/tests/__init__.py'
--- dashboard/frontend/kernel_build/tests/__init__.py 2012-08-23 11:22:21 +0000
+++ dashboard/frontend/kernel_build/tests/__init__.py 2012-10-04 15:43:21 +0000
@@ -1,9 +1,11 @@
1from dashboard.frontend.kernel_build.tests.test_kernel_build_views import *1from dashboard.frontend.kernel_build.tests.test_kernel_build_views import *
2from dashboard.frontend.kernel_build.tests.test_kernel_build_clientresponse \2from dashboard.frontend.kernel_build.tests.test_kernel_build_clientresponse \
3 import *3 import *
4from dashboard.frontend.kernel_build.tests.test_kernel_chaining import *
45
56
6__test__ = {7__test__ = {
7 'KernelBuildViewsTest': KernelBuildViewsTest,8 'KernelBuildViewsTest': KernelBuildViewsTest,
8 'KernelBuildClientResponseTests': KernelBuildClientResponseTests,9 'KernelBuildClientResponseTests': KernelBuildClientResponseTests,
10 'KernelHwpackChainingTest': KernelHwpackChainingTest,
9}11}
1012
=== added file 'dashboard/frontend/kernel_build/tests/test_kernel_chaining.py'
--- dashboard/frontend/kernel_build/tests/test_kernel_chaining.py 1970-01-01 00:00:00 +0000
+++ dashboard/frontend/kernel_build/tests/test_kernel_chaining.py 2012-10-04 15:43:21 +0000
@@ -0,0 +1,70 @@
1# Copyright (C) 2012 Linaro
2#
3# This file is part of linaro-ci-dashboard.
4#
5# linaro-ci-dashboard is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Affero General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# linaro-ci-dashboard is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU Affero General Public License for more details.
14#
15# You should have received a copy of the GNU Affero General Public License
16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
17
18from django.test import TestCase
19from mock import patch
20
21from jenkinsserver.models.jenkins_server import JenkinsServer
22from frontend.models.loop_build import LoopBuild
23from frontend.kernel_build.models.kernel_loop import KernelLoop
24from frontend.hwpack_loop.models.hwpack_loop import HwpackLoop
25
26
27class KernelHwpackChainingTest(TestCase):
28
29 def setUp(self):
30 self.server = JenkinsServer.objects.get(id=1)
31
32 @patch('jenkinsserver.models.jenkins_server.JenkinsServer.create_or_update_loop')
33 @patch('jenkinsserver.models.jenkins_server.JenkinsServer.get_build_artifact_content')
34 @patch('lib.jenkins_dashboard.JenkinsDashboard.get_build_info')
35 def test_chaining(self, get_build_info_m, get_build_artifact_content_m, mck1):
36 kernel_package = "linux-image.deb"
37 get_build_info_m.return_value = {"result": LoopBuild.SUCCESS}
38 get_build_artifact_content_m.return_value = kernel_package + "\n"
39
40 hwpack_loop = HwpackLoop()
41 hwpack_loop.name = "hwpack_test"
42 hwpack_loop.server = self.server
43 hwpack_loop.save()
44 self.assertFalse(hwpack_loop.kernel_deb_url)
45
46 loop = KernelLoop()
47 loop.name = "kernel_test"
48 loop.server = self.server
49 loop.next_loop = hwpack_loop
50 loop.save()
51 build = loop.schedule_build()
52
53 self.server.sync_unfinished_builds()
54
55 # Reload object from DB in Django-speak
56 build = LoopBuild.objects.get(id=build.id)
57
58 # Make sure kernel build status/results were updated
59 self.assertEquals(build.status, LoopBuild.SUCCESS)
60 self.assertTrue(kernel_package in build.result_xml)
61
62 # Make sure results from kernel loop were propagated to hwpack
63 hwpack_loop = HwpackLoop.objects.get(id=hwpack_loop.id)
64 self.assertTrue(kernel_package in hwpack_loop.kernel_deb_url)
65
66 # Make sure that hwpack build was scheduled
67 self.assertEquals(len(LoopBuild.objects.all()), 2)
68 hwpack_build = LoopBuild.objects.get(loop=hwpack_loop)
69 self.assertTrue(hwpack_build)
70 self.assertEquals(hwpack_build.status, LoopBuild.SCHEDULED)
071
=== modified file 'dashboard/frontend/models/loop.py'
--- dashboard/frontend/models/loop.py 2012-09-20 13:51:27 +0000
+++ dashboard/frontend/models/loop.py 2012-10-04 15:43:21 +0000
@@ -259,8 +259,26 @@
259259
260 def preconfigure(self, configuration=None):260 def preconfigure(self, configuration=None):
261 """Makes a build parameter dict from build result configuration.261 """Makes a build parameter dict from build result configuration.
262262 Default implementation just assigns loop's attributes from dictionary
263 This method should be overridden by subclasses.263 keys. This method may be overridden by subclasses.
264
265 :return Extra Jenkins build scheduling parameters. TODO: breaks separation
266 """
267 if configuration:
268 for key, value in configuration.iteritems():
269 if hasattr(self, key):
270 setattr(self, key, value)
271 else:
272 self.log.warning("Input build parameters for %s contain "\
273 "unknown key/value pair %s=%s", self, key, value)
274 self.save()
275 return {}
276
277 def process_build_results(self, loop_build):
278 """This method gets called with list of artifact files from a build.
279 Its responsibility is to categorize artifacts, and prepare result data
280 to be passed to downstream loop in chain. If needed, this method can
281 access content of artifacts (not just list of them).
264 """282 """
265 return {}283 return {}
266284
267285
=== modified file 'dashboard/frontend/models/loop_build.py'
--- dashboard/frontend/models/loop_build.py 2012-09-19 10:50:22 +0000
+++ dashboard/frontend/models/loop_build.py 2012-10-04 15:43:21 +0000
@@ -84,3 +84,41 @@
84 def build_finished(self):84 def build_finished(self):
85 """Method to be called after the build is executed."""85 """Method to be called after the build is executed."""
86 self.loop.continue_down_the_chain(self.get_build_result())86 self.loop.continue_down_the_chain(self.get_build_result())
87
88 def list_artifacts(self):
89 """
90 List artifacts as produced by the build.
91
92 :return List containing full paths of artifacts files
93 """
94 return self.loop.server.list_build_artifacts(self.loop.name,
95 self.remote_number)
96
97 def get_artifact_url(self, artifact_name):
98 """
99 Get a URL from which given artifact can be downloaded (for example,
100 for passing to a build script).
101
102 :param artifact_name Full artifact path, as returned by
103 list_artifacts()
104 :return A URL
105 """
106 return self.loop.server.get_build_artifact_url(self.loop.name,
107 self.remote_number, artifact_name)
108
109 def get_artifact_content(self, artifact_name):
110 """
111 Get contents of an artifact. This function loads entire artifact
112 into memory, so should not be used with big artifacts.
113
114 :param artifact_name Full artifact path, as returned by
115 list_artifacts()
116 :return Raw binary data of artifact
117 """
118 return self.loop.server.get_build_artifact_url(self.loop.name,
119 self.remote_number, artifact_name)
120
121 def __repr__(self):
122 return "%s(id=%s, loop.name=%s, remote_numer=%s)" % \
123 (self.__class__.__name__, self.id, self.loop.name,
124 self.remote_number)
87125
=== modified file 'dashboard/frontend/models/loop_reference.py'
--- dashboard/frontend/models/loop_reference.py 2012-08-31 11:53:01 +0000
+++ dashboard/frontend/models/loop_reference.py 2012-10-04 15:43:21 +0000
@@ -22,3 +22,4 @@
22KERNEL_LOOP = ('KernelLoop')22KERNEL_LOOP = ('KernelLoop')
23INTEGRATION_LOOP = ('IntegrationLoop')23INTEGRATION_LOOP = ('IntegrationLoop')
24ANDROID_TEXTFIELD_LOOP = ('AndroidTextFieldLoop')24ANDROID_TEXTFIELD_LOOP = ('AndroidTextFieldLoop')
25HWPACK_LOOP = ('HwpackLoop')
2526
=== modified file 'dashboard/jenkinsserver/models/jenkins_server.py'
--- dashboard/jenkinsserver/models/jenkins_server.py 2012-09-18 16:01:01 +0000
+++ dashboard/jenkinsserver/models/jenkins_server.py 2012-10-04 15:43:21 +0000
@@ -16,11 +16,14 @@
16# You should have received a copy of the GNU Affero General Public License16# You should have received a copy of the GNU Affero General Public License
17# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.17# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1818
19import urllib2
20
19from django.db import models21from django.db import models
20from django.conf import settings22from django.conf import settings
21from lib.jenkins_dashboard import JenkinsDashboard23from lib.jenkins_dashboard import JenkinsDashboard
22from lib.logger import Logger24from lib.logger import Logger
23from lib.template import TextTemplate, XMLTemplate25from lib.template import TextTemplate, XMLTemplate
26from dashboard.lib.xml_to_dict import DictToXml
2427
2528
26class JenkinsServer(models.Model):29class JenkinsServer(models.Model):
@@ -71,9 +74,9 @@
71 # update the build info in CI.74 # update the build info in CI.
72 if build.remote_number and \75 if build.remote_number and \
73 jenkins_build["number"] == build.remote_number:76 jenkins_build["number"] == build.remote_number:
74 self.get_build_info(build)77 self.update_build_info(build)
7578
76 def get_build_info(self, build):79 def update_build_info(self, build):
77 jenkins_build = self.jenkins.get_build_info(build.loop.name,80 jenkins_build = self.jenkins.get_build_info(build.loop.name,
78 build.remote_number)81 build.remote_number)
79 # Duration in jenkins is presented in milliseconds82 # Duration in jenkins is presented in milliseconds
@@ -82,18 +85,22 @@
8285
83 if jenkins_build.get("result", None):86 if jenkins_build.get("result", None):
84 build.status = jenkins_build.get("result", None).lower()87 build.status = jenkins_build.get("result", None).lower()
85 # TODO: Add build result xml, but from where?88 # Let loop process the build outcome and extract anything relevant
89 # to store as build results.
90 loop = build.loop.get_child_object()
91 values = loop.process_build_results(build)
92 build.result_xml = DictToXml(values).dict_to_tree()
86 build.save()93 build.save()
8794
88 def sync_unfinished_builds(self):95 def sync_unfinished_builds(self):
89 from frontend.models.loop_build import LoopBuild96 from frontend.models.loop_build import LoopBuild
90 builds = LoopBuild.objects.filter(97 builds = LoopBuild.objects.filter(
91 status__in=LoopBuild.NON_FINISHED_STATUSES)98 status__in=LoopBuild.NON_FINISHED_STATUSES).order_by('id')
92 for build in builds:99 for build in builds:
93 # We don't wont the sync proces to fail if there is no particular100 # We don't wont the sync proces to fail if there is no particular
94 # loop present in jenkins. Log the error and continue.101 # loop present in jenkins. Log the error and continue.
95 try:102 try:
96 self.get_build_info(build)103 self.update_build_info(build)
97 except:104 except:
98 self.log.exception("build[%s] does not exist for job %s" % (build.remote_number, build.loop.name))105 self.log.exception("build[%s] does not exist for job %s" % (build.remote_number, build.loop.name))
99 pass106 pass
@@ -148,3 +155,27 @@
148 self.jenkins.build_job(jobname, parameters)155 self.jenkins.build_job(jobname, parameters)
149 job_info = self.jenkins.get_job_info(jobname)156 job_info = self.jenkins.get_job_info(jobname)
150 return job_info["nextBuildNumber"]157 return job_info["nextBuildNumber"]
158
159 def get_build_artifact_url(self, jobname, build_no, artifact_name):
160 url = settings.ARTIFACT_TEMPLATE_URL % locals()
161 return url
162
163 def get_build_artifact_content(self, jobname, build_no, artifact_name):
164 "Get contents of a build artifact."
165 url = self.get_build_artifact_url(jobname, build_no, artifact_name)
166 response = urllib2.urlopen(url)
167 contents = response.read()
168 response.close()
169 return contents
170
171 def list_build_artifacts(self, jobname, build_no):
172 try:
173 manifest = self.get_build_artifact_content(jobname, build_no, "MANIFEST")
174 except urllib2.HTTPError, e:
175 if e.code == 404:
176 self.log.warn("MANIFEST artifact not found for %s/%s, assumed empty", jobname, build_no)
177 manifest = ""
178 else:
179 raise
180 files = [f for f in manifest.split("\n") if f]
181 return files
151182
=== modified file 'dashboard/jenkinsserver/tests/test_jenkins_server.py'
--- dashboard/jenkinsserver/tests/test_jenkins_server.py 2012-09-18 16:01:01 +0000
+++ dashboard/jenkinsserver/tests/test_jenkins_server.py 2012-10-04 15:43:21 +0000
@@ -15,9 +15,12 @@
15#15#
16# You should have received a copy of the GNU Affero General Public License16# You should have received a copy of the GNU Affero General Public License
17# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.17# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
18import time
19
18from django.test import TestCase20from django.test import TestCase
19from jenkinsserver.models.jenkins_server import JenkinsServer21from jenkinsserver.models.jenkins_server import JenkinsServer
20import jenkins22import jenkins
23from frontend.models.loop_build import LoopBuild
21from frontend.integration_loop.models.integration_loop import IntegrationLoop24from frontend.integration_loop.models.integration_loop import IntegrationLoop
2225
2326
@@ -118,3 +121,35 @@
118 self.assertIsNotNone(build.duration)121 self.assertIsNotNone(build.duration)
119 self.assertIsNot(build.duration, 0.0)122 self.assertIsNot(build.duration, 0.0)
120 self.server.delete_job(self.test_job_name)123 self.server.delete_job(self.test_job_name)
124
125 def test_build_artifacts(self):
126 integration_loop = IntegrationLoop()
127 integration_loop.name = self.test_job_name
128 integration_loop.server = self.server
129 integration_loop.branch = 'none:none || mkdir -p '
130 integration_loop.precommand = 'cd ..; echo "foobar" >file.result'
131 integration_loop.command = 'ls -1 *.result >MANIFEST'
132 integration_loop.save()
133 build = integration_loop.schedule_build()
134 for i in range(10):
135 self.server.sync_unfinished_builds()
136 # Reload object from DB in Django-speak
137 build = LoopBuild.objects.get(id=build.id)
138 if build.status not in (LoopBuild.SCHEDULED, LoopBuild.RUNNING):
139 break
140 time.sleep(2)
141 self.assertEquals(build.status, LoopBuild.SUCCESS)
142 # We could test LoopBuild methods here, that would give us even more
143 # coverage, but - we're testing JenkinsServer here, so let's test it.
144 artifacts = self.server.list_build_artifacts(integration_loop.name,
145 build.remote_number)
146 self.assertEquals(artifacts, ["file.result"])
147
148 url = self.server.get_build_artifact_url(integration_loop.name,
149 build.remote_number, artifacts[0])
150 self.assertTrue(url.startswith("http://"))
151 self.assertTrue(artifacts[0] in url)
152
153 data = self.server.get_build_artifact_content(integration_loop.name,
154 build.remote_number, artifacts[0])
155 self.assertEquals(data, "foobar\n")
121156
=== modified file 'dashboard/lib/jenkins_dashboard.py'
--- dashboard/lib/jenkins_dashboard.py 2012-09-18 15:06:02 +0000
+++ dashboard/lib/jenkins_dashboard.py 2012-10-04 15:43:21 +0000
@@ -30,8 +30,9 @@
30 def get_build_info(self, name, build_number):30 def get_build_info(self, name, build_number):
3131
32 try:32 try:
33 url = self.server + self.BUILD_INFO % locals()
33 response = self.jenkins_open(34 response = self.jenkins_open(
34 urllib2.Request(self.server + self.BUILD_INFO % locals()))35 urllib2.Request(url))
35 if response:36 if response:
36 return json.loads(response)37 return json.loads(response)
37 else:38 else:
3839
=== modified file 'dashboard/settings.py'
--- dashboard/settings.py 2012-09-25 08:07:10 +0000
+++ dashboard/settings.py 2012-10-04 15:43:21 +0000
@@ -227,6 +227,10 @@
227JENKINS_LOG_NAME = 'jenkins.log'227JENKINS_LOG_NAME = 'jenkins.log'
228JENKINS_DB_ID = 1228JENKINS_DB_ID = 1
229229
230# URL from which build artifacts can be downloaded
231ARTIFACT_TEMPLATE_URL = "http://localhost:" + JENKINS_PORT + \
232 "/jenkins/job/%(jobname)s/%(build_no)s/artifact/%(artifact_name)s"
233
230# LAVA specific values234# LAVA specific values
231LAVA_URL = 'validation.linaro.org/lava-server/RPC2/'235LAVA_URL = 'validation.linaro.org/lava-server/RPC2/'
232LAVA_ACCEPTING_ADDRESS = '0.0.0.0'236LAVA_ACCEPTING_ADDRESS = '0.0.0.0'

Subscribers

People subscribed via source and target branches