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
DIRAC/Core/Utilities/List.py
CHANGED
|
@@ -1,127 +1 @@
|
|
|
1
|
-
|
|
2
|
-
By default on Error they return None.
|
|
3
|
-
"""
|
|
4
|
-
import random
|
|
5
|
-
import sys
|
|
6
|
-
from typing import Any, TypeVar
|
|
7
|
-
from collections.abc import Iterable
|
|
8
|
-
|
|
9
|
-
T = TypeVar("T")
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def uniqueElements(aList: list) -> list:
|
|
13
|
-
"""Utility to retrieve list of unique elements in a list (order is kept)."""
|
|
14
|
-
|
|
15
|
-
# Use dict.fromkeys instead of set ensure the order is preserved
|
|
16
|
-
return list(dict.fromkeys(aList))
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def appendUnique(aList: list, anObject: Any):
|
|
20
|
-
"""Append to list if object does not exist.
|
|
21
|
-
|
|
22
|
-
:param aList: list of elements
|
|
23
|
-
:param anObject: object you want to append
|
|
24
|
-
"""
|
|
25
|
-
if anObject not in aList:
|
|
26
|
-
aList.append(anObject)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def fromChar(inputString: str, sepChar: str = ","):
|
|
30
|
-
"""Generates a list splitting a string by the required character(s)
|
|
31
|
-
resulting string items are stripped and empty items are removed.
|
|
32
|
-
|
|
33
|
-
:param inputString: list serialised to string
|
|
34
|
-
:param sepChar: separator
|
|
35
|
-
:return: list of strings or None if sepChar has a wrong type
|
|
36
|
-
"""
|
|
37
|
-
# to prevent getting an empty String as argument
|
|
38
|
-
if not (isinstance(inputString, str) and isinstance(sepChar, str) and sepChar):
|
|
39
|
-
return None
|
|
40
|
-
return [fieldString.strip() for fieldString in inputString.split(sepChar) if len(fieldString.strip()) > 0]
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def randomize(aList: Iterable[T]) -> list[T]:
|
|
44
|
-
"""Return a randomly sorted list.
|
|
45
|
-
|
|
46
|
-
:param aList: list to permute
|
|
47
|
-
"""
|
|
48
|
-
tmpList = list(aList)
|
|
49
|
-
random.shuffle(tmpList)
|
|
50
|
-
return tmpList
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def pop(aList, popElement):
|
|
54
|
-
"""Pop the first element equal to popElement from the list.
|
|
55
|
-
|
|
56
|
-
:param aList: list
|
|
57
|
-
:type aList: python:list
|
|
58
|
-
:param popElement: element to pop
|
|
59
|
-
"""
|
|
60
|
-
if popElement in aList:
|
|
61
|
-
return aList.pop(aList.index(popElement))
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def stringListToString(aList: list) -> str:
|
|
65
|
-
"""This function is used for making MySQL queries with a list of string elements.
|
|
66
|
-
|
|
67
|
-
:param aList: list to be serialized to string for making queries
|
|
68
|
-
"""
|
|
69
|
-
return ",".join(f"'{x}'" for x in aList)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def intListToString(aList: list) -> str:
|
|
73
|
-
"""This function is used for making MySQL queries with a list of int elements.
|
|
74
|
-
|
|
75
|
-
:param aList: list to be serialized to string for making queries
|
|
76
|
-
"""
|
|
77
|
-
return ",".join(str(x) for x in aList)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def getChunk(aList: list, chunkSize: int):
|
|
81
|
-
"""Generator yielding chunk from a list of a size chunkSize.
|
|
82
|
-
|
|
83
|
-
:param aList: list to be splitted
|
|
84
|
-
:param chunkSize: lenght of one chunk
|
|
85
|
-
:raise: StopIteration
|
|
86
|
-
|
|
87
|
-
Usage:
|
|
88
|
-
|
|
89
|
-
>>> for chunk in getChunk( aList, chunkSize=10):
|
|
90
|
-
process( chunk )
|
|
91
|
-
|
|
92
|
-
"""
|
|
93
|
-
chunkSize = int(chunkSize)
|
|
94
|
-
for i in range(0, len(aList), chunkSize):
|
|
95
|
-
yield aList[i : i + chunkSize]
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def breakListIntoChunks(aList: list, chunkSize: int):
|
|
99
|
-
"""This function takes a list as input and breaks it into list of size 'chunkSize'.
|
|
100
|
-
It returns a list of lists.
|
|
101
|
-
|
|
102
|
-
:param aList: list of elements
|
|
103
|
-
:param chunkSize: len of a single chunk
|
|
104
|
-
:return: list of lists of length of chunkSize
|
|
105
|
-
:raise: RuntimeError if numberOfFilesInChunk is less than 1
|
|
106
|
-
"""
|
|
107
|
-
if chunkSize < 1:
|
|
108
|
-
raise RuntimeError("chunkSize cannot be less than 1")
|
|
109
|
-
if isinstance(aList, (set, dict, tuple, {}.keys().__class__, {}.items().__class__, {}.values().__class__)):
|
|
110
|
-
aList = list(aList)
|
|
111
|
-
return [chunk for chunk in getChunk(aList, chunkSize)]
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def getIndexInList(anItem: Any, aList: list) -> int:
|
|
115
|
-
"""Return the index of the element x in the list l
|
|
116
|
-
or sys.maxint if it does not exist
|
|
117
|
-
|
|
118
|
-
:param anItem: element to look for
|
|
119
|
-
:param aList: list to look into
|
|
120
|
-
|
|
121
|
-
:return: the index or sys.maxint
|
|
122
|
-
"""
|
|
123
|
-
# try:
|
|
124
|
-
if anItem in aList:
|
|
125
|
-
return aList.index(anItem)
|
|
126
|
-
else:
|
|
127
|
-
return sys.maxsize
|
|
1
|
+
from DIRACCommon.Core.Utilities.List import * # noqa: F401,F403
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""Backward compatibility wrapper - moved to DIRACCommon
|
|
2
2
|
|
|
3
|
-
This module has been moved to DIRACCommon.
|
|
3
|
+
This module has been moved to DIRACCommon.Core.Utilities.ReturnValues to avoid
|
|
4
4
|
circular dependencies and allow DiracX to use these utilities without
|
|
5
5
|
triggering DIRAC's global state initialization.
|
|
6
6
|
|
|
7
7
|
All exports are maintained for backward compatibility.
|
|
8
8
|
"""
|
|
9
9
|
# Re-export everything from DIRACCommon for backward compatibility
|
|
10
|
-
from DIRACCommon.
|
|
10
|
+
from DIRACCommon.Core.Utilities.ReturnValues import * # noqa: F401, F403
|
|
@@ -1,185 +1,19 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Backward compatibility wrapper - moved to DIRACCommon
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class State:
|
|
9
|
-
"""
|
|
10
|
-
State class that represents a single step on a StateMachine, with all the
|
|
11
|
-
possible transitions, the default transition and an ordering level.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
examples:
|
|
15
|
-
>>> s0 = State(100)
|
|
16
|
-
>>> s1 = State(0, ['StateName1', 'StateName2'], defState='StateName1')
|
|
17
|
-
>>> s2 = State(0, ['StateName1', 'StateName2'])
|
|
18
|
-
# this example is tricky. The transition rule says that will go to
|
|
19
|
-
# nextState, e.g. 'StateNext'. But, it is not on the stateMap, and there
|
|
20
|
-
# is no default defined, so it will end up going to StateNext anyway. You
|
|
21
|
-
# must be careful while defining states and their stateMaps and defaults.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
def __init__(self, level, stateMap=None, defState=None):
|
|
25
|
-
"""
|
|
26
|
-
:param int level: each state is mapped to an integer, which is used to sort the states according to that integer.
|
|
27
|
-
:param list stateMap: it is a list (of strings) with the reachable states from this particular status.
|
|
28
|
-
If not defined, we assume there are no restrictions.
|
|
29
|
-
:param str defState: default state used in case the next state is not in stateMap (not defined or simply not there).
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
self.level = level
|
|
33
|
-
self.stateMap = stateMap if stateMap else []
|
|
34
|
-
self.default = defState
|
|
35
|
-
|
|
36
|
-
def transitionRule(self, nextState):
|
|
37
|
-
"""
|
|
38
|
-
Method that selects next state, knowing the default and the transitions
|
|
39
|
-
map, and the proposed next state. If <nextState> is in stateMap, goes there.
|
|
40
|
-
If not, then goes to <self.default> if any. Otherwise, goes to <nextState>
|
|
41
|
-
anyway.
|
|
42
|
-
|
|
43
|
-
examples:
|
|
44
|
-
>>> s0.transitionRule('nextState')
|
|
45
|
-
'nextState'
|
|
46
|
-
>>> s1.transitionRule('StateName2')
|
|
47
|
-
'StateName2'
|
|
48
|
-
>>> s1.transitionRule('StateNameNotInMap')
|
|
49
|
-
'StateName1'
|
|
50
|
-
>>> s2.transitionRule('StateNameNotInMap')
|
|
51
|
-
'StateNameNotInMap'
|
|
52
|
-
|
|
53
|
-
:param str nextState: name of the state in the stateMap
|
|
54
|
-
:return: state name
|
|
55
|
-
:rtype: str
|
|
56
|
-
"""
|
|
57
|
-
|
|
58
|
-
# If next state is on the list of next states, go ahead.
|
|
59
|
-
if nextState in self.stateMap:
|
|
60
|
-
return nextState
|
|
61
|
-
|
|
62
|
-
# If not, calculate defaultState:
|
|
63
|
-
# if there is a default, that one
|
|
64
|
-
# otherwise is nextState (states with empty list have no movement restrictions)
|
|
65
|
-
defaultNext = self.default if self.default else nextState
|
|
66
|
-
return defaultNext
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
class StateMachine:
|
|
70
|
-
"""
|
|
71
|
-
StateMachine class that represents the whole state machine with all transitions.
|
|
72
|
-
|
|
73
|
-
examples:
|
|
74
|
-
>>> sm0 = StateMachine()
|
|
75
|
-
>>> sm1 = StateMachine(state = 'Active')
|
|
76
|
-
|
|
77
|
-
:param state: current state of the StateMachine, could be None if we do not use the
|
|
78
|
-
StateMachine to calculate transitions. Beware, it is not checked if the
|
|
79
|
-
state is on the states map !
|
|
80
|
-
:type state: None or str
|
|
81
|
-
|
|
82
|
-
"""
|
|
3
|
+
This module has been moved to DIRACCommon.Core.Utilities.StateMachine to avoid
|
|
4
|
+
circular dependencies and allow DiracX to use these utilities without
|
|
5
|
+
triggering DIRAC's global state initialization.
|
|
83
6
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
self.state = state
|
|
90
|
-
# To be overwritten by child classes, unless you like Nirvana state that much.
|
|
91
|
-
self.states = {"Nirvana": State(100)}
|
|
92
|
-
|
|
93
|
-
def getLevelOfState(self, state):
|
|
94
|
-
"""
|
|
95
|
-
Given a state name, it returns its level (integer), which defines the hierarchy.
|
|
7
|
+
All exports are maintained for backward compatibility.
|
|
8
|
+
"""
|
|
9
|
+
# Re-export everything from DIRACCommon for backward compatibility
|
|
10
|
+
from DIRACCommon.Core.Utilities.StateMachine import * # noqa: F401, F403
|
|
96
11
|
|
|
97
|
-
|
|
98
|
-
100
|
|
99
|
-
>>> sm0.getLevelOfState('AnotherState')
|
|
100
|
-
-1
|
|
12
|
+
from DIRAC import gLogger
|
|
101
13
|
|
|
102
|
-
:param str state: name of the state, it should be on <self.states> key set
|
|
103
|
-
:return: `int` || -1 (if not in <self.states>)
|
|
104
|
-
"""
|
|
105
14
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return self.states[state].level
|
|
15
|
+
class StateMachine(StateMachine): # noqa: F405 pylint: disable=function-redefined
|
|
16
|
+
"""Backward compatibility wrapper - moved to DIRACCommon"""
|
|
109
17
|
|
|
110
18
|
def setState(self, candidateState, noWarn=False):
|
|
111
|
-
|
|
112
|
-
Final states are also checked.
|
|
113
|
-
|
|
114
|
-
examples:
|
|
115
|
-
>>> sm0.setState(None)['OK']
|
|
116
|
-
True
|
|
117
|
-
>>> sm0.setState('Nirvana')['OK']
|
|
118
|
-
True
|
|
119
|
-
>>> sm0.setState('AnotherState')['OK']
|
|
120
|
-
False
|
|
121
|
-
|
|
122
|
-
:param state: state which will be set as current state of the StateMachine
|
|
123
|
-
:type state: None or str
|
|
124
|
-
:return: S_OK || S_ERROR
|
|
125
|
-
"""
|
|
126
|
-
if candidateState == self.state:
|
|
127
|
-
return S_OK(candidateState)
|
|
128
|
-
|
|
129
|
-
if not candidateState:
|
|
130
|
-
self.state = candidateState
|
|
131
|
-
elif candidateState in self.states:
|
|
132
|
-
if not self.states[self.state].stateMap:
|
|
133
|
-
if not noWarn:
|
|
134
|
-
gLogger.warn("Final state, won't move", f"({self.state}, asked to move to {candidateState})")
|
|
135
|
-
return S_OK(self.state)
|
|
136
|
-
if candidateState not in self.states[self.state].stateMap:
|
|
137
|
-
gLogger.warn(f"Can't move from {self.state} to {candidateState}, choosing a good one")
|
|
138
|
-
result = self.getNextState(candidateState)
|
|
139
|
-
if not result["OK"]:
|
|
140
|
-
return result
|
|
141
|
-
self.state = result["Value"]
|
|
142
|
-
# If the StateMachine does not accept the candidate, return error message
|
|
143
|
-
else:
|
|
144
|
-
return S_ERROR(f"setState: {candidateState!r} is not a valid state")
|
|
145
|
-
|
|
146
|
-
return S_OK(self.state)
|
|
147
|
-
|
|
148
|
-
def getStates(self):
|
|
149
|
-
"""
|
|
150
|
-
Returns all possible states in the state map
|
|
151
|
-
|
|
152
|
-
examples:
|
|
153
|
-
>>> sm0.getStates()
|
|
154
|
-
[ 'Nirvana' ]
|
|
155
|
-
|
|
156
|
-
:return: list(stateNames)
|
|
157
|
-
"""
|
|
158
|
-
|
|
159
|
-
return list(self.states)
|
|
160
|
-
|
|
161
|
-
def getNextState(self, candidateState):
|
|
162
|
-
"""
|
|
163
|
-
Method that gets the next state, given the proposed transition to candidateState.
|
|
164
|
-
If candidateState is not on the state map <self.states>, it is rejected. If it is
|
|
165
|
-
not the case, we have two options: if <self.state> is None, then the next state
|
|
166
|
-
will be <candidateState>. Otherwise, the current state is using its own
|
|
167
|
-
transition rule to decide.
|
|
168
|
-
|
|
169
|
-
examples:
|
|
170
|
-
>>> sm0.getNextState(None)
|
|
171
|
-
S_OK(None)
|
|
172
|
-
>>> sm0.getNextState('NextState')
|
|
173
|
-
S_OK('NextState')
|
|
174
|
-
|
|
175
|
-
:param str candidateState: name of the next state
|
|
176
|
-
:return: S_OK(nextState) || S_ERROR
|
|
177
|
-
"""
|
|
178
|
-
if candidateState not in self.states:
|
|
179
|
-
return S_ERROR(f"getNextState: {candidateState!r} is not a valid state")
|
|
180
|
-
|
|
181
|
-
# FIXME: do we need this anymore ?
|
|
182
|
-
if self.state is None:
|
|
183
|
-
return S_OK(candidateState)
|
|
184
|
-
|
|
185
|
-
return S_OK(self.states[self.state].transitionRule(candidateState))
|
|
19
|
+
return super().setState(candidateState, noWarn, logger_warn=gLogger.warn)
|
|
@@ -1,260 +1,17 @@
|
|
|
1
|
-
"""
|
|
2
|
-
DIRAC TimeUtilities module
|
|
3
|
-
Support for basic Date and Time operations
|
|
4
|
-
based on system datetime module.
|
|
5
|
-
|
|
6
|
-
It provides common interface to UTC timestamps,
|
|
7
|
-
converter to string types and back.
|
|
8
|
-
|
|
9
|
-
Useful timedelta constant are also provided to
|
|
10
|
-
define time intervals.
|
|
11
|
-
|
|
12
|
-
Notice: datetime.timedelta objects allow multiplication and division by interger
|
|
13
|
-
but not by float. Thus:
|
|
14
|
-
|
|
15
|
-
- DIRAC.TimeUtilities.second * 1.5 is not allowed
|
|
16
|
-
- DIRAC.TimeUtilities.second * 3 / 2 is allowed
|
|
1
|
+
"""Backward compatibility wrapper - moved to DIRACCommon
|
|
17
2
|
|
|
18
|
-
|
|
19
|
-
|
|
3
|
+
This module has been moved to DIRACCommon.Core.Utilities.TimeUtilities to avoid
|
|
4
|
+
circular dependencies and allow DiracX to use these utilities without
|
|
5
|
+
triggering DIRAC's global state initialization.
|
|
20
6
|
|
|
7
|
+
All exports are maintained for backward compatibility.
|
|
21
8
|
"""
|
|
22
|
-
import
|
|
23
|
-
import sys
|
|
24
|
-
import time
|
|
25
|
-
|
|
26
|
-
from DIRAC import gLogger
|
|
27
|
-
|
|
28
|
-
# Some useful constants for time operations
|
|
29
|
-
microsecond = datetime.timedelta(microseconds=1)
|
|
30
|
-
second = datetime.timedelta(seconds=1)
|
|
31
|
-
minute = datetime.timedelta(minutes=1)
|
|
32
|
-
hour = datetime.timedelta(hours=1)
|
|
33
|
-
day = datetime.timedelta(days=1)
|
|
34
|
-
week = datetime.timedelta(days=7)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def timeThis(method):
|
|
38
|
-
"""Function to be used as a decorator for timing other functions/methods"""
|
|
39
|
-
|
|
40
|
-
def timed(*args, **kw):
|
|
41
|
-
"""What actually times"""
|
|
42
|
-
ts = time.time()
|
|
43
|
-
result = method(*args, **kw)
|
|
44
|
-
if sys.stdout.isatty():
|
|
45
|
-
return result
|
|
46
|
-
te = time.time()
|
|
47
|
-
|
|
48
|
-
pre = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC ")
|
|
49
|
-
|
|
50
|
-
try:
|
|
51
|
-
pre += args[0].log.getName() + "/" + args[0].log.getSubName() + " TIME: " + args[0].transString
|
|
52
|
-
except AttributeError:
|
|
53
|
-
try:
|
|
54
|
-
pre += args[0].log.getName() + " TIME: " + args[0].transString
|
|
55
|
-
except AttributeError:
|
|
56
|
-
try:
|
|
57
|
-
pre += args[0].log.getName() + "/" + args[0].log.getSubName() + " TIME: "
|
|
58
|
-
except AttributeError:
|
|
59
|
-
pre += "TIME: "
|
|
60
|
-
except IndexError:
|
|
61
|
-
pre += "TIME: "
|
|
62
|
-
|
|
63
|
-
argsLen = ""
|
|
64
|
-
if args:
|
|
65
|
-
try:
|
|
66
|
-
if isinstance(args[1], (list, dict)):
|
|
67
|
-
argsLen = f"arguments len: {len(args[1])}"
|
|
68
|
-
except IndexError:
|
|
69
|
-
if kw:
|
|
70
|
-
try:
|
|
71
|
-
if isinstance(list(list(kw.items())[0])[1], (list, dict)):
|
|
72
|
-
argsLen = f"arguments len: {len(list(list(kw.items())[0])[1])}"
|
|
73
|
-
except IndexError:
|
|
74
|
-
argsLen = ""
|
|
75
|
-
|
|
76
|
-
gLogger.info(f"{pre} Exec time ===> function {method.__name__!r} {argsLen} -> {te - ts:2.2f} sec")
|
|
77
|
-
return result
|
|
78
|
-
|
|
79
|
-
return timed
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def toEpoch(dateTimeObject=None):
|
|
83
|
-
"""
|
|
84
|
-
Get seconds since epoch. Accepts datetime or date objects
|
|
85
|
-
"""
|
|
86
|
-
return toEpochMilliSeconds(dateTimeObject) // 1000
|
|
9
|
+
from functools import partial
|
|
87
10
|
|
|
11
|
+
# Re-export everything from DIRACCommon for backward compatibility
|
|
12
|
+
from DIRACCommon.Core.Utilities.TimeUtilities import * # noqa: F401, F403
|
|
88
13
|
|
|
89
|
-
|
|
90
|
-
"""
|
|
91
|
-
Get milliseconds since epoch
|
|
92
|
-
"""
|
|
93
|
-
if dateTimeObject is None:
|
|
94
|
-
dateTimeObject = datetime.datetime.utcnow()
|
|
95
|
-
if dateTimeObject.resolution == datetime.timedelta(days=1):
|
|
96
|
-
# Add time information corresponding to midnight UTC if it's a datetime.date
|
|
97
|
-
dateTimeObject = datetime.datetime.combine(
|
|
98
|
-
dateTimeObject, datetime.time.min.replace(tzinfo=datetime.timezone.utc)
|
|
99
|
-
)
|
|
100
|
-
posixTime = dateTimeObject.replace(tzinfo=datetime.timezone.utc).timestamp()
|
|
101
|
-
return int(posixTime * 1000)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def fromEpoch(epoch):
|
|
105
|
-
"""
|
|
106
|
-
Get datetime object from epoch
|
|
107
|
-
"""
|
|
108
|
-
# Check if the timestamp is in milliseconds
|
|
109
|
-
if epoch > 10**17: # nanoseconds
|
|
110
|
-
epoch /= 1000**3
|
|
111
|
-
elif epoch > 10**14: # microseconds
|
|
112
|
-
epoch /= 1000**2
|
|
113
|
-
elif epoch > 10**11: # milliseconds
|
|
114
|
-
epoch /= 1000
|
|
115
|
-
return datetime.datetime.utcfromtimestamp(epoch)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def toString(myDate=None):
|
|
119
|
-
"""
|
|
120
|
-
Convert to String
|
|
121
|
-
if argument type is neither _dateTimeType, _dateType, nor _timeType
|
|
122
|
-
the current dateTime converted to String is returned instead
|
|
123
|
-
|
|
124
|
-
Notice: datetime.timedelta are converted to strings using the format:
|
|
125
|
-
[day] days [hour]:[min]:[sec]:[microsec]
|
|
126
|
-
where hour, min, sec, microsec are always positive integers,
|
|
127
|
-
and day carries the sign.
|
|
128
|
-
To keep internal consistency we are using:
|
|
129
|
-
[hour]:[min]:[sec]:[microsec]
|
|
130
|
-
where min, sec, microsec are always positive integers and hour carries the sign.
|
|
131
|
-
"""
|
|
132
|
-
if isinstance(myDate, datetime.date):
|
|
133
|
-
return str(myDate)
|
|
134
|
-
|
|
135
|
-
elif isinstance(myDate, datetime.time):
|
|
136
|
-
return "%02d:%02d:%02d.%06d" % (
|
|
137
|
-
myDate.days * 24 + myDate.seconds / 3600,
|
|
138
|
-
myDate.seconds % 3600 / 60,
|
|
139
|
-
myDate.seconds % 60,
|
|
140
|
-
myDate.microseconds,
|
|
141
|
-
)
|
|
142
|
-
else:
|
|
143
|
-
return toString(datetime.datetime.utcnow())
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
def fromString(myDate=None):
|
|
147
|
-
"""
|
|
148
|
-
Convert date/time/datetime String back to appropriated objects
|
|
149
|
-
|
|
150
|
-
The format of the string it is assume to be that returned by toString method.
|
|
151
|
-
See notice on toString method
|
|
152
|
-
On Error, return None
|
|
153
|
-
|
|
154
|
-
:param myDate: the date string to be converted
|
|
155
|
-
:type myDate: str or datetime.datetime
|
|
156
|
-
"""
|
|
157
|
-
if isinstance(myDate, datetime.datetime):
|
|
158
|
-
return myDate
|
|
159
|
-
if isinstance(myDate, str):
|
|
160
|
-
if myDate.find(" ") > 0:
|
|
161
|
-
dateTimeTuple = myDate.split(" ")
|
|
162
|
-
dateTuple = dateTimeTuple[0].split("-")
|
|
163
|
-
try:
|
|
164
|
-
return datetime.datetime(year=dateTuple[0], month=dateTuple[1], day=dateTuple[2]) + fromString(
|
|
165
|
-
dateTimeTuple[1]
|
|
166
|
-
)
|
|
167
|
-
# return datetime.datetime.utcnow().combine( fromString( dateTimeTuple[0] ),
|
|
168
|
-
# fromString( dateTimeTuple[1] ) )
|
|
169
|
-
except Exception:
|
|
170
|
-
try:
|
|
171
|
-
return datetime.datetime(
|
|
172
|
-
year=int(dateTuple[0]), month=int(dateTuple[1]), day=int(dateTuple[2])
|
|
173
|
-
) + fromString(dateTimeTuple[1])
|
|
174
|
-
except ValueError:
|
|
175
|
-
return None
|
|
176
|
-
# return datetime.datetime.utcnow().combine( fromString( dateTimeTuple[0] ),
|
|
177
|
-
# fromString( dateTimeTuple[1] ) )
|
|
178
|
-
elif myDate.find(":") > 0:
|
|
179
|
-
timeTuple = myDate.replace(".", ":").split(":")
|
|
180
|
-
try:
|
|
181
|
-
if len(timeTuple) == 4:
|
|
182
|
-
return datetime.timedelta(
|
|
183
|
-
hours=int(timeTuple[0]),
|
|
184
|
-
minutes=int(timeTuple[1]),
|
|
185
|
-
seconds=int(timeTuple[2]),
|
|
186
|
-
microseconds=int(timeTuple[3]),
|
|
187
|
-
)
|
|
188
|
-
elif len(timeTuple) == 3:
|
|
189
|
-
try:
|
|
190
|
-
return datetime.timedelta(
|
|
191
|
-
hours=int(timeTuple[0]),
|
|
192
|
-
minutes=int(timeTuple[1]),
|
|
193
|
-
seconds=int(timeTuple[2]),
|
|
194
|
-
microseconds=0,
|
|
195
|
-
)
|
|
196
|
-
except ValueError:
|
|
197
|
-
return None
|
|
198
|
-
else:
|
|
199
|
-
return None
|
|
200
|
-
except Exception:
|
|
201
|
-
return None
|
|
202
|
-
elif myDate.find("-") > 0:
|
|
203
|
-
dateTuple = myDate.split("-")
|
|
204
|
-
try:
|
|
205
|
-
return datetime.date(int(dateTuple[0]), int(dateTuple[1]), int(dateTuple[2]))
|
|
206
|
-
except Exception:
|
|
207
|
-
return None
|
|
208
|
-
|
|
209
|
-
return None
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
class timeInterval:
|
|
213
|
-
"""
|
|
214
|
-
Simple class to define a timeInterval object able to check if a given
|
|
215
|
-
dateTime is inside
|
|
216
|
-
"""
|
|
217
|
-
|
|
218
|
-
def __init__(self, initialDateTime, intervalTimeDelta):
|
|
219
|
-
"""
|
|
220
|
-
Initialization method, it requires the initial dateTime and the
|
|
221
|
-
timedelta that define the limits.
|
|
222
|
-
The upper limit is not included thus it is [begin,end)
|
|
223
|
-
If not properly initialized an error flag is set, and subsequent calls
|
|
224
|
-
to any method will return None
|
|
225
|
-
"""
|
|
226
|
-
if not isinstance(initialDateTime, datetime.datetime) or not isinstance(intervalTimeDelta, datetime.timedelta):
|
|
227
|
-
self.__error = True
|
|
228
|
-
return None
|
|
229
|
-
self.__error = False
|
|
230
|
-
if intervalTimeDelta.days < 0:
|
|
231
|
-
self.__startDateTime = initialDateTime + intervalTimeDelta
|
|
232
|
-
self.__endDateTime = initialDateTime
|
|
233
|
-
else:
|
|
234
|
-
self.__startDateTime = initialDateTime
|
|
235
|
-
self.__endDateTime = initialDateTime + intervalTimeDelta
|
|
236
|
-
|
|
237
|
-
def includes(self, myDateTime):
|
|
238
|
-
""" """
|
|
239
|
-
if self.__error:
|
|
240
|
-
return None
|
|
241
|
-
if not isinstance(myDateTime, datetime.datetime):
|
|
242
|
-
return None
|
|
243
|
-
if myDateTime < self.__startDateTime:
|
|
244
|
-
return False
|
|
245
|
-
if myDateTime >= self.__endDateTime:
|
|
246
|
-
return False
|
|
247
|
-
return True
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
def queryTime(f):
|
|
251
|
-
"""Decorator to measure the function call time"""
|
|
14
|
+
from DIRAC import gLogger
|
|
252
15
|
|
|
253
|
-
def measureQueryTime(*args, **kwargs):
|
|
254
|
-
start = time.time()
|
|
255
|
-
result = f(*args, **kwargs)
|
|
256
|
-
if result["OK"] and "QueryTime" not in result:
|
|
257
|
-
result["QueryTime"] = time.time() - start
|
|
258
|
-
return result
|
|
259
16
|
|
|
260
|
-
|
|
17
|
+
timeThis = partial(timeThis, logger_info=gLogger.info)
|
|
@@ -19,9 +19,6 @@ def jdl_monkey_business(monkeypatch):
|
|
|
19
19
|
monkeypatch.setattr("DIRAC.Core.Base.API.getSites", lambda: S_OK(["LCG.IN2P3.fr"]))
|
|
20
20
|
monkeypatch.setattr("DIRAC.WorkloadManagementSystem.Utilities.JobModel.getSites", lambda: S_OK(["LCG.IN2P3.fr"]))
|
|
21
21
|
monkeypatch.setattr("DIRAC.Interfaces.API.Job.getDIRACPlatforms", lambda: S_OK("x86_64-slc6-gcc49-opt"))
|
|
22
|
-
monkeypatch.setattr(
|
|
23
|
-
"DIRAC.WorkloadManagementSystem.Utilities.JobModel.getDIRACPlatforms", lambda: S_OK("x86_64-slc6-gcc49-opt")
|
|
24
|
-
)
|
|
25
22
|
yield
|
|
26
23
|
|
|
27
24
|
|
|
@@ -16,6 +16,7 @@ from sqlalchemy import (
|
|
|
16
16
|
Enum,
|
|
17
17
|
Float,
|
|
18
18
|
ForeignKey,
|
|
19
|
+
Index,
|
|
19
20
|
Integer,
|
|
20
21
|
MetaData,
|
|
21
22
|
SmallInteger,
|
|
@@ -85,6 +86,7 @@ fts3JobTable = Table(
|
|
|
85
86
|
Column("error", String(2048)),
|
|
86
87
|
Column("status", Enum(*FTS3Job.ALL_STATES), server_default=FTS3Job.INIT_STATE, index=True),
|
|
87
88
|
Column("assignment", String(255), server_default=None),
|
|
89
|
+
Index("idx_jobs_lastupdate_assignment", "lastUpdate", "assignment"),
|
|
88
90
|
mysql_engine="InnoDB",
|
|
89
91
|
)
|
|
90
92
|
|
|
@@ -110,6 +112,7 @@ fts3OperationTable = Table(
|
|
|
110
112
|
Column("error", String(1024)),
|
|
111
113
|
Column("type", String(255)),
|
|
112
114
|
Column("assignment", String(255), server_default=None),
|
|
115
|
+
Index("idx_operations_lastupdate_assignment", "lastUpdate", "assignment"),
|
|
113
116
|
mysql_engine="InnoDB",
|
|
114
117
|
)
|
|
115
118
|
|
|
@@ -6,6 +6,7 @@ integration tests (Test_ReqDB.py)
|
|
|
6
6
|
# pylint: disable=invalid-name,wrong-import-position
|
|
7
7
|
import time
|
|
8
8
|
|
|
9
|
+
import pytest
|
|
9
10
|
|
|
10
11
|
from DIRAC.RequestManagementSystem.Client.Request import Request
|
|
11
12
|
from DIRAC.RequestManagementSystem.Client.Operation import Operation
|
|
@@ -42,6 +43,7 @@ def test_stress(reqDB):
|
|
|
42
43
|
assert delete["OK"], delete
|
|
43
44
|
|
|
44
45
|
|
|
46
|
+
@pytest.mark.slow
|
|
45
47
|
def test_stressBulk(reqDB):
|
|
46
48
|
"""stress test bulk"""
|
|
47
49
|
|
|
@@ -151,7 +151,7 @@ class RucioFileCatalogClient(FileCatalogClientBase):
|
|
|
151
151
|
self.authHost = options.get("AuthHost", None)
|
|
152
152
|
self.caCertPath = Locations.getCAsLocation()
|
|
153
153
|
try:
|
|
154
|
-
sLog.
|
|
154
|
+
sLog.debug(f"Logging in with a proxy located at: {self.proxyPath}")
|
|
155
155
|
sLog.debug("account: ", self.username)
|
|
156
156
|
sLog.debug("rucio host: ", self.rucioHost)
|
|
157
157
|
sLog.debug("auth host: ", self.authHost)
|
|
@@ -25,7 +25,7 @@ while True:
|
|
|
25
25
|
if os.path.isfile(stopFile):
|
|
26
26
|
os.remove(stopFile)
|
|
27
27
|
break
|
|
28
|
-
if (time.time() - start) >
|
|
28
|
+
if (time.time() - start) > 5:
|
|
29
29
|
break
|
|
30
30
|
print("End job", jobNumber, time.time())
|
|
31
31
|
"""
|
|
@@ -262,6 +262,7 @@ def test_executeJob_wholeNode8(createAndDelete):
|
|
|
262
262
|
assert "Not enough processors" in submissionResult["Message"]
|
|
263
263
|
|
|
264
264
|
|
|
265
|
+
@pytest.mark.slow
|
|
265
266
|
def test_executeJob_submitAndStop(createAndDelete):
|
|
266
267
|
time.sleep(0.5)
|
|
267
268
|
|