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.

Files changed (90) hide show
  1. rucio/cli/account.py +14 -14
  2. rucio/cli/command.py +9 -9
  3. rucio/cli/config.py +3 -3
  4. rucio/cli/did.py +13 -13
  5. rucio/cli/lifetime_exception.py +1 -1
  6. rucio/cli/replica.py +3 -3
  7. rucio/cli/rse.py +18 -18
  8. rucio/cli/rule.py +5 -5
  9. rucio/cli/scope.py +2 -2
  10. rucio/cli/subscription.py +4 -4
  11. rucio/client/baseclient.py +0 -3
  12. rucio/client/lifetimeclient.py +46 -13
  13. rucio/common/config.py +0 -26
  14. rucio/common/stomp_utils.py +119 -383
  15. rucio/common/utils.py +14 -17
  16. rucio/core/account_limit.py +56 -79
  17. rucio/core/did_meta_plugins/filter_engine.py +1 -3
  18. rucio/core/rse_selector.py +3 -3
  19. rucio/core/rule_grouping.py +0 -1
  20. rucio/daemons/cache/consumer.py +90 -26
  21. rucio/daemons/conveyor/receiver.py +123 -53
  22. rucio/daemons/hermes/hermes.py +343 -41
  23. rucio/daemons/tracer/kronos.py +139 -114
  24. rucio/gateway/account_limit.py +40 -76
  25. rucio/transfertool/fts3.py +1 -1
  26. rucio/vcsversion.py +4 -4
  27. rucio/web/rest/flaskapi/v1/accounts.py +3 -9
  28. rucio/web/rest/flaskapi/v1/auth.py +3 -3
  29. rucio/web/rest/flaskapi/v1/common.py +10 -4
  30. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-replica-recoverer +1 -1
  31. {rucio-37.0.0rc3.dist-info → rucio-37.1.0.dist-info}/METADATA +1 -1
  32. {rucio-37.0.0rc3.dist-info → rucio-37.1.0.dist-info}/RECORD +90 -90
  33. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/alembic.ini.template +0 -0
  34. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/alembic_offline.ini.template +0 -0
  35. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/globus-config.yml.template +0 -0
  36. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/ldap.cfg.template +0 -0
  37. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/mail_templates/rule_approval_request.tmpl +0 -0
  38. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +0 -0
  39. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/mail_templates/rule_approved_user.tmpl +0 -0
  40. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +0 -0
  41. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/mail_templates/rule_denied_user.tmpl +0 -0
  42. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +0 -0
  43. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/rse-accounts.cfg.template +0 -0
  44. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/rucio.cfg.atlas.client.template +0 -0
  45. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/rucio.cfg.template +0 -0
  46. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/etc/rucio_multi_vo.cfg.template +0 -0
  47. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/requirements.server.txt +0 -0
  48. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/tools/bootstrap.py +0 -0
  49. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/tools/merge_rucio_configs.py +0 -0
  50. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/data/rucio/tools/reset_database.py +0 -0
  51. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio +0 -0
  52. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-abacus-account +0 -0
  53. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-abacus-collection-replica +0 -0
  54. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-abacus-rse +0 -0
  55. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-admin +0 -0
  56. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-atropos +0 -0
  57. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-auditor +0 -0
  58. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-automatix +0 -0
  59. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-bb8 +0 -0
  60. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-cache-client +0 -0
  61. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-cache-consumer +0 -0
  62. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-conveyor-finisher +0 -0
  63. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-conveyor-poller +0 -0
  64. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-conveyor-preparer +0 -0
  65. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-conveyor-receiver +0 -0
  66. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-conveyor-stager +0 -0
  67. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-conveyor-submitter +0 -0
  68. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-conveyor-throttler +0 -0
  69. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-dark-reaper +0 -0
  70. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-dumper +0 -0
  71. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-follower +0 -0
  72. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-hermes +0 -0
  73. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-judge-cleaner +0 -0
  74. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-judge-evaluator +0 -0
  75. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-judge-injector +0 -0
  76. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-judge-repairer +0 -0
  77. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-kronos +0 -0
  78. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-minos +0 -0
  79. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-minos-temporary-expiration +0 -0
  80. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-necromancer +0 -0
  81. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-oauth-manager +0 -0
  82. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-reaper +0 -0
  83. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-rse-decommissioner +0 -0
  84. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-storage-consistency-actions +0 -0
  85. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-transmogrifier +0 -0
  86. {rucio-37.0.0rc3.data → rucio-37.1.0.data}/scripts/rucio-undertaker +0 -0
  87. {rucio-37.0.0rc3.dist-info → rucio-37.1.0.dist-info}/WHEEL +0 -0
  88. {rucio-37.0.0rc3.dist-info → rucio-37.1.0.dist-info}/licenses/AUTHORS.rst +0 -0
  89. {rucio-37.0.0rc3.dist-info → rucio-37.1.0.dist-info}/licenses/LICENSE +0 -0
  90. {rucio-37.0.0rc3.dist-info → rucio-37.1.0.dist-info}/top_level.txt +0 -0
