Skip to content

Commit f402fa6

Browse files
committed
Issue #115 only collect assets of main subjob for crossbackend job
1 parent 56bf3f5 commit f402fa6

File tree

3 files changed

+99
-4
lines changed

3 files changed

+99
-4
lines changed

src/openeo_aggregator/partitionedjobs/crossbackend.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ def split_streaming(
8282
self,
8383
process_graph: FlatPG,
8484
get_replacement: GetReplacementCallable = _default_get_replacement,
85+
main_subgraph_id: SubGraphId = "main",
8586
) -> Iterator[Tuple[SubGraphId, SubJob, List[SubGraphId]]]:
8687
"""
8788
Split given process graph in sub-process graphs and return these as an iterator
@@ -113,7 +114,7 @@ def split_streaming(
113114
secondary_backends = {b for b in backend_usage if b != primary_backend}
114115
_log.info(f"Backend split: {primary_backend=} {secondary_backends=}")
115116

116-
primary_id = "main"
117+
primary_id = main_subgraph_id
117118
primary_pg = {}
118119
primary_has_load_collection = False
119120
primary_dependencies = []

src/openeo_aggregator/partitionedjobs/tracking.py

+21-3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
from openeo_aggregator.partitionedjobs.zookeeper import ZooKeeperPartitionedJobDB
3333
from openeo_aggregator.utils import _UNSET, Clock, PGWithMetadata, timestamp_to_rfc3339
3434

35+
PJOB_METADATA_FIELD_RESULT_JOBS = "result_jobs"
36+
3537
_log = logging.getLogger(__name__)
3638

3739

@@ -79,12 +81,16 @@ def create_crossbackend_pjob(
7981
before we have finalised sub-processgraphs, whose metadata can then be persisted in the ZooKeeperPartitionedJobDB
8082
"""
8183
# Start with reserving a new partitioned job id based on initial metadata
84+
main_subgraph_id = "main"
8285
pjob_node_value = self._db.serialize(
8386
user_id=user_id,
8487
created=Clock.time(),
8588
process=process,
8689
metadata=metadata,
8790
job_options=job_options,
91+
**{
92+
PJOB_METADATA_FIELD_RESULT_JOBS: [main_subgraph_id],
93+
},
8894
)
8995
pjob_id = self._db.obtain_new_pjob_id(user_id=user_id, initial_value=pjob_node_value)
9096
self._db.set_pjob_status(user_id=user_id, pjob_id=pjob_id, status=STATUS_INSERTED, create=True)
@@ -107,7 +113,7 @@ def get_replacement(node_id: str, node: dict, subgraph_id: SubGraphId) -> dict:
107113
}
108114

109115
for sjob_id, subjob, subjob_dependencies in splitter.split_streaming(
110-
process_graph=process["process_graph"], get_replacement=get_replacement
116+
process_graph=process["process_graph"], get_replacement=get_replacement, main_subgraph_id=main_subgraph_id
111117
):
112118
subjobs[sjob_id] = subjob
113119
dependencies[sjob_id] = subjob_dependencies
@@ -380,9 +386,21 @@ def get_assets(self, user_id: str, pjob_id: str, flask_request: flask.Request) -
380386
# TODO: do a sync if latest sync is too long ago?
381387
pjob_metadata = self._db.get_pjob_metadata(user_id=user_id, pjob_id=pjob_id)
382388
sjobs = self._db.list_subjobs(user_id=user_id, pjob_id=pjob_id)
389+
if pjob_metadata.get(PJOB_METADATA_FIELD_RESULT_JOBS):
390+
result_jobs = set(pjob_metadata[PJOB_METADATA_FIELD_RESULT_JOBS])
391+
result_sjob_ids = [s for s in sjobs if s in result_jobs]
392+
log_msg = f"Collect {pjob_id} subjob assets: subset {result_sjob_ids} (from {len(sjobs)})"
393+
else:
394+
# Collect results of all subjobs by default
395+
result_sjob_ids = list(sjobs.keys())
396+
log_msg = f"Collect {pjob_id} subjob assets: all {len(sjobs)})"
397+
383398
assets = []
384-
with TimingLogger(title=f"Collect assets of {pjob_id} ({len(sjobs)} sub-jobs)", logger=_log):
385-
for sjob_id, sjob_metadata in sjobs.items():
399+
with TimingLogger(title=log_msg, logger=_log):
400+
for sjob_id in result_sjob_ids:
401+
sjob_metadata = sjobs[sjob_id]
402+
403+
# TODO: skip subjobs that are just dependencies for a main/grouping job
386404
sjob_status = self._db.get_sjob_status(user_id=user_id, pjob_id=pjob_id, sjob_id=sjob_id)["status"]
387405
if sjob_status in {STATUS_INSERTED, STATUS_CREATED, STATUS_RUNNING}:
388406
raise JobNotFinishedException

tests/partitionedjobs/test_api.py

+76
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def __init__(self, date: str):
4444

