DIRAC 9.0.0a64__py3-none-any.whl → 9.0.0a66__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- DIRAC/ConfigurationSystem/Client/LocalConfiguration.py +11 -8
- DIRAC/Core/Utilities/DErrno.py +5 -309
- DIRAC/Core/Utilities/ReturnValues.py +7 -252
- DIRAC/Core/scripts/dirac_agent.py +1 -1
- DIRAC/TransformationSystem/Agent/TransformationCleaningAgent.py +1 -1
- DIRAC/WorkloadManagementSystem/Agent/JobCleaningAgent.py +1 -1
- DIRAC/WorkloadManagementSystem/Agent/StalledJobAgent.py +1 -1
- DIRAC/WorkloadManagementSystem/DB/StatusUtils.py +125 -0
- DIRAC/WorkloadManagementSystem/DB/tests/Test_StatusUtils.py +28 -0
- DIRAC/WorkloadManagementSystem/Service/JobManagerHandler.py +1 -1
- DIRAC/WorkloadManagementSystem/Utilities/jobAdministration.py +0 -123
- DIRAC/__init__.py +55 -54
- {dirac-9.0.0a64.dist-info → dirac-9.0.0a66.dist-info}/METADATA +2 -1
- {dirac-9.0.0a64.dist-info → dirac-9.0.0a66.dist-info}/RECORD +18 -17
- DIRAC/WorkloadManagementSystem/Utilities/test/Test_JobAdministration.py +0 -28
- {dirac-9.0.0a64.dist-info → dirac-9.0.0a66.dist-info}/WHEEL +0 -0
- {dirac-9.0.0a64.dist-info → dirac-9.0.0a66.dist-info}/entry_points.txt +0 -0
- {dirac-9.0.0a64.dist-info → dirac-9.0.0a66.dist-info}/licenses/LICENSE +0 -0
- {dirac-9.0.0a64.dist-info → dirac-9.0.0a66.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
1
|
+
"""This is the guy that parses and interprets the local configuration options."""
|
|
2
|
+
|
|
3
3
|
import re
|
|
4
4
|
import os
|
|
5
5
|
import sys
|
|
@@ -286,16 +286,18 @@ class LocalConfiguration:
|
|
|
286
286
|
gLogger.exception()
|
|
287
287
|
return S_ERROR(str(e))
|
|
288
288
|
|
|
289
|
-
def initialize(self, *, returnErrors=False):
|
|
289
|
+
def initialize(self, *, returnErrors=False, requireSuccessfulSync=False):
|
|
290
290
|
"""Entrypoint used by :py:class:`DIRAC.initialize`
|
|
291
291
|
|
|
292
|
+
:param requireSuccessfulSync: fails if syncing with the remote did not work
|
|
293
|
+
|
|
292
294
|
TODO: This is currently a hack that returns a list of errors for so it
|
|
293
295
|
can be used by ``__addUserDataToConfiguration``. This entire module
|
|
294
296
|
should be refactored and simplified with ``Script.parseCommandLine``.
|
|
295
297
|
"""
|
|
296
298
|
errorsList = self.__loadCFGFiles()
|
|
297
299
|
if gConfigurationData.getServers():
|
|
298
|
-
retVal = self.syncRemoteConfiguration()
|
|
300
|
+
retVal = self.syncRemoteConfiguration(strict=requireSuccessfulSync)
|
|
299
301
|
if not retVal["OK"]:
|
|
300
302
|
return retVal
|
|
301
303
|
else:
|
|
@@ -321,7 +323,7 @@ class LocalConfiguration:
|
|
|
321
323
|
gLogger.showHeaders(True)
|
|
322
324
|
gLogger.enableLogsFromExternalLibs()
|
|
323
325
|
|
|
324
|
-
def loadUserData(self):
|
|
326
|
+
def loadUserData(self, requireSuccessfulSync=False):
|
|
325
327
|
"""
|
|
326
328
|
This is the magic method that reads the command line and processes it
|
|
327
329
|
It is used by the Script Base class and the dirac-service and dirac-agent scripts
|
|
@@ -329,6 +331,7 @@ class LocalConfiguration:
|
|
|
329
331
|
- any additional switches to be processed
|
|
330
332
|
- mandatory and default configuration configuration options must be defined.
|
|
331
333
|
|
|
334
|
+
:param requireSuccessfulSync: if True, will fail if the sync with remote server failed
|
|
332
335
|
"""
|
|
333
336
|
if self.initialized:
|
|
334
337
|
return S_OK()
|
|
@@ -336,7 +339,7 @@ class LocalConfiguration:
|
|
|
336
339
|
try:
|
|
337
340
|
if not self.isParsed:
|
|
338
341
|
self.__parseCommandLine() # Parse command line
|
|
339
|
-
retVal = self.__addUserDataToConfiguration()
|
|
342
|
+
retVal = self.__addUserDataToConfiguration(requireSuccessfulSync=requireSuccessfulSync)
|
|
340
343
|
|
|
341
344
|
for optionTuple in self.optionalEntryList:
|
|
342
345
|
optionPath = self.__getAbsolutePath(optionTuple[0])
|
|
@@ -496,8 +499,8 @@ class LocalConfiguration:
|
|
|
496
499
|
|
|
497
500
|
return errorsList
|
|
498
501
|
|
|
499
|
-
def __addUserDataToConfiguration(self):
|
|
500
|
-
retVal = self.initialize(returnErrors=True)
|
|
502
|
+
def __addUserDataToConfiguration(self, requireSuccessfulSync=False):
|
|
503
|
+
retVal = self.initialize(returnErrors=True, requireSuccessfulSync=requireSuccessfulSync)
|
|
501
504
|
if not retVal["OK"]:
|
|
502
505
|
return retVal
|
|
503
506
|
errorsList = retVal["Value"]
|
DIRAC/Core/Utilities/DErrno.py
CHANGED
|
@@ -37,320 +37,16 @@
|
|
|
37
37
|
DErrno.ERRX : ['An error message for ERRX that is specific to LHCb']}
|
|
38
38
|
|
|
39
39
|
"""
|
|
40
|
-
import os
|
|
41
40
|
import importlib
|
|
42
41
|
import sys
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
# To avoid conflict, the error numbers should be greater than 1000
|
|
47
|
-
# We decided to group the by range of 100 per system
|
|
48
|
-
|
|
49
|
-
# 1000: Generic
|
|
50
|
-
# 1100: Core
|
|
51
|
-
# 1200: Framework
|
|
52
|
-
# 1300: Interfaces
|
|
53
|
-
# 1400: Config
|
|
54
|
-
# 1500: WMS + Workflow
|
|
55
|
-
# 1600: DMS + StorageManagement
|
|
56
|
-
# 1700: RMS
|
|
57
|
-
# 1800: Accounting + Monitoring
|
|
58
|
-
# 1900: TS + Production
|
|
59
|
-
# 2000: Resources + RSS
|
|
60
|
-
|
|
61
|
-
# ## Generic (10XX)
|
|
62
|
-
# Python related: 0X
|
|
63
|
-
ETYPE = 1000
|
|
64
|
-
EIMPERR = 1001
|
|
65
|
-
ENOMETH = 1002
|
|
66
|
-
ECONF = 1003
|
|
67
|
-
EVALUE = 1004
|
|
68
|
-
EEEXCEPTION = 1005
|
|
69
|
-
# Files manipulation: 1X
|
|
70
|
-
ECTMPF = 1010
|
|
71
|
-
EOF = 1011
|
|
72
|
-
ERF = 1012
|
|
73
|
-
EWF = 1013
|
|
74
|
-
ESPF = 1014
|
|
75
|
-
|
|
76
|
-
# ## Core (11XX)
|
|
77
|
-
# Certificates and Proxy: 0X
|
|
78
|
-
EX509 = 1100
|
|
79
|
-
EPROXYFIND = 1101
|
|
80
|
-
EPROXYREAD = 1102
|
|
81
|
-
ECERTFIND = 1103
|
|
82
|
-
ECERTREAD = 1104
|
|
83
|
-
ENOCERT = 1105
|
|
84
|
-
ENOCHAIN = 1106
|
|
85
|
-
ENOPKEY = 1107
|
|
86
|
-
ENOGROUP = 1108
|
|
87
|
-
# DISET: 1X
|
|
88
|
-
EDISET = 1110
|
|
89
|
-
ENOAUTH = 1111
|
|
90
|
-
# 3rd party security: 2X
|
|
91
|
-
E3RDPARTY = 1120
|
|
92
|
-
EVOMS = 1121
|
|
93
|
-
# Databases : 3X
|
|
94
|
-
EDB = 1130
|
|
95
|
-
EMYSQL = 1131
|
|
96
|
-
ESQLA = 1132
|
|
97
|
-
# Message Queues: 4X
|
|
98
|
-
EMQUKN = 1140
|
|
99
|
-
EMQNOM = 1141
|
|
100
|
-
EMQCONN = 1142
|
|
101
|
-
# OpenSearch
|
|
102
|
-
EELNOFOUND = 1146
|
|
103
|
-
# Tokens
|
|
104
|
-
EATOKENFIND = 1150
|
|
105
|
-
EATOKENREAD = 1151
|
|
106
|
-
ETOKENTYPE = 1152
|
|
107
|
-
|
|
108
|
-
# config
|
|
109
|
-
ESECTION = 1400
|
|
110
|
-
|
|
111
|
-
# processes
|
|
112
|
-
EEZOMBIE = 1147
|
|
113
|
-
EENOPID = 1148
|
|
114
|
-
|
|
115
|
-
# ## WMS/Workflow
|
|
116
|
-
EWMSUKN = 1500
|
|
117
|
-
EWMSJDL = 1501
|
|
118
|
-
EWMSRESC = 1502
|
|
119
|
-
EWMSSUBM = 1503
|
|
120
|
-
EWMSJMAN = 1504
|
|
121
|
-
EWMSSTATUS = 1505
|
|
122
|
-
EWMSNOMATCH = 1510
|
|
123
|
-
EWMSPLTVER = 1511
|
|
124
|
-
EWMSNOPILOT = 1550
|
|
125
|
-
|
|
126
|
-
# ## DMS/StorageManagement (16XX)
|
|
127
|
-
EFILESIZE = 1601
|
|
128
|
-
EGFAL = 1602
|
|
129
|
-
EBADCKS = 1603
|
|
130
|
-
EFCERR = 1604
|
|
131
|
-
|
|
132
|
-
# ## RMS (17XX)
|
|
133
|
-
ERMSUKN = 1700
|
|
134
|
-
|
|
135
|
-
# ## TS (19XX)
|
|
136
|
-
ETSUKN = 1900
|
|
137
|
-
ETSDATA = 1901
|
|
138
|
-
|
|
139
|
-
# ## Resources and RSS (20XX)
|
|
140
|
-
ERESGEN = 2000
|
|
141
|
-
ERESUNA = 2001
|
|
142
|
-
ERESUNK = 2002
|
|
143
|
-
|
|
144
|
-
# This translates the integer number into the name of the variable
|
|
145
|
-
dErrorCode = {
|
|
146
|
-
# ## Generic (10XX)
|
|
147
|
-
# 100X: Python related
|
|
148
|
-
1000: "ETYPE",
|
|
149
|
-
1001: "EIMPERR",
|
|
150
|
-
1002: "ENOMETH",
|
|
151
|
-
1003: "ECONF",
|
|
152
|
-
1004: "EVALUE",
|
|
153
|
-
1005: "EEEXCEPTION",
|
|
154
|
-
# 101X: Files manipulation
|
|
155
|
-
1010: "ECTMPF",
|
|
156
|
-
1011: "EOF",
|
|
157
|
-
1012: "ERF",
|
|
158
|
-
1013: "EWF",
|
|
159
|
-
1014: "ESPF",
|
|
160
|
-
# ## Core
|
|
161
|
-
# 110X: Certificates and Proxy
|
|
162
|
-
1100: "EX509",
|
|
163
|
-
1101: "EPROXYFIND",
|
|
164
|
-
1102: "EPROXYREAD",
|
|
165
|
-
1103: "ECERTFIND",
|
|
166
|
-
1104: "ECERTREAD",
|
|
167
|
-
1105: "ENOCERT",
|
|
168
|
-
1106: "ENOCHAIN",
|
|
169
|
-
1107: "ENOPKEY",
|
|
170
|
-
1108: "ENOGROUP",
|
|
171
|
-
# 111X: DISET
|
|
172
|
-
1110: "EDISET",
|
|
173
|
-
1111: "ENOAUTH",
|
|
174
|
-
# 112X: 3rd party security
|
|
175
|
-
1120: "E3RDPARTY",
|
|
176
|
-
1121: "EVOMS",
|
|
177
|
-
# 113X: Databases
|
|
178
|
-
1130: "EDB",
|
|
179
|
-
1131: "EMYSQL",
|
|
180
|
-
1132: "ESQLA",
|
|
181
|
-
# 114X: Message Queues
|
|
182
|
-
1140: "EMQUKN",
|
|
183
|
-
1141: "EMQNOM",
|
|
184
|
-
1142: "EMQCONN",
|
|
185
|
-
# OpenSearch
|
|
186
|
-
1146: "EELNOFOUND",
|
|
187
|
-
# 115X: Tokens
|
|
188
|
-
1150: "EATOKENFIND",
|
|
189
|
-
1151: "EATOKENREAD",
|
|
190
|
-
1152: "ETOKENTYPE",
|
|
191
|
-
# Config
|
|
192
|
-
1400: "ESECTION",
|
|
193
|
-
# Processes
|
|
194
|
-
1147: "EEZOMBIE",
|
|
195
|
-
1148: "EENOPID",
|
|
196
|
-
# WMS/Workflow
|
|
197
|
-
1500: "EWMSUKN",
|
|
198
|
-
1501: "EWMSJDL",
|
|
199
|
-
1502: "EWMSRESC",
|
|
200
|
-
1503: "EWMSSUBM",
|
|
201
|
-
1504: "EWMSJMAN",
|
|
202
|
-
1505: "EWMSSTATUS",
|
|
203
|
-
1510: "EWMSNOMATCH",
|
|
204
|
-
1511: "EWMSPLTVER",
|
|
205
|
-
1550: "EWMSNOPILOT",
|
|
206
|
-
# DMS/StorageManagement
|
|
207
|
-
1601: "EFILESIZE",
|
|
208
|
-
1602: "EGFAL",
|
|
209
|
-
1603: "EBADCKS",
|
|
210
|
-
1604: "EFCERR",
|
|
211
|
-
# RMS
|
|
212
|
-
1700: "ERMSUKN",
|
|
213
|
-
# Resources and RSS
|
|
214
|
-
2000: "ERESGEN",
|
|
215
|
-
2001: "ERESUNA",
|
|
216
|
-
2002: "ERESUNK",
|
|
217
|
-
# TS
|
|
218
|
-
1900: "ETSUKN",
|
|
219
|
-
1901: "ETSDATA",
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
dStrError = { # Generic (10XX)
|
|
224
|
-
# 100X: Python related
|
|
225
|
-
ETYPE: "Object Type Error",
|
|
226
|
-
EIMPERR: "Failed to import library",
|
|
227
|
-
ENOMETH: "No such method or function",
|
|
228
|
-
ECONF: "Configuration error",
|
|
229
|
-
EVALUE: "Wrong value passed",
|
|
230
|
-
EEEXCEPTION: "runtime general exception",
|
|
231
|
-
# 101X: Files manipulation
|
|
232
|
-
ECTMPF: "Failed to create temporary file",
|
|
233
|
-
EOF: "Cannot open file",
|
|
234
|
-
ERF: "Cannot read from file",
|
|
235
|
-
EWF: "Cannot write to file",
|
|
236
|
-
ESPF: "Cannot set permissions to file",
|
|
237
|
-
# ## Core
|
|
238
|
-
# 110X: Certificates and Proxy
|
|
239
|
-
EX509: "Generic Error with X509",
|
|
240
|
-
EPROXYFIND: "Can't find proxy",
|
|
241
|
-
EPROXYREAD: "Can't read proxy",
|
|
242
|
-
ECERTFIND: "Can't find certificate",
|
|
243
|
-
ECERTREAD: "Can't read certificate",
|
|
244
|
-
ENOCERT: "No certificate loaded",
|
|
245
|
-
ENOCHAIN: "No chain loaded",
|
|
246
|
-
ENOPKEY: "No private key loaded",
|
|
247
|
-
ENOGROUP: "No DIRAC group",
|
|
248
|
-
# 111X: DISET
|
|
249
|
-
EDISET: "DISET Error",
|
|
250
|
-
ENOAUTH: "Unauthorized query",
|
|
251
|
-
# 112X: 3rd party security
|
|
252
|
-
E3RDPARTY: "3rd party security service error",
|
|
253
|
-
EVOMS: "VOMS Error",
|
|
254
|
-
# 113X: Databases
|
|
255
|
-
EDB: "Database Error",
|
|
256
|
-
EMYSQL: "MySQL Error",
|
|
257
|
-
ESQLA: "SQLAlchemy Error",
|
|
258
|
-
# 114X: Message Queues
|
|
259
|
-
EMQUKN: "Unknown MQ Error",
|
|
260
|
-
EMQNOM: "No messages",
|
|
261
|
-
EMQCONN: "MQ connection failure",
|
|
262
|
-
# 114X OpenSearch
|
|
263
|
-
EELNOFOUND: "Index not found",
|
|
264
|
-
# 115X: Tokens
|
|
265
|
-
EATOKENFIND: "Can't find a bearer access token.",
|
|
266
|
-
EATOKENREAD: "Can't read a bearer access token.",
|
|
267
|
-
ETOKENTYPE: "Unsupported access token type.",
|
|
268
|
-
# Config
|
|
269
|
-
ESECTION: "Section is not found",
|
|
270
|
-
# processes
|
|
271
|
-
EEZOMBIE: "Zombie process",
|
|
272
|
-
EENOPID: "No PID of process",
|
|
273
|
-
# WMS/Workflow
|
|
274
|
-
EWMSUKN: "Unknown WMS error",
|
|
275
|
-
EWMSJDL: "Invalid job description",
|
|
276
|
-
EWMSRESC: "Job to reschedule",
|
|
277
|
-
EWMSSUBM: "Job submission error",
|
|
278
|
-
EWMSJMAN: "Job management error",
|
|
279
|
-
EWMSSTATUS: "Job status error",
|
|
280
|
-
EWMSNOPILOT: "No pilots found",
|
|
281
|
-
EWMSPLTVER: "Pilot version does not match",
|
|
282
|
-
EWMSNOMATCH: "No match found",
|
|
283
|
-
# DMS/StorageManagement
|
|
284
|
-
EFILESIZE: "Bad file size",
|
|
285
|
-
EGFAL: "Error with the gfal call",
|
|
286
|
-
EBADCKS: "Bad checksum",
|
|
287
|
-
EFCERR: "FileCatalog error",
|
|
288
|
-
# RMS
|
|
289
|
-
ERMSUKN: "Unknown RMS error",
|
|
290
|
-
# Resources and RSS
|
|
291
|
-
ERESGEN: "Unknown Resource Failure",
|
|
292
|
-
ERESUNA: "Resource not available",
|
|
293
|
-
ERESUNK: "Unknown Resource",
|
|
294
|
-
# TS
|
|
295
|
-
ETSUKN: "Unknown Transformation System Error",
|
|
296
|
-
ETSDATA: "Invalid Input Data definition",
|
|
297
|
-
}
|
|
43
|
+
# Import all the stateless parts from DIRACCommon
|
|
44
|
+
from DIRACCommon.Utils.DErrno import * # noqa: F401, F403
|
|
298
45
|
|
|
46
|
+
from DIRAC.Core.Utilities.Extensions import extensionsByPriority
|
|
299
47
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
It completes it with the DIRAC specific errors.
|
|
303
|
-
"""
|
|
304
|
-
|
|
305
|
-
if code == 0:
|
|
306
|
-
return "Undefined error"
|
|
307
|
-
|
|
308
|
-
errMsg = f"Unknown error {code}"
|
|
309
|
-
|
|
310
|
-
try:
|
|
311
|
-
errMsg = dStrError[code]
|
|
312
|
-
except KeyError:
|
|
313
|
-
# It is not a DIRAC specific error, try the os one
|
|
314
|
-
try:
|
|
315
|
-
errMsg = os.strerror(code)
|
|
316
|
-
# On some system, os.strerror raises an exception with unknown code,
|
|
317
|
-
# on others, it returns a message...
|
|
318
|
-
except ValueError:
|
|
319
|
-
pass
|
|
320
|
-
|
|
321
|
-
return errMsg
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
def cmpError(inErr, candidate):
|
|
325
|
-
"""This function compares an error (in its old form (a string or dictionary) or in its int form
|
|
326
|
-
with a candidate error code.
|
|
327
|
-
|
|
328
|
-
:param inErr: a string, an integer, a S_ERROR dictionary
|
|
329
|
-
:type inErr: str or int or S_ERROR
|
|
330
|
-
:param int candidate: error code to compare with
|
|
331
|
-
|
|
332
|
-
:return: True or False
|
|
333
|
-
|
|
334
|
-
If an S_ERROR instance is passed, we compare the code with S_ERROR['Errno']
|
|
335
|
-
If it is a Integer, we do a direct comparison
|
|
336
|
-
If it is a String, we use strerror to check the error string
|
|
337
|
-
"""
|
|
338
|
-
|
|
339
|
-
if isinstance(inErr, str): # old style
|
|
340
|
-
# Compare error message strings
|
|
341
|
-
errMsg = strerror(candidate)
|
|
342
|
-
return errMsg in inErr
|
|
343
|
-
elif isinstance(inErr, dict): # if the S_ERROR structure is given
|
|
344
|
-
# Check if Errno defined in the dict
|
|
345
|
-
errorNumber = inErr.get("Errno")
|
|
346
|
-
if errorNumber:
|
|
347
|
-
return errorNumber == candidate
|
|
348
|
-
errMsg = strerror(candidate)
|
|
349
|
-
return errMsg in inErr.get("Message", "")
|
|
350
|
-
elif isinstance(inErr, int):
|
|
351
|
-
return inErr == candidate
|
|
352
|
-
else:
|
|
353
|
-
raise TypeError(f"Unknown input error type {type(inErr)}")
|
|
48
|
+
# compatErrorString is used by the extension mechanism but not in DIRACCommon
|
|
49
|
+
compatErrorString = {}
|
|
354
50
|
|
|
355
51
|
|
|
356
52
|
def includeExtensionErrors():
|
|
@@ -1,255 +1,10 @@
|
|
|
1
|
-
"""
|
|
2
|
-
DIRAC return dictionary
|
|
1
|
+
"""Backward compatibility wrapper - moved to DIRACCommon
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
This module has been moved to DIRACCommon.Utils.ReturnValues to avoid
|
|
4
|
+
circular dependencies and allow DiracX to use these utilities without
|
|
5
|
+
triggering DIRAC's global state initialization.
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
All exports are maintained for backward compatibility.
|
|
7
8
|
"""
|
|
8
|
-
from
|
|
9
|
-
|
|
10
|
-
import functools
|
|
11
|
-
import sys
|
|
12
|
-
import traceback
|
|
13
|
-
from types import TracebackType
|
|
14
|
-
from typing import Any, Callable, cast, Generic, Literal, overload, Type, TypeVar, Union
|
|
15
|
-
from typing_extensions import TypedDict, ParamSpec, NotRequired
|
|
16
|
-
|
|
17
|
-
from DIRAC.Core.Utilities.DErrno import strerror
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
T = TypeVar("T")
|
|
21
|
-
P = ParamSpec("P")
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class DOKReturnType(TypedDict, Generic[T]):
|
|
25
|
-
"""used for typing the DIRAC return structure"""
|
|
26
|
-
|
|
27
|
-
OK: Literal[True]
|
|
28
|
-
Value: T
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class DErrorReturnType(TypedDict):
|
|
32
|
-
"""used for typing the DIRAC return structure"""
|
|
33
|
-
|
|
34
|
-
OK: Literal[False]
|
|
35
|
-
Message: str
|
|
36
|
-
Errno: int
|
|
37
|
-
ExecInfo: NotRequired[tuple[type[BaseException], BaseException, TracebackType]]
|
|
38
|
-
CallStack: NotRequired[list[str]]
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
DReturnType = Union[DOKReturnType[T], DErrorReturnType]
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def S_ERROR(*args: Any, **kwargs: Any) -> DErrorReturnType:
|
|
45
|
-
"""return value on error condition
|
|
46
|
-
|
|
47
|
-
Arguments are either Errno and ErrorMessage or just ErrorMessage fro backward compatibility
|
|
48
|
-
|
|
49
|
-
:param int errno: Error number
|
|
50
|
-
:param string message: Error message
|
|
51
|
-
:param list callStack: Manually override the CallStack attribute better performance
|
|
52
|
-
"""
|
|
53
|
-
callStack = kwargs.pop("callStack", None)
|
|
54
|
-
|
|
55
|
-
result: DErrorReturnType = {"OK": False, "Errno": 0, "Message": ""}
|
|
56
|
-
|
|
57
|
-
message = ""
|
|
58
|
-
if args:
|
|
59
|
-
if isinstance(args[0], int):
|
|
60
|
-
result["Errno"] = args[0]
|
|
61
|
-
if len(args) > 1:
|
|
62
|
-
message = args[1]
|
|
63
|
-
else:
|
|
64
|
-
message = args[0]
|
|
65
|
-
|
|
66
|
-
if result["Errno"]:
|
|
67
|
-
message = f"{strerror(result['Errno'])} ( {result['Errno']} : {message})"
|
|
68
|
-
result["Message"] = message
|
|
69
|
-
|
|
70
|
-
if callStack is None:
|
|
71
|
-
try:
|
|
72
|
-
callStack = traceback.format_stack()
|
|
73
|
-
callStack.pop()
|
|
74
|
-
except Exception:
|
|
75
|
-
callStack = []
|
|
76
|
-
|
|
77
|
-
result["CallStack"] = callStack
|
|
78
|
-
|
|
79
|
-
return result
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
# mypy doesn't understand default parameter values with generics so use overloads (python/mypy#3737)
|
|
83
|
-
@overload
|
|
84
|
-
def S_OK() -> DOKReturnType[None]:
|
|
85
|
-
...
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
@overload
|
|
89
|
-
def S_OK(value: T) -> DOKReturnType[T]:
|
|
90
|
-
...
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def S_OK(value=None): # type: ignore
|
|
94
|
-
"""return value on success
|
|
95
|
-
|
|
96
|
-
:param value: value of the 'Value'
|
|
97
|
-
:return: dictionary { 'OK' : True, 'Value' : value }
|
|
98
|
-
"""
|
|
99
|
-
return {"OK": True, "Value": value}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def isReturnStructure(unk: Any) -> bool:
|
|
103
|
-
"""Check if value is an `S_OK`/`S_ERROR` object"""
|
|
104
|
-
if not isinstance(unk, dict):
|
|
105
|
-
return False
|
|
106
|
-
if "OK" not in unk:
|
|
107
|
-
return False
|
|
108
|
-
if unk["OK"]:
|
|
109
|
-
return "Value" in unk
|
|
110
|
-
else:
|
|
111
|
-
return "Message" in unk
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def isSError(value: Any) -> bool:
|
|
115
|
-
"""Check if value is an `S_ERROR` object"""
|
|
116
|
-
if not isinstance(value, dict):
|
|
117
|
-
return False
|
|
118
|
-
if "OK" not in value:
|
|
119
|
-
return False
|
|
120
|
-
return "Message" in value
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def reprReturnErrorStructure(struct: DErrorReturnType, full: bool = False) -> str:
|
|
124
|
-
errorNumber = struct.get("Errno", 0)
|
|
125
|
-
message = struct.get("Message", "")
|
|
126
|
-
if errorNumber:
|
|
127
|
-
reprStr = f"{strerror(errorNumber)} ( {errorNumber} : {message})"
|
|
128
|
-
else:
|
|
129
|
-
reprStr = message
|
|
130
|
-
|
|
131
|
-
if full:
|
|
132
|
-
callStack = struct.get("CallStack")
|
|
133
|
-
if callStack:
|
|
134
|
-
reprStr += "\n" + "".join(callStack)
|
|
135
|
-
|
|
136
|
-
return reprStr
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def returnSingleResult(dictRes: DReturnType[Any]) -> DReturnType[Any]:
|
|
140
|
-
"""Transform the S_OK{Successful/Failed} dictionary convention into
|
|
141
|
-
an S_OK/S_ERROR return. To be used when a single returned entity
|
|
142
|
-
is expected from a generally bulk call.
|
|
143
|
-
|
|
144
|
-
:param dictRes: S_ERROR or S_OK( "Failed" : {}, "Successful" : {})
|
|
145
|
-
:returns: S_ERROR or S_OK(value)
|
|
146
|
-
|
|
147
|
-
The following rules are applied:
|
|
148
|
-
|
|
149
|
-
- if dictRes is an S_ERROR: returns it as is
|
|
150
|
-
- we start by looking at the Failed directory
|
|
151
|
-
- if there are several items in a dictionary, we return the first one
|
|
152
|
-
- if both dictionaries are empty, we return S_ERROR
|
|
153
|
-
- For an item in Failed, we return S_ERROR
|
|
154
|
-
- Far an item in Successful we return S_OK
|
|
155
|
-
|
|
156
|
-
Behavior examples (would be perfect unit test :-) )::
|
|
157
|
-
|
|
158
|
-
{'Message': 'Kaput', 'OK': False} -> {'Message': 'Kaput', 'OK': False}
|
|
159
|
-
{'OK': True, 'Value': {'Successful': {}, 'Failed': {'a': 1}}} -> {'Message': '1', 'OK': False}
|
|
160
|
-
{'OK': True, 'Value': {'Successful': {'b': 2}, 'Failed': {}}} -> {'OK': True, 'Value': 2}
|
|
161
|
-
{'OK': True, 'Value': {'Successful': {'b': 2}, 'Failed': {'a': 1}}} -> {'Message': '1', 'OK': False}
|
|
162
|
-
{'OK': True, 'Value': {'Successful': {'b': 2}, 'Failed': {'a': 1, 'c': 3}}} -> {'Message': '1', 'OK': False}
|
|
163
|
-
{'OK': True, 'Value': {'Successful': {'b': 2, 'd': 4}, 'Failed': {}}} -> {'OK': True, 'Value': 2}
|
|
164
|
-
{'OK': True, 'Value': {'Successful': {}, 'Failed': {}}} ->
|
|
165
|
-
{'Message': 'returnSingleResult: Failed and Successful dictionaries are empty', 'OK': False}
|
|
166
|
-
"""
|
|
167
|
-
# if S_ERROR was returned, we return it as well
|
|
168
|
-
if not dictRes["OK"]:
|
|
169
|
-
return dictRes
|
|
170
|
-
# if there is a Failed, we return the first one in an S_ERROR
|
|
171
|
-
if "Failed" in dictRes["Value"] and len(dictRes["Value"]["Failed"]):
|
|
172
|
-
errorMessage = list(dictRes["Value"]["Failed"].values())[0]
|
|
173
|
-
if isinstance(errorMessage, dict):
|
|
174
|
-
if isReturnStructure(errorMessage):
|
|
175
|
-
return cast(DErrorReturnType, errorMessage)
|
|
176
|
-
else:
|
|
177
|
-
return S_ERROR(str(errorMessage))
|
|
178
|
-
return S_ERROR(errorMessage)
|
|
179
|
-
# if there is a Successful, we return the first one in an S_OK
|
|
180
|
-
elif "Successful" in dictRes["Value"] and len(dictRes["Value"]["Successful"]):
|
|
181
|
-
return S_OK(list(dictRes["Value"]["Successful"].values())[0])
|
|
182
|
-
else:
|
|
183
|
-
return S_ERROR("returnSingleResult: Failed and Successful dictionaries are empty")
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
class SErrorException(Exception):
|
|
187
|
-
"""Exception class for use with `convertToReturnValue`"""
|
|
188
|
-
|
|
189
|
-
def __init__(self, result: DErrorReturnType | str, errCode: int = 0):
|
|
190
|
-
"""Create a new exception return value
|
|
191
|
-
|
|
192
|
-
If `result` is a `S_ERROR` return it directly else convert it to an
|
|
193
|
-
appropriate value using `S_ERROR(errCode, result)`.
|
|
194
|
-
|
|
195
|
-
:param result: The error to propagate
|
|
196
|
-
:param errCode: the error code to propagate
|
|
197
|
-
"""
|
|
198
|
-
if not isSError(result):
|
|
199
|
-
result = S_ERROR(errCode, result)
|
|
200
|
-
self.result = cast(DErrorReturnType, result)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def returnValueOrRaise(result: DReturnType[T], *, errorCode: int = 0) -> T:
|
|
204
|
-
"""Unwrap an S_OK/S_ERROR response into a value or Exception
|
|
205
|
-
|
|
206
|
-
This method assists with using exceptions in DIRAC code by raising
|
|
207
|
-
:exc:`SErrorException` if `result` is an error. This can then by propagated
|
|
208
|
-
automatically as an `S_ERROR` by wrapping public facing functions with
|
|
209
|
-
`@convertToReturnValue`.
|
|
210
|
-
|
|
211
|
-
:param result: Result of a DIRAC function which returns `S_OK`/`S_ERROR`
|
|
212
|
-
:returns: The value associated with the `S_OK` object
|
|
213
|
-
:raises: If `result["OK"]` is falsey the original exception is re-raised.
|
|
214
|
-
If no exception is known an :exc:`SErrorException` is raised.
|
|
215
|
-
"""
|
|
216
|
-
if not result["OK"]:
|
|
217
|
-
if "ExecInfo" in result:
|
|
218
|
-
raise result["ExecInfo"][0]
|
|
219
|
-
else:
|
|
220
|
-
raise SErrorException(result, errorCode)
|
|
221
|
-
return result["Value"]
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
def convertToReturnValue(func: Callable[P, T]) -> Callable[P, DReturnType[T]]:
|
|
225
|
-
"""Decorate a function to convert return values to `S_OK`/`S_ERROR`
|
|
226
|
-
|
|
227
|
-
If `func` returns, wrap the return value in `S_OK`.
|
|
228
|
-
If `func` raises :exc:`SErrorException`, return the associated `S_ERROR`
|
|
229
|
-
If `func` raises any other exception type, convert it to an `S_ERROR` object
|
|
230
|
-
|
|
231
|
-
:param result: The bare result of a function call
|
|
232
|
-
:returns: `S_OK`/`S_ERROR`
|
|
233
|
-
"""
|
|
234
|
-
|
|
235
|
-
@functools.wraps(func)
|
|
236
|
-
def wrapped(*args: P.args, **kwargs: P.kwargs) -> DReturnType[T]:
|
|
237
|
-
try:
|
|
238
|
-
value = func(*args, **kwargs)
|
|
239
|
-
except SErrorException as e:
|
|
240
|
-
return e.result
|
|
241
|
-
except Exception as e:
|
|
242
|
-
retval = S_ERROR(f"{repr(e)}: {e}")
|
|
243
|
-
# Replace CallStack with the one from the exception
|
|
244
|
-
# Use cast as mypy doesn't understand that sys.exc_info can't return None in an exception block
|
|
245
|
-
retval["ExecInfo"] = cast(tuple[type[BaseException], BaseException, TracebackType], sys.exc_info())
|
|
246
|
-
exc_type, exc_value, exc_tb = retval["ExecInfo"]
|
|
247
|
-
retval["CallStack"] = traceback.format_tb(exc_tb)
|
|
248
|
-
return retval
|
|
249
|
-
else:
|
|
250
|
-
return S_OK(value)
|
|
251
|
-
|
|
252
|
-
# functools will copy the annotations. Since we change the return type
|
|
253
|
-
# we have to update it
|
|
254
|
-
wrapped.__annotations__["return"] = DReturnType
|
|
255
|
-
return wrapped
|
|
9
|
+
# Re-export everything from DIRACCommon for backward compatibility
|
|
10
|
+
from DIRACCommon.Utils.ReturnValues import * # noqa: F401, F403
|
|
@@ -25,7 +25,7 @@ def main():
|
|
|
25
25
|
localCfg.setConfigurationForAgent(agentName)
|
|
26
26
|
localCfg.addDefaultEntry("/DIRAC/Security/UseServerCertificate", "yes")
|
|
27
27
|
localCfg.addDefaultEntry("LogColor", True)
|
|
28
|
-
resultDict = localCfg.loadUserData()
|
|
28
|
+
resultDict = localCfg.loadUserData(requireSuccessfulSync=True)
|
|
29
29
|
if not resultDict["OK"]:
|
|
30
30
|
gLogger.error("There were errors when loading configuration", resultDict["Message"])
|
|
31
31
|
sys.exit(1)
|
|
@@ -37,7 +37,7 @@ from DIRAC.WorkloadManagementSystem.Service.JobPolicy import (
|
|
|
37
37
|
RIGHT_DELETE,
|
|
38
38
|
RIGHT_KILL,
|
|
39
39
|
)
|
|
40
|
-
from DIRAC.WorkloadManagementSystem.
|
|
40
|
+
from DIRAC.WorkloadManagementSystem.DB.StatusUtils import kill_delete_jobs
|
|
41
41
|
|
|
42
42
|
# # agent's name
|
|
43
43
|
AGENT_NAME = "Transformation/TransformationCleaningAgent"
|
|
@@ -39,7 +39,7 @@ from DIRAC.WorkloadManagementSystem.Client.WMSClient import WMSClient
|
|
|
39
39
|
from DIRAC.WorkloadManagementSystem.DB.JobDB import JobDB
|
|
40
40
|
from DIRAC.WorkloadManagementSystem.DB.SandboxMetadataDB import SandboxMetadataDB
|
|
41
41
|
from DIRAC.WorkloadManagementSystem.Service.JobPolicy import RIGHT_DELETE
|
|
42
|
-
from DIRAC.WorkloadManagementSystem.
|
|
42
|
+
from DIRAC.WorkloadManagementSystem.DB.StatusUtils import kill_delete_jobs
|
|
43
43
|
from DIRAC.WorkloadManagementSystem.Utilities.JobParameters import getJobParameters
|
|
44
44
|
|
|
45
45
|
|
|
@@ -23,7 +23,7 @@ from DIRAC.WorkloadManagementSystem.DB.JobDB import JobDB
|
|
|
23
23
|
from DIRAC.WorkloadManagementSystem.DB.JobLoggingDB import JobLoggingDB
|
|
24
24
|
from DIRAC.WorkloadManagementSystem.DB.PilotAgentsDB import PilotAgentsDB
|
|
25
25
|
from DIRAC.WorkloadManagementSystem.Service.JobPolicy import RIGHT_KILL
|
|
26
|
-
from DIRAC.WorkloadManagementSystem.
|
|
26
|
+
from DIRAC.WorkloadManagementSystem.DB.StatusUtils import kill_delete_jobs
|
|
27
27
|
from DIRAC.WorkloadManagementSystem.Utilities.JobParameters import getJobParameters
|
|
28
28
|
from DIRAC.WorkloadManagementSystem.Utilities.Utils import rescheduleJobs
|
|
29
29
|
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from DIRAC import S_ERROR, S_OK, gLogger
|
|
2
|
+
from DIRAC.StorageManagementSystem.DB.StorageManagementDB import StorageManagementDB
|
|
3
|
+
from DIRAC.WorkloadManagementSystem.Client import JobStatus
|
|
4
|
+
from DIRAC.WorkloadManagementSystem.DB.JobDB import JobDB
|
|
5
|
+
from DIRAC.WorkloadManagementSystem.DB.PilotAgentsDB import PilotAgentsDB
|
|
6
|
+
from DIRAC.WorkloadManagementSystem.DB.TaskQueueDB import TaskQueueDB
|
|
7
|
+
from DIRAC.WorkloadManagementSystem.Service.JobPolicy import RIGHT_DELETE, RIGHT_KILL
|
|
8
|
+
from DIRAC.WorkloadManagementSystem.Utilities.jobAdministration import _filterJobStateTransition
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _deleteJob(jobID, force=False):
|
|
12
|
+
"""Set the job status to "Deleted"
|
|
13
|
+
and remove the pilot that ran and its logging info if the pilot is finished.
|
|
14
|
+
|
|
15
|
+
:param int jobID: job ID
|
|
16
|
+
:return: S_OK()/S_ERROR()
|
|
17
|
+
"""
|
|
18
|
+
if not (result := JobDB().setJobStatus(jobID, JobStatus.DELETED, "Checking accounting", force=force))["OK"]:
|
|
19
|
+
gLogger.warn("Failed to set job Deleted status", result["Message"])
|
|
20
|
+
return result
|
|
21
|
+
|
|
22
|
+
if not (result := TaskQueueDB().deleteJob(jobID))["OK"]:
|
|
23
|
+
gLogger.warn("Failed to delete job from the TaskQueue")
|
|
24
|
+
|
|
25
|
+
# if it was the last job for the pilot
|
|
26
|
+
result = PilotAgentsDB().getPilotsForJobID(jobID)
|
|
27
|
+
if not result["OK"]:
|
|
28
|
+
gLogger.error("Failed to get Pilots for JobID", result["Message"])
|
|
29
|
+
return result
|
|
30
|
+
for pilot in result["Value"]:
|
|
31
|
+
res = PilotAgentsDB().getJobsForPilot(pilot)
|
|
32
|
+
if not res["OK"]:
|
|
33
|
+
gLogger.error("Failed to get jobs for pilot", res["Message"])
|
|
34
|
+
return res
|
|
35
|
+
if not res["Value"]: # if list of jobs for pilot is empty, delete pilot
|
|
36
|
+
result = PilotAgentsDB().getPilotInfo(pilotID=pilot)
|
|
37
|
+
if not result["OK"]:
|
|
38
|
+
gLogger.error("Failed to get pilot info", result["Message"])
|
|
39
|
+
return result
|
|
40
|
+
ret = PilotAgentsDB().deletePilot(result["Value"]["PilotJobReference"])
|
|
41
|
+
if not ret["OK"]:
|
|
42
|
+
gLogger.error("Failed to delete pilot from PilotAgentsDB", ret["Message"])
|
|
43
|
+
return ret
|
|
44
|
+
|
|
45
|
+
return S_OK()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _killJob(jobID, sendKillCommand=True, force=False):
|
|
49
|
+
"""Kill one job
|
|
50
|
+
|
|
51
|
+
:param int jobID: job ID
|
|
52
|
+
:param bool sendKillCommand: send kill command
|
|
53
|
+
|
|
54
|
+
:return: S_OK()/S_ERROR()
|
|
55
|
+
"""
|
|
56
|
+
if sendKillCommand:
|
|
57
|
+
if not (result := JobDB().setJobCommand(jobID, "Kill"))["OK"]:
|
|
58
|
+
gLogger.warn("Failed to set job Kill command", result["Message"])
|
|
59
|
+
return result
|
|
60
|
+
|
|
61
|
+
gLogger.info("Job marked for termination", jobID)
|
|
62
|
+
if not (result := JobDB().setJobStatus(jobID, JobStatus.KILLED, "Marked for termination", force=force))["OK"]:
|
|
63
|
+
gLogger.warn("Failed to set job Killed status", result["Message"])
|
|
64
|
+
if not (result := TaskQueueDB().deleteJob(jobID))["OK"]:
|
|
65
|
+
gLogger.warn("Failed to delete job from the TaskQueue", result["Message"])
|
|
66
|
+
|
|
67
|
+
return S_OK()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def kill_delete_jobs(right, validJobList, nonauthJobList=[], force=False):
|
|
71
|
+
"""Kill (== set the status to "KILLED") or delete (== set the status to "DELETED") jobs as necessary
|
|
72
|
+
|
|
73
|
+
:param str right: RIGHT_KILL or RIGHT_DELETE
|
|
74
|
+
|
|
75
|
+
:return: S_OK()/S_ERROR()
|
|
76
|
+
"""
|
|
77
|
+
badIDs = []
|
|
78
|
+
|
|
79
|
+
killJobList = []
|
|
80
|
+
deleteJobList = []
|
|
81
|
+
if validJobList:
|
|
82
|
+
result = JobDB().getJobsAttributes(killJobList, ["Status"])
|
|
83
|
+
if not result["OK"]:
|
|
84
|
+
return result
|
|
85
|
+
jobStates = result["Value"]
|
|
86
|
+
|
|
87
|
+
# Get the jobs allowed to transition to the Killed state
|
|
88
|
+
killJobList.extend(_filterJobStateTransition(jobStates, JobStatus.KILLED))
|
|
89
|
+
|
|
90
|
+
if right == RIGHT_DELETE:
|
|
91
|
+
# Get the jobs allowed to transition to the Deleted state
|
|
92
|
+
deleteJobList.extend(_filterJobStateTransition(jobStates, JobStatus.DELETED))
|
|
93
|
+
|
|
94
|
+
for jobID in killJobList:
|
|
95
|
+
result = _killJob(jobID, force=force)
|
|
96
|
+
if not result["OK"]:
|
|
97
|
+
badIDs.append(jobID)
|
|
98
|
+
|
|
99
|
+
for jobID in deleteJobList:
|
|
100
|
+
result = _deleteJob(jobID, force=force)
|
|
101
|
+
if not result["OK"]:
|
|
102
|
+
badIDs.append(jobID)
|
|
103
|
+
|
|
104
|
+
# Look for jobs that are in the Staging state to send kill signal to the stager
|
|
105
|
+
stagingJobList = [jobID for jobID, sDict in jobStates.items() if sDict["Status"] == JobStatus.STAGING]
|
|
106
|
+
|
|
107
|
+
if stagingJobList:
|
|
108
|
+
stagerDB = StorageManagementDB()
|
|
109
|
+
gLogger.info("Going to send killing signal to stager as well!")
|
|
110
|
+
result = stagerDB.killTasksBySourceTaskID(stagingJobList)
|
|
111
|
+
if not result["OK"]:
|
|
112
|
+
gLogger.warn("Failed to kill some Stager tasks", result["Message"])
|
|
113
|
+
|
|
114
|
+
if nonauthJobList or badIDs:
|
|
115
|
+
result = S_ERROR("Some jobs failed deletion")
|
|
116
|
+
if nonauthJobList:
|
|
117
|
+
gLogger.warn("Non-authorized JobIDs won't be deleted", str(nonauthJobList))
|
|
118
|
+
result["NonauthorizedJobIDs"] = nonauthJobList
|
|
119
|
+
if badIDs:
|
|
120
|
+
gLogger.warn("JobIDs failed to be deleted", str(badIDs))
|
|
121
|
+
result["FailedJobIDs"] = badIDs
|
|
122
|
+
return result
|
|
123
|
+
|
|
124
|
+
jobsList = killJobList if right == RIGHT_KILL else deleteJobList
|
|
125
|
+
return S_OK(jobsList)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
""" unit test (pytest) of JobAdministration module
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
from unittest.mock import MagicMock
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
# sut
|
|
9
|
+
from DIRAC.WorkloadManagementSystem.DB.StatusUtils import kill_delete_jobs
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.mark.parametrize(
|
|
13
|
+
"jobIDs_list, right",
|
|
14
|
+
[
|
|
15
|
+
([], "Kill"),
|
|
16
|
+
([], "Delete"),
|
|
17
|
+
(1, "Kill"),
|
|
18
|
+
([1, 2], "Kill"),
|
|
19
|
+
],
|
|
20
|
+
)
|
|
21
|
+
def test___kill_delete_jobs(mocker, jobIDs_list, right):
|
|
22
|
+
mocker.patch("DIRAC.WorkloadManagementSystem.DB.StatusUtils.JobDB", MagicMock())
|
|
23
|
+
mocker.patch("DIRAC.WorkloadManagementSystem.DB.StatusUtils.TaskQueueDB", MagicMock())
|
|
24
|
+
mocker.patch("DIRAC.WorkloadManagementSystem.DB.StatusUtils.PilotAgentsDB", MagicMock())
|
|
25
|
+
mocker.patch("DIRAC.WorkloadManagementSystem.DB.StatusUtils.StorageManagementDB", MagicMock())
|
|
26
|
+
|
|
27
|
+
res = kill_delete_jobs(right, jobIDs_list)
|
|
28
|
+
assert res["OK"]
|
|
@@ -30,7 +30,7 @@ from DIRAC.WorkloadManagementSystem.Service.JobPolicy import (
|
|
|
30
30
|
RIGHT_SUBMIT,
|
|
31
31
|
JobPolicy,
|
|
32
32
|
)
|
|
33
|
-
from DIRAC.WorkloadManagementSystem.
|
|
33
|
+
from DIRAC.WorkloadManagementSystem.DB.StatusUtils import kill_delete_jobs
|
|
34
34
|
from DIRAC.WorkloadManagementSystem.Utilities.JobModel import JobDescriptionModel
|
|
35
35
|
from DIRAC.WorkloadManagementSystem.Utilities.ParametricJob import generateParametricJobs, getParameterVectorLength
|
|
36
36
|
from DIRAC.WorkloadManagementSystem.Utilities.Utils import rescheduleJobs
|
|
@@ -1,127 +1,4 @@
|
|
|
1
|
-
from DIRAC import S_ERROR, S_OK, gLogger
|
|
2
|
-
from DIRAC.StorageManagementSystem.DB.StorageManagementDB import StorageManagementDB
|
|
3
1
|
from DIRAC.WorkloadManagementSystem.Client import JobStatus
|
|
4
|
-
from DIRAC.WorkloadManagementSystem.DB.JobDB import JobDB
|
|
5
|
-
from DIRAC.WorkloadManagementSystem.DB.PilotAgentsDB import PilotAgentsDB
|
|
6
|
-
from DIRAC.WorkloadManagementSystem.DB.TaskQueueDB import TaskQueueDB
|
|
7
|
-
from DIRAC.WorkloadManagementSystem.Service.JobPolicy import RIGHT_DELETE, RIGHT_KILL
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def _deleteJob(jobID, force=False):
|
|
11
|
-
"""Set the job status to "Deleted"
|
|
12
|
-
and remove the pilot that ran and its logging info if the pilot is finished.
|
|
13
|
-
|
|
14
|
-
:param int jobID: job ID
|
|
15
|
-
:return: S_OK()/S_ERROR()
|
|
16
|
-
"""
|
|
17
|
-
if not (result := JobDB().setJobStatus(jobID, JobStatus.DELETED, "Checking accounting", force=force))["OK"]:
|
|
18
|
-
gLogger.warn("Failed to set job Deleted status", result["Message"])
|
|
19
|
-
return result
|
|
20
|
-
|
|
21
|
-
if not (result := TaskQueueDB().deleteJob(jobID))["OK"]:
|
|
22
|
-
gLogger.warn("Failed to delete job from the TaskQueue")
|
|
23
|
-
|
|
24
|
-
# if it was the last job for the pilot
|
|
25
|
-
result = PilotAgentsDB().getPilotsForJobID(jobID)
|
|
26
|
-
if not result["OK"]:
|
|
27
|
-
gLogger.error("Failed to get Pilots for JobID", result["Message"])
|
|
28
|
-
return result
|
|
29
|
-
for pilot in result["Value"]:
|
|
30
|
-
res = PilotAgentsDB().getJobsForPilot(pilot)
|
|
31
|
-
if not res["OK"]:
|
|
32
|
-
gLogger.error("Failed to get jobs for pilot", res["Message"])
|
|
33
|
-
return res
|
|
34
|
-
if not res["Value"]: # if list of jobs for pilot is empty, delete pilot
|
|
35
|
-
result = PilotAgentsDB().getPilotInfo(pilotID=pilot)
|
|
36
|
-
if not result["OK"]:
|
|
37
|
-
gLogger.error("Failed to get pilot info", result["Message"])
|
|
38
|
-
return result
|
|
39
|
-
ret = PilotAgentsDB().deletePilot(result["Value"]["PilotJobReference"])
|
|
40
|
-
if not ret["OK"]:
|
|
41
|
-
gLogger.error("Failed to delete pilot from PilotAgentsDB", ret["Message"])
|
|
42
|
-
return ret
|
|
43
|
-
|
|
44
|
-
return S_OK()
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def _killJob(jobID, sendKillCommand=True, force=False):
|
|
48
|
-
"""Kill one job
|
|
49
|
-
|
|
50
|
-
:param int jobID: job ID
|
|
51
|
-
:param bool sendKillCommand: send kill command
|
|
52
|
-
|
|
53
|
-
:return: S_OK()/S_ERROR()
|
|
54
|
-
"""
|
|
55
|
-
if sendKillCommand:
|
|
56
|
-
if not (result := JobDB().setJobCommand(jobID, "Kill"))["OK"]:
|
|
57
|
-
gLogger.warn("Failed to set job Kill command", result["Message"])
|
|
58
|
-
return result
|
|
59
|
-
|
|
60
|
-
gLogger.info("Job marked for termination", jobID)
|
|
61
|
-
if not (result := JobDB().setJobStatus(jobID, JobStatus.KILLED, "Marked for termination", force=force))["OK"]:
|
|
62
|
-
gLogger.warn("Failed to set job Killed status", result["Message"])
|
|
63
|
-
if not (result := TaskQueueDB().deleteJob(jobID))["OK"]:
|
|
64
|
-
gLogger.warn("Failed to delete job from the TaskQueue", result["Message"])
|
|
65
|
-
|
|
66
|
-
return S_OK()
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def kill_delete_jobs(right, validJobList, nonauthJobList=[], force=False):
|
|
70
|
-
"""Kill (== set the status to "KILLED") or delete (== set the status to "DELETED") jobs as necessary
|
|
71
|
-
|
|
72
|
-
:param str right: RIGHT_KILL or RIGHT_DELETE
|
|
73
|
-
|
|
74
|
-
:return: S_OK()/S_ERROR()
|
|
75
|
-
"""
|
|
76
|
-
badIDs = []
|
|
77
|
-
|
|
78
|
-
killJobList = []
|
|
79
|
-
deleteJobList = []
|
|
80
|
-
if validJobList:
|
|
81
|
-
result = JobDB().getJobsAttributes(killJobList, ["Status"])
|
|
82
|
-
if not result["OK"]:
|
|
83
|
-
return result
|
|
84
|
-
jobStates = result["Value"]
|
|
85
|
-
|
|
86
|
-
# Get the jobs allowed to transition to the Killed state
|
|
87
|
-
killJobList.extend(_filterJobStateTransition(jobStates, JobStatus.KILLED))
|
|
88
|
-
|
|
89
|
-
if right == RIGHT_DELETE:
|
|
90
|
-
# Get the jobs allowed to transition to the Deleted state
|
|
91
|
-
deleteJobList.extend(_filterJobStateTransition(jobStates, JobStatus.DELETED))
|
|
92
|
-
|
|
93
|
-
for jobID in killJobList:
|
|
94
|
-
result = _killJob(jobID, force=force)
|
|
95
|
-
if not result["OK"]:
|
|
96
|
-
badIDs.append(jobID)
|
|
97
|
-
|
|
98
|
-
for jobID in deleteJobList:
|
|
99
|
-
result = _deleteJob(jobID, force=force)
|
|
100
|
-
if not result["OK"]:
|
|
101
|
-
badIDs.append(jobID)
|
|
102
|
-
|
|
103
|
-
# Look for jobs that are in the Staging state to send kill signal to the stager
|
|
104
|
-
stagingJobList = [jobID for jobID, sDict in jobStates.items() if sDict["Status"] == JobStatus.STAGING]
|
|
105
|
-
|
|
106
|
-
if stagingJobList:
|
|
107
|
-
stagerDB = StorageManagementDB()
|
|
108
|
-
gLogger.info("Going to send killing signal to stager as well!")
|
|
109
|
-
result = stagerDB.killTasksBySourceTaskID(stagingJobList)
|
|
110
|
-
if not result["OK"]:
|
|
111
|
-
gLogger.warn("Failed to kill some Stager tasks", result["Message"])
|
|
112
|
-
|
|
113
|
-
if nonauthJobList or badIDs:
|
|
114
|
-
result = S_ERROR("Some jobs failed deletion")
|
|
115
|
-
if nonauthJobList:
|
|
116
|
-
gLogger.warn("Non-authorized JobIDs won't be deleted", str(nonauthJobList))
|
|
117
|
-
result["NonauthorizedJobIDs"] = nonauthJobList
|
|
118
|
-
if badIDs:
|
|
119
|
-
gLogger.warn("JobIDs failed to be deleted", str(badIDs))
|
|
120
|
-
result["FailedJobIDs"] = badIDs
|
|
121
|
-
return result
|
|
122
|
-
|
|
123
|
-
jobsList = killJobList if right == RIGHT_KILL else deleteJobList
|
|
124
|
-
return S_OK(jobsList)
|
|
125
2
|
|
|
126
3
|
|
|
127
4
|
def _filterJobStateTransition(jobStates, candidateState):
|
DIRAC/__init__.py
CHANGED
|
@@ -1,59 +1,60 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
2
|
+
DIRAC - Distributed Infrastructure with Remote Agent Control
|
|
3
|
+
|
|
4
|
+
The distributed data production and analysis system of LHCb and other VOs.
|
|
5
|
+
|
|
6
|
+
DIRAC is a software framework for distributed computing which
|
|
7
|
+
allows to integrate various computing resources in a single
|
|
8
|
+
system. At the same time it integrates all kinds of computing
|
|
9
|
+
activities like Monte Carlo simulations, data processing, or
|
|
10
|
+
final user analysis.
|
|
11
|
+
|
|
12
|
+
It is build as number of cooperating systems:
|
|
13
|
+
- Accounting
|
|
14
|
+
- Configuration
|
|
15
|
+
- Core
|
|
16
|
+
- Base
|
|
17
|
+
- Security
|
|
18
|
+
- Utilities
|
|
19
|
+
- Workflow
|
|
20
|
+
- Framework
|
|
21
|
+
- RequestManagement
|
|
22
|
+
- Resources
|
|
23
|
+
- Transformation
|
|
24
|
+
|
|
25
|
+
Which are used by other system providing functionality to
|
|
26
|
+
the end user:
|
|
27
|
+
- DataManagement
|
|
28
|
+
- Interfaces
|
|
29
|
+
- ResourceStatus
|
|
30
|
+
- StorageManagement
|
|
31
|
+
- WorkloadManagement
|
|
32
|
+
|
|
33
|
+
It defines the following data members:
|
|
34
|
+
- version: DIRAC version string
|
|
35
|
+
|
|
36
|
+
- errorMail: mail address for important errors
|
|
37
|
+
- alarmMail: mail address for important alarms
|
|
38
|
+
|
|
39
|
+
It loads Modules from :
|
|
40
|
+
- DIRAC.Core.Utililies
|
|
41
|
+
|
|
42
|
+
It loads:
|
|
43
|
+
- S_OK: OK return structure
|
|
44
|
+
- S_ERROR: ERROR return structure
|
|
45
|
+
- gLogger: global Logger object
|
|
46
|
+
- gConfig: global Config object
|
|
47
|
+
|
|
48
|
+
It defines the following functions:
|
|
49
|
+
- abort: aborts execution
|
|
50
|
+
- exit: finish execution using callbacks
|
|
51
|
+
- siteName: returns DIRAC name for current site
|
|
52
|
+
|
|
53
|
+
- getPlatform(): DIRAC platform string for current host
|
|
54
|
+
- getPlatformTuple(): DIRAC platform tuple for current host
|
|
55
55
|
|
|
56
56
|
"""
|
|
57
|
+
|
|
57
58
|
import importlib.metadata
|
|
58
59
|
import os
|
|
59
60
|
import re
|
|
@@ -237,7 +238,7 @@ def initialize(
|
|
|
237
238
|
log_level = getattr(LogLevel, gLogger.getLevel())
|
|
238
239
|
gLogger.setLevel(LogLevel.ALWAYS)
|
|
239
240
|
try:
|
|
240
|
-
returnValueOrRaise(localCfg.initialize())
|
|
241
|
+
returnValueOrRaise(localCfg.initialize(requireSuccessfulSync=require_auth))
|
|
241
242
|
finally:
|
|
242
243
|
# Restore the pre-existing log level
|
|
243
244
|
gLogger.setLevel(log_level)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: DIRAC
|
|
3
|
-
Version: 9.0.
|
|
3
|
+
Version: 9.0.0a66
|
|
4
4
|
Summary: DIRAC is an interware, meaning a software framework for distributed computing.
|
|
5
5
|
Home-page: https://github.com/DIRACGrid/DIRAC/
|
|
6
6
|
License: GPL-3.0-only
|
|
@@ -19,6 +19,7 @@ Requires-Dist: cachetools
|
|
|
19
19
|
Requires-Dist: certifi
|
|
20
20
|
Requires-Dist: cwltool
|
|
21
21
|
Requires-Dist: diraccfg
|
|
22
|
+
Requires-Dist: DIRACCommon==v9.0.0a66
|
|
22
23
|
Requires-Dist: diracx-client>=v0.0.1a18
|
|
23
24
|
Requires-Dist: diracx-core>=v0.0.1a18
|
|
24
25
|
Requires-Dist: db12
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
DIRAC/__init__.py,sha256=
|
|
1
|
+
DIRAC/__init__.py,sha256=uEe7WZkuhG6uOif8DHAsP5fT045vFDNK8DSlU3e3u64,11207
|
|
2
2
|
DIRAC/AccountingSystem/ConfigTemplate.cfg,sha256=xT_JHJGumwEx_w1aditaxn3zq0r4ehIScX4qYFwjT3o,819
|
|
3
3
|
DIRAC/AccountingSystem/__init__.py,sha256=E88siUatzE7vxErfezaEuZxK4yfHvkKCqqC9toGvqwg,539
|
|
4
4
|
DIRAC/AccountingSystem/Agent/NetworkAgent.py,sha256=qHY-aewzEYS99VsW_Ka-aoap2kqNEAe9qcH3jg25yqI,10999
|
|
@@ -58,7 +58,7 @@ DIRAC/ConfigurationSystem/Client/CSShellCLI.py,sha256=dlkGA-2-YJV8mwllJ2qG8_dvQf
|
|
|
58
58
|
DIRAC/ConfigurationSystem/Client/Config.py,sha256=b8UBxKSJ3ZXVpIDGVXPR95dPs4RApEiLtqdN3fDo22k,524
|
|
59
59
|
DIRAC/ConfigurationSystem/Client/ConfigurationClient.py,sha256=Eb0rzqYAhrAYsXDNELWBvfjV2x-9LisaYy5tIk8oQBQ,2434
|
|
60
60
|
DIRAC/ConfigurationSystem/Client/ConfigurationData.py,sha256=3BhUZUne2KiMKSNqs1ePqiczKdT7sAh210CmWbbIYhw,124
|
|
61
|
-
DIRAC/ConfigurationSystem/Client/LocalConfiguration.py,sha256=
|
|
61
|
+
DIRAC/ConfigurationSystem/Client/LocalConfiguration.py,sha256=KaVGD4YVEfxCspVavddAEQpcetynvek-msfFIquawE4,32442
|
|
62
62
|
DIRAC/ConfigurationSystem/Client/PathFinder.py,sha256=wxOWC0_UznF7XJc0LP9DX8y3yltOApJXwZehx1ghgpU,11054
|
|
63
63
|
DIRAC/ConfigurationSystem/Client/Utilities.py,sha256=lQepoCgaAvg9GW3_-s-IXkWb3fN3HJJP95pYhNEC2_4,27200
|
|
64
64
|
DIRAC/ConfigurationSystem/Client/VOMS2CSSynchronizer.py,sha256=U-y7vc8NYwsyQLFkSzKCXnVEJMIoLsxgthFC0z9x7Dk,31426
|
|
@@ -223,7 +223,7 @@ DIRAC/Core/Utilities/CGroups2.py,sha256=dKJf0yAACpcO5wIGsOgvf5Sh7ZZNua_gSn_u95gj
|
|
|
223
223
|
DIRAC/Core/Utilities/CountryMapping.py,sha256=6OfH_sYS0F3qrJn-Hp_YSR0l7WWNsglHtH6AIDGR58A,1080
|
|
224
224
|
DIRAC/Core/Utilities/DAG.py,sha256=ME-oxtsV2PYIuWikL8-571RR3U21siHxo1hFQGi3s9Q,3587
|
|
225
225
|
DIRAC/Core/Utilities/DEncode.py,sha256=O-5Yi185V05tBNyXCxoourRQkuOCP6pTyDYaRgNdi8w,15578
|
|
226
|
-
DIRAC/Core/Utilities/DErrno.py,sha256=
|
|
226
|
+
DIRAC/Core/Utilities/DErrno.py,sha256=hnRO64KrCrhETHAEiJh44CQfc0VoC8L-FNi7UOlWvUU,3193
|
|
227
227
|
DIRAC/Core/Utilities/DIRACSingleton.py,sha256=UqJHJNJyX4ehCB5PGDuA74FuCaoih6V979ftr8KboH4,1219
|
|
228
228
|
DIRAC/Core/Utilities/Decorators.py,sha256=WAYke0p9VKov24stHO-S4HEia-HGCSA1c1XDyxxZtw8,7153
|
|
229
229
|
DIRAC/Core/Utilities/Devloader.py,sha256=nPEinBId-NzyG1zcjcnUQOO-pWmNJsmWkj-Xkt5dn_c,2572
|
|
@@ -254,7 +254,7 @@ DIRAC/Core/Utilities/ProcessPool.py,sha256=AQ-ABejCKbdYVn4SD_Qy2DWZYA39fIt_3Occp
|
|
|
254
254
|
DIRAC/Core/Utilities/Profiler.py,sha256=sSMe9Mp-C1lNT_AkMbZMcOsD2vxlKNWGa-eMSCp4t4c,7531
|
|
255
255
|
DIRAC/Core/Utilities/PromptUser.py,sha256=Pg51ZPZwXLYk8ncwrVvpimXfelWKxCeocRekRlqs2fQ,1441
|
|
256
256
|
DIRAC/Core/Utilities/Proxy.py,sha256=BDAq_PXcuN6AlGX1gs9n8_yY9xT7JOtlKIO2cdTS24I,9816
|
|
257
|
-
DIRAC/Core/Utilities/ReturnValues.py,sha256=
|
|
257
|
+
DIRAC/Core/Utilities/ReturnValues.py,sha256=utF0CJ5PQX4D3J7KBRBJwOiNVm8ut8bgToXmWs0k_c8,438
|
|
258
258
|
DIRAC/Core/Utilities/Shifter.py,sha256=EkJGPR_zbL0SS3E2_cq1-MjkTH91ZaFJlh-tQ2PxDGk,2571
|
|
259
259
|
DIRAC/Core/Utilities/SiteSEMapping.py,sha256=Uynx_evXQPmfbMKH-TfqzLq4e_7APVrP6gC20cJEBYs,5683
|
|
260
260
|
DIRAC/Core/Utilities/StateMachine.py,sha256=jxPsFSlqQvbNBZiKCuiztpTf9onsHeQDhk_1piNQcyw,6691
|
|
@@ -322,7 +322,7 @@ DIRAC/Core/Workflow/test/WFSamples.py,sha256=BNQSG31x393ho2-2TIjGKJQjbisNXx-5cx3
|
|
|
322
322
|
DIRAC/Core/Workflow/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
323
323
|
DIRAC/Core/Workflow/test/step_g.py,sha256=hlncZ_tbb74wz0mRxyHTl0NnjRut2ysWLgUfiTvi9yo,21830
|
|
324
324
|
DIRAC/Core/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
325
|
-
DIRAC/Core/scripts/dirac_agent.py,sha256=
|
|
325
|
+
DIRAC/Core/scripts/dirac_agent.py,sha256=cjR5wuh4j5D5nTvu9ZfPbjdbZiEnRG34GJj7iSh47qk,1670
|
|
326
326
|
DIRAC/Core/scripts/dirac_apptainer_exec.py,sha256=6zs6PepCtLj96OoDDxpRUQk9nKgs9ksAG8o6J6djbFo,4666
|
|
327
327
|
DIRAC/Core/scripts/dirac_cert_convert.py,sha256=3ThPmb1Tg-UgeH-3ZV0ilkZ8-H4RsK8SOszLuuYG-vs,2498
|
|
328
328
|
DIRAC/Core/scripts/dirac_configure.py,sha256=c3ZfQUe_JUJASi3iuvl4qD83kyfWvS_QixISyG833sg,29494
|
|
@@ -1053,7 +1053,7 @@ DIRAC/TransformationSystem/Agent/RequestTaskAgent.py,sha256=0FxuRVONDkC6DtDqfSC4
|
|
|
1053
1053
|
DIRAC/TransformationSystem/Agent/TaskManagerAgentBase.py,sha256=M8CVPGCY4UMNfKNDdcfgUd2er3JPAIeNzGghsk0ohUQ,30824
|
|
1054
1054
|
DIRAC/TransformationSystem/Agent/TransformationAgent.py,sha256=rUPyfX_Ip3ZUyBrmVBrYiddVmgeTx6gKiYPEzwUDOcY,33813
|
|
1055
1055
|
DIRAC/TransformationSystem/Agent/TransformationAgentsUtilities.py,sha256=XrgrjQWSDQm0uxG-wyEY-W0A_MBJyIE-22TafqrLP3A,2805
|
|
1056
|
-
DIRAC/TransformationSystem/Agent/TransformationCleaningAgent.py,sha256=
|
|
1056
|
+
DIRAC/TransformationSystem/Agent/TransformationCleaningAgent.py,sha256=uAu_aQxko4GF_cTxDxY4KmARqjuytBO89e4XVy5PPUM,32898
|
|
1057
1057
|
DIRAC/TransformationSystem/Agent/TransformationPlugin.py,sha256=EpWil1Bla9xdrJkhj61oUFDpGzyWfC8acW3tjBh5JpQ,11322
|
|
1058
1058
|
DIRAC/TransformationSystem/Agent/ValidateOutputDataAgent.py,sha256=tsE0yE2bqSKwdjJml3Mros-tMOfpSDr9Bt8R3MTeqRo,10108
|
|
1059
1059
|
DIRAC/TransformationSystem/Agent/WorkflowTaskAgent.py,sha256=UBNVVx0ZgiFnzAPCNC1dSuJXKkt2pPfpSqhy0CREFlg,1741
|
|
@@ -1122,13 +1122,13 @@ DIRAC/Workflow/Utilities/test/Test_Utilities.py,sha256=DNjFPpBmpojwCWhZSUSoG3AIh
|
|
|
1122
1122
|
DIRAC/WorkloadManagementSystem/ConfigTemplate.cfg,sha256=EpXTiHV-omMuMcWDzQzWZ_LmAZFMRTabfJpfZmVfzEA,9065
|
|
1123
1123
|
DIRAC/WorkloadManagementSystem/__init__.py,sha256=9-_-HOT_8S3i-TMmTR_gFVVlNyktBRk-S2qSuOBKoIc,50
|
|
1124
1124
|
DIRAC/WorkloadManagementSystem/Agent/JobAgent.py,sha256=Hi3zPfK9h7CwNO2pK4IABCqO_uShCIaKvZGlUdgsWPA,41034
|
|
1125
|
-
DIRAC/WorkloadManagementSystem/Agent/JobCleaningAgent.py,sha256=
|
|
1125
|
+
DIRAC/WorkloadManagementSystem/Agent/JobCleaningAgent.py,sha256=TR8nDO43DuJxOQVIdOUrqpWjRjJp52dVzOKUUewL9U4,14708
|
|
1126
1126
|
DIRAC/WorkloadManagementSystem/Agent/PilotLoggingAgent.py,sha256=ZIgvFpasGTh76GW3G7O4hKC_Kkm31uWymlH-MTT3AXg,10541
|
|
1127
1127
|
DIRAC/WorkloadManagementSystem/Agent/PilotStatusAgent.py,sha256=qY6TbYCPOFFXhHffmRJLNEbWvZPyg5Lc5B_8BbyQ7zc,9711
|
|
1128
1128
|
DIRAC/WorkloadManagementSystem/Agent/PilotSyncAgent.py,sha256=Q50axgfx_D9iF2hT0aNaJ1KA85idOluBeBWDcjR6j8c,4845
|
|
1129
1129
|
DIRAC/WorkloadManagementSystem/Agent/PushJobAgent.py,sha256=OkIIFnJrwUYCroWEc5bgAfsnGks98y0gO1GLKamBXO0,38820
|
|
1130
1130
|
DIRAC/WorkloadManagementSystem/Agent/SiteDirector.py,sha256=ZnbjKca8tWLekObALrsFXm7EoXAYxEpHSGRAbrqU5mk,45229
|
|
1131
|
-
DIRAC/WorkloadManagementSystem/Agent/StalledJobAgent.py,sha256=
|
|
1131
|
+
DIRAC/WorkloadManagementSystem/Agent/StalledJobAgent.py,sha256=foEbmRotEmfeQG6nyIsJv1kSJkm4flkQsPYbSylS3SM,24572
|
|
1132
1132
|
DIRAC/WorkloadManagementSystem/Agent/StatesAccountingAgent.py,sha256=iNIlWQEDBk6R1S8oHOIusZUwxOwLtgwuzR_4s32-o5w,8707
|
|
1133
1133
|
DIRAC/WorkloadManagementSystem/Agent/TaskQueuesAgent.py,sha256=ypsmo233TFXq9IC5uz6pum7_joOh2gFPUYNmmCpukAY,943
|
|
1134
1134
|
DIRAC/WorkloadManagementSystem/Agent/__init__.py,sha256=Pp2qIXA0zZxJXBQwPEDUt23Y_ct5cjs77w8ZEV6Ll6M,56
|
|
@@ -1186,10 +1186,12 @@ DIRAC/WorkloadManagementSystem/DB/PilotAgentsDB.py,sha256=tUSuhv59CQgB7NkxsXo5r-
|
|
|
1186
1186
|
DIRAC/WorkloadManagementSystem/DB/PilotAgentsDB.sql,sha256=RnCDCDGa__7sDxjSFGlu_5OwKbzxeK3a9TOMpV5ftRw,2738
|
|
1187
1187
|
DIRAC/WorkloadManagementSystem/DB/SandboxMetadataDB.py,sha256=a4tHv19jN2lKi4o5SYvfkG6JPbVcjJO_SgJBJORUV7Y,15631
|
|
1188
1188
|
DIRAC/WorkloadManagementSystem/DB/SandboxMetadataDB.sql,sha256=2TcyLiewFuPWqBhD43geHG0obCFRGTSLbQwPUwENYnQ,23
|
|
1189
|
+
DIRAC/WorkloadManagementSystem/DB/StatusUtils.py,sha256=K4fIDNOWbSxUaxVfKbsSKzPAlF5v_S5FSCGxHRkPScM,5124
|
|
1189
1190
|
DIRAC/WorkloadManagementSystem/DB/TaskQueueDB.py,sha256=4ClljoQUnHKq1KHAIFpUwL_5-NrUUuM8oHKzOZULXZg,54623
|
|
1190
1191
|
DIRAC/WorkloadManagementSystem/DB/TaskQueueDB.sql,sha256=GfeceE3VY7l15pMwt6MzI8pGqdk772XuIg_DE0vkj74,541
|
|
1191
1192
|
DIRAC/WorkloadManagementSystem/DB/__init__.py,sha256=1H5briNEaZlSL6pMxzjDmkZkVvGxHix8DtfkM-mOwGc,53
|
|
1192
1193
|
DIRAC/WorkloadManagementSystem/DB/tests/Test_JobDB.py,sha256=PppmPBWFKQMeNUZXHcfl07hVu7hdrJcqFXFloXO2JMU,1068
|
|
1194
|
+
DIRAC/WorkloadManagementSystem/DB/tests/Test_StatusUtils.py,sha256=nibKxynwLdJZafC0k01oE1FwOFgSlEMw2hUlrlacwEo,845
|
|
1193
1195
|
DIRAC/WorkloadManagementSystem/DB/tests/Test_TaskQueueDB.py,sha256=QWGxTbGez9wojkHzbpqksdKc6I6_X-Dh49tXB8gxc8s,6368
|
|
1194
1196
|
DIRAC/WorkloadManagementSystem/Executor/InputData.py,sha256=ybhBMXkXf1JxLYKpNCAiyvtoiAICzvrm4UOnMSurmVA,18049
|
|
1195
1197
|
DIRAC/WorkloadManagementSystem/Executor/JobPath.py,sha256=ovsRPGQiPixnTBsK4WRTZ2wqeXazK_tEloSgNwAtF80,2511
|
|
@@ -1219,7 +1221,7 @@ DIRAC/WorkloadManagementSystem/JobWrapper/test/script-RESC.sh,sha256=kiY-Af9a1bs
|
|
|
1219
1221
|
DIRAC/WorkloadManagementSystem/JobWrapper/test/script-fail.sh,sha256=f1R0qtgJgOI3AbEoxGBqN8ClA6Y8Bz8tHlBXrxjySDs,63
|
|
1220
1222
|
DIRAC/WorkloadManagementSystem/JobWrapper/test/script-long.sh,sha256=yzaSDg7P5hGv9SSCBsl8FLuj6-udCcpd0qId9kSv4Nc,41
|
|
1221
1223
|
DIRAC/WorkloadManagementSystem/JobWrapper/test/script.sh,sha256=fbYLJJTVw9K7f9NdMvW_a-NkBI6SPup4NebPFYFKsbQ,9
|
|
1222
|
-
DIRAC/WorkloadManagementSystem/Service/JobManagerHandler.py,sha256=
|
|
1224
|
+
DIRAC/WorkloadManagementSystem/Service/JobManagerHandler.py,sha256=flr1tqCQpRKb0u-RMBdCxqJJ4Ns-XL6XxPlbteRClCY,21098
|
|
1223
1225
|
DIRAC/WorkloadManagementSystem/Service/JobMonitoringHandler.py,sha256=sFJOme3KDLeAppH_AF5dTt8om_g_zUSQvPnIs9BAQkc,7586
|
|
1224
1226
|
DIRAC/WorkloadManagementSystem/Service/JobPolicy.py,sha256=o88xR3roe_JRB5F53oxb1LIuqY2qCCw6w5njprrJFMI,8057
|
|
1225
1227
|
DIRAC/WorkloadManagementSystem/Service/JobStateUpdateHandler.py,sha256=TiSnLQTNd-HgAhPoqwebzN6G95pVXXgETJ5Hbw29XnY,9226
|
|
@@ -1246,8 +1248,7 @@ DIRAC/WorkloadManagementSystem/Utilities/QueueUtilities.py,sha256=J5-n_lvWbW_TRj
|
|
|
1246
1248
|
DIRAC/WorkloadManagementSystem/Utilities/RemoteRunner.py,sha256=7FcEtlYSJMzdbLIFBUKD-j_wqRHya-ISqk8w-JRy3kw,12159
|
|
1247
1249
|
DIRAC/WorkloadManagementSystem/Utilities/Utils.py,sha256=A6M-GnyC5LXeO0pJxwzqNdLjZLMC3WZ1o7dVFXumrsU,5331
|
|
1248
1250
|
DIRAC/WorkloadManagementSystem/Utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1249
|
-
DIRAC/WorkloadManagementSystem/Utilities/jobAdministration.py,sha256=
|
|
1250
|
-
DIRAC/WorkloadManagementSystem/Utilities/test/Test_JobAdministration.py,sha256=m4ZwTx6xKtUDIJu93MRmATBncVPbWfjPWp_CM6vAKuk,910
|
|
1251
|
+
DIRAC/WorkloadManagementSystem/Utilities/jobAdministration.py,sha256=uLgB0QLFPKUlZoWCOJYtQHaKykgCpodalHnMWpGld4s,548
|
|
1251
1252
|
DIRAC/WorkloadManagementSystem/Utilities/test/Test_JobModel.py,sha256=dY7rRU8iOVYEgVLLXwjbx6vCUUHUZqOytDQO6ziarM8,8200
|
|
1252
1253
|
DIRAC/WorkloadManagementSystem/Utilities/test/Test_JobStatusUtility.py,sha256=3Oj4zhiysp-Qiax1BsXK1fEyCuppln4tT5hmXx_BFxA,7931
|
|
1253
1254
|
DIRAC/WorkloadManagementSystem/Utilities/test/Test_ParametricJob.py,sha256=mo_u-m4lM65c1Sgq6As4NpIiKRC0ny1oeBVYRBo61CM,3334
|
|
@@ -1295,9 +1296,9 @@ DIRAC/tests/Workflow/Integration/exe-script.py,sha256=B_slYdTocEzqfQLRhwuPiLyYUn
|
|
|
1295
1296
|
DIRAC/tests/Workflow/Integration/helloWorld.py,sha256=tBgEHH3ZF7ZiTS57gtmm3DW-Qxgm_57HWHpM-Y8XSws,205
|
|
1296
1297
|
DIRAC/tests/Workflow/Regression/helloWorld.py,sha256=69eCgFuVSYo-mK3Dj2dw1c6g86sF5FksKCf8V2aGVoM,509
|
|
1297
1298
|
DIRAC/tests/Workflow/Regression/helloWorld.xml,sha256=xwydIcFTAHIX-YPfQfyxuQ7hzvIO3IhR3UAF7ORgkGg,5310
|
|
1298
|
-
dirac-9.0.
|
|
1299
|
-
dirac-9.0.
|
|
1300
|
-
dirac-9.0.
|
|
1301
|
-
dirac-9.0.
|
|
1302
|
-
dirac-9.0.
|
|
1303
|
-
dirac-9.0.
|
|
1299
|
+
dirac-9.0.0a66.dist-info/licenses/LICENSE,sha256=uyr4oV6jmjUeepXZPPjkJRwa5q5MrI7jqJz5sVXNblQ,32452
|
|
1300
|
+
dirac-9.0.0a66.dist-info/METADATA,sha256=O7cq5FZoLgGPJiwSKDLOIcAwemtym0zmOo9JHwNjXpc,10020
|
|
1301
|
+
dirac-9.0.0a66.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
1302
|
+
dirac-9.0.0a66.dist-info/entry_points.txt,sha256=hupzIL8aVmjK3nn7RLKdhcaiPmLOiD3Kulh3CSDHKmw,16492
|
|
1303
|
+
dirac-9.0.0a66.dist-info/top_level.txt,sha256=RISrnN9kb_mPqmVu8_o4jF-DSX8-h6AcgfkO9cgfkHA,6
|
|
1304
|
+
dirac-9.0.0a66.dist-info/RECORD,,
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
""" unit test (pytest) of JobAdministration module
|
|
2
|
-
"""
|
|
3
|
-
|
|
4
|
-
from unittest.mock import MagicMock
|
|
5
|
-
|
|
6
|
-
import pytest
|
|
7
|
-
|
|
8
|
-
# sut
|
|
9
|
-
from DIRAC.WorkloadManagementSystem.Utilities.jobAdministration import kill_delete_jobs
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@pytest.mark.parametrize(
|
|
13
|
-
"jobIDs_list, right",
|
|
14
|
-
[
|
|
15
|
-
([], "Kill"),
|
|
16
|
-
([], "Delete"),
|
|
17
|
-
(1, "Kill"),
|
|
18
|
-
([1, 2], "Kill"),
|
|
19
|
-
],
|
|
20
|
-
)
|
|
21
|
-
def test___kill_delete_jobs(mocker, jobIDs_list, right):
|
|
22
|
-
mocker.patch("DIRAC.WorkloadManagementSystem.Utilities.jobAdministration.JobDB", MagicMock())
|
|
23
|
-
mocker.patch("DIRAC.WorkloadManagementSystem.Utilities.jobAdministration.TaskQueueDB", MagicMock())
|
|
24
|
-
mocker.patch("DIRAC.WorkloadManagementSystem.Utilities.jobAdministration.PilotAgentsDB", MagicMock())
|
|
25
|
-
mocker.patch("DIRAC.WorkloadManagementSystem.Utilities.jobAdministration.StorageManagementDB", MagicMock())
|
|
26
|
-
|
|
27
|
-
res = kill_delete_jobs(right, jobIDs_list)
|
|
28
|
-
assert res["OK"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|