|
| 1 | +""" Test class for PilotLoggingAgent Agent |
| 2 | +""" |
| 3 | +import os |
| 4 | +import time |
| 5 | +import tempfile |
| 6 | + |
| 7 | +import pytest |
| 8 | +from unittest.mock import MagicMock, patch |
| 9 | + |
| 10 | +# DIRAC Components |
| 11 | +import DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent as plaModule |
| 12 | +from DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent import PilotLoggingAgent |
| 13 | +from DIRAC import gLogger, gConfig, S_OK, S_ERROR |
| 14 | + |
| 15 | +gLogger.setLevel("DEBUG") |
| 16 | + |
| 17 | +# Mock Objects |
| 18 | +mockReply = MagicMock() |
| 19 | +mockReply1 = MagicMock() |
| 20 | +mockOperations = MagicMock() |
| 21 | +mockTornadoClient = MagicMock() |
| 22 | +mockDataManager = MagicMock() |
| 23 | +mockAM = MagicMock() |
| 24 | +mockNone = MagicMock() |
| 25 | +mockNone.return_value = None |
| 26 | + |
| 27 | +upDict = { |
| 28 | + "OK": True, |
| 29 | + "Value": {"User": "proxyUser", "Group": "proxyGroup"}, |
| 30 | +} |
| 31 | + |
| 32 | + |
| 33 | +@pytest.fixture |
| 34 | +def plaBase(mocker): |
| 35 | + mocker.patch("DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent.AgentModule.__init__") |
| 36 | + mocker.patch( |
| 37 | + "DIRAC.WorkloadManagementSystem.Agent.JobCleaningAgent.AgentModule._AgentModule__moduleProperties", |
| 38 | + side_effect=lambda x, y=None: y, |
| 39 | + create=True, |
| 40 | + ) |
| 41 | + mocker.patch("DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent.AgentModule.am_getOption", return_value=mockAM) |
| 42 | + mocker.patch( |
| 43 | + "DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent.getVOs", |
| 44 | + return_value={"OK": True, "Value": ["gridpp", "lz"]}, |
| 45 | + ) |
| 46 | + mocker.patch("DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent.gConfig.getValue", return_value="GridPP") |
| 47 | + mocker.patch("DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent.Operations.getValue", side_effect=mockReply) |
| 48 | + mocker.patch( |
| 49 | + "DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent.Operations.getOptionsDict", side_effect=mockReply1 |
| 50 | + ) |
| 51 | + pla = PilotLoggingAgent() |
| 52 | + pla.log = gLogger |
| 53 | + pla._AgentModule__configDefaults = mockAM |
| 54 | + return pla |
| 55 | + |
| 56 | + |
| 57 | +@pytest.fixture |
| 58 | +def pla_initialised(mocker, plaBase): |
| 59 | + mocker.patch("DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent.PilotLoggingAgent.executeForVO") |
| 60 | + plaBase.initialize() |
| 61 | + return plaBase |
| 62 | + |
| 63 | + |
| 64 | +@pytest.fixture |
| 65 | +def pla(mocker, plaBase): |
| 66 | + mocker.patch( |
| 67 | + "DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent.TornadoPilotLoggingClient", |
| 68 | + side_effect=mockTornadoClient, |
| 69 | + ) |
| 70 | + mocker.patch("DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent.Operations", side_effect=mockOperations) |
| 71 | + mocker.patch( |
| 72 | + "DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent.DataManager", |
| 73 | + side_effect=mockDataManager, |
| 74 | + ) |
| 75 | + plaBase.initialize() |
| 76 | + return plaBase |
| 77 | + |
| 78 | + |
| 79 | +def test_initialize(plaBase): |
| 80 | + res = plaBase.initialize() |
| 81 | + assert plaBase.voList == plaModule.getVOs()["Value"] |
| 82 | + assert res == S_OK() |
| 83 | + |
| 84 | + |
| 85 | +@pytest.mark.parametrize( |
| 86 | + "mockReplyInput, expected, expectedExecOut, expected2", |
| 87 | + [ |
| 88 | + ("/Pilot/RemoteLogging", [True, False], S_OK(), upDict), |
| 89 | + ("/Pilot/RemoteLogging", [False, False], S_OK(), upDict), |
| 90 | + ("/Pilot/RemoteLogging", [True, False], S_ERROR("Execute for VO failed"), upDict), |
| 91 | + ], |
| 92 | +) |
| 93 | +def test_execute(pla_initialised, mockReplyInput, expected, expectedExecOut, expected2): |
| 94 | + """Testing a thin version of execute (executeForVO is mocked)""" |
| 95 | + assert pla_initialised.voList == plaModule.getVOs()["Value"] |
| 96 | + mockReply.side_effect = expected |
| 97 | + mockReply1.return_value = expected2 |
| 98 | + # remote pilot logging on (gridpp only) and off. |
| 99 | + pla_initialised.executeForVO.return_value = expectedExecOut |
| 100 | + res = pla_initialised.execute() |
| 101 | + if not any(expected): |
| 102 | + pla_initialised.executeForVO.assert_not_called() |
| 103 | + else: |
| 104 | + assert pla_initialised.executeForVO.called |
| 105 | + pla_initialised.executeForVO.assert_called_with( |
| 106 | + "gridpp", |
| 107 | + proxyUserName=upDict["Value"]["User"], |
| 108 | + proxyUserGroup=upDict["Value"]["Group"], |
| 109 | + ) |
| 110 | + assert res["OK"] == expectedExecOut["OK"] |
| 111 | + |
| 112 | + |
| 113 | +@pytest.mark.parametrize( |
| 114 | + "ppath, files, result", |
| 115 | + [ |
| 116 | + ("pilot/log/path/", ["file1.log", "file2.log", "file3.log"], S_OK()), |
| 117 | + ("pilot/log/path/", [], S_OK()), |
| 118 | + ], |
| 119 | +) |
| 120 | +def test_executeForVO(pla, ppath, files, result): |
| 121 | + opsHelperValues = {"OK": True, "Value": {"UploadSE": "testUploadSE", "UploadPath": "/gridpp/uploadPath"}} |
| 122 | + # full local temporary path: |
| 123 | + filepath = os.path.join(tempfile.TemporaryDirectory().name, ppath) |
| 124 | + # this is what getMetadata returns: |
| 125 | + resDict = {"OK": True, "Value": {"LogPath": filepath}} |
| 126 | + mockTornadoClient.return_value.getMetadata.return_value = resDict |
| 127 | + mockDataManager.return_value.putAndRegister.return_value = result |
| 128 | + if files: |
| 129 | + os.makedirs(os.path.join(filepath, "gridpp"), exist_ok=True) |
| 130 | + for elem in files: |
| 131 | + open(os.path.join(filepath, "gridpp", elem), "w") |
| 132 | + mockOperations.return_value.getOptionsDict.return_value = opsHelperValues |
| 133 | + pla.opsHelper = mockOperations.return_value |
| 134 | + # success route |
| 135 | + res = pla.executeForVO(vo="gridpp") |
| 136 | + mockTornadoClient.assert_called_with(useCertificates=True) |
| 137 | + assert mockTornadoClient.return_value.getMetadata.called |
| 138 | + # only called with a non-empty file list: |
| 139 | + if files: |
| 140 | + assert mockDataManager.return_value.putAndRegister.called |
| 141 | + assert res == S_OK() |
| 142 | + |
| 143 | + |
| 144 | +def test_executeForVOMetaFails(pla): |
| 145 | + opsHelperValues = {"OK": True, "Value": {"UploadSE": "testUploadSE", "UploadPath": "/gridpp/uploadPath"}} |
| 146 | + mockOperations.return_value.getOptionsDict.return_value = opsHelperValues |
| 147 | + pla.opsHelper = mockOperations.return_value |
| 148 | + # getMetadata call fails. |
| 149 | + mockTornadoClient.return_value.getMetadata.return_value = {"OK": False, "Message": "Failed, sorry.."} |
| 150 | + res = pla.executeForVO(vo="anything") |
| 151 | + assert res["OK"] is False |
| 152 | + |
| 153 | + |
| 154 | +@pytest.mark.parametrize( |
| 155 | + "opsHelperValues, expectedRes", |
| 156 | + [ |
| 157 | + ({"OK": True, "Value": {"UploadPath": "/gridpp/uploadPath"}}, S_ERROR("Upload SE not defined")), |
| 158 | + ({"OK": True, "Value": {"UploadSE": "testUploadSE"}}, S_ERROR("Upload path on SE testUploadSE not defined")), |
| 159 | + ({"OK": False}, S_ERROR(f"No pilot section for gridpp vo")), |
| 160 | + ], |
| 161 | +) |
| 162 | +def test_executeForVOBadConfig(pla, opsHelperValues, expectedRes): |
| 163 | + """Testing an incomplete configuration""" |
| 164 | + mockOperations.return_value.getOptionsDict.return_value = opsHelperValues |
| 165 | + pla.opsHelper = mockOperations.return_value |
| 166 | + res = pla.executeForVO(vo="gridpp") |
| 167 | + assert res["OK"] is False |
| 168 | + assert res["Message"] == expectedRes["Message"] |
| 169 | + mockTornadoClient.return_value.getMetadata.reset_mock() |
| 170 | + mockTornadoClient.return_value.getMetadata.assert_not_called() |
| 171 | + |
| 172 | + |
| 173 | +@pytest.mark.parametrize( |
| 174 | + "filename, fileAge, ageLimit, expectedResult", [("survives.log", 10, 20, True), ("getsdeleted.log", 21, 20, False)] |
| 175 | +) |
| 176 | +def test_oldLogsCleaner(plaBase, filename, fileAge, ageLimit, expectedResult): |
| 177 | + """Testing old files removal""" |
| 178 | + plaBase.clearPilotsDelay = ageLimit |
| 179 | + filepath = tempfile.TemporaryDirectory().name |
| 180 | + os.makedirs(filepath, exist_ok=True) |
| 181 | + testfile = os.path.join(filepath, filename) |
| 182 | + fd = open(testfile, "w") |
| 183 | + fd.close() |
| 184 | + assert os.path.exists(testfile) is True |
| 185 | + # cannot patch os.stat globally because os.path.exists uses it ! |
| 186 | + with patch("DIRAC.WorkloadManagementSystem.Agent.PilotLoggingAgent.os.stat") as mockOSStat: |
| 187 | + mockOSStat.return_value.st_mtime = time.time() - fileAge * 86400 # file older that fileAge in seconds |
| 188 | + plaBase.clearOldPilotLogs(filepath) |
| 189 | + assert os.path.exists(testfile) is expectedResult |
0 commit comments