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/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
|
-
|
|
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
|
-
|
|
860
|
-
|
|
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
|
-
|
|
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]:
|
rucio/core/account_limit.py
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
-
:
|
|
99
|
-
|
|
100
|
-
:
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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)
|
rucio/core/rse_selector.py
CHANGED
|
@@ -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
|
|
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 =
|
|
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,
|
|
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:
|
rucio/core/rule_grouping.py
CHANGED
|
@@ -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
|
rucio/daemons/cache/consumer.py
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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 =
|
|
96
|
+
rse_vo_str = '{} on {}'.format(rse_vo_str, msg['vo'])
|
|
71
97
|
if msg['operation'] == 'add_replicas':
|
|
72
|
-
self.
|
|
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.
|
|
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.
|
|
104
|
+
self.__logger(logging.DEBUG, 'Check failed: %s %s '
|
|
105
|
+
% (isinstance(msg, dict), 'operation' in msg.keys()))
|
|
79
106
|
except:
|
|
80
|
-
self.
|
|
107
|
+
self.__logger(logging.ERROR, str(format_exc()))
|
|
81
108
|
|
|
82
109
|
|
|
83
|
-
def consumer(id_: int, num_thread: int = 1
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
166
|
+
stomp_conn_mngr.disconnect()
|
|
102
167
|
logger(logging.INFO, 'graceful stop done')
|
|
103
168
|
|
|
104
169
|
|
|
105
|
-
def stop(signum:
|
|
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
|
-
|
|
124
|
-
threads = [
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
193
|
+
logging.info('waiting for interrupts')
|
|
131
194
|
|
|
132
|
-
|
|
133
|
-
|
|
195
|
+
# Interruptible joins require a timeout.
|
|
196
|
+
while threads[0].is_alive():
|
|
197
|
+
[t.join(timeout=3.14) for t in threads]
|