-
Notifications
You must be signed in to change notification settings - Fork 0
Azure: rework how conflict detection works #147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Reviewer's GuideThis PR reworks the AzureService.ensure_can_publish method to immediately detect and raise on any in-progress submission by iterating over all submissions, removes its long-running retry behavior, introduces a custom ConflictError exception, and updates tests to mock and assert against the new get_submissions API. Sequence diagram for updated conflict detection in AzureService.ensure_can_publishsequenceDiagram
participant AzureService
participant Logger
participant Submission
participant ConflictError
AzureService->>Logger: log.info("Ensuring no other publishing jobs are in progress")
AzureService->>AzureService: get_submissions(product_id)
loop For each Submission
AzureService->>Submission: Check status
alt Submission status is not "completed"
AzureService->>Logger: log.error(conflict message)
AzureService->>ConflictError: raise ConflictError(conflict message)
end
end
Class diagram for ConflictError addition and AzureService changesclassDiagram
class AzureService {
+ensure_can_publish(product_id: str)
-get_submissions(product_id)
}
class ConflictError {
"Report a submission conflict error."
}
AzureService ..> ConflictError : raises
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes - here's some feedback:
- Consider filtering out non‐blocking statuses (e.g. failed or canceled) in ensure_can_publish so that only truly in‐progress submissions raise a ConflictError, avoiding false positives.
- To improve maintainability and testability, extract the iteration and conflict-detection logic in ensure_can_publish into a small helper method.
- Querying all submissions on every publish could get expensive at scale—consider adding server-side filters or pagination to limit the results.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider filtering out non‐blocking statuses (e.g. failed or canceled) in ensure_can_publish so that only truly in‐progress submissions raise a ConflictError, avoiding false positives.
- To improve maintainability and testability, extract the iteration and conflict-detection logic in ensure_can_publish into a small helper method.
- Querying all submissions on every publish could get expensive at scale—consider adding server-side filters or pagination to limit the results.
## Individual Comments
### Comment 1
<location> `tests/ms_azure/test_service.py:752-753` </location>
<code_context>
- @pytest.mark.parametrize("target", ["preview", "live"])
- @mock.patch("cloudpub.ms_azure.AzureService.get_submission_state")
+ @mock.patch("cloudpub.ms_azure.AzureService.get_submissions")
def test_ensure_can_publish_success(
self,
- mock_getsubst: mock.MagicMock,
</code_context>
<issue_to_address>
**suggestion (testing):** Missing test for error conditions in ensure_can_publish.
Add a test to confirm that ensure_can_publish raises ConflictError when the submission status is not 'completed', such as 'running' or 'pending'.
</issue_to_address>
### Comment 2
<location> `tests/ms_azure/test_service.py:758-767` </location>
<code_context>
+ submissions = [
</code_context>
<issue_to_address>
**suggestion (testing):** Test does not cover edge cases for get_submissions return values.
Add tests for cases where get_submissions returns an empty list, None, or submissions with unexpected status or result values to ensure ensure_can_publish is robust.
Suggested implementation:
```python
submissions = [
{
"$schema": "https://product-ingestion.azureedge.net/schema/submission/2022-03-01-preview2", # noqa: E501
"id": "submission/ffffffff-ffff-ffff-ffff-ffffffffffff/0",
"product": "product/ffffffff-ffff-ffff-ffff-ffffffffffff",
"target": {"targetType": tgt},
"lifecycleState": "generallyAvailable",
"status": "completed",
"result": "succeeded",
"created": "2024-07-04T22:06:16.2895521Z",
}
]
def test_ensure_can_publish_empty_list(monkeypatch):
def mock_get_submissions(*args, **kwargs):
return []
monkeypatch.setattr("ms_azure.service.get_submissions", mock_get_submissions)
# Replace with actual call to ensure_can_publish and expected assertion
# Example:
# assert not ensure_can_publish(...)
def test_ensure_can_publish_none(monkeypatch):
def mock_get_submissions(*args, **kwargs):
return None
monkeypatch.setattr("ms_azure.service.get_submissions", mock_get_submissions)
# Replace with actual call to ensure_can_publish and expected assertion
# Example:
# assert not ensure_can_publish(...)
def test_ensure_can_publish_unexpected_status(monkeypatch):
def mock_get_submissions(*args, **kwargs):
return [
{
"status": "failed",
"result": "unknown"
}
]
monkeypatch.setattr("ms_azure.service.get_submissions", mock_get_submissions)
# Replace with actual call to ensure_can_publish and expected assertion
# Example:
# assert not ensure_can_publish(...)
def test_ensure_can_publish_unexpected_result(monkeypatch):
def mock_get_submissions(*args, **kwargs):
return [
{
"status": "completed",
"result": "unexpected_value"
}
]
monkeypatch.setattr("ms_azure.service.get_submissions", mock_get_submissions)
# Replace with actual call to ensure_can_publish and expected assertion
# Example:
# assert not ensure_can_publish(...)
```
You will need to:
- Replace the comments in each test with the actual call to `ensure_can_publish` and the expected assertion, based on your implementation.
- Ensure that the `monkeypatch` target string matches the import path for `get_submissions` in your codebase.
- If you use fixtures or a different mocking strategy, adapt the test setup accordingly.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
This commit changes the existing code of `AzureService.ensure_can_publish` to behave as follows: 1. It no longer retries on error but simply raises whenever a conflict is detected 2. It no longer searches only for "live" or "preview" but for all existing submissions 3. It improves the logging mechanism to inform which state the submission was and its target. Rationale: 1. If we simply wait for the publishing to finish as the previous version (using retry) we could end up in a scenario where the changes made by the library are no longer the latest, causing the Graph API to return the error: `The submission cannot be pushed to as its not the latest` With that, we would need to retry the whole `publish` operations so it makes more sense to just raise `ConflictError` and let `pubtools` retry when this error is caught. 2. The previous code were not able to catch a lot of conflicts, resulting in many errors with `An In Progress submission already exists` By looking into each submission target we may caught conflicts early. 3. Logs will now show exactly what was the reason it was already in progress and for which submission target. 4. A retry can/will be implemented on `pubtools-marketplacesvm` on Azure `push` in case a `ConflictError` is detected. 5. Replaces !119 Refers to SPSTRAT-611 and SPSTRAT-549 Signed-off-by: Jonathan Gangi <jgangi@redhat.com>
|
Example of the idea: https://github.com/release-engineering/pubtools-marketplacesvm/pull/125/files |
| f"{sub.target.targetType}: {sub.status}/{sub.result}" | ||
| ) | ||
| log.error(msg) | ||
| raise ConflictError(msg) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we create similar "wait method" as we have for AWS (AWSProductService.wait_active_changesets)?
*
cloudpub/cloudpub/aws/service.py
Lines 283 to 307 in 075853a
| def wait_active_changesets(self, entity_id: str) -> None: | |
| """ | |
| Get the first active changeset, if there is one, and wait for it to finish. | |
| Args: | |
| entity_id (str) | |
| The Id of the entity to wait for active changesets | |
| """ | |
| def changeset_not_complete(change_set_list: List[ListChangeSet]) -> bool: | |
| if change_set_list: | |
| self.wait_for_changeset(change_set_list[0].id) | |
| return True | |
| else: | |
| return False | |
| r = Retrying( | |
| stop=stop_after_attempt(self.wait_for_changeset_attempts), | |
| retry=retry_if_result(changeset_not_complete), | |
| ) | |
| try: | |
| r(self.get_product_active_changesets, entity_id) | |
| except RetryError: | |
| self._raise_error(Timeout, f"Timed out waiting for {entity_id} to be unlocked") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can, yes, but I fear we would end up having some errors like "The submission cannot be pushed to as its not the latest" due to the changes we're attempting to publish not being the latest, or other weird errors like "attempting to remove an image which is already published" due to missing the version which was added in parallel, thus I thought on doing the whole operation again by retrying on pubtools.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking here: what I can do, instead, is wait for it before the whole "main" operation starts, like the first thing to do would be wait and then we do everything. Do you think it would be better? I can open a separate PR for that as well 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking on opening a different PR but I believe I can reuse this one with a commit on top of it. I had a different idea on how to take advantage of this current implementation in order to implement the "wait" feature.
I'll patch it soon
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Patched.
THis commit creates a new method for `AzureService` named `wait_active_publishing` which relies on `ensure_can_publish` to verify whether changes are being made or not and wait until it's possible to proceed or timeout if necessary. The timeout and retry interval are now possible to be set during the class construction as optional parameters. With this the `publish` method will double check before doing any changes: 1. Before loading the product information it will wait for active changes so it can retrieve the "latest" content 2. During the publishing phase, if it's no longer the latest change it will raise from `ensure_can_publish` Refers to SPSTRAT-611 and SPSTRAT-549 Signed-off-by: Jonathan Gangi <jgangi@redhat.com>
Use the same retry values declared in the `AzureService` constructor for `_wait_for_job_completion` Signed-off-by: Jonathan Gangi <jgangi@redhat.com>
|
@lslebodn PTAL again. Now the workflow is:
|
This commit changes the existing code of
AzureService.ensure_can_publishto behave as follows:It no longer retries on error but simply raises whenever a conflict is detected
It no longer searches only for "live" or "preview" but for all existing submissions
It improves the logging mechanism to inform which state the submission was and its target.
Rationale:
If we simply wait for the publishing to finish as the previous version (using retry) we could end up in a scenario where the changes made by the library are no longer the latest, causing the Graph API to return the error:
The submission cannot be pushed to as its not the latest.With that, we would need to retry the whole
publishoperations so it makes more sense to just raiseConflictErrorand letpubtoolsretry when this error is caught.The previous code were not able to catch a lot of conflicts, resulting in many errors with
An In Progress submission already existsBy looking into each submission target we may caught conflicts early.
Logs will now show exactly what was the reason it was already in progress and for which submission target.
A retry can/will be implemented on
pubtools-marketplacesvmon Azurepushin case aConflictErroris detected.Replaces SPSTRAT-549: Azure: Retry publishing when a submission is in progress/conflict #119
Refers to SPSTRAT-611 and SPSTRAT-549
Summary by Sourcery
Rework AzureService.ensure_can_publish to detect conflicts across all submission targets, remove retry logic, raise a dedicated ConflictError on in-progress submissions, and update tests and logging accordingly.
New Features:
Enhancements:
Tests: