From 5179e7edb4ae201cb6bd1ffbb3adab5e48ad2ff8 Mon Sep 17 00:00:00 2001 From: tarteo Date: Thu, 16 Apr 2026 15:21:26 +0200 Subject: [PATCH] [IMP] membership_activity_gitlab: retry on 502 and 504 errors --- gitlab/models/gitlab.py | 6 ++- .../models/project_project.py | 45 +++++++++++++++---- membership_activity_gitlab/tests/__init__.py | 1 + .../tests/test_gitlab.py | 40 +++++++++++++++++ 4 files changed, 83 insertions(+), 9 deletions(-) create mode 100644 membership_activity_gitlab/tests/__init__.py create mode 100644 membership_activity_gitlab/tests/test_gitlab.py diff --git a/gitlab/models/gitlab.py b/gitlab/models/gitlab.py index b0f62885..e41a031a 100644 --- a/gitlab/models/gitlab.py +++ b/gitlab/models/gitlab.py @@ -19,7 +19,11 @@ def _compute_display_name(self): def get_server_connection(self): self.ensure_one() - gl = gitlab.Gitlab(url=self.url, private_token=self.private_token) + gl = gitlab.Gitlab( + url=self.url, + private_token=self.private_token, + retry_transient_errors=True, + ) if self.debug: gl.enable_debug() return gl diff --git a/membership_activity_gitlab/models/project_project.py b/membership_activity_gitlab/models/project_project.py index b038e71a..529b0dda 100644 --- a/membership_activity_gitlab/models/project_project.py +++ b/membership_activity_gitlab/models/project_project.py @@ -1,11 +1,33 @@ -from datetime import datetime, timezone import re +from datetime import datetime, timezone +from functools import wraps import dateutil from dateutil.relativedelta import relativedelta from odoo import api, exceptions, fields, models +from odoo.addons.queue_job.exception import RetryableJobError + +from gitlab import GitlabError + + +def use_gitlab(method): + @wraps(method) + def _wrap(self, *args, **kwargs): + # Gitlab (gitlab.com) can sometimes return 502 or 504 + try: + return method(self, *args, **kwargs) + except GitlabError as e: + if e.response_code in (502, 504): # Bad gateway or Gateway Timeout + raise RetryableJobError( + e.error_message, + seconds=60, # Retry after 1 minute + ) from e + raise e + + return _wrap + class Project(models.Model): _inherit = "project.project" @@ -22,9 +44,11 @@ def _constrain_gitlab_full_name(self): # Checks on format 'namespace/projectname' with any number of subgroups in the namespace but no trailing slash. # When creating a new project trailing _, - are not allowed but there are present in existing projects # We keep the regex expression simple to mainly avoid trailing slashes - expression = r"^[^/]+(?:/[^/]+)*$" + expression = r"^[^/]+(?:/[^/]+)*$" for project in self: - if project.gitlab_full_name and not re.match(expression, project.gitlab_full_name): + if project.gitlab_full_name and not re.match( + expression, project.gitlab_full_name + ): raise exceptions.ValidationError( "Gitlab Fullname must be in the format 'namespace/projectname'" ) @@ -51,6 +75,7 @@ def get_gitlab_commits(self): 1, project.gitlab_id.per_page, since, until ) + @use_gitlab def get_gitlab_commits_iterated(self, page, per_page, since, until): self.ensure_one() gl = self.gitlab_id.get_server_connection() @@ -101,6 +126,7 @@ def get_gitlab_merge_requests(self): 1, project.gitlab_id.per_page, since, until ) + @use_gitlab def get_gitlab_merge_requests_iterated(self, page, per_page, since, until): self.ensure_one() gl = self.gitlab_id.get_server_connection() @@ -123,7 +149,7 @@ def get_gitlab_merge_requests_iterated(self, page, per_page, since, until): ("url", "=", merge_request.web_url), ("type_id", "=", activity_type.id), ("gitlab_username", "=", merge_request.author["username"]), - ("date", "=", date) + ("date", "=", date), ] ): continue @@ -157,6 +183,7 @@ def get_gitlab_issues(self): 1, project.gitlab_id.per_page, since, until ) + @use_gitlab def get_gitlab_issues_iterated(self, page, per_page, since, until): self.ensure_one() gl = self.gitlab_id.get_server_connection() @@ -176,10 +203,10 @@ def get_gitlab_issues_iterated(self, page, per_page, since, until): # Check if the issue already exists if self.env["membership.activity"].search_count( [ - ("url", "=", issue.web_url), + ("url", "=", issue.web_url), ("type_id", "=", activity_type.id), ("gitlab_username", "=", issue.author["username"]), - ("date", "=", date) + ("date", "=", date), ] ): continue @@ -213,6 +240,7 @@ def get_gitlab_notes(self): 1, project.gitlab_id.per_page, since, until ) + @use_gitlab def get_gitlab_notes_iterated(self, page, per_page, since, until): self.ensure_one() gl = self.gitlab_id.get_server_connection() @@ -238,7 +266,7 @@ def get_gitlab_notes_iterated(self, page, per_page, since, until): [ ("type_id", "=", activity_type.id), ("gitlab_username", "=", note["author"]["username"]), - ("date", "=", date) + ("date", "=", date), ] ): continue @@ -272,6 +300,7 @@ def get_gitlab_approvals(self): 1, project.gitlab_id.per_page, since, until ) + @use_gitlab def get_gitlab_approvals_iterated(self, page, per_page, since, until): self.ensure_one() gl = self.gitlab_id.get_server_connection() @@ -294,7 +323,7 @@ def get_gitlab_approvals_iterated(self, page, per_page, since, until): [ ("type_id", "=", activity_type.id), ("gitlab_username", "=", event.author_username), - ("date", "=", date) + ("date", "=", date), ] ): continue diff --git a/membership_activity_gitlab/tests/__init__.py b/membership_activity_gitlab/tests/__init__.py new file mode 100644 index 00000000..15d04594 --- /dev/null +++ b/membership_activity_gitlab/tests/__init__.py @@ -0,0 +1 @@ +from . import test_gitlab diff --git a/membership_activity_gitlab/tests/test_gitlab.py b/membership_activity_gitlab/tests/test_gitlab.py new file mode 100644 index 00000000..0c773594 --- /dev/null +++ b/membership_activity_gitlab/tests/test_gitlab.py @@ -0,0 +1,40 @@ +from unittest.mock import patch + +from odoo.tests.common import TransactionCase + +from odoo.addons.membership_activity_gitlab.models.project_project import use_gitlab +from odoo.addons.queue_job.exception import RetryableJobError + +from gitlab import GitlabError + + +class TestGitlab(TransactionCase): + def test_retryable_error_on_502(self): + """Test that a RetryableJobError is raised when a 502 error occurs.""" + + # Mock get_gitlab_commits_iterated to raise a GitlabError with a 502 response code + project = self.env["project.project"].create( + { + "name": "Test Project", + "gitlab_id": self.env["gitlab"] + .create( + { + "url": "https://gitlab.com", + "private_token": "fake_token", + } + ) + .id, + } + ) + + @use_gitlab + def mock_get_gitlab_commits_iterated(self, page, per_page, since, until): + raise GitlabError("Bad Gateway", response_code=502) + + with patch( + "odoo.addons.membership_activity_gitlab.models." + "project_project.Project.get_gitlab_commits_iterated", + new=mock_get_gitlab_commits_iterated, + ): + with self.assertRaises(RetryableJobError): + project.get_gitlab_commits_iterated(1, 100, "2024-01-01", "2024-01-31")