DIRAC 9.0.0a61__py3-none-any.whl → 9.0.0a63__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/Registry.py +35 -7
- DIRAC/ConfigurationSystem/Client/LocalConfiguration.py +3 -0
- DIRAC/ConfigurationSystem/Client/VOMS2CSSynchronizer.py +8 -1
- DIRAC/Core/Security/DiracX.py +1 -1
- DIRAC/Core/Security/ProxyInfo.py +9 -5
- DIRAC/Core/Tornado/Client/ClientSelector.py +4 -1
- DIRAC/Core/Utilities/Extensions.py +10 -1
- DIRAC/Core/Utilities/Os.py +32 -1
- DIRAC/Core/scripts/dirac_apptainer_exec.py +10 -3
- DIRAC/Interfaces/API/Dirac.py +22 -13
- DIRAC/Interfaces/API/DiracAdmin.py +17 -5
- DIRAC/Interfaces/scripts/dirac_admin_allow_site.py +7 -1
- DIRAC/Interfaces/scripts/dirac_admin_ban_site.py +7 -1
- DIRAC/MonitoringSystem/Client/Types/WMSHistory.py +4 -0
- DIRAC/MonitoringSystem/Service/WebAppHandler.py +68 -1
- DIRAC/ResourceStatusSystem/Client/SiteStatus.py +4 -2
- DIRAC/ResourceStatusSystem/Utilities/CSHelpers.py +2 -31
- DIRAC/ResourceStatusSystem/scripts/dirac_rss_set_status.py +18 -4
- DIRAC/Resources/Computing/BatchSystems/Condor.py +23 -4
- DIRAC/TransformationSystem/Agent/TaskManagerAgentBase.py +10 -13
- DIRAC/TransformationSystem/Agent/TransformationAgent.py +22 -1
- DIRAC/TransformationSystem/Agent/TransformationCleaningAgent.py +15 -15
- DIRAC/TransformationSystem/Client/Utilities.py +6 -0
- DIRAC/WorkloadManagementSystem/Agent/JobCleaningAgent.py +11 -7
- DIRAC/WorkloadManagementSystem/Agent/StalledJobAgent.py +3 -26
- DIRAC/WorkloadManagementSystem/Agent/StatesAccountingAgent.py +41 -1
- DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_JobCleaningAgent.py +7 -9
- DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_StalledJobAgent.py +1 -2
- DIRAC/WorkloadManagementSystem/Client/JobMonitoringClient.py +4 -11
- DIRAC/WorkloadManagementSystem/Client/JobStatus.py +0 -59
- DIRAC/WorkloadManagementSystem/Client/SandboxStoreClient.py +25 -38
- DIRAC/WorkloadManagementSystem/Client/WMSClient.py +2 -3
- DIRAC/WorkloadManagementSystem/DB/JobDB.py +0 -58
- DIRAC/WorkloadManagementSystem/DB/SandboxMetadataDB.py +25 -37
- DIRAC/WorkloadManagementSystem/Executor/JobSanity.py +3 -3
- DIRAC/WorkloadManagementSystem/FutureClient/JobStateUpdateClient.py +2 -14
- DIRAC/WorkloadManagementSystem/Service/JobManagerHandler.py +27 -138
- DIRAC/WorkloadManagementSystem/Service/JobMonitoringHandler.py +0 -126
- DIRAC/WorkloadManagementSystem/Service/JobStateUpdateHandler.py +0 -16
- DIRAC/WorkloadManagementSystem/Service/SandboxStoreHandler.py +5 -51
- DIRAC/WorkloadManagementSystem/Utilities/JobParameters.py +1 -1
- DIRAC/WorkloadManagementSystem/Utilities/PilotWrapper.py +2 -0
- DIRAC/WorkloadManagementSystem/Utilities/jobAdministration.py +138 -0
- DIRAC/WorkloadManagementSystem/Utilities/test/Test_JobAdministration.py +28 -0
- {dirac-9.0.0a61.dist-info → dirac-9.0.0a63.dist-info}/METADATA +2 -1
- {dirac-9.0.0a61.dist-info → dirac-9.0.0a63.dist-info}/RECORD +50 -52
- {dirac-9.0.0a61.dist-info → dirac-9.0.0a63.dist-info}/entry_points.txt +0 -3
- DIRAC/TransformationSystem/scripts/dirac_transformation_archive.py +0 -30
- DIRAC/TransformationSystem/scripts/dirac_transformation_clean.py +0 -30
- DIRAC/TransformationSystem/scripts/dirac_transformation_remove_output.py +0 -30
- DIRAC/WorkloadManagementSystem/Utilities/test/Test_JobManager.py +0 -58
- {dirac-9.0.0a61.dist-info → dirac-9.0.0a63.dist-info}/WHEEL +0 -0
- {dirac-9.0.0a61.dist-info → dirac-9.0.0a63.dist-info}/licenses/LICENSE +0 -0
- {dirac-9.0.0a61.dist-info → dirac-9.0.0a63.dist-info}/top_level.txt +0 -0
|
@@ -1,24 +1,53 @@
|
|
|
1
|
-
"""
|
|
2
|
-
"""
|
|
1
|
+
"""Helper for /Registry section"""
|
|
3
2
|
|
|
4
3
|
import errno
|
|
4
|
+
import inspect
|
|
5
|
+
import sys
|
|
5
6
|
|
|
6
7
|
from threading import Lock
|
|
8
|
+
from collections.abc import Iterable
|
|
7
9
|
|
|
8
10
|
from cachetools import TTLCache, cached
|
|
11
|
+
from cachetools.keys import hashkey
|
|
9
12
|
|
|
10
13
|
|
|
14
|
+
from typing import Optional
|
|
15
|
+
from collections.abc import Iterable
|
|
16
|
+
|
|
11
17
|
from DIRAC import S_OK, S_ERROR
|
|
12
18
|
from DIRAC.ConfigurationSystem.Client.Config import gConfig
|
|
13
19
|
from DIRAC.ConfigurationSystem.Client.Helpers.CSGlobals import getVO
|
|
14
20
|
|
|
15
21
|
ID_DN_PREFIX = "/O=DIRAC/CN="
|
|
16
22
|
|
|
23
|
+
# 300 is the default CS refresh time
|
|
24
|
+
|
|
25
|
+
CACHE_REFRESH_TIME = 300
|
|
17
26
|
# pylint: disable=missing-docstring
|
|
18
27
|
|
|
19
28
|
gBaseRegistrySection = "/Registry"
|
|
20
29
|
|
|
21
30
|
|
|
31
|
+
def reset_all_caches():
|
|
32
|
+
"""This method is called to clear all caches.
|
|
33
|
+
It is necessary to reinitialize them after the central CS
|
|
34
|
+
has been loaded
|
|
35
|
+
"""
|
|
36
|
+
for cache in [
|
|
37
|
+
obj
|
|
38
|
+
for name, obj in inspect.getmembers(sys.modules[__name__])
|
|
39
|
+
if (inspect.isfunction(obj) and hasattr(obj, "cache_clear"))
|
|
40
|
+
]:
|
|
41
|
+
cache.cache_clear()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_username_for_dn_key(dn: str, userList: Optional[Iterable[str]] = None):
|
|
45
|
+
if userList:
|
|
46
|
+
return hashkey(dn, *sorted(userList))
|
|
47
|
+
return hashkey(dn)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@cached(TTLCache(maxsize=1000, ttl=CACHE_REFRESH_TIME), lock=Lock(), key=get_username_for_dn_key)
|
|
22
51
|
def getUsernameForDN(dn, usersList=None):
|
|
23
52
|
"""Find DIRAC user for DN
|
|
24
53
|
|
|
@@ -39,6 +68,7 @@ def getUsernameForDN(dn, usersList=None):
|
|
|
39
68
|
return S_ERROR(f"No username found for dn {dn}")
|
|
40
69
|
|
|
41
70
|
|
|
71
|
+
@cached(TTLCache(maxsize=1000, ttl=CACHE_REFRESH_TIME), lock=Lock())
|
|
42
72
|
def getDNForUsername(username):
|
|
43
73
|
"""Get user DN for user
|
|
44
74
|
|
|
@@ -419,6 +449,7 @@ def getBannedIPs():
|
|
|
419
449
|
return gConfig.getValue(f"{gBaseRegistrySection}/BannedIPs", [])
|
|
420
450
|
|
|
421
451
|
|
|
452
|
+
@cached(TTLCache(maxsize=1000, ttl=CACHE_REFRESH_TIME), lock=Lock())
|
|
422
453
|
def getVOForGroup(group):
|
|
423
454
|
"""Search VO name for group
|
|
424
455
|
|
|
@@ -426,7 +457,7 @@ def getVOForGroup(group):
|
|
|
426
457
|
|
|
427
458
|
:return: str
|
|
428
459
|
"""
|
|
429
|
-
return
|
|
460
|
+
return gConfig.getValue(f"{gBaseRegistrySection}/Groups/{group}/VO", "") or getVO()
|
|
430
461
|
|
|
431
462
|
|
|
432
463
|
def getIdPForGroup(group):
|
|
@@ -634,10 +665,7 @@ def getDNProperty(userDN, value, defaultValue=None):
|
|
|
634
665
|
return S_OK(defaultValue)
|
|
635
666
|
|
|
636
667
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
@cached(_cache_getProxyProvidersForDN, lock=Lock())
|
|
668
|
+
@cached(TTLCache(maxsize=1000, ttl=CACHE_REFRESH_TIME), lock=Lock())
|
|
641
669
|
def getProxyProvidersForDN(userDN):
|
|
642
670
|
"""Get proxy providers by user DN
|
|
643
671
|
|
|
@@ -568,6 +568,9 @@ class LocalConfiguration:
|
|
|
568
568
|
objLoader = ObjectLoader()
|
|
569
569
|
objLoader.reloadRootModules()
|
|
570
570
|
self.__initLogger(self.componentName, self.loggingSection, forceInit=True)
|
|
571
|
+
from DIRAC.ConfigurationSystem.Client.Helpers.Registry import reset_all_caches
|
|
572
|
+
|
|
573
|
+
reset_all_caches()
|
|
571
574
|
return res
|
|
572
575
|
|
|
573
576
|
def isCSEnabled(self):
|
|
@@ -87,8 +87,15 @@ def _getUserNameFromDN(dn, vo):
|
|
|
87
87
|
return nname
|
|
88
88
|
else:
|
|
89
89
|
robot = False
|
|
90
|
+
# only pop if the remains are sufficient (i.e. not just digits)
|
|
90
91
|
if names[0].lower().startswith("robot"):
|
|
91
|
-
|
|
92
|
+
nameok = False
|
|
93
|
+
if len(names) > 1:
|
|
94
|
+
for name in names[1:]:
|
|
95
|
+
if not name.isdigit():
|
|
96
|
+
nameok = True
|
|
97
|
+
if nameok:
|
|
98
|
+
names.pop(0)
|
|
92
99
|
robot = True
|
|
93
100
|
for name in list(names):
|
|
94
101
|
if name[0].isdigit() or "@" in name:
|
DIRAC/Core/Security/DiracX.py
CHANGED
|
@@ -47,7 +47,7 @@ RE_DIRACX_PEM = re.compile(rf"{PEM_BEGIN}\n(.*)\n{PEM_END}", re.MULTILINE | re.D
|
|
|
47
47
|
def addTokenToPEM(pemPath, group):
|
|
48
48
|
from DIRAC.Core.Base.Client import Client
|
|
49
49
|
|
|
50
|
-
vo = Registry.
|
|
50
|
+
vo = Registry.getVOForGroup(group)
|
|
51
51
|
if not vo:
|
|
52
52
|
gLogger.error(f"ERROR: Could not find VO for group {group}, DiracX will not work!")
|
|
53
53
|
disabledVOs = gConfig.getValue("/DiracX/DisabledVOs", [])
|
DIRAC/Core/Security/ProxyInfo.py
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
Set of utilities to retrieve Information from proxy
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
import base64
|
|
5
6
|
|
|
6
7
|
from DIRAC import S_ERROR, S_OK, gLogger
|
|
7
8
|
from DIRAC.ConfigurationSystem.Client.Helpers import Registry
|
|
9
|
+
from DIRAC.ConfigurationSystem.Client.Helpers.CSGlobals import getVO
|
|
10
|
+
|
|
8
11
|
from DIRAC.Core.Security import Locations
|
|
9
12
|
from DIRAC.Core.Security.DiracX import diracxTokenFromPEM
|
|
10
13
|
from DIRAC.Core.Security.VOMS import VOMS
|
|
@@ -207,10 +210,11 @@ def getVOfromProxyGroup():
|
|
|
207
210
|
"""
|
|
208
211
|
Return the VO associated to the group in the proxy
|
|
209
212
|
"""
|
|
210
|
-
|
|
213
|
+
|
|
211
214
|
ret = getProxyInfo(disableVOMS=True)
|
|
212
|
-
if not ret["OK"]:
|
|
213
|
-
|
|
214
|
-
|
|
215
|
+
if not ret["OK"] or "group" not in ret["Value"]:
|
|
216
|
+
voName = getVO()
|
|
217
|
+
else:
|
|
215
218
|
voName = Registry.getVOForGroup(ret["Value"]["group"])
|
|
219
|
+
|
|
216
220
|
return S_OK(voName)
|
|
@@ -17,7 +17,6 @@ from DIRAC.Core.DISET.RPCClient import RPCClient
|
|
|
17
17
|
from DIRAC.Core.DISET.TransferClient import TransferClient
|
|
18
18
|
from DIRAC.Core.Tornado.Client.TornadoClient import TornadoClient
|
|
19
19
|
|
|
20
|
-
|
|
21
20
|
sLog = gLogger.getSubLogger(__name__)
|
|
22
21
|
|
|
23
22
|
|
|
@@ -82,6 +81,10 @@ def ClientSelector(disetClient, *args, **kwargs): # We use same interface as RP
|
|
|
82
81
|
rpc = tornadoClient(*args, **kwargs)
|
|
83
82
|
else:
|
|
84
83
|
rpc = disetClient(*args, **kwargs)
|
|
84
|
+
except NotImplementedError as e:
|
|
85
|
+
# We catch explicitly NotImplementedError to avoid just printing "there's an error"
|
|
86
|
+
# If we mis-configured the CS for legacy adapted services, we MUST have an error.
|
|
87
|
+
raise e
|
|
85
88
|
except Exception as e: # pylint: disable=broad-except
|
|
86
89
|
# If anything went wrong in the resolution, we return default RPCClient
|
|
87
90
|
# So the behaviour is exactly the same as before implementation of Tornado
|
|
@@ -73,6 +73,15 @@ def findServices(modules):
|
|
|
73
73
|
return findModules(modules, "Service", "*Handler")
|
|
74
74
|
|
|
75
75
|
|
|
76
|
+
def findFutureServices(modules):
|
|
77
|
+
"""Find the legacy adapted services for one or more DIRAC extension(s)
|
|
78
|
+
|
|
79
|
+
:param list/str/module module: One or more Python modules or Python module names
|
|
80
|
+
:returns: list of tuples of the form (SystemName, ServiceName)
|
|
81
|
+
"""
|
|
82
|
+
return findModules(modules, "FutureClient")
|
|
83
|
+
|
|
84
|
+
|
|
76
85
|
@iterateThenSort
|
|
77
86
|
def findDatabases(module):
|
|
78
87
|
"""Find the DB SQL schema defintions for one or more DIRAC extension(s)
|
|
@@ -182,7 +191,7 @@ def parseArgs():
|
|
|
182
191
|
parser = argparse.ArgumentParser()
|
|
183
192
|
subparsers = parser.add_subparsers(required=True, dest="function")
|
|
184
193
|
defaultExtensions = extensionsByPriority()
|
|
185
|
-
for func in [findSystems, findAgents, findExecutors, findServices, findDatabases]:
|
|
194
|
+
for func in [findSystems, findAgents, findExecutors, findServices, findDatabases, findFutureServices]:
|
|
186
195
|
subparser = subparsers.add_parser(func.__name__)
|
|
187
196
|
subparser.add_argument("--extensions", nargs="+", default=defaultExtensions)
|
|
188
197
|
subparser.set_defaults(func=func)
|
DIRAC/Core/Utilities/Os.py
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
by default on Error they return None
|
|
4
4
|
"""
|
|
5
5
|
import os
|
|
6
|
+
import threading
|
|
6
7
|
|
|
7
8
|
import DIRAC
|
|
8
|
-
from DIRAC.Core.Utilities.Subprocess import shellCall, systemCall
|
|
9
9
|
from DIRAC.Core.Utilities import List
|
|
10
|
+
from DIRAC.Core.Utilities.Subprocess import shellCall, systemCall
|
|
10
11
|
|
|
11
12
|
DEBUG = 0
|
|
12
13
|
|
|
@@ -128,3 +129,33 @@ def sourceEnv(timeout, cmdTuple, inputEnv=None):
|
|
|
128
129
|
result["stderr"] = stderr
|
|
129
130
|
|
|
130
131
|
return result
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def safe_listdir(directory, timeout=60):
|
|
135
|
+
"""This is a "safe" list directory,
|
|
136
|
+
for lazily-loaded File Systems like CVMFS.
|
|
137
|
+
There's by default a 60 seconds timeout.
|
|
138
|
+
|
|
139
|
+
.. warning::
|
|
140
|
+
There is no distinction between an empty directory, and a non existent one.
|
|
141
|
+
It will return `[]` in both cases.
|
|
142
|
+
|
|
143
|
+
:param str directory: directory to list
|
|
144
|
+
:param int timeout: optional timeout, in seconds. Defaults to 60.
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
def listdir(directory):
|
|
148
|
+
try:
|
|
149
|
+
return os.listdir(directory)
|
|
150
|
+
except FileNotFoundError:
|
|
151
|
+
print(f"{directory} not found")
|
|
152
|
+
return []
|
|
153
|
+
|
|
154
|
+
contents = []
|
|
155
|
+
t = threading.Thread(target=lambda: contents.extend(listdir(directory)))
|
|
156
|
+
t.daemon = True # don't delay program's exit
|
|
157
|
+
t.start()
|
|
158
|
+
t.join(timeout)
|
|
159
|
+
if t.is_alive():
|
|
160
|
+
return None # timeout
|
|
161
|
+
return contents
|
|
@@ -10,6 +10,7 @@ import DIRAC
|
|
|
10
10
|
from DIRAC import S_ERROR, gConfig, gLogger
|
|
11
11
|
from DIRAC.Core.Base.Script import Script
|
|
12
12
|
from DIRAC.Core.Security.Locations import getCAsLocation, getProxyLocation, getVOMSLocation
|
|
13
|
+
from DIRAC.Core.Utilities.Os import safe_listdir
|
|
13
14
|
from DIRAC.Core.Utilities.Subprocess import systemCall
|
|
14
15
|
|
|
15
16
|
|
|
@@ -73,7 +74,7 @@ def main():
|
|
|
73
74
|
cmd.extend(["--contain"]) # use minimal /dev and empty other directories (e.g. /tmp and $HOME)
|
|
74
75
|
cmd.extend(["--ipc"]) # run container in a new IPC namespace
|
|
75
76
|
cmd.extend(["--pid"]) # run container in a new PID namespace
|
|
76
|
-
cmd.extend(["--bind", f"{os.getcwd()}
|
|
77
|
+
cmd.extend(["--bind", f"{os.getcwd()}"]) # bind current directory for dirac_container.sh
|
|
77
78
|
if proxy_location:
|
|
78
79
|
cmd.extend(["--bind", f"{proxy_location}:/etc/proxy"]) # bind proxy file
|
|
79
80
|
cmd.extend(["--bind", f"{getCAsLocation()}:/etc/grid-security/certificates"]) # X509_CERT_DIR
|
|
@@ -83,12 +84,18 @@ def main():
|
|
|
83
84
|
cmd.extend(["--bind", f"{vomses_location}:/etc/grid-security/vomses"]) # X509_VOMSES
|
|
84
85
|
cmd.extend(["--bind", "{0}:{0}:ro".format(etc_dir)]) # etc dir for dirac.cfg
|
|
85
86
|
cmd.extend(["--bind", "{0}:{0}:ro".format(os.path.join(os.path.realpath(sys.base_prefix)))]) # code dir
|
|
86
|
-
|
|
87
|
+
# here bind optional paths
|
|
88
|
+
for bind_path in gConfig.getValue("/Resources/Computing/Singularity/BindPaths", []):
|
|
89
|
+
if safe_listdir(bind_path):
|
|
90
|
+
cmd.extend(["--bind", f"{bind_path}:{bind_path}"])
|
|
91
|
+
else:
|
|
92
|
+
gLogger.warning(f"Bind path {bind_path} does not exist, skipping")
|
|
93
|
+
cmd.extend(["--cwd", f"{os.getcwd()}"]) # set working directory
|
|
87
94
|
|
|
88
95
|
rootImage = user_image or gConfig.getValue("/Resources/Computing/Singularity/ContainerRoot") or CONTAINER_DEFROOT
|
|
89
96
|
|
|
90
97
|
if os.path.isdir(rootImage) or os.path.isfile(rootImage):
|
|
91
|
-
cmd.extend([rootImage, "/
|
|
98
|
+
cmd.extend([rootImage, f"{os.getcwd()}/dirac_container.sh"])
|
|
92
99
|
else:
|
|
93
100
|
# if we are here is because there's no image, or it is not accessible (e.g. not on CVMFS)
|
|
94
101
|
gLogger.error("Apptainer image to exec not found: ", rootImage)
|
DIRAC/Interfaces/API/Dirac.py
CHANGED
|
@@ -46,6 +46,7 @@ from DIRAC.WorkloadManagementSystem.Client import JobStatus
|
|
|
46
46
|
from DIRAC.WorkloadManagementSystem.Client.JobMonitoringClient import JobMonitoringClient
|
|
47
47
|
from DIRAC.WorkloadManagementSystem.Client.SandboxStoreClient import SandboxStoreClient
|
|
48
48
|
from DIRAC.WorkloadManagementSystem.Client.WMSClient import WMSClient
|
|
49
|
+
from DIRAC.WorkloadManagementSystem.Utilities.jobAdministration import _filterJobStateTransition
|
|
49
50
|
|
|
50
51
|
|
|
51
52
|
def parseArguments(args):
|
|
@@ -1450,10 +1451,13 @@ class Dirac(API):
|
|
|
1450
1451
|
# Remove any job IDs that can't change to the Killed or Deleted states
|
|
1451
1452
|
filteredJobs = set()
|
|
1452
1453
|
for filterState in (JobStatus.KILLED, JobStatus.DELETED):
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1454
|
+
# get a dictionary of jobID:status
|
|
1455
|
+
res = JobMonitoringClient().getJobsStatus(jobIDs)
|
|
1456
|
+
if not res["OK"]:
|
|
1457
|
+
return res
|
|
1458
|
+
js = {k: v["Status"] for k, v in res["Value"].items()}
|
|
1459
|
+
# then filter
|
|
1460
|
+
filteredJobs.update(_filterJobStateTransition(js, filterState))
|
|
1457
1461
|
|
|
1458
1462
|
return WMSClient(useCertificates=self.useCertificates).deleteJob(list(filteredJobs))
|
|
1459
1463
|
|
|
@@ -1480,11 +1484,13 @@ class Dirac(API):
|
|
|
1480
1484
|
return ret
|
|
1481
1485
|
jobIDs = ret["Value"]
|
|
1482
1486
|
|
|
1483
|
-
#
|
|
1484
|
-
|
|
1485
|
-
if not
|
|
1486
|
-
return
|
|
1487
|
-
|
|
1487
|
+
# get a dictionary of jobID:status
|
|
1488
|
+
res = JobMonitoringClient().getJobsStatus(jobIDs)
|
|
1489
|
+
if not res["OK"]:
|
|
1490
|
+
return res
|
|
1491
|
+
js = {k: v["Status"] for k, v in res["Value"].items()}
|
|
1492
|
+
# then filter
|
|
1493
|
+
jobIDsToReschedule = _filterJobStateTransition(js, JobStatus.RESCHEDULED)
|
|
1488
1494
|
|
|
1489
1495
|
return WMSClient(useCertificates=self.useCertificates).rescheduleJob(jobIDsToReschedule)
|
|
1490
1496
|
|
|
@@ -1510,10 +1516,13 @@ class Dirac(API):
|
|
|
1510
1516
|
# Remove any job IDs that can't change to the Killed or Deleted states
|
|
1511
1517
|
filteredJobs = set()
|
|
1512
1518
|
for filterState in (JobStatus.KILLED, JobStatus.DELETED):
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1519
|
+
# get a dictionary of jobID:status
|
|
1520
|
+
res = JobMonitoringClient().getJobsStatus(jobIDs)
|
|
1521
|
+
if not res["OK"]:
|
|
1522
|
+
return res
|
|
1523
|
+
js = {k: v["Status"] for k, v in res["Value"].items()}
|
|
1524
|
+
# then filter
|
|
1525
|
+
filteredJobs.update(_filterJobStateTransition(js, filterState))
|
|
1517
1526
|
|
|
1518
1527
|
return WMSClient(useCertificates=self.useCertificates).killJob(list(filteredJobs))
|
|
1519
1528
|
|
|
@@ -4,7 +4,9 @@ All administrative functionality is exposed through the DIRAC Admin API. Exampl
|
|
|
4
4
|
site banning and unbanning, WMS proxy uploading etc.
|
|
5
5
|
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import os
|
|
9
|
+
from datetime import datetime, timedelta
|
|
8
10
|
|
|
9
11
|
from DIRAC import S_ERROR, S_OK, gConfig, gLogger
|
|
10
12
|
from DIRAC.ConfigurationSystem.Client.CSAPI import CSAPI
|
|
@@ -150,7 +152,7 @@ class DiracAdmin(API):
|
|
|
150
152
|
return result
|
|
151
153
|
|
|
152
154
|
#############################################################################
|
|
153
|
-
def allowSite(self, site, comment, printOutput=False):
|
|
155
|
+
def allowSite(self, site, comment, printOutput=False, days=1):
|
|
154
156
|
"""Adds the site to the site mask. The site must be a valid DIRAC site name
|
|
155
157
|
|
|
156
158
|
Example usage:
|
|
@@ -174,7 +176,13 @@ class DiracAdmin(API):
|
|
|
174
176
|
gLogger.notice(f"Site {site} is already Active")
|
|
175
177
|
return S_OK(f"Site {site} is already Active")
|
|
176
178
|
|
|
177
|
-
|
|
179
|
+
tokenLifetime = int(days)
|
|
180
|
+
if tokenLifetime <= 0:
|
|
181
|
+
tokenExpiration = datetime.max
|
|
182
|
+
else:
|
|
183
|
+
tokenExpiration = datetime.utcnow().replace(microsecond=0) + timedelta(days=tokenLifetime)
|
|
184
|
+
|
|
185
|
+
if not (result := self.sitestatus.setSiteStatus(site, "Active", comment, expiry=tokenExpiration))["OK"]:
|
|
178
186
|
return result
|
|
179
187
|
|
|
180
188
|
if printOutput:
|
|
@@ -223,7 +231,7 @@ class DiracAdmin(API):
|
|
|
223
231
|
return S_OK()
|
|
224
232
|
|
|
225
233
|
#############################################################################
|
|
226
|
-
def banSite(self, site, comment, printOutput=False):
|
|
234
|
+
def banSite(self, site, comment, printOutput=False, days=1):
|
|
227
235
|
"""Removes the site from the site mask.
|
|
228
236
|
|
|
229
237
|
Example usage:
|
|
@@ -236,7 +244,6 @@ class DiracAdmin(API):
|
|
|
236
244
|
"""
|
|
237
245
|
if not (result := self._checkSiteIsValid(site))["OK"]:
|
|
238
246
|
return result
|
|
239
|
-
|
|
240
247
|
mask = self.getSiteMask(status="Banned")
|
|
241
248
|
if not mask["OK"]:
|
|
242
249
|
return mask
|
|
@@ -246,7 +253,12 @@ class DiracAdmin(API):
|
|
|
246
253
|
gLogger.notice(f"Site {site} is already Banned")
|
|
247
254
|
return S_OK(f"Site {site} is already Banned")
|
|
248
255
|
|
|
249
|
-
|
|
256
|
+
tokenLifetime = int(days)
|
|
257
|
+
if tokenLifetime <= 0:
|
|
258
|
+
tokenExpiration = datetime.max
|
|
259
|
+
else:
|
|
260
|
+
tokenExpiration = datetime.utcnow().replace(microsecond=0) + timedelta(days=tokenLifetime)
|
|
261
|
+
if not (result := self.sitestatus.setSiteStatus(site, "Banned", comment, expiry=tokenExpiration))["OK"]:
|
|
250
262
|
return result
|
|
251
263
|
|
|
252
264
|
if printOutput:
|
|
@@ -13,6 +13,9 @@ from DIRAC.Core.Base.Script import Script
|
|
|
13
13
|
@Script()
|
|
14
14
|
def main():
|
|
15
15
|
Script.registerSwitch("E:", "email=", "Boolean True/False (True by default)")
|
|
16
|
+
Script.registerSwitch(
|
|
17
|
+
"", "days=", "Number of days the token is valid for. Default is 1 day. 0 or less days denotes forever."
|
|
18
|
+
)
|
|
16
19
|
# Registering arguments will automatically add their description to the help menu
|
|
17
20
|
Script.registerArgument("Site: Name of the Site")
|
|
18
21
|
Script.registerArgument("Comment: Reason of the action")
|
|
@@ -32,9 +35,12 @@ def main():
|
|
|
32
35
|
Script.showHelp()
|
|
33
36
|
|
|
34
37
|
email = True
|
|
38
|
+
days = 1
|
|
35
39
|
for switch in Script.getUnprocessedSwitches():
|
|
36
40
|
if switch[0] == "email":
|
|
37
41
|
email = getBoolean(switch[1])
|
|
42
|
+
if switch[0] == "days":
|
|
43
|
+
days = int(switch[1])
|
|
38
44
|
|
|
39
45
|
diracAdmin = DiracAdmin()
|
|
40
46
|
exitCode = 0
|
|
@@ -42,7 +48,7 @@ def main():
|
|
|
42
48
|
|
|
43
49
|
# parseCommandLine show help when mandatory arguments are not specified or incorrect argument
|
|
44
50
|
site, comment = Script.getPositionalArgs(group=True)
|
|
45
|
-
result = diracAdmin.allowSite(site, comment, printOutput=True)
|
|
51
|
+
result = diracAdmin.allowSite(site, comment, printOutput=True, days=days)
|
|
46
52
|
if not result["OK"]:
|
|
47
53
|
errorList.append((site, result["Message"]))
|
|
48
54
|
exitCode = 2
|
|
@@ -13,6 +13,9 @@ from DIRAC.Core.Base.Script import Script
|
|
|
13
13
|
@Script()
|
|
14
14
|
def main():
|
|
15
15
|
Script.registerSwitch("E:", "email=", "Boolean True/False (True by default)")
|
|
16
|
+
Script.registerSwitch(
|
|
17
|
+
"", "days=", "Number of days the token is valid for. Default is 1 day. 0 or less days denotes forever."
|
|
18
|
+
)
|
|
16
19
|
# Registering arguments will automatically add their description to the help menu
|
|
17
20
|
Script.registerArgument("Site: Name of the Site")
|
|
18
21
|
Script.registerArgument("Comment: Reason of the action")
|
|
@@ -32,9 +35,12 @@ def main():
|
|
|
32
35
|
Script.showHelp()
|
|
33
36
|
|
|
34
37
|
email = True
|
|
38
|
+
days = 1
|
|
35
39
|
for switch in Script.getUnprocessedSwitches():
|
|
36
40
|
if switch[0] == "email":
|
|
37
41
|
email = getBoolean(switch[1])
|
|
42
|
+
if switch[0] == "days":
|
|
43
|
+
days = int(switch[1])
|
|
38
44
|
|
|
39
45
|
diracAdmin = DiracAdmin()
|
|
40
46
|
exitCode = 0
|
|
@@ -50,7 +56,7 @@ def main():
|
|
|
50
56
|
|
|
51
57
|
# parseCommandLine show help when mandatory arguments are not specified or incorrect argument
|
|
52
58
|
site, comment = Script.getPositionalArgs(group=True)
|
|
53
|
-
result = diracAdmin.banSite(site, comment, printOutput=True)
|
|
59
|
+
result = diracAdmin.banSite(site, comment, printOutput=True, days=days)
|
|
54
60
|
if not result["OK"]:
|
|
55
61
|
errorList.append((site, result["Message"]))
|
|
56
62
|
exitCode = 2
|
|
@@ -30,6 +30,8 @@ class WMSHistory(BaseType):
|
|
|
30
30
|
"MinorStatus",
|
|
31
31
|
"ApplicationStatus",
|
|
32
32
|
"JobSplitType",
|
|
33
|
+
"Tier",
|
|
34
|
+
"Type",
|
|
33
35
|
]
|
|
34
36
|
|
|
35
37
|
self.monitoringFields = ["Jobs", "Reschedules"]
|
|
@@ -46,6 +48,8 @@ class WMSHistory(BaseType):
|
|
|
46
48
|
"User": {"type": "keyword"},
|
|
47
49
|
"JobGroup": {"type": "keyword"},
|
|
48
50
|
"UserGroup": {"type": "keyword"},
|
|
51
|
+
"Tier": {"type": "keyword"},
|
|
52
|
+
"Type": {"type": "keyword"},
|
|
49
53
|
}
|
|
50
54
|
)
|
|
51
55
|
# {'timestamp': {'type': 'date'}} will be added for all monitoring types
|
|
@@ -6,7 +6,6 @@ from DIRAC import S_ERROR, S_OK
|
|
|
6
6
|
from DIRAC.ConfigurationSystem.Client.Helpers.Operations import Operations
|
|
7
7
|
from DIRAC.ConfigurationSystem.Client.Helpers.Resources import getSites
|
|
8
8
|
from DIRAC.Core.DISET.RequestHandler import RequestHandler
|
|
9
|
-
from DIRAC.Core.Utilities.JEncode import strToIntDict
|
|
10
9
|
from DIRAC.Core.Utilities.ObjectLoader import ObjectLoader
|
|
11
10
|
from DIRAC.RequestManagementSystem.Client.Operation import Operation
|
|
12
11
|
from DIRAC.RequestManagementSystem.Client.Request import Request
|
|
@@ -334,6 +333,74 @@ class WebAppHandler(RequestHandler):
|
|
|
334
333
|
|
|
335
334
|
return S_OK(resultDict)
|
|
336
335
|
|
|
336
|
+
types_getApplicationStates = []
|
|
337
|
+
|
|
338
|
+
@classmethod
|
|
339
|
+
def export_getApplicationStates(cls, condDict=None, older=None, newer=None):
|
|
340
|
+
"""Return Distinct Values of ApplicationStatus job Attribute in WMS"""
|
|
341
|
+
return cls.jobDB.getDistinctJobAttributes("ApplicationStatus", condDict, older, newer)
|
|
342
|
+
|
|
343
|
+
types_getJobTypes = []
|
|
344
|
+
|
|
345
|
+
@classmethod
|
|
346
|
+
def export_getJobTypes(cls, condDict=None, older=None, newer=None):
|
|
347
|
+
"""Return Distinct Values of JobType job Attribute in WMS"""
|
|
348
|
+
return cls.jobDB.getDistinctJobAttributes("JobType", condDict, older, newer)
|
|
349
|
+
|
|
350
|
+
types_getOwners = []
|
|
351
|
+
|
|
352
|
+
@classmethod
|
|
353
|
+
def export_getOwners(cls, condDict=None, older=None, newer=None):
|
|
354
|
+
"""
|
|
355
|
+
Return Distinct Values of Owner job Attribute in WMS
|
|
356
|
+
"""
|
|
357
|
+
return cls.jobDB.getDistinctJobAttributes("Owner", condDict, older, newer)
|
|
358
|
+
|
|
359
|
+
types_getOwnerGroup = []
|
|
360
|
+
|
|
361
|
+
@classmethod
|
|
362
|
+
def export_getOwnerGroup(cls):
|
|
363
|
+
"""
|
|
364
|
+
Return Distinct Values of OwnerGroup from the JobDB
|
|
365
|
+
"""
|
|
366
|
+
return cls.jobDB.getDistinctJobAttributes("OwnerGroup")
|
|
367
|
+
|
|
368
|
+
types_getJobGroups = []
|
|
369
|
+
|
|
370
|
+
@classmethod
|
|
371
|
+
def export_getJobGroups(cls, condDict=None, older=None, cutDate=None):
|
|
372
|
+
"""
|
|
373
|
+
Return Distinct Values of ProductionId job Attribute in WMS
|
|
374
|
+
"""
|
|
375
|
+
return cls.jobDB.getDistinctJobAttributes("JobGroup", condDict, older, newer=cutDate)
|
|
376
|
+
|
|
377
|
+
types_getSites = []
|
|
378
|
+
|
|
379
|
+
@classmethod
|
|
380
|
+
def export_getSites(cls, condDict=None, older=None, newer=None):
|
|
381
|
+
"""
|
|
382
|
+
Return Distinct Values of Site job Attribute in WMS
|
|
383
|
+
"""
|
|
384
|
+
return cls.jobDB.getDistinctJobAttributes("Site", condDict, older, newer)
|
|
385
|
+
|
|
386
|
+
types_getStates = []
|
|
387
|
+
|
|
388
|
+
@classmethod
|
|
389
|
+
def export_getStates(cls, condDict=None, older=None, newer=None):
|
|
390
|
+
"""
|
|
391
|
+
Return Distinct Values of Status job Attribute in WMS
|
|
392
|
+
"""
|
|
393
|
+
return cls.jobDB.getDistinctJobAttributes("Status", condDict, older, newer)
|
|
394
|
+
|
|
395
|
+
types_getMinorStates = []
|
|
396
|
+
|
|
397
|
+
@classmethod
|
|
398
|
+
def export_getMinorStates(cls, condDict=None, older=None, newer=None):
|
|
399
|
+
"""
|
|
400
|
+
Return Distinct Values of Minor Status job Attribute in WMS
|
|
401
|
+
"""
|
|
402
|
+
return cls.jobDB.getDistinctJobAttributes("MinorStatus", condDict, older, newer)
|
|
403
|
+
|
|
337
404
|
##############################################################################
|
|
338
405
|
# Transformations
|
|
339
406
|
##############################################################################
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""SiteStatus helper
|
|
2
2
|
|
|
3
3
|
Module that acts as a helper for knowing the status of a site.
|
|
4
4
|
It takes care of switching between the CS and the RSS.
|
|
@@ -195,7 +195,7 @@ class SiteStatus(metaclass=DIRACSingleton):
|
|
|
195
195
|
|
|
196
196
|
return S_OK(siteList)
|
|
197
197
|
|
|
198
|
-
def setSiteStatus(self, site, status, comment="No comment"):
|
|
198
|
+
def setSiteStatus(self, site, status, comment="No comment", expiry=None):
|
|
199
199
|
"""
|
|
200
200
|
Set the status of a site in the 'SiteStatus' table of RSS
|
|
201
201
|
|
|
@@ -231,6 +231,8 @@ class SiteStatus(metaclass=DIRACSingleton):
|
|
|
231
231
|
return S_ERROR(f"Unable to get user proxy info {result['Message']} ")
|
|
232
232
|
|
|
233
233
|
tokenExpiration = datetime.utcnow() + timedelta(days=1)
|
|
234
|
+
if expiry:
|
|
235
|
+
tokenExpiration = expiry
|
|
234
236
|
|
|
235
237
|
self.rssCache.acquireLock()
|
|
236
238
|
try:
|
|
@@ -3,11 +3,10 @@ Module containing functions interacting with the CS and useful for the RSS
|
|
|
3
3
|
modules.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from DIRAC import gConfig, gLogger
|
|
6
|
+
from DIRAC import S_OK, gConfig, gLogger
|
|
7
|
+
from DIRAC.ConfigurationSystem.Client.Helpers.Resources import getQueues
|
|
7
8
|
from DIRAC.Core.Utilities.SiteSEMapping import getSEParameters
|
|
8
|
-
from DIRAC.ConfigurationSystem.Client.Helpers.Resources import getQueues, getCESiteMapping
|
|
9
9
|
from DIRAC.DataManagementSystem.Utilities.DMSHelpers import DMSHelpers
|
|
10
|
-
from DIRAC.ResourceStatusSystem.Utilities import Utils
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
def warmUp():
|
|
@@ -19,28 +18,6 @@ def warmUp():
|
|
|
19
18
|
gRefresher.refreshConfigurationIfNeeded()
|
|
20
19
|
|
|
21
20
|
|
|
22
|
-
def getResources():
|
|
23
|
-
"""
|
|
24
|
-
Gets all resources
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
resources = DMSHelpers().getStorageElements()
|
|
28
|
-
|
|
29
|
-
fts = getFTS()
|
|
30
|
-
if fts["OK"]:
|
|
31
|
-
resources = resources + fts["Value"]
|
|
32
|
-
|
|
33
|
-
fc = getFileCatalogs()
|
|
34
|
-
if fc["OK"]:
|
|
35
|
-
resources = resources + fc["Value"]
|
|
36
|
-
|
|
37
|
-
res = getCESiteMapping()
|
|
38
|
-
if res["OK"]:
|
|
39
|
-
resources = resources + list(res["Value"])
|
|
40
|
-
|
|
41
|
-
return S_OK(resources)
|
|
42
|
-
|
|
43
|
-
|
|
44
21
|
def getStorageElementEndpoint(seName):
|
|
45
22
|
"""Get endpoints of a StorageElement
|
|
46
23
|
|
|
@@ -86,12 +63,6 @@ def getFTS():
|
|
|
86
63
|
return S_OK([])
|
|
87
64
|
|
|
88
65
|
|
|
89
|
-
def getSpaceTokenEndpoints():
|
|
90
|
-
"""Get Space Token Endpoints"""
|
|
91
|
-
|
|
92
|
-
return Utils.getCSTree("Shares/Disk")
|
|
93
|
-
|
|
94
|
-
|
|
95
66
|
def getFileCatalogs():
|
|
96
67
|
"""
|
|
97
68
|
Gets all storage elements from /Resources/FileCatalogs
|