1
1
import argparse
2
+ import contextlib
2
3
import datetime as dt
3
4
import json
4
5
import logging
8
9
import time
9
10
import typing
10
11
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
12
13
13
14
import requests
14
15
from openeo .rest .auth .oidc import OidcClientCredentialsAuthenticator , OidcClientInfo , OidcProviderInfo
@@ -241,6 +242,19 @@ def set_user_agent(self):
241
242
user_agent += f"/{ self ._backend_id } "
242
243
self ._session .headers ["User-Agent" ] = user_agent
243
244
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
+
244
258
@property
245
259
def backend_id (self ) -> str :
246
260
assert self ._backend_id
@@ -282,13 +296,9 @@ def _do_request(
282
296
json : Union [dict , list , None ] = None ,
283
297
use_auth : bool = True ,
284
298
expected_status : int = 200 ,
285
- logging_extra : Optional [dict ] = None ,
286
299
) -> Union [dict , list , None ]:
287
300
"""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 } `" ):
292
302
headers = {}
293
303
if use_auth :
294
304
access_token = self ._cache .get_or_call (
@@ -300,10 +310,7 @@ def _do_request(
300
310
headers ["Authorization" ] = f"Bearer { access_token } "
301
311
302
312
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 ()= } " )
307
314
if self ._debug_show_curl :
308
315
curl_command = self ._as_curl (method = method , url = url , data = json , headers = headers )
309
316
self .logger .debug (f"Equivalent curl command: { curl_command } " )
@@ -315,10 +322,7 @@ def _do_request(
315
322
headers = headers ,
316
323
timeout = self ._REQUEST_TIMEOUT ,
317
324
)
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} " )
322
326
if expected_status and response .status_code != expected_status :
323
327
raise EjrHttpError .from_response (response = response )
324
328
else :
@@ -382,48 +386,74 @@ def create_job(
382
386
"api_version" : api_version ,
383
387
# TODO: additional technical metadata, see https://github.com/Open-EO/openeo-api/issues/472
384
388
}
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
394
393
395
394
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
+ }
402
404
}
403
- }
404
405
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 = } " )
417
420
418
421
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 = } " )
427
457
428
458
def set_status (
429
459
self ,
@@ -446,11 +476,9 @@ def set_status(
446
476
447
477
def _update (self , job_id : str , data : dict ) -> JobDict :
448
478
"""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 )
454
482
455
483
def set_dependencies (
456
484
self , job_id : str , dependencies : List [Dict [str , str ]]
0 commit comments