Skip to content

Commit d3542e4

Browse files
committed
Merge branch 'issue163-ejr-job-deletion'
2 parents 25ee566 + f7c53bb commit d3542e4

File tree

3 files changed

+202
-80
lines changed

3 files changed

+202
-80
lines changed

openeo_driver/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.69.0a1"
1+
__version__ = "0.69.1a1"

openeo_driver/jobregistry.py

Lines changed: 83 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import argparse
2+
import contextlib
23
import datetime as dt
34
import json
45
import logging
@@ -8,7 +9,7 @@
89
import time
910
import typing
1011
from decimal import Decimal
11-
from typing import Any, Dict, List, NamedTuple, Optional, Union
12+
from typing import Any, Dict, List, NamedTuple, Optional, Union, Sequence
1213

1314
import requests
1415
from openeo.rest.auth.oidc import OidcClientCredentialsAuthenticator, OidcClientInfo, OidcProviderInfo
@@ -241,6 +242,19 @@ def set_user_agent(self):
241242
user_agent += f"/{self._backend_id}"
242243
self._session.headers["User-Agent"] = user_agent
243244

245+
@contextlib.contextmanager
246+
def _with_extra_logging(self, **kwargs):
247+
"""Context manager to temporarily add extra logging fields in a context"""
248+
orig = self.logger
249+
extra = kwargs
250+
if isinstance(orig, logging.LoggerAdapter):
251+
extra = {**orig.extra, **extra}
252+
self.logger = logging.LoggerAdapter(logger=orig, extra=extra)
253+
try:
254+
yield
255+
finally:
256+
self.logger = orig
257+
244258
@property
245259
def backend_id(self) -> str:
246260
assert self._backend_id
@@ -282,13 +296,9 @@ def _do_request(
282296
json: Union[dict, list, None] = None,
283297
use_auth: bool = True,
284298
expected_status: int = 200,
285-
logging_extra: Optional[dict] = None,
286299
) -> Union[dict, list, None]:
287300
"""Do an HTTP request to Elastic Job Tracker service."""
288-
with TimingLogger(
289-
logger=(lambda m: self.logger.debug(m, extra=logging_extra)),
290-
title=f"EJR Request `{method} {path}`",
291-
):
301+
with TimingLogger(logger=self.logger.debug, title=f"EJR Request `{method} {path}`"):
292302
headers = {}
293303
if use_auth:
294304
access_token = self._cache.get_or_call(
@@ -300,10 +310,7 @@ def _do_request(
300310
headers["Authorization"] = f"Bearer {access_token}"
301311

302312
url = url_join(self._api_url, path)
303-
self.logger.debug(
304-
f"Doing EJR request `{method} {url}` {headers.keys()=}",
305-
extra=logging_extra,
306-
)
313+
self.logger.debug(f"Doing EJR request `{method} {url}` {headers.keys()=}")
307314
if self._debug_show_curl:
308315
curl_command = self._as_curl(method=method, url=url, data=json, headers=headers)
309316
self.logger.debug(f"Equivalent curl command: {curl_command}")
@@ -315,10 +322,7 @@ def _do_request(
315322
headers=headers,
316323
timeout=self._REQUEST_TIMEOUT,
317324
)
318-
self.logger.debug(
319-
f"EJR response on `{method} {path}`: {response.status_code!r}",
320-
extra=logging_extra,
321-
)
325+
self.logger.debug(f"EJR response on `{method} {path}`: {response.status_code!r}")
322326
if expected_status and response.status_code != expected_status:
323327
raise EjrHttpError.from_response(response=response)
324328
else:
@@ -382,48 +386,74 @@ def create_job(
382386
"api_version": api_version,
383387
# TODO: additional technical metadata, see https://github.com/Open-EO/openeo-api/issues/472
384388
}
385-
logging_extra = {"job_id": job_id}
386-
self.logger.info(f"EJR creating {job_id=} {created=}", extra=logging_extra)
387-
return self._do_request(
388-
"POST",
389-
"/jobs",
390-
json=job_data,
391-
expected_status=201,
392-
logging_extra=logging_extra,
393-
)
389+
with self._with_extra_logging(job_id=job_id):
390+
self.logger.info(f"EJR creating {job_id=} {created=}")
391+
result = self._do_request("POST", "/jobs", json=job_data, expected_status=201)
392+
return result
394393

395394
def get_job(self, job_id: str, fields: Optional[List[str]] = None) -> JobDict:
396-
query = {
397-
"bool": {
398-
"filter": [
399-
{"term": {"backend_id": self.backend_id}},
400-
{"term": {"job_id": job_id}},
401-
]
395+
with self._with_extra_logging(job_id=job_id):
396+
self.logger.info(f"EJR get job data {job_id=}")
397+
query = {
398+
"bool": {
399+
"filter": [
400+
{"term": {"backend_id": self.backend_id}},
401+
{"term": {"job_id": job_id}},
402+
]
403+
}
402404
}
403-
}
404405

405-
# Return full document, by default
406-
jobs = self._search(query=query, fields=fields or ["*"])
407-
if len(jobs) == 1:
408-
job = jobs[0]
409-
assert job["job_id"] == job_id, f"{job['job_id']=} != {job_id=}"
410-
return job
411-
elif len(jobs) == 0:
412-
raise JobNotFoundException(job_id=job_id)
413-
else:
414-
summary = [{k: j.get(k) for k in ["user_id", "created"]} for j in jobs]
415-
self.logger.error(f"Found multiple ({len(jobs)}) jobs for {job_id=}: {repr_truncate(summary, width=200)}")
416-
raise InternalException(message=f"Found {len(jobs)} jobs for {job_id=}")
406+
# Return full document, by default
407+
jobs = self._search(query=query, fields=fields or ["*"])
408+
if len(jobs) == 1:
409+
job = jobs[0]
410+
assert job["job_id"] == job_id, f"{job['job_id']=} != {job_id=}"
411+
return job
412+
elif len(jobs) == 0:
413+
raise JobNotFoundException(job_id=job_id)
414+
else:
415+
summary = [{k: j.get(k) for k in ["user_id", "created"]} for j in jobs]
416+
self.logger.error(
417+
f"Found multiple ({len(jobs)}) jobs for {job_id=}: {repr_truncate(summary, width=200)}"
418+
)
419+
raise InternalException(message=f"Found {len(jobs)} jobs for {job_id=}")
417420

418421
def delete_job(self, job_id: str) -> None:
419-
try:
420-
self._do_request(method="DELETE", path=f"/jobs/{job_id}")
421-
logging_extra = {"job_id": job_id}
422-
self.logger.info(f"EJR deleted {job_id=}", extra=logging_extra)
423-
except EjrHttpError as e:
424-
if e.status_code == 404:
425-
raise JobNotFoundException(job_id=job_id) from e
426-
raise e
422+
with self._with_extra_logging(job_id=job_id):
423+
try:
424+
self._do_request(method="DELETE", path=f"/jobs/{job_id}")
425+
self.logger.info(f"EJR deleted {job_id=}")
426+
except EjrHttpError as e:
427+
if e.status_code == 404:
428+
raise JobNotFoundException(job_id=job_id) from e
429+
raise e
430+
self._verify_job_existence(job_id=job_id, exists=False)
431+
432+
def _verify_job_existence(self, job_id: str, exists: bool = True, backoffs: Sequence[float] = (0, 0.1, 1.0)):
433+
"""
434+
Verify that EJR committed the job creation/deletion
435+
:param job_id: job id
436+
:param exists: whether the job should exist (after creation) or not exist (after deletion)
437+
:return:
438+
"""
439+
if not backoffs:
440+
return
441+
for backoff in backoffs:
442+
self.logger.debug(f"_verify_job_existence {job_id=} {exists=} {backoff=}")
443+
time.sleep(backoff)
444+
try:
445+
self.get_job(job_id=job_id, fields=["job_id"])
446+
if exists:
447+
return
448+
except JobNotFoundException:
449+
if not exists:
450+
return
451+
except Exception as e:
452+
# TODO: fail hard instead of just logging?
453+
self.logger.exception(f"Unexpected error while verifying {job_id=} {exists=}: {e=}")
454+
return
455+
# TODO: fail hard instead of just logging?
456+
self.logger.error(f"Failed to verify {job_id=} {exists=}")
427457

428458
def set_status(
429459
self,
@@ -446,11 +476,9 @@ def set_status(
446476

447477
def _update(self, job_id: str, data: dict) -> JobDict:
448478
"""Generic update method"""
449-
logging_extra = {"job_id": job_id}
450-
self.logger.info(f"EJR update {job_id=} {data=}", extra=logging_extra)
451-
return self._do_request(
452-
"PATCH", f"/jobs/{job_id}", json=data, logging_extra=logging_extra
453-
)
479+
with self._with_extra_logging(job_id=job_id):
480+
self.logger.info(f"EJR update {job_id=} {data=}")
481+
return self._do_request("PATCH", f"/jobs/{job_id}", json=data)
454482

455483
def set_dependencies(
456484
self, job_id: str, dependencies: List[Dict[str, str]]

0 commit comments

Comments
 (0)