DIRACCommon 9.0.0__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.
Files changed (29) hide show
  1. DIRACCommon/ConfigurationSystem/Client/Helpers/Resources.py +52 -0
  2. DIRACCommon/ConfigurationSystem/Client/Helpers/__init__.py +3 -0
  3. DIRACCommon/ConfigurationSystem/Client/__init__.py +3 -0
  4. DIRACCommon/ConfigurationSystem/__init__.py +3 -0
  5. DIRACCommon/Core/Utilities/ClassAd/ClassAdLight.py +295 -0
  6. DIRACCommon/Core/Utilities/ClassAd/__init__.py +1 -0
  7. DIRACCommon/Core/Utilities/DErrno.py +327 -0
  8. DIRACCommon/Core/Utilities/JDL.py +199 -0
  9. DIRACCommon/Core/Utilities/List.py +127 -0
  10. DIRACCommon/Core/Utilities/ReturnValues.py +255 -0
  11. DIRACCommon/Core/Utilities/StateMachine.py +185 -0
  12. DIRACCommon/Core/Utilities/TimeUtilities.py +259 -0
  13. DIRACCommon/Core/Utilities/__init__.py +3 -0
  14. DIRACCommon/Core/__init__.py +1 -0
  15. DIRACCommon/WorkloadManagementSystem/Client/JobState/JobManifest.py +235 -0
  16. DIRACCommon/WorkloadManagementSystem/Client/JobState/__init__.py +0 -0
  17. DIRACCommon/WorkloadManagementSystem/Client/JobStatus.py +95 -0
  18. DIRACCommon/WorkloadManagementSystem/Client/__init__.py +1 -0
  19. DIRACCommon/WorkloadManagementSystem/DB/JobDBUtils.py +170 -0
  20. DIRACCommon/WorkloadManagementSystem/DB/__init__.py +1 -0
  21. DIRACCommon/WorkloadManagementSystem/Utilities/JobModel.py +236 -0
  22. DIRACCommon/WorkloadManagementSystem/Utilities/JobStatusUtility.py +93 -0
  23. DIRACCommon/WorkloadManagementSystem/Utilities/ParametricJob.py +179 -0
  24. DIRACCommon/WorkloadManagementSystem/Utilities/__init__.py +1 -0
  25. DIRACCommon/WorkloadManagementSystem/__init__.py +1 -0
  26. DIRACCommon/__init__.py +21 -0
  27. diraccommon-9.0.0.dist-info/METADATA +281 -0
  28. diraccommon-9.0.0.dist-info/RECORD +29 -0
  29. diraccommon-9.0.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,52 @@
