Skip to content

Commit 938b955

Browse files
authored
Merge pull request #11 from release-engineering/azure_detect_publishing
Azure: Add publishing collision detection
2 parents 8b06a89 + e7498fb commit 938b955

File tree

2 files changed

+96
-0
lines changed

2 files changed

+96
-0
lines changed

cloudpub/ms_azure/service.py

+18
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,23 @@ def submit_to_status(self, product_id: str, status: str) -> ConfigureStatus:
440440

441441
return self.configure(resource=submission)
442442

443+
def ensure_can_publish(self, product_id: str) -> None:
444+
"""
445+
Ensure the offer is not already being published.
446+
447+
Args:
448+
product_id (str)
449+
The product ID to check the offer's publishing status
450+
Raises:
451+
RuntimeError: whenever a publishing is already in progress.
452+
"""
453+
submission_targets = ["preview", "live"]
454+
455+
for target in submission_targets:
456+
sub = self.get_submission_state(product_id, state=target)
457+
if sub and sub.status and sub.status == "running":
458+
raise RuntimeError(f"The offer {product_id} is already being published to {target}")
459+
443460
def _get_plan_tech_config(self, product: Product, plan: PlanSummary) -> VMIPlanTechConfig:
444461
"""
445462
Return the VMIPlanTechConfig resource for the given product/plan.
@@ -761,6 +778,7 @@ def publish(self, metadata: AzurePublishingMetadata) -> None:
761778
# 5. Proceed to publishing if it was requested.
762779
if not metadata.keepdraft:
763780
self._logdiff(self.diff_offer(product))
781+
self.ensure_can_publish(product.id)
764782

765783
self._publish_preview(product, product_name)
766784

tests/ms_azure/test_service.py

+78
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,81 @@ def test_submit_to_status_not_found(
711711

712712
mock_configure.assert_not_called()
713713

714+
@pytest.mark.parametrize("target", ["preview", "live"])
715+
@mock.patch("cloudpub.ms_azure.AzureService.get_submission_state")
716+
def test_ensure_can_publish_success(
717+
self,
718+
mock_getsubst: mock.MagicMock,
719+
target: str,
720+
azure_service: AzureService,
721+
) -> None:
722+
submission = {
723+
"$schema": "https://product-ingestion.azureedge.net/schema/submission/2022-03-01-preview2", # noqa: E501
724+
"id": "submission/ffffffff-ffff-ffff-ffff-ffffffffffff/0",
725+
"product": "product/ffffffff-ffff-ffff-ffff-ffffffffffff",
726+
"target": {"targetType": target},
727+
"lifecycleState": "generallyAvailable",
728+
"status": "completed",
729+
"result": "succeeded",
730+
"created": "2024-07-04T22:06:16.2895521Z",
731+
}
732+
mock_getsubst.return_value = ProductSubmission.from_json(submission)
733+
734+
azure_service.ensure_can_publish("ffffffff-ffff-ffff-ffff-ffffffffffff")
735+
736+
# All targets are called by the method, it should pass all
737+
mock_getsubst.assert_has_calls(
738+
[
739+
mock.call("ffffffff-ffff-ffff-ffff-ffffffffffff", state="preview"),
740+
mock.call("ffffffff-ffff-ffff-ffff-ffffffffffff", state="live"),
741+
]
742+
)
743+
744+
@pytest.mark.parametrize("target", ["preview", "live"])
745+
@mock.patch("cloudpub.ms_azure.AzureService.get_submission_state")
746+
def test_ensure_can_publish_raises(
747+
self,
748+
mock_getsubst: mock.MagicMock,
749+
target: str,
750+
azure_service: AzureService,
751+
) -> None:
752+
next_target = {
753+
"preview": "live",
754+
"live": "preview",
755+
}
756+
sub1 = {
757+
"$schema": "https://product-ingestion.azureedge.net/schema/submission/2022-03-01-preview2", # noqa: E501
758+
"id": "submission/ffffffff-ffff-ffff-ffff-ffffffffffff/0",
759+
"product": "product/ffffffff-ffff-ffff-ffff-ffffffffffff",
760+
"target": {"targetType": next_target[target]},
761+
"lifecycleState": "generallyAvailable",
762+
"status": "completed",
763+
"result": "succeeded",
764+
"created": "2024-07-04T22:06:16.2895521Z",
765+
}
766+
sub2 = {
767+
"$schema": "https://product-ingestion.azureedge.net/schema/submission/2022-03-01-preview2", # noqa: E501
768+
"id": "submission/ffffffff-ffff-ffff-ffff-ffffffffffff/0",
769+
"product": "product/ffffffff-ffff-ffff-ffff-ffffffffffff",
770+
"target": {"targetType": target},
771+
"lifecycleState": "generallyAvailable",
772+
"status": "running",
773+
"result": "pending",
774+
"created": "2024-07-04T22:06:16.2895521Z",
775+
}
776+
if target == "preview":
777+
subs = [ProductSubmission.from_json(sub2), ProductSubmission.from_json(sub1)]
778+
else:
779+
subs = [ProductSubmission.from_json(sub1), ProductSubmission.from_json(sub2)]
780+
mock_getsubst.side_effect = subs
781+
782+
err = (
783+
f"The offer ffffffff-ffff-ffff-ffff-ffffffffffff is already being published to {target}"
784+
)
785+
786+
with pytest.raises(RuntimeError, match=err):
787+
azure_service.ensure_can_publish("ffffffff-ffff-ffff-ffff-ffffffffffff")
788+
714789
@mock.patch("cloudpub.ms_azure.AzureService.configure")
715790
@mock.patch("cloudpub.ms_azure.AzureService.submit_to_status")
716791
@mock.patch("cloudpub.ms_azure.service.update_skus")
@@ -1061,6 +1136,7 @@ def test_is_submission_in_preview(
10611136
assert res is True
10621137
mock_substt.assert_called_once_with(current.product_id, "live")
10631138

1139+
@mock.patch("cloudpub.ms_azure.AzureService.ensure_can_publish")
10641140
@mock.patch("cloudpub.ms_azure.AzureService.diff_offer")
10651141
@mock.patch("cloudpub.ms_azure.AzureService.configure")
10661142
@mock.patch("cloudpub.ms_azure.AzureService.submit_to_status")
@@ -1079,6 +1155,7 @@ def test_publish_live(
10791155
mock_submit: mock.MagicMock,
10801156
mock_configure: mock.MagicMock,
10811157
mock_diff_offer: mock.MagicMock,
1158+
mock_ensure_publish: mock.MagicMock,
10821159
product_obj: Product,
10831160
plan_summary_obj: PlanSummary,
10841161
metadata_azure_obj: AzurePublishingMetadata,
@@ -1138,3 +1215,4 @@ def test_publish_live(
11381215
mock.call(product_id=product_obj.id, status="live"),
11391216
]
11401217
mock_submit.assert_has_calls(submit_calls)
1218+
mock_ensure_publish.assert_called_once_with(product_obj.id)

0 commit comments

Comments
 (0)