4545
@pytest.fixture
4646
def dummy1(backend1, requests_mock) -> DummyBackend:
47+
# TODO: rename this fixture to dummy_backed1
4748
dummy = DummyBackend(requests_mock=requests_mock, backend_url=backend1, job_id_template="1-jb-{i}")
4849
dummy.setup_basic_requests_mocks()
4950
dummy.register_user(bearer_token=TEST_USER_BEARER_TOKEN, user_id=TEST_USER)
@@ -651,6 +652,7 @@ def test_create_job_simple(self, flask_app, api100, zk_db, dummy1):
651652
"process": {"process_graph": pg},
652653
"metadata": {},
653654
"job_options": {"split_strategy": "crossbackend"},
655+
"result_jobs": ["main"],
654656
}
655657

656658
assert zk_db.get_pjob_status(user_id=TEST_USER, pjob_id=pjob_id) == {
@@ -722,6 +724,7 @@ def test_create_job_basic(self, flask_app, api100, zk_db, dummy1):
722724
"process": {"process_graph": pg},
723725
"metadata": {},
724726
"job_options": {"split_strategy": "crossbackend"},
727+
"result_jobs": ["main"],
725728
}
726729

727730
assert zk_db.get_pjob_status(user_id=TEST_USER, pjob_id=pjob_id) == {
@@ -797,3 +800,76 @@ def test_create_job_basic(self, flask_app, api100, zk_db, dummy1):
797800
"result": True,
798801
},
799802
}
803+
804+
@now.mock
805+
def test_start_and_job_results(self, flask_app, api100, zk_db, dummy1):
806+
"""Run the jobs and get results"""
807+
api100.set_auth_bearer_token(token=TEST_USER_BEARER_TOKEN)
808+
809+
pg = {
810+
"lc1": {"process_id": "load_collection", "arguments": {"id": "S2"}},
811+
"lc2": {"process_id": "load_collection", "arguments": {"id": "S2"}},
812+
"merge": {
813+
"process_id": "merge_cubes",
814+
"arguments": {"cube1": {"from_node": "lc1"}, "cube2": {"from_node": "lc2"}},
815+
"result": True,
816+
},
817+
}
818+
819+
res = api100.post(
820+
"/jobs",
821+
json={
822+
"process": {"process_graph": pg},
823+
"job_options": {"split_strategy": "crossbackend"},
824+
},
825+
).assert_status_code(201)
826+
827+
pjob_id = "pj-20220119-123456"
828+
expected_job_id = f"agg-{pjob_id}"
829+
assert res.headers["OpenEO-Identifier"] == expected_job_id
830+
831+
res = api100.get(f"/jobs/{expected_job_id}").assert_status_code(200)
832+
assert res.json == {
833+
"id": expected_job_id,
834+
"process": {"process_graph": pg},
835+
"status": "created",
836+
"created": self.now.rfc3339,
837+
"progress": 0,
838+
}
839+
840+
# start job
841+
api100.post(f"/jobs/{expected_job_id}/results").assert_status_code(202)
842+
dummy1.set_job_status(TEST_USER, "1-jb-0", status="running")
843+
dummy1.set_job_status(TEST_USER, "1-jb-1", status="queued")
844+
res = api100.get(f"/jobs/{expected_job_id}").assert_status_code(200)
845+
assert res.json == DictSubSet({"id": expected_job_id, "status": "running", "progress": 0})
846+
847+
# First job is ready
848+
dummy1.set_job_status(TEST_USER, "1-jb-0", status="finished")
849+
dummy1.setup_assets(job_id=f"1-jb-0", assets=["1-jb-0-result.tif"])
850+
dummy1.set_job_status(TEST_USER, "1-jb-1", status="running")
851+
res = api100.get(f"/jobs/{expected_job_id}").assert_status_code(200)
852+
assert res.json == DictSubSet({"id": expected_job_id, "status": "running", "progress": 50})
853+
854+
# Main job is ready too
855+
dummy1.set_job_status(TEST_USER, "1-jb-1", status="finished")
856+
dummy1.setup_assets(job_id=f"1-jb-1", assets=["1-jb-1-result.tif"])
857+
res = api100.get(f"/jobs/{expected_job_id}").assert_status_code(200)
858+
assert res.json == DictSubSet({"id": expected_job_id, "status": "finished", "progress": 100})
859+
860+
# Get results
861+
res = api100.get(f"/jobs/{expected_job_id}/results").assert_status_code(200)
862+
assert res.json == DictSubSet(
863+
{
864+
"id": expected_job_id,
865+
"assets": {
866+
"main-1-jb-1-result.tif": {
867+
"file:nodata": [None],
868+
"href": "https://b1.test/v1/jobs/1-jb-1/results/1-jb-1-result.tif",
869+
"roles": ["data"],
870+
"title": "main-1-jb-1-result.tif",
871+
"type": "application/octet-stream",
872+
},
873+
},
874+
}
875+
)

0 commit comments

Comments
 (0)