12
12
# # imports
13
13
import os
14
14
import time
15
+ import tempfile
15
16
from DIRAC import S_OK , S_ERROR , gConfig
16
17
from DIRAC .ConfigurationSystem .Client .Helpers .Operations import Operations
17
18
from DIRAC .ConfigurationSystem .Client .Helpers .Registry import getVOs
18
19
from DIRAC .Core .Base .AgentModule import AgentModule
19
- from DIRAC .Core .Utilities .Proxy import executeWithUserProxy
20
+ from DIRAC .Core .Security .ProxyInfo import getProxyInfo
21
+ from DIRAC .Core .Utilities .Proxy import executeWithoutServerCertificate
22
+ from DIRAC .Core .Utilities .Proxy import getProxy
20
23
from DIRAC .DataManagementSystem .Client .DataManager import DataManager
24
+ from DIRAC .ConfigurationSystem .Client .Helpers .Registry import getVOMSAttributeForGroup , getDNForUsername
21
25
from DIRAC .WorkloadManagementSystem .Client .TornadoPilotLoggingClient import TornadoPilotLoggingClient
22
26
23
27
@@ -31,46 +35,39 @@ class PilotLoggingAgent(AgentModule):
31
35
def __init__ (self , * args , ** kwargs ):
32
36
"""c'tor"""
33
37
super ().__init__ (* args , ** kwargs )
34
- self .clearPilotsDelay = 30
38
+ self .clearPilotsDelay = 30 # in days
39
+ self .proxyTimeleftLimit = 600 # in seconds
35
40
36
41
def initialize (self ):
37
42
"""
38
43
agent's initialisation. Use this agent's CS information to:
39
- Determine what Defaults/Shifter shifter proxy to use.,
40
- get the target SE name from the CS.
41
- Obtain log file location from Tornado.
44
+ Determine VOs with remote logging enabled,
45
+ Determine what Defaults/Shifter shifter proxy to use., download the proxies.
42
46
43
47
:param self: self reference
44
48
"""
45
49
# pilot logs lifetime in days
46
50
self .clearPilotsDelay = self .am_getOption ("ClearPilotsDelay" , self .clearPilotsDelay )
47
- # configured VOs and setup
51
+ # proxy timeleft limit before we get a new one.
52
+ self .proxyTimeleftLimit = self .am_getOption ("ProxyTimeleftLimit" , self .proxyTimeleftLimit )
53
+ # configured VOs
48
54
res = getVOs ()
49
55
if not res ["OK" ]:
50
56
return res
51
57
self .voList = res .get ("Value" , [])
52
58
53
59
if isinstance (self .voList , str ):
54
60
self .voList = [self .voList ]
61
+ # download shifter proxies for enabled VOs:
62
+ self .proxyDict = {}
55
63
56
- return S_OK ()
57
-
58
- def execute (self ):
59
- """
60
- Execute one agent cycle. Upload log files to the SE and register them in the DFC.
61
- Use a shifter proxy dynamically loaded for every VO
62
-
63
- :param self: self reference
64
- """
65
- voRes = {}
66
64
for vo in self .voList :
67
- self . opsHelper = Operations (vo = vo )
65
+ opsHelper = Operations (vo = vo )
68
66
# is remote pilot logging enabled for the VO ?
69
- pilotLogging = self . opsHelper .getValue ("/Pilot/RemoteLogging" , False )
67
+ pilotLogging = opsHelper .getValue ("/Pilot/RemoteLogging" , False )
70
68
if pilotLogging :
71
- res = self . opsHelper .getOptionsDict ("Shifter/DataManager" )
69
+ res = opsHelper .getOptionsDict ("Shifter/DataManager" )
72
70
if not res ["OK" ]:
73
- voRes [vo ] = "No shifter defined - skipped"
74
71
self .log .error (f"No shifter defined for VO: { vo } - skipping ..." )
75
72
continue
76
73
@@ -80,36 +77,75 @@ def execute(self):
80
77
self .log .error (
81
78
f"No proxy user or group defined for pilot: VO: { vo } , User: { proxyUser } , Group: { proxyGroup } "
82
79
)
83
- voRes [vo ] = "No proxy user or group defined - skipped"
84
80
continue
85
81
86
82
self .log .info (f"Proxy used for pilot logging: VO: { vo } , User: { proxyUser } , Group: { proxyGroup } " )
87
- res = self .executeForVO ( # pylint: disable=unexpected-keyword-arg
88
- vo , proxyUserName = proxyUser , proxyUserGroup = proxyGroup
89
- )
90
- if not res ["OK" ]:
91
- voRes [vo ] = res ["Message" ]
83
+ # download a proxy and save a file name, userDN and proxyGroup for future use:
84
+ result = getDNForUsername (proxyUser )
85
+ if not result ["OK" ]:
86
+ self .log .error (f"Could not obtain a DN of user { proxyUser } for VO { vo } , skipped" )
87
+ continue
88
+ userDNs = result ["Value" ] # the same user may have more than one DN
89
+
90
+ with tempfile .NamedTemporaryFile (prefix = "gridpp" + "__" , delete = False ) as ntf :
91
+ result = self ._downloadProxy (vo , userDNs , proxyGroup , ntf .name )
92
+
93
+ if not result ["OK" ]:
94
+ # no proxy, we have no other option than to skip the VO
95
+ continue
96
+ self .proxyDict [vo ] = {"proxy" : result ["Value" ], "DN" : userDNs , "group" : proxyGroup }
97
+
98
+ return S_OK ()
99
+
100
+ def execute (self ):
101
+ """
102
+ Execute one agent cycle. Upload log files to the SE and register them in the DFC.
103
+ Consider only VOs we have proxies for.
104
+
105
+ :param self: self reference
106
+ """
107
+ voRes = {}
108
+ self .log .verbose (f"VOs configured for remote logging: { list (self .proxyDict .keys ())} " )
109
+ originalUserProxy = os .environ .get ("X509_USER_PROXY" )
110
+ for vo , elem in self .proxyDict .items ():
111
+ if self ._isProxyExpired (elem ["proxy" ], self .proxyTimeleftLimit ):
112
+ result = self ._downloadProxy (vo , elem ["DN" ], elem ["group" ], elem ["proxy" ])
113
+ if not result ["OK" ]:
114
+ voRes [vo ] = result ["Message" ]
115
+ continue
116
+ os .environ ["X509_USER_PROXY" ] = elem ["proxy" ]
117
+ res = self .executeForVO (vo )
118
+ if not res ["OK" ]:
119
+ voRes [vo ] = res ["Message" ]
120
+ # restore the original proxy:
121
+ if originalUserProxy :
122
+ os .environ ["X509_USER_PROXY" ] = originalUserProxy
123
+ else :
124
+ os .environ .pop ("X509_USER_PROXY" , None )
125
+
92
126
if voRes :
93
127
for key , value in voRes .items ():
94
128
self .log .error (f"Error for { key } vo; message: { value } " )
95
129
voRes .update (S_ERROR ("Agent cycle for some VO finished with errors" ))
96
130
return voRes
131
+
97
132
return S_OK ()
98
133
99
- @executeWithUserProxy
134
+ @executeWithoutServerCertificate
100
135
def executeForVO (self , vo ):
101
136
"""
102
137
Execute one agent cycle for a VO. It obtains VO-specific configuration pilot options from the CS:
103
138
UploadPath - the path where the VO wants to upload pilot logs. It has to start with a VO name (/vo/path).
104
139
UploadSE - Storage element where the logs will be kept.
105
140
106
- :param str vo: vo enabled for remote pilot logging
141
+ :param str vo: vo enabled for remote pilot logging (and a successfully downloaded proxy for the VO)
107
142
:return: S_OK or S_ERROR
108
143
:rtype: dict
109
144
"""
110
145
111
146
self .log .info (f"Pilot files upload cycle started for VO: { vo } " )
112
- res = self .opsHelper .getOptionsDict ("Pilot" )
147
+ opsHelper = Operations (vo = vo )
148
+ res = opsHelper .getOptionsDict ("Pilot" )
113
149
if not res ["OK" ]:
114
150
return S_ERROR (f"No pilot section for { vo } vo" )
115
151
pilotOptions = res ["Value" ]
@@ -184,3 +220,41 @@ def clearOldPilotLogs(self, pilotLogPath):
184
220
os .remove (fullpath )
185
221
except Exception as excp :
186
222
self .log .exception (f"Cannot remove an old log file after { fullpath } " , lException = excp )
223
+
224
+ def _downloadProxy (self , vo , userDNs , proxyGroup , filename ):
225
+ """
226
+ Fetch a new proxy and store it in a file filename.
227
+
228
+ :param str vo: VO to get a proxy for
229
+ :param list userDNs: user DN list
230
+ :param str proxyGroup: user group
231
+ :param str filename: file name to store a proxy
232
+ :return: Dirac S_OK or S_ERROR object
233
+ :rtype: dict
234
+ """
235
+ vomsAttr = getVOMSAttributeForGroup (proxyGroup )
236
+ result = getProxy (userDNs , proxyGroup , vomsAttr = vomsAttr , proxyFilePath = filename )
237
+ if not result ["OK" ]:
238
+ self .log .error (f"Could not download a proxy for DN { userDNs } , group { proxyGroup } for VO { vo } , skipped" )
239
+ return S_ERROR (f"Could not download a proxy, { vo } skipped" )
240
+ return result
241
+
242
+ def _isProxyExpired (self , proxyfile , limit ):
243
+ """
244
+ Check proxy timeleft. If less than a limit, return True.
245
+
246
+ :param str proxyfile:
247
+ :param int limit: timeleft threshold below which a proxy is considered expired.
248
+ :return: True or False
249
+ :rtype: bool
250
+ """
251
+ result = getProxyInfo (proxyfile )
252
+ if not result ["OK" ]:
253
+ self .log .error (f"Could not get proxy info { result ['Message' ]} " )
254
+ return True
255
+ timeleft = result ["Value" ]["secondsLeft" ]
256
+ self .log .debug (f"Proxy { proxyfile } time left: { timeleft } " )
257
+ if timeleft < limit :
258
+ self .log .info (f"proxy { proxyfile } expired/is about to expire. Will fetch a new one" )
259
+ return True
260
+ return False
0 commit comments