Merge lp:~adeuring/ci-director/bug-1365808 into lp:ci-director

Proposed by Abel Deuring
Status: Merged
Merged at revision: 132
Proposed branch: lp:~adeuring/ci-director/bug-1365808
Merge into: lp:ci-director
Diff against target: 195 lines (+59/-14)
3 files modified
cidirector/storage.py (+32/-13)
cidirector/tests/test_cidirector.py (+5/-1)
cidirector/tests/test_storage.py (+22/-0)
To merge this branch: bzr merge lp:~adeuring/ci-director/bug-1365808
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+236721@code.launchpad.net

Description of the change

A fix for bug 1365808: Exception when job number dosen't exit

This branch implements the suggestion from comment 4 of the bug.

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cidirector/storage.py'
2--- cidirector/storage.py 2014-09-30 09:02:07 +0000
3+++ cidirector/storage.py 2014-10-01 14:36:51 +0000
4@@ -4,7 +4,9 @@
5 from datetime import datetime
6 import errno
7 import fcntl
8+from jenkins import JenkinsException
9 import json
10+import logging
11 import os
12 import subprocess
13
14@@ -64,6 +66,7 @@
15 }
16 self._data = data
17 self._fileobj = fileobj
18+ self.logger = logging.getLogger('cidirector')
19
20 @classmethod
21 def from_file(cls, fileobj):
22@@ -165,7 +168,7 @@
23 # Handle legacy entries-- new entries will always have this set by
24 # start_revision()
25 build_data = version_data.setdefault('build', {'status': BUILDING})
26- return StateFileJob(BUILD_REVISION, build_data)
27+ return StateFileJob(BUILD_REVISION, build_data, logger=self.logger)
28
29 def publication_job(self):
30 """Return the publication StateFileJob for the current version.
31@@ -179,7 +182,8 @@
32 if job.test_id == PUBLISH_REVISION:
33 return job
34 return StateFileJob(
35- PUBLISH_REVISION, version_data.setdefault('publication', {}))
36+ PUBLISH_REVISION, version_data.setdefault('publication', {}),
37+ logger=self.logger)
38
39 def get_build_windows_installer_job(self):
40 """Return the StateFileJob for building the Windows installer."""
41@@ -210,11 +214,11 @@
42 return None
43 build_data = version_data.setdefault(
44 job_key, {'status': PENDING, 'failures': 0})
45- return StateFileJob(name, build_data, failure_threshold)
46+ return StateFileJob(
47+ name, build_data, failure_threshold, logger=self.logger)
48
49- @staticmethod
50- def _make_test_state_file_job(test_id, test_dict):
51- return StateFileJob(test_id, test_dict, 5)
52+ def _make_test_state_file_job(self, test_id, test_dict):
53+ return StateFileJob(test_id, test_dict, 5, logger=self.logger)
54
55 def get_grouped_job(self, job_id,
56 group_name=None, failure_threshold=None, tags=None):
57@@ -231,7 +235,8 @@
58 'failures': 0,
59 }
60 job_dict = group.setdefault(job_id, default)
61- return StateFileJob(job_id, job_dict, failure_threshold)
62+ return StateFileJob(
63+ job_id, job_dict, failure_threshold, logger=self.logger)
64
65 def iter_grouped_jobs(self):
66 """Iterate through all group StateFileJobs for the current version.
67@@ -245,12 +250,13 @@
68 if job_id == 'build' or not isinstance(job_dict, dict):
69 continue
70 if 'failures' in job_dict:
71- yield StateFileJob(job_id, job_dict, None)
72+ yield StateFileJob(job_id, job_dict, None, logger=self.logger)
73 else:
74 group = job_dict
75 for job_id, job_dict in group.items():
76 if isinstance(job_dict, dict) and 'failures' in job_dict:
77- yield StateFileJob(job_id, job_dict, None)
78+ yield StateFileJob(
79+ job_id, job_dict, None, logger=self.logger)
80
81 def get_test(self, test_id):
82 """Retrieve the StateFileJob for a given test."""
83@@ -279,7 +285,7 @@
84 unit_tests = self._get_job_section(revision_build, 'unit-tests')
85 return StateFileJob(test_id, unit_tests.setdefault(test_id, {
86 'status': PENDING, 'failures': 0
87- }), failure_threshold=4)
88+ }), failure_threshold=4, logger=self.logger)
89
90 def iter_unit_test_jobs(self, revision_build):
91 """Iterate through the unit test StateFileJobs for a revision build.
92@@ -302,7 +308,7 @@
93 'functional-tests')
94 return StateFileJob(job_id, functional_tests.setdefault(job_id, {
95 'status': PENDING, 'failures': 0
96- }), failure_threshold=3)
97+ }), failure_threshold=3, logger=self.logger)
98
99 def iter_functional_test_jobs(self, revision_build):
100 """Iterate the functional test StateFileJobs for a revision build.
101@@ -387,7 +393,7 @@
102 class StateFileJob:
103 """Representation of a Job in the StateFile."""
104
105- def __init__(self, test_id, data, failure_threshold=1):
106+ def __init__(self, test_id, data, failure_threshold=1, logger=None):
107 """Constructor.
108
109 :param test_id: The ID of the job.
110@@ -412,6 +418,7 @@
111 base_data.update(data)
112 data.update(base_data)
113 self._data = data
114+ self.logger = logger
115
116 def __repr__(self):
117 return 'StateFileJob<%r, %r>' % (self.test_id, self.get_status())
118@@ -475,7 +482,19 @@
119 'deploy-via-windows' : WIN_CLIENT_DEPLOY,
120 }
121 test_id = specific_to_resourceful.get(self.test_id, self.test_id)
122- info = jenkins.get_build_info(test_id, last_build)
123+ try:
124+ info = jenkins.get_build_info(test_id, last_build)
125+ except JenkinsException as err:
126+ if str(err).endswith('number[%s] does not exist' % last_build):
127+ # A build can really disappear, see bug 1365808.
128+ self._data['status'] = None
129+ self._data['last_build'] = None
130+ if self.logger is not None:
131+ self.logger.warn(
132+ 'Build %s of job %s is missing' % (last_build, test_id))
133+ return
134+ else:
135+ raise
136 result = info.get('result')
137 if result is None:
138 return
139
140=== modified file 'cidirector/tests/test_cidirector.py'
141--- cidirector/tests/test_cidirector.py 2014-09-29 13:02:53 +0000
142+++ cidirector/tests/test_cidirector.py 2014-10-01 14:36:51 +0000
143@@ -190,7 +190,11 @@
144
145 @log_calls
146 def get_build_info(self, test_id, build_id):
147- return self.build_info[test_id][build_id]
148+ if build_id in self.build_info[test_id]:
149+ return self.build_info[test_id][build_id]
150+ else:
151+ raise JenkinsException(
152+ 'job[%s] number[%s] does not exist'% (test_id, build_id))
153
154 def set_build_result(self, job_id, build_number, result, artifacts=[]):
155 info = self.build_info.setdefault(job_id, {build_number: {}})
156
157=== modified file 'cidirector/tests/test_storage.py'
158--- cidirector/tests/test_storage.py 2014-09-29 13:02:53 +0000
159+++ cidirector/tests/test_storage.py 2014-10-01 14:36:51 +0000
160@@ -1,6 +1,7 @@
161 __metaclass__ = type
162
163 from datetime import datetime
164+import logging
165 import os.path
166 from StringIO import StringIO
167 import subprocess
168@@ -233,6 +234,27 @@
169 artifacts, current_version, 'fakettp://fake.fake/job/foo/1234',
170 build_number)
171
172+ def test_update_from_jenkins_missing_build(self):
173+ build_number = 1234
174+ foo_job = 'foo'
175+ foo_data = {
176+ 'status': BUILDING, 'failures': 0, 'failure-threshold': 3,
177+ 'build_number': build_number, 'last_build': build_number - 1,
178+ 'building': True,
179+ }
180+ jobs = {foo_job: foo_data}
181+ current_version = 4567
182+ jenkins = FakeJenkins(jobs, current_version)
183+ logger = logging.getLogger()
184+ handler = logging.handlers.MemoryHandler(10)
185+ logger.addHandler(handler)
186+ sfj = StateFileJob(foo_job, foo_data, None, logger)
187+ sfj.update_from_jenkins(jenkins, current_version)
188+ self.assertIs(None, sfj._data['status'])
189+ self.assertIs(None, sfj._data['last_build'])
190+ self.assertEqual(
191+ 'Build 1233 of job foo is missing', handler.buffer[0].msg)
192+
193 def test_archive_results(self):
194 sfj = StateFileJob('foo', {})
195 artifacts = [

Subscribers

People subscribed via source and target branches