@@ -163,9 +163,9 @@ class BuildResult(models.Model):
163
163
trigger_id = fields .Many2one ('runbot.trigger' , related = 'params_id.trigger_id' , store = True , index = True )
164
164
165
165
# state machine
166
- global_state = fields .Selection (make_selection (state_order ), string = 'Status' , compute = '_compute_global_state ' , store = True , recursive = True )
166
+ global_state = fields .Selection (make_selection (state_order ), string = 'Status' , default = 'pending ' , required = True )
167
167
local_state = fields .Selection (make_selection (state_order ), string = 'Build Status' , default = 'pending' , required = True , index = True )
168
- global_result = fields .Selection (make_selection (result_order ), string = 'Result' , compute = '_compute_global_result' , store = True , recursive = True )
168
+ global_result = fields .Selection (make_selection (result_order ), string = 'Result' , default = 'ok' )
169
169
local_result = fields .Selection (make_selection (result_order ), string = 'Build Result' , default = 'ok' )
170
170
171
171
requested_action = fields .Selection ([('wake_up' , 'To wake up' ), ('deathrow' , 'To kill' )], string = 'Action requested' , index = True )
@@ -254,20 +254,6 @@ def _compute_log_list(self): # storing this field because it will be access trh
254
254
build .log_list = ',' .join ({step .name for step in build .params_id .config_id .step_ids () if step ._has_log ()})
255
255
# TODO replace logic, add log file to list when executed (avoid 404, link log on docker start, avoid fake is_docker_step)
256
256
257
- @api .depends ('children_ids.global_state' , 'local_state' )
258
- def _compute_global_state (self ):
259
- for record in self :
260
- waiting_score = record ._get_state_score ('waiting' )
261
- children_ids = [child for child in record .children_ids if not child .orphan_result ]
262
- if record ._get_state_score (record .local_state ) > waiting_score and children_ids : # if finish, check children
263
- children_state = record ._get_youngest_state ([child .global_state for child in children_ids ])
264
- if record ._get_state_score (children_state ) > waiting_score :
265
- record .global_state = record .local_state
266
- else :
267
- record .global_state = 'waiting'
268
- else :
269
- record .global_state = record .local_state
270
-
271
257
@api .depends ('gc_delay' , 'job_end' )
272
258
def _compute_gc_date (self ):
273
259
icp = self .env ['ir.config_parameter' ].sudo ()
@@ -299,22 +285,6 @@ def _get_youngest_state(self, states):
299
285
def _get_state_score (self , result ):
300
286
return state_order .index (result )
301
287
302
- @api .depends ('children_ids.global_result' , 'local_result' , 'children_ids.orphan_result' )
303
- def _compute_global_result (self ):
304
- for record in self :
305
- if record .local_result and record ._get_result_score (record .local_result ) >= record ._get_result_score ('ko' ):
306
- record .global_result = record .local_result
307
- else :
308
- children_ids = [child for child in record .children_ids if not child .orphan_result ]
309
- if children_ids :
310
- children_result = record ._get_worst_result ([child .global_result for child in children_ids ], max_res = 'ko' )
311
- if record .local_result :
312
- record .global_result = record ._get_worst_result ([record .local_result , children_result ])
313
- else :
314
- record .global_result = children_result
315
- else :
316
- record .global_result = record .local_result
317
-
318
288
def _get_worst_result (self , results , max_res = False ):
319
289
results = [result for result in results if result ] # filter Falsy values
320
290
index = max ([self ._get_result_score (result ) for result in results ]) if results else 0
@@ -340,37 +310,50 @@ def copy_data(self, default=None):
340
310
})
341
311
return [values ]
342
312
313
+ @api .model_create_multi
314
+ def create (self , vals_list ):
315
+ for values in vals_list :
316
+ if 'local_state' in values :
317
+ values ['global_state' ] = values ['local_state' ]
318
+ if 'local_result' in values :
319
+ values ['global_result' ] = values ['local_result' ]
320
+ records = super ().create (vals_list )
321
+ if records .parent_id :
322
+ records .parent_id ._update_globals ()
323
+ return records
324
+
343
325
def write (self , values ):
344
326
# some validation to ensure db consistency
345
327
if 'local_state' in values :
346
328
if values ['local_state' ] == 'done' :
347
329
self .filtered (lambda b : b .local_state != 'done' ).commit_export_ids .unlink ()
348
330
349
- local_result = values .get ('local_result' )
350
- for build in self :
351
- if local_result and local_result != self ._get_worst_result ([build .local_result , local_result ]): # dont write ok on a warn/error build
352
- if len (self ) == 1 :
353
- values .pop ('local_result' )
354
- else :
355
- raise ValidationError ('Local result cannot be set to a less critical level' )
356
-
357
- init_global_results = self .mapped ('global_result' )
358
- init_global_states = self .mapped ('global_state' )
359
- res = super (BuildResult , self ).write (values )
360
- for init_global_result , build in zip (init_global_results , self ):
361
- if init_global_result != build .global_result :
362
- build ._github_status ()
363
-
364
- for init_global_state , build in zip (init_global_states , self ):
365
- if not build .parent_id and init_global_state not in ('done' , 'running' ) and build .global_state in ('done' , 'running' ):
366
- build ._github_status ()
367
-
368
- if values .get ('global_state' ) in ('done' , 'running' ):
369
- for build in self :
370
- if not build .parent_id and build .global_state not in ('done' , 'running' ):
371
- build ._github_status ()
372
-
373
- return res
331
+ # don't update if the value doesn't change to avoid triggering concurrent updates
332
+ def minimal_update (records , field_name ):
333
+ updated = self .browse ()
334
+ if field_name in values :
335
+ field_result = values .pop (field_name )
336
+ updated = records .filtered (lambda b : (b [field_name ] != field_result ))
337
+ if updated :
338
+ super (BuildResult , updated ).write ({field_name : field_result })
339
+ return updated
340
+
341
+ # local result is a special case since we don't only want to avoid an update if the value didn't change, but also if the value is less than the previous one
342
+ # example: don't write 'ok' if result is 'ko' or 'warn'
343
+ updated = self .browse ()
344
+ if 'local_result' in values :
345
+ to_update = self .filtered (lambda b : (self ._get_result_score (values ['local_result' ]) > self ._get_result_score (b .local_result )))
346
+ updated = minimal_update (to_update , 'local_result' )
347
+ updated |= minimal_update (self , 'local_state' )
348
+ updated ._update_globals ()
349
+ parents_to_update = minimal_update (self , 'global_result' ).parent_id
350
+ parents_to_update |= minimal_update (self , 'global_state' ).parent_id
351
+ parents_to_update |= minimal_update (self , 'orphan_result' ).parent_id
352
+ parents_to_update ._notify_global_update ()
353
+
354
+ if values :
355
+ super ().write (values )
356
+ return True
374
357
375
358
def _add_child (self , param_values , orphan = False , description = False , additionnal_commit_links = False ):
376
359
@@ -476,8 +459,6 @@ def _rebuild(self, message=None):
476
459
self .orphan_result = True
477
460
478
461
new_build = self .create (values )
479
- if self .parent_id :
480
- new_build ._github_status ()
481
462
user = request .env .user if request else self .env .user
482
463
new_build ._log ('rebuild' , 'Rebuild initiated by %s%s' % (user .name , (' :%s' % message ) if message else '' ))
483
464
@@ -669,14 +650,56 @@ def _process_requested_actions(self):
669
650
run_step = step_ids [- 1 ]
670
651
else :
671
652
run_step = self .env .ref ('runbot.runbot_build_config_step_run' )
672
- run_step ._run_step (build , log_path , force = True )
653
+ run_step ._run_step (build , log_path , force = True )()
673
654
# reload_nginx will be triggered by _run_run_odoo
674
655
except Exception :
675
656
_logger .exception ('Failed to wake up build %s' , build .dest )
676
657
build ._log ('_schedule' , 'Failed waking up build' , level = 'ERROR' )
677
658
build .write ({'requested_action' : False , 'local_state' : 'done' })
678
659
continue
679
660
661
+ def _notify_global_update (self ):
662
+ for record in self :
663
+ if not record .host_id :
664
+ record ._update_globals ()
665
+ else :
666
+ self .env ['runbot.host.message' ].create ({
667
+ 'host_id' : record .host_id .id ,
668
+ 'build_id' : record .id ,
669
+ 'message' : 'global_updated' ,
670
+ })
671
+
672
+ def _update_globals (self ):
673
+ for record in self :
674
+ children = record .children_ids .filtered (lambda child : not child .orphan_result )
675
+ global_result = record .local_result
676
+ if children :
677
+ child_result = record ._get_worst_result (children .mapped ('global_result' ), max_res = 'ko' )
678
+ global_result = record ._get_worst_result ([record .local_result , child_result ])
679
+ if global_result != record .global_result :
680
+ record .global_result = global_result
681
+ if not record .parent_id :
682
+ record ._github_status () # failfast
683
+
684
+ init_state = record .global_state
685
+ testing_children = any (child .global_state not in ('running' , 'done' ) for child in children )
686
+ global_state = record .local_state
687
+ if testing_children :
688
+ child_state = 'waiting'
689
+ global_state = record ._get_youngest_state ([record .local_state , child_state ])
690
+
691
+ if global_state != record .global_state :
692
+ record .global_state = global_state
693
+
694
+ ending_build = init_state not in ('done' , 'running' ) and record .global_state in ('done' , 'running' )
695
+
696
+ if ending_build :
697
+ if not record .local_result : # Set 'ok' result if no result set (no tests job on build)
698
+ record .local_result = 'ok'
699
+ record .build_end = now ()
700
+ if not record .parent_id :
701
+ record ._github_status ()
702
+
680
703
def _schedule (self ):
681
704
"""schedule the build"""
682
705
icp = self .env ['ir.config_parameter' ].sudo ()
@@ -959,7 +982,6 @@ def _kill(self, result=None):
959
982
v ['local_result' ] = result
960
983
build .write (v )
961
984
self .env .cr .commit ()
962
- build ._github_status ()
963
985
self .invalidate_cache ()
964
986
965
987
def _ask_kill (self , lock = True , message = None ):
0 commit comments