From 3bf9b263f0bd80cd9c8896862ededc9543d02658 Mon Sep 17 00:00:00 2001 From: Xavier-Do Date: Mon, 6 Apr 2020 12:49:12 +0200 Subject: [PATCH] [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. --- runbot/models/build.py | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/runbot/models/build.py b/runbot/models/build.py index 71564907..001a595c 100644 --- a/runbot/models/build.py +++ b/runbot/models/build.py @@ -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 ..fields import JsonDictField 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.http import request 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) 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 + 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) @@ -1040,19 +1041,36 @@ class runbot_build(models.Model): """Notify each repo with a status""" self.ensure_one() if self.config_id.update_github_state: - repos = {b.repo_id for b in self.search([('name', '=', self.name)])} - for repo in repos: - _logger.debug("github updating %s status %s to %s in repo %s", status['context'], self.name, status['state'], repo.name) + repos_ids = {b.repo_id.id for b in self.search([('name', '=', self.name)])} + build_name = self.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: - repo._github('/repos/:owner/:repo/statuses/%s' % self.name, status, ignore_errors=True) - except Exception: - self._log('_github_status_notify_all', 'Status notification failed for "%s" in repo "%s"' % (self.name, repo.name)) + db_registry = registry(_dbname) + with api.Environment.manage(), db_registry.cursor() as cr: + 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): """Notify github of failed/successful builds""" for build in self: 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: runbot_domain = self.env['runbot.repo']._domain() desc = "runbot build %s" % (build.dest,) @@ -1076,7 +1094,11 @@ class runbot_build(models.Model): "description": desc, "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): self.ensure_one()