Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions runbot/controllers/frontend.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ def build(self, build_id, search=None, from_batch=None, **post):
if not build.exists():
return request.not_found()
siblings = (build.parent_id.children_ids if build.parent_id else from_batch.slot_ids.build_id if from_batch else build).sorted('id')
# TODO FIXME for linked builds. Sibling may depends on the context of the batch
context = {
'build': build,
'from_batch': from_batch,
Expand Down
99 changes: 86 additions & 13 deletions runbot/models/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,10 +350,15 @@ class BuildResult(models.Model):
string='Build type')

parent_id = fields.Many2one('runbot.build', 'Parent Build', index=True)
parent_link_ids = fields.One2many('runbot.build.link', 'child_id', string='Used in links')
linked_parent_build_ids = fields.Many2many('runbot.build', compute='_compute_linked_parent_build_ids', string='All parent builds')

child_link_ids = fields.One2many('runbot.build.link', 'parent_id', string='Child links')
linked_children_build_ids = fields.Many2many('runbot.build', compute='_compute_linked_children_build_id', string='All child builds')

parent_path = fields.Char('Parent path', index=True)
top_parent = fields.Many2one('runbot.build', compute='_compute_top_parent')
ancestors = fields.Many2many('runbot.build', compute='_compute_ancestors')
# should we add a has children stored boolean?
children_ids = fields.One2many('runbot.build', 'parent_id')
all_children_ids = fields.One2many('runbot.build', compute='_compute_all_children_ids')

Expand Down Expand Up @@ -390,12 +395,22 @@ def _compute_host_id(self):
for record in self:
record.host_id = get_host(record.host)

def _compute_linked_parent_build_ids(self):
for build in self:
build.linked_parent_build_ids = build.parent_link_ids.parent_id

def _compute_linked_children_build_ids(self):
for build in self:
build.linked_children_build_ids = build.child_link_ids.child_id

def _update_globals(self):
for record in self:
waiting_score = record._get_state_score('waiting')
children_ids = [child for child in record.children_ids if not child.orphan_result]
if record._get_state_score(record.local_state) > waiting_score and children_ids: # if finish, check children
children_state = record._get_youngest_state([child.global_state for child in children_ids])
children_ids = [child.id for child in record.children_ids if not child.orphan_result]
children_ids += [link.child_id.id for link in record.child_link_ids if not link.orphan_result]
children = self.browse(children_ids)
if record._get_state_score(record.local_state) > waiting_score and children: # if finish, check children
children_state = record._get_youngest_state([child.global_state for child in children])
if record._get_state_score(children_state) > waiting_score:
record.global_state = record.local_state
else:
Expand All @@ -406,9 +421,11 @@ def _update_globals(self):
if record.local_result and record._get_result_score(record.local_result) >= record._get_result_score('ko'):
record.global_result = record.local_result
else:
children_ids = [child for child in record.children_ids if not child.orphan_result]
if children_ids:
children_result = record._get_worst_result([child.global_result for child in children_ids], max_res='ko')
children_ids = [child.id for child in record.children_ids if not child.orphan_result]
children_ids += [link.child_id.id for link in record.child_link_ids if not link.orphan_result]
children = self.browse(children_ids)
if children:
children_result = record._get_worst_result([child.global_result for child in children], max_res='ko')
if record.local_result:
record.global_result = record._get_worst_result([record.local_result, children_result])
else:
Expand Down Expand Up @@ -555,7 +572,7 @@ def write(self, values):

return res

def _add_child(self, param_values, orphan=False, description=False, additionnal_commit_links=False, reference_parent=...):
def _add_child(self, param_values, orphan=False, description=False, additionnal_commit_links=False, reference_parent=..., link=False):
build_values = {key: value for key, value in param_values.items() if key not in self.params_id._fields}
param_values = {key: value for key, value in param_values.items() if key in self.params_id._fields}

Expand Down Expand Up @@ -588,17 +605,40 @@ def _add_child(self, param_values, orphan=False, description=False, additionnal_
}
param_values['config_data'] = new_config_data

return self.create({
'params_id': self.params_id.copy(param_values).id,
'parent_id': self.id,
params = self.params_id.copy(param_values)

build_values = {
'params_id': params.id,
'build_type': self.build_type,
'priority_level': self.priority_level,
'description': description,
'orphan_result': orphan,
'keep_host': self.keep_host,
'host': self.host if self.keep_host else False,
**build_values,
})
}

