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
rucio/common/utils.py CHANGED
@@ -852,27 +852,24 @@ def get_bytes_value_from_string(input_string: str) -> Union[bool, int]:
852
852
  :param input_string: String containing a value and an unit
853
853
  :return: Integer value representing the value in bytes
854
854
  """
855
- result = re.findall('^([0-9]+)([A-Za-z]+)$', input_string)
855
+ unit_multipliers = {
856
+ 'b': 1,
857
+ 'kb': 10**3,
858
+ 'mb': 10**6,
859
+ 'gb': 10**9,
860
+ 'tb': 10**12,
861
+ 'pb': 10**15,
862
+ }
863
+
864
+ result = re.findall(r'^([0-9]+)([A-Za-z]+)$', input_string)
856
865
  if result:
857
866
  value = int(result[0][0])
858
867
  unit = result[0][1].lower()
859
- if unit == 'b':
860
- value = value
861
- elif unit == 'kb':
862
- value = value * 1000
863
- elif unit == 'mb':
864
- value = value * 1000000
865
- elif unit == 'gb':
866
- value = value * 1000000000
867
- elif unit == 'tb':
868
- value = value * 1000000000000
869
- elif unit == 'pb':
870
- value = value * 1000000000000000
871
- else:
868
+ multiplier = unit_multipliers.get(unit)
869
+ if multiplier is None:
872
870
  return False
873
- return value
874
- else:
875
- return False
871
+ return value * multiplier
872
+ return False
876
873
 
877
874
 
878
875
  def parse_did_filter_from_string(input_string: str) -> tuple[dict[str, Any], str]:
@@ -18,13 +18,16 @@ from sqlalchemy.exc import NoResultFound
18
18
  from sqlalchemy.sql import func, literal, select
19
19
  from sqlalchemy.sql.expression import and_, or_
20
20
 
21
- from rucio.core.account import get_all_rse_usages_per_account
21
+ from rucio.common.exception import AccountNotFound
22
+ from rucio.core.account import account_exists, get_all_rse_usages_per_account
22
23
  from rucio.core.rse import get_rse_name
23
24
  from rucio.core.rse_expression_parser import parse_expression
24
25
  from rucio.db.sqla import models
25
26
  from rucio.db.sqla.session import read_session, transactional_session
26
27
 
27
28
  if TYPE_CHECKING:
29
+ from collections.abc import Iterable
30
+
28
31
  from sqlalchemy.orm import Session
29
32
 
30
33
  from rucio.common.types import InternalAccount, RSEAccountUsageDict, RSEGlobalAccountUsageDict, RSELocalAccountUsageDict, RSEResolvedGlobalAccountLimitDict
@@ -91,26 +94,39 @@ def get_rse_account_usage(rse_id: str, *, session: "Session") -> list["RSEAccoun
91
94
 
92
95
 
93
96
  @read_session
94
- def get_global_account_limits(account: Optional["InternalAccount"] = None, *, session: "Session") -> dict[str, "RSEResolvedGlobalAccountLimitDict"]:
97
+ def get_global_account_limit(account: Optional["InternalAccount"] = None, rse_expression: Optional[str] = None, *,
98
+ session: "Session") -> Optional[Union[int, float, dict[str, "RSEResolvedGlobalAccountLimitDict"]]]:
95
99
  """
96
- Returns the global account limits for the account.
100
+ Returns the global account limit for the given account and RSE expression.
101
+ If no RSE expression is provided, returns all limits for the given account.
97
102
 
