Skip to content

Commit 5eb5379

Browse files
committed
fix: fix unit tests for multi-VO handling and address comments from a review.
1 parent 0f5c07e commit 5eb5379

File tree

9 files changed

+66
-75
lines changed

9 files changed

+66
-75
lines changed

src/DIRAC/WorkloadManagementSystem/Agent/PilotLoggingAgent.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class PilotLoggingAgent(AgentModule):
2929

3030
def initialize(self):
3131
"""
32-
agent's initalisation. Use this agent's CS information to:
32+
agent's initialisation. Use this agent's CS information to:
3333
Determine what Defaults/Shifter shifter proxy to use.,
3434
get the target SE name from the CS.
3535
Obtain log file location from Tornado.
@@ -40,7 +40,7 @@ def initialize(self):
4040
# configured VOs and setup
4141
res = getVOs()
4242
if not res["OK"]:
43-
return S_ERROR(res["Message"])
43+
return res
4444
self.voList = res.get("Value", [])
4545

4646
if isinstance(self.voList, str):
@@ -93,7 +93,9 @@ def execute(self):
9393
@executeWithUserProxy
9494
def executeForVO(self, vo):
9595
"""
96-
Execute one agent cycle for VO
96+
Execute one agent cycle for a VO. It obtains VO-specific configuration pilot options from the CS:
97+
UploadPath - the path where the VO wants to upload pilot logs. It has to start with a VO name (/vo/path).
98+
UploadSE - Storage element where the logs will be kept.
9799
98100
:param str vo: vo enabled for remote pilot logging
99101
:return: S_OK or S_ERROR
@@ -110,14 +112,13 @@ def executeForVO(self, vo):
110112
uploadPath = pilotOptions.get("UploadPath")
111113
if uploadPath is None:
112114
return S_ERROR(f"Upload path on SE {uploadSE} not defined")
113-
uploadPath = os.path.join("/", vo, uploadPath)
114115
self.log.info(f"Pilot upload path: {uploadPath}")
115116

116117
client = TornadoPilotLoggingClient(useCertificates=True)
117118
resDict = client.getMetadata()
118119

119120
if not resDict["OK"]:
120-
return resDict["Message"]
121+
return resDict
121122

122123
# vo-specific source log path:
123124
pilotLogPath = os.path.join(resDict["Value"]["LogPath"], vo)
@@ -141,7 +142,7 @@ def executeForVO(self, vo):
141142
if not res["OK"]:
142143
self.log.error("Could not upload", f"to {uploadSE}: {res['Message']}")
143144
else:
144-
self.log.info("File uploaded: ", f"LFN = {res['Value']}")
145+
self.log.verbose("File uploaded: ", f"LFN = {res['Value']}")
145146
try:
146147
os.remove(name)
147148
except Exception as excp:

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

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import unittest
22
import os
33
from contextlib import contextmanager
4-
from mock import MagicMock, patch, call
4+
from unittest.mock import MagicMock, patch, call
55

