Skip to content

Commit a9955b6

Browse files
humitosstsewd
andauthored
Build: skip duplicated commands (#10573)
* Build: skip duplicated commands Sometimes it happens the web servers are congested and the builder retries the API call to save the command in the database. However, one of the first attempts finally ended up working resulting in the command being saved twice. This small chunk of code checks for the existence of the command before saving it into the database and logs a warning and return 204 if it already exists, instead of re-saving it again. It's not a perfect solution, but it could help under similar circumstances. Related #10567 * Test case for duplicated BuildCommandResult * Add `start_date` to the filter * Typo * Update readthedocs/api/v2/views/model_views.py Co-authored-by: Santos Gallegos <[email protected]> --------- Co-authored-by: Santos Gallegos <[email protected]>
1 parent 12cbb3d commit a9955b6

File tree

2 files changed

+50
-0
lines changed

2 files changed

+50
-0
lines changed

readthedocs/api/v2/views/model_views.py

+8
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,14 @@ def perform_create(self, serializer):
336336
build_api_key = self.request.build_api_key
337337
if not build_api_key.project.builds.filter(pk=build_pk).exists():
338338
raise PermissionDenied()
339+
340+
if BuildCommandResult.objects.filter(
341+
build=serializer.validated_data["build"],
342+
start_time=serializer.validated_data["start_time"],
343+
).exists():
344+
log.warning("Build command is duplicated. Skipping...")
345+
return
346+
339347
return super().perform_create(serializer)
340348

341349
def get_queryset_for_api_key(self, api_key):

readthedocs/rtd_tests/tests/test_api.py

+42
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,48 @@ def test_build_read_and_write_endpoints_for_build_api_token(self):
909909
resp = client.patch(f"/api/v2/build/{build.pk}/")
910910
self.assertEqual(resp.status_code, 404)
911911

912+
def test_build_commands_duplicated_command(self):
913+
"""Sending the same request twice should only create one BuildCommandResult."""
914+
project = get(
915+
Project,
916+
language="en",
917+
)
918+
version = project.versions.first()
919+
build = Build.objects.create(project=project, version=version)
920+
921+
self.assertEqual(BuildCommandResult.objects.count(), 0)
922+
923+
client = APIClient()
924+
_, build_api_key = BuildAPIKey.objects.create_key(project)
925+
client.credentials(HTTP_AUTHORIZATION=f"Token {build_api_key}")
926+
927+
now = timezone.now()
928+
start_time = now - datetime.timedelta(seconds=5)
929+
end_time = now
930+
931+
data = {
932+
"build": build.pk,
933+
"command": "git status",
934+
"description": "Git status",
935+
"exit_code": 0,
936+
"start_time": start_time,
937+
"end_time": end_time,
938+
}
939+
940+
response = client.post(
941+
"/api/v2/command/",
942+
data,
943+
format="json",
944+
)
945+
self.assertEqual(response.status_code, 201)
946+
response = client.post(
947+
"/api/v2/command/",
948+
data,
949+
format="json",
950+
)
951+
self.assertEqual(response.status_code, 201)
952+
self.assertEqual(BuildCommandResult.objects.count(), 1)
953+
912954
def test_build_commands_read_only_endpoints_for_normal_user(self):
913955
user_normal = get(User, is_staff=False)
914956
user_admin = get(User, is_staff=True)

0 commit comments

Comments
 (0)