98
- :param account: Account to check the limit for.
99
- :param session: Database session in use.
100
- :return: Dict {'MOCK': {'resolved_rses': ['MOCK'], 'limit': 10, 'resolved_rse_ids': [123]}}.
103
+ Note: The account parameter must always be specified when fetching global account limit for an account.
104
+
105
+ :param account: Account to check the limit for (required unless fetching all limits for all accounts is supported).
106
+ :param rse_expression: Specific RSE expression to check the limit for (optional).
107
+ :param session: Database session in use.
108
+ :return: Limit in Bytes for a single RSE expression, or a dictionary of all limits {'MOCK': {'resolved_rses': ['MOCK'], 'limit': 10, 'resolved_rse_ids': [123]}}.
101
109
  """
102
110
  if account:
103
- stmt = select(
104
- models.AccountGlobalLimit
105
- ).where(
106
- models.AccountGlobalLimit.account == account
107
- )
108
- global_account_limits = session.execute(stmt).scalars().all()
109
- else:
110
- stmt = select(
111
- models.AccountGlobalLimit
112
- )
113
- global_account_limits = session.execute(stmt).scalars().all()
111
+ if not account_exists(account, session=session):
112
+ raise AccountNotFound(f"Account {account} does not exist")
113
+ if rse_expression:
114
+ # Fetch limit for a single RSE expression
115
+ try:
116
+ stmt = select(models.AccountGlobalLimit).where(
117
+ and_(models.AccountGlobalLimit.account == account,
118
+ models.AccountGlobalLimit.rse_expression == rse_expression)
119
+ )
120
+ global_account_limit = session.execute(stmt).scalar_one()
121
+ return float("inf") if global_account_limit.bytes == -1 else global_account_limit.bytes
122
+ except NoResultFound:
123
+ return None
124
+
125
+ # Fetch all global limits for the account (or all accounts if no account specified)
126
+ stmt = select(models.AccountGlobalLimit)
127
+ if account:
128
+ stmt = stmt.where(models.AccountGlobalLimit.account == account)
129
+ global_account_limits = session.execute(stmt).scalars().all()
114
130
 
115
131
  resolved_global_account_limits = {}
116
132
  for limit in global_account_limits:
@@ -118,9 +134,7 @@ def get_global_account_limits(account: Optional["InternalAccount"] = None, *, se
118
134
  resolved_rses = parse_expression(limit['rse_expression'], filter_={'vo': account.vo}, session=session)
119
135
  else:
120
136
  resolved_rses = parse_expression(limit['rse_expression'], session=session)
121
- limit_in_bytes = limit['bytes']
122
- if limit_in_bytes == -1:
123
- limit_in_bytes = float('inf')
137
+ limit_in_bytes = float('inf') if limit['bytes'] == -1 else limit['bytes']
124
138
  resolved_global_account_limits[limit['rse_expression']] = {
125
139
  'resolved_rses': [resolved_rse['rse'] for resolved_rse in resolved_rses],
126
140
  'resolved_rse_ids': [resolved_rse['id'] for resolved_rse in resolved_rses],
@@ -130,69 +144,32 @@ def get_global_account_limits(account: Optional["InternalAccount"] = None, *, se
130
144
 
131
145
 
132
146
  @read_session
133
- def get_global_account_limit(account: "InternalAccount", rse_expression: str, *, session: "Session") -> Union[int, float, None]:
147
+ def get_local_account_limit(account: "InternalAccount", rse_ids: Optional[Union[str, "Iterable[str]"]] = None, *, session: "Session") -> Optional[Union[int, float, dict[str, int]]]:
134
148
  """
135
- Returns the global account limit for the account on the rse expression.
136
-
137
- :param account: Account to check the limit for.
138
- :param rse_expression: RSE expression to check the limit for.
139
- :param session: Database session in use.
140
- :return: Limit in Bytes.
141
- """
142
- try:
143
- stmt = select(
144
- models.AccountGlobalLimit
145
- ).where(
146
- and_(models.AccountGlobalLimit.account == account,
147
- models.AccountGlobalLimit.rse_expression == rse_expression)
148
- )
149
- global_account_limit = session.execute(stmt).scalar_one()
150
- if global_account_limit.bytes == -1:
151
- return float("inf")
152
- else:
153
- return global_account_limit.bytes
154
- except NoResultFound:
155
- return None
156
-
157
-
158
- @read_session
159
- def get_local_account_limit(account: "InternalAccount", rse_id: str, *, session: "Session") -> Union[int, float, None]:
160
- """
161
- Returns the account limit for the account on the rse.
162
-
163
- :param account: Account to check the limit for.
164
- :param rse_id: RSE id to check the limit for.
165
- :param session: Database session in use.
166
- :return: Limit in Bytes.
167
- """
168
- try:
169
- stmt = select(
170
- models.AccountLimit
171
- ).where(
172
- and_(models.AccountLimit.account == account,
173
- models.AccountLimit.rse_id == rse_id)
174
- )
175
- account_limit = session.execute(stmt).scalar_one()
176
- if account_limit.bytes == -1:
177
- return float("inf")
178
- else:
179
- return account_limit.bytes
180
- except NoResultFound:
181
- return None
182
-
183
-
184
- @read_session
185
- def get_local_account_limits(account: "InternalAccount", rse_ids: Optional[list[str]] = None, *, session: "Session") -> dict[str, int]:
186
- """
187
- Returns the account limits for the account on the list of rses.
149
+ Returns the local account limit for a given RSE or list of RSEs.
188
150
 
