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/webdav.py
CHANGED
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
+
import logging
|
|
15
16
|
import os
|
|
16
17
|
import sys
|
|
17
|
-
import xml.etree.ElementTree as ET
|
|
18
18
|
from dataclasses import dataclass
|
|
19
19
|
from typing import Any, Optional
|
|
20
20
|
from urllib.parse import urlparse
|
|
21
|
+
from xml.etree import ElementTree
|
|
21
22
|
|
|
22
23
|
import requests
|
|
23
24
|
from requests.adapters import HTTPAdapter
|
|
@@ -96,7 +97,7 @@ class _PropfindFile:
|
|
|
96
97
|
size: Optional[int]
|
|
97
98
|
|
|
98
99
|
@classmethod
|
|
99
|
-
def from_xml_node(cls, node:
|
|
100
|
+
def from_xml_node(cls, node: ElementTree.Element):
|
|
100
101
|
"""Extract file properties from a `<{DAV:}response>` node."""
|
|
101
102
|
|
|
102
103
|
xml_href = node.find('./{DAV:}href')
|
|
@@ -133,8 +134,8 @@ class _PropfindResponse:
|
|
|
133
134
|
"""
|
|
134
135
|
|
|
135
136
|
try:
|
|
136
|
-
xml =
|
|
137
|
-
except
|
|
137
|
+
xml = ElementTree.fromstring(document) # noqa: S314
|
|
138
|
+
except ElementTree.ParseError as ex:
|
|
138
139
|
raise ValueError("Couldn't parse XML document") from ex
|
|
139
140
|
|
|
140
141
|
if xml.tag != '{DAV:}multistatus':
|
|
@@ -161,6 +162,7 @@ class Default(protocol.RSEProtocol):
|
|
|
161
162
|
:raises RSEAccessDenied
|
|
162
163
|
"""
|
|
163
164
|
credentials = credentials or {}
|
|
165
|
+
using_presigned_urls = self.rse['sign_url'] is not None
|
|
164
166
|
try:
|
|
165
167
|
parse_url = urlparse(self.path2pfn(''))
|
|
166
168
|
self.server = f'{parse_url.scheme}://{parse_url.netloc}'
|
|
@@ -177,23 +179,29 @@ class Default(protocol.RSEProtocol):
|
|
|
177
179
|
except KeyError:
|
|
178
180
|
self.auth_type = 'cert'
|
|
179
181
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
182
|
+
if using_presigned_urls:
|
|
183
|
+
# Suppress all authentication, otherwise S3 servers will reject
|
|
184
|
+
# requests.
|
|
185
|
+
self.cert = None
|
|
186
|
+
self.auth_token = None
|
|
187
|
+
else:
|
|
188
|
+
try:
|
|
189
|
+
self.cert = credentials['cert']
|
|
190
|
+
except KeyError:
|
|
191
|
+
x509 = os.getenv('X509_USER_PROXY')
|
|
192
|
+
if not x509:
|
|
193
|
+
# Trying to get the proxy from the default location
|
|
194
|
+
proxy_path = '/tmp/x509up_u%s' % os.geteuid()
|
|
195
|
+
if os.path.isfile(proxy_path):
|
|
196
|
+
self.cert = (proxy_path, proxy_path)
|
|
197
|
+
elif self.auth_token:
|
|
198
|
+
# If no proxy is found, we set the cert to None and use the auth_token
|
|
199
|
+
self.cert = None
|
|
200
|
+
pass
|
|
201
|
+
else:
|
|
202
|
+
raise exception.RSEAccessDenied('X509_USER_PROXY is not set')
|
|
193
203
|
else:
|
|
194
|
-
|
|
195
|
-
else:
|
|
196
|
-
self.cert = (x509, x509)
|
|
204
|
+
self.cert = (x509, x509)
|
|
197
205
|
|
|
198
206
|
try:
|
|
199
207
|
self.timeout = credentials['timeout']
|
|
@@ -205,11 +213,16 @@ class Default(protocol.RSEProtocol):
|
|
|
205
213
|
self.session.headers.update({'Authorization': 'Bearer ' + self.auth_token})
|
|
206
214
|
# "ping" to see if the server is available
|
|
207
215
|
try:
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
216
|
+
test_url = self.path2pfn('')
|
|
217
|
+
res = self.session.request('HEAD', test_url, verify=False, timeout=self.timeout, cert=self.cert)
|
|
218
|
+
# REVISIT: this test checks some URL that doesn't correspond to
|
|
219
|
+
# any valid Rucio file. Although this works for normal WebDAV
|
|
220
|
+
# endpoints, it fails for endpoints using presigned URLs. As a
|
|
221
|
+
# work-around, accept 4xx status codes when using presigned URLs.
|
|
222
|
+
if res.status_code != 200 and not (using_presigned_urls and res.status_code < 500):
|
|
223
|
+
raise exception.ServiceUnavailable('Bad status code %s %s : %s' % (res.status_code, test_url, res.text))
|
|
211
224
|
except requests.exceptions.ConnectionError as error:
|
|
212
|
-
raise exception.ServiceUnavailable('Problem to connect %s : %s' % (
|
|
225
|
+
raise exception.ServiceUnavailable('Problem to connect %s : %s' % (test_url, error))
|
|
213
226
|
except requests.exceptions.ReadTimeout as error:
|
|
214
227
|
raise exception.ServiceUnavailable(error)
|
|
215
228
|
|
|
@@ -259,14 +272,16 @@ class Default(protocol.RSEProtocol):
|
|
|
259
272
|
|
|
260
273
|
:param pfn: Physical file name of requested file
|
|
261
274
|
:param dest: Name and path of the files when stored at the client
|
|
262
|
-
:param transfer_timeout: Transfer timeout (in seconds)
|
|
275
|
+
:param transfer_timeout: Transfer timeout (in seconds)
|
|
263
276
|
|
|
264
277
|
:raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound, RSEAccessDenied
|
|
265
278
|
"""
|
|
266
279
|
path = self.path2pfn(pfn)
|
|
267
280
|
chunksize = 1024
|
|
281
|
+
transfer_timeout = self.timeout if transfer_timeout is None else transfer_timeout
|
|
282
|
+
|
|
268
283
|
try:
|
|
269
|
-
result = self.session.get(path, verify=False, stream=True, timeout=
|
|
284
|
+
result = self.session.get(path, verify=False, stream=True, timeout=transfer_timeout, cert=self.cert)
|
|
270
285
|
if result and result.status_code in [200, ]:
|
|
271
286
|
length = None
|
|
272
287
|
if 'content-length' in result.headers:
|
|
@@ -297,7 +312,7 @@ class Default(protocol.RSEProtocol):
|
|
|
297
312
|
:param source: Physical file name
|
|
298
313
|
:param target: Name of the file on the storage system e.g. with prefixed scope
|
|
299
314
|
:param source_dir Path where the to be transferred files are stored in the local file system
|
|
300
|
-
:param transfer_timeout Transfer timeout (in seconds)
|
|
315
|
+
:param transfer_timeout Transfer timeout (in seconds)
|
|
301
316
|
|
|
302
317
|
:raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound, RSEAccessDenied
|
|
303
318
|
"""
|
|
@@ -305,11 +320,13 @@ class Default(protocol.RSEProtocol):
|
|
|
305
320
|
full_name = source_dir + '/' + source if source_dir else source
|
|
306
321
|
directories = path.split('/')
|
|
307
322
|
# Try the upload without testing the existence of the destination directory
|
|
323
|
+
transfer_timeout = self.timeout if transfer_timeout is None else transfer_timeout
|
|
324
|
+
|
|
308
325
|
try:
|
|
309
326
|
if not os.path.exists(full_name):
|
|
310
327
|
raise exception.SourceNotFound()
|
|
311
328
|
it = UploadInChunks(full_name, 10000000, progressbar)
|
|
312
|
-
result = self.session.put(path, data=IterableToFileAdapter(it), verify=False, allow_redirects=True, timeout=
|
|
329
|
+
result = self.session.put(path, data=IterableToFileAdapter(it), verify=False, allow_redirects=True, timeout=transfer_timeout, cert=self.cert)
|
|
313
330
|
if result.status_code in [200, 201]:
|
|
314
331
|
return
|
|
315
332
|
if result.status_code in [409, ]:
|
|
@@ -323,7 +340,7 @@ class Default(protocol.RSEProtocol):
|
|
|
323
340
|
if not os.path.exists(full_name):
|
|
324
341
|
raise exception.SourceNotFound()
|
|
325
342
|
it = UploadInChunks(full_name, 10000000, progressbar)
|
|
326
|
-
result = self.session.put(path, data=IterableToFileAdapter(it), verify=False, allow_redirects=True, timeout=
|
|
343
|
+
result = self.session.put(path, data=IterableToFileAdapter(it), verify=False, allow_redirects=True, timeout=transfer_timeout, cert=self.cert)
|
|
327
344
|
if result.status_code in [200, 201]:
|
|
328
345
|
return
|
|
329
346
|
if result.status_code in [409, ]:
|
|
@@ -537,7 +554,7 @@ class Default(protocol.RSEProtocol):
|
|
|
537
554
|
headers = {'Depth': '0'}
|
|
538
555
|
|
|
539
556
|
try:
|
|
540
|
-
root =
|
|
557
|
+
root = ElementTree.fromstring(self.session.request('PROPFIND', endpoint_basepath, verify=False, headers=headers, cert=self.session.cert).text) # noqa: S314
|
|
541
558
|
usedsize = root[0][1][0].find('{DAV:}quota-used-bytes').text
|
|
542
559
|
try:
|
|
543
560
|
unusedsize = root[0][1][0].find('{DAV:}quota-available-bytes').text
|
|
@@ -548,3 +565,30 @@ class Default(protocol.RSEProtocol):
|
|
|
548
565
|
return totalsize, unusedsize
|
|
549
566
|
except Exception as error:
|
|
550
567
|
raise exception.ServiceUnavailable(error)
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
class NoRename(Default):
|
|
571
|
+
""" Implementing access to RSEs using the WebDAV protocol but without
|
|
572
|
+
renaming files on upload/download. Necessary for some storage endpoints.
|
|
573
|
+
"""
|
|
574
|
+
|
|
575
|
+
def __init__(self, protocol_attr, rse_settings, logger=logging.log):
|
|
576
|
+
""" Initializes the object with information about the referred RSE.
|
|
577
|
+
|
|
578
|
+
:param protocol_attr: Properties of the requested protocol.
|
|
579
|
+
:param rse_settings: The RSE settings.
|
|
580
|
+
:param logger: Optional decorated logger that can be passed from the calling daemons or servers.
|
|
581
|
+
"""
|
|
582
|
+
super(NoRename, self).__init__(protocol_attr, rse_settings, logger=logger)
|
|
583
|
+
self.renaming = False
|
|
584
|
+
self.attributes.pop('determinism_type', None)
|
|
585
|
+
|
|
586
|
+
def rename(self, pfn, new_pfn):
|
|
587
|
+
""" Allows to rename a file stored inside the connected RSE.
|
|
588
|
+
|
|
589
|
+
:param pfn: Current physical file name
|
|
590
|
+
:param new_pfn New physical file name
|
|
591
|
+
|
|
592
|
+
:raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound
|
|
593
|
+
"""
|
|
594
|
+
raise NotImplementedError
|
rucio/rse/protocols/xrootd.py
CHANGED
|
@@ -16,7 +16,8 @@ import logging
|
|
|
16
16
|
import os
|
|
17
17
|
|
|
18
18
|
from rucio.common import exception
|
|
19
|
-
from rucio.common.
|
|
19
|
+
from rucio.common.checksum import PREFERRED_CHECKSUM
|
|
20
|
+
from rucio.common.utils import execute
|
|
20
21
|
from rucio.rse.protocols import protocol
|
|
21
22
|
|
|
22
23
|
|
rucio/rse/rsemanager.py
CHANGED
|
@@ -15,31 +15,43 @@
|
|
|
15
15
|
import copy
|
|
16
16
|
import logging
|
|
17
17
|
import random
|
|
18
|
-
from collections.abc import Callable
|
|
19
18
|
from time import sleep
|
|
19
|
+
from typing import TYPE_CHECKING, Any, Literal, Optional, Union, cast
|
|
20
20
|
from urllib.parse import urlparse
|
|
21
21
|
|
|
22
22
|
from rucio.common import constants, exception, types, utils
|
|
23
|
+
from rucio.common.checksum import GLOBALLY_SUPPORTED_CHECKSUMS
|
|
23
24
|
from rucio.common.config import config_get_int
|
|
24
|
-
from rucio.common.constants import RSE_SUPPORTED_PROTOCOL_OPERATIONS
|
|
25
25
|
from rucio.common.constraints import STRING_TYPES
|
|
26
26
|
from rucio.common.logging import formatted_logger
|
|
27
|
-
from rucio.common.utils import
|
|
27
|
+
from rucio.common.utils import make_valid_did
|
|
28
28
|
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from collections.abc import Callable
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
from sqlalchemy.orm import Session
|
|
33
|
+
|
|
34
|
+
from rucio.rse.protocols.protocol import RSEProtocol
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_scope_protocol(vo: str = 'def') -> 'Callable':
|
|
31
38
|
"""
|
|
32
39
|
Returns the callable protocol to translate the pfn to a name/scope pair
|
|
33
40
|
|
|
34
41
|
:returns:
|
|
35
42
|
Callable: Scope Parser function
|
|
36
43
|
"""
|
|
37
|
-
from rucio.rse.
|
|
44
|
+
from rucio.rse.translation import RSEDeterministicScopeTranslation
|
|
38
45
|
translation = RSEDeterministicScopeTranslation(vo=vo)
|
|
39
46
|
return translation.parser
|
|
40
47
|
|
|
41
48
|
|
|
42
|
-
def get_rse_info(
|
|
49
|
+
def get_rse_info(
|
|
50
|
+
rse: Optional[str] = None,
|
|
51
|
+
vo: str = 'def',
|
|
52
|
+
rse_id: Optional[str] = None,
|
|
53
|
+
session: Optional["Session"] = None
|
|
54
|
+
) -> types.RSESettingsDict:
|
|
43
55
|
"""
|
|
44
56
|
Returns all protocol related RSE attributes.
|
|
45
57
|
Call with either rse and vo, or (in server mode) rse_id
|
|
@@ -79,7 +91,13 @@ def get_rse_info(rse=None, vo='def', rse_id=None, session=None) -> types.RSESett
|
|
|
79
91
|
return rse_info
|
|
80
92
|
|
|
81
93
|
|
|
82
|
-
def _get_possible_protocols(
|
|
94
|
+
def _get_possible_protocols(
|
|
95
|
+
rse_settings: types.RSESettingsDict,
|
|
96
|
+
operation: str,
|
|
97
|
+
scheme: Optional[Union[list[str], str]] = None,
|
|
98
|
+
domain: Optional[str] = None,
|
|
99
|
+
impl: Optional[str] = None
|
|
100
|
+
) -> list[types.RSEProtocolDict]:
|
|
83
101
|
"""
|
|
84
102
|
Filter the list of available protocols or provided by the supported ones.
|
|
85
103
|
|
|
@@ -129,8 +147,14 @@ def _get_possible_protocols(rse_settings: types.RSESettingsDict, operation, sche
|
|
|
129
147
|
return [c for c in candidates if c not in tbr]
|
|
130
148
|
|
|
131
149
|
|
|
132
|
-
def get_protocols_ordered(
|
|
133
|
-
|
|
150
|
+
def get_protocols_ordered(
|
|
151
|
+
rse_settings: types.RSESettingsDict,
|
|
152
|
+
operation: constants.RSE_ALL_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL,
|
|
153
|
+
scheme: Optional[Union[list[str], str]] = None,
|
|
154
|
+
domain: str = 'wan',
|
|
155
|
+
impl: Optional[str] = None
|
|
156
|
+
) -> list[types.RSEProtocolDict]:
|
|
157
|
+
if operation not in constants.RSE_ALL_SUPPORTED_PROTOCOL_OPERATIONS:
|
|
134
158
|
raise exception.RSEOperationNotSupported('Operation %s is not supported' % operation)
|
|
135
159
|
|
|
136
160
|
if domain and domain not in utils.rse_supported_protocol_domains():
|
|
@@ -141,8 +165,13 @@ def get_protocols_ordered(rse_settings: types.RSESettingsDict, operation, scheme
|
|
|
141
165
|
return candidates
|
|
142
166
|
|
|
143
167
|
|
|
144
|
-
def select_protocol(
|
|
145
|
-
|
|
168
|
+
def select_protocol(
|
|
169
|
+
rse_settings: types.RSESettingsDict,
|
|
170
|
+
operation: constants.RSE_ALL_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL,
|
|
171
|
+
scheme: Optional[str] = None,
|
|
172
|
+
domain: str = 'wan'
|
|
173
|
+
) -> types.RSEProtocolDict:
|
|
174
|
+
if operation not in constants.RSE_ALL_SUPPORTED_PROTOCOL_OPERATIONS:
|
|
146
175
|
raise exception.RSEOperationNotSupported('Operation %s is not supported' % operation)
|
|
147
176
|
|
|
148
177
|
if domain and domain not in utils.rse_supported_protocol_domains():
|
|
@@ -154,7 +183,16 @@ def select_protocol(rse_settings: types.RSESettingsDict, operation, scheme=None,
|
|
|
154
183
|
return min(candidates, key=lambda k: k['domains'][domain][operation])
|
|
155
184
|
|
|
156
185
|
|
|
157
|
-
def create_protocol(
|
|
186
|
+
def create_protocol(
|
|
187
|
+
rse_settings: types.RSESettingsDict,
|
|
188
|
+
operation: str,
|
|
189
|
+
scheme: Optional[str] = None,
|
|
190
|
+
domain: str = 'wan',
|
|
191
|
+
auth_token: Optional[str] = None,
|
|
192
|
+
protocol_attr: Optional[types.RSEProtocolDict] = None,
|
|
193
|
+
logger: types.LoggerFunction = logging.log,
|
|
194
|
+
impl: Optional[str] = None
|
|
195
|
+
) -> "RSEProtocol":
|
|
158
196
|
"""
|
|
159
197
|
Instantiates the protocol defined for the given operation.
|
|
160
198
|
|
|
@@ -170,9 +208,11 @@ def create_protocol(rse_settings: types.RSESettingsDict, operation, scheme=None,
|
|
|
170
208
|
|
|
171
209
|
# Verify feasibility of Protocol
|
|
172
210
|
operation = operation.lower()
|
|
173
|
-
if operation not in
|
|
211
|
+
if operation not in constants.RSE_ALL_SUPPORTED_PROTOCOL_OPERATIONS:
|
|
174
212
|
raise exception.RSEOperationNotSupported('Operation %s is not supported' % operation)
|
|
175
213
|
|
|
214
|
+
operation = cast("constants.RSE_ALL_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL", operation)
|
|
215
|
+
|
|
176
216
|
if domain and domain not in utils.rse_supported_protocol_domains():
|
|
177
217
|
raise exception.RSEProtocolDomainNotSupported('Domain %s not supported' % domain)
|
|
178
218
|
|
|
@@ -204,7 +244,16 @@ def create_protocol(rse_settings: types.RSESettingsDict, operation, scheme=None,
|
|
|
204
244
|
return protocol
|
|
205
245
|
|
|
206
246
|
|
|
207
|
-
def lfns2pfns(
|
|
247
|
+
def lfns2pfns(
|
|
248
|
+
rse_settings: types.RSESettingsDict,
|
|
249
|
+
lfns: Union[list[types.LFNDict], types.LFNDict],
|
|
250
|
+
operation: str = 'write',
|
|
251
|
+
scheme: Optional[str] = None,
|
|
252
|
+
domain: str = 'wan',
|
|
253
|
+
auth_token: Optional[str] = None,
|
|
254
|
+
logger: types.LoggerFunction = logging.log,
|
|
255
|
+
impl: Optional[str] = None
|
|
256
|
+
) -> dict[str, str]:
|
|
208
257
|
"""
|
|
209
258
|
Convert the lfn to a pfn
|
|
210
259
|
|
|
@@ -222,7 +271,13 @@ def lfns2pfns(rse_settings: types.RSESettingsDict, lfns, operation='write', sche
|
|
|
222
271
|
return create_protocol(rse_settings, operation, scheme, domain, auth_token=auth_token, logger=logger, impl=impl).lfns2pfns(lfns)
|
|
223
272
|
|
|
224
273
|
|
|
225
|
-
def parse_pfns(
|
|
274
|
+
def parse_pfns(
|
|
275
|
+
rse_settings: types.RSESettingsDict,
|
|
276
|
+
pfns: list[str],
|
|
277
|
+
operation: str = 'read',
|
|
278
|
+
domain: str = 'wan',
|
|
279
|
+
auth_token: Optional[str] = None
|
|
280
|
+
) -> dict[str, dict[str, str]]:
|
|
226
281
|
"""
|
|
227
282
|
Checks if a PFN is feasible for a given RSE. If so it splits the pfn in its various components.
|
|
228
283
|
|
|
@@ -244,7 +299,16 @@ def parse_pfns(rse_settings: types.RSESettingsDict, pfns, operation='read', doma
|
|
|
244
299
|
return create_protocol(rse_settings, operation, urlparse(pfns[0]).scheme, domain, auth_token=auth_token).parse_pfns(pfns)
|
|
245
300
|
|
|
246
301
|
|
|
247
|
-
def exists(
|
|
302
|
+
def exists(
|
|
303
|
+
rse_settings: types.RSESettingsDict,
|
|
304
|
+
files: Union[list[dict[str, str]], dict[str, str]],
|
|
305
|
+
domain: str = 'wan',
|
|
306
|
+
scheme: Optional[str] = None,
|
|
307
|
+
impl: Optional[str] = None,
|
|
308
|
+
auth_token: Optional[str] = None,
|
|
309
|
+
vo: str = 'def',
|
|
310
|
+
logger: types.LoggerFunction = logging.log
|
|
311
|
+
) -> Union[bool, list[Union[bool, dict[dict[str, str], bool]]]]:
|
|
248
312
|
"""
|
|
249
313
|
Checks if a file is present at the connected storage.
|
|
250
314
|
Providing a list indicates the bulk mode.
|
|
@@ -263,26 +327,28 @@ def exists(rse_settings: types.RSESettingsDict, files, domain='wan', scheme=None
|
|
|
263
327
|
:raises RSENotConnected: no connection to a specific storage has been established
|
|
264
328
|
"""
|
|
265
329
|
|
|
266
|
-
ret = {}
|
|
267
|
-
gs = True # gs represents the global status which indicates if every operation worked in bulk mode
|
|
268
|
-
|
|
269
330
|
protocol = create_protocol(rse_settings, 'read', scheme=scheme, impl=impl, domain=domain, auth_token=auth_token, logger=logger)
|
|
270
331
|
protocol.connect()
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
332
|
+
|
|
333
|
+
from rucio.rse.protocols.protocol import RSEProtocol # Placed it here to avoid possible circular imports
|
|
334
|
+
# Check if 'exists' is truly overridden on the read protocol
|
|
335
|
+
if not utils.is_method_overridden(protocol, RSEProtocol, 'exists'):
|
|
336
|
+
# If not overridden, optionally fall back to a write protocol
|
|
274
337
|
protocol = create_protocol(rse_settings, 'write', scheme=scheme, domain=domain, auth_token=auth_token, logger=logger)
|
|
275
338
|
protocol.connect()
|
|
276
|
-
except:
|
|
277
|
-
pass
|
|
278
339
|
|
|
279
|
-
|
|
340
|
+
ret = {}
|
|
341
|
+
gs = True # gs represents the global status which indicates if every operation worked in bulk mode
|
|
342
|
+
|
|
343
|
+
if not isinstance(files, list):
|
|
344
|
+
files = [files]
|
|
280
345
|
for f in files:
|
|
281
346
|
exists = None
|
|
282
347
|
if isinstance(f, STRING_TYPES):
|
|
283
348
|
exists = protocol.exists(f)
|
|
284
349
|
ret[f] = exists
|
|
285
350
|
elif 'scope' in f: # a LFN is provided
|
|
351
|
+
f = cast("types.LFNDict", f)
|
|
286
352
|
pfn = list(protocol.lfns2pfns(f).values())[0]
|
|
287
353
|
if isinstance(pfn, exception.RucioException):
|
|
288
354
|
raise pfn
|
|
@@ -300,12 +366,25 @@ def exists(rse_settings: types.RSESettingsDict, files, domain='wan', scheme=None
|
|
|
300
366
|
|
|
301
367
|
protocol.close()
|
|
302
368
|
if len(ret) == 1:
|
|
303
|
-
|
|
304
|
-
return ret[x]
|
|
369
|
+
return next(iter(ret.values()))
|
|
305
370
|
return [gs, ret]
|
|
306
371
|
|
|
307
372
|
|
|
308
|
-
def upload(
|
|
373
|
+
def upload(
|
|
374
|
+
rse_settings: types.RSESettingsDict,
|
|
375
|
+
lfns: Union[list[types.LFNDict], types.LFNDict],
|
|
376
|
+
domain: str = 'wan',
|
|
377
|
+
source_dir: Optional[str] = None,
|
|
378
|
+
force_pfn: Optional[str] = None,
|
|
379
|
+
force_scheme: Optional[str] = None,
|
|
380
|
+
transfer_timeout: Optional[int] = None,
|
|
381
|
+
delete_existing: bool = False,
|
|
382
|
+
sign_service: Optional[str] = None,
|
|
383
|
+
auth_token: Optional[str] = None,
|
|
384
|
+
vo: str = 'def',
|
|
385
|
+
logger: types.LoggerFunction = logging.log,
|
|
386
|
+
impl: Optional[str] = None
|
|
387
|
+
) -> dict[Union[int, str], Union[bool, str, dict[str, Union[Literal[True], Exception]]]]:
|
|
309
388
|
"""
|
|
310
389
|
Uploads a file to the connected storage.
|
|
311
390
|
Providing a list indicates the bulk mode.
|
|
@@ -343,7 +422,9 @@ def upload(rse_settings: types.RSESettingsDict, lfns, domain='wan', source_dir=N
|
|
|
343
422
|
protocol.connect()
|
|
344
423
|
protocol_delete = create_protocol(rse_settings, 'delete', domain=domain, auth_token=auth_token, logger=logger, impl=impl)
|
|
345
424
|
protocol_delete.connect()
|
|
346
|
-
|
|
425
|
+
|
|
426
|
+
if not isinstance(lfns, list):
|
|
427
|
+
lfns = [lfns]
|
|
347
428
|
for lfn in lfns:
|
|
348
429
|
base_name = lfn.get('filename', lfn['name'])
|
|
349
430
|
name = lfn.get('name', base_name)
|
|
@@ -398,7 +479,7 @@ def upload(rse_settings: types.RSESettingsDict, lfns, domain='wan', source_dir=N
|
|
|
398
479
|
|
|
399
480
|
try: # Try uploading file
|
|
400
481
|
logger(logging.DEBUG, 'Uploading to %s.rucio.upload', pfn)
|
|
401
|
-
protocol.put(base_name, '%s.rucio.upload' % pfn, source_dir, transfer_timeout=transfer_timeout)
|
|
482
|
+
protocol.put(base_name, '%s.rucio.upload' % pfn, source_dir, transfer_timeout=transfer_timeout) # type: ignore (source_dir could be None)
|
|
402
483
|
except Exception as e:
|
|
403
484
|
gs = False
|
|
404
485
|
ret['%s:%s' % (scope, name)] = e
|
|
@@ -496,15 +577,22 @@ def upload(rse_settings: types.RSESettingsDict, lfns, domain='wan', source_dir=N
|
|
|
496
577
|
protocol.close()
|
|
497
578
|
protocol_delete.close()
|
|
498
579
|
if len(ret) == 1:
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
580
|
+
ret_value = next(iter(ret.values()))
|
|
581
|
+
if isinstance(ret_value, Exception):
|
|
582
|
+
raise ret_value
|
|
583
|
+
else:
|
|
584
|
+
return {0: ret_value, 1: ret, 'success': ret_value, 'pfn': pfn}
|
|
504
585
|
return {0: gs, 1: ret, 'success': gs, 'pfn': pfn}
|
|
505
586
|
|
|
506
587
|
|
|
507
|
-
def delete(
|
|
588
|
+
def delete(
|
|
589
|
+
rse_settings: types.RSESettingsDict,
|
|
590
|
+
lfns: Union[list[types.LFNDict], types.LFNDict],
|
|
591
|
+
domain: str = 'wan',
|
|
592
|
+
auth_token: Optional[str] = None,
|
|
593
|
+
logger: types.LoggerFunction = logging.log,
|
|
594
|
+
impl: Optional[str] = None
|
|
595
|
+
) -> Union[bool, list[Union[bool, dict[str, Union[Literal[True], Exception]]]]]:
|
|
508
596
|
"""
|
|
509
597
|
Delete a file from the connected storage.
|
|
510
598
|
Providing a list indicates the bulk mode.
|
|
@@ -527,7 +615,8 @@ def delete(rse_settings: types.RSESettingsDict, lfns, domain='wan', auth_token=N
|
|
|
527
615
|
protocol = create_protocol(rse_settings, 'delete', domain=domain, auth_token=auth_token, logger=logger, impl=impl)
|
|
528
616
|
protocol.connect()
|
|
529
617
|
|
|
530
|
-
|
|
618
|
+
if not isinstance(lfns, list):
|
|
619
|
+
lfns = [lfns]
|
|
531
620
|
for lfn in lfns:
|
|
532
621
|
pfn = list(protocol.lfns2pfns(lfn).values())[0]
|
|
533
622
|
try:
|
|
@@ -539,15 +628,22 @@ def delete(rse_settings: types.RSESettingsDict, lfns, domain='wan', auth_token=N
|
|
|
539
628
|
|
|
540
629
|
protocol.close()
|
|
541
630
|
if len(ret) == 1:
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
631
|
+
ret_value = next(iter(ret.values()))
|
|
632
|
+
if isinstance(ret_value, Exception):
|
|
633
|
+
raise ret_value
|
|
634
|
+
else:
|
|
635
|
+
return ret_value
|
|
547
636
|
return [gs, ret]
|
|
548
637
|
|
|
549
638
|
|
|
550
|
-
def rename(
|
|
639
|
+
def rename(
|
|
640
|
+
rse_settings: types.RSESettingsDict,
|
|
641
|
+
files: Union[list[dict[str, str]], dict[str, str]],
|
|
642
|
+
domain: str = 'wan',
|
|
643
|
+
auth_token: Optional[str] = None,
|
|
644
|
+
logger: types.LoggerFunction = logging.log,
|
|
645
|
+
impl: Optional[str] = None
|
|
646
|
+
) -> Union[bool, list[Union[bool, dict[str, Union[Literal[True], Exception]]]]]:
|
|
551
647
|
"""
|
|
552
648
|
Rename files stored on the connected storage.
|
|
553
649
|
Providing a list indicates the bulk mode.
|
|
@@ -578,7 +674,8 @@ def rename(rse_settings: types.RSESettingsDict, files, domain='wan', auth_token=
|
|
|
578
674
|
protocol = create_protocol(rse_settings, 'write', domain=domain, auth_token=auth_token, logger=logger, impl=impl)
|
|
579
675
|
protocol.connect()
|
|
580
676
|
|
|
581
|
-
|
|
677
|
+
if not isinstance(files, list):
|
|
678
|
+
files = [files]
|
|
582
679
|
for f in files:
|
|
583
680
|
pfn = None
|
|
584
681
|
new_pfn = None
|
|
@@ -615,15 +712,22 @@ def rename(rse_settings: types.RSESettingsDict, files, domain='wan', auth_token=
|
|
|
615
712
|
|
|
616
713
|
protocol.close()
|
|
617
714
|
if len(ret) == 1:
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
715
|
+
ret_value = next(iter(ret.values()))
|
|
716
|
+
if isinstance(ret_value, Exception):
|
|
717
|
+
raise ret_value
|
|
718
|
+
else:
|
|
719
|
+
return ret_value
|
|
623
720
|
return [gs, ret]
|
|
624
721
|
|
|
625
722
|
|
|
626
|
-
def get_space_usage(
|
|
723
|
+
def get_space_usage(
|
|
724
|
+
rse_settings: types.RSESettingsDict,
|
|
725
|
+
scheme: Optional[str] = None,
|
|
726
|
+
domain: str = 'wan',
|
|
727
|
+
auth_token: Optional[str] = None,
|
|
728
|
+
logger: types.LoggerFunction = logging.log,
|
|
729
|
+
impl: Optional[str] = None
|
|
730
|
+
) -> list[Union[bool, Union[dict[str, int], Exception]]]:
|
|
627
731
|
"""
|
|
628
732
|
Get RSE space usage information.
|
|
629
733
|
|
|
@@ -655,7 +759,14 @@ def get_space_usage(rse_settings: types.RSESettingsDict, scheme=None, domain='wa
|
|
|
655
759
|
return [gs, ret]
|
|
656
760
|
|
|
657
761
|
|
|
658
|
-
def find_matching_scheme(
|
|
762
|
+
def find_matching_scheme(
|
|
763
|
+
rse_settings_dest: types.RSESettingsDict,
|
|
764
|
+
rse_settings_src: types.RSESettingsDict,
|
|
765
|
+
operation_src: str,
|
|
766
|
+
operation_dest: str,
|
|
767
|
+
domain: str = 'wan',
|
|
768
|
+
scheme: Optional[Union[str, list[str]]] = None
|
|
769
|
+
) -> tuple[str, str, int, int]:
|
|
659
770
|
"""
|
|
660
771
|
Find the best matching scheme between two RSEs
|
|
661
772
|
|
|
@@ -724,7 +835,10 @@ def find_matching_scheme(rse_settings_dest, rse_settings_src, operation_src, ope
|
|
|
724
835
|
raise exception.RSEProtocolNotSupported('No protocol for provided settings found : %s.' % str(rse_settings_dest))
|
|
725
836
|
|
|
726
837
|
|
|
727
|
-
def _retry_protocol_stat(
|
|
838
|
+
def _retry_protocol_stat(
|
|
839
|
+
protocol: "RSEProtocol",
|
|
840
|
+
pfn: str
|
|
841
|
+
) -> dict[str, Any]:
|
|
728
842
|
"""
|
|
729
843
|
try to stat file, on fail try again 1s, 2s, 4s, 8s, 16s, 32s later. Fail is all fail
|
|
730
844
|
|
|
@@ -746,7 +860,10 @@ def _retry_protocol_stat(protocol, pfn):
|
|
|
746
860
|
return protocol.stat(pfn)
|
|
747
861
|
|
|
748
862
|
|
|
749
|
-
def __check_compatible_scheme(
|
|
863
|
+
def __check_compatible_scheme(
|
|
864
|
+
dest_scheme: str,
|
|
865
|
+
src_scheme: str
|
|
866
|
+
) -> bool:
|
|
750
867
|
"""
|
|
751
868
|
Check if two schemes are compatible, such as srm and gsiftp
|
|
752
869
|
|