rucio 38.2.0__py3-none-any.whl → 38.3.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/bin_legacy/rucio.py +26 -23
- rucio/cli/command.py +36 -26
- rucio/cli/config.py +22 -7
- rucio/cli/did.py +1 -1
- rucio/cli/download.py +1 -1
- rucio/cli/opendata.py +59 -8
- rucio/cli/utils.py +13 -1
- rucio/client/configclient.py +23 -0
- rucio/client/richclient.py +6 -0
- rucio/common/constants.py +5 -0
- rucio/common/exception.py +10 -0
- rucio/common/plugins.py +24 -8
- rucio/common/utils.py +3 -3
- rucio/core/config.py +8 -6
- rucio/core/did_meta_plugins/did_column_meta.py +226 -69
- rucio/core/replica.py +3 -4
- rucio/core/request.py +1 -1
- rucio/core/rule.py +6 -3
- rucio/core/rule_grouping.py +2 -2
- rucio/gateway/config.py +2 -37
- rucio/gateway/rule.py +2 -2
- rucio/rse/translation.py +3 -3
- rucio/transfertool/fts3_plugins.py +3 -3
- rucio/vcsversion.py +3 -3
- rucio/web/rest/flaskapi/v1/config.py +52 -14
- {rucio-38.2.0.dist-info → rucio-38.3.0.dist-info}/METADATA +1 -1
- {rucio-38.2.0.dist-info → rucio-38.3.0.dist-info}/RECORD +86 -86
- {rucio-38.2.0.data → rucio-38.3.0.data}/data/rucio/etc/alembic.ini.template +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/data/rucio/etc/alembic_offline.ini.template +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/data/rucio/etc/globus-config.yml.template +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/data/rucio/etc/ldap.cfg.template +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/data/rucio/etc/mail_templates/rule_approval_request.tmpl +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/data/rucio/etc/mail_templates/rule_approved_user.tmpl +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/data/rucio/etc/mail_templates/rule_denied_user.tmpl +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/data/rucio/etc/rse-accounts.cfg.template +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/data/rucio/etc/rucio.cfg.atlas.client.template +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/data/rucio/etc/rucio.cfg.template +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/data/rucio/etc/rucio_multi_vo.cfg.template +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/data/rucio/requirements.server.txt +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/data/rucio/tools/bootstrap.py +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/data/rucio/tools/merge_rucio_configs.py +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/data/rucio/tools/reset_database.py +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-abacus-account +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-abacus-collection-replica +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-abacus-rse +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-admin +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-atropos +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-auditor +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-automatix +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-bb8 +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-cache-client +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-cache-consumer +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-conveyor-finisher +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-conveyor-poller +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-conveyor-preparer +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-conveyor-receiver +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-conveyor-stager +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-conveyor-submitter +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-conveyor-throttler +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-dark-reaper +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-dumper +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-follower +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-hermes +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-judge-cleaner +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-judge-evaluator +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-judge-injector +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-judge-repairer +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-kronos +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-minos +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-minos-temporary-expiration +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-necromancer +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-oauth-manager +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-reaper +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-replica-recoverer +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-rse-decommissioner +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-storage-consistency-actions +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-transmogrifier +0 -0
- {rucio-38.2.0.data → rucio-38.3.0.data}/scripts/rucio-undertaker +0 -0
- {rucio-38.2.0.dist-info → rucio-38.3.0.dist-info}/WHEEL +0 -0
- {rucio-38.2.0.dist-info → rucio-38.3.0.dist-info}/licenses/AUTHORS.rst +0 -0
- {rucio-38.2.0.dist-info → rucio-38.3.0.dist-info}/licenses/LICENSE +0 -0
- {rucio-38.2.0.dist-info → rucio-38.3.0.dist-info}/top_level.txt +0 -0
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import operator
|
|
16
16
|
from datetime import datetime, timedelta
|
|
17
|
-
from typing import TYPE_CHECKING
|
|
17
|
+
from typing import TYPE_CHECKING, Any
|
|
18
18
|
|
|
19
19
|
from sqlalchemy import inspect, update
|
|
20
20
|
from sqlalchemy.exc import CompileError, InvalidRequestError, NoResultFound
|
|
@@ -30,46 +30,95 @@ from rucio.db.sqla.constants import DIDType
|
|
|
30
30
|
from rucio.db.sqla.session import read_session, stream_session, transactional_session
|
|
31
31
|
|
|
32
32
|
if TYPE_CHECKING:
|
|
33
|
-
from
|
|
33
|
+
from collections.abc import Iterator
|
|
34
|
+
from typing import Literal, Optional, Union
|
|
34
35
|
|
|
35
36
|
from sqlalchemy.orm import Session
|
|
36
37
|
|
|
38
|
+
from rucio.common.types import InternalScope
|
|
39
|
+
|
|
37
40
|
|
|
38
41
|
class DidColumnMeta(DidMetaPlugin):
|
|
39
42
|
"""
|
|
40
43
|
A metadata plugin to interact with the base DID table metadata.
|
|
41
44
|
"""
|
|
42
|
-
|
|
45
|
+
|
|
46
|
+
def __init__(self) -> None:
|
|
47
|
+
"""Initialize the DID column metadata plugin."""
|
|
43
48
|
super(DidColumnMeta, self).__init__()
|
|
44
49
|
self.plugin_name = "DID_COLUMN"
|
|
45
50
|
|
|
46
51
|
@read_session
|
|
47
|
-
def get_metadata(
|
|
52
|
+
def get_metadata(
|
|
53
|
+
self,
|
|
54
|
+
scope: "InternalScope",
|
|
55
|
+
name: str,
|
|
56
|
+
*,
|
|
57
|
+
session: "Session",
|
|
58
|
+
) -> dict[str, Any]:
|
|
48
59
|
"""
|
|
49
|
-
Get data identifier
|
|
60
|
+
Get all the metadata of some data identifier.
|
|
50
61
|
|
|
51
|
-
:param scope: The scope
|
|
52
|
-
:param name: The
|
|
62
|
+
:param scope: The scope of the DID.
|
|
63
|
+
:param name: The name of the DID.
|
|
53
64
|
:param session: The database session in use.
|
|
65
|
+
:returns: DID metadata as a dictionary.
|
|
54
66
|
"""
|
|
55
67
|
try:
|
|
56
|
-
row = session.query(models.DataIdentifier).filter_by(scope=scope, name=name)
|
|
68
|
+
row = session.query(models.DataIdentifier).filter_by(scope=scope, name=name). \
|
|
57
69
|
with_hint(models.DataIdentifier, "INDEX(DIDS DIDS_PK)", 'oracle').one()
|
|
58
70
|
return row.to_dict()
|
|
59
71
|
except NoResultFound:
|
|
60
72
|
raise exception.DataIdentifierNotFound(f"Data identifier '{scope}:{name}' not found")
|
|
61
73
|
|
|
62
74
|
@transactional_session
|
|
63
|
-
def set_metadata(
|
|
75
|
+
def set_metadata(
|
|
76
|
+
self,
|
|
77
|
+
scope: "InternalScope",
|
|
78
|
+
name: str,
|
|
79
|
+
key: str,
|
|
80
|
+
value: Any,
|
|
81
|
+
recursive: bool = False,
|
|
82
|
+
*,
|
|
83
|
+
session: "Session",
|
|
84
|
+
) -> None:
|
|
85
|
+
"""
|
|
86
|
+
Add a single key-value metadata pair to a data identifier.
|
|
87
|
+
|
|
88
|
+
:param scope: The scope of the DID.
|
|
89
|
+
:param name: The name of the DID.
|
|
90
|
+
:param key: The metadata key.
|
|
91
|
+
:param value: The metadata value.
|
|
92
|
+
:param recursive: Option to propagate the metadata updates to child content.
|
|
93
|
+
:param session: The database session in use.
|
|
94
|
+
"""
|
|
64
95
|
self.set_metadata_bulk(scope=scope, name=name, metadata={key: value}, recursive=recursive, session=session)
|
|
65
96
|
|
|
66
97
|
@transactional_session
|
|
67
|
-
def set_metadata_bulk(
|
|
68
|
-
|
|
98
|
+
def set_metadata_bulk(
|
|
99
|
+
self,
|
|
100
|
+
scope: "InternalScope",
|
|
101
|
+
name: str,
|
|
102
|
+
metadata: dict[str, Any],
|
|
103
|
+
recursive: bool = False,
|
|
104
|
+
*,
|
|
105
|
+
session: "Session",
|
|
106
|
+
) -> None:
|
|
107
|
+
"""
|
|
108
|
+
Add multiple key-value metadata pairs to a data identifier.
|
|
109
|
+
|
|
110
|
+
:param scope: The scope of the DID.
|
|
111
|
+
:param name: The name of the DID.
|
|
112
|
+
:param metadata: All key-value metadata pairs to set.
|
|
113
|
+
:param recursive: Option to propagate the metadata updates to child content.
|
|
114
|
+
:param session: The database session in use.
|
|
115
|
+
"""
|
|
116
|
+
did_query = session.query(models.DataIdentifier).with_hint(models.DataIdentifier, "INDEX(DIDS DIDS_PK)",
|
|
117
|
+
'oracle').filter_by(scope=scope, name=name)
|
|
69
118
|
if did_query.one_or_none() is None:
|
|
70
119
|
raise exception.DataIdentifierNotFound("Data identifier '%s:%s' not found" % (scope, name))
|
|
71
120
|
|
|
72
|
-
remainder = {}
|
|
121
|
+
remainder: dict[Any, Any] = {}
|
|
73
122
|
for key, value in metadata.items():
|
|
74
123
|
if key == 'eol_at' and isinstance(value, str):
|
|
75
124
|
try:
|
|
@@ -92,51 +141,106 @@ class DidColumnMeta(DidMetaPlugin):
|
|
|
92
141
|
# check for DID presence
|
|
93
142
|
raise exception.UnsupportedOperation('%s for %s:%s cannot be updated' % (key, scope, name))
|
|
94
143
|
elif key in ['guid', 'events']:
|
|
95
|
-
rowcount = did_query
|
|
144
|
+
rowcount = did_query \
|
|
145
|
+
.filter_by(did_type=DIDType.FILE) \
|
|
146
|
+
.update({key: value}, synchronize_session=False)
|
|
96
147
|
if not rowcount:
|
|
97
148
|
# check for DID presence
|
|
98
149
|
raise exception.UnsupportedOperation('%s for %s:%s cannot be updated' % (key, scope, name))
|
|
99
150
|
|
|
100
|
-
session.query(models.DataIdentifierAssociation)
|
|
151
|
+
session.query(models.DataIdentifierAssociation) \
|
|
152
|
+
.filter_by(child_scope=scope, child_name=name, child_type=DIDType.FILE) \
|
|
153
|
+
.update({key: value}, synchronize_session=False)
|
|
101
154
|
if key == 'events':
|
|
102
|
-
for parent_scope, parent_name
|
|
103
|
-
|
|
104
|
-
|
|
155
|
+
for parent_scope, parent_name \
|
|
156
|
+
in session.query(models.DataIdentifierAssociation.scope,
|
|
157
|
+
models.DataIdentifierAssociation.name
|
|
158
|
+
).filter_by(child_scope=scope, child_name=name):
|
|
159
|
+
events = session.query(func.sum(models.DataIdentifierAssociation.events)) \
|
|
160
|
+
.filter_by(scope=parent_scope, name=parent_name).one()[0]
|
|
161
|
+
session.query(models.DataIdentifier) \
|
|
162
|
+
.filter_by(scope=parent_scope, name=parent_name) \
|
|
163
|
+
.update({'events': events}, synchronize_session=False)
|
|
105
164
|
elif key == 'adler32':
|
|
106
|
-
rowcount = did_query
|
|
165
|
+
rowcount = did_query \
|
|
166
|
+
.filter_by(did_type=DIDType.FILE) \
|
|
167
|
+
.update({key: value}, synchronize_session=False)
|
|
107
168
|
if not rowcount:
|
|
108
169
|
# check for DID presence
|
|
109
170
|
raise exception.UnsupportedOperation('%s for %s:%s cannot be updated' % (key, scope, name))
|
|
110
171
|
|
|
111
|
-
session.query(models.DataIdentifierAssociation)
|
|
112
|
-
|
|
113
|
-
|
|
172
|
+
session.query(models.DataIdentifierAssociation) \
|
|
173
|
+
.filter_by(child_scope=scope, child_name=name, child_type=DIDType.FILE) \
|
|
174
|
+
.update({key: value}, synchronize_session=False)
|
|
175
|
+
session.query(models.Request) \
|
|
176
|
+
.filter_by(scope=scope, name=name) \
|
|
177
|
+
.update({key: value}, synchronize_session=False)
|
|
178
|
+
session.query(models.RSEFileAssociation) \
|
|
179
|
+
.filter_by(scope=scope, name=name) \
|
|
180
|
+
.update({key: value}, synchronize_session=False)
|
|
114
181
|
elif key == 'bytes':
|
|
115
|
-
rowcount = did_query
|
|
182
|
+
rowcount = did_query \
|
|
183
|
+
.filter_by(did_type=DIDType.FILE) \
|
|
184
|
+
.update({key: value}, synchronize_session=False)
|
|
116
185
|
if not rowcount:
|
|
117
186
|
# check for DID presence
|
|
118
187
|
raise exception.UnsupportedOperation('%s for %s:%s cannot be updated' % (key, scope, name))
|
|
119
188
|
|
|
120
|
-
session.query(models.DataIdentifierAssociation)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
189
|
+
session.query(models.DataIdentifierAssociation) \
|
|
190
|
+
.filter_by(child_scope=scope, child_name=name, child_type=DIDType.FILE) \
|
|
191
|
+
.update({key: value}, synchronize_session=False)
|
|
192
|
+
session.query(models.Request) \
|
|
193
|
+
.filter_by(scope=scope, name=name) \
|
|
194
|
+
.update({key: value}, synchronize_session=False)
|
|
195
|
+
|
|
196
|
+
for account, bytes_, rse_id, rule_id \
|
|
197
|
+
in session.query(models.ReplicaLock.account,
|
|
198
|
+
models.ReplicaLock.bytes,
|
|
199
|
+
models.ReplicaLock.rse_id,
|
|
200
|
+
models.ReplicaLock.rule_id
|
|
201
|
+
).filter_by(scope=scope, name=name):
|
|
202
|
+
session.query(models.ReplicaLock) \
|
|
203
|
+
.filter_by(scope=scope, name=name, rule_id=rule_id, rse_id=rse_id) \
|
|
204
|
+
.update({key: value}, synchronize_session=False)
|
|
125
205
|
account_counter.decrease(rse_id=rse_id, account=account, files=1, bytes_=bytes_, session=session)
|
|
126
206
|
account_counter.increase(rse_id=rse_id, account=account, files=1, bytes_=value, session=session)
|
|
127
207
|
|
|
128
|
-
for bytes_, rse_id
|
|
129
|
-
|
|
208
|
+
for bytes_, rse_id \
|
|
209
|
+
in session.query(models.RSEFileAssociation.bytes,
|
|
210
|
+
models.RSEFileAssociation.rse_id
|
|
211
|
+
).filter_by(scope=scope, name=name):
|
|
212
|
+
session.query(models.RSEFileAssociation) \
|
|
213
|
+
.filter_by(scope=scope, name=name, rse_id=rse_id) \
|
|
214
|
+
.update({key: value}, synchronize_session=False)
|
|
130
215
|
rse_counter.decrease(rse_id=rse_id, files=1, bytes_=bytes_, session=session)
|
|
131
216
|
rse_counter.increase(rse_id=rse_id, files=1, bytes_=value, session=session)
|
|
132
217
|
|
|
133
|
-
for parent_scope, parent_name
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
218
|
+
for parent_scope, parent_name \
|
|
219
|
+
in session.query(models.DataIdentifierAssociation.scope,
|
|
220
|
+
models.DataIdentifierAssociation.name
|
|
221
|
+
).filter_by(child_scope=scope, child_name=name):
|
|
222
|
+
values: dict[Any, Any] = {
|
|
223
|
+
'length': (session
|
|
224
|
+
.query(func.count(models.DataIdentifierAssociation.scope),
|
|
225
|
+
func.sum(models.DataIdentifierAssociation.bytes),
|
|
226
|
+
func.sum(models.DataIdentifierAssociation.events))
|
|
227
|
+
.filter_by(scope=parent_scope, name=parent_name).one())[0],
|
|
228
|
+
'bytes': (session
|
|
229
|
+
.query(func.count(models.DataIdentifierAssociation.scope),
|
|
230
|
+
func.sum(models.DataIdentifierAssociation.bytes),
|
|
231
|
+
func.sum(models.DataIdentifierAssociation.events))
|
|
232
|
+
.filter_by(scope=parent_scope, name=parent_name).one())[1],
|
|
233
|
+
'events': (session
|
|
234
|
+
.query(func.count(models.DataIdentifierAssociation.scope),
|
|
235
|
+
func.sum(models.DataIdentifierAssociation.bytes),
|
|
236
|
+
func.sum(models.DataIdentifierAssociation.events))
|
|
237
|
+
.filter_by(scope=parent_scope, name=parent_name).one())[2]}
|
|
238
|
+
session.query(models.DataIdentifier) \
|
|
239
|
+
.filter_by(scope=parent_scope, name=parent_name) \
|
|
240
|
+
.update(values, synchronize_session=False)
|
|
241
|
+
session.query(models.DatasetLock) \
|
|
242
|
+
.filter_by(scope=parent_scope, name=parent_name) \
|
|
243
|
+
.update({'length': values['length'], 'bytes': values['bytes']}, synchronize_session=False)
|
|
140
244
|
else:
|
|
141
245
|
remainder[key] = value
|
|
142
246
|
|
|
@@ -148,42 +252,63 @@ class DidColumnMeta(DidMetaPlugin):
|
|
|
148
252
|
except InvalidRequestError:
|
|
149
253
|
raise exception.InvalidMetadata("Some of the keys are not accepted: " + str(list(remainder.keys())))
|
|
150
254
|
if not rowcount:
|
|
151
|
-
raise exception.UnsupportedOperation(
|
|
255
|
+
raise exception.UnsupportedOperation(
|
|
256
|
+
'Some of the keys for %s:%s cannot be updated: %s' % (scope, name, str(list(remainder.keys()))))
|
|
152
257
|
|
|
153
258
|
# propagate metadata updates to child content
|
|
154
259
|
if recursive:
|
|
155
|
-
content_query = session.query(models.DataIdentifierAssociation.child_scope,
|
|
156
|
-
|
|
260
|
+
content_query = session.query(models.DataIdentifierAssociation.child_scope,
|
|
261
|
+
models.DataIdentifierAssociation.child_name)
|
|
262
|
+
content_query = content_query.with_hint(models.DataIdentifierAssociation, "INDEX(CONTENTS CONTENTS_PK)",
|
|
263
|
+
'oracle').filter_by(scope=scope, name=name)
|
|
157
264
|
|
|
158
265
|
for child_scope, child_name in content_query:
|
|
159
266
|
try:
|
|
160
|
-
stmt = update(models.DataIdentifier)\
|
|
161
|
-
.prefix_with("/*+ INDEX(DIDS DIDS_PK) */", dialect='oracle')\
|
|
162
|
-
.filter_by(scope=child_scope, name=child_name)\
|
|
163
|
-
.execution_options(synchronize_session='fetch')\
|
|
267
|
+
stmt = update(models.DataIdentifier) \
|
|
268
|
+
.prefix_with("/*+ INDEX(DIDS DIDS_PK) */", dialect='oracle') \
|
|
269
|
+
.filter_by(scope=child_scope, name=child_name) \
|
|
270
|
+
.execution_options(synchronize_session='fetch') \
|
|
164
271
|
.values(remainder)
|
|
165
272
|
session.execute(stmt)
|
|
166
273
|
except CompileError as error:
|
|
167
274
|
raise exception.InvalidMetadata(error)
|
|
168
275
|
except InvalidRequestError:
|
|
169
|
-
raise exception.InvalidMetadata(
|
|
276
|
+
raise exception.InvalidMetadata(
|
|
277
|
+
"Some of the keys are not accepted recursively: " + str(list(remainder.keys())))
|
|
170
278
|
|
|
171
279
|
@stream_session
|
|
172
|
-
def list_dids(
|
|
173
|
-
|
|
280
|
+
def list_dids(
|
|
281
|
+
self,
|
|
282
|
+
scope: "InternalScope",
|
|
283
|
+
filters: "Union[dict[str, Any], list[dict[str, Any]]]",
|
|
284
|
+
did_type: "Literal['all', 'collection', 'dataset', 'container', 'file']" = 'collection',
|
|
285
|
+
ignore_case: bool = False,
|
|
286
|
+
limit: "Optional[int]" = None,
|
|
287
|
+
offset: "Optional[int]" = None,
|
|
288
|
+
long: bool = False,
|
|
289
|
+
recursive: bool = False,
|
|
290
|
+
ignore_dids: "Optional[set[str]]" = None,
|
|
291
|
+
*,
|
|
292
|
+
session: "Session",
|
|
293
|
+
) -> "Iterator[Union[str, dict[str, Any]]]":
|
|
174
294
|
"""
|
|
175
295
|
Search data identifiers.
|
|
176
296
|
|
|
177
|
-
:param scope:
|
|
178
|
-
:param filters:
|
|
179
|
-
|
|
180
|
-
:param
|
|
181
|
-
|
|
182
|
-
:param
|
|
183
|
-
:param
|
|
297
|
+
:param scope: The scope of the DIDs to list.
|
|
298
|
+
:param filters: A single dict or a list of dicts representing OR groups (disjunction).
|
|
299
|
+
Each group can include a semantic 'type' expanded into did_type filters.
|
|
300
|
+
:param did_type: Option to filter by a specific DID type:
|
|
301
|
+
all(container, dataset, file), collection(dataset or container), dataset, container, file.
|
|
302
|
+
:param ignore_case: Has no effect.
|
|
303
|
+
:param limit: Option to limit the number of returned results.
|
|
304
|
+
:param offset: Has no effect.
|
|
305
|
+
:param long: Option to display more information for each DID.
|
|
306
|
+
:param recursive: Option to recursively list child-DIDs content.
|
|
307
|
+
:param ignore_dids: A set of 'scope:name' strings to de-duplicate results across OR groups and recursion.
|
|
184
308
|
:param session: The database session in use.
|
|
185
|
-
:
|
|
186
|
-
|
|
309
|
+
:yields:
|
|
310
|
+
- If long is False: DID names (str).
|
|
311
|
+
- If long is True: dicts with keys: {'scope', 'name', 'did_type', 'bytes', 'length'}.
|
|
187
312
|
"""
|
|
188
313
|
if not ignore_dids:
|
|
189
314
|
ignore_dids = set()
|
|
@@ -202,7 +327,8 @@ class DidColumnMeta(DidMetaPlugin):
|
|
|
202
327
|
filters = [filters]
|
|
203
328
|
|
|
204
329
|
# for each or_group, make sure there is a mapped "did_type" filter.
|
|
205
|
-
# if type maps to many DIDTypes, the corresponding or_group will be copied the
|
|
330
|
+
# if type maps to many DIDTypes, the corresponding or_group will be copied the
|
|
331
|
+
# required number of times to satisfy all the logical possibilities.
|
|
206
332
|
filters_tmp = []
|
|
207
333
|
for or_group in filters:
|
|
208
334
|
if 'type' not in or_group:
|
|
@@ -210,7 +336,8 @@ class DidColumnMeta(DidMetaPlugin):
|
|
|
210
336
|
else:
|
|
211
337
|
or_group_type = or_group.pop('type').lower()
|
|
212
338
|
if or_group_type not in type_to_did_type_mapping.keys():
|
|
213
|
-
raise exception.UnsupportedOperation(
|
|
339
|
+
raise exception.UnsupportedOperation(
|
|
340
|
+
'{} is not a valid type. Valid types are {}'.format(or_group_type, type_to_did_type_mapping.keys()))
|
|
214
341
|
|
|
215
342
|
for mapped_did_type in type_to_did_type_mapping[or_group_type]:
|
|
216
343
|
or_group['did_type'] = mapped_did_type
|
|
@@ -245,24 +372,32 @@ class DidColumnMeta(DidMetaPlugin):
|
|
|
245
372
|
if recursive:
|
|
246
373
|
from rucio.core.did import list_content
|
|
247
374
|
|
|
248
|
-
# Get attached DIDs and save in list because query has to be finished before starting a new one in the recursion
|
|
375
|
+
# Get attached DIDs and save in a list because the query has to be finished before starting a new one in the recursion
|
|
249
376
|
collections_content = []
|
|
250
377
|
for did in session.execute(stmt).yield_per(100):
|
|
251
|
-
if
|
|
378
|
+
if did.did_type == DIDType.CONTAINER or did.did_type == DIDType.DATASET:
|
|
252
379
|
collections_content += [d for d in list_content(scope=did.scope, name=did.name)]
|
|
253
380
|
|
|
254
381
|
# Replace any name filtering with recursed DID names.
|
|
255
382
|
for did in collections_content:
|
|
256
383
|
for or_group in filters:
|
|
257
384
|
or_group['name'] = did['name']
|
|
258
|
-
for result in self.list_dids(scope=did['scope'],
|
|
259
|
-
|
|
385
|
+
for result in self.list_dids(scope=did['scope'],
|
|
386
|
+
filters=filters,
|
|
387
|
+
recursive=True,
|
|
388
|
+
did_type=did_type,
|
|
389
|
+
limit=limit,
|
|
390
|
+
offset=offset,
|
|
391
|
+
long=long,
|
|
392
|
+
ignore_dids=ignore_dids,
|
|
393
|
+
session=session):
|
|
260
394
|
yield result
|
|
261
395
|
|
|
262
|
-
for did in session.execute(stmt).yield_per(
|
|
396
|
+
for did in session.execute(stmt).yield_per(
|
|
397
|
+
5): # don't unpack this as it makes it dependent on query return order!
|
|
263
398
|
if long:
|
|
264
399
|
did_full = "{}:{}".format(did.scope, did.name)
|
|
265
|
-
if did_full not in ignore_dids:
|
|
400
|
+
if did_full not in ignore_dids: # concatenating results of OR clauses may contain duplicate DIDs if the query result sets not mutually exclusive.
|
|
266
401
|
ignore_dids.add(did_full)
|
|
267
402
|
yield {
|
|
268
403
|
'scope': did.scope,
|
|
@@ -273,21 +408,41 @@ class DidColumnMeta(DidMetaPlugin):
|
|
|
273
408
|
}
|
|
274
409
|
else:
|
|
275
410
|
did_full = "{}:{}".format(did.scope, did.name)
|
|
276
|
-
if did_full not in ignore_dids:
|
|
411
|
+
if did_full not in ignore_dids: # concatenating results of OR clauses may contain duplicate DIDs if the query result sets not mutually exclusive.
|
|
277
412
|
ignore_dids.add(did_full)
|
|
278
413
|
yield did.name
|
|
279
414
|
|
|
280
|
-
def delete_metadata(
|
|
415
|
+
def delete_metadata(
|
|
416
|
+
self,
|
|
417
|
+
scope: "InternalScope",
|
|
418
|
+
name: str,
|
|
419
|
+
key: str,
|
|
420
|
+
*,
|
|
421
|
+
session: "Optional[Session]" = None,
|
|
422
|
+
) -> None:
|
|
281
423
|
"""
|
|
282
|
-
Deletes the metadata stored for the given key.
|
|
424
|
+
Deletes the metadata stored for the given key. (Currently not implemented)
|
|
283
425
|
|
|
284
426
|
:param scope: The scope of the DID.
|
|
285
427
|
:param name: The name of the DID.
|
|
286
428
|
:param key: Key of the metadata.
|
|
429
|
+
:param session: The database session in use.
|
|
287
430
|
"""
|
|
288
431
|
raise NotImplementedError('The DidColumnMeta plugin does not currently support deleting metadata.')
|
|
289
432
|
|
|
290
|
-
def manages_key(
|
|
433
|
+
def manages_key(
|
|
434
|
+
self,
|
|
435
|
+
key: str,
|
|
436
|
+
*,
|
|
437
|
+
session: "Optional[Session]" = None,
|
|
438
|
+
) -> bool:
|
|
439
|
+
"""
|
|
440
|
+
Return whether a metadata key is managed by this plugin.
|
|
441
|
+
|
|
442
|
+
:param key: Key of the metadata.
|
|
443
|
+
:param session: Unused; accepted for interface compatibility.
|
|
444
|
+
:returns: ``True`` if the key is managed by this plugin, else ``False``.
|
|
445
|
+
"""
|
|
291
446
|
# Build list of which keys are managed by this plugin.
|
|
292
447
|
#
|
|
293
448
|
all_did_table_columns = []
|
|
@@ -323,9 +478,11 @@ class DidColumnMeta(DidMetaPlugin):
|
|
|
323
478
|
|
|
324
479
|
return key in hardcoded_keys
|
|
325
480
|
|
|
326
|
-
def get_plugin_name(
|
|
481
|
+
def get_plugin_name(
|
|
482
|
+
self
|
|
483
|
+
) -> str:
|
|
327
484
|
"""
|
|
328
|
-
|
|
485
|
+
Return a unique identifier for this plugin.
|
|
329
486
|
:returns: The name of the plugin.
|
|
330
487
|
"""
|
|
331
488
|
return self.plugin_name
|
rucio/core/replica.py
CHANGED
|
@@ -22,7 +22,6 @@ from curses.ascii import isprint
|
|
|
22
22
|
from datetime import datetime, timedelta
|
|
23
23
|
from hashlib import sha256
|
|
24
24
|
from itertools import groupby
|
|
25
|
-
from json import dumps
|
|
26
25
|
from re import match
|
|
27
26
|
from struct import unpack
|
|
28
27
|
from traceback import format_exc
|
|
@@ -2304,9 +2303,9 @@ def __cleanup_after_replica_deletion(
|
|
|
2304
2303
|
for scope, name, did_type in session.execute(stmt):
|
|
2305
2304
|
if did_type == DIDType.DATASET:
|
|
2306
2305
|
messages.append({'event_type': 'ERASE',
|
|
2307
|
-
'payload':
|
|
2308
|
-
|
|
2309
|
-
|
|
2306
|
+
'payload': {'scope': scope.external,
|
|
2307
|
+
'name': name,
|
|
2308
|
+
'account': 'root'}})
|
|
2310
2309
|
dids_to_delete.add(ScopeName(scope=scope, name=name))
|
|
2311
2310
|
|
|
2312
2311
|
# Remove Archive Constituents
|
rucio/core/request.py
CHANGED
|
@@ -1513,7 +1513,7 @@ class TransferStatsManager:
|
|
|
1513
1513
|
def __enter__(self) -> "TransferStatsManager":
|
|
1514
1514
|
self.record_stats = config_get_bool('transfers', 'stats_enabled', default=self.record_stats)
|
|
1515
1515
|
downsample_period = config_get_int('transfers', 'stats_downsample_period', default=self.downsample_period)
|
|
1516
|
-
# Introduce some voluntary jitter to reduce the
|
|
1516
|
+
# Introduce some voluntary jitter to reduce the likelihood of performing this database
|
|
1517
1517
|
# operation multiple times in parallel.
|
|
1518
1518
|
self.downsample_period = random.randint(downsample_period * 3 // 4, math.ceil(downsample_period * 5 / 4)) # noqa: S311
|
|
1519
1519
|
if self.record_stats:
|
rucio/core/rule.py
CHANGED
|
@@ -37,7 +37,7 @@ import rucio.core.lock # import get_replica_locks, get_files_and_replica_locks_
|
|
|
37
37
|
import rucio.core.replica # import get_and_lock_file_replicas, get_and_lock_file_replicas_for_dataset
|
|
38
38
|
from rucio.common.cache import MemcacheRegion
|
|
39
39
|
from rucio.common.config import config_get
|
|
40
|
-
from rucio.common.constants import DEFAULT_VO, RseAttr
|
|
40
|
+
from rucio.common.constants import DEFAULT_ACTIVITY, DEFAULT_VO, POLICY_ALGORITHM_TYPES_LITERAL, RseAttr
|
|
41
41
|
from rucio.common.exception import (
|
|
42
42
|
DataIdentifierNotFound,
|
|
43
43
|
DuplicateRule,
|
|
@@ -98,7 +98,7 @@ class AutoApprove(PolicyPackageAlgorithms):
|
|
|
98
98
|
Handle automatic approval algorithms for replication rules
|
|
99
99
|
"""
|
|
100
100
|
|
|
101
|
-
_algorithm_type = 'auto_approve'
|
|
101
|
+
_algorithm_type: POLICY_ALGORITHM_TYPES_LITERAL = 'auto_approve'
|
|
102
102
|
|
|
103
103
|
def __init__(self, rule: models.ReplicationRule, did: models.DataIdentifier, session: 'Session', vo: str = DEFAULT_VO) -> None:
|
|
104
104
|
super().__init__()
|
|
@@ -180,7 +180,7 @@ def add_rule(
|
|
|
180
180
|
locked: bool,
|
|
181
181
|
subscription_id: Optional[str],
|
|
182
182
|
source_replica_expression: Optional[str] = None,
|
|
183
|
-
activity: str =
|
|
183
|
+
activity: Optional[str] = None,
|
|
184
184
|
notify: Optional[Literal['Y', 'N', 'C', 'P']] = None,
|
|
185
185
|
purge_replicas: bool = False,
|
|
186
186
|
ignore_availability: bool = False,
|
|
@@ -232,6 +232,9 @@ def add_rule(
|
|
|
232
232
|
if copies <= 0:
|
|
233
233
|
raise InvalidValueForKey("The number of copies for a replication rule should be greater than 0.")
|
|
234
234
|
|
|
235
|
+
if not activity:
|
|
236
|
+
activity = DEFAULT_ACTIVITY
|
|
237
|
+
|
|
235
238
|
rule_ids = []
|
|
236
239
|
|
|
237
240
|
grouping_value = {'ALL': RuleGrouping.ALL, 'NONE': RuleGrouping.NONE}.get(grouping, RuleGrouping.DATASET)
|
rucio/core/rule_grouping.py
CHANGED
|
@@ -921,8 +921,8 @@ def __is_retry_required(lock, activity):
|
|
|
921
921
|
:param activity: The activity of the rule.
|
|
922
922
|
"""
|
|
923
923
|
|
|
924
|
-
created_at_diff = (datetime.utcnow() - lock.created_at).
|
|
925
|
-
updated_at_diff = (datetime.utcnow() - lock.updated_at).
|
|
924
|
+
created_at_diff = (datetime.utcnow() - lock.created_at).total_seconds()
|
|
925
|
+
updated_at_diff = (datetime.utcnow() - lock.updated_at).total_seconds()
|
|
926
926
|
|
|
927
927
|
if activity == 'Express':
|
|
928
928
|
if updated_at_diff > 3600 * 2:
|
rucio/gateway/config.py
CHANGED
|
@@ -47,23 +47,6 @@ def sections(issuer: str, vo: str = DEFAULT_VO) -> list[str]:
|
|
|
47
47
|
return config.sections(session=session)
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
def add_section(section: str, issuer: str, vo: str = DEFAULT_VO) -> None:
|
|
51
|
-
"""
|
|
52
|
-
Add a section to the configuration.
|
|
53
|
-
|
|
54
|
-
:param section: The name of the section.
|
|
55
|
-
:param issuer: The issuer account.
|
|
56
|
-
:param vo: The VO to act on.
|
|
57
|
-
"""
|
|
58
|
-
|
|
59
|
-
kwargs = {'issuer': issuer, 'section': section}
|
|
60
|
-
with db_session(DatabaseOperationType.WRITE) as session:
|
|
61
|
-
auth_result = permission.has_permission(issuer=issuer, vo=vo, action='config_add_section', kwargs=kwargs, session=session)
|
|
62
|
-
if not auth_result.allowed:
|
|
63
|
-
raise exception.AccessDenied('%s cannot add section %s. %s' % (issuer, section, auth_result.message))
|
|
64
|
-
return config.add_section(section, session=session)
|
|
65
|
-
|
|
66
|
-
|
|
67
50
|
def has_section(section: str, issuer: str, vo: str = DEFAULT_VO) -> bool:
|
|
68
51
|
"""
|
|
69
52
|
Indicates whether the named section is present in the configuration.
|
|
@@ -79,25 +62,7 @@ def has_section(section: str, issuer: str, vo: str = DEFAULT_VO) -> bool:
|
|
|
79
62
|
auth_result = permission.has_permission(issuer=issuer, vo=vo, action='config_has_section', kwargs=kwargs, session=session)
|
|
80
63
|
if not auth_result.allowed:
|
|
81
64
|
raise exception.AccessDenied('%s cannot check existence of section %s. %s' % (issuer, section, auth_result.message))
|
|
82
|
-
return config.has_section(section, session=session)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def options(section: str, issuer: str, vo: str = DEFAULT_VO) -> list[str]:
|
|
86
|
-
"""
|
|
87
|
-
Returns a list of options available in the specified section.
|
|
88
|
-
|
|
89
|
-
:param section: The name of the section.
|
|
90
|
-
:param issuer: The issuer account.
|
|
91
|
-
:param vo: The VO to act on.
|
|
92
|
-
:returns: ['option', ...]
|
|
93
|
-
"""
|
|
94
|
-
|
|
95
|
-
kwargs = {'issuer': issuer, 'section': section}
|
|
96
|
-
with db_session(DatabaseOperationType.READ) as session:
|
|
97
|
-
auth_result = permission.has_permission(issuer=issuer, vo=vo, action='config_options', kwargs=kwargs, session=session)
|
|
98
|
-
if auth_result.allowed:
|
|
99
|
-
raise exception.AccessDenied('%s cannot retrieve options from section %s. %s' % (issuer, section, auth_result.message))
|
|
100
|
-
return config.options(section, session=session)
|
|
65
|
+
return config.has_section(section, session=session, use_cache=False)
|
|
101
66
|
|
|
102
67
|
|
|
103
68
|
def has_option(section: str, option: str, issuer: str, vo: str = DEFAULT_VO) -> bool:
|
|
@@ -116,7 +81,7 @@ def has_option(section: str, option: str, issuer: str, vo: str = DEFAULT_VO) ->
|
|
|
116
81
|
auth_result = permission.has_permission(issuer=issuer, vo=vo, action='config_has_option', kwargs=kwargs, session=session)
|
|
117
82
|
if not auth_result.allowed:
|
|
118
83
|
raise exception.AccessDenied('%s cannot check existence of option %s from section %s. %s' % (issuer, option, section, auth_result.message))
|
|
119
|
-
return config.has_option(section, option, session=session)
|
|
84
|
+
return config.has_option(section, option, session=session, use_cache=False)
|
|
120
85
|
|
|
121
86
|
|
|
122
87
|
def get(section: str, option: str, issuer: str, vo: str = DEFAULT_VO) -> Any:
|
rucio/gateway/rule.py
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
from typing import TYPE_CHECKING, Any, Literal, Optional
|
|
16
16
|
|
|
17
17
|
from rucio.common.config import config_get_bool
|
|
18
|
-
from rucio.common.constants import DEFAULT_VO
|
|
18
|
+
from rucio.common.constants import DEFAULT_ACTIVITY, DEFAULT_VO
|
|
19
19
|
from rucio.common.exception import AccessDenied
|
|
20
20
|
from rucio.common.schema import validate_schema
|
|
21
21
|
from rucio.common.types import InternalAccount, InternalScope
|
|
@@ -97,7 +97,7 @@ def add_replication_rule(
|
|
|
97
97
|
account = issuer
|
|
98
98
|
|
|
99
99
|
if activity is None:
|
|
100
|
-
activity =
|
|
100
|
+
activity = DEFAULT_ACTIVITY
|
|
101
101
|
|
|
102
102
|
kwargs = {'dids': dids, 'copies': copies, 'rse_expression': rse_expression, 'weight': weight, 'lifetime': lifetime,
|
|
103
103
|
'grouping': grouping, 'account': account, 'locked': locked, 'subscription_id': subscription_id,
|
rucio/rse/translation.py
CHANGED
|
@@ -18,7 +18,7 @@ from configparser import NoOptionError, NoSectionError
|
|
|
18
18
|
from typing import TYPE_CHECKING, Any, Optional
|
|
19
19
|
|
|
20
20
|
from rucio.common import config
|
|
21
|
-
from rucio.common.constants import DEFAULT_VO, RseAttr
|
|
21
|
+
from rucio.common.constants import DEFAULT_VO, POLICY_ALGORITHM_TYPES_LITERAL, RseAttr
|
|
22
22
|
from rucio.common.exception import ConfigNotFound
|
|
23
23
|
from rucio.common.plugins import PolicyPackageAlgorithms
|
|
24
24
|
|
|
@@ -33,7 +33,7 @@ class RSEDeterministicScopeTranslation(PolicyPackageAlgorithms):
|
|
|
33
33
|
Translates a pfn dictionary into a scope and name
|
|
34
34
|
"""
|
|
35
35
|
|
|
36
|
-
_algorithm_type = "pfn2lfn"
|
|
36
|
+
_algorithm_type: POLICY_ALGORITHM_TYPES_LITERAL = "pfn2lfn"
|
|
37
37
|
|
|
38
38
|
def __init__(self, vo: str = DEFAULT_VO):
|
|
39
39
|
super().__init__()
|
|
@@ -111,7 +111,7 @@ class RSEDeterministicTranslation(PolicyPackageAlgorithms):
|
|
|
111
111
|
"""
|
|
112
112
|
|
|
113
113
|
_DEFAULT_LFN2PFN = "hash"
|
|
114
|
-
_algorithm_type = "lfn2pfn"
|
|
114
|
+
_algorithm_type: POLICY_ALGORITHM_TYPES_LITERAL = "lfn2pfn"
|
|
115
115
|
|
|
116
116
|
def __init__(
|
|
117
117
|
self,
|