189
151
  :param account: Account to check the limit for.
190
- :param rse_ids: List of RSE ids to check the limit for.
152
+ :param rse_ids: Single RSE id or an iterable of RSE ids to check the limit for.
191
153
  :param session: Database session in use.
192
- :return: Dictionary {'rse_id': bytes, ...}.
154
+ :return: Limit in Bytes (int/float) for a single RSE or
155
+ Dictionary {'rse_id': bytes, ...} for multiple RSEs.
193
156
  """
157
+ if not account_exists(account, session=session):
158
+ raise AccountNotFound(f"Account {account} does not exist")
159
+ if isinstance(rse_ids, str): # Single RSE case
160
+ try:
161
+ stmt = select(models.AccountLimit).where(
162
+ and_(models.AccountLimit.account == account, models.AccountLimit.rse_id == rse_ids)
163
+ )
164
+ account_limit = session.execute(stmt).scalar_one()
165
+ return float("inf") if account_limit.bytes == -1 else account_limit.bytes
166
+ except NoResultFound:
167
+ return None
194
168
 
169
+ # Multiple RSE case or no RSE specified
195
170
  account_limits = {}
171
+
172
+ # If rse_ids is a list of RSEs
196
173
  if rse_ids:
197
174
  rse_id_clauses = []
198
175
  for rse_id in rse_ids:
@@ -339,14 +316,14 @@ def get_local_account_usage(account: "InternalAccount", rse_id: Optional[str] =
339
316
  )
340
317
  if not rse_id:
341
318
  # All RSESs
342
- limits = get_local_account_limits(account=account, session=session)
319
+ limits = get_local_account_limit(account=account, session=session)
343
320
  counters = {c.rse_id: c for c in session.execute(stmt).scalars().all()}
344
321
  else:
345
322
  # One RSE
346
323
  stmt.where(
347
324
  models.AccountUsage.rse_id == rse_id
348
325
  )
349
- limits = get_local_account_limits(account=account, rse_ids=[rse_id], session=session)
326
+ limits = get_local_account_limit(account=account, rse_ids=[rse_id], session=session)
350
327
  counters = {c.rse_id: c for c in session.execute(stmt).scalars().all()}
351
328
  result_list = []
352
329
 
@@ -385,7 +362,7 @@ def get_global_account_usage(account: "InternalAccount", rse_expression: Optiona
385
362
  result_list = []
386
363
  if not rse_expression:
387
364
  # All RSE Expressions
388
- limits = get_global_account_limits(account=account, session=session)
365
+ limits = get_global_account_limit(account=account, session=session)
389
366
  all_rse_usages = {usage['rse_id']: (usage['bytes'], usage['files']) for usage in get_all_rse_usages_per_account(account=account, session=session)}
390
367
  for rse_expression, limit in limits.items():
391
368
  usage = 0
@@ -102,9 +102,7 @@ class FilterEngine:
102
102
  filters, _ = parse_did_filter_from_string_fe(filters, omit_name=True)
103
103
  elif isinstance(filters, dict):
104
104
  filters = [filters]
105
- elif isinstance(filters, list):
106
- filters = filters
107
- else:
105
+ elif not isinstance(filters, list):
108
106
  raise exception.DIDFilterSyntaxError("Input filters are of an unrecognised type.")
109
107
 
110
108
  filters = self._make_input_backwards_compatible(filters=filters)
@@ -17,7 +17,7 @@ from typing import TYPE_CHECKING, Optional
17
17
 
18
18
  from rucio.common.exception import InsufficientAccountLimit, InsufficientTargetRSEs, InvalidRuleWeight, RSEOverQuota
19
19
  from rucio.core.account import get_all_rse_usages_per_account, get_usage, has_account_attribute
20
- from rucio.core.account_limit import get_global_account_limits, get_local_account_limit
20
+ from rucio.core.account_limit import get_global_account_limit, get_local_account_limit
21
21
  from rucio.core.rse import get_rse_limits, has_rse_attribute, list_rse_attributes
22
22
  from rucio.core.rse_counter import get_counter as get_rse_counter
23
23
  from rucio.core.rse_expression_parser import parse_expression
@@ -86,7 +86,7 @@ class RSESelector:
86
86
  rse['space_left'] = float('inf')
87
87
  rses_with_enough_quota.append(rse)
88
88
  else:
89
- global_quota_limit = get_global_account_limits(account=account, session=session)
89
+ global_quota_limit = get_global_account_limit(account=account, session=session)
90
90
  all_rse_usages = {usage['rse_id']: usage['bytes'] for usage in get_all_rse_usages_per_account(account=account, session=session)}
91
91
  for rse in self.rses:
92
92
  if rse['mock_rse']:
@@ -96,7 +96,7 @@ class RSESelector:
96
96
  else:
97
97
  # check local quota
98
98
  local_quota_left = None
99
- quota_limit = get_local_account_limit(account=account, rse_id=rse['rse_id'], session=session)
99
+ quota_limit = get_local_account_limit(account=account, rse_ids=rse['rse_id'], session=session)
100
100
  if quota_limit is None:
101
101
  local_quota_left = 0
102
102
  else:
@@ -1617,4 +1617,3 @@ def apply_rule(did, rule, rses, source_rses, rseselector, *, session: "Session",
1617
1617
  account_counter.increase(rse_id=rse_id, account=rule.account, files=account_counters_files[rse_id], bytes_=account_counters_bytes[rse_id], session=session)
1618
1618
  session.flush()
1619
1619
 
1620
- return
@@ -21,12 +21,13 @@ import logging
21
21
  import threading
22
22
  import time
23
23
  from traceback import format_exc
24
- from typing import TYPE_CHECKING
24
+ from typing import TYPE_CHECKING, Optional
25
25
 
26
26
  import rucio.db.sqla.util
27
27
  from rucio.common import exception
28
+ from rucio.common.config import config_get, config_get_bool, config_get_int, config_get_list
28
29
  from rucio.common.logging import formatted_logger, setup_logging
29
- from rucio.common.stomp_utils import ListenerBase, StompConnectionManager
30
+ from rucio.common.stomp_utils import StompConnectionManager
30
31
  from rucio.common.types import InternalScope, LoggerFunction
31
32
  from rucio.core.monitor import MetricManager
32
33
  from rucio.core.rse import get_rse_id
@@ -35,6 +36,7 @@ from rucio.core.volatile_replica import add_volatile_replicas, delete_volatile_r
35
36
  if TYPE_CHECKING:
36
37
  from types import FrameType
37
38
 
39
+ from stomp import Connection
38
40
  from stomp.utils import Frame
39
41
 
40
42
  logging.getLogger("stomp").setLevel(logging.CRITICAL)
@@ -44,11 +46,35 @@ GRACEFUL_STOP = threading.Event()
44
46
  DAEMON_NAME = 'cache-consumer'
45
47
 
46
48
 
47
- class AMQConsumer(ListenerBase):
49
+ class AMQConsumer:
48
50
  """
