Skip to content

Commit 80fc1ce

Browse files
committed
Azure: Retry on publishing when a conflict occurs
This commit changes the Azure module to retry publishing the VM image whenever a submission in progress/conflict happens. It will first attempt to change the target to `preview` or `live` for 3 times and then, if the exception comes as `ConflictError` or `RunningSubmissionError` it will restart the publishing task. Assisted-By: Cursor Signed-off-by: Jonathan Gangi <[email protected]>
1 parent 7396e69 commit 80fc1ce

File tree

2 files changed

+37
-5
lines changed

2 files changed

+37
-5
lines changed

cloudpub/ms_azure/service.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66

77
from deepdiff import DeepDiff
88
from requests import HTTPError
9-
from tenacity import retry
10-
from tenacity.retry import retry_if_result
9+
from tenacity import retry, wait_exponential
10+
from tenacity.retry import retry_if_exception_type, retry_if_result
1111
from tenacity.stop import stop_after_attempt, stop_after_delay
1212
from tenacity.wait import wait_chain, wait_fixed
1313

1414
from cloudpub.common import BaseService
15-
from cloudpub.error import InvalidStateError, NotFoundError
15+
from cloudpub.error import ConflictError, InvalidStateError, NotFoundError, RunningSubmissionError
1616
from cloudpub.models.ms_azure import (
1717
RESOURCE_MAPING,
1818
AzureResource,
@@ -38,6 +38,8 @@
3838
from cloudpub.ms_azure.session import PartnerPortalSession
3939
from cloudpub.ms_azure.utils import (
4040
AzurePublishingMetadata,
41+
check_for_conflict,
42+
check_for_running_submission,
4143
create_disk_version_from_scratch,
4244
is_azure_job_not_complete,
4345
is_sas_present,
@@ -175,6 +177,10 @@ def _wait_for_job_completion(self, job_id: str) -> ConfigureStatus:
175177
job_details = self._query_job_details(job_id=job_id)
176178
if job_details.job_result == "failed":
177179
error_message = f"Job {job_id} failed: \n{job_details.errors}"
180+
if check_for_conflict(job_details):
181+
self._raise_error(ConflictError, error_message)
182+
elif check_for_running_submission(job_details):
183+
self._raise_error(RunningSubmissionError, error_message)
178184
self._raise_error(InvalidStateError, error_message)
179185
elif job_details.job_result == "succeeded":
180186
log.debug(f"Job {job_id} succeeded")
@@ -548,7 +554,7 @@ def _publish_preview(self, product: Product, product_name: str) -> None:
548554
if res.job_result != 'succeeded' or not self.get_submission_state(
549555
product.id, state="preview"
550556
):
551-
errors = "\n".join(res.errors)
557+
errors = "\n".join(["%s: %s" % (error.code, error.message) for error in res.errors])
552558
failure_msg = (
553559
f"Failed to submit the product {product.id} to preview. "
554560
f"Status: {res.job_result} Errors: {errors}"
@@ -576,13 +582,19 @@ def _publish_live(self, product: Product, product_name: str) -> None:
576582
res = self.submit_to_status(product_id=product.id, status='live')
577583

578584
if res.job_result != 'succeeded' or not self.get_submission_state(product.id, state="live"):
579-
errors = "\n".join(res.errors)
585+
errors = "\n".join(["%s: %s" % (error.code, error.message) for error in res.errors])
580586
failure_msg = (
581587
f"Failed to submit the product {product.id} to live. "
582588
f"Status: {res.job_result} Errors: {errors}"
583589
)
584590
raise RuntimeError(failure_msg)
585591

592+
@retry(
593+
retry=retry_if_exception_type([ConflictError, RunningSubmissionError]),
594+
wait=wait_exponential(multiplier=1, min=60, max=60 * 60 * 24 * 7),
595+
stop=stop_after_attempt(3),
596+
reraise=True,
597+
)
586598
def publish(self, metadata: AzurePublishingMetadata) -> None:
587599
"""
588600
Associate a VM image with a given product listing (destination) and publish it if required.

cloudpub/ms_azure/utils.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# SPDX-License-Identifier: GPL-3.0-or-later
22
import logging
3+
import re
34
from operator import attrgetter
45
from typing import Any, Dict, List, Optional, Tuple
56

@@ -592,3 +593,22 @@ def logdiff(diff: DeepDiff) -> None:
592593
"""Log the offer diff if it exists."""
593594
if diff:
594595
log.warning(f"Found the following offer diff before publishing:\n{diff.pretty()}")
596+
597+
598+
def check_for_conflict(job_details: ConfigureStatus) -> bool:
599+
"""Check if the job details contain a conflict error."""
600+
err_lookup = r"The submission cannot be pushed to \w+ as its not the latest .*"
601+
for error in job_details.errors:
602+
if error.code == "conflict" and re.match(err_lookup, error.message):
603+
return True
604+
return False
605+
606+
607+
def check_for_running_submission(job_details: ConfigureStatus) -> bool:
608+
"""Check if the job details contain a running submission error."""
609+
err_lookup = r"An In Progress submission [0-9]+ already exists."
610+
for error in job_details.errors:
611+
err_msg = error.message
612+
if error.code == "internalServerError" and re.match(err_lookup, err_msg):
613+
return True
614+
return False

0 commit comments

Comments
 (0)