-
Notifications
You must be signed in to change notification settings - Fork 2
Lookup submision IDs by strategic metadata #112
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: master
Are you sure you want to change the base?
Changes from all commits
ddee243
36606c1
f329863
54bc50b
be5f6d2
6c0256e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ | |
| SubmissionFailedError, | ||
| SubmissionNotCancellableError, | ||
| SubmissionNotFoundError, | ||
| TooManyMatchingSubmissionsError, | ||
| ) | ||
| from .opsqueue_internal import ( # type: ignore[import-not-found] | ||
| SubmissionId, | ||
|
|
@@ -38,10 +39,15 @@ | |
| "SubmissionNotCancellable", | ||
| "SubmissionNotCancellableError", | ||
| "SubmissionNotFoundError", | ||
| "TooManyMatchingSubmissionsError", | ||
| "ChunkFailed", | ||
| ] | ||
|
|
||
|
|
||
| class LookupIdsWithEmptyStrategicMetadataError(Exception): | ||
| pass | ||
|
Comment on lines
+47
to
+48
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this error still produced? |
||
|
|
||
|
|
||
| class ProducerClient: | ||
| """ | ||
| Opsqueue producer client. Allows sending of large collections of operations ('submissions') | ||
|
|
@@ -367,6 +373,25 @@ def lookup_submission_id_by_prefix(self, prefix: str) -> SubmissionId | None: | |
| """ | ||
| return self.inner.lookup_submission_id_by_prefix(prefix) | ||
|
|
||
| def lookup_submission_ids_by_strategic_metadata( | ||
| self, strategic_metadata: dict[str, int] | ||
| ) -> list[SubmissionId]: | ||
|
Comment on lines
+376
to
+378
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I checked the other APIs on the producer and they either provide a single element or an iterator so that it can be lazily evaluated and doesn't need to be materialized in memory all at once.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The other This new API is different (not chunks, not querying object storage) from the existing APIs that return iterators, so would need some new wiring put in place to support streaming. But since we are only dealing with submission IDs, it's less of a memory concern. A pragmatic solution for now might be to just implement the configurable upper bound + add some metrics and keep an eye on it. |
||
| """Attempts to find in-progress submissions where the strategic metadata | ||
| of that submission includes all of the key-value pairs of the given | ||
| 'strategic_metadata'. A matching submission must include all of the | ||
| given key-value pairs, but it may also contain other key-value pairs. | ||
|
|
||
| Raises: | ||
| - `TooManyMatchingSubmissionsError` if the lookup matches more | ||
| submissions than the server's configured maximum. Narrow the query | ||
| with more specific strategic metadata. | ||
| - `InternalProducerClientError` if there is a low-level internal error. | ||
|
|
||
| """ | ||
| return self.inner.lookup_submission_ids_by_strategic_metadata( # type: ignore[no-any-return] | ||
| strategic_metadata | ||
| ) | ||
|
|
||
| def is_completed(self, submission_id: SubmissionId) -> bool: | ||
| raise NotImplementedError | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -5,7 +5,7 @@ use std::error::Error; | |||||
| use opsqueue::common::chunk::ChunkId; | ||||||
| use opsqueue::common::errors::{ | ||||||
| ChunkNotFound, IncorrectUsage, SubmissionNotCancellable, SubmissionNotFound, | ||||||
| UnexpectedOpsqueueConsumerServerResponse, E, | ||||||
| TooManyMatchingSubmissions, UnexpectedOpsqueueConsumerServerResponse, E, | ||||||
| }; | ||||||
| use pyo3::exceptions::PyBaseException; | ||||||
| use pyo3::{import_exception, Bound, PyErr, Python}; | ||||||
|
|
@@ -22,6 +22,7 @@ import_exception!(opsqueue.exceptions, TryFromIntError); | |||||
| import_exception!(opsqueue.exceptions, ChunkNotFoundError); | ||||||
| import_exception!(opsqueue.exceptions, SubmissionNotFoundError); | ||||||
| import_exception!(opsqueue.exceptions, SubmissionNotCancellableError); | ||||||
| import_exception!(opsqueue.exceptions, TooManyMatchingSubmissionsError); | ||||||
| import_exception!(opsqueue.exceptions, NewObjectStoreClientError); | ||||||
| import_exception!(opsqueue.exceptions, SubmissionNotCompletedYetError); | ||||||
|
|
||||||
|
|
@@ -146,6 +147,12 @@ impl From<CError<SubmissionNotFound>> for PyErr { | |||||
| } | ||||||
| } | ||||||
|
|
||||||
| impl From<CError<TooManyMatchingSubmissions>> for PyErr { | ||||||
| fn from(value: CError<TooManyMatchingSubmissions>) -> Self { | ||||||
| TooManyMatchingSubmissionsError::new_err(value.0 .0) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| } | ||||||
| } | ||||||
|
|
||||||
| pub struct SubmissionFailed( | ||||||
| pub crate::common::SubmissionFailed, | ||||||
| pub crate::common::ChunkFailed, | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -14,18 +14,19 @@ | |||||||||||||||||||||||||||||||||||||
| SubmissionNotFoundError, | ||||||||||||||||||||||||||||||||||||||
| SubmissionNotCancellable, | ||||||||||||||||||||||||||||||||||||||
| SubmissionNotCancellableError, | ||||||||||||||||||||||||||||||||||||||
| TooManyMatchingSubmissionsError, | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| from opsqueue.consumer import ConsumerClient, Chunk | ||||||||||||||||||||||||||||||||||||||
| from opsqueue.common import SerializationFormat | ||||||||||||||||||||||||||||||||||||||
| from conftest import ( | ||||||||||||||||||||||||||||||||||||||
| background_process, | ||||||||||||||||||||||||||||||||||||||
| multiple_background_processes, | ||||||||||||||||||||||||||||||||||||||
| OpsqueueProcess, | ||||||||||||||||||||||||||||||||||||||
| opsqueue_service, | ||||||||||||||||||||||||||||||||||||||
| StrategyDescription, | ||||||||||||||||||||||||||||||||||||||
| strategy_from_description, | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| import logging | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| import pytest | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
@@ -508,3 +509,80 @@ def consume(x: int) -> int | None: | |||||||||||||||||||||||||||||||||||||
| with pytest.raises(SubmissionFailedError) as exc_info: | ||||||||||||||||||||||||||||||||||||||
| producer_client.blocking_stream_completed_submission(submission_id) | ||||||||||||||||||||||||||||||||||||||
| assert exc_info.value.submission.chunks_done == len(chunks) - 1 | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def test_lookup_submission_ids_by_strategic_metadata(opsqueue: OpsqueueProcess) -> None: | ||||||||||||||||||||||||||||||||||||||
| """Lookup of submission IDs should only match in progress submissions with | ||||||||||||||||||||||||||||||||||||||
| all pieces of strategic metadata. | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||
| url = "file:///tmp/opsqueue/test_lookup_submission_ids_by_strategic_metadata" | ||||||||||||||||||||||||||||||||||||||
| producer_client = ProducerClient(f"localhost:{opsqueue.port}", url) | ||||||||||||||||||||||||||||||||||||||
| id_1 = producer_client.insert_submission( | ||||||||||||||||||||||||||||||||||||||
| [1], chunk_size=1, strategic_metadata={"foo": 1, "bar": 2, "wow": 3} | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| id_2 = producer_client.insert_submission( | ||||||||||||||||||||||||||||||||||||||
| [1], chunk_size=1, strategic_metadata={"foo": 1, "bar": 2, "moo": 3} | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| # Inserting some similar data to that above, which shouldn't get matched. | ||||||||||||||||||||||||||||||||||||||
| producer_client.insert_submission( | ||||||||||||||||||||||||||||||||||||||
| [1], chunk_size=1, strategic_metadata={"foo": 2, "bar": 1} | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def test_lookup( | ||||||||||||||||||||||||||||||||||||||
| strategic_metadata: dict[str, int], expected_ids: list[int] | ||||||||||||||||||||||||||||||||||||||
| ) -> None: | ||||||||||||||||||||||||||||||||||||||
| found_ids = producer_client.lookup_submission_ids_by_strategic_metadata( | ||||||||||||||||||||||||||||||||||||||
| strategic_metadata | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| assert isinstance(found_ids, list) | ||||||||||||||||||||||||||||||||||||||
| assert all(map(lambda x: isinstance(x, SubmissionId), found_ids)) | ||||||||||||||||||||||||||||||||||||||
| assert found_ids == expected_ids | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| test_lookup({"foo": 1}, [id_1, id_2]) | ||||||||||||||||||||||||||||||||||||||
| test_lookup({"foo": 1, "bar": 2}, [id_1, id_2]) | ||||||||||||||||||||||||||||||||||||||
| test_lookup({"foo": 1, "MISS": 2}, []) | ||||||||||||||||||||||||||||||||||||||
| test_lookup({"wow": 3}, [id_1]) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # Should only match in-progress submission. | ||||||||||||||||||||||||||||||||||||||
| producer_client.cancel_submission(id_1) | ||||||||||||||||||||||||||||||||||||||
| test_lookup({"foo": 1}, [id_2]) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def test_lookup_submission_ids_by_empty_strategic_metadata( | ||||||||||||||||||||||||||||||||||||||
| opsqueue: OpsqueueProcess, | ||||||||||||||||||||||||||||||||||||||
| ) -> None: | ||||||||||||||||||||||||||||||||||||||
| """Lookup of submission IDs with empty strategic_metadata should NOT raise | ||||||||||||||||||||||||||||||||||||||
| an exception. | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||
| url = "file:///tmp/opsqueue/test_lookup_submission_ids_by_empty_strategic_metadata" | ||||||||||||||||||||||||||||||||||||||
| producer_client = ProducerClient(f"localhost:{opsqueue.port}", url) | ||||||||||||||||||||||||||||||||||||||
| count = 6 | ||||||||||||||||||||||||||||||||||||||
| for _ in range(count): | ||||||||||||||||||||||||||||||||||||||
| producer_client.insert_submission([1], chunk_size=1) | ||||||||||||||||||||||||||||||||||||||
| assert len(producer_client.lookup_submission_ids_by_strategic_metadata({})) == count | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def test_lookup_too_many_submission_ids_by_strategic_metadata() -> None: | ||||||||||||||||||||||||||||||||||||||
| """Lookup of too many submission IDs beyond the configured limit raises | ||||||||||||||||||||||||||||||||||||||
| TooManyMatchingSubmissionsError. | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||
| max_ = 2 | ||||||||||||||||||||||||||||||||||||||
| # We didn't request the OpsQueueProcess as a parameter so an instance isn't | ||||||||||||||||||||||||||||||||||||||
| # started, instead we start one here with custom args. | ||||||||||||||||||||||||||||||||||||||
| with opsqueue_service( | ||||||||||||||||||||||||||||||||||||||
| command_args=["--max-submissions-returned", str(max_)] | ||||||||||||||||||||||||||||||||||||||
| ) as opsqueue: | ||||||||||||||||||||||||||||||||||||||
| url = "file:///tmp/opsqueue/test_lookup_too_many_matching_submissions" | ||||||||||||||||||||||||||||||||||||||
| producer_client = ProducerClient(f"localhost:{opsqueue.port}", url) | ||||||||||||||||||||||||||||||||||||||
| inserted = 0 | ||||||||||||||||||||||||||||||||||||||
| for _ in range(max_ + 1): | ||||||||||||||||||||||||||||||||||||||
| producer_client.insert_submission( | ||||||||||||||||||||||||||||||||||||||
| [1], chunk_size=1, strategic_metadata={"k": 1} | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| inserted += 1 | ||||||||||||||||||||||||||||||||||||||
| with pytest.raises(TooManyMatchingSubmissionsError): | ||||||||||||||||||||||||||||||||||||||
| assert inserted == max_ + 1 # Make sure Exception wasn't raised too early. | ||||||||||||||||||||||||||||||||||||||
| producer_client.lookup_submission_ids_by_strategic_metadata({"k": 1}) | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+580
to
+588
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can also test the endpoint keeps functioning until the one that brings it over the edge, this prevents off-by-one errors:
Suggested change
|
||||||||||||||||||||||||||||||||||||||
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 am not sure these timeouts are long enough on CI.
I had the same experience locally and made it configurable for local use:
https://github.com/channable/opsqueue/pull/122/changes#diff-deb9bb56fb122db0b605aa5b63f95a4665c905b18dd670e1fa6c877576a94ff1