Merge lp:~notnownikki/offspring/exclude-failing-builders into lp:offspring
- exclude-failing-builders
- Merge into trunk
Proposed by
Nicola Heald
Status: | Merged |
---|---|
Approved by: | Kevin McDermott |
Approved revision: | 166 |
Merged at revision: | 164 |
Proposed branch: | lp:~notnownikki/offspring/exclude-failing-builders |
Merge into: | lp:offspring |
Diff against target: |
380 lines (+232/-7) 8 files modified
config/offspring.cfg (+1/-0) lib/offspring/master/master.py (+9/-1) lib/offspring/master/models.py (+1/-0) lib/offspring/master/tests/helpers.py (+3/-1) lib/offspring/master/tests/test_master.py (+40/-2) lib/offspring/web/queuemanager/admin.py (+3/-3) lib/offspring/web/queuemanager/migrations/0004_auto__add_field_lexbuilder_contiguous_errors.py (+174/-0) lib/offspring/web/queuemanager/models.py (+1/-0) |
To merge this branch: | bzr merge lp:~notnownikki/offspring/exclude-failing-builders |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Kevin McDermott | Approve | ||
Review via email: mp+165662@code.launchpad.net |
Commit message
Description of the change
Adds contiguous_errors field to Lexbuilder, increments this when a build fails. Resets to 0 on a successful build. Will not use builders that have failed more that X times in a row (configurable)
To post a comment you must log in.
- 165. By Nicola Heald
-
Removed TODO comments
Revision history for this message
Kevin McDermott (bigkevmcd) : | # |
review:
Needs Information
Revision history for this message
Kevin McDermott (bigkevmcd) wrote : | # |
Oh, and as the configuration variable is "limit" then we the query should probably be...
+ Lexbuilder.
+ "master", "builder_
:-)
- 166. By Nicola Heald
-
error limit behaves like a limit now, config option changed to remove _upper from the name.
Revision history for this message
Kevin McDermott (bigkevmcd) wrote : | # |
Looks good now, thanks for the changes :-)
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'config/offspring.cfg' |
2 | --- config/offspring.cfg 2012-02-16 06:44:19 +0000 |
3 | +++ config/offspring.cfg 2013-05-24 16:54:37 +0000 |
4 | @@ -140,6 +140,7 @@ |
5 | poll_frequency: 20 |
6 | logfile: %(log_dir)s/offspring-master.log |
7 | launchpad_oauth: %(config_dir)s/launchpad.oauth |
8 | +builder_error_limit: 5 |
9 | |
10 | #=============================================================================== |
11 | # Offspring publisher |
12 | |
13 | === modified file 'lib/offspring/master/master.py' |
14 | --- lib/offspring/master/master.py 2013-05-23 04:18:49 +0000 |
15 | +++ lib/offspring/master/master.py 2013-05-24 16:54:37 +0000 |
16 | @@ -11,6 +11,7 @@ |
17 | from storm.locals import create_database, Desc, Store |
18 | |
19 | from offspring.daemon import LexbuilderDaemon |
20 | +from offspring.enums import ProjectBuildStates |
21 | from offspring.master.exceptions import SlaveCommunicationError |
22 | from offspring.master.models import BuildRequest, Lexbuilder |
23 | from offspring.master import notifications |
24 | @@ -97,7 +98,10 @@ |
25 | self.is_online = False |
26 | |
27 | def _get_builders(self): |
28 | - return self.db_store.find(Lexbuilder, Lexbuilder.is_retired == False) |
29 | + return self.db_store.find( |
30 | + Lexbuilder, Lexbuilder.is_retired == False, |
31 | + Lexbuilder.contiguous_errors <= self.config.getint( |
32 | + "master", "builder_error_limit")) |
33 | |
34 | def scanSlaves(self): |
35 | """ |
36 | @@ -146,6 +150,10 @@ |
37 | logging.error( |
38 | "PROBLEM DETECTED: A non-fatal problem has" |
39 | " been detected. See above for details.") |
40 | + if builder.current_job.result == ProjectBuildStates.FAILED: |
41 | + builder.contiguous_errors += 1 |
42 | + else: |
43 | + builder.contiguous_errors = 0 |
44 | builder.current_job = None |
45 | self.db_store.commit() |
46 | else: |
47 | |
48 | === modified file 'lib/offspring/master/models.py' |
49 | --- lib/offspring/master/models.py 2013-05-23 04:18:49 +0000 |
50 | +++ lib/offspring/master/models.py 2013-05-24 16:54:37 +0000 |
51 | @@ -35,6 +35,7 @@ |
52 | is_active = Bool(default=True) |
53 | is_okay = Bool(default=True) |
54 | is_retired = Bool(default=False) |
55 | + contiguous_errors = Int(default=0) |
56 | current_job_id = Int() |
57 | current_job = Reference(current_job_id, "BuildResult.id") |
58 | notes = Unicode() |
59 | |
60 | === modified file 'lib/offspring/master/tests/helpers.py' |
61 | --- lib/offspring/master/tests/helpers.py 2013-05-23 04:18:49 +0000 |
62 | +++ lib/offspring/master/tests/helpers.py 2013-05-24 16:54:37 +0000 |
63 | @@ -91,6 +91,7 @@ |
64 | self.log_dir = self.makeDir() |
65 | self.config.set("master", "log_dir", self.log_dir) |
66 | self.config.set("master", "db", self.database_uri) |
67 | + self.config.set("master", "builder_error_limit", "5") |
68 | |
69 | |
70 | class BaseOffspringMasterTestCase(LexbuilderMasterTestMixin, |
71 | @@ -178,7 +179,8 @@ |
72 | "current_job_id INTEGER, " |
73 | "is_okay BOOLEAN NOT NULL, " |
74 | "is_active BOOLEAN NOT NULL DEFAULT TRUE, " |
75 | - "is_retired BOOLEAN NOT NULL DEFAULT FALSE)") |
76 | + "is_retired BOOLEAN NOT NULL DEFAULT FALSE," |
77 | + "contiguous_errors INTEGER NOT NULL DEFAULT 0)") |
78 | |
79 | self.connection.execute( |
80 | "CREATE TABLE buildrequests (" |
81 | |
82 | === modified file 'lib/offspring/master/tests/test_master.py' |
83 | --- lib/offspring/master/tests/test_master.py 2013-05-23 13:55:56 +0000 |
84 | +++ lib/offspring/master/tests/test_master.py 2013-05-24 16:54:37 +0000 |
85 | @@ -7,6 +7,7 @@ |
86 | import storm |
87 | from storm.exceptions import DisconnectionError |
88 | |
89 | +from offspring.enums import ProjectBuildStates |
90 | from offspring.master.master import LexbuilderMaster |
91 | from offspring.master.models import ( |
92 | Lexbuilder, BuildResult) |
93 | @@ -40,6 +41,7 @@ |
94 | builder.is_active = kwargs.get("is_active", True) |
95 | builder.is_okay = kwargs.get("is_okay", True) |
96 | builder.is_retired = kwargs.get("is_retired", False) |
97 | + builder.contiguous_errors = kwargs.get("contiguous_errors", 0) |
98 | builder.machine_type = kwargs.get("arch", self.project.arch) |
99 | self.db_store.add(builder) |
100 | return builder |
101 | @@ -118,12 +120,33 @@ |
102 | "Scan cycle completed\n", |
103 | log_file.getvalue()) |
104 | |
105 | - def build_builder_and_slave_with_job_and_result(self): |
106 | + def test_scan_slaves_error_increments_error_count(self): |
107 | + """ |
108 | + When a builder has a failed build, the contiguous_errors count |
109 | + is incremented. |
110 | + """ |
111 | + master, builder = self.build_builder_and_slave_with_job_and_result( |
112 | + result=ProjectBuildStates.FAILED) |
113 | + master.scanSlaves() |
114 | + self.assertEqual(1, builder.contiguous_errors) |
115 | + |
116 | + def test_scan_slaves_non_error_resets_error_count(self): |
117 | + """ |
118 | + When a builder has a non-erroring build, the contiguous_errors |
119 | + field is reset to 0. |
120 | + """ |
121 | + master, builder = self.build_builder_and_slave_with_job_and_result( |
122 | + result=ProjectBuildStates.SUCCESS, contiguous_errors=1) |
123 | + master.scanSlaves() |
124 | + self.assertEqual(0, builder.contiguous_errors) |
125 | + |
126 | + def build_builder_and_slave_with_job_and_result( |
127 | + self, result=None, contiguous_errors=0): |
128 | """ |
129 | This builds and returns a LexbuilderMaster with a single slave builder, |
130 | and a job result. |
131 | """ |
132 | - builder = self.create_builder() |
133 | + builder = self.create_builder(contiguous_errors=contiguous_errors) |
134 | builder_mock = self.mocker.patch(builder) |
135 | builder_mock.scan() |
136 | self.mocker.result((Slave.STATE_IDLE, Slave.STATE_IDLE)) |
137 | @@ -131,6 +154,7 @@ |
138 | build_request = self.create_build_request() |
139 | build_result = BuildResult(build_request, builder) |
140 | build_result.name = u"20111115-testing" |
141 | + build_result.result = result |
142 | self.db_store.add(build_result) |
143 | |
144 | builder_mock.getBuildResult() |
145 | @@ -232,6 +256,20 @@ |
146 | self.assertEqual(2, builders.count()) |
147 | self.assertEqual(["BuilderB", "BuilderC"], list(builders.values(Lexbuilder.name))) |
148 | |
149 | + def test_get_builders_does_not_return_errors_over_threshold(self): |
150 | + """ |
151 | + Test that builders with contiguous_errors over the values defined in settings |
152 | + are not returned. |
153 | + """ |
154 | + master = self.get_master() |
155 | + builder1 = self.create_builder(name="BuilderA", contiguous_errors=5) |
156 | + builder2 = self.create_builder(name="BuilderB", contiguous_errors=6) |
157 | + builder3 = self.create_builder(name="BuilderC") |
158 | + builders = master._get_builders() |
159 | + self.assertEqual(2, builders.count()) |
160 | + self.assertEqual(["BuilderA", "BuilderC"], list(builders.values(Lexbuilder.name))) |
161 | + |
162 | + |
163 | def test_dispatch_builds(self): |
164 | """ |
165 | dispatchBuilds sends BuildRequests to a slave. |
166 | |
167 | === modified file 'lib/offspring/web/queuemanager/admin.py' |
168 | --- lib/offspring/web/queuemanager/admin.py 2013-05-23 04:18:49 +0000 |
169 | +++ lib/offspring/web/queuemanager/admin.py 2013-05-24 16:54:37 +0000 |
170 | @@ -20,8 +20,8 @@ |
171 | |
172 | class LexbuilderAdmin(admin.ModelAdmin): |
173 | list_display = ('name', 'machine_type', 'is_active', 'is_okay', |
174 | - 'is_retired', 'current_state', 'current_build', |
175 | - 'updated_at') |
176 | + 'is_retired', 'current_state', 'contiguous_errors', |
177 | + 'current_build', 'updated_at') |
178 | list_filter = ['current_state', 'machine_type', 'is_active', |
179 | 'is_okay', 'is_retired'] |
180 | actions=['make_enabled', 'make_disabled'] |
181 | @@ -36,7 +36,7 @@ |
182 | make_disabled.short_description = "Mark selected builders as disabled" |
183 | |
184 | def make_enabled(self, request, queryset): |
185 | - rows_updated = queryset.update(is_active=True) |
186 | + rows_updated = queryset.update(is_active=True, contiguous_errors=0) |
187 | if rows_updated == 1: |
188 | message_bit = "1 lexbuilder was" |
189 | else: |
190 | |
191 | === added file 'lib/offspring/web/queuemanager/migrations/0004_auto__add_field_lexbuilder_contiguous_errors.py' |
192 | --- lib/offspring/web/queuemanager/migrations/0004_auto__add_field_lexbuilder_contiguous_errors.py 1970-01-01 00:00:00 +0000 |
193 | +++ lib/offspring/web/queuemanager/migrations/0004_auto__add_field_lexbuilder_contiguous_errors.py 2013-05-24 16:54:37 +0000 |
194 | @@ -0,0 +1,174 @@ |
195 | +# encoding: utf-8 |
196 | +import datetime |
197 | +from south.db import db |
198 | +from south.v2 import SchemaMigration |
199 | +from django.db import models |
200 | + |
201 | +class Migration(SchemaMigration): |
202 | + |
203 | + def forwards(self, orm): |
204 | + |
205 | + # Adding field 'Lexbuilder.contiguous_errors' |
206 | + db.add_column('lexbuilders', 'contiguous_errors', self.gf('django.db.models.fields.IntegerField')(default=0), keep_default=False) |
207 | + |
208 | + |
209 | + def backwards(self, orm): |
210 | + |
211 | + # Deleting field 'Lexbuilder.contiguous_errors' |
212 | + db.delete_column('lexbuilders', 'contiguous_errors') |
213 | + |
214 | + |
215 | + models = { |
216 | + 'auth.group': { |
217 | + 'Meta': {'object_name': 'Group'}, |
218 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
219 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
220 | + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
221 | + }, |
222 | + 'auth.permission': { |
223 | + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, |
224 | + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
225 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), |
226 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
227 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
228 | + }, |
229 | + 'auth.user': { |
230 | + 'Meta': {'object_name': 'User'}, |
231 | + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2013, 5, 24, 9, 34, 39, 946400)'}), |
232 | + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), |
233 | + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
234 | + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), |
235 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
236 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
237 | + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
238 | + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
239 | + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2013, 5, 24, 9, 34, 39, 946337)'}), |
240 | + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
241 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
242 | + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), |
243 | + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
244 | + }, |
245 | + 'contenttypes.contenttype': { |
246 | + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
247 | + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
248 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
249 | + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
250 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
251 | + }, |
252 | + 'django_group_access.accessgroup': { |
253 | + 'Meta': {'ordering': "('name',)", 'object_name': 'AccessGroup'}, |
254 | + 'auto_share_groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'auto_share_groups_rel_+'", 'blank': 'True', 'to': "orm['django_group_access.AccessGroup']"}), |
255 | + 'can_be_shared_with': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
256 | + 'can_share_with': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['django_group_access.AccessGroup']", 'symmetrical': 'False', 'blank': 'True'}), |
257 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
258 | + 'members': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'}), |
259 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}), |
260 | + 'supergroup': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) |
261 | + }, |
262 | + 'queuemanager.buildrequest': { |
263 | + 'Meta': {'object_name': 'BuildRequest', 'db_table': "'buildrequests'"}, |
264 | + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
265 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
266 | + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['queuemanager.Project']", 'db_column': "'project_name'"}), |
267 | + 'reason': ('django.db.models.fields.TextField', [], {'max_length': '200', 'blank': 'True'}), |
268 | + 'requestor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'db_column': "'requestor_id'", 'blank': 'True'}), |
269 | + 'score': ('django.db.models.fields.IntegerField', [], {'default': '10'}) |
270 | + }, |
271 | + 'queuemanager.buildresult': { |
272 | + 'Meta': {'object_name': 'BuildResult', 'db_table': "'buildresults'"}, |
273 | + 'builder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['queuemanager.Lexbuilder']", 'null': 'True', 'blank': 'True'}), |
274 | + 'dispatched_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), |
275 | + 'finished_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), |
276 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
277 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), |
278 | + 'notes': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), |
279 | + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['queuemanager.Project']", 'db_column': "'project_name'"}), |
280 | + 'reason': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), |
281 | + 'requested_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}), |
282 | + 'requestor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'db_column': "'requestor_id'", 'blank': 'True'}), |
283 | + 'result': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), |
284 | + 'started_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) |
285 | + }, |
286 | + 'queuemanager.dailybuildorder': { |
287 | + 'Meta': {'object_name': 'DailyBuildOrder', 'db_table': "'dailybuildorders'"}, |
288 | + 'hour': ('django.db.models.fields.PositiveIntegerField', [], {}), |
289 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
290 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), |
291 | + 'projects': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['queuemanager.Project']", 'symmetrical': 'False'}) |
292 | + }, |
293 | + 'queuemanager.launchpadproject': { |
294 | + 'Meta': {'object_name': 'LaunchpadProject', 'db_table': "'launchpad_projects'"}, |
295 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200', 'primary_key': 'True'}), |
296 | + 'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}) |
297 | + }, |
298 | + 'queuemanager.launchpadprojectmilestone': { |
299 | + 'Meta': {'object_name': 'LaunchpadProjectMilestone', 'db_table': "'launchpad_project_milestones'"}, |
300 | + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
301 | + 'date_targeted': ('django.db.models.fields.DateField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), |
302 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
303 | + 'launchpad_project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['queuemanager.LaunchpadProject']", 'null': 'True', 'blank': 'True'}), |
304 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), |
305 | + 'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}) |
306 | + }, |
307 | + 'queuemanager.lexbuilder': { |
308 | + 'Meta': {'object_name': 'Lexbuilder', 'db_table': "'lexbuilders'"}, |
309 | + 'contiguous_errors': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
310 | + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
311 | + 'current_job': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['queuemanager.BuildResult']", 'null': 'True', 'db_column': "'current_job_id'", 'blank': 'True'}), |
312 | + 'current_state': ('django.db.models.fields.CharField', [], {'default': "'UNKNOWN'", 'max_length': '200'}), |
313 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
314 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
315 | + 'is_okay': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
316 | + 'is_retired': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
317 | + 'machine_type': ('django.db.models.fields.CharField', [], {'default': "'x86_64'", 'max_length': '200'}), |
318 | + 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '200', 'db_index': 'True'}), |
319 | + 'notes': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), |
320 | + 'previous_state': ('django.db.models.fields.CharField', [], {'default': "'UNKNOWN'", 'max_length': '200'}), |
321 | + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), |
322 | + 'uri': ('django.db.models.fields.CharField', [], {'max_length': '200'}) |
323 | + }, |
324 | + 'queuemanager.project': { |
325 | + 'Meta': {'object_name': 'Project', 'db_table': "'projects'"}, |
326 | + 'access_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['django_group_access.AccessGroup']", 'null': 'True', 'blank': 'True'}), |
327 | + 'arch': ('django.db.models.fields.CharField', [], {'max_length': '10'}), |
328 | + 'config_url': ('django.db.models.fields.CharField', [], {'max_length': '200'}), |
329 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
330 | + 'launchpad_project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['queuemanager.LaunchpadProject']", 'null': 'True', 'blank': 'True'}), |
331 | + 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '200', 'primary_key': 'True', 'db_index': 'True'}), |
332 | + 'notes': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), |
333 | + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'project_owner'", 'null': 'True', 'to': "orm['auth.User']"}), |
334 | + 'priority': ('django.db.models.fields.IntegerField', [], {'default': '50'}), |
335 | + 'project_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['queuemanager.ProjectGroup']", 'null': 'True', 'blank': 'True'}), |
336 | + 'series': ('django.db.models.fields.CharField', [], {'default': "'lucid'", 'max_length': '200'}), |
337 | + 'status': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), |
338 | + 'suite': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}), |
339 | + 'title': ('django.db.models.fields.CharField', [], {'max_length': '30'}) |
340 | + }, |
341 | + 'queuemanager.projectgroup': { |
342 | + 'Meta': {'object_name': 'ProjectGroup', 'db_table': "'projectgroups'"}, |
343 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200', 'primary_key': 'True'}), |
344 | + 'title': ('django.db.models.fields.CharField', [], {'max_length': '200'}) |
345 | + }, |
346 | + 'queuemanager.projectnotificationsubscription': { |
347 | + 'Meta': {'object_name': 'ProjectNotificationSubscription', 'db_table': "'project_notification_subscriptions'"}, |
348 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
349 | + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['queuemanager.Project']", 'db_column': "'project_name'"}), |
350 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'db_column': "'user_id'"}) |
351 | + }, |
352 | + 'queuemanager.release': { |
353 | + 'Meta': {'object_name': 'Release', 'db_table': "'releases'"}, |
354 | + 'build': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['queuemanager.BuildResult']", 'unique': 'True', 'primary_key': 'True'}), |
355 | + 'checklist_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), |
356 | + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
357 | + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), |
358 | + 'milestone': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['queuemanager.LaunchpadProjectMilestone']", 'null': 'True', 'blank': 'True'}), |
359 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), |
360 | + 'notes': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
361 | + 'published_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), |
362 | + 'status': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}), |
363 | + 'tag': ('django.db.models.fields.CharField', [], {'max_length': '5', 'null': 'True', 'blank': 'True'}), |
364 | + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) |
365 | + } |
366 | + } |
367 | + |
368 | + complete_apps = ['queuemanager'] |
369 | |
370 | === modified file 'lib/offspring/web/queuemanager/models.py' |
371 | --- lib/offspring/web/queuemanager/models.py 2013-05-23 04:18:49 +0000 |
372 | +++ lib/offspring/web/queuemanager/models.py 2013-05-24 16:54:37 +0000 |
373 | @@ -165,6 +165,7 @@ |
374 | is_active = models.BooleanField(default=True) |
375 | is_okay = models.BooleanField(default=True, editable=False) |
376 | is_retired = models.BooleanField(default=False) |
377 | + contiguous_errors = models.IntegerField(default=0) |
378 | machine_type = models.CharField(max_length=200, default="x86_64") |
379 | current_job = models.ForeignKey("BuildResult", db_column="current_job_id", editable=False, blank=True, null=True) |
380 | notes = models.TextField('whiteboard', blank=True, null=True) |
Looks good, nothing major, couple of minors (and the first is a personal preference).
=== modified file 'config/ offspring. cfg' upper_error_ limit: 5
+builder_
Given that we don't have a lower error limit, "upper" seems redundant here?
=== modified file 'lib/offspring/ master/ master. py' is_retired == False, contiguous_ errors < int( get("master" , "builder_ upper_error_ limit") ))
+ return self.db_store.find(
+ Lexbuilder, Lexbuilder.
+ Lexbuilder.
+ self.config.
self.config is a ConfigParser, so you could replace this with
+ Lexbuilder. contiguous_ errors < self.config.getint( upper_error_ limit") )
+ "master", "builder_
What I was wondering about, is should we expose failing builders through the monitoring API? Or should we send a notification when we knock a builder offline (i.e. when we increment the contiguous_errors above the limit?)