49
51
  class Consumer
50
52
  """
51
53
 
54
+ def __init__(
55
+ self,
56
+ broker: str,
57
+ conn: "Connection",
58
+ logger: "LoggerFunction"
59
+ ):
60
+ """
61
+ __init__
62
+ """
63
+ self.__broker = broker
64
+ self.__conn = conn
65
+ self.__logger = logger
66
+
67
+ @METRICS.count_it
68
+ def on_heartbeat_timeout(self) -> None:
69
+ self.__conn.disconnect()
70
+
71
+ @METRICS.count_it
72
+ def on_error(self, frame: "Frame") -> None:
73
+ """
74
+ on_error
75
+ """
76
+ self.__logger(logging.ERROR, 'Message receive error: [%s] %s' % (self.__broker, frame.body))
77
+
52
78
  @METRICS.count_it
53
79
  def on_message(self, frame: "Frame") -> None:
54
80
  """
@@ -56,7 +82,7 @@ class AMQConsumer(ListenerBase):
56
82
  """
57
83
  try:
58
84
  msg = json.loads(frame.body) # type: ignore
59
- self._logger(logging.DEBUG, 'Message received: %s', msg)
85
+ self.__logger(logging.DEBUG, 'Message received: %s ' % msg)
60
86
  if isinstance(msg, dict) and 'operation' in msg.keys():
61
87
  for f in msg['files']:
62
88
  f['scope'] = InternalScope(f['scope'])
@@ -67,42 +93,81 @@ class AMQConsumer(ListenerBase):
67
93
 
68
94
  rse_vo_str = msg['rse']
69
95
  if 'vo' in msg and msg['vo'] != 'def':
70
- rse_vo_str = f"{rse_vo_str} on {msg['vo']}"
96
+ rse_vo_str = '{} on {}'.format(rse_vo_str, msg['vo'])
71
97
  if msg['operation'] == 'add_replicas':
72
- self._logger(logging.INFO, "add_replicas to RSE %s: %s", rse_vo_str, str(msg['files']))
98
+ self.__logger(logging.INFO, 'add_replicas to RSE %s: %s ' % (rse_vo_str, str(msg['files'])))
73
99
  add_volatile_replicas(rse_id=rse_id, replicas=msg['files'])
74
100
  elif msg['operation'] == 'delete_replicas':
75
- self._logger(logging.INFO, "delete_replicas to RSE %s: %s", rse_vo_str, str(msg['files']))
101
+ self.__logger(logging.INFO, 'delete_replicas to RSE %s: %s ' % (rse_vo_str, str(msg['files'])))
76
102
  delete_volatile_replicas(rse_id=rse_id, replicas=msg['files'])
77
103
  else:
78
- self._logger(logging.DEBUG, 'Check failed: %s %s', isinstance(msg, dict), "operation" in msg.keys())
104
+ self.__logger(logging.DEBUG, 'Check failed: %s %s '
105
+ % (isinstance(msg, dict), 'operation' in msg.keys()))
79
106
  except:
80
- self._logger(logging.ERROR, str(format_exc()))
107
+ self.__logger(logging.ERROR, str(format_exc()))
81
108
 
82
109
 
83
- def consumer(id_: int, num_thread: int = 1, logger: LoggerFunction = logging.log) -> None:
110
+ def consumer(id_: int, num_thread: int = 1) -> None:
84
111
  """
