Skip to content

Commit 907cb65

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 7ab4b23 commit 907cb65

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,
@@ -180,6 +182,10 @@ def _wait_for_job_completion(self, job_id: str) -> ConfigureStatus:
180182
job_details = self._query_job_details(job_id=job_id)
181183
if job_details.job_result == "failed":
182184
error_message = f"Job {job_id} failed: \n{job_details.errors}"
185+
if check_for_conflict(job_details):
186+
self._raise_error(ConflictError, error_message)
187+
elif check_for_running_submission(job_details):
188+
self._raise_error(RunningSubmissionError, error_message)
183189
self._raise_error(InvalidStateError, error_message)
184190
elif job_details.job_result == "succeeded":
185191
log.debug("Job %s succeeded", job_id)
@@ -558,7 +564,7 @@ def _publish_preview(self, product: Product, product_name: str) -> None:
558564
if res.job_result != 'succeeded' or not self.get_submission_state(
559565
product.id, state="preview"
560566
):
561-
errors = "\n".join(res.errors)
567+
errors = "\n".join(["%s: %s" % (error.code, error.message) for error in res.errors])
562568
failure_msg = (
563569
f"Failed to submit the product {product.id} to preview. "
564570
f"Status: {res.job_result} Errors: {errors}"
@@ -585,13 +591,19 @@ def _publish_live(self, product: Product, product_name: str) -> None:
585591
res = self.submit_to_status(product_id=product.id, status='live')
586592

587593
if res.job_result != 'succeeded' or not self.get_submission_state(product.id, state="live"):
588-
errors = "\n".join(res.errors)
594+
errors = "\n".join(["%s: %s" % (error.code, error.message) for error in res.errors])
589595
failure_msg = (
590596
f"Failed to submit the product {product.id} to live. "
591597
f"Status: {res.job_result} Errors: {errors}"
592598
)
593599
raise RuntimeError(failure_msg)
594600

601+
@retry(
602+
retry=retry_if_exception_type((ConflictError, RunningSubmissionError)),
603+
wait=wait_exponential(multiplier=1, min=60, max=60 * 60 * 24 * 7),
604+
stop=stop_after_attempt(3),
605+
reraise=True,
606+
)
595607
def publish(self, metadata: AzurePublishingMetadata) -> None:
596608
"""
597609
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

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

0 commit comments

Comments
 (0)