mirror of
https://github.com/odoo/runbot.git
synced 2025-03-15 15:35:46 +07:00
Merge pull request #21 from odoo/master-other-improvements-ged
[MERGE] runbot: share workers between repos + lint/improve docstrings and variable names
This commit is contained in:
commit
305c9ba1fc
@ -1 +1,2 @@
|
||||
import runbot
|
||||
import res_config
|
||||
|
@ -11,6 +11,7 @@
|
||||
},
|
||||
'data': [
|
||||
'runbot.xml',
|
||||
'res_config_view.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'installable': True,
|
||||
|
57
runbot/res_config.py
Normal file
57
runbot/res_config.py
Normal file
@ -0,0 +1,57 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import fields, osv
|
||||
|
||||
class runbot_config_settings(osv.osv_memory):
|
||||
_name = 'runbot.config.settings'
|
||||
_inherit = 'res.config.settings'
|
||||
_columns = {
|
||||
'default_workers': fields.integer('Total Number of Workers'),
|
||||
'default_running_max': fields.integer('Maximum Number of Running Builds'),
|
||||
'default_timeout': fields.integer('Default timeout (in seconds)'),
|
||||
}
|
||||
|
||||
def get_default_parameters(self, cr, uid, fields, context=None):
|
||||
icp = self.pool['ir.config_parameter']
|
||||
workers = icp.get_param(cr, uid, 'runbot.workers', default=6)
|
||||
running_max = icp.get_param(cr, uid, 'runbot.running_max', default=75)
|
||||
timeout = icp.get_param(cr, uid, 'runbot.timeout', default=1800)
|
||||
return {
|
||||
'default_workers': int(workers),
|
||||
'default_running_max': int(running_max)
|
||||
}
|
||||
|
||||
def set_default_parameters(self, cr, uid, ids, context=None):
|
||||
config = self.browse(cr, uid, ids[0], context)
|
||||
icp = self.pool['ir.config_parameter']
|
||||
icp.set_param(cr, uid, 'runbot.workers', config.default_workers)
|
||||
icp.set_param(cr, uid, 'runbot.running_max', config.default_running_max)
|
||||
icp.set_param(cr, uid, 'runbot.timeout', config.default_timeout)
|
||||
|
||||
_defaults = {
|
||||
'default_workers': 6,
|
||||
'default_running_max': 75,
|
||||
'default_timeout': 1800,
|
||||
}
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
47
runbot/res_config_view.xml
Normal file
47
runbot/res_config_view.xml
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="view_runbot_configuration" model="ir.ui.view">
|
||||
<field name="name">Configure Runbot</field>
|
||||
<field name="model">runbot.config.settings</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Configure Runbot" class= "oe_form_configuration">
|
||||
<header>
|
||||
<button string="Apply" type="object" name="execute" class="oe_highlight"/>
|
||||
or
|
||||
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
|
||||
</header>
|
||||
<separator string="Runbot"/>
|
||||
<group>
|
||||
<label for="id" string="Settings"/>
|
||||
<div>
|
||||
<div>
|
||||
<field name="default_workers" class="oe_inline"/>
|
||||
<label for="default_workers"/>
|
||||
</div>
|
||||
<div>
|
||||
<field name="default_running_max" class="oe_inline"/>
|
||||
<label for="default_running_max"/>
|
||||
</div>
|
||||
<div>
|
||||
<field name="default_timeout" class="oe_inline"/>
|
||||
<label for="default_timeout"/>
|
||||
</div>
|
||||
</div>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="action_runbot_configuration" model="ir.actions.act_window">
|
||||
<field name="name">Configure Runbot</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">runbot.config.settings</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">inline</field>
|
||||
</record>
|
||||
<menuitem id="menu_runbot_configuration" name="Runbot" parent="base.menu_config"
|
||||
sequence="19" action="action_runbot_configuration"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
283
runbot/runbot.py
283
runbot/runbot.py
@ -35,63 +35,63 @@ _logger = logging.getLogger(__name__)
|
||||
#----------------------------------------------------------
|
||||
|
||||
def log(*l, **kw):
|
||||
out = []
|
||||
for i in l:
|
||||
if not isinstance(i, basestring):
|
||||
i = repr(i)
|
||||
out.append(i)
|
||||
out += ["%s=%r" % (k, v) for k, v in kw.items()]
|
||||
out = [i if isinstance(i, basestring) else repr(i) for i in l] + \
|
||||
["%s=%r" % (k, v) for k, v in kw.items()]
|
||||
_logger.debug(' '.join(out))
|
||||
|
||||
def dashes(s):
|
||||
def dashes(string):
|
||||
"""Sanitize the input string"""
|
||||
for i in '~":\'':
|
||||
s = s.replace(i, "")
|
||||
string = string.replace(i, "")
|
||||
for i in '/_. ':
|
||||
s = s.replace(i, "-")
|
||||
return s
|
||||
string = string.replace(i, "-")
|
||||
return string
|
||||
|
||||
def mkdirs(dirs):
|
||||
for i in dirs:
|
||||
if not os.path.exists(i):
|
||||
os.makedirs(i)
|
||||
for d in dirs:
|
||||
if not os.path.exists(d):
|
||||
os.makedirs(d)
|
||||
|
||||
def grep(filename, s):
|
||||
def grep(filename, string):
|
||||
if os.path.isfile(filename):
|
||||
return open(filename).read().find(s) != -1
|
||||
return open(filename).read().find(string) != -1
|
||||
return False
|
||||
|
||||
_re_error = r'^(?:\d{4}-\d\d-\d\d \d\d:\d\d:\d\d,\d{3} \d+ (?:ERROR|CRITICAL) )|(?:Traceback \(most recent call last\):)$'
|
||||
_re_warning = r'^\d{4}-\d\d-\d\d \d\d:\d\d:\d\d,\d{3} \d+ WARNING '
|
||||
_re_job = re.compile('job_\d')
|
||||
|
||||
def rfind(filename, patern):
|
||||
def rfind(filename, pattern):
|
||||
"""Determine in something in filename matches the pattern"""
|
||||
if os.path.isfile(filename):
|
||||
p = re.compile(patern, re.M)
|
||||
regexp = re.compile(pattern, re.M)
|
||||
with open(filename, 'r') as f:
|
||||
if p.findall(f.read()):
|
||||
if regexp.findall(f.read()):
|
||||
return True
|
||||
return False
|
||||
|
||||
def lock(name):
|
||||
fd = os.open(name, os.O_CREAT | os.O_RDWR, 0600)
|
||||
def lock(filename):
|
||||
fd = os.open(filename, os.O_CREAT | os.O_RDWR, 0600)
|
||||
fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
|
||||
def locked(name):
|
||||
r = False
|
||||
def locked(filename):
|
||||
result = False
|
||||
try:
|
||||
fd = os.open(name, os.O_CREAT | os.O_RDWR, 0600)
|
||||
fd = os.open(filename, os.O_CREAT | os.O_RDWR, 0600)
|
||||
try:
|
||||
fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except IOError:
|
||||
r = True
|
||||
result = True
|
||||
os.close(fd)
|
||||
except OSError:
|
||||
r = False
|
||||
return r
|
||||
result = False
|
||||
return result
|
||||
|
||||
def nowait():
|
||||
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
|
||||
|
||||
def run(l, env=None):
|
||||
"""Run a command described by l in environment env"""
|
||||
log("run", l)
|
||||
env = dict(os.environ, **env) if env else None
|
||||
if isinstance(l, list):
|
||||
@ -111,14 +111,16 @@ def run(l, env=None):
|
||||
def now():
|
||||
return time.strftime(openerp.tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
|
||||
def dt2time(dt):
|
||||
return time.mktime(time.strptime(dt, openerp.tools.DEFAULT_SERVER_DATETIME_FORMAT))
|
||||
def dt2time(datetime):
|
||||
"""Convert datetime to time"""
|
||||
return time.mktime(time.strptime(datetime, openerp.tools.DEFAULT_SERVER_DATETIME_FORMAT))
|
||||
|
||||
def s2human(t):
|
||||
for m,u in [(86400,'d'),(3600,'h'),(60,'m')]:
|
||||
if t>=m:
|
||||
return str(int(t/m))+u
|
||||
return str(int(t))+"s"
|
||||
def s2human(time):
|
||||
"""Convert a time in second into an human readable string"""
|
||||
for delay, desc in [(86400,'d'),(3600,'h'),(60,'m')]:
|
||||
if time >= delay:
|
||||
return str(int(time / delay)) + desc
|
||||
return str(int(time)) + "s"
|
||||
|
||||
#----------------------------------------------------------
|
||||
# RunBot Models
|
||||
@ -129,22 +131,22 @@ class runbot_repo(osv.osv):
|
||||
_order = 'name'
|
||||
|
||||
def _get_path(self, cr, uid, ids, field_name, arg, context=None):
|
||||
wd = self.root(cr, uid)
|
||||
r = {}
|
||||
root = self.root(cr, uid)
|
||||
result = {}
|
||||
for repo in self.browse(cr, uid, ids, context=context):
|
||||
name = repo.name
|
||||
for i in '@:/':
|
||||
name = name.replace(i, '_')
|
||||
r[repo.id] = os.path.join(wd, 'repo', name)
|
||||
return r
|
||||
result[repo.id] = os.path.join(root, 'repo', name)
|
||||
return result
|
||||
|
||||
def _get_base(self, cr, uid, ids, field_name, arg, context=None):
|
||||
r = {}
|
||||
result = {}
|
||||
for repo in self.browse(cr, uid, ids, context=context):
|
||||
name = re.sub('.+@', '', repo.name)
|
||||
name = name.replace(':','/')
|
||||
r[repo.id] = name
|
||||
return r
|
||||
result[repo.id] = name
|
||||
return result
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Repository', required=True),
|
||||
@ -170,11 +172,12 @@ class runbot_repo(osv.osv):
|
||||
return domain
|
||||
|
||||
def root(self, cr, uid, context=None):
|
||||
"""Return root directory of repository"""
|
||||
default = os.path.join(os.path.dirname(__file__), 'static')
|
||||
root = self.pool.get('ir.config_parameter').get_param(cr, uid, 'runbot.root', default)
|
||||
return root
|
||||
return self.pool.get('ir.config_parameter').get_param(cr, uid, 'runbot.root', default)
|
||||
|
||||
def git(self, cr, uid, ids, cmd, context=None):
|
||||
"""Execute git command cmd"""
|
||||
for repo in self.browse(cr, uid, ids, context=context):
|
||||
cmd = ['git', '--git-dir=%s' % repo.path] + cmd
|
||||
_logger.info("git: %s", ' '.join(cmd))
|
||||
@ -189,22 +192,25 @@ class runbot_repo(osv.osv):
|
||||
p2.communicate()[0]
|
||||
|
||||
def github(self, cr, uid, ids, url, payload=None, delete=False, context=None):
|
||||
"""Return a http request to be sent to github"""
|
||||
for repo in self.browse(cr, uid, ids, context=context):
|
||||
mo = re.search('([^/]+)/([^/]+)/([^/]+)', repo.base)
|
||||
if mo:
|
||||
url = url.replace(':owner', mo.group(2))
|
||||
url = url.replace(':repo', mo.group(3))
|
||||
url = 'https://api.%s%s' % (mo.group(1),url)
|
||||
s = requests.Session()
|
||||
s.auth = (repo.token,'x-oauth-basic')
|
||||
s.headers.update({'Accept': 'application/vnd.github.she-hulk-preview+json'})
|
||||
if not repo.token:
|
||||
raise Exception('Repository does not have a token to authenticate')
|
||||
match_object = re.search('([^/]+)/([^/]+)/([^/]+)', repo.base)
|
||||
if match_object:
|
||||
url = url.replace(':owner', match_object.group(2))
|
||||
url = url.replace(':repo', match_object.group(3))
|
||||
url = 'https://api.%s%s' % (match_object.group(1),url)
|
||||
session = requests.Session()
|
||||
session.auth = (repo.token,'x-oauth-basic')
|
||||
session.headers.update({'Accept': 'application/vnd.github.she-hulk-preview+json'})
|
||||
if payload:
|
||||
r = s.post(url, data=simplejson.dumps(payload))
|
||||
response = session.post(url, data=simplejson.dumps(payload))
|
||||
elif delete:
|
||||
r = s.delete(url)
|
||||
response = session.delete(url)
|
||||
else:
|
||||
r = s.get(url)
|
||||
return r.json()
|
||||
response = session.get(url)
|
||||
return response.json()
|
||||
|
||||
def update(self, cr, uid, ids, context=None):
|
||||
for repo in self.browse(cr, uid, ids, context=context):
|
||||
@ -224,18 +230,17 @@ class runbot_repo(osv.osv):
|
||||
repo.git(['fetch', '-p', 'origin', '+refs/pull/*/head:refs/pull/*'])
|
||||
|
||||
fields = ['refname','objectname','authordate:iso8601','authorname','subject']
|
||||
fmt = "%00".join(["%("+i+")" for i in fields])
|
||||
out = repo.git(['for-each-ref', '--format', fmt, '--sort=-committerdate', 'refs/heads', 'refs/pull'])
|
||||
out = out.strip()
|
||||
refs = []
|
||||
for l in out.split('\n'):
|
||||
ref = []
|
||||
for i in l.split('\x00'):
|
||||
try:
|
||||
ref.append(i.decode('utf-8'))
|
||||
except UnicodeDecodeError:
|
||||
ref.append('')
|
||||
refs.append(ref)
|
||||
fmt = "%00".join(["%("+field+")" for field in fields])
|
||||
git_refs = repo.git(['for-each-ref', '--format', fmt, '--sort=-committerdate', 'refs/heads', 'refs/pull'])
|
||||
git_refs = git_refs.strip()
|
||||
|
||||
def decode_utf(string):
|
||||
try:
|
||||
return field.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
return ''
|
||||
refs = [[decode_utf(field) for field in line.split('\x00')] for line in git_refs.split('\n')]
|
||||
|
||||
for name, sha, date, author, subject in refs:
|
||||
# create or get branch
|
||||
branch_ids = self.pool['runbot.branch'].search(cr, uid, [('repo_id', '=', repo.id), ('name', '=', name)])
|
||||
@ -256,73 +261,78 @@ class runbot_repo(osv.osv):
|
||||
Build.write(cr, uid, to_be_skipped_ids, {'state': 'done', 'result': 'skipped'})
|
||||
|
||||
_logger.debug('repo %s branch %s new build found revno %s', branch.repo_id.name, branch.name, sha)
|
||||
v = {
|
||||
build_info = {
|
||||
'branch_id': branch.id,
|
||||
'name': sha,
|
||||
'author': author,
|
||||
'subject': subject,
|
||||
}
|
||||
Build.create(cr, uid, v)
|
||||
Build.create(cr, uid, build_info)
|
||||
|
||||
# skip old builds (if their sequence number is too low, they will not ever be built)
|
||||
skippable_domain = [('repo_id', '=', repo.id), ('state', '=', 'pending')]
|
||||
to_be_skipped_ids = Build.search(cr, uid, skippable_domain, order='sequence desc', offset=repo.running)
|
||||
icp = self.pool['ir.config_parameter']
|
||||
running_max = int(icp.get_param(cr, uid, 'runbot.running_max', default=75))
|
||||
to_be_skipped_ids = Build.search(cr, uid, skippable_domain, order='sequence desc', offset=running_max)
|
||||
Build.write(cr, uid, to_be_skipped_ids, {'state': 'done', 'result': 'skipped'})
|
||||
|
||||
def scheduler(self, cr, uid, ids=None, context=None):
|
||||
for repo in self.browse(cr, uid, ids, context=context):
|
||||
bo = self.pool['runbot.build']
|
||||
dom = [('repo_id', '=', repo.id)]
|
||||
icp = self.pool['ir.config_parameter']
|
||||
workers = int(icp.get_param(cr, uid, 'runbot.workers', default=6))
|
||||
running_max = int(icp.get_param(cr, uid, 'runbot.running_max', default=75))
|
||||
|
||||
# schedule jobs
|
||||
build_ids = bo.search(cr, uid, dom + [('state', 'in', ['testing', 'running'])])
|
||||
bo.schedule(cr, uid, build_ids)
|
||||
Build = self.pool['runbot.build']
|
||||
domain = [('repo_id', 'in', ids)]
|
||||
|
||||
# launch new tests
|
||||
testing = bo.search_count(cr, uid, dom + [('state', '=', 'testing')])
|
||||
pending = bo.search_count(cr, uid, dom + [('state', '=', 'pending')])
|
||||
# schedule jobs (transitions testing -> running, kill jobs, ...)
|
||||
build_ids = Build.search(cr, uid, domain + [('state', 'in', ['testing', 'running'])])
|
||||
Build.schedule(cr, uid, build_ids)
|
||||
|
||||
while testing < repo.testing and pending > 0:
|
||||
# launch new tests
|
||||
testing = Build.search_count(cr, uid, domain + [('state', '=', 'testing')])
|
||||
pending = Build.search_count(cr, uid, domain + [('state', '=', 'pending')])
|
||||
|
||||
# find sticky pending build if any, otherwise, last pending (by id, not by sequence) will do the job
|
||||
pending_ids = bo.search(cr, uid, dom + [('state', '=', 'pending'), ('branch_id.sticky', '=', True)], limit=1)
|
||||
if not pending_ids:
|
||||
pending_ids = bo.search(cr, uid, dom + [('state', '=', 'pending')], order="sequence", limit=1)
|
||||
while testing < workers and pending > 0:
|
||||
|
||||
pending = bo.browse(cr, uid, pending_ids[0])
|
||||
pending.schedule()
|
||||
# find sticky pending build if any, otherwise, last pending (by id, not by sequence) will do the job
|
||||
pending_ids = Build.search(cr, uid, domain + [('state', '=', 'pending'), ('branch_id.sticky', '=', True)], limit=1)
|
||||
if not pending_ids:
|
||||
pending_ids = Build.search(cr, uid, domain + [('state', '=', 'pending')], order="sequence", limit=1)
|
||||
|
||||
# compute the number of testing and pending jobs again
|
||||
testing = bo.search_count(cr, uid, dom + [('state', '=', 'testing')])
|
||||
pending = bo.search_count(cr, uid, dom + [('state', '=', 'pending')])
|
||||
pending_build = Build.browse(cr, uid, pending_ids[0])
|
||||
pending_build.schedule()
|
||||
|
||||
# terminate and reap doomed build
|
||||
build_ids = bo.search(cr, uid, dom + [('state', '=', 'running')])
|
||||
# sort builds: the last build of each sticky branch then the rest
|
||||
sticky = {}
|
||||
non_sticky = []
|
||||
for build in bo.browse(cr, uid, build_ids):
|
||||
if build.branch_id.sticky and build.branch_id.id not in sticky:
|
||||
sticky[build.branch_id.id] = build.id
|
||||
else:
|
||||
non_sticky.append(build.id)
|
||||
build_ids = sticky.values()
|
||||
build_ids += non_sticky
|
||||
# terminate extra running builds
|
||||
bo.terminate(cr, uid, build_ids[repo.running:])
|
||||
bo.reap(cr, uid, build_ids)
|
||||
# compute the number of testing and pending jobs again
|
||||
testing = Build.search_count(cr, uid, domain + [('state', '=', 'testing')])
|
||||
pending = Build.search_count(cr, uid, domain + [('state', '=', 'pending')])
|
||||
|
||||
# terminate and reap doomed build
|
||||
build_ids = Build.search(cr, uid, domain + [('state', '=', 'running')])
|
||||
# sort builds: the last build of each sticky branch then the rest
|
||||
sticky = {}
|
||||
non_sticky = []
|
||||
for build in Build.browse(cr, uid, build_ids):
|
||||
if build.branch_id.sticky and build.branch_id.id not in sticky:
|
||||
sticky[build.branch_id.id] = build.id
|
||||
else:
|
||||
non_sticky.append(build.id)
|
||||
build_ids = sticky.values()
|
||||
build_ids += non_sticky
|
||||
# terminate extra running builds
|
||||
Build.terminate(cr, uid, build_ids[running_max:])
|
||||
Build.reap(cr, uid, build_ids)
|
||||
|
||||
def nginx(self, cr, uid, context=None):
|
||||
v = {}
|
||||
v['port'] = openerp.tools.config['xmlrpc_port']
|
||||
settings = {}
|
||||
settings['port'] = openerp.tools.config['xmlrpc_port']
|
||||
nginx_dir = os.path.join(self.root(cr, uid), 'nginx')
|
||||
v['nginx_dir'] = nginx_dir
|
||||
settings['nginx_dir'] = nginx_dir
|
||||
ids = self.search(cr, uid, [('nginx','=',True)], order='id')
|
||||
if ids:
|
||||
build_ids = self.pool['runbot.build'].search(cr, uid, [('repo_id','in',ids), ('state','=','running')])
|
||||
v['builds'] = self.pool['runbot.build'].browse(cr, uid, build_ids)
|
||||
settings['builds'] = self.pool['runbot.build'].browse(cr, uid, build_ids)
|
||||
|
||||
nginx_config = self.pool['ir.ui.view'].render(cr, uid, "runbot.nginx_config", v)
|
||||
nginx_config = self.pool['ir.ui.view'].render(cr, uid, "runbot.nginx_config", settings)
|
||||
mkdirs([nginx_dir])
|
||||
open(os.path.join(nginx_dir, 'nginx.conf'),'w').write(nginx_config)
|
||||
try:
|
||||
@ -335,10 +345,10 @@ class runbot_repo(osv.osv):
|
||||
|
||||
def killall(self, cr, uid, ids=None, context=None):
|
||||
# kill switch
|
||||
bo = self.pool['runbot.build']
|
||||
build_ids = bo.search(cr, uid, [('state', 'not in', ['done', 'pending'])])
|
||||
bo.terminate(cr, uid, build_ids)
|
||||
bo.reap(cr, uid, build_ids)
|
||||
Build = self.pool['runbot.build']
|
||||
build_ids = Build.search(cr, uid, [('state', 'not in', ['done', 'pending'])])
|
||||
Build.terminate(cr, uid, build_ids)
|
||||
Build.reap(cr, uid, build_ids)
|
||||
|
||||
def cron(self, cr, uid, ids=None, context=None):
|
||||
ids = self.search(cr, uid, [('auto', '=', True)])
|
||||
@ -387,6 +397,7 @@ class runbot_build(osv.osv):
|
||||
return r
|
||||
|
||||
def _get_time(self, cr, uid, ids, field_name, arg, context=None):
|
||||
"""Return the time taken by the tests"""
|
||||
r = {}
|
||||
for build in self.browse(cr, uid, ids, context=context):
|
||||
r[build.id] = 0
|
||||
@ -397,6 +408,7 @@ class runbot_build(osv.osv):
|
||||
return r
|
||||
|
||||
def _get_age(self, cr, uid, ids, field_name, arg, context=None):
|
||||
"""Return the time between job start and now"""
|
||||
r = {}
|
||||
for build in self.browse(cr, uid, ids, context=context):
|
||||
r[build.id] = 0
|
||||
@ -441,8 +453,8 @@ class runbot_build(osv.osv):
|
||||
}
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
bid = super(runbot_build, self).create(cr, uid, values, context=context)
|
||||
self.write(cr, uid, [bid], {'sequence' : bid}, context=context)
|
||||
build_id = super(runbot_build, self).create(cr, uid, values, context=context)
|
||||
self.write(cr, uid, [build_id], {'sequence' : build_id}, context=context)
|
||||
|
||||
def reset(self, cr, uid, ids, context=None):
|
||||
self.write(cr, uid, ids, { 'state' : 'pending' }, context=context)
|
||||
@ -454,9 +466,7 @@ class runbot_build(osv.osv):
|
||||
_logger.debug(*l)
|
||||
|
||||
def list_jobs(self):
|
||||
jobs = [i for i in dir(self) if i.startswith('job')]
|
||||
jobs.sort()
|
||||
return jobs
|
||||
return sorted(job for job in dir(self) if _re_job.match(job))
|
||||
|
||||
def find_port(self, cr, uid):
|
||||
# currently used port
|
||||
@ -530,6 +540,7 @@ class runbot_build(osv.osv):
|
||||
openerp.service.db._create_empty_database(dbname)
|
||||
|
||||
def cmd(self, cr, uid, ids, context=None):
|
||||
"""Return a list describing the command to start the build"""
|
||||
for build in self.browse(cr, uid, ids, context=context):
|
||||
# Server
|
||||
server_path = build.path("openerp-server")
|
||||
@ -542,12 +553,12 @@ class runbot_build(osv.osv):
|
||||
|
||||
# modules
|
||||
if build.repo_id.modules:
|
||||
mods = build.repo_id.modules
|
||||
modules = build.repo_id.modules
|
||||
else:
|
||||
l = glob.glob(build.server('addons', '*', '__init__.py'))
|
||||
mods = set(os.path.basename(os.path.dirname(i)) for i in l)
|
||||
mods = mods - set(['auth_ldap', 'document_ftp', 'hw_escpos', 'hw_proxy', 'hw_scanner', 'base_gengo', 'website_gengo'])
|
||||
mods = ",".join(list(mods))
|
||||
modules = set(os.path.basename(os.path.dirname(i)) for i in l)
|
||||
modules = modules - set(['auth_ldap', 'document_ftp', 'hw_escpos', 'hw_proxy', 'hw_scanner', 'base_gengo', 'website_gengo'])
|
||||
modules = ",".join(list(modules))
|
||||
|
||||
# commandline
|
||||
cmd = [
|
||||
@ -570,7 +581,7 @@ class runbot_build(osv.osv):
|
||||
#self.run_log(cmd, logfile=self.test_all_path)
|
||||
#run(["coverage","html","-d",self.coverage_base_path,"--ignore-errors","--include=*.py"],env={'COVERAGE_FILE': self.coverage_file_path})
|
||||
|
||||
return cmd, mods
|
||||
return cmd, modules
|
||||
|
||||
def spawn(self, cmd, lock_path, log_path, cpu_limit=None, shell=False, showstderr=False):
|
||||
def preexec_fn():
|
||||
@ -594,6 +605,7 @@ class runbot_build(osv.osv):
|
||||
return p.pid
|
||||
|
||||
def github_status(self, cr, uid, ids, context=None):
|
||||
"""Notify github of failed/successful builds"""
|
||||
for build in self.browse(cr, uid, ids, context=context):
|
||||
desc = "runbot build %s" % (build.dest,)
|
||||
if build.state == 'testing':
|
||||
@ -698,6 +710,7 @@ class runbot_build(osv.osv):
|
||||
return self.spawn(cmd, lock_path, log_path, cpu_limit=None, showstderr=True)
|
||||
|
||||
def force(self, cr, uid, ids, context=None):
|
||||
"""Force a rebuild"""
|
||||
for build in self.browse(cr, uid, ids, context=context):
|
||||
max_id = self.search(cr, uid, [('repo_id','=',build.repo_id.id)], order='id desc', limit=1)[0]
|
||||
# Force it now
|
||||
@ -717,6 +730,9 @@ class runbot_build(osv.osv):
|
||||
|
||||
def schedule(self, cr, uid, ids, context=None):
|
||||
jobs = self.list_jobs()
|
||||
icp = self.pool['ir.config_parameter']
|
||||
timeout = int(icp.get_param(cr, uid, 'runbot.timeout', default=1800))
|
||||
|
||||
for build in self.browse(cr, uid, ids, context=context):
|
||||
if build.state == 'pending':
|
||||
# allocate port and schedule first job
|
||||
@ -735,7 +751,7 @@ class runbot_build(osv.osv):
|
||||
lock_path = build.path('logs', '%s.lock' % build.job)
|
||||
if locked(lock_path):
|
||||
# kill if overpassed
|
||||
if build.job != jobs[-1] and build.job_time > 2400:
|
||||
if build.job != jobs[-1] and build.job_time > timeout:
|
||||
build.logger('%s time exceded (%ss)', build.job, build.job_time)
|
||||
build.kill()
|
||||
continue
|
||||
@ -842,6 +858,13 @@ class RunbotController(http.Controller):
|
||||
registry, cr, uid, context = request.registry, request.cr, 1, request.context
|
||||
branch_obj = registry['runbot.branch']
|
||||
build_obj = registry['runbot.build']
|
||||
icp = registry['ir.config_parameter']
|
||||
workers = icp.get_param(cr, uid, 'runbot.workers', default=6)
|
||||
running_max = icp.get_param(cr, uid, 'runbot.running_max', default=75)
|
||||
pending_total = build_obj.search_count(cr, uid, [('state','=','pending')])
|
||||
testing_total = build_obj.search_count(cr, uid, [('state','=','testing')])
|
||||
running_total = build_obj.search_count(cr, uid, [('state','=','running')])
|
||||
|
||||
v = self.common(cr, uid)
|
||||
# repo
|
||||
if not repo and v['repos']:
|
||||
@ -875,21 +898,27 @@ class RunbotController(http.Controller):
|
||||
branch_ids.append(br[0])
|
||||
|
||||
branches = branch_obj.browse(cr, uid, branch_ids, context=context)
|
||||
v['branches'] = []
|
||||
for branch in branches:
|
||||
build_ids = build_obj.search(cr, uid, [('branch_id','=',branch.id)], limit=4)
|
||||
branch.builds = build_obj.browse(cr, uid, build_ids, context=context)
|
||||
v['branches'] = branches
|
||||
v['branches'].append(branch)
|
||||
|
||||
# stats
|
||||
v['testing'] = build_obj.search(cr, uid, [('repo_id','=',repo.id), ('state','=','testing')], count=True)
|
||||
v['running'] = build_obj.search(cr, uid, [('repo_id','=',repo.id), ('state','=','running')], count=True)
|
||||
v['pending'] = build_obj.search(cr, uid, [('repo_id','=',repo.id), ('state','=','pending')], count=True)
|
||||
v['testing'] = build_obj.search_count(cr, uid, [('repo_id','=',repo.id), ('state','=','testing')])
|
||||
v['running'] = build_obj.search_count(cr, uid, [('repo_id','=',repo.id), ('state','=','running')])
|
||||
v['pending'] = build_obj.search_count(cr, uid, [('repo_id','=',repo.id), ('state','=','pending')])
|
||||
|
||||
v.update({
|
||||
'search': search,
|
||||
'limit': limit,
|
||||
'refresh': refresh,
|
||||
'repo': repo,
|
||||
'workers': workers,
|
||||
'running_max': running_max,
|
||||
'pending_total': pending_total,
|
||||
'running_total': running_total,
|
||||
'testing_total': testing_total,
|
||||
})
|
||||
return request.render("runbot.repo", v)
|
||||
|
||||
|
@ -266,7 +266,7 @@
|
||||
}
|
||||
</style>
|
||||
</t>
|
||||
<div class="container" style="width: 100%;">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class='col-md-12'>
|
||||
<nav class="navbar navbar-default" role="navigation">
|
||||
@ -278,40 +278,21 @@
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" t-attf-href="/runbot/{{'repo/' + slug(repo) if repo else ''}}"><b t-if="repo"><t t-esc="repo.base"/></b></a>
|
||||
<t t-if="repo">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><b style="font-size: 18px;"><t t-esc="repo.base"/></b><b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<t t-foreach='repos' t-as='repo'>
|
||||
<li><a t-attf-href="/runbot/repo/{{slug(repo)}}"><t t-esc="repo.base"/></a></li>
|
||||
</t>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</t>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<form class="navbar-form navbar-left form-inline">
|
||||
<div class="btn-group" t-if="repo">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
Filter <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li t-if="filters['pending']=='0'"><a t-att-href="qu(pending=1)">Pending</a></li>
|
||||
<li t-if="filters['pending']=='1'"><a t-att-href="qu(pending='0')"><i class="fa fa-check"/> Pending</a></li>
|
||||
<li t-if="filters['testing']=='0'"><a t-att-href="qu(testing=1)">Testing</a></li>
|
||||
<li t-if="filters['testing']=='1'"><a t-att-href="qu(testing='0')"><i class="fa fa-check"/> Testing</a></li>
|
||||
<li t-if="filters['running']=='0'"><a t-att-href="qu(running=1)">Running</a></li>
|
||||
<li t-if="filters['running']=='1'"><a t-att-href="qu(running='0')"><i class="fa fa-check"/> Running</a></li>
|
||||
<li t-if="filters['done']=='0'"><a t-att-href="qu(done=1)">Done</a></li>
|
||||
<li t-if="filters['done']=='1'"><a t-att-href="qu(done='0')"><i class="fa fa-check"/> Done</a></li>
|
||||
<li class="divider"></li>
|
||||
<li t-att-class="'active' if limit=='100' else ''"><a t-att-href="qu(limit=100)">Show last 100</a></li>
|
||||
<li t-att-class="'active' if limit=='1000' else ''"><a t-att-href="qu(limit=1000)">Show last 1000</a></li>
|
||||
<li t-att-class="'active' if limit=='10000' else ''"><a t-att-href="qu(limit=10000)">Show last 10000</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</form>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Switch repository <b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<t t-foreach='repos' t-as='repo'>
|
||||
<li><a t-attf-href="/runbot/repo/{{slug(repo)}}"><t t-esc="repo.base"/></a></li>
|
||||
</t>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<t t-if="repo">
|
||||
<form class="navbar-form navbar-right" role="search" t-att-action="qu(search='')" method="get">
|
||||
<div class="form-group">
|
||||
@ -319,6 +300,32 @@
|
||||
<button type="submit" class="btn btn-default">Search</button>
|
||||
</div>
|
||||
</form>
|
||||
<form class="navbar-form navbar-right form-inline">
|
||||
<div class="btn-group" t-if="repo">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
Filter <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li t-if="filters['pending']=='0'"><a t-att-href="qu(pending=1)">Pending</a></li>
|
||||
<li t-if="filters['pending']=='1'"><a t-att-href="qu(pending='0')"><i class="fa fa-check"/> Pending</a></li>
|
||||
<li t-if="filters['testing']=='0'"><a t-att-href="qu(testing=1)">Testing</a></li>
|
||||
<li t-if="filters['testing']=='1'"><a t-att-href="qu(testing='0')"><i class="fa fa-check"/> Testing</a></li>
|
||||
<li t-if="filters['running']=='0'"><a t-att-href="qu(running=1)">Running</a></li>
|
||||
<li t-if="filters['running']=='1'"><a t-att-href="qu(running='0')"><i class="fa fa-check"/> Running</a></li>
|
||||
<li t-if="filters['done']=='0'"><a t-att-href="qu(done=1)">Done</a></li>
|
||||
<li t-if="filters['done']=='1'"><a t-att-href="qu(done='0')"><i class="fa fa-check"/> Done</a></li>
|
||||
<li class="divider"></li>
|
||||
<li t-att-class="'active' if limit=='100' else ''"><a t-att-href="qu(limit=100)">Show last 100</a></li>
|
||||
<li t-att-class="'active' if limit=='1000' else ''"><a t-att-href="qu(limit=1000)">Show last 1000</a></li>
|
||||
<li t-att-class="'active' if limit=='10000' else ''"><a t-att-href="qu(limit=10000)">Show last 10000</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</form>
|
||||
<span class="navbar-brand navbar-right">
|
||||
<t t-esc="testing_total"/>/<t t-esc="workers"/> testing,
|
||||
<t t-esc="running_total"/>/<t t-esc="running_max"/> running,
|
||||
<t t-esc="pending_total"/> pending
|
||||
</span>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
@ -331,8 +338,9 @@
|
||||
<tr>
|
||||
<th>Branch</th>
|
||||
<td colspan="4" class="text-right">
|
||||
<t t-esc="testing"/>/<t t-esc="repo.testing"/> testing,
|
||||
<t t-esc="running"/>/<t t-esc="repo.running"/> running,
|
||||
<t t-esc="repo.base"/>:
|
||||
<t t-esc="testing"/> testing,
|
||||
<t t-esc="running"/> running,
|
||||
<t t-esc="pending"/> pending.
|
||||
</td>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user