[IMP] runbot: add kill button (+ deathrow state)

Allow logged users to kill builds that are pending or testing.

The kill button solves the following problem:

When you have a community and enterprise branch with pull request.
The enterprise PR gets fetched before the branch by runbot, so it
builds with the community master branch instead of the corresponding
community branch. As the branch shares the same HEAD as the PR,
so no other build is done for the branch.

Previously you had to wait the build to finish building, then rebuild it
from the branch, taking twice the time necessary. Now you'll be able to
kill & rebuild it just after pushing the branch.

Also having a kill button is globally a nice to have feature.
This commit is contained in:
Denis Vermylen 2017-06-23 13:57:36 +02:00 committed by Christophe Simonis
parent a3d30e9d3c
commit fb1e1dfd53
3 changed files with 48 additions and 10 deletions

View File

@ -375,7 +375,7 @@ class runbot_repo(osv.osv):
domain_host = domain + [('host', '=', host)]
# schedule jobs (transitions testing -> running, kill jobs, ...)
build_ids = Build.search(cr, uid, domain_host + [('state', 'in', ['testing', 'running'])])
build_ids = Build.search(cr, uid, domain_host + [('state', 'in', ['testing', 'running', 'deathrow'])])
Build._schedule(cr, uid, build_ids)
# launch new tests
@ -598,9 +598,9 @@ class runbot_build(osv.osv):
'subject': fields.text('Subject'),
'sequence': fields.integer('Sequence', select=1),
'modules': fields.char("Modules to Install"),
'result': fields.char('Result'), # ok, ko, warn, skipped, killed
'result': fields.char('Result'), # ok, ko, warn, skipped, killed, manually_killed
'pid': fields.integer('Pid'),
'state': fields.char('Status'), # pending, testing, running, done, duplicate
'state': fields.char('Status'), # pending, testing, running, done, duplicate, deathrow
'job': fields.char('Job'), # job_*
'job_start': fields.datetime('Job start'),
'job_end': fields.datetime('Job end'),
@ -1128,7 +1128,10 @@ class runbot_build(osv.osv):
default_timeout = int(icp.get_param(cr, uid, 'runbot.timeout', default=1800)) / 60
for build in self.browse(cr, uid, ids, context=context):
if build.state == 'pending':
if build.state == 'deathrow':
build._kill(result='manually_killed')
continue
elif build.state == 'pending':
# allocate port and schedule first job
port = self._find_port(cr, uid)
values = {
@ -1271,6 +1274,16 @@ class runbot_build(osv.osv):
build._github_status()
build._local_cleanup()
def _ask_kill(self, cr, uid, ids, context=None):
user = self.pool['res.users'].browse(cr, uid, uid, context=context)
for build in self.browse(cr, SUPERUSER_ID, ids, context=context):
if build.state == 'pending':
build._skip()
build.log('_ask_kill', 'Skipping build %s, requested by user %s (%s)' % build.dest, user.name, uid)
elif build.state in ['testing', 'running']:
build.write({'state': 'deathrow'})
build.log('_ask_kill', 'Killing build %s, requested by user %s (%s)' % build.dest, user.name, uid)
def _reap(self, cr, uid, ids):
while True:
try:
@ -1338,7 +1351,7 @@ class RunbotController(http.Controller):
build_ids = []
if repo:
filters = {key: post.get(key, '1') for key in ['pending', 'testing', 'running', 'done']}
filters = {key: post.get(key, '1') for key in ['pending', 'testing', 'running', 'done', 'deathrow']}
domain = [('repo_id','=',repo.id)]
domain += [('state', '!=', key) for key, value in filters.iteritems() if value == '0']
if search:
@ -1548,6 +1561,13 @@ class RunbotController(http.Controller):
repo_id = registry['runbot.build']._force(cr, uid, [int(build_id)])
return werkzeug.utils.redirect('/runbot/repo/%s' % repo_id + ('?search=%s' % search if search else ''))
@http.route(['/runbot/build/<build_id>/kill'], type='http', auth="user", methods=['POST'], csrf=False)
def build_ask_kill(self, build_id, search=None, **post):
registry, cr, uid = request.registry, request.cr, request.uid
build = registry['runbot.build'].browse(cr, uid, build_id)
build._ask_kill()
return werkzeug.utils.redirect('/runbot/repo/%s' % build.repo_id + ('?search=%s' % search if search else ''))
@http.route([
'/runbot/badge/<int:repo_id>/<branch>.svg',
'/runbot/badge/<any(default,flat):theme>/<int:repo_id>/<branch>.svg',
@ -1558,7 +1578,7 @@ class RunbotController(http.Controller):
('branch_id.branch_name', '=', branch),
('branch_id.sticky', '=', True),
('state', 'in', ['testing', 'running', 'done']),
('result', '!=', 'skipped'),
('result', 'not in', ['skipped', 'manually_killed']),
]
last_update = '__last_update'

View File

@ -187,6 +187,7 @@
<filter string="Running" domain="[('state','=', 'running')]"/>
<filter string="Done" domain="[('state','=','done')]"/>
<filter string="Duplicate" domain="[('state','=', 'duplicate')]"/>
<filter string="Deathrow" domain="[('state','=', 'deathrow')]"/>
<separator />
<group expand="0" string="Group By...">
<filter string="Repo" domain="[]" context="{'group_by':'repo_id'}"/>
@ -254,6 +255,7 @@
</template>
<template id="runbot.build_name">
<t t-if="bu['state']=='deathrow'"><i class="text-info fa fa-crosshairs"/> killing</t>
<t t-if="bu['state']=='pending'"><i class="text-default fa fa-pause"/> pending</t>
<t t-if="bu['state']=='testing'"><i class="text-info fa fa-spinner"/> testing <t t-esc="bu['job']"/> <small t-if="not hide_time"><t t-esc="bu['job_time']"/></small></t>
<t t-if="bu['result']=='ok'"><i class="text-success fa fa-thumbs-up"/><small t-if="not hide_time"> age <t t-esc="bu['job_age']"/> time <t t-esc="bu['job_time']"/></small></t>
@ -261,6 +263,7 @@
<t t-if="bu['result']=='warn'"><i class="text-warning fa fa-warning"/><small t-if="not hide_time"> age <t t-esc="bu['job_age']"/> time <t t-esc="bu['job_time']"/></small></t>
<t t-if="bu['result']=='skipped'"><i class="text-danger fa fa-ban"/> skipped</t>
<t t-if="bu['result']=='killed'"><i class="text-danger fa fa-times"/> killed</t>
<t t-if="bu['result']=='manually_killed'"><i class="text-danger fa fa-times"/> manually killed</t>
<t t-if="bu['server_match'] in ('default', 'fuzzy')">
<i class="text-warning fa fa-question-circle fa-fw"
@ -284,9 +287,12 @@
<li><a t-attf-href="http://{{bu['domain']}}/?db={{bu['real_dest']}}-base">Connect base <i class="fa fa-sign-in"></i></a></li>
<li><a t-attf-href="http://{{bu['domain']}}/">Connect <i class="fa fa-sign-in"></i></a></li>
</t>
<li t-if="bu['state'] in ['done','running'] and bu_index==0" groups="runbot.group_user">
<li t-if="bu['state'] in ['done','running','deathrow'] and bu_index==0" groups="runbot.group_user">
<a href="#" class="runbot-rebuild" t-att-data-runbot-build="bu['id']">Rebuild <i class="fa fa-refresh"/></a>
</li>
<li t-if="bu['state'] in ['pending','testing','running']" groups="runbot.group_user">
<a href="#" class="runbot-kill" t-att-data-runbot-build="bu['id']">Kill <i class="fa fa-crosshairs"/></a>
</li>
<li t-if="bu['state']!='testing' and bu['state']!='pending'" class="divider"></li>
<li><a t-attf-href="/runbot/build/{{bu['id']}}">Logs <i class="fa fa-file-text-o"/></a></li>
<li t-if="bu['host']"><a t-attf-href="http://{{bu['host']}}/runbot/static/build/#{bu['real_dest']}/logs/job_10_test_base.txt">Full base logs <i class="fa fa-file-text-o"/></a></li>
@ -413,11 +419,12 @@
<t t-foreach="br['builds']" t-as="bu">
<t t-if="bu['state']=='pending'"><t t-set="klass">default</t></t>
<t t-if="bu['state']=='testing'"><t t-set="klass">info</t></t>
<t t-if="bu['state']=='deathrow'"><t t-set="klass">default</t></t>
<t t-if="bu['state'] in ['running','done'] and bu['result'] == 'ko'"><t t-set="klass">danger</t></t>
<t t-if="bu['state'] in ['running','done'] and bu['result'] == 'warn'"><t t-set="klass">warning</t></t>
<t t-if="bu['state'] in ['running','done'] and bu['result'] == 'ok'"><t t-set="klass">success</t></t>
<t t-if="bu['state'] in ['running','done'] and bu['result'] == 'skipped'"><t t-set="klass">default</t></t>
<t t-if="bu['state'] in ['running','done'] and bu['result'] == 'killed'"><t t-set="klass">killed</t></t>
<t t-if="bu['state'] in ['running','done'] and bu['result'] in ['killed', 'manually_killed']"><t t-set="klass">killed</t></t>
<td t-attf-class="{{klass}}">
<t t-call="runbot.build_button"><t t-set="klass">btn-group-sm</t></t>
<t t-if="bu['subject']">
@ -489,11 +496,12 @@
<t t-foreach="br['builds']" t-as="bu">
<t t-if="bu['state']=='pending'"><t t-set="klass">default</t></t>
<t t-if="bu['state']=='testing'"><t t-set="klass">info</t></t>
<t t-if="bu['state']=='deathrow'"><t t-set="klass">default</t></t>
<t t-if="bu['state'] in ['running','done'] and bu['result'] == 'ko'"><t t-set="klass">danger</t></t>
<t t-if="bu['state'] in ['running','done'] and bu['result'] == 'warn'"><t t-set="klass">warning</t></t>
<t t-if="bu['state'] in ['running','done'] and bu['result'] == 'ok'"><t t-set="klass">success</t></t>
<t t-if="bu['state'] in ['running','done'] and bu['result'] == 'skipped'"><t t-set="klass">default</t></t>
<t t-if="bu['state'] in ['running','done'] and bu['result'] == 'killed'"><t t-set="klass">killed</t></t>
<t t-if="bu['state'] in ['running','done'] and bu['result'] in ['killed', 'manually_killed']"><t t-set="klass">killed</t></t>
<div t-attf-class="bg-{{klass}} col-md-4">
<i class="fa fa-at"></i>
<t t-esc="bu['author']"/>

View File

@ -4,7 +4,17 @@
$(function() {
$('a.runbot-rebuild').click(function() {
var $f = $('<form method="POST">'),
url = _.str.sprintf('/runbot/build/%s/force', $(this).data('runbot-build')) + window.location.search;
url = _.str.sprintf('/runbot/build/%s/force', $(this).data('runbot-build')) + window.location.search;
$f.attr('action', url);
$f.appendTo($('body'));
$f.submit();
return false;
});
});
$(function() {
$('a.runbot-kill').click(function() {
var $f = $('<form method="POST">'),
url = _.str.sprintf('/runbot/build/%s/kill', $(this).data('runbot-build')) + window.location.search;
$f.attr('action', url);
$f.appendTo($('body'));
$f.submit();