if link:
existing_builds = params.build_ids.filtered(lambda b: not b.parent_id)
if self.keep_host:
existing_builds = existing_builds.filtered(lambda b: b.host == self.host)
if existing_builds:
build = existing_builds.sorted('id')[-1]
# build.killable = False

if not existing_builds:
build = self.create(build_values)

link = self.env['runbot.build.link'].create({
'parent_id': self.id,
'child_id': build.id,
})
return link, build
else:
return self.create({
'parent_id': self.id,
**build_values,
})

@api.depends('params_id.version_id.name')
def _compute_dest(self):
Expand Down Expand Up @@ -661,6 +701,7 @@ def _compute_last_update(self):
def _compute_load_time(self):
for build in self:
build.load_time = sum([build.build_time] + [child.load_time for child in build.children_ids])
# TODO check, we don't count linked childrent in load time to avoid duplicate, but we need to ensure we take them into account

@api.depends('job_start')
def _compute_build_age(self):
Expand All @@ -687,8 +728,17 @@ def _rebuild(self, message=None):
'parent_id': self.parent_id.id,
'description': self.description,
})

new_build = self.create(values)

if self.parent_link_ids: # TODO check, should we forbid rebuild in some cases if we have multiple parents? Or just link to an active parent?
for link in self.parent_link_ids:
link.orphan_result = True
self.env['runbot.build.link'].create({
'parent_id': link.parent_id.id,
'child_id': new_build.id,
})

if self.parent_id:
self.orphan_result = True

Expand Down Expand Up @@ -1797,3 +1847,26 @@ def action_view_build_errors(self):
"name": "Build errors",
"view_mode": "list,form"
}


class BuildLink(models.Model):
_name = 'runbot.build.link'
_description = 'Runbot Build Link'
_order = 'id desc'

parent_id = fields.Many2one('runbot.build', string='Parent Build', required=True, ondelete='cascade')
child_id = fields.Many2one('runbot.build', string='Child Build', required=True, ondelete='cascade')
params_id = fields.Many2one('runbot.build.params', string='Params', related='child_id.params_id', store=True)
orphan_result = fields.Boolean(string='Orphan Result', help='If set, the result of the child build will not be taken into account for the parent build result')

@api.model_create_multi
def create(self, vals_list):
links = super().create(vals_list)
links.mapped('parent_id')._update_globals()
return links

def write(self, values):
res = super().write(values)
if 'orphan_result' in values or 'child_id' in values:
self.parent_id._update_globals()
return res
20 changes: 10 additions & 10 deletions runbot/models/build_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1052,7 +1052,7 @@ def get_reference_builds_for_versions(versions):
build._log('_run_configure_upgrade', 'No database found for pattern %s' % (upgrade_db.db_pattern), level='ERROR')

for db in valid_databases:
child = build._add_child({
_link, child = build._add_child({
'upgrade_to_build_id': None,
'upgrade_from_build_id': source.id,
'dump_db': db.id,
Expand All @@ -1062,7 +1062,7 @@ def get_reference_builds_for_versions(versions):
'version_id': target.params_id.version_id.id,
'trigger_id': None,
'dockerfile_id': target.params_id.dockerfile_id.id,
})
}, link=True)
source_description = source.params_id.version_id.name
target_description = target.params_id.version_id.name
if source in build.create_batch_id.slot_ids.build_id:
Expand All @@ -1075,14 +1075,14 @@ def get_reference_builds_for_versions(versions):
db.name,
)

