Skip to content

Commit 7e094f9

Browse files
committed
fix: renew a proxy when it is about to expire
1 parent 27fef21 commit 7e094f9

File tree

2 files changed

+70
-17
lines changed

2 files changed

+70
-17
lines changed

src/DIRAC/WorkloadManagementSystem/Agent/PilotLoggingAgent.py

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from DIRAC.ConfigurationSystem.Client.Helpers.Operations import Operations
1818
from DIRAC.ConfigurationSystem.Client.Helpers.Registry import getVOs
1919
from DIRAC.Core.Base.AgentModule import AgentModule
20+
from DIRAC.Core.Security.ProxyInfo import getProxyInfo
2021
from DIRAC.Core.Utilities.Proxy import executeWithoutServerCertificate
2122
from DIRAC.Core.Utilities.Proxy import getProxy
2223
from DIRAC.DataManagementSystem.Client.DataManager import DataManager
@@ -34,7 +35,8 @@ class PilotLoggingAgent(AgentModule):
3435
def __init__(self, *args, **kwargs):
3536
"""c'tor"""
3637
super().__init__(*args, **kwargs)
37-
self.clearPilotsDelay = 30
38+
self.clearPilotsDelay = 30 # in days
39+
self.proxyTimeleftLimit = 600 # in seconds
3840

3941
def initialize(self):
4042
"""
@@ -46,6 +48,8 @@ def initialize(self):
4648
"""
4749
# pilot logs lifetime in days
4850
self.clearPilotsDelay = self.am_getOption("ClearPilotsDelay", self.clearPilotsDelay)
51+
# proxy timeleft limit before we get a new one.
52+
self.proxyTimeleftLimit = self.am_getOption("ProxyTimeleftLimit", self.proxyTimeleftLimit)
4953
# configured VOs
5054
res = getVOs()
5155
if not res["OK"]:
@@ -76,24 +80,20 @@ def initialize(self):
7680
continue
7781

7882
self.log.info(f"Proxy used for pilot logging: VO: {vo}, User: {proxyUser}, Group: {proxyGroup}")
79-
# download a proxy and save a filename for future use:
83+
# download a proxy and save a file name, userDN and proxyGroup for future use:
8084
result = getDNForUsername(proxyUser)
8185
if not result["OK"]:
8286
self.log.error(f"Could not obtain a DN of user {proxyUser} for VO {vo}, skipped")
8387
continue
84-
userDNs = result["Value"] # a same user may have more than one DN
85-
fd, filename = tempfile.mkstemp(prefix=vo + "__")
86-
print("filename", filename)
87-
os.close(fd)
88-
vomsAttr = getVOMSAttributeForGroup(proxyGroup)
89-
result = getProxy(userDNs, proxyGroup, vomsAttr=vomsAttr, proxyFilePath=filename)
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)
9092

9193
if not result["OK"]:
92-
self.log.error(
93-
f"Could not download a proxy for DN {userDNs}, group {proxyGroup} for VO {vo}, skipped"
94-
)
94+
# no proxy, we have no other option than to skip the VO
9595
continue
96-
self.proxyDict[vo] = result["Value"]
96+
self.proxyDict[vo] = {"proxy": result["Value"], "DN": userDNs, "group": proxyGroup}
9797

9898
return S_OK()
9999

@@ -107,8 +107,13 @@ def execute(self):
107107
voRes = {}
108108
self.log.verbose(f"VOs configured for remote logging: {list(self.proxyDict.keys())}")
109109
originalUserProxy = os.environ.get("X509_USER_PROXY")
110-
for vo, proxy in self.proxyDict.items():
111-
os.environ["X509_USER_PROXY"] = 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"]
112117
res = self.executeForVO(vo)
113118
if not res["OK"]:
114119
voRes[vo] = res["Message"]
@@ -215,3 +220,41 @@ def clearOldPilotLogs(self, pilotLogPath):
215220
os.remove(fullpath)
216221
except Exception as excp:
217222
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

src/DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_PilotLoggingAgent.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,15 @@ def pla(mocker, plaBase):
7878
@pytest.mark.parametrize(
7979
"remoteLogging, options, getDN, getVOMS, getProxy, resDict, expectedRes",
8080
[
81-
([True, False], upDict, S_OK(["myDN"]), S_OK(), S_OK("proxyfilename"), {"gridpp": "proxyfilename"}, S_OK()),
81+
(
82+
[True, False],
83+
upDict,
84+
S_OK(["myDN"]),
85+
S_OK(),
86+
S_OK("proxyfilename"),
87+
{"gridpp": {"DN": ["myDN"], "group": "proxyGroup", "proxy": "proxyfilename"}},
88+
S_OK(),
89+
),
8290
([False, False], upDict, S_OK(["myDN"]), S_OK(), S_OK(), {}, S_OK()),
8391
([True, False], upDict, S_ERROR("Could not obtain a DN"), S_OK(), S_OK(), {}, S_OK()),
8492
([True, False], upDict, S_ERROR("Could not download proxy"), S_OK(), S_ERROR("Failure"), {}, S_OK()),
@@ -110,9 +118,9 @@ def test_initialize(plaBase, remoteLogging, options, getDN, getVOMS, getProxy, r
110118
"proxyDict, execVORes, expectedResult",
111119
[
112120
({}, S_OK(), S_OK()),
113-
({"gridpp": "gridpp_proxyfile"}, S_OK(), S_OK()),
121+
({"gridpp": {"DN": ["myDN"], "group": "proxyGroup", "proxy": "proxyfilename"}}, S_OK(), S_OK()),
114122
(
115-
{"gridpp": "gridpp_proxyfile"},
123+
{"gridpp": {"DN": ["myDN"], "group": "proxyGroup", "proxy": "proxyfilename"}},
116124
S_ERROR("Execute for VO failed"),
117125
S_ERROR("Agent cycle for some VO finished with errors"),
118126
),
@@ -122,6 +130,8 @@ def test_execute(plaBase, proxyDict, execVORes, expectedResult):
122130
"""Testing a thin version of execute (executeForVO is mocked)"""
123131

124132
plaBase.executeForVO = MagicMock()
133+
plaBase._isProxyExpired = MagicMock()
134+
plaBase._isProxyExpired.return_value = False
125135
plaBase.proxyDict = proxyDict
126136
plaBase.executeForVO.return_value = execVORes
127137
res = plaBase.execute()

0 commit comments

Comments
 (0)