@@ -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 formatted_logger, setup_logging
34
- from rucio.common.stomp_utils import ListenerBase, StompConnectionManager
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(ListenerBase):
60
+ class AMQConsumer:
61
61
  """ActiveMQ message consumer"""
62
62
 
63
- def __init__(self,
64
- conn: "Connection",
65
- queue: str,
66
- chunksize: int,
67
- subscription_id: str,
68
- excluded_usrdns: "Set[str]",
69
- dataset_queue: Queue,
70
- bad_files_patterns: list[re.Pattern],
71
- logger: LoggerFunction = logging.log,
72
- **kwargs: dict) -> None:
73
- super().__init__(conn=conn, logger=logger, **kwargs)
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._conn.ack(msg_id, self.__subscription_id)
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._logger(logging.ERROR, 'json error', exc_info=True)
107
- self._conn.ack(msg_id, self.__subscription_id)
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._logger(logging.DEBUG,
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._conn.ack(msg_id, self.__subscription_id)
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._logger(logging.ERROR, 'Missing url in the following trace : ' + str(report))
156
+ self.__logger(logging.ERROR, 'Missing url in the following trace : ' + str(report))
149
157
  else:
150
158
  try:
151
- surl = report['url']
152
- declare_bad_file_replicas([surl, ], reason=reason, issuer=InternalAccount('root', vo=report['vo']), status=BadFilesStatus.SUSPICIOUS)
153
- self._logger(logging.INFO,
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._logger(logging.ERROR, 'Failed to declare suspicious file' + str(error))
163
+ self.__logger(logging.ERROR, 'Failed to declare suspicious file' + str(error))
159
164
  except Exception as error:
160
- self._logger(logging.ERROR, 'Problem with bad trace : %s . Error %s', str(report), str(error))
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._logger(logging.WARNING, "Cannot lookup rse_id for %s. Will skip this report.", rse)
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._logger(logging.WARNING, "Cannot lookup rse_id for %s.", rse)
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._logger(logging.ERROR, "Cannot handle report.", exc_info=True)
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._logger(logging.ERROR, "Exception", exc_info=True)
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._logger(logging.WARNING, "Cannot lookup rse_id for %s. Will skip this report.", rse)
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._logger(logging.DEBUG, "trying to update replicas: %s", replicas)
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._conn.send(body=jdumps(resubmit), destination=self.__queue, headers={'appversion': 'rucio', 'resubmitted': '1'})
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._logger(logging.ERROR, "Cannot update replicas.", exc_info=True)
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(once: bool = False,
310
- dataset_queue: "Queue | None" = None,
311
- sleep_time: int = 60,
312
- logger: LoggerFunction = logging.log) -> None:
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
- bad_files_patterns = []
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
- stomp_conn_mngr.subscribe(id_=config_get('tracer-kronos', 'subscription_id'),
370
- ack='client-individual',
371
- destination=config_get('tracer-kronos', 'queue'),
372
- headers={'activemq.prefetchSize': config_get_int('tracer-kronos', 'prefetch_size')})
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: "int | None" = None, frame: "FrameType | None" = None) -> None:
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) -> None:
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
- logger(logging.INFO, 'starting tracer consumer threads')
520
+ logging.info('starting tracer consumer threads')
493
521
 
494
522
  thread_list = []
495
- for _ in range(threads):
496
- krf_thread = Thread(target=kronos_file, kwargs={'once': once,
497
- 'logger': logger,
498
- 'sleep_time': sleep_time_files,
499
- 'dataset_queue': dataset_queue})
500
- krf_thread.start()
501
- krd_thread = Thread(target=kronos_dataset, kwargs={'once': once,
502
- 'sleep_time': sleep_time_datasets,
503
- 'dataset_queue': dataset_queue})
504
- krd_thread.start()
505
- thread_list.append(krf_thread)
506
- thread_list.append(krd_thread)
507
-
508
- logger(logging.INFO, 'waiting for interrupts')
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()]
@@ -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
- account: str,
80
- rse: str,
81
- vo: str = 'def',
82
- *,
83
- session: "Session"
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 and rse 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: The account name.
91
- :param rse: The rse name.
92
- :param vo: The VO to act on.
93
- :param session: The database session in use.
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: The account limit.
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
- rse_id = get_rse_id(rse=rse, vo=vo, session=session)
101
- return {rse: account_limit_core.get_local_account_limit(account=internal_account, rse_id=rse_id, session=session)}
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
- internal_account = InternalAccount('*', vo=vo)
126
-
127
- return account_limit_core.get_global_account_limits(account=internal_account, session=session)
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
- account: str,
133
- rse_expression: str,
134
- vo: str = 'def',
135
- *,
136
- session: "Session"
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 name and rse expression.
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 rse expression.
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: The account limit.
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
- return {rse_expression: account_limit_core.get_global_account_limit(account=internal_account, rse_expression=rse_expression, session=session)}
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
@@ -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('src_type'),
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.0rc3',
8
- 'branch_nick': 'master',
9
- 'revision_id': 'bf26fe877a4d5e7d7a44f486fd95fc6ac7395238',
10
- 'revno': 13611
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, get_global_account_limits, get_global_account_usage, get_local_account_limit, get_local_account_limits, get_local_account_usage
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
- if rse:
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
- if rse_expression:
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