1
+ """Platform utilities for DIRAC platform compatibility and management.
2
+
3
+ This module provides functions for working with DIRAC platforms and OS compatibility.
4
+ """
5
+ import re
6
+
7
+ from DIRACCommon.Core.Utilities.ReturnValues import S_ERROR, S_OK, DReturnType
8
+
9
+
10
+ def getDIRACPlatform(osList: list[str], osCompatibilityDict: dict[str, set[str]]) -> DReturnType[list[str]]:
11
+ """Get standard DIRAC platform(s) compatible with the argument.
12
+
13
+ NB: The returned value is a list, ordered by numeric components in the platform.
14
+ In practice the "highest" version (which should be the most "desirable" one is returned first)
15
+
16
+ :param list osList: list of platforms defined by resource providers
17
+ :param dict osCompatibilityDict: dictionary with OS compatibility information
18
+ :return: a list of DIRAC platforms that can be specified in job descriptions
19
+ """
20
+
21
+ if not osCompatibilityDict:
22
+ return S_ERROR("OS compatibility info not found")
23
+
24
+ # making an OS -> platforms dict
25
+ os2PlatformDict = dict()
26
+ for platform, osItems in osCompatibilityDict.items():
27
+ for osItem in osItems:
28
+ os2PlatformDict.setdefault(osItem, set()).add(platform)
29
+
30
+ platforms = set()
31
+ for os in osList:
32
+ platforms |= os2PlatformDict.get(os, set())
33
+
34
+ if not platforms:
35
+ return S_ERROR(f"No compatible DIRAC platform found for {','.join(osList)}")
36
+
37
+ return S_OK(sorted(platforms, key=_platformSortKey, reverse=True))
38
+
39
+
40
+ def _platformSortKey(version: str) -> list[str]:
41
+ # Loosely based on distutils.version.LooseVersion
42
+ parts = []
43
+ for part in re.split(r"(\d+|[a-z]+|\.| -)", version.lower()):
44
+ if not part or part == ".":
45
+ continue
46
+ if part[:1] in "0123456789":
47
+ part = part.zfill(8)
48
+ else:
49
+ while parts and parts[-1] == "00000000":
50
+ parts.pop()
51
+ parts.append(part)
52
+ return parts
@@ -0,0 +1,3 @@
1
+ """
2
+ DIRACCommon.ConfigurationSystem.Client.Helpers - Configuration system helper functions
3
+ """
@@ -0,0 +1,3 @@
1
+ """
2
+ DIRACCommon.ConfigurationSystem.Client - Configuration system client components
3
+ """
@@ -0,0 +1,3 @@
1
+ """
2
+ DIRACCommon.ConfigurationSystem - Configuration system components
3
+ """
@@ -0,0 +1,295 @@
1
+ """ ClassAd Class - a light purely Python representation of the
2
+ Condor ClassAd library.
3
+ """
4
+
5
+
6
+ class ClassAd:
7
+ def __init__(self, jdl):
8
+ """ClassAd constructor from a JDL string"""
9
+ self.contents = {}
10
+ result = self.__analyse_jdl(jdl)
11
+ if result:
12
+ self.contents = result
13
+
14
+ def __analyse_jdl(self, jdl, index=0):
15
+ """Analyse one [] jdl enclosure"""
16
+
17
+ jdl = jdl.strip()
18
+
19
+ # Strip all the blanks first
20
+ # temp = jdl.replace(' ','').replace('\n','')
21
+ temp = jdl
22
+
23
+ result = {}
24
+
25
+ if temp[0] != "[" or temp[-1] != "]":
26
+ print("Invalid JDL: it should start with [ and end with ]")
27
+ return result
28
+
29
+ # Parse the jdl string now
30
+ body = temp[1:-1]
31
+ index = 0
32
+ namemode = 1
33
+ valuemode = 0
34
+ while index < len(body):
35
+ if namemode:
36
+ ind = body.find("=", index)
37
+ if ind != -1:
38
+ name = body[index:ind]
39
+ index = ind + 1
40
+ valuemode = 1
41
+ namemode = 0
42
+ else:
43
+ break
44
+ elif valuemode:
45
+ ind1 = body.find("[", index)
46
+ ind2 = body.find(";", index)
47
+ if ind1 != -1 and ind1 < ind2:
48
+ value, newind = self.__find_subjdl(body, ind1)
49
+ elif ind1 == -1 and ind2 == -1:
50
+ value = body[index:]
51
+ newind = len(body)
52
+ else:
53
+ if index == ind2:
54
+ return {}
55
+ else:
56
+ value = body[index:ind2]
57
+ newind = ind2 + 1
58
+
59
+ result[name.strip()] = value.strip().replace("\n", "")
60
+ index = newind
61
+ valuemode = 0
62
+ namemode = 1
63
+
64
+ return result
65
+
66
+ def __find_subjdl(self, body, index):
67
+ """Find a full [] enclosure starting from index"""
68
+ result = ""
69
+ if body[index] != "[":
70
+ return (result, 0)
71
+
72
+ depth = 0
73
+ ind = index
74
+ while depth < 10:
75
+ ind1 = body.find("]", ind + 1)
76
+ ind2 = body.find("[", ind + 1)
77
+ if ind2 != -1 and ind2 < ind1:
78
+ depth += 1
79
+ ind = ind2
80
+ else:
81
+ if depth > 0:
82
+ depth -= 1
83
+ ind = ind1
84
+ else:
85
+ result = body[index : ind1 + 1]
86
+ if body[ind1 + 1] == ";":
87
+ return (result, ind1 + 2)
88
+ return result, 0
89
+
90
+ return result, 0
91
+
92
+ def insertAttributeInt(self, name, attribute):
93
+ """Insert a named integer attribute"""
94
+
95
+ self.contents[name] = str(attribute)
96
+
97
+ def insertAttributeBool(self, name, attribute):
98
+ """Insert a named boolean attribute"""
99
+
100
+ if attribute:
101
+ self.contents[name] = "true"
102
+ else:
103
+ self.contents[name] = "false"
104
+
105
+ def insertAttributeString(self, name, attribute):
106
+ """Insert a named string attribute"""
107
+
108
+ self.contents[name] = '"' + str(attribute) + '"'
109
+
110
+ def insertAttributeVectorString(self, name, attributelist):
111
+ """Insert a named string list attribute"""
112
+
113
+ tmp = ['"' + x + '"' for x in attributelist]
114
+ tmpstr = ",".join(tmp)
115
+ self.contents[name] = "{" + tmpstr + "}"
116
+
117
+ def insertAttributeVectorInt(self, name, attributelist):
118
+ """Insert a named string list attribute"""
119
+
120
+ tmp = [str(x) for x in attributelist]
121
+ tmpstr = ",".join(tmp)
122
+ self.contents[name] = "{" + tmpstr + "}"
123
+
124
+ def insertAttributeVectorStringList(self, name, attributelist):
125
+ """Insert a named list of string lists"""
126
+
127
+ listOfLists = []
128
+ for stringList in attributelist:
129
+ # tmp = map ( lambda x : '"' + x + '"', stringList )
130
+ tmpstr = ",".join(stringList)
131
+ listOfLists.append("{" + tmpstr + "}")
132
+ self.contents[name] = "{" + ",".join(listOfLists) + "}"
133
+
134
+ def lookupAttribute(self, name):
135
+ """Check the presence of the given attribute"""
136
+
137
+ return name in self.contents
138
+
139
+ def set_expression(self, name, attribute):
140
+ """Insert a named expression attribute"""
141
+
142
+ self.contents[name] = str(attribute)
143
+
144
+ def get_expression(self, name):
145
+ """Get expression corresponding to a named attribute"""
146
+
147
+ if name in self.contents:
148
+ if isinstance(self.contents[name], int):
149
+ return str(self.contents[name])
150
+ return self.contents[name]
151
+ return ""
152
+
153
+ def isAttributeList(self, name):
154
+ """Check if the given attribute is of the List type"""
155
+ attribute = self.get_expression(name).strip()
156
+ return attribute.startswith("{")
157
+
158
+ def getListFromExpression(self, name):
159
+ """Get a list of strings from a given expression"""
160
+
161
+ tempString = self.get_expression(name).strip()
162
+ listMode = False
163
+ if tempString.startswith("{"):
164
+ tempString = tempString[1:-1]
165
+ listMode = True
166
+
167
+ tempString = tempString.replace(" ", "").replace("\n", "")
168
+ if tempString.find("{") < 0:
169
+ if not listMode:
170
+ tempString = tempString.replace('"', "")
171
+ if not tempString:
172
+ return []
173
+ return tempString.split(",")
174
+
175
+ resultList = []
176
+ while tempString:
177
+ if tempString.find("{") == 0:
178
+ end = tempString.find("}")
179
+ resultList.append(tempString[: end + 1])
180
+ tempString = tempString[end + 1 :]
181
+ if tempString.startswith(","):
182
+ tempString = tempString[1:]
183
+ elif tempString.find('"') == 0:
184
+ end = tempString[1:].find('"')
185
+ resultList.append(tempString[1 : end + 1])
186
+ tempString = tempString[end + 2 :]
187
+ if tempString.startswith(","):
188
+ tempString = tempString[1:]
189
+ else:
190
+ end = tempString.find(",")
191
+ if end < 0:
192
+ resultList.append(tempString.replace('"', "").replace(" ", ""))
193
+ break
194
+ else:
195
+ resultList.append(tempString[:end].replace('"', "").replace(" ", ""))
196
+ tempString = tempString[end + 1 :]
197
+
198
+ return resultList
199
+
200
+ def getDictionaryFromSubJDL(self, name):
201
+ """Get a dictionary of the JDL attributes from a subsection"""
202
+
203
+ tempList = self.get_expression(name)[1:-1]
204
+ resDict = {}
205
+ for item in tempList.split(";"):
206
+ if len(item.split("=")) == 2:
207
+ resDict[item.split("=")[0].strip()] = item.split("=")[1].strip().replace('"', "")
208
+ else:
209
+ return {}
210
+
211
+ return resDict
212
+
213
+ def deleteAttribute(self, name):
214
+ """Delete a named attribute"""
215
+
216
+ if name in self.contents:
217
+ del self.contents[name]
218
+ return 1
219
+ return 0
220
+
221
+ def isOK(self):
222
+ """Check the JDL validity - to be defined"""
223
+
224
+ if self.contents:
225
+ return 1
226
+ return 0
227
+
228
+ def asJDL(self):
229
+ """Convert the JDL description into a string"""
230
+
231
+ result = []
232
+ for name, value in sorted(self.contents.items()):
233
+ if value[0:1] == "{":
234
+ result += [4 * " " + name + " = \n"]
235
+ result += [8 * " " + "{\n"]
236
+ strings = value[1:-1].split(",")
237
+ for st in strings:
238
+ result += [12 * " " + st.strip() + ",\n"]
239
+ result[-1] = result[-1][:-2]
240
+ result += ["\n" + 8 * " " + "};\n"]
241
+ elif value[0:1] == "[":
242
+ tempad = ClassAd(value)
243
+ tempjdl = tempad.asJDL() + ";"
244
+ lines = tempjdl.split("\n")
245
+ result += [4 * " " + name + " = \n"]
246
+ for line in lines:
247
+ result += [8 * " " + line + "\n"]
248
+
249
+ else:
250
+ result += [4 * " " + name + " = " + str(value) + ";\n"]
251
+ if result:
252
+ result[-1] = result[-1][:-1]
253
+ return "[ \n" + "".join(result) + "\n]"
254
+
255
+ def getAttributeString(self, name):
256
+ """Get String type attribute value"""
257
+ value = ""
258
+ if self.lookupAttribute(name):
259
+ value = self.get_expression(name).replace('"', "")
260
+ return value
261
+
262
+ def getAttributeInt(self, name):
263
+ """Get Integer type attribute value"""
264
+ value = None
265
+ if self.lookupAttribute(name):
266
+ try:
267
+ value = int(self.get_expression(name).replace('"', ""))
268
+ except Exception:
269
+ value = None
270
+ return value
271
+
272
+ def getAttributeBool(self, name):
273
+ """Get Boolean type attribute value"""
274
+ if not self.lookupAttribute(name):
275
+ return False
276
+
277
+ value = self.get_expression(name).replace('"', "")
278
+ return value.lower() == "true"
279
+
280
+ def getAttributeFloat(self, name):
281
+ """Get Float type attribute value"""
282
+ value = None
283
+ if self.lookupAttribute(name):
284
+ try:
285
+ value = float(self.get_expression(name).replace('"', ""))
286
+ except Exception:
287
+ value = None
288
+ return value
289
+
290
+ def getAttributes(self) -> list[str]:
291
+ """Get the list of all the attribute names
292
+
293
+ :return: list of names as strings
294
+ """
295
+ return list(self.contents)
@@ -0,0 +1 @@
1
+ """ClassAd utilities for DIRACCommon"""
@@ -0,0 +1,327 @@
1
+ """ :mod: DErrno
2
+
3
+ ==========================
4
+
5
+ .. module: DErrno
6
+
7
+ :synopsis: Error list and utilities for handling errors in DIRAC
8
+
9
+
10
+ This module contains list of errors that can be encountered in DIRAC.
11
+ It complements the errno module of python.
12
+
13
+ It also contains utilities to manipulate these errors.
14
+
15
+ This is a stateless version extracted to DIRACCommon to avoid circular dependencies.
16
+ The extension loading functionality has been removed.
17
+ """
18
+ import os
19
+
20
+ # To avoid conflict, the error numbers should be greater than 1000
21
+ # We decided to group the by range of 100 per system
22
+
23
+ # 1000: Generic
24
+ # 1100: Core
25
+ # 1200: Framework
26
+ # 1300: Interfaces
27
+ # 1400: Config
28
+ # 1500: WMS + Workflow
29
+ # 1600: DMS + StorageManagement
30
+ # 1700: RMS
31
+ # 1800: Accounting + Monitoring
32
+ # 1900: TS + Production
33
+ # 2000: Resources + RSS
34
+
35
+ # ## Generic (10XX)
36
+ # Python related: 0X
37
+ ETYPE = 1000
38
+ EIMPERR = 1001
39
+ ENOMETH = 1002
40
+ ECONF = 1003
41
+ EVALUE = 1004
42
+ EEEXCEPTION = 1005
43
+ # Files manipulation: 1X
44
+ ECTMPF = 1010
45
+ EOF = 1011
46
+ ERF = 1012
47
+ EWF = 1013
48
+ ESPF = 1014
49
+
50
+ # ## Core (11XX)
51
+ # Certificates and Proxy: 0X
52
+ EX509 = 1100
53
+ EPROXYFIND = 1101
54
+ EPROXYREAD = 1102
55
+ ECERTFIND = 1103
56
+ ECERTREAD = 1104
57
+ ENOCERT = 1105
58
+ ENOCHAIN = 1106
59
+ ENOPKEY = 1107
60
+ ENOGROUP = 1108
61
+ # DISET: 1X
62
+ EDISET = 1110
63
+ ENOAUTH = 1111
64
+ # 3rd party security: 2X
65
+ E3RDPARTY = 1120
66
+ EVOMS = 1121
67
+ # Databases : 3X
68
+ EDB = 1130
69
+ EMYSQL = 1131
70
+ ESQLA = 1132
71
+ # Message Queues: 4X
72
+ EMQUKN = 1140
73
+ EMQNOM = 1141
74
+ EMQCONN = 1142
75
+ # OpenSearch
76
+ EELNOFOUND = 1146
77
+ # Tokens
78
+ EATOKENFIND = 1150
79
+ EATOKENREAD = 1151
80
+ ETOKENTYPE = 1152
81
+
82
+ # config
83
+ ESECTION = 1400
84
+
85
+ # processes
86
+ EEZOMBIE = 1147
87
+ EENOPID = 1148
88
+
89
+ # ## WMS/Workflow
90
+ EWMSUKN = 1500
91
+ EWMSJDL = 1501
92
+ EWMSRESC = 1502
93
+ EWMSSUBM = 1503
94
+ EWMSJMAN = 1504
95
+ EWMSSTATUS = 1505
96
+ EWMSNOMATCH = 1510
97
+ EWMSPLTVER = 1511
98
+ EWMSNOPILOT = 1550
99
+
100
+ # ## DMS/StorageManagement (16XX)
101
+ EFILESIZE = 1601
102
+ EGFAL = 1602
103
+ EBADCKS = 1603
104
+ EFCERR = 1604
105
+
106
+ # ## RMS (17XX)
107
+ ERMSUKN = 1700
108
+
109
+ # ## TS (19XX)
110
+ ETSUKN = 1900
111
+ ETSDATA = 1901
112
+
113
+ # ## Resources and RSS (20XX)
114
+ ERESGEN = 2000
115
+ ERESUNA = 2001
116
+ ERESUNK = 2002
117
+
118
+ # This translates the integer number into the name of the variable
119
+ dErrorCode = {
120
+ # ## Generic (10XX)
121
+ # 100X: Python related
122
+ 1000: "ETYPE",
123
+ 1001: "EIMPERR",
124
+ 1002: "ENOMETH",
125
+ 1003: "ECONF",
126
+ 1004: "EVALUE",
127
+ 1005: "EEEXCEPTION",
128
+ # 101X: Files manipulation
129
+ 1010: "ECTMPF",
130
+ 1011: "EOF",
131
+ 1012: "ERF",
132
+ 1013: "EWF",
133
+ 1014: "ESPF",
134
+ # ## Core
135
+ # 110X: Certificates and Proxy
136
+ 1100: "EX509",
137
+ 1101: "EPROXYFIND",
138
+ 1102: "EPROXYREAD",
139
+ 1103: "ECERTFIND",
140
+ 1104: "ECERTREAD",
141
+ 1105: "ENOCERT",
142
+ 1106: "ENOCHAIN",
143
+ 1107: "ENOPKEY",
144
+ 1108: "ENOGROUP",
145
+ # 111X: DISET
146
+ 1110: "EDISET",
147
+ 1111: "ENOAUTH",
148
+ # 112X: 3rd party security
149
+ 1120: "E3RDPARTY",
150
+ 1121: "EVOMS",
151
+ # 113X: Databases
152
+ 1130: "EDB",
153
+ 1131: "EMYSQL",
154
+ 1132: "ESQLA",
155
+ # 114X: Message Queues
156
+ 1140: "EMQUKN",
157
+ 1141: "EMQNOM",
158
+ 1142: "EMQCONN",
159
+ # OpenSearch
160
+ 1146: "EELNOFOUND",
161
+ # 115X: Tokens
162
+ 1150: "EATOKENFIND",
163
+ 1151: "EATOKENREAD",
164
+ 1152: "ETOKENTYPE",
165
+ # Config
166
+ 1400: "ESECTION",
167
+ # Processes
168
+ 1147: "EEZOMBIE",
169
+ 1148: "EENOPID",
170
+ # WMS/Workflow
171
+ 1500: "EWMSUKN",
172
+ 1501: "EWMSJDL",
173
+ 1502: "EWMSRESC",
174
+ 1503: "EWMSSUBM",
175
+ 1504: "EWMSJMAN",
176
+ 1505: "EWMSSTATUS",
177
+ 1510: "EWMSNOMATCH",
178
+ 1511: "EWMSPLTVER",
179
+ 1550: "EWMSNOPILOT",
180
+ # DMS/StorageManagement
181
+ 1601: "EFILESIZE",
182
+ 1602: "EGFAL",
183
+ 1603: "EBADCKS",
184
+ 1604: "EFCERR",
185
+ # RMS
186
+ 1700: "ERMSUKN",
187
+ # Resources and RSS
188
+ 2000: "ERESGEN",
189
+ 2001: "ERESUNA",
190
+ 2002: "ERESUNK",
191
+ # TS
192
+ 1900: "ETSUKN",
193
+ 1901: "ETSDATA",
194
+ }
195
+
196
+
197
+ dStrError = { # Generic (10XX)
198
+ # 100X: Python related
199
+ ETYPE: "Object Type Error",
200
+ EIMPERR: "Failed to import library",
201
+ ENOMETH: "No such method or function",
202
+ ECONF: "Configuration error",
203
+ EVALUE: "Wrong value passed",
204
+ EEEXCEPTION: "runtime general exception",
205
+ # 101X: Files manipulation
206
+ ECTMPF: "Failed to create temporary file",
207
+ EOF: "Cannot open file",
208
+ ERF: "Cannot read from file",
209
+ EWF: "Cannot write to file",
210
+ ESPF: "Cannot set permissions to file",
211
+ # ## Core
212
+ # 110X: Certificates and Proxy
213
+ EX509: "Generic Error with X509",
214
+ EPROXYFIND: "Can't find proxy",
215
+ EPROXYREAD: "Can't read proxy",
216
+ ECERTFIND: "Can't find certificate",
217
+ ECERTREAD: "Can't read certificate",
218
+ ENOCERT: "No certificate loaded",
219
+ ENOCHAIN: "No chain loaded",
220
+ ENOPKEY: "No private key loaded",
221
+ ENOGROUP: "No DIRAC group",
222
+ # 111X: DISET
223
+ EDISET: "DISET Error",
224
+ ENOAUTH: "Unauthorized query",
225
+ # 112X: 3rd party security
226
+ E3RDPARTY: "3rd party security service error",
227
+ EVOMS: "VOMS Error",
228
+ # 113X: Databases
229
+ EDB: "Database Error",
230
+ EMYSQL: "MySQL Error",
231
+ ESQLA: "SQLAlchemy Error",
232
+ # 114X: Message Queues
233
+ EMQUKN: "Unknown MQ Error",
234
+ EMQNOM: "No messages",
235
+ EMQCONN: "MQ connection failure",
236
+ # 114X OpenSearch
237
+ EELNOFOUND: "Index not found",
238
+ # 115X: Tokens
239
+ EATOKENFIND: "Can't find a bearer access token.",
240
+ EATOKENREAD: "Can't read a bearer access token.",
241
+ ETOKENTYPE: "Unsupported access token type.",
242
+ # Config
243
+ ESECTION: "Section is not found",
244
+ # processes
245
+ EEZOMBIE: "Zombie process",
246
+ EENOPID: "No PID of process",
247
+ # WMS/Workflow
248
+ EWMSUKN: "Unknown WMS error",
249
+ EWMSJDL: "Invalid job description",
250
+ EWMSRESC: "Job to reschedule",
251
+ EWMSSUBM: "Job submission error",
252
+ EWMSJMAN: "Job management error",
253
+ EWMSSTATUS: "Job status error",
254
+ EWMSNOPILOT: "No pilots found",
255
+ EWMSPLTVER: "Pilot version does not match",
256
+ EWMSNOMATCH: "No match found",
257
+ # DMS/StorageManagement
258
+ EFILESIZE: "Bad file size",
259
+ EGFAL: "Error with the gfal call",
260
+ EBADCKS: "Bad checksum",
261
+ EFCERR: "FileCatalog error",
262
+ # RMS
263
+ ERMSUKN: "Unknown RMS error",
264
+ # Resources and RSS
265
+ ERESGEN: "Unknown Resource Failure",
266
+ ERESUNA: "Resource not available",
267
+ ERESUNK: "Unknown Resource",
268
+ # TS
269
+ ETSUKN: "Unknown Transformation System Error",
270
+ ETSDATA: "Invalid Input Data definition",
271
+ }
272
+
273
+
274
+ def strerror(code: int) -> str:
275
+ """This method wraps up os.strerror, and behave the same way.
276
+ It completes it with the DIRAC specific errors.
277
+ """
278
+
279
+ if code == 0:
280
+ return "Undefined error"
281
+
282
+ errMsg = f"Unknown error {code}"
283
+
284
+ try:
285
+ errMsg = dStrError[code]
286
+ except KeyError:
287
+ # It is not a DIRAC specific error, try the os one
288
+ try:
289
+ errMsg = os.strerror(code)
290
+ # On some system, os.strerror raises an exception with unknown code,
291
+ # on others, it returns a message...
292
+ except ValueError:
293
+ pass
294
+
295
+ return errMsg
296
+
297
+
298
+ def cmpError(inErr: str | int | dict, candidate: int) -> bool:
299
+ """This function compares an error (in its old form (a string or dictionary) or in its int form
300
+ with a candidate error code.
301
+
302
+ :param inErr: a string, an integer, a S_ERROR dictionary
303
+ :type inErr: str or int or S_ERROR
304
+ :param int candidate: error code to compare with
305
+
306
+ :return: True or False
307
+
308
+ If an S_ERROR instance is passed, we compare the code with S_ERROR['Errno']
309
+ If it is a Integer, we do a direct comparison
310
+ If it is a String, we use strerror to check the error string
311
+ """
312
+
313
+ if isinstance(inErr, str): # old style
314
+ # Compare error message strings
315
+ errMsg = strerror(candidate)
316
+ return errMsg in inErr
317
+ elif isinstance(inErr, dict): # if the S_ERROR structure is given
318
+ # Check if Errno defined in the dict
319
+ errorNumber = inErr.get("Errno")
320
+ if errorNumber:
321
+ return errorNumber == candidate
322
+ errMsg = strerror(candidate)
323
+ return errMsg in inErr.get("Message", "")
324
+ elif isinstance(inErr, int):
325
+ return inErr == candidate
326
+ else:
327
+ raise TypeError(f"Unknown input error type {type(inErr)}")