66
from DIRAC import gLogger, gConfig, S_OK, S_ERROR
77
import DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent
@@ -69,8 +69,8 @@ def test_execute(self, mockExVO, mockSetup, mockVOs, mockOp):
6969
res = mAgent.execute()
7070
assert mockOp.called
7171
mockOp.assert_called_with(vo=vo, setup=mockSetup.return_value)
72-
mockOp.return_value.getValue.assert_called_with("/Services/JobMonitoring/usePilotsLoggingFlag", False)
73-
mockOp.return_value.getOptionsDict.assert_called_with("/Shifter/DataManager")
72+
mockOp.return_value.getValue.assert_called_with("/Pilot/RemoteLogging", True)
73+
mockOp.return_value.getOptionsDict.assert_called_with("Shifter/DataManager")
7474
mAgent.executeForVO.assert_called_with(
7575
vo,
7676
proxyUserName=upDict["Value"]["User"],
@@ -101,44 +101,52 @@ def test_execute(self, mockExVO, mockSetup, mockVOs, mockOp):
101101
self.assertIn(vo, res)
102102
self.assertIsNotNone(res[vo])
103103

104+
@patch("DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent.Operations")
104105
@patch("DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent.DataManager")
105106
@patch.object(DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent.os, "listdir")
106107
@patch.object(DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent.os, "remove")
107108
@patch.object(DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent.os.path, "isfile")
109+
@patch.object(DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent.os.path, "exists")
108110
@patch("DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent.TornadoPilotLoggingClient")
109111
@patch.object(DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent, "executeWithUserProxy")
110-
def test_executeForVO(self, mockProxy, mockTC, mockisfile, mockremove, mocklistdir, mockDM):
112+
def test_executeForVO(self, mockProxy, mockTC, mockexists, mockisfile, mockremove, mocklistdir, mockDM, mockOp):
111113
with patch_parent(PilotLoggingAgent) as MockAgent:
114+
115+
opsHelperValues = {"UploadSE": "testUploadSE", "UploadPath": "/gridpp/uploadPath"}
112116
mAgent = MockAgent()
113-
opsHelperValues = ["uploadSE", "/uploadPath", "tornadoServer"]
114-
mAgent.opsHelper.getValue.side_effect = opsHelperValues
117+
mockOp.return_value.getOptionsDict.return_value = opsHelperValues
118+
mAgent.opsHelper = mockOp.return_value
115119
mockisfile.return_value = True
116120
mocklistdir.return_value = ["file1.log", "file2.log", "file3.log"]
117121
resDict = {"OK": True, "Value": {"LogPath": "/pilot/log/path/"}}
118122
mockTC.return_value.getMetadata.return_value = resDict
123+
vo = "gridpp"
119124

120125
# success route
121-
res = mAgent.executeForVO(vo="gridpp")
126+
res = mAgent.executeForVO(vo=vo)
122127

123-
mockTC.assert_called_with("tornadoServer", useCertificates=True)
128+
mockTC.assert_called_with(useCertificates=True)
124129
assert mockTC.return_value.getMetadata.called
125-
mocklistdir.assert_called_with(resDict["Value"]["LogPath"])
130+
mockexists.return_value = True
131+
mocklistdir.assert_called_with(os.path.join(resDict["Value"]["LogPath"], vo))
126132

127133
calls = []
128134
for elem in mocklistdir.return_value:
129-
calls.append(call(os.path.join(resDict["Value"]["LogPath"], elem)))
130-
mockisfile.has_calls(calls)
135+
calls.append(call(os.path.join(resDict["Value"]["LogPath"], vo, elem)))
136+
mockisfile.assert_has_calls(calls)
131137

132138
assert mockDM.called
133139
mockDM.return_value.putAndRegister.return_value = {"OK": True}
134140

135141
calls = []
142+
mockDM.return_value.putAndRegister.assert_called()
136143
for elem in mocklistdir.return_value:
137-
lfn = opsHelperValues[1] + elem
138-
name = resDict["Value"]["LogPath"] + elem
139-
calls.append(call(lfn=lfn, fileName=name, diracSE=opsHelperValues[0], overwrite=True))
144+
lfn = os.path.join(opsHelperValues["UploadPath"], elem)
145+
name = os.path.join(resDict["Value"]["LogPath"], vo, elem)
146+
mockDM.return_value.putAndRegister.assert_any_call(
147+
lfn=lfn, fileName=name, diracSE=opsHelperValues["UploadSE"], overwrite=True
148+
)
140149

141-
mockDM.return_value.putAndRegister.has_calls(calls)
142150
call_count = len(mocklistdir.return_value)
143151
self.assertEqual(call_count, mockDM.return_value.putAndRegister.call_count)
144152
self.assertEqual(call_count, mockremove.call_count)
@@ -151,26 +159,27 @@ def test_executeForVO(self, mockProxy, mockTC, mockisfile, mockremove, mocklistd
151159
mAgent.opsHelper.getValue.side_effect = opsHelperValues
152160
mockTC.reset_mock(return_value=True)
153161
mockTC.return_value.getMetadata.return_value = {"OK": False, "Message": "Failed, sorry.."}
154-
res = mAgent.executeForVO(vo="gridpp")
162+
res = mAgent.executeForVO(vo=vo)
155163
self.assertFalse(res["OK"])
156164

157165
# config values not correct:
158166
opsHelperValues = [None, "/uploadPath", "tornadoServer"]
159167
mAgent.opsHelper.getValue.side_effect = opsHelperValues
160-
res = mAgent.executeForVO(vo="gridpp")
168+
res = mAgent.executeForVO(vo=vo)
161169
self.assertFalse(res["OK"])
162170

163171
opsHelperValues = ["uploadSE", None, "tornadoServer"]
164172
mAgent.opsHelper.getValue.side_effect = opsHelperValues
165-
res = mAgent.executeForVO(vo="gridpp")
173+
res = mAgent.executeForVO(vo=vo)
166174
self.assertFalse(res["OK"])
167175

168176
opsHelperValues = ["uploadSE", "uploadPath", None]
169177
mAgent.opsHelper.getValue.side_effect = opsHelperValues
170-
res = mAgent.executeForVO(vo="gridpp")
178+
res = mAgent.executeForVO(vo=vo)
171179
self.assertFalse(res["OK"])
172180

173181

174182
if __name__ == "__main__":
175-
suite = unittest.defaultTestLoader.loadTestsFromTestCase(PilotAgentTestCase)
176-
testResult = unittest.TextTestResult(verbosity=2).run(suite)
183+
suite = unittest.defaultTestLoader.loadTestsFromTestCase(PilotLoggingAgentTestCase)
184+
runner = unittest.TextTestRunner(verbosity=2)
185+
runner.run(suite)

src/DIRAC/WorkloadManagementSystem/Client/TornadoPilotLoggingClient.py

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,8 @@ def __init__(self, url=None, **kwargs):
1010
:param str url: Server URL, if None, defaults to "WorkloadManagement/TornadoPilotLogging"
1111
:param dict kwargs: additional keyword arguments, currently unused.
1212
"""
13-
super(TornadoPilotLoggingClient, self).__init__(**kwargs)
13+
super().__init__(**kwargs)
1414
if not url:
1515
self.serverURL = "WorkloadManagement/TornadoPilotLogging"
1616
else:
1717
self.serverURL = url
18-
19-
def getMetadata(self):
20-
"""
21-
Get metadata from the server.
22-
23-
:return: Dirac S_OK/S_ERROR dictionary with server properties.
24-
:rtype: dict
25-
"""
26-
27-
retVal = self._getRPC().getMetadata()
28-
29-
return retVal

src/DIRAC/WorkloadManagementSystem/Client/test/Test_TornadoPilotLoggingClient.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@
66

77

88
class TestTornadoPilotLoggingClient(unittest.TestCase):
9-
@patch.object(tplc.TornadoPilotLoggingClient, "executeRPC", return_value={"key": "value"})
10-
def test_client(self, clientMock):
9+
def test_client(self):
1110
client = tplc.TornadoPilotLoggingClient("test.server", useCertificates=True)
12-
res = client.getMetadata()
13-
clientMock.assert_called_with("getMetadata")
14-
self.assertEqual(res, clientMock.return_value)
11+
self.assertEqual(client.serverURL, "test.server")
12+
client = tplc.TornadoPilotLoggingClient(useCertificates=True)
13+
self.assertEqual(client.serverURL, "WorkloadManagement/TornadoPilotLogging")
1514

1615

1716
if __name__ == "__main__":

src/DIRAC/WorkloadManagementSystem/ConfigTemplate.cfg

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ Services
2626
{
2727
Default = authenticated
2828
sendMessage = "Operator"
29-
sendMessage += "Pilot"
3029
sendMessage += "GenericPilot"
3130
getMetadata = "Operator"
3231
getMetadata += "TrustedHost"
@@ -194,7 +193,6 @@ Agents
194193
PilotLoggingAgent
195194
{
196195
PollingTime = 600
197-
LogLevel = DEBUG
198196
}
199197
##END
200198
JobAgent

src/DIRAC/WorkloadManagementSystem/Service/BasicPilotLoggingPlugin.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ def sendMessage(self, message, UUID, vo):
2121
"""
2222
Dummy sendMessage method.
2323
24-
:param message: text to log
25-
:type message: str
24+
:param str message: text to log
2625
:return: None
2726
:rtype: None
2827
"""

src/DIRAC/WorkloadManagementSystem/Service/FileCacheLoggingPlugin.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
"""
22
File cache logging plugin.
33
"""
4-
import os, json, re
4+
import os
5+
import json
6+
import re
57
from DIRAC import S_OK, S_ERROR, gLogger
68

79
sLog = gLogger.getSubLogger(__name__)
@@ -62,10 +64,10 @@ def sendMessage(self, message, pilotUUID, vo):
6264
else:
6365
# it could be a string, if emitted by pilot logger StringIO handler
6466
pilotLog.write(messageContent)
65-
except IOError as ioerr:
66-
sLog.error("Error writing to log file:", str(ioerr))
67-
return S_ERROR(str(ioerr))
68-
except IOError as err:
67+
except OSError as oserr:
68+
sLog.error("Error writing to log file:", str(oserr))
69+
return S_ERROR(str(oserr))
70+
except OSError as err:
6971
sLog.exception("Error opening a pilot log file:", str(err), lException=err)
7072
return S_ERROR(str(err))
7173
return S_OK(f"Message logged successfully for pilot: {pilotUUID} and {vo}")

src/DIRAC/WorkloadManagementSystem/Service/TornadoPilotLoggingHandler.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ def initializeHandler(cls, infoDict):
2828
defaultOption, defaultClass = "LoggingPlugin", "BasicPilotLoggingPlugin"
2929
configValue = getServiceOption(infoDict, defaultOption, defaultClass)
3030

31-
result = ObjectLoader().loadObject("WorkloadManagementSystem.Service.%s" % (configValue,), configValue)
31+
result = ObjectLoader().loadObject(f"WorkloadManagementSystem.Service.{configValue}", configValue)
3232
if not result["OK"]:
33-
cls.log.error("Failed to load LoggingPlugin", "%s: %s" % (configValue, result["Message"]))
33+
cls.log.error("Failed to load LoggingPlugin", "{}: {}".format(configValue, result["Message"]))
3434
return result
3535

3636
componentClass = result["Value"]
@@ -44,15 +44,6 @@ def initializeHandler(cls, infoDict):
4444
os.makedirs(logPath)
4545
cls.log.info("Pilot logging directory:", logPath)
4646

47-
def initializeRequest(self):
48-
"""
49-
Called for each request.
50-
51-
:return: None
52-
"""
53-
54-
self.log.info("Request initialised.. ")
55-
5647
def export_sendMessage(self, message, pilotUUID):
5748
# def export_sendMessage(self, message, pilotUUID):
5849
"""

src/DIRAC/WorkloadManagementSystem/Utilities/test/Test_TornadoPilotLoggingHandler.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import unittest
2-
from mock import patch
2+
from unittest.mock import patch
33
import os
44
import json
55
import tempfile
@@ -84,26 +84,27 @@ def test_FileCachePlugin(self, mockExists, mockGetcwd, mockMakedirs):
8484
+ "2022-02-23 13:48:36.123456 UTC DEBUG [PilotParams] JSON file analysed: pilot.json"
8585
)
8686
messageJSON = json.dumps(messsageText)
87+
vo = "anyVO"
8788
pilotUUID = "78f39a90-2073-11ec-98d7-b496913c0cf4"
8889

8990
# use a temporary dir, not the one above. Plugin will create the file to write into.
9091
with tempfile.TemporaryDirectory(suffix="pilottests") as d:
9192
plugin.meta["LogPath"] = d
92-
res = plugin.sendMessage(messageJSON, pilotUUID)
93+
res = plugin.sendMessage(messageJSON, pilotUUID, vo)
9394
self.assertTrue(res["OK"])
94-
with open(os.path.join(d, pilotUUID), "r") as pilotLog:
95+
with open(os.path.join(d, vo, pilotUUID)) as pilotLog:
9596
content = pilotLog.read()
9697
self.assertEqual(content, messsageText)
9798

9899
# failures ?
99100
with tempfile.TemporaryDirectory(suffix="pilottests") as d:
100101
plugin.meta["LogPath"] = d
101102
os.chmod(d, 0o0000)
102-
res = plugin.sendMessage(messageJSON, pilotUUID)
103+
res = plugin.sendMessage(messageJSON, pilotUUID, vo)
103104
self.assertFalse(res["OK"])
104105

105106
pilotUUID = "whatever"
106-
res = plugin.sendMessage(messageJSON, pilotUUID)
107+
res = plugin.sendMessage(messageJSON, pilotUUID, vo)
107108
self.assertFalse(res["OK"])
108109

109110
@patch.object(DIRAC.WorkloadManagementSystem.Service.TornadoPilotLoggingHandler.os.path, "exists")
@@ -124,25 +125,28 @@ def test_finaliseLogs(self, mockGetcwd, mockExists):
124125
mockExists.return_value = True # will not create a file
125126
mockGetcwd.return_value = "/tornado/document/root" # so we have a path defined (will overwrite it below)
126127
plugin = FileCacheLoggingPlugin()
128+
vo = "anyVO"
127129

128130
with tempfile.TemporaryDirectory(suffix="pilottests") as d:
129131
plugin.meta["LogPath"] = d
130132
payload = '{"retCode": 0}'
131133
logfile = "78f39a90-2073-11ec-98d7-b496913c0cf4" # == pilotUUID
134+
os.mkdir(os.path.join(d, vo)) # vo specific directory, normally created by sending the first message
132135
# will fail here...
133-
res = plugin.finaliseLogs(payload, logfile)
136+
res = plugin.finaliseLogs(payload, logfile, vo)
134137
self.assertFalse(res["OK"])
135138
# create a file ..
136-
with open(os.path.join(d, logfile), "w") as f:
139+
with open(os.path.join(d, vo, logfile), "w") as f:
137140
f.write("Create a dummy logfile")
138-
res = plugin.finaliseLogs(payload, logfile)
141+
res = plugin.finaliseLogs(payload, logfile, vo)
139142
self.assertTrue(res["OK"])
140143

141144
logfile = "invalid!"
142-
res = plugin.finaliseLogs(payload, logfile)
145+
res = plugin.finaliseLogs(payload, logfile, vo)
143146
self.assertFalse(res["OK"])
144147

145148

146149
if __name__ == "__main__":
147150
suite = unittest.defaultTestLoader.loadTestsFromTestCase(TornadoPilotLoggingHandlerTestCase)
148-
testResult = unittest.TextTestResult(verbosity=2).run(suite)
151+
runner = unittest.TextTestRunner(verbosity=2)
152+
runner.run(suite)

0 commit comments

Comments
 (0)