85
112
  Main loop to consume messages from the Rucio Cache producer.
86
113
  """
114
+
115
+ logger = formatted_logger(logging.log, DAEMON_NAME + ' %s')
116
+
87
117
  logger(logging.INFO, 'Rucio Cache consumer starting')
88
118
 
89
- conn_mgr = StompConnectionManager(config_section='messaging-cache', logger=logger)
119
+ brokers = config_get_list('messaging-cache', 'brokers')
120
+
121
+ use_ssl = config_get_bool('messaging-cache', 'use_ssl', default=True, raise_exception=False)
122
+ if not use_ssl:
123
+ username = config_get('messaging-cache', 'username')
124
+ password = config_get('messaging-cache', 'password')
125
+ destination = config_get('messaging-cache', 'destination')
126
+ subscription_id = 'rucio-cache-messaging'
127
+
128
+ vhost = config_get('messaging-cache', 'broker_virtual_host', raise_exception=False)
129
+ port = config_get_int('messaging-cache', 'port')
130
+ reconnect_attempts = config_get_int('messaging-cache', 'reconnect_attempts', default=100)
131
+ ssl_key_file = config_get('messaging-cache', 'ssl_key_file', raise_exception=False)
132
+ ssl_cert_file = config_get('messaging-cache', 'ssl_cert_file', raise_exception=False)
133
+
134
+ stomp_conn_mngr = StompConnectionManager()
135
+ conns, _ = stomp_conn_mngr.re_configure(
136
+ brokers=brokers,
137
+ port=port,
138
+ use_ssl=use_ssl,
139
+ vhost=vhost,
140
+ reconnect_attempts=reconnect_attempts,
141
+ ssl_key_file=ssl_key_file,
142
+ ssl_cert_file=ssl_cert_file,
143
+ timeout=None,
144
+ logger=logger
145
+ )
90
146
 
91
147
  logger(logging.INFO, 'consumer started')
92
148
 
93
- conn_mgr.set_listener_factory('rucio-cache-consumer', AMQConsumer, heartbeats=conn_mgr.config.heartbeats)
94
-
95
149
  while not GRACEFUL_STOP.is_set():
150
+ for conn in conns:
151
+ if not conn.is_connected():
152
+ host_port = conn.transport._Transport__host_and_ports[0]
153
+
154
+ logger(logging.INFO, 'connecting to %s' % host_port[0])
155
+ METRICS.counter('reconnect.{host}').labels(host=host_port[0]).inc()
156
+ conn.set_listener('rucio-cache-consumer', AMQConsumer(broker=host_port, conn=conn, logger=logger))
157
+ if not use_ssl:
158
+ conn.connect(username, password)
159
+ else:
160
+ conn.connect()
96
161
 
97
- conn_mgr.subscribe(id_='rucio-cache-messaging', ack='auto')
162
+ conn.subscribe(destination=destination, ack='auto', id=subscription_id)
98
163
  time.sleep(1)
99
164
 
100
165
  logger(logging.INFO, 'graceful stop requested')
101
- conn_mgr.disconnect()
166
+ stomp_conn_mngr.disconnect()
102
167
  logger(logging.INFO, 'graceful stop done')
103
168
 
104
169
 
105
- def stop(signum: "int | None" = None, frame: "FrameType | None" = None) -> None:
170
+ def stop(signum: Optional[int] = None, frame: Optional["FrameType"] = None) -> None:
106
171
  """
