rucio-clients 35.7.0__py3-none-any.whl → 37.0.0rc2__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-clients might be problematic. Click here for more details.
- rucio/alembicrevision.py +1 -1
- rucio/cli/__init__.py +14 -0
- rucio/cli/account.py +216 -0
- rucio/cli/bin_legacy/__init__.py +13 -0
- rucio_clients-35.7.0.data/scripts/rucio → rucio/cli/bin_legacy/rucio.py +769 -486
- rucio_clients-35.7.0.data/scripts/rucio-admin → rucio/cli/bin_legacy/rucio_admin.py +476 -423
- rucio/cli/command.py +272 -0
- rucio/cli/config.py +72 -0
- rucio/cli/did.py +191 -0
- rucio/cli/download.py +128 -0
- rucio/cli/lifetime_exception.py +33 -0
- rucio/cli/replica.py +162 -0
- rucio/cli/rse.py +293 -0
- rucio/cli/rule.py +158 -0
- rucio/cli/scope.py +40 -0
- rucio/cli/subscription.py +73 -0
- rucio/cli/upload.py +60 -0
- rucio/cli/utils.py +226 -0
- rucio/client/accountclient.py +0 -1
- rucio/client/baseclient.py +33 -24
- rucio/client/client.py +45 -1
- rucio/client/didclient.py +5 -3
- rucio/client/downloadclient.py +6 -8
- rucio/client/replicaclient.py +0 -2
- rucio/client/richclient.py +317 -0
- rucio/client/rseclient.py +4 -4
- rucio/client/uploadclient.py +26 -12
- rucio/common/bittorrent.py +234 -0
- rucio/common/cache.py +66 -29
- rucio/common/checksum.py +168 -0
- rucio/common/client.py +122 -0
- rucio/common/config.py +22 -35
- rucio/common/constants.py +61 -3
- rucio/common/didtype.py +72 -24
- rucio/common/exception.py +65 -8
- rucio/common/extra.py +5 -10
- rucio/common/logging.py +13 -13
- rucio/common/pcache.py +8 -7
- rucio/common/plugins.py +59 -27
- rucio/common/policy.py +12 -3
- rucio/common/schema/__init__.py +84 -34
- rucio/common/schema/generic.py +0 -17
- rucio/common/schema/generic_multi_vo.py +0 -17
- rucio/common/stomp_utils.py +383 -119
- rucio/common/test_rucio_server.py +12 -6
- rucio/common/types.py +132 -52
- rucio/common/utils.py +93 -643
- rucio/rse/__init__.py +3 -3
- rucio/rse/protocols/bittorrent.py +11 -1
- rucio/rse/protocols/cache.py +0 -11
- rucio/rse/protocols/dummy.py +0 -11
- rucio/rse/protocols/gfal.py +14 -9
- rucio/rse/protocols/globus.py +1 -1
- rucio/rse/protocols/http_cache.py +1 -1
- rucio/rse/protocols/posix.py +2 -2
- rucio/rse/protocols/protocol.py +84 -317
- rucio/rse/protocols/rclone.py +2 -1
- rucio/rse/protocols/rfio.py +10 -1
- rucio/rse/protocols/ssh.py +2 -1
- rucio/rse/protocols/storm.py +2 -13
- rucio/rse/protocols/webdav.py +74 -30
- rucio/rse/protocols/xrootd.py +2 -1
- rucio/rse/rsemanager.py +170 -53
- rucio/rse/translation.py +260 -0
- rucio/vcsversion.py +4 -4
- rucio/version.py +7 -0
- {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/etc/rucio.cfg.atlas.client.template +3 -2
- {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/etc/rucio.cfg.template +3 -19
- {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/requirements.client.txt +11 -7
- rucio_clients-37.0.0rc2.data/scripts/rucio +133 -0
- rucio_clients-37.0.0rc2.data/scripts/rucio-admin +97 -0
- {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/METADATA +18 -14
- rucio_clients-37.0.0rc2.dist-info/RECORD +104 -0
- {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/licenses/AUTHORS.rst +3 -0
- rucio/common/schema/atlas.py +0 -413
- rucio/common/schema/belleii.py +0 -408
- rucio/common/schema/domatpc.py +0 -401
- rucio/common/schema/escape.py +0 -426
- rucio/common/schema/icecube.py +0 -406
- rucio/rse/protocols/gsiftp.py +0 -92
- rucio_clients-35.7.0.dist-info/RECORD +0 -88
- {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/etc/rse-accounts.cfg.template +0 -0
- {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/rucio_client/merge_rucio_configs.py +0 -0
- {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/WHEEL +0 -0
- {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/licenses/LICENSE +0 -0
- {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/top_level.txt +0 -0
rucio/rse/protocols/protocol.py
CHANGED
|
@@ -16,326 +16,50 @@
|
|
|
16
16
|
This module defines the base class for implementing a transfer protocol,
|
|
17
17
|
along with some of the default methods for LFN2PFN translations.
|
|
18
18
|
"""
|
|
19
|
-
import hashlib
|
|
20
19
|
import logging
|
|
21
|
-
from
|
|
22
|
-
from
|
|
23
|
-
from typing import Any, Optional, TypeVar
|
|
20
|
+
from abc import ABC, abstractmethod
|
|
21
|
+
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
24
22
|
from urllib.parse import urlparse
|
|
25
23
|
|
|
26
|
-
from rucio.common import
|
|
27
|
-
from rucio.common.constants import RseAttr
|
|
28
|
-
from rucio.common.plugins import PolicyPackageAlgorithms
|
|
24
|
+
from rucio.common import exception
|
|
29
25
|
from rucio.rse import rsemanager
|
|
26
|
+
from rucio.rse.translation import RSEDeterministicTranslation
|
|
30
27
|
|
|
31
28
|
if getattr(rsemanager, 'CLIENT_MODE', None):
|
|
32
29
|
from rucio.client.rseclient import RSEClient
|
|
33
30
|
|
|
34
31
|
if getattr(rsemanager, 'SERVER_MODE', None):
|
|
35
|
-
from rucio.common.types import InternalScope
|
|
36
32
|
from rucio.core import replica
|
|
37
33
|
from rucio.core.rse import get_rse_vo
|
|
38
34
|
|
|
35
|
+
if getattr(rsemanager, 'SERVER_MODE', None) or TYPE_CHECKING:
|
|
36
|
+
from rucio.common.types import InternalScope, LFNDict, LoggerFunction, RSESettingsDict
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
Translates a pfn dictionary into a scope and name
|
|
43
|
-
"""
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from collections.abc import Iterable
|
|
44
40
|
|
|
45
|
-
|
|
41
|
+
from rucio.common.types import DIDDict
|
|
46
42
|
|
|
47
|
-
def __init__(self, vo: str = 'def'):
|
|
48
|
-
super().__init__()
|
|
49
43
|
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
class RSEProtocol(ABC):
|
|
45
|
+
""" This class is virtual and acts as a base to inherit new protocols from. It further provides some common functionality which applies for the majority of the protocols."""
|
|
52
46
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
# Don't use a function from the policy package. Use one defined in this class according to vo
|
|
60
|
-
logger.debug("PFN2LFN function will not be fetched from the policy package")
|
|
61
|
-
if super()._supports(self._algorithm_type, vo):
|
|
62
|
-
algorithm_name = vo
|
|
63
|
-
else:
|
|
64
|
-
algorithm_name = "def"
|
|
65
|
-
|
|
66
|
-
self.parser = self.get_parser(algorithm_name)
|
|
67
|
-
|
|
68
|
-
@classmethod
|
|
69
|
-
def get_parser(cls, algorithm_name: str) -> Callable[..., Any]:
|
|
70
|
-
return super()._get_one_algorithm(cls._algorithm_type, algorithm_name)
|
|
71
|
-
|
|
72
|
-
@classmethod
|
|
73
|
-
def register(
|
|
74
|
-
cls,
|
|
75
|
-
pfn2lfn_callable: Callable,
|
|
76
|
-
name: Optional[str] = None
|
|
77
|
-
) -> None:
|
|
78
|
-
"""
|
|
79
|
-
Provided a callable function, register it as one of the valid PFN2LFN algorithms.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
:param pfn2lfn_callable: Callable function to use.
|
|
83
|
-
:param name: Algorithm name used for registration.
|
|
84
|
-
"""
|
|
85
|
-
if name is None:
|
|
86
|
-
name = pfn2lfn_callable.__name__
|
|
87
|
-
algorithm_dict = {name: pfn2lfn_callable}
|
|
88
|
-
super()._register(cls._algorithm_type, algorithm_dict)
|
|
89
|
-
|
|
90
|
-
@staticmethod
|
|
91
|
-
def _default(parsed_pfn: Mapping[str, str]) -> tuple[str, str]:
|
|
92
|
-
""" Translate pfn to name/scope pair
|
|
93
|
-
|
|
94
|
-
:param parsed_pfn: dictionary representing pfn containing:
|
|
95
|
-
- path: str,
|
|
96
|
-
- name: str
|
|
97
|
-
:return: tuple containing name, scope
|
|
98
|
-
"""
|
|
99
|
-
path = parsed_pfn['path']
|
|
100
|
-
scope = path.lstrip('/').split('/')[0]
|
|
101
|
-
name = parsed_pfn['name']
|
|
102
|
-
return name, scope
|
|
103
|
-
|
|
104
|
-
@staticmethod
|
|
105
|
-
def _atlas(parsed_pfn: Mapping[str, str]) -> tuple[str, str]:
|
|
106
|
-
""" Translate pfn to name/scope pair
|
|
107
|
-
|
|
108
|
-
:param parsed_pfn: dictionary representing pfn containing:
|
|
109
|
-
- path: str,
|
|
110
|
-
- name: str
|
|
111
|
-
:return: tuple containing name, scope
|
|
112
|
-
"""
|
|
113
|
-
path = parsed_pfn['path']
|
|
114
|
-
if path.startswith('/user') or path.startswith('/group'):
|
|
115
|
-
scope = '%s.%s' % (path.split('/')[1], path.split('/')[2])
|
|
116
|
-
name = parsed_pfn['name']
|
|
117
|
-
else:
|
|
118
|
-
name, scope = RSEDeterministicScopeTranslation._default(parsed_pfn)
|
|
119
|
-
|
|
120
|
-
return name, scope
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
RSEDeterministicScopeTranslation()
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
RSEDeterministicTranslationT = TypeVar('RSEDeterministicTranslationT', bound='RSEDeterministicTranslation')
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
class RSEDeterministicTranslation(PolicyPackageAlgorithms):
|
|
130
|
-
"""
|
|
131
|
-
Execute the logic for translating a LFN to a path.
|
|
132
|
-
"""
|
|
133
|
-
|
|
134
|
-
_DEFAULT_LFN2PFN = "hash"
|
|
135
|
-
_algorithm_type = "lfn2pfn"
|
|
136
|
-
|
|
137
|
-
def __init__(self, rse=None, rse_attributes=None, protocol_attributes=None):
|
|
138
|
-
"""
|
|
139
|
-
Initialize a translator object from the RSE, its attributes, and the protocol-specific
|
|
140
|
-
attributes.
|
|
141
|
-
|
|
142
|
-
:param rse: Name of RSE for this translation.
|
|
143
|
-
:param rse_attributes: A dictionary of RSE-specific attributes for use in the translation.
|
|
144
|
-
:param protocol_attributes: A dictionary of RSE/protocol-specific attributes.
|
|
145
|
-
"""
|
|
146
|
-
super().__init__()
|
|
147
|
-
self.rse = rse
|
|
148
|
-
self.rse_attributes = rse_attributes if rse_attributes else {}
|
|
149
|
-
self.protocol_attributes = protocol_attributes if protocol_attributes else {}
|
|
150
|
-
|
|
151
|
-
@classmethod
|
|
152
|
-
def supports(cls, name):
|
|
153
|
-
"""
|
|
154
|
-
Check to see if a specific algorithm is supported.
|
|
155
|
-
|
|
156
|
-
:param name: Name of the deterministic algorithm.
|
|
157
|
-
:returns: True if `name` is an algorithm supported by the translator class, False otherwise
|
|
158
|
-
"""
|
|
159
|
-
return super()._supports(cls._algorithm_type, name)
|
|
160
|
-
|
|
161
|
-
@classmethod
|
|
162
|
-
def register(cls, lfn2pfn_callable, name=None):
|
|
163
|
-
"""
|
|
164
|
-
Provided a callable function, register it as one of the valid LFN2PFN algorithms.
|
|
165
|
-
|
|
166
|
-
The callable will receive five arguments:
|
|
167
|
-
- scope: Scope of the LFN.
|
|
168
|
-
- name: LFN's path name
|
|
169
|
-
- rse: RSE name the translation is being done for.
|
|
170
|
-
- rse_attributes: Attributes of the RSE.
|
|
171
|
-
- protocol_attributes: Attributes of the RSE's protocol
|
|
172
|
-
The return value should be the last part of the PFN - it will be appended to the
|
|
173
|
-
rest of the URL.
|
|
174
|
-
|
|
175
|
-
:param lfn2pfn_callable: Callable function to use for generating paths.
|
|
176
|
-
:param name: Algorithm name used for registration. If None, then `lfn2pfn_callable.__name__` is used.
|
|
177
|
-
"""
|
|
178
|
-
if name is None:
|
|
179
|
-
name = lfn2pfn_callable.__name__
|
|
180
|
-
algorithm_dict = {name: lfn2pfn_callable}
|
|
181
|
-
super()._register(cls._algorithm_type, algorithm_dict)
|
|
182
|
-
|
|
183
|
-
@staticmethod
|
|
184
|
-
def __hash(scope, name, rse, rse_attrs, protocol_attrs):
|
|
185
|
-
"""
|
|
186
|
-
Given a LFN, turn it into a sub-directory structure using a hash function.
|
|
187
|
-
|
|
188
|
-
This takes the MD5 of the LFN and uses the first four characters as a subdirectory
|
|
189
|
-
name.
|
|
190
|
-
|
|
191
|
-
:param scope: Scope of the LFN.
|
|
192
|
-
:param name: File name of the LFN.
|
|
193
|
-
:param rse: RSE for PFN (ignored)
|
|
194
|
-
:param rse_attrs: RSE attributes for PFN (ignored)
|
|
195
|
-
:param protocol_attrs: RSE protocol attributes for PFN (ignored)
|
|
196
|
-
:returns: Path for use in the PFN generation.
|
|
197
|
-
"""
|
|
198
|
-
del rse
|
|
199
|
-
del rse_attrs
|
|
200
|
-
del protocol_attrs
|
|
201
|
-
hstr = hashlib.md5(('%s:%s' % (scope, name)).encode('utf-8')).hexdigest()
|
|
202
|
-
if scope.startswith('user') or scope.startswith('group'):
|
|
203
|
-
scope = scope.replace('.', '/')
|
|
204
|
-
return '%s/%s/%s/%s' % (scope, hstr[0:2], hstr[2:4], name)
|
|
205
|
-
|
|
206
|
-
@staticmethod
|
|
207
|
-
def __identity(scope, name, rse, rse_attrs, protocol_attrs):
|
|
208
|
-
"""
|
|
209
|
-
Given a LFN, convert it directly to a path using the mapping:
|
|
210
|
-
|
|
211
|
-
scope:path -> scope/path
|
|
212
|
-
|
|
213
|
-
:param scope: Scope of the LFN.
|
|
214
|
-
:param name: File name of the LFN.
|
|
215
|
-
:param rse: RSE for PFN (ignored)
|
|
216
|
-
:param rse_attrs: RSE attributes for PFN (ignored)
|
|
217
|
-
:param protocol_attrs: RSE protocol attributes for PFN (ignored)
|
|
218
|
-
:returns: Path for use in the PFN generation.
|
|
219
|
-
"""
|
|
220
|
-
del rse
|
|
221
|
-
del rse_attrs
|
|
222
|
-
del protocol_attrs
|
|
223
|
-
if scope.startswith('user') or scope.startswith('group'):
|
|
224
|
-
scope = scope.replace('.', '/')
|
|
225
|
-
return '%s/%s' % (scope, name)
|
|
226
|
-
|
|
227
|
-
@staticmethod
|
|
228
|
-
def __belleii(scope, name, rse, rse_attrs, protocol_attrs):
|
|
229
|
-
"""
|
|
230
|
-
Given a LFN, convert it directly to a path using the mapping:
|
|
231
|
-
|
|
232
|
-
path -> path
|
|
233
|
-
This is valid only for the belleii convention where the scope can be determined
|
|
234
|
-
from the LFN using a determinitic function.
|
|
235
|
-
|
|
236
|
-
:param scope: Scope of the LFN.
|
|
237
|
-
:param name: File name of the LFN.
|
|
238
|
-
:param rse: RSE for PFN (ignored)
|
|
239
|
-
:param rse_attrs: RSE attributes for PFN (ignored)
|
|
240
|
-
:param protocol_attrs: RSE protocol attributes for PFN (ignored)
|
|
241
|
-
:returns: Path for use in the PFN generation.
|
|
242
|
-
"""
|
|
243
|
-
del scope
|
|
244
|
-
del rse
|
|
245
|
-
del rse_attrs
|
|
246
|
-
del protocol_attrs
|
|
247
|
-
return name
|
|
248
|
-
|
|
249
|
-
@staticmethod
|
|
250
|
-
def __ligo(scope, name, rse, rse_attrs, protocol_attrs):
|
|
251
|
-
"""
|
|
252
|
-
Given a LFN, convert it directly to a path using the Caltech schema
|
|
253
|
-
|
|
254
|
-
e.g.,: ER8:H-H1_HOFT_C02-1126256640-4096 ->
|
|
255
|
-
ER8/hoft_C02/H1/H-H1_HOFT_C02-11262/H-H1_HOFT_C02-1126256640-4096
|
|
256
|
-
|
|
257
|
-
:param scope: Scope of the LFN (observing run: ER8, O2, postO1, ...)
|
|
258
|
-
:param name: File name of the LFN (E.g., H-H1_HOFT_C02-1126256640-4096.gwf)
|
|
259
|
-
:param rse: RSE for PFN (ignored)
|
|
260
|
-
:param rse_attrs: RSE attributes for PFN (ignored)
|
|
261
|
-
:param protocol_attrs: RSE protocol attributes for PFN (ignored)
|
|
262
|
-
:returns: Path for use in the PFN generation.
|
|
263
|
-
"""
|
|
264
|
-
del rse
|
|
265
|
-
del rse_attrs
|
|
266
|
-
del protocol_attrs
|
|
267
|
-
from ligo_rucio import lfn2pfn as ligo_lfn2pfn # pylint: disable=import-error
|
|
268
|
-
return ligo_lfn2pfn.ligo_lab(scope, name, None, None, None)
|
|
269
|
-
|
|
270
|
-
@staticmethod
|
|
271
|
-
def __xenon(scope, name, rse, rse_attrs, protocol_attrs):
|
|
272
|
-
"""
|
|
273
|
-
Given a LFN, turn it into a two level sub-directory structure based on the scope
|
|
274
|
-
plus a third level based on the name
|
|
275
|
-
:param scope: Scope of the LFN.
|
|
276
|
-
:param name: File name of the LFN.
|
|
277
|
-
:param rse: RSE for PFN (ignored)
|
|
278
|
-
:param rse_attrs: RSE attributes for PFN (ignored)
|
|
279
|
-
:param protocol_attrs: RSE protocol attributes for PFN (ignored)
|
|
280
|
-
:returns: Path for use in the PFN generation.
|
|
281
|
-
"""
|
|
282
|
-
del rse
|
|
283
|
-
del rse_attrs
|
|
284
|
-
del protocol_attrs
|
|
285
|
-
|
|
286
|
-
return '%s/%s/%s/%s' % (scope[0:7], scope[4:len(scope)], name.split('-')[0] + "-" + name.split('-')[1], name)
|
|
287
|
-
|
|
288
|
-
@classmethod
|
|
289
|
-
def _module_init_(cls):
|
|
290
|
-
"""
|
|
291
|
-
Initialize the class object on first module load.
|
|
292
|
-
"""
|
|
293
|
-
cls.register(cls.__hash, "hash")
|
|
294
|
-
cls.register(cls.__identity, "identity")
|
|
295
|
-
cls.register(cls.__ligo, "ligo")
|
|
296
|
-
cls.register(cls.__belleii, "belleii")
|
|
297
|
-
cls.register(cls.__xenon, "xenon")
|
|
298
|
-
policy_module = None
|
|
299
|
-
try:
|
|
300
|
-
policy_module = config.config_get('policy', 'lfn2pfn_module')
|
|
301
|
-
except (NoOptionError, NoSectionError):
|
|
302
|
-
pass
|
|
303
|
-
if policy_module:
|
|
304
|
-
# TODO: The import of importlib is done like this due to a dependency issue with python 2.6 and incompatibility of the module with py3.x
|
|
305
|
-
# More information https://github.com/rucio/rucio/issues/875
|
|
306
|
-
import importlib
|
|
307
|
-
importlib.import_module(policy_module)
|
|
308
|
-
|
|
309
|
-
cls._DEFAULT_LFN2PFN = config.get_lfn2pfn_algorithm_default()
|
|
310
|
-
|
|
311
|
-
def path(self, scope, name):
|
|
312
|
-
""" Transforms the logical file name into a PFN's path.
|
|
313
|
-
|
|
314
|
-
:param lfn: filename
|
|
315
|
-
:param scope: scope
|
|
316
|
-
|
|
317
|
-
:returns: RSE specific URI of the physical file
|
|
318
|
-
"""
|
|
319
|
-
algorithm = self.rse_attributes.get(RseAttr.LFN2PFN_ALGORITHM, 'default')
|
|
320
|
-
if algorithm == 'default':
|
|
321
|
-
algorithm = RSEDeterministicTranslation._DEFAULT_LFN2PFN
|
|
322
|
-
algorithm_callable = super()._get_one_algorithm(RSEDeterministicTranslation._algorithm_type, algorithm)
|
|
323
|
-
return algorithm_callable(scope, name, self.rse, self.rse_attributes, self.protocol_attributes)
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
RSEDeterministicTranslation._module_init_() # pylint: disable=protected-access
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
class RSEProtocol:
|
|
330
|
-
""" This class is virtual and acts as a base to inherit new protocols from. It further provides some common functionality which applies for the amjority of the protocols."""
|
|
331
|
-
|
|
332
|
-
def __init__(self, protocol_attr, rse_settings, logger=logging.log):
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
protocol_attr: dict[str, Any],
|
|
50
|
+
rse_settings: "RSESettingsDict",
|
|
51
|
+
logger: "LoggerFunction" = logging.log
|
|
52
|
+
):
|
|
333
53
|
""" Initializes the object with information about the referred RSE.
|
|
334
54
|
|
|
335
55
|
:param protocol_attr: Properties of the requested protocol.
|
|
336
56
|
:param rse_settting: The RSE settings.
|
|
337
57
|
:param logger: Optional decorated logger that can be passed from the calling daemons or servers.
|
|
338
58
|
"""
|
|
59
|
+
|
|
60
|
+
if 'auth_token' not in protocol_attr:
|
|
61
|
+
raise exception.NoAuthInformation('No authentication token passed for the RSE protocol')
|
|
62
|
+
|
|
339
63
|
self.auth_token = protocol_attr['auth_token']
|
|
340
64
|
protocol_attr.pop('auth_token')
|
|
341
65
|
self.attributes = protocol_attr
|
|
@@ -356,7 +80,10 @@ class RSEProtocol:
|
|
|
356
80
|
if getattr(rsemanager, 'SERVER_MODE', None):
|
|
357
81
|
setattr(self, '_get_path', self._get_path_nondeterministic_server)
|
|
358
82
|
|
|
359
|
-
def lfns2pfns(
|
|
83
|
+
def lfns2pfns(
|
|
84
|
+
self,
|
|
85
|
+
lfns: Union[list["LFNDict"], "LFNDict"]
|
|
86
|
+
) -> dict[str, str]:
|
|
360
87
|
"""
|
|
361
88
|
Returns a fully qualified PFN for the file referred by path.
|
|
362
89
|
|
|
@@ -398,7 +125,10 @@ class RSEProtocol:
|
|
|
398
125
|
self.logger(logging.WARNING, str(e))
|
|
399
126
|
return pfns
|
|
400
127
|
|
|
401
|
-
def __lfns2pfns_client(
|
|
128
|
+
def __lfns2pfns_client(
|
|
129
|
+
self,
|
|
130
|
+
lfns: Union[list["DIDDict"], "DIDDict"]
|
|
131
|
+
) -> dict[str, str]:
|
|
402
132
|
""" Provides the path of a replica for non-deterministic sites. Will be assigned to get path by the __init__ method if necessary.
|
|
403
133
|
|
|
404
134
|
:param scope: list of DIDs
|
|
@@ -411,7 +141,10 @@ class RSEProtocol:
|
|
|
411
141
|
lfn_query = ["%s:%s" % (lfn['scope'], lfn['name']) for lfn in lfns]
|
|
412
142
|
return client.lfns2pfns(self.rse['rse'], lfn_query, scheme=self.attributes['scheme'])
|
|
413
143
|
|
|
414
|
-
def _get_path(
|
|
144
|
+
def _get_path(
|
|
145
|
+
self,
|
|
146
|
+
scope: str,
|
|
147
|
+
name: str):
|
|
415
148
|
""" Transforms the logical file name into a PFN.
|
|
416
149
|
Suitable for sites implementing the RUCIO naming convention.
|
|
417
150
|
This implementation is only invoked if the RSE is deterministic.
|
|
@@ -421,26 +154,33 @@ class RSEProtocol:
|
|
|
421
154
|
|
|
422
155
|
:returns: RSE specific URI of the physical file
|
|
423
156
|
"""
|
|
424
|
-
return self.translator.path(scope, name)
|
|
157
|
+
return self.translator.path(scope, name) # type: ignore (translator could be none)
|
|
425
158
|
|
|
426
|
-
def _get_path_nondeterministic_server(
|
|
159
|
+
def _get_path_nondeterministic_server( # pylint: disable=invalid-name
|
|
160
|
+
self,
|
|
161
|
+
scope: str,
|
|
162
|
+
name: str
|
|
163
|
+
) -> str:
|
|
427
164
|
""" Provides the path of a replica for non-deterministic sites. Will be assigned to get path by the __init__ method if necessary. """
|
|
428
165
|
vo = get_rse_vo(self.rse['id']) # pylint: disable=E0601
|
|
429
|
-
|
|
430
|
-
rep = replica.get_replica(scope=
|
|
166
|
+
internal_scope = InternalScope(scope, vo=vo) # pylint: disable=E0601
|
|
167
|
+
rep = replica.get_replica(scope=internal_scope, name=name, rse_id=self.rse['id']) # pylint: disable=E0601
|
|
431
168
|
if 'path' in rep and rep['path'] is not None:
|
|
432
169
|
path = rep['path']
|
|
433
170
|
elif 'state' in rep and (rep['state'] is None or rep['state'] == 'UNAVAILABLE'):
|
|
434
|
-
raise exception.ReplicaUnAvailable('Missing path information and state is UNAVAILABLE for replica %s:%s on non-deterministic storage named %s' % (
|
|
171
|
+
raise exception.ReplicaUnAvailable('Missing path information and state is UNAVAILABLE for replica %s:%s on non-deterministic storage named %s' % (internal_scope, name, self.rse['rse']))
|
|
435
172
|
else:
|
|
436
|
-
raise exception.ReplicaNotFound('Missing path information for replica %s:%s on non-deterministic storage named %s' % (
|
|
173
|
+
raise exception.ReplicaNotFound('Missing path information for replica %s:%s on non-deterministic storage named %s' % (internal_scope, name, self.rse['rse']))
|
|
437
174
|
if path.startswith('/'):
|
|
438
175
|
path = path[1:]
|
|
439
176
|
if path.endswith('/'):
|
|
440
177
|
path = path[:-1]
|
|
441
178
|
return path
|
|
442
179
|
|
|
443
|
-
def parse_pfns(
|
|
180
|
+
def parse_pfns(
|
|
181
|
+
self,
|
|
182
|
+
pfns: Union['Iterable[str]', str]
|
|
183
|
+
) -> dict[str, dict[str, str]]:
|
|
444
184
|
"""
|
|
445
185
|
Splits the given PFN into the parts known by the protocol. It is also checked if the provided protocol supports the given PFNs.
|
|
446
186
|
|
|
@@ -494,7 +234,10 @@ class RSEProtocol:
|
|
|
494
234
|
|
|
495
235
|
return ret
|
|
496
236
|
|
|
497
|
-
def exists(
|
|
237
|
+
def exists(
|
|
238
|
+
self,
|
|
239
|
+
path: Optional[str]
|
|
240
|
+
) -> bool:
|
|
498
241
|
"""
|
|
499
242
|
Checks if the requested file is known by the referred RSE.
|
|
500
243
|
|
|
@@ -506,7 +249,8 @@ class RSEProtocol:
|
|
|
506
249
|
"""
|
|
507
250
|
raise NotImplementedError
|
|
508
251
|
|
|
509
|
-
|
|
252
|
+
@abstractmethod
|
|
253
|
+
def connect(self) -> None:
|
|
510
254
|
"""
|
|
511
255
|
Establishes the actual connection to the referred RSE.
|
|
512
256
|
|
|
@@ -514,11 +258,18 @@ class RSEProtocol:
|
|
|
514
258
|
"""
|
|
515
259
|
raise NotImplementedError
|
|
516
260
|
|
|
517
|
-
|
|
261
|
+
@abstractmethod
|
|
262
|
+
def close(self) -> None:
|
|
518
263
|
""" Closes the connection to RSE."""
|
|
519
264
|
raise NotImplementedError
|
|
520
265
|
|
|
521
|
-
|
|
266
|
+
@abstractmethod
|
|
267
|
+
def get(
|
|
268
|
+
self,
|
|
269
|
+
path: str,
|
|
270
|
+
dest: str,
|
|
271
|
+
transfer_timeout: Optional[int] = None
|
|
272
|
+
) -> None:
|
|
522
273
|
"""
|
|
523
274
|
Provides access to files stored inside connected the RSE.
|
|
524
275
|
|
|
@@ -532,7 +283,14 @@ class RSEProtocol:
|
|
|
532
283
|
"""
|
|
533
284
|
raise NotImplementedError
|
|
534
285
|
|
|
535
|
-
|
|
286
|
+
@abstractmethod
|
|
287
|
+
def put(
|
|
288
|
+
self,
|
|
289
|
+
source: str,
|
|
290
|
+
target: str,
|
|
291
|
+
source_dir: Optional[str],
|
|
292
|
+
transfer_timeout: Optional[int] = None
|
|
293
|
+
) -> None:
|
|
536
294
|
"""
|
|
537
295
|
Allows to store files inside the referred RSE.
|
|
538
296
|
|
|
@@ -547,7 +305,11 @@ class RSEProtocol:
|
|
|
547
305
|
"""
|
|
548
306
|
raise NotImplementedError
|
|
549
307
|
|
|
550
|
-
|
|
308
|
+
@abstractmethod
|
|
309
|
+
def delete(
|
|
310
|
+
self,
|
|
311
|
+
path: str
|
|
312
|
+
) -> None:
|
|
551
313
|
"""
|
|
552
314
|
Deletes a file from the connected RSE.
|
|
553
315
|
|
|
@@ -558,7 +320,12 @@ class RSEProtocol:
|
|
|
558
320
|
"""
|
|
559
321
|
raise NotImplementedError
|
|
560
322
|
|
|
561
|
-
|
|
323
|
+
@abstractmethod
|
|
324
|
+
def rename(
|
|
325
|
+
self,
|
|
326
|
+
path: str,
|
|
327
|
+
new_path: str
|
|
328
|
+
) -> None:
|
|
562
329
|
""" Allows to rename a file stored inside the connected RSE.
|
|
563
330
|
|
|
564
331
|
:param path: path to the current file on the storage
|
|
@@ -570,17 +337,17 @@ class RSEProtocol:
|
|
|
570
337
|
"""
|
|
571
338
|
raise NotImplementedError
|
|
572
339
|
|
|
573
|
-
def get_space_usage(self):
|
|
340
|
+
def get_space_usage(self) -> tuple[int, int]:
|
|
574
341
|
"""
|
|
575
342
|
Get RSE space usage information.
|
|
576
343
|
|
|
577
|
-
:returns: a
|
|
344
|
+
:returns: a tuple 'totalsize' and 'unusedsize'
|
|
578
345
|
|
|
579
346
|
:raises ServiceUnavailable: if some generic error occurred in the library.
|
|
580
347
|
"""
|
|
581
348
|
raise NotImplementedError
|
|
582
349
|
|
|
583
|
-
def stat(self, path):
|
|
350
|
+
def stat(self, path: str) -> dict[str, Any]:
|
|
584
351
|
"""
|
|
585
352
|
Returns the stats of a file.
|
|
586
353
|
|
rucio/rse/protocols/rclone.py
CHANGED
|
@@ -17,8 +17,9 @@ import logging
|
|
|
17
17
|
import os
|
|
18
18
|
|
|
19
19
|
from rucio.common import exception
|
|
20
|
+
from rucio.common.checksum import PREFERRED_CHECKSUM
|
|
20
21
|
from rucio.common.config import get_config_dirs
|
|
21
|
-
from rucio.common.utils import
|
|
22
|
+
from rucio.common.utils import execute
|
|
22
23
|
from rucio.rse.protocols import protocol
|
|
23
24
|
|
|
24
25
|
|
rucio/rse/protocols/rfio.py
CHANGED
|
@@ -37,7 +37,7 @@ class Default(protocol.RSEProtocol):
|
|
|
37
37
|
:raises RSEAccessDenied: if no connection could be established.
|
|
38
38
|
"""
|
|
39
39
|
extended_attributes = self.rse['protocol']['extended_attributes']
|
|
40
|
-
if 'STAGE_SVCCLASS' in extended_attributes:
|
|
40
|
+
if isinstance(extended_attributes, dict) and 'STAGE_SVCCLASS' in extended_attributes:
|
|
41
41
|
os.environ['STAGE_SVCCLASS'] = extended_attributes['STAGE_SVCCLASS']
|
|
42
42
|
|
|
43
43
|
def path2pfn(self, path):
|
|
@@ -134,3 +134,12 @@ class Default(protocol.RSEProtocol):
|
|
|
134
134
|
ret['path'] = ret['path'].partition(ret['name'])[0]
|
|
135
135
|
|
|
136
136
|
return ret
|
|
137
|
+
|
|
138
|
+
def delete(self, path):
|
|
139
|
+
raise NotImplementedError
|
|
140
|
+
|
|
141
|
+
def get(self, path, dest, transfer_timeout=None):
|
|
142
|
+
raise NotImplementedError
|
|
143
|
+
|
|
144
|
+
def rename(self, path, new_path):
|
|
145
|
+
raise NotImplementedError
|
rucio/rse/protocols/ssh.py
CHANGED
|
@@ -17,7 +17,8 @@ import os
|
|
|
17
17
|
import re
|
|
18
18
|
|
|
19
19
|
from rucio.common import exception
|
|
20
|
-
from rucio.common.
|
|
20
|
+
from rucio.common.checksum import PREFERRED_CHECKSUM
|
|
21
|
+
from rucio.common.utils import execute
|
|
21
22
|
from rucio.rse.protocols import protocol
|
|
22
23
|
|
|
23
24
|
|
rucio/rse/protocols/storm.py
CHANGED
|
@@ -68,17 +68,6 @@ class Default(protocol.RSEProtocol):
|
|
|
68
68
|
"""
|
|
69
69
|
return ''.join([self.rse['scheme'], '://%s' % self.rse['hostname'], path])
|
|
70
70
|
|
|
71
|
-
def exists(self, pfn):
|
|
72
|
-
""" Checks if the requested file is known by the referred RSE.
|
|
73
|
-
|
|
74
|
-
:param pfn: Physical file name
|
|
75
|
-
|
|
76
|
-
:returns: True if the file exists, False if it doesn't
|
|
77
|
-
|
|
78
|
-
:raise ServiceUnavailable
|
|
79
|
-
"""
|
|
80
|
-
raise NotImplementedError
|
|
81
|
-
|
|
82
71
|
def connect(self):
|
|
83
72
|
""" Establishes the actual connection to the referred RSE.
|
|
84
73
|
|
|
@@ -139,7 +128,7 @@ class Default(protocol.RSEProtocol):
|
|
|
139
128
|
name = pfn.split('/')[-1]
|
|
140
129
|
if name not in target:
|
|
141
130
|
target = None
|
|
142
|
-
except:
|
|
131
|
+
except Exception:
|
|
143
132
|
target = None
|
|
144
133
|
pass
|
|
145
134
|
|
|
@@ -149,7 +138,7 @@ class Default(protocol.RSEProtocol):
|
|
|
149
138
|
# requests preferable
|
|
150
139
|
try:
|
|
151
140
|
rcode, etag_meta = requests_etag(pfn, 300)
|
|
152
|
-
except:
|
|
141
|
+
except Exception:
|
|
153
142
|
pass
|
|
154
143
|
# fallback to davix
|
|
155
144
|
if rcode != 207:
|