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.
Files changed (54) hide show
  1. DIRAC/ConfigurationSystem/Client/Helpers/Registry.py +35 -7
  2. DIRAC/ConfigurationSystem/Client/LocalConfiguration.py +3 -0
  3. DIRAC/ConfigurationSystem/Client/VOMS2CSSynchronizer.py +8 -1
  4. DIRAC/Core/Security/DiracX.py +1 -1
  5. DIRAC/Core/Security/ProxyInfo.py +9 -5
  6. DIRAC/Core/Tornado/Client/ClientSelector.py +4 -1
  7. DIRAC/Core/Utilities/Extensions.py +10 -1
  8. DIRAC/Core/Utilities/Os.py +32 -1
  9. DIRAC/Core/scripts/dirac_apptainer_exec.py +10 -3
  10. DIRAC/Interfaces/API/Dirac.py +22 -13
  11. DIRAC/Interfaces/API/DiracAdmin.py +17 -5
  12. DIRAC/Interfaces/scripts/dirac_admin_allow_site.py +7 -1
  13. DIRAC/Interfaces/scripts/dirac_admin_ban_site.py +7 -1
  14. DIRAC/MonitoringSystem/Client/Types/WMSHistory.py +4 -0
  15. DIRAC/MonitoringSystem/Service/WebAppHandler.py +68 -1
  16. DIRAC/ResourceStatusSystem/Client/SiteStatus.py +4 -2
  17. DIRAC/ResourceStatusSystem/Utilities/CSHelpers.py +2 -31
  18. DIRAC/ResourceStatusSystem/scripts/dirac_rss_set_status.py +18 -4
  19. DIRAC/Resources/Computing/BatchSystems/Condor.py +23 -4
  20. DIRAC/TransformationSystem/Agent/TaskManagerAgentBase.py +10 -13
  21. DIRAC/TransformationSystem/Agent/TransformationAgent.py +22 -1
  22. DIRAC/TransformationSystem/Agent/TransformationCleaningAgent.py +15 -15
  23. DIRAC/TransformationSystem/Client/Utilities.py +6 -0
  24. DIRAC/WorkloadManagementSystem/Agent/JobCleaningAgent.py +11 -7
  25. DIRAC/WorkloadManagementSystem/Agent/StalledJobAgent.py +3 -26
  26. DIRAC/WorkloadManagementSystem/Agent/StatesAccountingAgent.py +41 -1
  27. DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_JobCleaningAgent.py +7 -9
  28. DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_StalledJobAgent.py +1 -2
  29. DIRAC/WorkloadManagementSystem/Client/JobMonitoringClient.py +4 -11
  30. DIRAC/WorkloadManagementSystem/Client/JobStatus.py +0 -59
  31. DIRAC/WorkloadManagementSystem/Client/SandboxStoreClient.py +25 -38
  32. DIRAC/WorkloadManagementSystem/Client/WMSClient.py +2 -3
  33. DIRAC/WorkloadManagementSystem/DB/JobDB.py +0 -58
  34. DIRAC/WorkloadManagementSystem/DB/SandboxMetadataDB.py +25 -37
  35. DIRAC/WorkloadManagementSystem/Executor/JobSanity.py +3 -3
  36. DIRAC/WorkloadManagementSystem/FutureClient/JobStateUpdateClient.py +2 -14
  37. DIRAC/WorkloadManagementSystem/Service/JobManagerHandler.py +27 -138
  38. DIRAC/WorkloadManagementSystem/Service/JobMonitoringHandler.py +0 -126
  39. DIRAC/WorkloadManagementSystem/Service/JobStateUpdateHandler.py +0 -16
  40. DIRAC/WorkloadManagementSystem/Service/SandboxStoreHandler.py +5 -51
  41. DIRAC/WorkloadManagementSystem/Utilities/JobParameters.py +1 -1
  42. DIRAC/WorkloadManagementSystem/Utilities/PilotWrapper.py +2 -0
  43. DIRAC/WorkloadManagementSystem/Utilities/jobAdministration.py +138 -0
  44. DIRAC/WorkloadManagementSystem/Utilities/test/Test_JobAdministration.py +28 -0
  45. {dirac-9.0.0a61.dist-info → dirac-9.0.0a63.dist-info}/METADATA +2 -1
  46. {dirac-9.0.0a61.dist-info → dirac-9.0.0a63.dist-info}/RECORD +50 -52
  47. {dirac-9.0.0a61.dist-info → dirac-9.0.0a63.dist-info}/entry_points.txt +0 -3
  48. DIRAC/TransformationSystem/scripts/dirac_transformation_archive.py +0 -30
  49. DIRAC/TransformationSystem/scripts/dirac_transformation_clean.py +0 -30
  50. DIRAC/TransformationSystem/scripts/dirac_transformation_remove_output.py +0 -30
  51. DIRAC/WorkloadManagementSystem/Utilities/test/Test_JobManager.py +0 -58
  52. {dirac-9.0.0a61.dist-info → dirac-9.0.0a63.dist-info}/WHEEL +0 -0
  53. {dirac-9.0.0a61.dist-info → dirac-9.0.0a63.dist-info}/licenses/LICENSE +0 -0
  54. {dirac-9.0.0a61.dist-info → dirac-9.0.0a63.dist-info}/top_level.txt +0 -0
@@ -1,24 +1,53 @@
1
- """ Helper for /Registry section
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 getVO() or gConfig.getValue(f"{gBaseRegistrySection}/Groups/{group}/VO", "")
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
- _cache_getProxyProvidersForDN = TTLCache(maxsize=1000, ttl=60)
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
- names.pop(0)
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:
@@ -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.getVOMSVOForGroup(group)
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", [])
@@ -1,10 +1,13 @@
1
1
  """
2
- Set of utilities to retrieve Information from proxy
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
- voName = Registry.getVOForGroup("NoneExistingGroup")
213
+
211
214
  ret = getProxyInfo(disableVOMS=True)
212
- if not ret["OK"]:
213
- return S_OK(voName)
214
- if "group" in ret["Value"]:
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)
@@ -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()}:/mnt"]) # bind current directory for dirac_container.sh
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
- cmd.extend(["--cwd", "/mnt"]) # set working directory to /mnt
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, "/mnt/dirac_container.sh"])
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)
@@ -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
- filterRes = JobStatus.filterJobStateTransition(jobIDs, filterState)
1454
- if not filterRes["OK"]:
1455
- return filterRes
1456
- filteredJobs.update(filterRes["Value"])
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
- # Remove any job IDs that can't change to the rescheduled state
1484
- filterRes = JobStatus.filterJobStateTransition(jobIDs, JobStatus.RESCHEDULED)
1485
- if not filterRes["OK"]:
1486
- return filterRes
1487
- jobIDsToReschedule = filterRes["Value"]
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
- filterRes = JobStatus.filterJobStateTransition(jobIDs, filterState)
1514
- if not filterRes["OK"]:
1515
- return filterRes
1516
- filteredJobs.update(filterRes["Value"])
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
- if not (result := self.sitestatus.setSiteStatus(site, "Active", comment))["OK"]:
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
- if not (result := self.sitestatus.setSiteStatus(site, "Banned", comment))["OK"]:
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
- """ SiteStatus helper
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, S_OK
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