107
172
  Graceful exit.
108
173
  """
@@ -115,19 +180,18 @@ def run(num_thread: int = 1) -> None:
115
180
  Starts up the rucio cache consumer thread
116
181
  """
117
182
  setup_logging(process_name=DAEMON_NAME)
118
- logger = formatted_logger(logging.log, DAEMON_NAME + ' %s')
119
183
 
120
184
  if rucio.db.sqla.util.is_old_db():
121
185
  raise exception.DatabaseException('Database was not updated, daemon won\'t start')
122
186
 
123
- logger(logging.INFO, 'starting consumer thread')
124
- threads = []
125
- for i in range(num_thread):
126
- con_thread = threading.Thread(target=consumer, kwargs={'id_': i, 'num_thread': num_thread, 'logger': logger})
127
- con_thread.start()
128
- threads.append(con_thread)
187
+ logging.info('starting consumer thread')
188
+ threads = [threading.Thread(target=consumer, kwargs={'id_': i, 'num_thread': num_thread})
189
+ for i in range(0, num_thread)]
190
+
191
+ [t.start() for t in threads]
129
192
 
130
- logger(logging.INFO, 'waiting for interrupts')
193
+ logging.info('waiting for interrupts')
131
194
 
132
- while [thread.join(timeout=3.14) for thread in threads if thread.is_alive()]:
133
- pass
195
+ # Interruptible joins require a timeout.
196
+ while threads[0].is_alive():
197
+ [t.join(timeout=3.14) for t in threads]