[FIX] runbot: send status only once.

Runbot can send status multiple time for the same hash:
- if transaction fails in scheduler and is retried
- if multiple subbuild are failing

Leading to multiple issues:
- when github receive more than one failure status, mergebot will
be notified multiple times and send multiple mail (for forward ports mainly)
- github will answer `422 Unprocessable Entity for url...` after
1000 status.

This fix proposes to limit number of status:
- By avoiding to send status for orphan build (parent status will never change)
- By storing last send status to avoid to notify multiple time
- By sending status post commit to avoid to contact  github in case of failure.
This will also slightly reduce transaction time by removing an http request.
This commit is contained in:
Xavier-Do 2020-04-06 12:49:12 +02:00 committed by Christophe Monniez
parent b517df4ff0
commit 3bf9b263f0

View File

@ -12,7 +12,7 @@ from ..common import dt2time, fqdn, now, grep, local_pgadmin_cursor, s2human, Co
from ..container import docker_build, docker_stop, docker_state, Command from ..container import docker_build, docker_stop, docker_state, Command
from ..fields import JsonDictField from ..fields import JsonDictField
from odoo.addons.runbot.models.repo import RunbotException from odoo.addons.runbot.models.repo import RunbotException
from odoo import models, fields, api from odoo import models, fields, api, registry
from odoo.exceptions import UserError, ValidationError from odoo.exceptions import UserError, ValidationError
from odoo.http import request from odoo.http import request
from odoo.tools import appdirs from odoo.tools import appdirs
@ -89,6 +89,7 @@ class runbot_build(models.Model):
global_result = fields.Selection(make_selection(result_order), string='Result', compute='_compute_global_result', store=True) global_result = fields.Selection(make_selection(result_order), string='Result', compute='_compute_global_result', store=True)
local_result = fields.Selection(make_selection(result_order), string='Build Result') local_result = fields.Selection(make_selection(result_order), string='Build Result')
triggered_result = fields.Selection(make_selection(result_order), string='Triggered Result') # triggered by db only triggered_result = fields.Selection(make_selection(result_order), string='Triggered Result') # triggered by db only
last_github_state = fields.Char('Last github status', readonly=True)
requested_action = fields.Selection([('wake_up', 'To wake up'), ('deathrow', 'To kill')], string='Action requested', index=True) requested_action = fields.Selection([('wake_up', 'To wake up'), ('deathrow', 'To kill')], string='Action requested', index=True)
@ -1040,19 +1041,36 @@ class runbot_build(models.Model):
"""Notify each repo with a status""" """Notify each repo with a status"""
self.ensure_one() self.ensure_one()
if self.config_id.update_github_state: if self.config_id.update_github_state:
repos = {b.repo_id for b in self.search([('name', '=', self.name)])} repos_ids = {b.repo_id.id for b in self.search([('name', '=', self.name)])}
for repo in repos: build_name = self.name
_logger.debug("github updating %s status %s to %s in repo %s", status['context'], self.name, status['state'], repo.name) user_id = self.env.user.id
_dbname = self.env.cr.dbname
_context = self.env.context
build_id = self.id
def send_github_status():
try: try:
repo._github('/repos/:owner/:repo/statuses/%s' % self.name, status, ignore_errors=True) db_registry = registry(_dbname)
except Exception: with api.Environment.manage(), db_registry.cursor() as cr:
self._log('_github_status_notify_all', 'Status notification failed for "%s" in repo "%s"' % (self.name, repo.name)) env = api.Environment(cr, user_id, _context)
repos = env['runbot.repo'].browse(repos_ids)
for repo in repos:
_logger.debug(
"github updating %s status %s to %s in repo %s",
status['context'], build_name, status['state'], repo.name)
repo._github('/repos/:owner/:repo/statuses/%s' % build_name, status, ignore_errors=True)
except:
_logger.exception('Something went wrong sending notification for %s', build_id)
self._cr.after('commit', send_github_status)
def _github_status(self): def _github_status(self):
"""Notify github of failed/successful builds""" """Notify github of failed/successful builds"""
for build in self: for build in self:
if build.parent_id: if build.parent_id:
build.parent_id._github_status() if build.orphan_result:
_logger.debug('Skipping result for orphan build %s', self.id)
else:
build.parent_id._github_status()
elif build.config_id.update_github_state: elif build.config_id.update_github_state:
runbot_domain = self.env['runbot.repo']._domain() runbot_domain = self.env['runbot.repo']._domain()
desc = "runbot build %s" % (build.dest,) desc = "runbot build %s" % (build.dest,)
@ -1076,7 +1094,11 @@ class runbot_build(models.Model):
"description": desc, "description": desc,
"context": "ci/runbot" "context": "ci/runbot"
} }
build._github_status_notify_all(status) if self.last_github_state != state:
build._github_status_notify_all(status)
self.last_github_state = state
else:
_logger.debug('Skipping unchanged status for %s', self.id)
def _next_job_values(self): def _next_job_values(self):
self.ensure_one() self.ensure_one()