rucio 37.0.0rc3__py3-none-any.whl → 37.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rucio might be problematic. Click here for more details.
- rucio/cli/account.py +14 -14
- rucio/cli/command.py +9 -9
- rucio/cli/config.py +3 -3
- rucio/cli/did.py +13 -13
- rucio/cli/lifetime_exception.py +1 -1
- rucio/cli/replica.py +3 -3
- rucio/cli/rse.py +18 -18
- rucio/cli/rule.py +5 -5
- rucio/cli/scope.py +2 -2
- rucio/cli/subscription.py +4 -4
- rucio/client/baseclient.py +0 -3
- rucio/client/lifetimeclient.py +46 -13
- rucio/common/config.py +0 -26
- rucio/common/stomp_utils.py +119 -383
- rucio/common/utils.py +14 -17
- rucio/core/account_limit.py +56 -79
- rucio/core/did_meta_plugins/filter_engine.py +1 -3
- rucio/core/rse_selector.py +3 -3
- rucio/core/rule_grouping.py +0 -1
- rucio/daemons/cache/consumer.py +90 -26
- rucio/daemons/conveyor/receiver.py +123 -53
- rucio/daemons/hermes/hermes.py +343 -41
- rucio/daemons/tracer/kronos.py +139 -114
- rucio/gateway/account_limit.py +40 -76
- rucio/transfertool/fts3.py +1 -1
- rucio/vcsversion.py +4 -4
- rucio/web/rest/flaskapi/v1/accounts.py +3 -9
- rucio/web/rest/flaskapi/v1/auth.py +3 -3
- rucio/web/rest/flaskapi/v1/common.py +10 -4
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-replica-recoverer +1 -1
- {rucio-37.0.0rc3.dist-info → rucio-37.1.0.dist-info}/METADATA +1 -1
- {rucio-37.0.0rc3.dist-info → rucio-37.1.0.dist-info}/RECORD +90 -90
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/alembic.ini.template +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/alembic_offline.ini.template +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/globus-config.yml.template +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/ldap.cfg.template +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/mail_templates/rule_approval_request.tmpl +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/mail_templates/rule_approved_user.tmpl +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/mail_templates/rule_denied_user.tmpl +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/rse-accounts.cfg.template +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/rucio.cfg.atlas.client.template +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/rucio.cfg.template +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/rucio_multi_vo.cfg.template +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/requirements.server.txt +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/tools/bootstrap.py +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/tools/merge_rucio_configs.py +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/tools/reset_database.py +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-abacus-account +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-abacus-collection-replica +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-abacus-rse +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-admin +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-atropos +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-auditor +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-automatix +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-bb8 +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-cache-client +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-cache-consumer +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-conveyor-finisher +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-conveyor-poller +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-conveyor-preparer +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-conveyor-receiver +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-conveyor-stager +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-conveyor-submitter +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-conveyor-throttler +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-dark-reaper +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-dumper +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-follower +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-hermes +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-judge-cleaner +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-judge-evaluator +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-judge-injector +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-judge-repairer +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-kronos +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-minos +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-minos-temporary-expiration +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-necromancer +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-oauth-manager +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-reaper +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-rse-decommissioner +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-storage-consistency-actions +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-transmogrifier +0 -0
- {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-undertaker +0 -0
- {rucio-37.0.0rc3.dist-info → rucio-37.1.0.dist-info}/WHEEL +0 -0
- {rucio-37.0.0rc3.dist-info → rucio-37.1.0.dist-info}/licenses/AUTHORS.rst +0 -0
- {rucio-37.0.0rc3.dist-info → rucio-37.1.0.dist-info}/licenses/LICENSE +0 -0
- {rucio-37.0.0rc3.dist-info → rucio-37.1.0.dist-info}/top_level.txt +0 -0
rucio/daemons/tracer/kronos.py
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"""
|
|
16
16
|
This daemon consumes tracer messages from ActiveMQ and updates the atime for replicas.
|
|
17
17
|
"""
|
|
18
|
+
|
|
18
19
|
import functools
|
|
19
20
|
import logging
|
|
20
21
|
import re
|
|
@@ -25,13 +26,13 @@ from json import loads as jloads
|
|
|
25
26
|
from queue import Queue
|
|
26
27
|
from threading import Event, Thread
|
|
27
28
|
from time import time
|
|
28
|
-
from typing import TYPE_CHECKING
|
|
29
|
+
from typing import TYPE_CHECKING, Optional
|
|
29
30
|
|
|
30
31
|
import rucio.db.sqla.util
|
|
31
|
-
from rucio.common.config import config_get, config_get_int, config_get_list
|
|
32
|
+
from rucio.common.config import config_get, config_get_bool, config_get_int, config_get_list
|
|
32
33
|
from rucio.common.exception import DatabaseException, RSENotFound
|
|
33
|
-
from rucio.common.logging import
|
|
34
|
-
from rucio.common.stomp_utils import
|
|
34
|
+
from rucio.common.logging import setup_logging
|
|
35
|
+
from rucio.common.stomp_utils import StompConnectionManager
|
|
35
36
|
from rucio.common.stopwatch import Stopwatch
|
|
36
37
|
from rucio.common.types import InternalAccount, InternalScope, LoggerFunction
|
|
37
38
|
from rucio.core.did import list_parent_dids, touch_dids
|
|
@@ -46,10 +47,9 @@ if TYPE_CHECKING:
|
|
|
46
47
|
from collections.abc import Set
|
|
47
48
|
from types import FrameType
|
|
48
49
|
|
|
50
|
+
from stomp import Connection
|
|
49
51
|
from stomp.utils import Frame
|
|
50
52
|
|
|
51
|
-
from rucio.common.stomp_utils import Connection
|
|
52
|
-
|
|
53
53
|
|
|
54
54
|
logging.getLogger("stomp").setLevel(logging.CRITICAL)
|
|
55
55
|
|
|
@@ -57,20 +57,23 @@ METRICS = MetricManager(module=__name__)
|
|
|
57
57
|
graceful_stop = Event()
|
|
58
58
|
|
|
59
59
|
|
|
60
|
-
class AMQConsumer
|
|
60
|
+
class AMQConsumer:
|
|
61
61
|
"""ActiveMQ message consumer"""
|
|
62
62
|
|
|
63
|
-
def __init__(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
broker: str,
|
|
66
|
+
conn: "Connection",
|
|
67
|
+
queue: str,
|
|
68
|
+
chunksize: int,
|
|
69
|
+
subscription_id: str,
|
|
70
|
+
excluded_usrdns: "Set[str]",
|
|
71
|
+
dataset_queue: Queue,
|
|
72
|
+
bad_files_patterns: list[re.Pattern],
|
|
73
|
+
logger: LoggerFunction = logging.log
|
|
74
|
+
):
|
|
75
|
+
self.__broker = broker
|
|
76
|
+
self.__conn = conn
|
|
74
77
|
self.__queue = queue
|
|
75
78
|
self.__reports = []
|
|
76
79
|
self.__ids = []
|
|
@@ -82,6 +85,15 @@ class AMQConsumer(ListenerBase):
|
|
|
82
85
|
self.__excluded_usrdns = excluded_usrdns
|
|
83
86
|
self.__dataset_queue = dataset_queue
|
|
84
87
|
self.__bad_files_patterns = bad_files_patterns
|
|
88
|
+
self.__logger = logger
|
|
89
|
+
|
|
90
|
+
@METRICS.count_it
|
|
91
|
+
def on_heartbeat_timeout(self) -> None:
|
|
92
|
+
self.__conn.disconnect()
|
|
93
|
+
|
|
94
|
+
@METRICS.count_it
|
|
95
|
+
def on_error(self, frame: "Frame") -> None:
|
|
96
|
+
self.__logger(logging.ERROR, 'Message receive error: [%s] %s' % (self.__broker, frame.body))
|
|
85
97
|
|
|
86
98
|
@METRICS.count_it
|
|
87
99
|
def on_message(self, frame: "Frame") -> None:
|
|
@@ -95,7 +107,7 @@ class AMQConsumer(ListenerBase):
|
|
|
95
107
|
|
|
96
108
|
try:
|
|
97
109
|
if appversion == 'dq2':
|
|
98
|
-
self.
|
|
110
|
+
self.__conn.ack(msg_id, self.__subscription_id)
|
|
99
111
|
return
|
|
100
112
|
else:
|
|
101
113
|
report = jloads(frame.body) # type: ignore
|
|
@@ -103,26 +115,22 @@ class AMQConsumer(ListenerBase):
|
|
|
103
115
|
# message is corrupt, not much to do here
|
|
104
116
|
# send count to graphite, send ack to broker and return
|
|
105
117
|
METRICS.counter('json_error').inc()
|
|
106
|
-
self.
|
|
107
|
-
self.
|
|
118
|
+
self.__logger(logging.ERROR, 'json error', exc_info=True)
|
|
119
|
+
self.__conn.ack(msg_id, self.__subscription_id)
|
|
108
120
|
return
|
|
109
121
|
|
|
110
122
|
self.__ids.append(msg_id)
|
|
111
123
|
self.__reports.append(report)
|
|
112
124
|
|
|
113
125
|
try:
|
|
114
|
-
self.
|
|
115
|
-
"message received: %s %s %s",
|
|
116
|
-
str(report['eventType']),
|
|
117
|
-
report['filename'],
|
|
118
|
-
report['remoteSite'])
|
|
126
|
+
self.__logger(logging.DEBUG, 'message received: %s %s %s' % (str(report['eventType']), report['filename'], report['remoteSite']))
|
|
119
127
|
except Exception:
|
|
120
128
|
pass
|
|
121
129
|
|
|
122
130
|
if len(self.__ids) >= self.__chunksize:
|
|
123
131
|
self.__update_atime()
|
|
124
132
|
for msg_id in self.__ids:
|
|
125
|
-
self.
|
|
133
|
+
self.__conn.ack(msg_id, self.__subscription_id)
|
|
126
134
|
|
|
127
135
|
self.__reports = []
|
|
128
136
|
self.__ids = []
|
|
@@ -145,19 +153,16 @@ class AMQConsumer(ListenerBase):
|
|
|
145
153
|
if 'stateReason' in report and report['stateReason'] and isinstance(report['stateReason'], str) and pattern.match(report['stateReason']):
|
|
146
154
|
reason = report['stateReason'][:255]
|
|
147
155
|
if 'url' not in report or not report['url']:
|
|
148
|
-
self.
|
|
156
|
+
self.__logger(logging.ERROR, 'Missing url in the following trace : ' + str(report))
|
|
149
157
|
else:
|
|
150
158
|
try:
|
|
151
|
-
|
|
152
|
-
declare_bad_file_replicas([
|
|
153
|
-
self.
|
|
154
|
-
"Declare suspicious file %s with reason %s",
|
|
155
|
-
report['url'],
|
|
156
|
-
reason)
|
|
159
|
+
pfn = report['url']
|
|
160
|
+
declare_bad_file_replicas([pfn, ], reason=reason, issuer=InternalAccount('root', vo=report['vo']), status=BadFilesStatus.SUSPICIOUS)
|
|
161
|
+
self.__logger(logging.INFO, 'Declare suspicious file %s with reason %s' % (report['url'], reason))
|
|
157
162
|
except Exception as error:
|
|
158
|
-
self.
|
|
163
|
+
self.__logger(logging.ERROR, 'Failed to declare suspicious file' + str(error))
|
|
159
164
|
except Exception as error:
|
|
160
|
-
self.
|
|
165
|
+
self.__logger(logging.ERROR, 'Problem with bad trace : %s . Error %s' % (str(report), str(error)))
|
|
161
166
|
|
|
162
167
|
# check if scope in report. if not skip this one.
|
|
163
168
|
if 'scope' not in report:
|
|
@@ -220,7 +225,7 @@ class AMQConsumer(ListenerBase):
|
|
|
220
225
|
try:
|
|
221
226
|
rse_id = get_rse_id(rse=rse, vo=report['vo'])
|
|
222
227
|
except RSENotFound:
|
|
223
|
-
self.
|
|
228
|
+
self.__logger(logging.WARNING, "Cannot lookup rse_id for %s. Will skip this report.", rse)
|
|
224
229
|
METRICS.counter('rse_not_found').inc()
|
|
225
230
|
continue
|
|
226
231
|
replicas.append({'name': report['filename'], 'scope': report['scope'], 'rse': rse, 'rse_id': rse_id, 'accessed_at': datetime.utcfromtimestamp(report['traceTimeentryUnix']),
|
|
@@ -236,7 +241,7 @@ class AMQConsumer(ListenerBase):
|
|
|
236
241
|
try:
|
|
237
242
|
rse_id = get_rse_id(rse=rse, vo=report['vo'])
|
|
238
243
|
except RSENotFound:
|
|
239
|
-
self.
|
|
244
|
+
self.__logger(logging.WARNING, "Cannot lookup rse_id for %s.", rse)
|
|
240
245
|
METRICS.counter('rse_not_found').inc()
|
|
241
246
|
if 'datasetScope' in report:
|
|
242
247
|
self.__dataset_queue.put({'scope': InternalScope(report['datasetScope'], vo=report['vo']),
|
|
@@ -254,11 +259,11 @@ class AMQConsumer(ListenerBase):
|
|
|
254
259
|
'accessed_at': datetime.utcfromtimestamp(report['traceTimeentryUnix'])})
|
|
255
260
|
|
|
256
261
|
except (KeyError, AttributeError):
|
|
257
|
-
self.
|
|
262
|
+
self.__logger(logging.ERROR, "Cannot handle report.", exc_info=True)
|
|
258
263
|
METRICS.counter('report_error').inc()
|
|
259
264
|
continue
|
|
260
265
|
except Exception:
|
|
261
|
-
self.
|
|
266
|
+
self.__logger(logging.ERROR, "Exception", exc_info=True)
|
|
262
267
|
continue
|
|
263
268
|
|
|
264
269
|
for did in list_parent_dids(report['scope'], report['filename']):
|
|
@@ -271,15 +276,15 @@ class AMQConsumer(ListenerBase):
|
|
|
271
276
|
try:
|
|
272
277
|
rse_id = get_rse_id(rse=rse, vo=report['vo'])
|
|
273
278
|
except RSENotFound:
|
|
274
|
-
self.
|
|
279
|
+
self.__logger(logging.WARNING, "Cannot lookup rse_id for %s. Will skip this report.", rse)
|
|
275
280
|
METRICS.counter('rse_not_found').inc()
|
|
276
281
|
continue
|
|
277
282
|
self.__dataset_queue.put({'scope': did['scope'], 'name': did['name'], 'did_type': did['type'], 'rse_id': rse_id, 'accessed_at': datetime.utcfromtimestamp(report['traceTimeentryUnix'])})
|
|
278
283
|
|
|
279
|
-
if not replicas:
|
|
284
|
+
if not len(replicas):
|
|
280
285
|
return
|
|
281
286
|
|
|
282
|
-
self.
|
|
287
|
+
self.__logger(logging.DEBUG, "trying to update replicas: %s", replicas)
|
|
283
288
|
|
|
284
289
|
stopwatch = Stopwatch()
|
|
285
290
|
try:
|
|
@@ -296,50 +301,25 @@ class AMQConsumer(ListenerBase):
|
|
|
296
301
|
'eventVersion': replica['eventVersion']}
|
|
297
302
|
if replica['scope'].vo != 'def':
|
|
298
303
|
resubmit['vo'] = replica['scope'].vo
|
|
299
|
-
self.
|
|
304
|
+
self.__conn.send(body=jdumps(resubmit), destination=self.__queue, headers={'appversion': 'rucio', 'resubmitted': '1'})
|
|
300
305
|
METRICS.counter('sent_resubmitted').inc()
|
|
301
306
|
METRICS.timer('update_atime').observe(stopwatch.elapsed)
|
|
302
307
|
except Exception:
|
|
303
|
-
self.
|
|
308
|
+
self.__logger(logging.ERROR, "Cannot update replicas.", exc_info=True)
|
|
304
309
|
METRICS.counter('update_error').inc()
|
|
305
310
|
|
|
306
311
|
METRICS.counter('updated_replicas').inc()
|
|
307
312
|
|
|
308
313
|
|
|
309
|
-
def kronos_file(
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
314
|
+
def kronos_file(
|
|
315
|
+
once: bool = False,
|
|
316
|
+
dataset_queue: Optional[Queue] = None,
|
|
317
|
+
sleep_time: int = 60
|
|
318
|
+
) -> None:
|
|
313
319
|
"""
|
|
314
320
|
Main loop to consume tracer reports.
|
|
315
321
|
"""
|
|
316
|
-
|
|
317
|
-
try:
|
|
318
|
-
patterns = config_get_list(section='kronos', option='bad_files_patterns', session=None)
|
|
319
|
-
for pat in patterns:
|
|
320
|
-
bad_files_patterns.append(re.compile(pat.strip()))
|
|
321
|
-
except (NoOptionError, NoSectionError, RuntimeError):
|
|
322
|
-
logger(logging.ERROR, "Failed to get bad_file_patterns")
|
|
323
|
-
bad_files_patterns.clear()
|
|
324
|
-
except Exception as error:
|
|
325
|
-
logger(logging.ERROR, f'Failed to get bad_file_patterns {str(error)}')
|
|
326
|
-
bad_files_patterns.clear()
|
|
327
|
-
|
|
328
|
-
excluded_usrdns = set(config_get('tracer-kronos', 'excluded_usrdns').split(','))
|
|
329
|
-
|
|
330
|
-
subscription_id = config_get('tracer-kronos', 'subscription_id')
|
|
331
|
-
stomp_conn_mngr = StompConnectionManager(config_section='tracer-kronos',
|
|
332
|
-
logger=logger)
|
|
333
|
-
stomp_conn_mngr.set_listener_factory('rucio-tracer-kronos',
|
|
334
|
-
AMQConsumer,
|
|
335
|
-
queue=config_get('tracer-kronos', 'queue'),
|
|
336
|
-
chunksize=config_get_int('tracer-kronos', 'chunksize'),
|
|
337
|
-
subscription_id=subscription_id,
|
|
338
|
-
excluded_usrdns=excluded_usrdns,
|
|
339
|
-
dataset_queue=dataset_queue,
|
|
340
|
-
bad_files_patterns=bad_files_patterns,
|
|
341
|
-
heartbeats=stomp_conn_mngr.config.heartbeats)
|
|
342
|
-
|
|
322
|
+
stomp_conn_mngr = StompConnectionManager()
|
|
343
323
|
run_daemon(
|
|
344
324
|
once=once,
|
|
345
325
|
graceful_stop=graceful_stop,
|
|
@@ -356,20 +336,74 @@ def kronos_file(once: bool = False,
|
|
|
356
336
|
stomp_conn_mngr.disconnect()
|
|
357
337
|
|
|
358
338
|
|
|
359
|
-
def run_once_kronos_file(heartbeat_handler: HeartbeatHandler,
|
|
360
|
-
stomp_conn_mngr: StompConnectionManager,
|
|
361
|
-
dataset_queue: Queue,
|
|
362
|
-
sleep_time: int,
|
|
363
|
-
**kwargs: dict) -> None:
|
|
339
|
+
def run_once_kronos_file(heartbeat_handler: HeartbeatHandler, stomp_conn_mngr: StompConnectionManager, dataset_queue: Queue, sleep_time: int, **kwargs) -> None:
|
|
364
340
|
"""
|
|
365
341
|
Run the amq consumer once.
|
|
366
342
|
"""
|
|
367
343
|
_, _, logger = heartbeat_handler.live()
|
|
368
344
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
345
|
+
chunksize = config_get_int('tracer-kronos', 'chunksize')
|
|
346
|
+
prefetch_size = config_get_int('tracer-kronos', 'prefetch_size')
|
|
347
|
+
subscription_id = config_get('tracer-kronos', 'subscription_id')
|
|
348
|
+
# Load bad file patterns from config
|
|
349
|
+
try:
|
|
350
|
+
bad_files_patterns = []
|
|
351
|
+
pattern = config_get(section='kronos', option='bad_files_patterns', session=None)
|
|
352
|
+
pattern = str(pattern)
|
|
353
|
+
patterns = pattern.split(",")
|
|
354
|
+
for pat in patterns:
|
|
355
|
+
bad_files_patterns.append(re.compile(pat.strip()))
|
|
356
|
+
except (NoOptionError, NoSectionError, RuntimeError):
|
|
357
|
+
bad_files_patterns = []
|
|
358
|
+
except Exception as error:
|
|
359
|
+
logger.error(f'Failed to get bad_file_patterns {str(error)}')
|
|
360
|
+
bad_files_patterns = []
|
|
361
|
+
|
|
362
|
+
use_ssl = config_get_bool('tracer-kronos', 'use_ssl', default=True, raise_exception=False)
|
|
363
|
+
if not use_ssl:
|
|
364
|
+
username = config_get('tracer-kronos', 'username')
|
|
365
|
+
password = config_get('tracer-kronos', 'password')
|
|
366
|
+
|
|
367
|
+
excluded_usrdns = set(config_get_list('tracer-kronos', 'excluded_usrdns'))
|
|
368
|
+
vhost = config_get('tracer-kronos', 'broker_virtual_host', raise_exception=False)
|
|
369
|
+
|
|
370
|
+
brokers_alias = config_get_list('tracer-kronos', 'brokers')
|
|
371
|
+
port = config_get_int('tracer-kronos', 'port')
|
|
372
|
+
reconnect_attempts = config_get_int('tracer-kronos', 'reconnect_attempts')
|
|
373
|
+
ssl_key_file = config_get('tracer-kronos', 'ssl_key_file', raise_exception=False)
|
|
374
|
+
ssl_cert_file = config_get('tracer-kronos', 'ssl_cert_file', raise_exception=False)
|
|
375
|
+
|
|
376
|
+
created_conns, _ = stomp_conn_mngr.re_configure(
|
|
377
|
+
brokers=brokers_alias,
|
|
378
|
+
port=port,
|
|
379
|
+
use_ssl=use_ssl,
|
|
380
|
+
vhost=vhost,
|
|
381
|
+
reconnect_attempts=reconnect_attempts,
|
|
382
|
+
ssl_key_file=ssl_key_file,
|
|
383
|
+
ssl_cert_file=ssl_cert_file,
|
|
384
|
+
timeout=sleep_time,
|
|
385
|
+
heartbeats=(0, 5000),
|
|
386
|
+
logger=logger,
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
for conn in created_conns:
|
|
390
|
+
if not conn.is_connected():
|
|
391
|
+
logger(logging.INFO, 'connecting to %s' % str(conn.transport._Transport__host_and_ports[0]))
|
|
392
|
+
METRICS.counter('reconnect.{host}').labels(host=conn.transport._Transport__host_and_ports[0][0]).inc()
|
|
393
|
+
conn.set_listener('rucio-tracer-kronos', AMQConsumer(broker=conn.transport._Transport__host_and_ports[0],
|
|
394
|
+
conn=conn,
|
|
395
|
+
queue=config_get('tracer-kronos', 'queue'),
|
|
396
|
+
chunksize=chunksize,
|
|
397
|
+
subscription_id=subscription_id,
|
|
398
|
+
excluded_usrdns=excluded_usrdns,
|
|
399
|
+
dataset_queue=dataset_queue,
|
|
400
|
+
bad_files_patterns=bad_files_patterns,
|
|
401
|
+
logger=logger))
|
|
402
|
+
if not use_ssl:
|
|
403
|
+
conn.connect(username, password)
|
|
404
|
+
else:
|
|
405
|
+
conn.connect()
|
|
406
|
+
conn.subscribe(destination=config_get('tracer-kronos', 'queue'), ack='client-individual', id=subscription_id, headers={'activemq.prefetchSize': prefetch_size})
|
|
373
407
|
|
|
374
408
|
|
|
375
409
|
def kronos_dataset(dataset_queue: Queue, once: bool = False, sleep_time: int = 60) -> None:
|
|
@@ -388,16 +422,10 @@ def kronos_dataset(dataset_queue: Queue, once: bool = False, sleep_time: int = 6
|
|
|
388
422
|
)
|
|
389
423
|
|
|
390
424
|
# once again for potential backlog
|
|
391
|
-
run_once_kronos_dataset(dataset_queue=dataset_queue,
|
|
392
|
-
return_values=return_values,
|
|
393
|
-
heartbeat_handler=return_values['heartbeat_handler'],
|
|
394
|
-
sleep_time=sleep_time)
|
|
425
|
+
run_once_kronos_dataset(dataset_queue=dataset_queue, return_values=return_values, heartbeat_handler=return_values['heartbeat_handler'], sleep_time=sleep_time)
|
|
395
426
|
|
|
396
427
|
|
|
397
|
-
def run_once_kronos_dataset(dataset_queue: Queue,
|
|
398
|
-
return_values: dict,
|
|
399
|
-
heartbeat_handler: "HeartbeatHandler | None",
|
|
400
|
-
**kwargs) -> None:
|
|
428
|
+
def run_once_kronos_dataset(dataset_queue: Queue, return_values: dict, heartbeat_handler: HeartbeatHandler, **kwargs) -> None:
|
|
401
429
|
if heartbeat_handler is None:
|
|
402
430
|
if "heartbeat_handler" not in return_values.keys():
|
|
403
431
|
return_values["heartbeat_handler"] = HeartbeatHandler("kronos-dataset", 10)
|
|
@@ -467,7 +495,7 @@ def run_once_kronos_dataset(dataset_queue: Queue,
|
|
|
467
495
|
logger(logging.INFO, 'update done for %d collection replicas, %d failed (%ds)' % (total, failed, time() - start))
|
|
468
496
|
|
|
469
497
|
|
|
470
|
-
def stop(signum:
|
|
498
|
+
def stop(signum: Optional[int] = None, frame: Optional["FrameType"] = None) -> None:
|
|
471
499
|
"""
|
|
472
500
|
Graceful exit.
|
|
473
501
|
"""
|
|
@@ -478,34 +506,31 @@ def run(
|
|
|
478
506
|
once: bool = False,
|
|
479
507
|
threads: int = 1,
|
|
480
508
|
sleep_time_datasets: int = 60,
|
|
481
|
-
sleep_time_files: int = 60
|
|
509
|
+
sleep_time_files: int = 60
|
|
510
|
+
) -> None:
|
|
482
511
|
"""
|
|
483
512
|
Starts up the consumer threads
|
|
484
513
|
"""
|
|
485
514
|
setup_logging(process_name='tracer-kronos')
|
|
486
|
-
logger = formatted_logger(logging.log, 'Kronos %s')
|
|
487
515
|
|
|
488
516
|
if rucio.db.sqla.util.is_old_db():
|
|
489
517
|
raise DatabaseException('Database was not updated, daemon won\'t start')
|
|
490
518
|
|
|
491
519
|
dataset_queue = Queue()
|
|
492
|
-
|
|
520
|
+
logging.info('starting tracer consumer threads')
|
|
493
521
|
|
|
494
522
|
thread_list = []
|
|
495
|
-
for _ in range(threads):
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
while [thread.join(timeout=3.) for thread in thread_list if thread.is_alive()]:
|
|
511
|
-
pass
|
|
523
|
+
for _ in range(0, threads):
|
|
524
|
+
thread_list.append(Thread(target=kronos_file, kwargs={'once': once,
|
|
525
|
+
'sleep_time': sleep_time_files,
|
|
526
|
+
'dataset_queue': dataset_queue}))
|
|
527
|
+
thread_list.append(Thread(target=kronos_dataset, kwargs={'once': once,
|
|
528
|
+
'sleep_time': sleep_time_datasets,
|
|
529
|
+
'dataset_queue': dataset_queue}))
|
|
530
|
+
|
|
531
|
+
[thread.start() for thread in thread_list]
|
|
532
|
+
|
|
533
|
+
logging.info('waiting for interrupts')
|
|
534
|
+
|
|
535
|
+
while len(thread_list) > 0:
|
|
536
|
+
thread_list = [thread.join(timeout=3) for thread in thread_list if thread and thread.is_alive()]
|
rucio/gateway/account_limit.py
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
from typing import TYPE_CHECKING, Any, Union
|
|
15
|
+
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
16
16
|
|
|
17
17
|
import rucio.common.exception
|
|
18
18
|
import rucio.gateway.permission
|
|
@@ -47,110 +47,74 @@ def get_rse_account_usage(
|
|
|
47
47
|
return [gateway_update_return_dict(d, session=session) for d in account_limit_core.get_rse_account_usage(rse_id=rse_id, session=session)]
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
@read_session
|
|
51
|
-
def get_local_account_limits(
|
|
52
|
-
account: str,
|
|
53
|
-
vo: str = 'def',
|
|
54
|
-
*,
|
|
55
|
-
session: "Session"
|
|
56
|
-
) -> dict[str, Any]:
|
|
57
|
-
"""
|
|
58
|
-
Lists the limitation names/values for the specified account name.
|
|
59
|
-
|
|
60
|
-
REST API: http://<host>:<port>/rucio/account/<account>/limits
|
|
61
|
-
|
|
62
|
-
:param account: The account name.
|
|
63
|
-
:param vo: The VO to act on.
|
|
64
|
-
:param session: The database session in use.
|
|
65
|
-
|
|
66
|
-
:returns: The account limits.
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
internal_account = InternalAccount(account, vo=vo)
|
|
70
|
-
|
|
71
|
-
rse_instead_id = {}
|
|
72
|
-
for elem in account_limit_core.get_local_account_limits(account=internal_account, session=session).items():
|
|
73
|
-
rse_instead_id[get_rse_name(rse_id=elem[0], session=session)] = elem[1]
|
|
74
|
-
return rse_instead_id
|
|
75
|
-
|
|
76
|
-
|
|
77
50
|
@read_session
|
|
78
51
|
def get_local_account_limit(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
52
|
+
account: str,
|
|
53
|
+
rse: Optional[str] = None,
|
|
54
|
+
vo: str = 'def',
|
|
55
|
+
*,
|
|
56
|
+
session: "Session"
|
|
84
57
|
) -> dict[str, Union[int, float, None]]:
|
|
85
58
|
"""
|
|
86
|
-
Lists the limitation names/values for the specified account name
|
|
59
|
+
Lists the limitation names/values for the specified account name.
|
|
60
|
+
If an RSE is provided, it returns the limit for that specific RSE.
|
|
61
|
+
Otherwise, it returns all account limits.
|
|
87
62
|
|
|
88
63
|
REST API: http://<host>:<port>/rucio/account/<account>/limits
|
|
89
64
|
|
|
90
|
-
:param account:
|
|
91
|
-
:param rse:
|
|
92
|
-
:param vo:
|
|
93
|
-
:param session:
|
|
65
|
+
:param account: The account name.
|
|
66
|
+
:param rse: The RSE name (optional).
|
|
67
|
+
:param vo: The VO to act on.
|
|
68
|
+
:param session: The database session in use.
|
|
94
69
|
|
|
95
|
-
:returns:
|
|
70
|
+
:returns: A dictionary of account limits with RSE names as keys and limits as values.
|
|
96
71
|
"""
|
|
97
72
|
|
|
98
73
|
internal_account = InternalAccount(account, vo=vo)
|
|
99
74
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
@read_session
|
|
105
|
-
def get_global_account_limits(
|
|
106
|
-
account: str,
|
|
107
|
-
vo: str = 'def',
|
|
108
|
-
*,
|
|
109
|
-
session: "Session"
|
|
110
|
-
) -> dict[str, RSEResolvedGlobalAccountLimitDict]:
|
|
111
|
-
"""
|
|
112
|
-
Lists the limitation names/values for the specified account name.
|
|
113
|
-
|
|
114
|
-
REST API: http://<host>:<port>/rucio/account/<account>/limits
|
|
115
|
-
|
|
116
|
-
:param account: The account name.
|
|
117
|
-
:param vo: The VO to act on.
|
|
118
|
-
:param session: The database session in use.
|
|
119
|
-
|
|
120
|
-
:returns: The account limits.
|
|
121
|
-
"""
|
|
122
|
-
if account:
|
|
123
|
-
internal_account = InternalAccount(account, vo=vo)
|
|
75
|
+
if rse:
|
|
76
|
+
# Single RSE lookup
|
|
77
|
+
rse_id = get_rse_id(rse=rse, vo=vo, session=session)
|
|
78
|
+
return {rse: account_limit_core.get_local_account_limit(account=internal_account, rse_ids=rse_id, session=session)}
|
|
124
79
|
else:
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
80
|
+
# Fetch all RSE limits
|
|
81
|
+
limits = account_limit_core.get_local_account_limit(account=internal_account, rse_ids=None, session=session)
|
|
82
|
+
return {get_rse_name(rse_id=rse_id, session=session): limit for rse_id, limit in limits.items()}
|
|
128
83
|
|
|
129
84
|
|
|
130
85
|
@read_session
|
|
131
86
|
def get_global_account_limit(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
) -> dict[str, dict[str, RSEResolvedGlobalAccountLimitDict]]:
|
|
87
|
+
account: str,
|
|
88
|
+
rse_expression: Optional[str] = None,
|
|
89
|
+
vo: str = 'def',
|
|
90
|
+
*,
|
|
91
|
+
session: "Session"
|
|
92
|
+
) -> Union[dict[str, RSEResolvedGlobalAccountLimitDict], dict[str, dict[str, RSEResolvedGlobalAccountLimitDict]]]:
|
|
138
93
|
"""
|
|
139
|
-
Lists the limitation names/values for the specified account
|
|
94
|
+
Lists the global account limitation names/values for the specified account.
|
|
95
|
+
If an RSE expression is provided, fetches the limit for that expression.
|
|
96
|
+
Otherwise, fetches all global limits for the account.
|
|
140
97
|
|
|
141
98
|
REST API: http://<host>:<port>/rucio/account/<account>/limits
|
|
142
99
|
|
|
143
100
|
:param account: The account name.
|
|
144
|
-
:param rse_expression: The
|
|
101
|
+
:param rse_expression: The RSE expression (optional; if not provided, all limits will be fetched).
|
|
145
102
|
:param vo: The VO to act on.
|
|
146
103
|
:param session: The database session in use.
|
|
147
104
|
|
|
148
|
-
:returns:
|
|
105
|
+
:returns:
|
|
106
|
+
- If `rse_expression` is provided: `{rse_expression: {...}}`
|
|
107
|
+
- If `rse_expression` is not provided: `{...}` (dictionary of all limits).
|
|
149
108
|
"""
|
|
150
109
|
|
|
151
110
|
internal_account = InternalAccount(account, vo=vo)
|
|
152
111
|
|
|
153
|
-
|
|
112
|
+
if rse_expression:
|
|
113
|
+
return {rse_expression: account_limit_core.get_global_account_limit(
|
|
114
|
+
account=internal_account, rse_expression=rse_expression, session=session
|
|
115
|
+
)}
|
|
116
|
+
|
|
117
|
+
return account_limit_core.get_global_account_limit(account=internal_account, session=session)
|
|
154
118
|
|
|
155
119
|
|
|
156
120
|
@transactional_session
|
rucio/transfertool/fts3.py
CHANGED
|
@@ -607,7 +607,7 @@ class Fts3TransferStatusReport(TransferStatusReport):
|
|
|
607
607
|
'src-type': self._file_metadata.get('src_type'),
|
|
608
608
|
'src-rse': self._src_rse,
|
|
609
609
|
'src-url': self._src_url,
|
|
610
|
-
'dst-type': self._file_metadata.get('
|
|
610
|
+
'dst-type': self._file_metadata.get('dst_type'),
|
|
611
611
|
'dst-rse': self._file_metadata.get('dst_rse'),
|
|
612
612
|
'dst-url': self._dst_url,
|
|
613
613
|
'started_at': self.started_at,
|
rucio/vcsversion.py
CHANGED
|
@@ -4,8 +4,8 @@ This file is automatically generated; Do not edit it. :)
|
|
|
4
4
|
'''
|
|
5
5
|
VERSION_INFO = {
|
|
6
6
|
'final': True,
|
|
7
|
-
'version': '37.0
|
|
8
|
-
'branch_nick': '
|
|
9
|
-
'revision_id': '
|
|
10
|
-
'revno':
|
|
7
|
+
'version': '37.1.0',
|
|
8
|
+
'branch_nick': 'release-37',
|
|
9
|
+
'revision_id': 'f6fa712c32b14e1d104349a1c2596cdcb4d8d92d',
|
|
10
|
+
'revno': 13640
|
|
11
11
|
}
|
|
@@ -21,7 +21,7 @@ from flask import Flask, Response, jsonify, redirect, request
|
|
|
21
21
|
from rucio.common.exception import AccessDenied, AccountNotFound, CounterNotFound, Duplicate, IdentityError, InvalidObject, RSENotFound, RuleNotFound, ScopeNotFound
|
|
22
22
|
from rucio.common.utils import APIEncoder, render_json
|
|
23
23
|
from rucio.gateway.account import add_account, add_account_attribute, del_account, del_account_attribute, get_account_info, get_usage_history, list_account_attributes, list_accounts, list_identities, update_account
|
|
24
|
-
from rucio.gateway.account_limit import get_global_account_limit,
|
|
24
|
+
from rucio.gateway.account_limit import get_global_account_limit, get_global_account_usage, get_local_account_limit, get_local_account_usage
|
|
25
25
|
from rucio.gateway.identity import add_account_identity, del_account_identity
|
|
26
26
|
from rucio.gateway.rule import list_replication_rules
|
|
27
27
|
from rucio.gateway.scope import add_scope, get_scopes
|
|
@@ -561,10 +561,7 @@ class LocalAccountLimits(ErrorHandlingMethodView):
|
|
|
561
561
|
description: Not Acceptable
|
|
562
562
|
"""
|
|
563
563
|
try:
|
|
564
|
-
|
|
565
|
-
limits = get_local_account_limit(account=account, rse=rse, vo=request.environ.get('vo'))
|
|
566
|
-
else:
|
|
567
|
-
limits = get_local_account_limits(account=account, vo=request.environ.get('vo'))
|
|
564
|
+
limits = get_local_account_limit(account=account, rse=rse, vo=request.environ.get('vo'))
|
|
568
565
|
except RSENotFound as error:
|
|
569
566
|
return generate_http_error_flask(404, error)
|
|
570
567
|
|
|
@@ -609,10 +606,7 @@ class GlobalAccountLimits(ErrorHandlingMethodView):
|
|
|
609
606
|
description: Not Acceptable
|
|
610
607
|
"""
|
|
611
608
|
try:
|
|
612
|
-
|
|
613
|
-
limits = get_global_account_limit(account=account, rse_expression=rse_expression, vo=request.environ.get('vo'))
|
|
614
|
-
else:
|
|
615
|
-
limits = get_global_account_limits(account=account, vo=request.environ.get('vo'))
|
|
609
|
+
limits = get_global_account_limit(account=account, rse_expression=rse_expression, vo=request.environ.get('vo'))
|
|
616
610
|
except RSENotFound as error:
|
|
617
611
|
return generate_http_error_flask(404, error)
|
|
618
612
|
|