@@ -46,11 +46,16 @@ def __init__(self, ceUniqueID):
46
46
self .restVersion = "1.0"
47
47
# Time left before proxy renewal: 3 hours is a good default
48
48
self .proxyTimeLeftBeforeRenewal = 10800
49
+ # Current delegation ID, generated/fetched in submitJob(), renewed in getJobStatus()
50
+ self ._delegationID = None
49
51
# Timeout
50
52
self .timeout = 5.0
51
53
# Request session
52
54
self .session = None
53
- self .headers = {}
55
+ self .headers = {
56
+ "Accept" : "application/json" ,
57
+ "Content-Type" : "application/json" ,
58
+ }
54
59
# URL used to communicate with the REST interface
55
60
self .base_url = ""
56
61
@@ -88,13 +93,6 @@ def _reset(self):
88
93
# Set up the request framework
89
94
self .session = requests .Session ()
90
95
self .session .verify = Locations .getCAsLocation ()
91
- self .headers = {
92
- "Accept" : "application/json" ,
93
- "Content-Type" : "application/json" ,
94
- }
95
- # Attach the token to the headers if present
96
- if os .environ .get ("BEARER_TOKEN" ):
97
- self .headers ["Authorization" ] = "Bearer " + os .environ ["BEARER_TOKEN" ]
98
96
99
97
return S_OK ()
100
98
@@ -184,11 +182,22 @@ def _checkSession(self):
184
182
if not self .session :
185
183
return S_ERROR ("REST interface not initialised." )
186
184
187
- # Get a proxy
185
+ # Reinitialize the authentication parameters
186
+ self .session .cert = None
187
+ self .headers .pop ("Authorization" , None )
188
+
189
+ # Get a proxy: still mandatory, even if tokens are used to authenticate
188
190
result = self ._prepareProxy ()
189
191
if not result ["OK" ]:
190
192
self .log .error ("Failed to set up proxy" , result ["Message" ])
191
193
return result
194
+
195
+ if self .token :
196
+ # Attach the token to the headers if present
197
+ self .headers ["Authorization" ] = "Bearer " + self .token ["access_token" ]
198
+ return S_OK ()
199
+
200
+ # Attach the proxy to the session, only if the token is unavailable
192
201
self .session .cert = Locations .getProxyLocation ()
193
202
return S_OK ()
194
203
@@ -234,11 +243,15 @@ def __uploadCertificate(self, delegationID, csrContent):
234
243
235
244
# Get a proxy and sign the CSR
236
245
proxy = X509Chain ()
237
- result = proxy .loadProxyFromFile (self .session .cert )
246
+ proxyFile = Locations .getProxyLocation ()
247
+ if not proxyFile :
248
+ return S_ERROR (f"No proxy available" )
249
+ result = proxy .loadProxyFromFile (proxyFile )
238
250
if not result ["OK" ]:
239
- return S_ERROR (f"Can't load { self . session . cert } : { result ['Message' ]} " )
251
+ return S_ERROR (f"Can't load { proxyFile } : { result ['Message' ]} " )
240
252
result = proxy .generateChainFromRequestString (csrContent )
241
253
if not result ["OK" ]:
254
+ self .log .error ("Problem with the Certificate Signing Request:" , result ["Message" ])
242
255
return S_ERROR ("Problem with the Certificate Signing Request" )
243
256
244
257
# Submit the certificate
@@ -270,38 +283,44 @@ def _prepareDelegation(self):
270
283
return result
271
284
return S_OK (delegationID )
272
285
273
- def _getDelegationID (self , arcJobID ):
274
- """Query and return the delegation ID of the given job .
286
+ def _getDelegationIDs (self ):
287
+ """Query and return the delegation IDs .
275
288
276
- This happens when the call is from self.renewJobs. This function needs to know the
277
- delegation associated to the job
289
+ This happens when the call is from self.renewDelegations.
278
290
More info at
279
291
https://www.nordugrid.org/arc/arc6/tech/rest/rest.html#jobs-management
280
292
https://www.nordugrid.org/arc/arc6/tech/rest/rest.html#delegations-management
281
293
282
- :param str jobID: ARC job ID
283
- :return: delegation ID
294
+ :return: list of delegation IDs
284
295
"""
285
- params = {"action" : "delegations" }
286
- query = self ._urlJoin ("jobs" )
296
+ query = self ._urlJoin ("delegations" )
287
297
288
298
# Submit the POST request to get the delegation
289
- jobsJson = {"job" : [{"id" : arcJobID }]}
290
- result = self ._request ("post" , query , params = params , data = json .dumps (jobsJson ))
299
+ result = self ._request ("get" , query )
291
300
if not result ["OK" ]:
292
- self .log .error ("Issue while interacting with the delegation ." , result ["Message" ])
293
- return S_ERROR ("Issue while interacting with the delegation " )
301
+ self .log .error ("Issue while interacting with the delegations ." , result ["Message" ])
302
+ return S_ERROR ("Issue while interacting with the delegations " )
294
303
response = result ["Value" ]
295
304
296
- responseDelegation = response .json ()
297
- if "delegation_id" not in responseDelegation ["job" ]:
298
- return S_ERROR (f"Cannot find the Delegation ID for Job { arcJobID } " )
305
+ # If there is no delegation, response.json is expected to return an exception
306
+ try :
307
+ responseDelegation = response .json ()
308
+ except requests .JSONDecodeError :
309
+ return S_OK ([])
310
+
311
+ # This is not expected
312
+ if "delegation" not in responseDelegation :
313
+ return S_OK ([])
314
+
315
+ # If there is a single delegationID, then we get an str instead of a list
316
+ # Not specified in the documentation
317
+ delegations = responseDelegation ["delegation" ]
318
+ if isinstance (delegations , dict ):
319
+ delegations = [delegations ]
299
320
300
- delegationIDs = responseDelegation ["job" ]["delegation_id" ]
301
- # Documentation says "Array", but a single string is returned if there is only one
302
- if not isinstance (delegationIDs , list ):
303
- delegationIDs = [delegationIDs ]
304
- return S_OK (delegationIDs [0 ])
321
+ # responseDelegation should be {'delegation': [{'id': <delegationID>}, ...]}
322
+ delegationIDs = [delegationContent ["id" ] for delegationContent in delegations ]
323
+ return S_OK (delegationIDs )
305
324
306
325
#############################################################################
307
326
@@ -382,14 +401,23 @@ def submitJob(self, executableFile, proxy, numberOfJobs=1, inputs=None, outputs=
382
401
383
402
self .log .verbose (f"Executable file path: { executableFile } " )
384
403
385
- # Get a "delegation" and use the same delegation for all the jobs
386
- delegation = ""
387
- result = self ._prepareDelegation ()
404
+ # Get a delegation and use the same delegation for all the jobs
405
+ result = self ._getDelegationIDs ()
388
406
if not result ["OK" ]:
389
- self .log .warn ("Could not get a delegation" , f"For CE { self .ceHost } " )
390
- self .log .warn ("Continue without a delegation" )
407
+ self .log .error ("Could not get delegation IDs." , result ["Message" ])
408
+ return S_ERROR ("Could not get delegation IDs" )
409
+
410
+ delegationIDs = result ["Value" ]
411
+ if not delegationIDs :
412
+ # No existing delegation, we need to prepare one
413
+ result = self ._prepareDelegation ()
414
+ if not result ["OK" ]:
415
+ self .log .warn ("Could not get a new delegation" , f"for CE { self .ceHost } " )
416
+ return S_ERROR ("Could not get a new delegation" )
417
+ self ._delegationID = result ["Value" ]
391
418
else :
392
- delegation = f"\n (delegationid={ result ['Value' ]} )"
419
+ self ._delegationID = delegationIDs [0 ]
420
+ delegation = f"\n (delegationid={ self ._delegationID } )"
393
421
394
422
if not inputs :
395
423
inputs = []
@@ -571,73 +599,66 @@ def getCEStatus(self):
571
599
572
600
#############################################################################
573
601
574
- def _renewJobs (self , arcJobList ):
575
- """Written for the REST interface - jobList is already in the ARC format
576
-
577
- :param list arcJobList: list of ARC Job ID
578
- """
579
- # Renew the jobs
580
- for arcJob in arcJobList :
581
- # First get the delegation (proxy)
582
- result = self ._getDelegationID (arcJob )
583
- if not result ["OK" ]:
584
- self .log .warn ("Could not get a delegation from" , f"Job { arcJob } " )
585
- continue
586
- delegationID = result ["Value" ]
587
-
588
- # Prepare the command
589
- params = {"action" : "get" }
590
- query = self ._urlJoin (os .path .join ("delegations" , delegationID ))
602
+ def _renewDelegation (self ):
603
+ """Renew the delegations"""
604
+ # Prepare the command
605
+ params = {"action" : "get" }
606
+ query = self ._urlJoin (os .path .join ("delegations" , self ._delegationID ))
591
607
592
- # Submit the POST request to get the proxy
593
- result = self ._request ("post" , query , params = params )
594
- if not result ["OK" ]:
595
- self .log .debug ("Could not get a proxy for" , f"job { arcJob } : { result ['Message' ]} " )
596
- continue
597
- response = result ["Value" ]
608
+ # Submit the POST request to get the proxy
609
+ result = self ._request ("post" , query , params = params )
610
+ if not result ["OK" ]:
611
+ self .log .error ("Could not get a proxy for" , f"delegation { self . _delegationID } : { result ['Message' ]} " )
612
+ return S_ERROR ( f"Could not get a proxy for delegation { self . _delegationID } " )
613
+ response = result ["Value" ]
598
614
599
- proxy = X509Chain ()
600
- result = proxy .loadChainFromString (response .text )
601
- if not result ["OK" ]:
602
- continue
615
+ proxy = X509Chain ()
616
+ result = proxy .loadChainFromString (response .text )
617
+ if not result ["OK" ]:
618
+ self .log .error ("Could not load proxy for" , f"delegation { self ._delegationID } : { result ['Message' ]} " )
619
+ return S_ERROR (f"Could not load proxy for delegation { self ._delegationID } " )
603
620
604
- # Now test and renew the proxy
605
- result = proxy .getRemainingSecs ()
606
- if not result ["OK" ]:
607
- continue
608
- timeLeft = result ["Value" ]
621
+ # Now test and renew the proxy
622
+ result = proxy .getRemainingSecs ()
623
+ if not result ["OK" ]:
624
+ self .log .error (
625
+ "Could not get remaining time from the proxy for" ,
626
+ f"delegation { self ._delegationID } : { result ['Message' ]} " ,
627
+ )
628
+ return S_ERROR (f"Could not get remaining time from the proxy for delegation { self ._delegationID } " )
629
+ timeLeft = result ["Value" ]
609
630
610
- if timeLeft >= self .proxyTimeLeftBeforeRenewal :
611
- # No need to renew. Proxy is long enough
612
- continue
631
+ if timeLeft >= self .proxyTimeLeftBeforeRenewal :
632
+ # No need to renew. Proxy is long enough
633
+ return S_OK ()
613
634
614
- self .log .debug (
615
- "Renewing proxy for job" ,
616
- f"{ arcJob } whose proxy expires at { timeLeft } " ,
635
+ self .log .verbose (
636
+ "Renewing delegation" ,
637
+ f"{ self ._delegationID } whose proxy expires at { timeLeft } " ,
638
+ )
639
+ # Proxy needs to be renewed - try to renew it
640
+ # First, get a new CSR from the delegation
641
+ params = {"action" : "renew" }
642
+ query = self ._urlJoin (os .path .join ("delegations" , self ._delegationID ))
643
+ result = self ._request ("post" , query , params = params )
644
+ if not result ["OK" ]:
645
+ self .log .error (
646
+ "Proxy not renewed, failed to get CSR" ,
647
+ f"for delegation { self ._delegationID } " ,
617
648
)
618
- # Proxy needs to be renewed - try to renew it
619
- # First, get a new CSR from the delegation
620
- params = {"action" : "renew" }
621
- query = self ._urlJoin (os .path .join ("delegations" , delegationID ))
622
- result = self ._request ("post" , query , params = params )
649
+ return S_ERROR (f"Proxy not renewed, failed to get CSR for delegation { self ._delegationID } " )
650
+ response = result ["Value" ]
623
651
624
- if not response .ok :
625
- self .log .debug (
626
- "Proxy not renewed, failed to get CSR" ,
627
- f"for job { arcJob } with delegation { delegationID } " ,
628
- )
629
- continue
630
-
631
- # Then, sign and upload the certificate
632
- result = self .__uploadCertificate (delegationID , response .text )
633
- if not result ["OK" ]:
634
- self .log .debug (
635
- "Proxy not renewed, failed to send renewed proxy" ,
636
- f"for job { arcJob } with delegation { delegationID } : { result ['Message' ]} " ,
637
- )
638
- continue
652
+ # Then, sign and upload the certificate
653
+ result = self .__uploadCertificate (self ._delegationID , response .text )
654
+ if not result ["OK" ]:
655
+ self .log .error (
656
+ "Proxy not renewed, failed to send renewed proxy" ,
657
+ f"delegation { self ._delegationID } : { result ['Message' ]} " ,
658
+ )
659
+ return S_ERROR (f"Proxy not renewed, failed to send renewed proxy for delegation { self ._delegationID } " )
639
660
640
- self .log .debug ("Proxy successfully renewed" , f"for job { arcJob } " )
661
+ self .log .verbose ("Proxy successfully renewed" , f"for delegation { self . _delegationID } " )
641
662
642
663
return S_OK ()
643
664
@@ -673,7 +694,6 @@ def getJobStatus(self, jobIDList):
673
694
response = result ["Value" ]
674
695
675
696
resultDict = {}
676
- jobsToRenew = []
677
697
jobsToCancel = []
678
698
679
699
# A single job is returned in a dict, while multiple jobs are returned in a list
@@ -689,23 +709,19 @@ def getJobStatus(self, jobIDList):
689
709
self .log .debug ("REST ARC status" , f"for job { jobID } is { arcState } " )
690
710
resultDict [jobID ] = self .mapStates [arcState ]
691
711
692
- # Renew proxy only of jobs which are running or queuing
693
- if arcState in ("Running" , "Queuing" ):
694
- jobsToRenew .append (arcJob ["id" ])
695
712
# Cancel held jobs so they don't sit in the queue forever
696
713
if arcState == "Hold" :
697
714
jobsToCancel .append (arcJob ["id" ])
698
715
self .log .debug (f"Killing held job { jobID } " )
699
716
700
- # Renew jobs to be renewed
701
- # Does not work at present - wait for a new release of ARC CEs for this.
702
- if jobsToRenew :
703
- result = self ._renewJobs (jobsToRenew )
717
+ # Renew delegation to renew the proxies of the jobs
718
+ if self ._delegationID :
719
+ result = self ._renewDelegation ()
704
720
if not result ["OK" ]:
705
721
# Only log here as we still want to return statuses
706
- self .log .warn ("Failed to renew job proxies: " , result [" Message" ] )
722
+ self .log .warn ("Failed to renew delegation " , f" { self . _delegationID } : { result [' Message' ] } " )
707
723
708
- # Kill jobs to be killed
724
+ # Kill held jobs
709
725
if jobsToCancel :
710
726
result = self ._killJob (jobsToCancel )
711
727
if not result ["OK" ]:
0 commit comments