DIRAC 9.0.0a66__py3-none-any.whl → 9.0.0a68__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/Helpers/Resources.py +11 -43
- DIRAC/ConfigurationSystem/Client/Helpers/test/Test_Helpers.py +0 -16
- DIRAC/ConfigurationSystem/Client/VOMS2CSSynchronizer.py +1 -1
- DIRAC/Core/Security/IAMService.py +4 -3
- DIRAC/Core/Utilities/ClassAd/ClassAdLight.py +4 -290
- DIRAC/Core/Utilities/DErrno.py +1 -1
- DIRAC/Core/Utilities/JDL.py +1 -195
- DIRAC/Core/Utilities/List.py +1 -127
- DIRAC/Core/Utilities/ReturnValues.py +2 -2
- DIRAC/Core/Utilities/StateMachine.py +12 -178
- DIRAC/Core/Utilities/TimeUtilities.py +10 -253
- DIRAC/Core/Utilities/test/Test_JDL.py +0 -3
- DIRAC/DataManagementSystem/DB/FTS3DB.py +3 -0
- DIRAC/RequestManagementSystem/DB/test/RMSTestScenari.py +2 -0
- DIRAC/Resources/Catalog/RucioFileCatalogClient.py +1 -1
- DIRAC/Resources/Computing/test/Test_PoolComputingElement.py +2 -1
- DIRAC/Workflow/Modules/test/Test_Modules.py +5 -0
- DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_JobAgent.py +2 -0
- DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_PushJobAgent.py +1 -0
- DIRAC/WorkloadManagementSystem/Client/JobState/JobManifest.py +32 -261
- DIRAC/WorkloadManagementSystem/Client/JobStatus.py +8 -93
- DIRAC/WorkloadManagementSystem/DB/JobDBUtils.py +18 -147
- DIRAC/WorkloadManagementSystem/JobWrapper/JobWrapper.py +4 -2
- DIRAC/WorkloadManagementSystem/JobWrapper/test/Test_JobWrapper.py +21 -5
- DIRAC/WorkloadManagementSystem/JobWrapper/test/Test_JobWrapperTemplate.py +4 -0
- DIRAC/WorkloadManagementSystem/Utilities/JobModel.py +28 -199
- DIRAC/WorkloadManagementSystem/Utilities/JobStatusUtility.py +1 -63
- DIRAC/WorkloadManagementSystem/Utilities/ParametricJob.py +7 -171
- DIRAC/WorkloadManagementSystem/Utilities/test/Test_JobModel.py +1 -5
- DIRAC/WorkloadManagementSystem/Utilities/test/Test_ParametricJob.py +45 -128
- {dirac-9.0.0a66.dist-info → dirac-9.0.0a68.dist-info}/METADATA +2 -2
- {dirac-9.0.0a66.dist-info → dirac-9.0.0a68.dist-info}/RECORD +36 -38
- DIRAC/Core/Utilities/test/Test_List.py +0 -150
- DIRAC/Core/Utilities/test/Test_Time.py +0 -88
- {dirac-9.0.0a66.dist-info → dirac-9.0.0a68.dist-info}/WHEEL +0 -0
- {dirac-9.0.0a66.dist-info → dirac-9.0.0a68.dist-info}/entry_points.txt +0 -0
- {dirac-9.0.0a66.dist-info → dirac-9.0.0a68.dist-info}/licenses/LICENSE +0 -0
- {dirac-9.0.0a66.dist-info → dirac-9.0.0a68.dist-info}/top_level.txt +0 -0
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
""" Helper for the CS Resources section
|
|
2
2
|
"""
|
|
3
|
-
import re
|
|
4
3
|
from urllib import parse
|
|
5
4
|
|
|
6
5
|
from DIRAC import S_ERROR, S_OK, gConfig, gLogger
|
|
7
6
|
from DIRAC.ConfigurationSystem.Client.Helpers.Path import cfgPath
|
|
8
7
|
from DIRAC.Core.Utilities.List import fromChar, uniqueElements
|
|
8
|
+
from DIRACCommon.ConfigurationSystem.Client.Helpers.Resources import (
|
|
9
|
+
getDIRACPlatform as _getDIRACPlatform,
|
|
10
|
+
_platformSortKey,
|
|
11
|
+
)
|
|
9
12
|
|
|
10
13
|
gBaseResourcesSection = "/Resources"
|
|
11
14
|
|
|
@@ -328,8 +331,8 @@ def getCompatiblePlatforms(originalPlatforms):
|
|
|
328
331
|
if not (result["OK"] and result["Value"]):
|
|
329
332
|
return S_ERROR("OS compatibility info not found")
|
|
330
333
|
|
|
331
|
-
platformsDict = {k: v.replace(" ", "").split(",") for k, v in result["Value"].items()}
|
|
332
|
-
for k, v in platformsDict.items():
|
|
334
|
+
platformsDict = {k: v.replace(" ", "").split(",") for k, v in result["Value"].items()}
|
|
335
|
+
for k, v in platformsDict.items():
|
|
333
336
|
if k not in v:
|
|
334
337
|
v.append(k)
|
|
335
338
|
|
|
@@ -355,7 +358,6 @@ def getDIRACPlatform(OSList):
|
|
|
355
358
|
:param list OSList: list of platforms defined by resource providers
|
|
356
359
|
:return: a list of DIRAC platforms that can be specified in job descriptions
|
|
357
360
|
"""
|
|
358
|
-
|
|
359
361
|
# For backward compatibility allow a single string argument
|
|
360
362
|
osList = OSList
|
|
361
363
|
if isinstance(OSList, str):
|
|
@@ -365,31 +367,12 @@ def getDIRACPlatform(OSList):
|
|
|
365
367
|
if not (result["OK"] and result["Value"]):
|
|
366
368
|
return S_ERROR("OS compatibility info not found")
|
|
367
369
|
|
|
368
|
-
platformsDict = {k: v.replace(" ", "").split(",") for k, v in result["Value"].items()}
|
|
369
|
-
for k, v in platformsDict.items():
|
|
370
|
+
platformsDict = {k: set(v.replace(" ", "").split(",")) for k, v in result["Value"].items()}
|
|
371
|
+
for k, v in platformsDict.items():
|
|
370
372
|
if k not in v:
|
|
371
|
-
v.
|
|
372
|
-
|
|
373
|
-
# making an OS -> platforms dict
|
|
374
|
-
os2PlatformDict = dict()
|
|
375
|
-
for platform, osItems in platformsDict.items(): # can be an iterator
|
|
376
|
-
for osItem in osItems:
|
|
377
|
-
if os2PlatformDict.get(osItem):
|
|
378
|
-
os2PlatformDict[osItem].append(platform)
|
|
379
|
-
else:
|
|
380
|
-
os2PlatformDict[osItem] = [platform]
|
|
381
|
-
|
|
382
|
-
platforms = []
|
|
383
|
-
for os in osList:
|
|
384
|
-
if os in os2PlatformDict:
|
|
385
|
-
platforms += os2PlatformDict[os]
|
|
386
|
-
|
|
387
|
-
if not platforms:
|
|
388
|
-
return S_ERROR(f"No compatible DIRAC platform found for {','.join(OSList)}")
|
|
373
|
+
v.add(k)
|
|
389
374
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
return S_OK(platforms)
|
|
375
|
+
return _getDIRACPlatform(osList, platformsDict)
|
|
393
376
|
|
|
394
377
|
|
|
395
378
|
def getDIRACPlatforms():
|
|
@@ -451,7 +434,7 @@ def getInfoAboutProviders(of=None, providerName=None, option="", section=""):
|
|
|
451
434
|
result = gConfig.getConfigurationTree(relPath)
|
|
452
435
|
if not result["OK"]:
|
|
453
436
|
return result
|
|
454
|
-
for key, value in result["Value"].items():
|
|
437
|
+
for key, value in result["Value"].items():
|
|
455
438
|
if value:
|
|
456
439
|
resDict[key.replace(relPath, "")] = value
|
|
457
440
|
return S_OK(resDict)
|
|
@@ -459,18 +442,3 @@ def getInfoAboutProviders(of=None, providerName=None, option="", section=""):
|
|
|
459
442
|
return gConfig.getSections(f"{gBaseResourcesSection}/{of}Providers/{providerName}/{section}/")
|
|
460
443
|
else:
|
|
461
444
|
return S_OK(gConfig.getValue(f"{gBaseResourcesSection}/{of}Providers/{providerName}/{section}/{option}"))
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
def _platformSortKey(version: str) -> list[str]:
|
|
465
|
-
# Loosely based on distutils.version.LooseVersion
|
|
466
|
-
parts = []
|
|
467
|
-
for part in re.split(r"(\d+|[a-z]+|\.| -)", version.lower()):
|
|
468
|
-
if not part or part == ".":
|
|
469
|
-
continue
|
|
470
|
-
if part[:1] in "0123456789":
|
|
471
|
-
part = part.zfill(8)
|
|
472
|
-
else:
|
|
473
|
-
while parts and parts[-1] == "00000000":
|
|
474
|
-
parts.pop()
|
|
475
|
-
parts.append(part)
|
|
476
|
-
return parts
|
|
@@ -9,7 +9,6 @@ from DIRAC.ConfigurationSystem.Client import ConfigurationData
|
|
|
9
9
|
from DIRAC.ConfigurationSystem.Client.Helpers.Resources import (
|
|
10
10
|
getDIRACPlatform,
|
|
11
11
|
getCompatiblePlatforms,
|
|
12
|
-
_platformSortKey,
|
|
13
12
|
getQueue,
|
|
14
13
|
)
|
|
15
14
|
|
|
@@ -77,21 +76,6 @@ def test_getDIRACPlatform(mocker, mockGCReplyInput, requested, expectedRes, expe
|
|
|
77
76
|
assert set(res["Value"]) == set(expectedValue), res["Value"]
|
|
78
77
|
|
|
79
78
|
|
|
80
|
-
@pytest.mark.parametrize(
|
|
81
|
-
"string,expected",
|
|
82
|
-
[
|
|
83
|
-
("Darwin_arm64_12.4", ["darwin", "_", "arm", "64", "_", "12", "4"]),
|
|
84
|
-
("Linux_x86_64_glibc-2.17", ["linux", "_", "x", "86", "_", "64", "_", "glibc", "-", "2", "17"]),
|
|
85
|
-
("Linux_aarch64_glibc-2.28", ["linux", "_", "aarch", "64", "_", "glibc", "-", "2", "28"]),
|
|
86
|
-
],
|
|
87
|
-
)
|
|
88
|
-
def test_platformSortKey(string, expected):
|
|
89
|
-
actual = _platformSortKey(string)
|
|
90
|
-
for a, e in zip_longest(actual, expected):
|
|
91
|
-
# Numbers are padded with zeros so string comparison works
|
|
92
|
-
assert a.lstrip("0") == e
|
|
93
|
-
|
|
94
|
-
|
|
95
79
|
@pytest.mark.parametrize(
|
|
96
80
|
"mockGCReplyInput, requested, expectedRes, expectedValue",
|
|
97
81
|
[
|
|
@@ -594,7 +594,7 @@ class VOMS2CSSynchronizer:
|
|
|
594
594
|
|
|
595
595
|
# Try to fill in the DiracX section
|
|
596
596
|
if self.useIAM:
|
|
597
|
-
iam_subs = self.iamSrv.getUsersSub()
|
|
597
|
+
iam_subs = self.iamSrv.getUsersSub(self.vo)
|
|
598
598
|
diracx_vo_config = {"DiracX": {"CsSync": {"VOs": {self.vo: {"UserSubjects": iam_subs}}}}}
|
|
599
599
|
iam_sub_cfg = CFG()
|
|
600
600
|
iam_sub_cfg.loadFromDict(diracx_vo_config)
|
|
@@ -144,7 +144,7 @@ class IAMService:
|
|
|
144
144
|
result = S_OK({"Users": users, "Errors": errors})
|
|
145
145
|
return result
|
|
146
146
|
|
|
147
|
-
def getUsersSub(self) -> dict[str, str]:
|
|
147
|
+
def getUsersSub(self, vo=None) -> dict[str, str]:
|
|
148
148
|
"""
|
|
149
149
|
Return the mapping based on IAM sub:
|
|
150
150
|
{nickname : sub}
|
|
@@ -152,6 +152,7 @@ class IAMService:
|
|
|
152
152
|
iam_users_raw = self._getIamUserDump()
|
|
153
153
|
diracx_user_section = {}
|
|
154
154
|
for user_info in iam_users_raw:
|
|
155
|
+
userGroups = [grp["display"] for grp in user_info.get("groups", [])]
|
|
155
156
|
# The nickname is available in the list of attributes
|
|
156
157
|
# (if configured so)
|
|
157
158
|
# in the form {'name': 'nickname', 'value': 'chaen'}
|
|
@@ -165,8 +166,8 @@ class IAMService:
|
|
|
165
166
|
except (KeyError, IndexError):
|
|
166
167
|
nickname = user_info["userName"]
|
|
167
168
|
sub = user_info["id"]
|
|
168
|
-
|
|
169
|
-
|
|
169
|
+
if not vo or vo in userGroups:
|
|
170
|
+
diracx_user_section[nickname] = sub
|
|
170
171
|
# reorder it
|
|
171
172
|
diracx_user_section = dict(sorted(diracx_user_section.items()))
|
|
172
173
|
|
|
@@ -2,294 +2,8 @@
|
|
|
2
2
|
Condor ClassAd library.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
# Import from DIRACCommon for backward compatibility
|
|
6
|
+
from DIRACCommon.Core.Utilities.ClassAd.ClassAdLight import ClassAd
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
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)
|
|
8
|
+
# Re-export for backward compatibility
|
|
9
|
+
__all__ = ["ClassAd"]
|
DIRAC/Core/Utilities/DErrno.py
CHANGED
|
@@ -41,7 +41,7 @@ import importlib
|
|
|
41
41
|
import sys
|
|
42
42
|
|
|
43
43
|
# Import all the stateless parts from DIRACCommon
|
|
44
|
-
from DIRACCommon.
|
|
44
|
+
from DIRACCommon.Core.Utilities.DErrno import * # noqa: F401, F403
|
|
45
45
|
|
|
46
46
|
from DIRAC.Core.Utilities.Extensions import extensionsByPriority
|
|
47
47
|
|
DIRAC/Core/Utilities/JDL.py
CHANGED
|
@@ -1,203 +1,9 @@
|
|
|
1
1
|
"""Transformation classes around the JDL format."""
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from pydantic import ValidationError
|
|
3
|
+
from DIRACCommon.Core.Utilities.JDL import * # noqa: F403,F401
|
|
5
4
|
|
|
6
|
-
from DIRAC import S_OK, S_ERROR
|
|
7
|
-
from DIRAC.Core.Utilities import List
|
|
8
|
-
from DIRAC.Core.Utilities.ClassAd.ClassAdLight import ClassAd
|
|
9
5
|
from DIRAC.WorkloadManagementSystem.Utilities.JobModel import BaseJobDescriptionModel
|
|
10
6
|
|
|
11
|
-
ARGUMENTS = "Arguments"
|
|
12
|
-
BANNED_SITES = "BannedSites"
|
|
13
|
-
CPU_TIME = "CPUTime"
|
|
14
|
-
EXECUTABLE = "Executable"
|
|
15
|
-
EXECUTION_ENVIRONMENT = "ExecutionEnvironment"
|
|
16
|
-
GRID_CE = "GridCE"
|
|
17
|
-
INPUT_DATA = "InputData"
|
|
18
|
-
INPUT_DATA_POLICY = "InputDataPolicy"
|
|
19
|
-
INPUT_SANDBOX = "InputSandbox"
|
|
20
|
-
JOB_CONFIG_ARGS = "JobConfigArgs"
|
|
21
|
-
JOB_TYPE = "JobType"
|
|
22
|
-
JOB_GROUP = "JobGroup"
|
|
23
|
-
LOG_LEVEL = "LogLevel"
|
|
24
|
-
NUMBER_OF_PROCESSORS = "NumberOfProcessors"
|
|
25
|
-
MAX_NUMBER_OF_PROCESSORS = "MaxNumberOfProcessors"
|
|
26
|
-
MIN_NUMBER_OF_PROCESSORS = "MinNumberOfProcessors"
|
|
27
|
-
OUTPUT_DATA = "OutputData"
|
|
28
|
-
OUTPUT_PATH = "OutputPath"
|
|
29
|
-
OUTPUT_SE = "OutputSE"
|
|
30
|
-
PLATFORM = "Platform"
|
|
31
|
-
PRIORITY = "Priority"
|
|
32
|
-
STD_ERROR = "StdError"
|
|
33
|
-
STD_OUTPUT = "StdOutput"
|
|
34
|
-
OUTPUT_SANDBOX = "OutputSandbox"
|
|
35
|
-
JOB_NAME = "JobName"
|
|
36
|
-
SITE = "Site"
|
|
37
|
-
TAGS = "Tags"
|
|
38
|
-
|
|
39
|
-
OWNER = "Owner"
|
|
40
|
-
OWNER_GROUP = "OwnerGroup"
|
|
41
|
-
VO = "VirtualOrganization"
|
|
42
|
-
|
|
43
|
-
CREDENTIALS_FIELDS = {OWNER, OWNER_GROUP, VO}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def loadJDLAsCFG(jdl):
|
|
47
|
-
"""
|
|
48
|
-
Load a JDL as CFG
|
|
49
|
-
"""
|
|
50
|
-
|
|
51
|
-
def cleanValue(value):
|
|
52
|
-
value = value.strip()
|
|
53
|
-
if value[0] == '"':
|
|
54
|
-
entries = []
|
|
55
|
-
iPos = 1
|
|
56
|
-
current = ""
|
|
57
|
-
state = "in"
|
|
58
|
-
while iPos < len(value):
|
|
59
|
-
if value[iPos] == '"':
|
|
60
|
-
if state == "in":
|
|
61
|
-
entries.append(current)
|
|
62
|
-
current = ""
|
|
63
|
-
state = "out"
|
|
64
|
-
elif state == "out":
|
|
65
|
-
current = current.strip()
|
|
66
|
-
if current not in (",",):
|
|
67
|
-
return S_ERROR("value seems a list but is not separated in commas")
|
|
68
|
-
current = ""
|
|
69
|
-
state = "in"
|
|
70
|
-
else:
|
|
71
|
-
current += value[iPos]
|
|
72
|
-
iPos += 1
|
|
73
|
-
if state == "in":
|
|
74
|
-
return S_ERROR('value is opened with " but is not closed')
|
|
75
|
-
return S_OK(", ".join(entries))
|
|
76
|
-
else:
|
|
77
|
-
return S_OK(value.replace('"', ""))
|
|
78
|
-
|
|
79
|
-
def assignValue(key, value, cfg):
|
|
80
|
-
key = key.strip()
|
|
81
|
-
if len(key) == 0:
|
|
82
|
-
return S_ERROR("Invalid key name")
|
|
83
|
-
value = value.strip()
|
|
84
|
-
if not value:
|
|
85
|
-
return S_ERROR(f"No value for key {key}")
|
|
86
|
-
if value[0] == "{":
|
|
87
|
-
if value[-1] != "}":
|
|
88
|
-
return S_ERROR("Value '%s' seems a list but does not end in '}'" % (value))
|
|
89
|
-
valList = List.fromChar(value[1:-1])
|
|
90
|
-
for i in range(len(valList)):
|
|
91
|
-
result = cleanValue(valList[i])
|
|
92
|
-
if not result["OK"]:
|
|
93
|
-
return S_ERROR(f"Var {key} : {result['Message']}")
|
|
94
|
-
valList[i] = result["Value"]
|
|
95
|
-
if valList[i] is None:
|
|
96
|
-
return S_ERROR(f"List value '{value}' seems invalid for item {i}")
|
|
97
|
-
value = ", ".join(valList)
|
|
98
|
-
else:
|
|
99
|
-
result = cleanValue(value)
|
|
100
|
-
if not result["OK"]:
|
|
101
|
-
return S_ERROR(f"Var {key} : {result['Message']}")
|
|
102
|
-
nV = result["Value"]
|
|
103
|
-
if nV is None:
|
|
104
|
-
return S_ERROR(f"Value '{value} seems invalid")
|
|
105
|
-
value = nV
|
|
106
|
-
cfg.setOption(key, value)
|
|
107
|
-
return S_OK()
|
|
108
|
-
|
|
109
|
-
if jdl[0] == "[":
|
|
110
|
-
iPos = 1
|
|
111
|
-
else:
|
|
112
|
-
iPos = 0
|
|
113
|
-
key = ""
|
|
114
|
-
value = ""
|
|
115
|
-
action = "key"
|
|
116
|
-
insideLiteral = False
|
|
117
|
-
cfg = CFG()
|
|
118
|
-
while iPos < len(jdl):
|
|
119
|
-
char = jdl[iPos]
|
|
120
|
-
if char == ";" and not insideLiteral:
|
|
121
|
-
if key.strip():
|
|
122
|
-
result = assignValue(key, value, cfg)
|
|
123
|
-
if not result["OK"]:
|
|
124
|
-
return result
|
|
125
|
-
key = ""
|
|
126
|
-
value = ""
|
|
127
|
-
action = "key"
|
|
128
|
-
elif char == "[" and not insideLiteral:
|
|
129
|
-
key = key.strip()
|
|
130
|
-
if not key:
|
|
131
|
-
return S_ERROR("Invalid key in JDL")
|
|
132
|
-
if value.strip():
|
|
133
|
-
return S_ERROR(f"Key {key} seems to have a value and open a sub JDL at the same time")
|
|
134
|
-
result = loadJDLAsCFG(jdl[iPos:])
|
|
135
|
-
if not result["OK"]:
|
|
136
|
-
return result
|
|
137
|
-
subCfg, subPos = result["Value"]
|
|
138
|
-
cfg.createNewSection(key, contents=subCfg)
|
|
139
|
-
key = ""
|
|
140
|
-
value = ""
|
|
141
|
-
action = "key"
|
|
142
|
-
insideLiteral = False
|
|
143
|
-
iPos += subPos
|
|
144
|
-
elif char == "=" and not insideLiteral:
|
|
145
|
-
if action == "key":
|
|
146
|
-
action = "value"
|
|
147
|
-
insideLiteral = False
|
|
148
|
-
else:
|
|
149
|
-
value += char
|
|
150
|
-
elif char == "]" and not insideLiteral:
|
|
151
|
-
key = key.strip()
|
|
152
|
-
if len(key) > 0:
|
|
153
|
-
result = assignValue(key, value, cfg)
|
|
154
|
-
if not result["OK"]:
|
|
155
|
-
return result
|
|
156
|
-
return S_OK((cfg, iPos))
|
|
157
|
-
else:
|
|
158
|
-
if action == "key":
|
|
159
|
-
key += char
|
|
160
|
-
else:
|
|
161
|
-
value += char
|
|
162
|
-
if char == '"':
|
|
163
|
-
insideLiteral = not insideLiteral
|
|
164
|
-
iPos += 1
|
|
165
|
-
|
|
166
|
-
return S_OK((cfg, iPos))
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
def dumpCFGAsJDL(cfg, level=1, tab=" "):
|
|
170
|
-
indent = tab * level
|
|
171
|
-
contents = [f"{tab * (level - 1)}["]
|
|
172
|
-
sections = cfg.listSections()
|
|
173
|
-
|
|
174
|
-
for key in cfg:
|
|
175
|
-
if key in sections:
|
|
176
|
-
contents.append(f"{indent}{key} =")
|
|
177
|
-
contents.append(f"{dumpCFGAsJDL(cfg[key], level + 1, tab)};")
|
|
178
|
-
else:
|
|
179
|
-
val = List.fromChar(cfg[key])
|
|
180
|
-
# Some attributes are never lists
|
|
181
|
-
if len(val) < 2 or key in [ARGUMENTS, EXECUTABLE, STD_OUTPUT, STD_ERROR]:
|
|
182
|
-
value = cfg[key]
|
|
183
|
-
try:
|
|
184
|
-
try_value = float(value)
|
|
185
|
-
contents.append(f"{tab * level}{key} = {value};")
|
|
186
|
-
except Exception:
|
|
187
|
-
contents.append(f'{tab * level}{key} = "{value}";')
|
|
188
|
-
else:
|
|
189
|
-
contents.append(f"{indent}{key} =")
|
|
190
|
-
contents.append("%s{" % indent)
|
|
191
|
-
for iPos in range(len(val)):
|
|
192
|
-
try:
|
|
193
|
-
value = float(val[iPos])
|
|
194
|
-
except Exception:
|
|
195
|
-
val[iPos] = f'"{val[iPos]}"'
|
|
196
|
-
contents.append(",\n".join([f"{tab * (level + 1)}{value}" for value in val]))
|
|
197
|
-
contents.append("%s};" % indent)
|
|
198
|
-
contents.append(f"{tab * (level - 1)}]")
|
|
199
|
-
return "\n".join(contents)
|
|
200
|
-
|
|
201
7
|
|
|
202
8
|
def jdlToBaseJobDescriptionModel(classAd: ClassAd):
|
|
203
9
|
"""
|