if self.allow_similar_build_quick_result:
existing_done_build = next((build for build in child.params_id.build_ids.sorted('id') if build.global_state == 'done' and build.global_result == 'ok'), None)
if not existing_done_build and not build.create_batch_id.bundle_id.is_staging:
existing_done_build = next((build for build in child.params_id.build_ids.sorted('id') if build.global_state == 'done' and build.local_result not in ('skipped', 'killed') and not build.orphan_result), None)
if existing_done_build:
child._log('', 'A similar [build](%s) has been found, marking as done directly', existing_done_build.build_url, log_type='markdown')
child.local_state = 'done'
child.local_result = existing_done_build.local_result
# if self.allow_similar_build_quick_result:
# existing_done_build = next((build for build in child.params_id.build_ids.sorted('id') if build.global_state == 'done' and build.global_result == 'ok'), None)
# if not existing_done_build and not build.create_batch_id.bundle_id.is_staging:
# existing_done_build = next((build for build in child.params_id.build_ids.sorted('id') if build.global_state == 'done' and build.local_result not in ('skipped', 'killed') and not build.orphan_result), None)
# if existing_done_build:
# child._log('', 'A similar [build](%s) has been found, marking as done directly', existing_done_build.build_url, log_type='markdown')
# child.local_state = 'done'
# child.local_result = existing_done_build.local_result

def _filter_upgrade_database(self, dbs, pattern):
pat_list = pattern.split(',') if pattern else []
Expand Down
23 changes: 22 additions & 1 deletion runbot/models/runbot.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,30 @@ def _gc_testing(self, host):
if available_slots > 0 or nb_pending == 0:
return

killable_build = []

for build in testing_builds:
if build.top_parent.killable:
build.top_parent._ask_kill(message='Build automatically killed, new build found.')
killable_build.append(build.top_parent)
continue
# a build can have multiple parents but no direct parent id
# in this cas we can kill under two conditions:
# - the build is not linked to any parent non killable build (or the link are orphan_result)
# - the build won't be linked again to any
if not build.parent_id and build.parent_link_ids:
has_alive_parent = not all(l.orphan_result or l.parent_id.top_parent.killable for l in build.parent_link_ids)
if not has_alive_parent:
top_parents = build.linked_parent_build_ids.top_parent
candidate_batches = top_parents.slot_ids.batch_id.bundle_id.last_batch.filtered(lambda b: b.state in ['preparing', 'ready'])
if any(candidate_batche.state == 'preparing' for candidate_batche in candidate_batches):
continue
if any(batch.slot_ids.filtered(lambda s: s.trigger_id in top_parents.trigger_id and (not s.build_id or s.build_id.local_state != 'done')) for batch in candidate_batches):
continue
killable_build.append(build.top_parent)
continue

for build in killable_build:
build._ask_kill(message='Build automatically killed, new build found.')

def _reload_nginx(self):
env = self.env
Expand Down
3 changes: 3 additions & 0 deletions runbot/security/ir.model.access.csv
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ access_runbot_docker_layer_admin,access_runbot_docker_layer_admin,runbot.model_r
access_runbot_docker_build_result_public,access_runbot_docker_build_result_public,runbot.model_runbot_docker_build_result,runbot.base_runbot_model_access,1,0,0,0
access_runbot_docker_build_result_admin,access_runbot_docker_build_result_admin,runbot.model_runbot_docker_build_result,runbot.group_runbot_admin,1,1,1,1

runbot.access_runbot_build_link,access_runbot_build_link,runbot.model_runbot_build_link,base.group_user,1,0,0,0
runbot.access_runbot_build_link_admin,access_runbot_build_link_admin,runbot.model_runbot_build_link,base.group_runbot_admin,1,1,1,1

access_runbot_codeowner_admin,runbot_codeowner_admin,runbot.model_runbot_codeowner,runbot.group_runbot_admin,1,1,1,1
access_runbot_codeownermanager,runbot_codeowner_manager,runbot.model_runbot_codeowner,runbot.group_runbot_codeowner_manager,1,1,1,1
access_runbot_codeowner_user,runbot_codeowner_user,runbot.model_runbot_codeowner,group_user,1,0,0,0
Expand Down
1 change: 1 addition & 0 deletions runbot/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from . import test_build_error
from . import test_branch
from . import test_build
from . import test_build_link
from . import test_schedule
from . import test_build_config_step
from . import test_event
Expand Down
1 change: 1 addition & 0 deletions runbot/tests/test_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,7 @@ def get_log(message):
'File &#34;<a href="https://False/blob/dfdfcfcf0000ffffffffffffffffffffffffffff/addons/web/tests/test_web.py" target="_blank" class="subtle_link">/data/build/odoo/addons/web/tests/test_web.py</a>&#34;, in test_web'
)


class TestGc(RunbotCaseMinimalSetup):

def test_repo_gc_testing(self):
Expand Down
Loading