rucio-clients 35.8.2__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.
- rucio/__init__.py +17 -0
- rucio/alembicrevision.py +15 -0
- rucio/client/__init__.py +15 -0
- rucio/client/accountclient.py +433 -0
- rucio/client/accountlimitclient.py +183 -0
- rucio/client/baseclient.py +974 -0
- rucio/client/client.py +76 -0
- rucio/client/configclient.py +126 -0
- rucio/client/credentialclient.py +59 -0
- rucio/client/didclient.py +866 -0
- rucio/client/diracclient.py +56 -0
- rucio/client/downloadclient.py +1785 -0
- rucio/client/exportclient.py +44 -0
- rucio/client/fileclient.py +50 -0
- rucio/client/importclient.py +42 -0
- rucio/client/lifetimeclient.py +90 -0
- rucio/client/lockclient.py +109 -0
- rucio/client/metaconventionsclient.py +140 -0
- rucio/client/pingclient.py +44 -0
- rucio/client/replicaclient.py +454 -0
- rucio/client/requestclient.py +125 -0
- rucio/client/rseclient.py +746 -0
- rucio/client/ruleclient.py +294 -0
- rucio/client/scopeclient.py +90 -0
- rucio/client/subscriptionclient.py +173 -0
- rucio/client/touchclient.py +82 -0
- rucio/client/uploadclient.py +955 -0
- rucio/common/__init__.py +13 -0
- rucio/common/cache.py +74 -0
- rucio/common/config.py +801 -0
- rucio/common/constants.py +159 -0
- rucio/common/constraints.py +17 -0
- rucio/common/didtype.py +189 -0
- rucio/common/exception.py +1151 -0
- rucio/common/extra.py +36 -0
- rucio/common/logging.py +420 -0
- rucio/common/pcache.py +1408 -0
- rucio/common/plugins.py +153 -0
- rucio/common/policy.py +84 -0
- rucio/common/schema/__init__.py +150 -0
- rucio/common/schema/atlas.py +413 -0
- rucio/common/schema/belleii.py +408 -0
- rucio/common/schema/domatpc.py +401 -0
- rucio/common/schema/escape.py +426 -0
- rucio/common/schema/generic.py +433 -0
- rucio/common/schema/generic_multi_vo.py +412 -0
- rucio/common/schema/icecube.py +406 -0
- rucio/common/stomp_utils.py +159 -0
- rucio/common/stopwatch.py +55 -0
- rucio/common/test_rucio_server.py +148 -0
- rucio/common/types.py +403 -0
- rucio/common/utils.py +2238 -0
- rucio/rse/__init__.py +96 -0
- rucio/rse/protocols/__init__.py +13 -0
- rucio/rse/protocols/bittorrent.py +184 -0
- rucio/rse/protocols/cache.py +122 -0
- rucio/rse/protocols/dummy.py +111 -0
- rucio/rse/protocols/gfal.py +703 -0
- rucio/rse/protocols/globus.py +243 -0
- rucio/rse/protocols/gsiftp.py +92 -0
- rucio/rse/protocols/http_cache.py +82 -0
- rucio/rse/protocols/mock.py +123 -0
- rucio/rse/protocols/ngarc.py +209 -0
- rucio/rse/protocols/posix.py +250 -0
- rucio/rse/protocols/protocol.py +594 -0
- rucio/rse/protocols/rclone.py +364 -0
- rucio/rse/protocols/rfio.py +136 -0
- rucio/rse/protocols/srm.py +338 -0
- rucio/rse/protocols/ssh.py +413 -0
- rucio/rse/protocols/storm.py +206 -0
- rucio/rse/protocols/webdav.py +550 -0
- rucio/rse/protocols/xrootd.py +301 -0
- rucio/rse/rsemanager.py +764 -0
- rucio/vcsversion.py +11 -0
- rucio/version.py +38 -0
- rucio_clients-35.8.2.data/data/etc/rse-accounts.cfg.template +25 -0
- rucio_clients-35.8.2.data/data/etc/rucio.cfg.atlas.client.template +42 -0
- rucio_clients-35.8.2.data/data/etc/rucio.cfg.template +257 -0
- rucio_clients-35.8.2.data/data/requirements.client.txt +15 -0
- rucio_clients-35.8.2.data/data/rucio_client/merge_rucio_configs.py +144 -0
- rucio_clients-35.8.2.data/scripts/rucio +2542 -0
- rucio_clients-35.8.2.data/scripts/rucio-admin +2447 -0
- rucio_clients-35.8.2.dist-info/METADATA +50 -0
- rucio_clients-35.8.2.dist-info/RECORD +88 -0
- rucio_clients-35.8.2.dist-info/WHEEL +5 -0
- rucio_clients-35.8.2.dist-info/licenses/AUTHORS.rst +97 -0
- rucio_clients-35.8.2.dist-info/licenses/LICENSE +201 -0
- rucio_clients-35.8.2.dist-info/top_level.txt +1 -0
rucio/rse/rsemanager.py
ADDED
|
@@ -0,0 +1,764 @@
|
|
|
1
|
+
# Copyright European Organization for Nuclear Research (CERN) since 2012
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import copy
|
|
16
|
+
import logging
|
|
17
|
+
import random
|
|
18
|
+
from collections.abc import Callable
|
|
19
|
+
from time import sleep
|
|
20
|
+
from urllib.parse import urlparse
|
|
21
|
+
|
|
22
|
+
from rucio.common import constants, exception, types, utils
|
|
23
|
+
from rucio.common.config import config_get_int
|
|
24
|
+
from rucio.common.constants import RSE_SUPPORTED_PROTOCOL_OPERATIONS
|
|
25
|
+
from rucio.common.constraints import STRING_TYPES
|
|
26
|
+
from rucio.common.logging import formatted_logger
|
|
27
|
+
from rucio.common.utils import GLOBALLY_SUPPORTED_CHECKSUMS, make_valid_did
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_scope_protocol(vo: str = 'def') -> Callable:
|
|
31
|
+
"""
|
|
32
|
+
Returns the callable protocol to translate the pfn to a name/scope pair
|
|
33
|
+
|
|
34
|
+
:returns:
|
|
35
|
+
Callable: Scope Parser function
|
|
36
|
+
"""
|
|
37
|
+
from rucio.rse.protocols.protocol import RSEDeterministicScopeTranslation
|
|
38
|
+
translation = RSEDeterministicScopeTranslation(vo=vo)
|
|
39
|
+
return translation.parser
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_rse_info(rse=None, vo='def', rse_id=None, session=None) -> types.RSESettingsDict:
|
|
43
|
+
"""
|
|
44
|
+
Returns all protocol related RSE attributes.
|
|
45
|
+
Call with either rse and vo, or (in server mode) rse_id
|
|
46
|
+
|
|
47
|
+
:param rse: Name of the requested RSE
|
|
48
|
+
:param vo: The VO for the RSE.
|
|
49
|
+
:param rse_id: The id of the rse (use in server mode to avoid db calls)
|
|
50
|
+
:param session: The eventual database session.
|
|
51
|
+
|
|
52
|
+
:returns: a dict object with the following attributes:
|
|
53
|
+
id ... an internal identifier
|
|
54
|
+
rse ... the name of the RSE as string
|
|
55
|
+
type ... the storage type odf the RSE e.g. DISK
|
|
56
|
+
volatile ... boolean indicating if the RSE is volatile
|
|
57
|
+
verify_checksum ... boolean indicating whether RSE supports requests for checksums
|
|
58
|
+
deterministic ... boolean indicating of the naming of the files follows the defined determinism
|
|
59
|
+
domain ... indicating the domain that should be assumed for transfers. Values are 'ALL', 'LAN', or 'WAN'
|
|
60
|
+
protocols ... all supported protocol in form of a list of dict objects with the following structure
|
|
61
|
+
- scheme ... protocol scheme e.g. http, srm, ...
|
|
62
|
+
- hostname ... hostname of the site
|
|
63
|
+
- prefix ... path to the folder where the files are stored
|
|
64
|
+
- port ... port used for this protocol
|
|
65
|
+
- impl ... naming the python class of the protocol implementation
|
|
66
|
+
- extended_attributes ... additional information for the protocol
|
|
67
|
+
- domains ... a dict naming each domain and the priority of the protocol for each operation (lower is better, zero is not supported)
|
|
68
|
+
|
|
69
|
+
:raises RSENotFound: if the provided RSE could not be found in the database.
|
|
70
|
+
"""
|
|
71
|
+
# __request_rse_info will be assigned when the module is loaded as it depends on the rucio environment (server or client)
|
|
72
|
+
# __request_rse_info, rse_region are defined in /rucio/rse/__init__.py
|
|
73
|
+
key = '{}:{}'.format(rse, vo) if rse_id is None else str(rse_id)
|
|
74
|
+
key = 'rse_info_%s' % (key)
|
|
75
|
+
rse_info = RSE_REGION.get(key) # NOQA pylint: disable=undefined-variable
|
|
76
|
+
if not rse_info: # no cached entry found
|
|
77
|
+
rse_info = __request_rse_info(str(rse), vo=vo, rse_id=rse_id, session=session) # NOQA pylint: disable=undefined-variable
|
|
78
|
+
RSE_REGION.set(key, rse_info) # NOQA pylint: disable=undefined-variable
|
|
79
|
+
return rse_info
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _get_possible_protocols(rse_settings: types.RSESettingsDict, operation, scheme=None, domain=None, impl=None):
|
|
83
|
+
"""
|
|
84
|
+
Filter the list of available protocols or provided by the supported ones.
|
|
85
|
+
|
|
86
|
+
:param rse_settings: The rse settings.
|
|
87
|
+
:param operation: The operation (write, read).
|
|
88
|
+
:param scheme: Optional filter if no specific protocol is defined in
|
|
89
|
+
rse_setting for the provided operation.
|
|
90
|
+
:param domain: Optional domain (lan/wan), if not specified, both will be returned
|
|
91
|
+
:returns: The list of possible protocols.
|
|
92
|
+
"""
|
|
93
|
+
operation = operation.lower()
|
|
94
|
+
candidates = rse_settings['protocols']
|
|
95
|
+
|
|
96
|
+
# convert scheme to list, if given as string
|
|
97
|
+
if scheme and not isinstance(scheme, list):
|
|
98
|
+
scheme = scheme.split(',')
|
|
99
|
+
|
|
100
|
+
tbr = []
|
|
101
|
+
for protocol in candidates:
|
|
102
|
+
# Check if impl given and filter if so
|
|
103
|
+
if impl and protocol['impl'] != impl:
|
|
104
|
+
tbr.append(protocol)
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
# Check if scheme given and filter if so
|
|
108
|
+
if scheme and protocol['scheme'] not in scheme:
|
|
109
|
+
tbr.append(protocol)
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
filtered = True
|
|
113
|
+
|
|
114
|
+
if not domain:
|
|
115
|
+
for d in list(protocol['domains'].keys()):
|
|
116
|
+
if protocol['domains'][d][operation]:
|
|
117
|
+
filtered = False
|
|
118
|
+
else:
|
|
119
|
+
if protocol['domains'].get(domain, {operation: None}).get(operation):
|
|
120
|
+
filtered = False
|
|
121
|
+
|
|
122
|
+
if filtered:
|
|
123
|
+
tbr.append(protocol)
|
|
124
|
+
|
|
125
|
+
if len(candidates) <= len(tbr):
|
|
126
|
+
raise exception.RSEProtocolNotSupported('No protocol for provided settings'
|
|
127
|
+
' found : %s.' % str(rse_settings))
|
|
128
|
+
|
|
129
|
+
return [c for c in candidates if c not in tbr]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def get_protocols_ordered(rse_settings: types.RSESettingsDict, operation, scheme=None, domain='wan', impl=None):
|
|
133
|
+
if operation not in RSE_SUPPORTED_PROTOCOL_OPERATIONS:
|
|
134
|
+
raise exception.RSEOperationNotSupported('Operation %s is not supported' % operation)
|
|
135
|
+
|
|
136
|
+
if domain and domain not in utils.rse_supported_protocol_domains():
|
|
137
|
+
raise exception.RSEProtocolDomainNotSupported('Domain %s not supported' % domain)
|
|
138
|
+
|
|
139
|
+
candidates = _get_possible_protocols(rse_settings, operation, scheme, domain, impl)
|
|
140
|
+
candidates.sort(key=lambda k: k['domains'][domain][operation])
|
|
141
|
+
return candidates
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def select_protocol(rse_settings: types.RSESettingsDict, operation, scheme=None, domain='wan'):
|
|
145
|
+
if operation not in RSE_SUPPORTED_PROTOCOL_OPERATIONS:
|
|
146
|
+
raise exception.RSEOperationNotSupported('Operation %s is not supported' % operation)
|
|
147
|
+
|
|
148
|
+
if domain and domain not in utils.rse_supported_protocol_domains():
|
|
149
|
+
raise exception.RSEProtocolDomainNotSupported('Domain %s not supported' % domain)
|
|
150
|
+
|
|
151
|
+
candidates = _get_possible_protocols(rse_settings, operation, scheme, domain)
|
|
152
|
+
# Shuffle candidates to load-balance over equal sources
|
|
153
|
+
random.shuffle(candidates)
|
|
154
|
+
return min(candidates, key=lambda k: k['domains'][domain][operation])
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def create_protocol(rse_settings: types.RSESettingsDict, operation, scheme=None, domain='wan', auth_token=None, protocol_attr=None, logger=logging.log, impl=None):
|
|
158
|
+
"""
|
|
159
|
+
Instantiates the protocol defined for the given operation.
|
|
160
|
+
|
|
161
|
+
:param rse_settings: RSE attributes
|
|
162
|
+
:param operation: Intended operation for this protocol
|
|
163
|
+
:param scheme: Optional filter if no specific protocol is defined in rse_setting for the provided operation
|
|
164
|
+
:param domain: Optional specification of the domain
|
|
165
|
+
:param auth_token: Optionally passing JSON Web Token (OIDC) string for authentication
|
|
166
|
+
:param protocol_attr: Optionally passing the full protocol availability information to correctly select WAN/LAN
|
|
167
|
+
:param logger: Optional decorated logger that can be passed from the calling daemons or servers.
|
|
168
|
+
:returns: An instance of the requested protocol
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
# Verify feasibility of Protocol
|
|
172
|
+
operation = operation.lower()
|
|
173
|
+
if operation not in RSE_SUPPORTED_PROTOCOL_OPERATIONS:
|
|
174
|
+
raise exception.RSEOperationNotSupported('Operation %s is not supported' % operation)
|
|
175
|
+
|
|
176
|
+
if domain and domain not in utils.rse_supported_protocol_domains():
|
|
177
|
+
raise exception.RSEProtocolDomainNotSupported('Domain %s not supported' % domain)
|
|
178
|
+
|
|
179
|
+
if impl:
|
|
180
|
+
candidate = _get_possible_protocols(rse_settings, operation, scheme, domain, impl=impl)
|
|
181
|
+
if len(candidate) == 0:
|
|
182
|
+
raise exception.RSEProtocolNotSupported('Protocol implementation %s operation %s on domain %s not supported' % (impl, operation, domain))
|
|
183
|
+
protocol_attr = candidate[0]
|
|
184
|
+
elif not protocol_attr:
|
|
185
|
+
protocol_attr = select_protocol(rse_settings, operation, scheme, domain)
|
|
186
|
+
else:
|
|
187
|
+
candidates = _get_possible_protocols(rse_settings, operation, scheme, domain)
|
|
188
|
+
if protocol_attr not in candidates:
|
|
189
|
+
raise exception.RSEProtocolNotSupported('Protocol %s operation %s on domain %s not supported' % (protocol_attr, operation, domain))
|
|
190
|
+
|
|
191
|
+
# Instantiate protocol
|
|
192
|
+
comp = protocol_attr['impl'].split('.')
|
|
193
|
+
prefix = '.'.join(comp[-2:]) + ': '
|
|
194
|
+
logger = formatted_logger(logger, prefix + "%s")
|
|
195
|
+
mod = __import__('.'.join(comp[:-1]))
|
|
196
|
+
for n in comp[1:]:
|
|
197
|
+
try:
|
|
198
|
+
mod = getattr(mod, n)
|
|
199
|
+
except AttributeError as e:
|
|
200
|
+
logger(logging.DEBUG, 'Protocol implementations not supported.')
|
|
201
|
+
raise exception.RucioException(str(e)) # TODO: provide proper rucio exception
|
|
202
|
+
protocol_attr['auth_token'] = auth_token
|
|
203
|
+
protocol = mod(protocol_attr, rse_settings, logger=logger)
|
|
204
|
+
return protocol
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def lfns2pfns(rse_settings: types.RSESettingsDict, lfns, operation='write', scheme=None, domain='wan', auth_token=None, logger=logging.log, impl=None):
|
|
208
|
+
"""
|
|
209
|
+
Convert the lfn to a pfn
|
|
210
|
+
|
|
211
|
+
:param rse_settings: RSE attributes
|
|
212
|
+
:param lfns: logical file names as a dict containing 'scope' and 'name' as keys. For bulk a list of dicts can be provided
|
|
213
|
+
:param operation: Intended operation for this protocol
|
|
214
|
+
:param scheme: Optional filter if no specific protocol is defined in rse_setting for the provided operation
|
|
215
|
+
:param domain: Optional specification of the domain
|
|
216
|
+
:param auth_token: Optionally passing JSON Web Token (OIDC) string for authentication
|
|
217
|
+
:param logger: Optional decorated logger that can be passed from the calling daemons or servers.
|
|
218
|
+
|
|
219
|
+
:returns: a dict with scope:name as key and the PFN as value
|
|
220
|
+
|
|
221
|
+
"""
|
|
222
|
+
return create_protocol(rse_settings, operation, scheme, domain, auth_token=auth_token, logger=logger, impl=impl).lfns2pfns(lfns)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def parse_pfns(rse_settings: types.RSESettingsDict, pfns, operation='read', domain='wan', auth_token=None):
|
|
226
|
+
"""
|
|
227
|
+
Checks if a PFN is feasible for a given RSE. If so it splits the pfn in its various components.
|
|
228
|
+
|
|
229
|
+
:param rse_settings: RSE attributes
|
|
230
|
+
:param pfns: list of PFNs
|
|
231
|
+
:param operation: Intended operation for this protocol
|
|
232
|
+
:param domain: Optional specification of the domain
|
|
233
|
+
:param auth_token: Optionally passing JSON Web Token (OIDC) string for authentication
|
|
234
|
+
|
|
235
|
+
:returns: A dict with the parts known by the selected protocol e.g. scheme, hostname, prefix, path, name
|
|
236
|
+
|
|
237
|
+
:raises RSEFileNameNotSupported: if provided PFN is not supported by the RSE/protocol
|
|
238
|
+
:raises RSENotFound: if the referred storage is not found i the repository (rse_id)
|
|
239
|
+
:raises InvalidObject: If the properties parameter doesn't include scheme, hostname, and port as keys
|
|
240
|
+
:raises RSEOperationNotSupported: If no matching protocol was found for the requested operation
|
|
241
|
+
"""
|
|
242
|
+
if len(set([urlparse(pfn).scheme for pfn in pfns])) != 1:
|
|
243
|
+
raise ValueError('All PFNs must provide the same protocol scheme')
|
|
244
|
+
return create_protocol(rse_settings, operation, urlparse(pfns[0]).scheme, domain, auth_token=auth_token).parse_pfns(pfns)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def exists(rse_settings: types.RSESettingsDict, files, domain='wan', scheme=None, impl=None, auth_token=None, vo='def', logger=logging.log):
|
|
248
|
+
"""
|
|
249
|
+
Checks if a file is present at the connected storage.
|
|
250
|
+
Providing a list indicates the bulk mode.
|
|
251
|
+
|
|
252
|
+
:param rse_settings: RSE attributes
|
|
253
|
+
:param files: a single dict or a list with dicts containing 'scope' and 'name'
|
|
254
|
+
if LFNs are used and only 'name' if PFNs are used.
|
|
255
|
+
E.g. {'name': '2_rse_remote_get.raw', 'scope': 'user.jdoe'}, {'name': 'user/jdoe/5a/98/3_rse_remote_get.raw'}
|
|
256
|
+
:param domain: The network domain, either 'wan' (default) or 'lan'
|
|
257
|
+
:param auth_token: Optionally passing JSON Web Token (OIDC) string for authentication
|
|
258
|
+
:param vo: The VO for the RSE
|
|
259
|
+
:param logger: Optional decorated logger that can be passed from the calling daemons or servers.
|
|
260
|
+
|
|
261
|
+
:returns: True/False for a single file or a dict object with 'scope:name' for LFNs or 'name' for PFNs as keys and True or the exception as value for each file in bulk mode
|
|
262
|
+
|
|
263
|
+
:raises RSENotConnected: no connection to a specific storage has been established
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
ret = {}
|
|
267
|
+
gs = True # gs represents the global status which indicates if every operation worked in bulk mode
|
|
268
|
+
|
|
269
|
+
protocol = create_protocol(rse_settings, 'read', scheme=scheme, impl=impl, domain=domain, auth_token=auth_token, logger=logger)
|
|
270
|
+
protocol.connect()
|
|
271
|
+
try:
|
|
272
|
+
protocol.exists(None)
|
|
273
|
+
except NotImplementedError:
|
|
274
|
+
protocol = create_protocol(rse_settings, 'write', scheme=scheme, domain=domain, auth_token=auth_token, logger=logger)
|
|
275
|
+
protocol.connect()
|
|
276
|
+
except:
|
|
277
|
+
pass
|
|
278
|
+
|
|
279
|
+
files = [files] if not type(files) is list else files
|
|
280
|
+
for f in files:
|
|
281
|
+
exists = None
|
|
282
|
+
if isinstance(f, STRING_TYPES):
|
|
283
|
+
exists = protocol.exists(f)
|
|
284
|
+
ret[f] = exists
|
|
285
|
+
elif 'scope' in f: # a LFN is provided
|
|
286
|
+
pfn = list(protocol.lfns2pfns(f).values())[0]
|
|
287
|
+
if isinstance(pfn, exception.RucioException):
|
|
288
|
+
raise pfn
|
|
289
|
+
logger(logging.DEBUG, 'Checking if %s exists', pfn)
|
|
290
|
+
# deal with URL signing if required
|
|
291
|
+
if rse_settings['sign_url'] is not None and pfn[:5] == 'https':
|
|
292
|
+
pfn = __get_signed_url(rse_settings['rse'], rse_settings['sign_url'], 'read', pfn, vo) # NOQA pylint: disable=undefined-variable
|
|
293
|
+
exists = protocol.exists(pfn)
|
|
294
|
+
ret[f['scope'] + ':' + f['name']] = exists
|
|
295
|
+
else:
|
|
296
|
+
exists = protocol.exists(f['name'])
|
|
297
|
+
ret[f['name']] = exists
|
|
298
|
+
if not exists:
|
|
299
|
+
gs = False
|
|
300
|
+
|
|
301
|
+
protocol.close()
|
|
302
|
+
if len(ret) == 1:
|
|
303
|
+
for x in ret:
|
|
304
|
+
return ret[x]
|
|
305
|
+
return [gs, ret]
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def upload(rse_settings: types.RSESettingsDict, lfns, domain='wan', source_dir=None, force_pfn=None, force_scheme=None, transfer_timeout=None, delete_existing=False, sign_service=None, auth_token=None, vo='def', logger=logging.log, impl=None):
|
|
309
|
+
"""
|
|
310
|
+
Uploads a file to the connected storage.
|
|
311
|
+
Providing a list indicates the bulk mode.
|
|
312
|
+
|
|
313
|
+
:param rse_settings: RSE attributes
|
|
314
|
+
:param lfns: a single dict or a list with dicts containing 'scope' and 'name'.
|
|
315
|
+
Examples:
|
|
316
|
+
[
|
|
317
|
+
{'name': '1_rse_local_put.raw', 'scope': 'user.jdoe', 'filesize': 42, 'adler32': '87HS3J968JSNWID'},
|
|
318
|
+
{'name': '2_rse_local_put.raw', 'scope': 'user.jdoe', 'filesize': 4711, 'adler32': 'RSSMICETHMISBA837464F'}
|
|
319
|
+
]
|
|
320
|
+
If the 'filename' key is present, it will be used by Rucio as the actual name of the file on disk (separate from the Rucio 'name').
|
|
321
|
+
:param domain: The network domain, either 'wan' (default) or 'lan'
|
|
322
|
+
:param source_dir: path to the local directory including the source files
|
|
323
|
+
:param force_pfn: use the given PFN -- can lead to dark data, use sparingly
|
|
324
|
+
:param force_scheme: use the given protocol scheme, overriding the protocol priority in the RSE description
|
|
325
|
+
:param transfer_timeout: set this timeout (in seconds) for the transfers, for protocols that support it
|
|
326
|
+
:param sign_service: use the given service (e.g. gcs, s3, swift) to sign the URL
|
|
327
|
+
:param auth_token: Optionally passing JSON Web Token (OIDC) string for authentication
|
|
328
|
+
:param vo: The VO for the RSE
|
|
329
|
+
:param logger: Optional decorated logger that can be passed from the calling daemons or servers.
|
|
330
|
+
|
|
331
|
+
:returns: True/False for a single file or a dict object with 'scope:name' as keys and True or the exception as value for each file in bulk mode
|
|
332
|
+
|
|
333
|
+
:raises RSENotConnected: no connection to a specific storage has been established
|
|
334
|
+
:raises SourceNotFound: local source file can not be found
|
|
335
|
+
:raises DestinationNotAccessible: remote destination directory is not accessible
|
|
336
|
+
:raises ServiceUnavailable: for any other reason
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
ret = {}
|
|
340
|
+
gs = True # gs represents the global status which indicates if every operation worked in bulk mode
|
|
341
|
+
|
|
342
|
+
protocol = create_protocol(rse_settings, 'write', scheme=force_scheme, domain=domain, auth_token=auth_token, logger=logger, impl=impl)
|
|
343
|
+
protocol.connect()
|
|
344
|
+
protocol_delete = create_protocol(rse_settings, 'delete', domain=domain, auth_token=auth_token, logger=logger, impl=impl)
|
|
345
|
+
protocol_delete.connect()
|
|
346
|
+
lfns = [lfns] if not type(lfns) is list else lfns
|
|
347
|
+
for lfn in lfns:
|
|
348
|
+
base_name = lfn.get('filename', lfn['name'])
|
|
349
|
+
name = lfn.get('name', base_name)
|
|
350
|
+
scope = lfn['scope']
|
|
351
|
+
if 'adler32' not in lfn and 'md5' not in lfn:
|
|
352
|
+
gs = False
|
|
353
|
+
ret['%s:%s' % (scope, name)] = exception.RucioException('Missing checksum for file %s:%s' % (lfn['scope'], name))
|
|
354
|
+
continue
|
|
355
|
+
if 'filesize' not in lfn:
|
|
356
|
+
gs = False
|
|
357
|
+
ret['%s:%s' % (scope, name)] = exception.RucioException('Missing filesize for file %s:%s' % (lfn['scope'], name))
|
|
358
|
+
continue
|
|
359
|
+
if force_pfn:
|
|
360
|
+
pfn = force_pfn
|
|
361
|
+
readpfn = force_pfn
|
|
362
|
+
else:
|
|
363
|
+
pfn = list(protocol.lfns2pfns(make_valid_did(lfn)).values())[0]
|
|
364
|
+
if isinstance(pfn, exception.RucioException):
|
|
365
|
+
raise pfn
|
|
366
|
+
readpfn = pfn
|
|
367
|
+
if sign_service is not None:
|
|
368
|
+
# need a separate signed URL for read operations (exists and stat)
|
|
369
|
+
readpfn = __get_signed_url(rse_settings['rse'], sign_service, 'read', pfn, vo) # NOQA pylint: disable=undefined-variable
|
|
370
|
+
pfn = __get_signed_url(rse_settings['rse'], sign_service, 'write', pfn, vo) # NOQA pylint: disable=undefined-variable
|
|
371
|
+
|
|
372
|
+
# First check if renaming operation is supported
|
|
373
|
+
if protocol.renaming:
|
|
374
|
+
|
|
375
|
+
# Check if file replica is already on the storage system
|
|
376
|
+
if protocol.overwrite is False and delete_existing is False and protocol.exists(pfn):
|
|
377
|
+
ret['%s:%s' % (scope, name)] = exception.FileReplicaAlreadyExists('File %s in scope %s already exists on storage as PFN %s' % (name, scope, pfn))
|
|
378
|
+
gs = False
|
|
379
|
+
else:
|
|
380
|
+
if protocol.exists('%s.rucio.upload' % pfn): # Check for left over of previous unsuccessful attempts
|
|
381
|
+
try:
|
|
382
|
+
logger(logging.DEBUG, 'Deleting %s.rucio.upload', pfn)
|
|
383
|
+
protocol_delete.delete('%s.rucio.upload' % list(protocol_delete.lfns2pfns(make_valid_did(lfn)).values())[0])
|
|
384
|
+
except Exception as e:
|
|
385
|
+
ret['%s:%s' % (scope, name)] = exception.RSEOperationNotSupported('Unable to remove temporary file %s.rucio.upload: %s' % (pfn, str(e)))
|
|
386
|
+
gs = False
|
|
387
|
+
continue
|
|
388
|
+
|
|
389
|
+
if delete_existing:
|
|
390
|
+
if protocol.exists('%s' % pfn): # Check for previous completed uploads that have to be removed before upload
|
|
391
|
+
try:
|
|
392
|
+
logger(logging.DEBUG, 'Deleting %s', pfn)
|
|
393
|
+
protocol_delete.delete('%s' % list(protocol_delete.lfns2pfns(make_valid_did(lfn)).values())[0])
|
|
394
|
+
except Exception as e:
|
|
395
|
+
ret['%s:%s' % (scope, name)] = exception.RSEOperationNotSupported('Unable to remove file %s: %s' % (pfn, str(e)))
|
|
396
|
+
gs = False
|
|
397
|
+
continue
|
|
398
|
+
|
|
399
|
+
try: # Try uploading file
|
|
400
|
+
logger(logging.DEBUG, 'Uploading to %s.rucio.upload', pfn)
|
|
401
|
+
protocol.put(base_name, '%s.rucio.upload' % pfn, source_dir, transfer_timeout=transfer_timeout)
|
|
402
|
+
except Exception as e:
|
|
403
|
+
gs = False
|
|
404
|
+
ret['%s:%s' % (scope, name)] = e
|
|
405
|
+
continue
|
|
406
|
+
|
|
407
|
+
valid = None
|
|
408
|
+
|
|
409
|
+
try: # Get metadata of file to verify if upload was successful
|
|
410
|
+
try:
|
|
411
|
+
stats = _retry_protocol_stat(protocol, '%s.rucio.upload' % pfn)
|
|
412
|
+
# Verify all supported checksums and keep rack of the verified ones
|
|
413
|
+
verified_checksums = []
|
|
414
|
+
for checksum_name in GLOBALLY_SUPPORTED_CHECKSUMS:
|
|
415
|
+
if (checksum_name in stats) and (checksum_name in lfn):
|
|
416
|
+
verified_checksums.append(stats[checksum_name] == lfn[checksum_name])
|
|
417
|
+
# Upload is successful if at least one checksum was found
|
|
418
|
+
valid = any(verified_checksums)
|
|
419
|
+
if not valid and ('filesize' in stats) and ('filesize' in lfn):
|
|
420
|
+
valid = stats['filesize'] == lfn['filesize']
|
|
421
|
+
except NotImplementedError:
|
|
422
|
+
if rse_settings['verify_checksum'] is False:
|
|
423
|
+
valid = True
|
|
424
|
+
else:
|
|
425
|
+
raise exception.RucioException('Checksum not validated')
|
|
426
|
+
except exception.RSEChecksumUnavailable:
|
|
427
|
+
if rse_settings['verify_checksum'] is False:
|
|
428
|
+
valid = True
|
|
429
|
+
else:
|
|
430
|
+
raise exception.RucioException('Checksum not validated')
|
|
431
|
+
except Exception as e:
|
|
432
|
+
gs = False
|
|
433
|
+
ret['%s:%s' % (scope, name)] = e
|
|
434
|
+
continue
|
|
435
|
+
|
|
436
|
+
if valid: # The upload finished successful and the file can be renamed
|
|
437
|
+
try:
|
|
438
|
+
logger(logging.DEBUG, 'Renaming %s.rucio.upload to %s', pfn, pfn)
|
|
439
|
+
protocol.rename('%s.rucio.upload' % pfn, pfn)
|
|
440
|
+
ret['%s:%s' % (scope, name)] = True
|
|
441
|
+
except Exception as e:
|
|
442
|
+
gs = False
|
|
443
|
+
ret['%s:%s' % (scope, name)] = e
|
|
444
|
+
else:
|
|
445
|
+
gs = False
|
|
446
|
+
ret['%s:%s' % (scope, name)] = exception.RucioException('Replica %s is corrupted.' % pfn)
|
|
447
|
+
else:
|
|
448
|
+
|
|
449
|
+
# Check if file replica is already on the storage system
|
|
450
|
+
if protocol.overwrite is False and delete_existing is False and protocol.exists(readpfn):
|
|
451
|
+
ret['%s:%s' % (scope, name)] = exception.FileReplicaAlreadyExists('File %s in scope %s already exists on storage as PFN %s' % (name, scope, pfn))
|
|
452
|
+
gs = False
|
|
453
|
+
else:
|
|
454
|
+
try: # Try uploading file
|
|
455
|
+
logger(logging.DEBUG, 'Uploading to %s', pfn)
|
|
456
|
+
protocol.put(base_name, pfn, source_dir, transfer_timeout=transfer_timeout)
|
|
457
|
+
except Exception as e:
|
|
458
|
+
gs = False
|
|
459
|
+
ret['%s:%s' % (scope, name)] = e
|
|
460
|
+
continue
|
|
461
|
+
|
|
462
|
+
valid = None
|
|
463
|
+
try: # Get metadata of file to verify if upload was successful
|
|
464
|
+
try:
|
|
465
|
+
stats = _retry_protocol_stat(protocol, pfn)
|
|
466
|
+
|
|
467
|
+
# Verify all supported checksums and keep rack of the verified ones
|
|
468
|
+
verified_checksums = []
|
|
469
|
+
for checksum_name in GLOBALLY_SUPPORTED_CHECKSUMS:
|
|
470
|
+
if (checksum_name in stats) and (checksum_name in lfn):
|
|
471
|
+
verified_checksums.append(stats[checksum_name] == lfn[checksum_name])
|
|
472
|
+
|
|
473
|
+
# Upload is successful if at least one checksum was found
|
|
474
|
+
valid = any(verified_checksums)
|
|
475
|
+
if not valid and ('filesize' in stats) and ('filesize' in lfn):
|
|
476
|
+
valid = stats['filesize'] == lfn['filesize']
|
|
477
|
+
except NotImplementedError:
|
|
478
|
+
if rse_settings['verify_checksum'] is False:
|
|
479
|
+
valid = True
|
|
480
|
+
else:
|
|
481
|
+
raise exception.RucioException('Checksum not validated')
|
|
482
|
+
except exception.RSEChecksumUnavailable:
|
|
483
|
+
if rse_settings['verify_checksum'] is False:
|
|
484
|
+
valid = True
|
|
485
|
+
else:
|
|
486
|
+
raise exception.RucioException('Checksum not validated')
|
|
487
|
+
except Exception as e:
|
|
488
|
+
gs = False
|
|
489
|
+
ret['%s:%s' % (scope, name)] = e
|
|
490
|
+
continue
|
|
491
|
+
|
|
492
|
+
if not valid:
|
|
493
|
+
gs = False
|
|
494
|
+
ret['%s:%s' % (scope, name)] = exception.RucioException('Replica %s is corrupted.' % pfn)
|
|
495
|
+
|
|
496
|
+
protocol.close()
|
|
497
|
+
protocol_delete.close()
|
|
498
|
+
if len(ret) == 1:
|
|
499
|
+
for x in ret:
|
|
500
|
+
if isinstance(ret[x], Exception):
|
|
501
|
+
raise ret[x]
|
|
502
|
+
else:
|
|
503
|
+
return {0: ret[x], 1: ret, 'success': ret[x], 'pfn': pfn}
|
|
504
|
+
return {0: gs, 1: ret, 'success': gs, 'pfn': pfn}
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def delete(rse_settings: types.RSESettingsDict, lfns, domain='wan', auth_token=None, logger=logging.log, impl=None):
|
|
508
|
+
"""
|
|
509
|
+
Delete a file from the connected storage.
|
|
510
|
+
Providing a list indicates the bulk mode.
|
|
511
|
+
|
|
512
|
+
:param rse_settings: RSE attributes
|
|
513
|
+
:param lfns: a single dict or a list with dicts containing 'scope' and 'name'. E.g. [{'name': '1_rse_remote_delete.raw', 'scope': 'user.jdoe'}, {'name': '2_rse_remote_delete.raw', 'scope': 'user.jdoe'}]
|
|
514
|
+
:param domain: The network domain, either 'wan' (default) or 'lan'
|
|
515
|
+
:param auth_token: Optionally passing JSON Web Token (OIDC) string for authentication
|
|
516
|
+
:param logger: Optional decorated logger that can be passed from the calling daemons or servers.
|
|
517
|
+
:returns: True/False for a single file or a dict object with 'scope:name' as keys and True or the exception as value for each file in bulk mode
|
|
518
|
+
|
|
519
|
+
:raises RSENotConnected: no connection to a specific storage has been established
|
|
520
|
+
:raises SourceNotFound: remote source file can not be found on storage
|
|
521
|
+
:raises ServiceUnavailable: for any other reason
|
|
522
|
+
|
|
523
|
+
"""
|
|
524
|
+
ret = {}
|
|
525
|
+
gs = True # gs represents the global status which indicates if every operation worked in bulk mode
|
|
526
|
+
|
|
527
|
+
protocol = create_protocol(rse_settings, 'delete', domain=domain, auth_token=auth_token, logger=logger, impl=impl)
|
|
528
|
+
protocol.connect()
|
|
529
|
+
|
|
530
|
+
lfns = [lfns] if not type(lfns) is list else lfns
|
|
531
|
+
for lfn in lfns:
|
|
532
|
+
pfn = list(protocol.lfns2pfns(lfn).values())[0]
|
|
533
|
+
try:
|
|
534
|
+
protocol.delete(pfn)
|
|
535
|
+
ret['%s:%s' % (lfn['scope'], lfn['name'])] = True
|
|
536
|
+
except Exception as e:
|
|
537
|
+
ret['%s:%s' % (lfn['scope'], lfn['name'])] = e
|
|
538
|
+
gs = False
|
|
539
|
+
|
|
540
|
+
protocol.close()
|
|
541
|
+
if len(ret) == 1:
|
|
542
|
+
for x in ret:
|
|
543
|
+
if isinstance(ret[x], Exception):
|
|
544
|
+
raise ret[x]
|
|
545
|
+
else:
|
|
546
|
+
return ret[x]
|
|
547
|
+
return [gs, ret]
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def rename(rse_settings: types.RSESettingsDict, files, domain='wan', auth_token=None, logger=logging.log, impl=None):
|
|
551
|
+
"""
|
|
552
|
+
Rename files stored on the connected storage.
|
|
553
|
+
Providing a list indicates the bulk mode.
|
|
554
|
+
|
|
555
|
+
:param rse_settings: RSE attributes
|
|
556
|
+
:param files: a single dict or a list with dicts containing 'scope', 'name', 'new_scope' and 'new_name'
|
|
557
|
+
if LFNs are used or only 'name' and 'new_name' if PFNs are used.
|
|
558
|
+
If 'new_scope' or 'new_name' are not provided, the current one is used.
|
|
559
|
+
Examples:
|
|
560
|
+
[
|
|
561
|
+
{'name': '3_rse_remote_rename.raw', 'scope': 'user.jdoe', 'new_name': '3_rse_new.raw', 'new_scope': 'user.jdoe'},
|
|
562
|
+
{'name': 'user/jdoe/d9/cb/9_rse_remote_rename.raw', 'new_name': 'user/jdoe/c6/4a/9_rse_new.raw'}
|
|
563
|
+
]
|
|
564
|
+
:param domain: The network domain, either 'wan' (default) or 'lan'
|
|
565
|
+
:param auth_token: Optionally passing JSON Web Token (OIDC) string for authentication
|
|
566
|
+
:param logger: Optional decorated logger that can be passed from the calling daemons or servers.
|
|
567
|
+
|
|
568
|
+
:returns: True/False for a single file or a dict object with LFN (key) and True/False (value) in bulk mode
|
|
569
|
+
|
|
570
|
+
:raises RSENotConnected: no connection to a specific storage has been established
|
|
571
|
+
:raises SourceNotFound: remote source file can not be found on storage
|
|
572
|
+
:raises DestinationNotAccessible: remote destination directory is not accessible
|
|
573
|
+
:raises ServiceUnavailable: for any other reason
|
|
574
|
+
"""
|
|
575
|
+
ret = {}
|
|
576
|
+
gs = True # gs represents the global status which indicates if every operation worked in bulk mode
|
|
577
|
+
|
|
578
|
+
protocol = create_protocol(rse_settings, 'write', domain=domain, auth_token=auth_token, logger=logger, impl=impl)
|
|
579
|
+
protocol.connect()
|
|
580
|
+
|
|
581
|
+
files = [files] if not type(files) is list else files
|
|
582
|
+
for f in files:
|
|
583
|
+
pfn = None
|
|
584
|
+
new_pfn = None
|
|
585
|
+
key = None
|
|
586
|
+
if 'scope' in f: # LFN is provided
|
|
587
|
+
key = '%s:%s' % (f['scope'], f['name'])
|
|
588
|
+
# Check if new name is provided
|
|
589
|
+
if 'new_name' not in f:
|
|
590
|
+
f['new_name'] = f['name']
|
|
591
|
+
# Check if new scope is provided
|
|
592
|
+
if 'new_scope' not in f:
|
|
593
|
+
f['new_scope'] = f['scope']
|
|
594
|
+
pfn = list(protocol.lfns2pfns({'name': f['name'], 'scope': f['scope']}).values())[0]
|
|
595
|
+
new_pfn = list(protocol.lfns2pfns({'name': f['new_name'], 'scope': f['new_scope']}).values())[0]
|
|
596
|
+
else:
|
|
597
|
+
pfn = f['name']
|
|
598
|
+
new_pfn = f['new_name']
|
|
599
|
+
key = pfn
|
|
600
|
+
# Check if target is not on storage
|
|
601
|
+
if protocol.exists(new_pfn):
|
|
602
|
+
ret[key] = exception.FileReplicaAlreadyExists('File %s already exists on storage' % (new_pfn))
|
|
603
|
+
gs = False
|
|
604
|
+
# Check if source is on storage
|
|
605
|
+
elif not protocol.exists(pfn):
|
|
606
|
+
ret[key] = exception.SourceNotFound('File %s not found on storage' % (pfn))
|
|
607
|
+
gs = False
|
|
608
|
+
else:
|
|
609
|
+
try:
|
|
610
|
+
protocol.rename(pfn, new_pfn)
|
|
611
|
+
ret[key] = True
|
|
612
|
+
except Exception as e:
|
|
613
|
+
ret[key] = e
|
|
614
|
+
gs = False
|
|
615
|
+
|
|
616
|
+
protocol.close()
|
|
617
|
+
if len(ret) == 1:
|
|
618
|
+
for x in ret:
|
|
619
|
+
if isinstance(ret[x], Exception):
|
|
620
|
+
raise ret[x]
|
|
621
|
+
else:
|
|
622
|
+
return ret[x]
|
|
623
|
+
return [gs, ret]
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def get_space_usage(rse_settings: types.RSESettingsDict, scheme=None, domain='wan', auth_token=None, logger=logging.log, impl=None):
|
|
627
|
+
"""
|
|
628
|
+
Get RSE space usage information.
|
|
629
|
+
|
|
630
|
+
:param rse_settings: RSE attributes
|
|
631
|
+
:param scheme: optional filter to select which protocol to be used.
|
|
632
|
+
:param domain: The network domain, either 'wan' (default) or 'lan'
|
|
633
|
+
:param auth_token: Optionally passing JSON Web Token (OIDC) string for authentication
|
|
634
|
+
:param logger: Optional decorated logger that can be passed from the calling daemons or servers.
|
|
635
|
+
|
|
636
|
+
:returns: a list with dict containing 'totalsize' and 'unusedsize'
|
|
637
|
+
|
|
638
|
+
:raises ServiceUnavailable: if some generic error occurred in the library.
|
|
639
|
+
"""
|
|
640
|
+
gs = True
|
|
641
|
+
ret = {}
|
|
642
|
+
|
|
643
|
+
protocol = create_protocol(rse_settings, 'read', scheme=scheme, domain=domain, auth_token=auth_token, logger=logger, impl=impl)
|
|
644
|
+
protocol.connect()
|
|
645
|
+
|
|
646
|
+
try:
|
|
647
|
+
totalsize, unusedsize = protocol.get_space_usage()
|
|
648
|
+
ret["totalsize"] = totalsize
|
|
649
|
+
ret["unusedsize"] = unusedsize
|
|
650
|
+
except Exception as e:
|
|
651
|
+
ret = e
|
|
652
|
+
gs = False
|
|
653
|
+
|
|
654
|
+
protocol.close()
|
|
655
|
+
return [gs, ret]
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def find_matching_scheme(rse_settings_dest, rse_settings_src, operation_src, operation_dest, domain='wan', scheme=None):
|
|
659
|
+
"""
|
|
660
|
+
Find the best matching scheme between two RSEs
|
|
661
|
+
|
|
662
|
+
:param rse_settings_dest: RSE settings for the destination RSE.
|
|
663
|
+
:param rse_settings_src: RSE settings for the src RSE.
|
|
664
|
+
:param operation_src: Source Operation such as read, write.
|
|
665
|
+
:param operation_dest: Dest Operation such as read, write.
|
|
666
|
+
:param domain: Domain such as lan, wan.
|
|
667
|
+
:param scheme: List of supported schemes.
|
|
668
|
+
:returns: Tuple of matching schemes (dest_scheme, src_scheme, dest_scheme_priority, src_scheme_priority).
|
|
669
|
+
"""
|
|
670
|
+
operation_src = operation_src.lower()
|
|
671
|
+
operation_dest = operation_dest.lower()
|
|
672
|
+
|
|
673
|
+
src_candidates = copy.copy(rse_settings_src['protocols'])
|
|
674
|
+
dest_candidates = copy.copy(rse_settings_dest['protocols'])
|
|
675
|
+
|
|
676
|
+
# Clean up src_candidates
|
|
677
|
+
tbr = list()
|
|
678
|
+
for protocol in src_candidates:
|
|
679
|
+
# Check if scheme given and filter if so
|
|
680
|
+
if scheme:
|
|
681
|
+
if not isinstance(scheme, list):
|
|
682
|
+
scheme = scheme.split(',')
|
|
683
|
+
if protocol['scheme'] not in scheme:
|
|
684
|
+
tbr.append(protocol)
|
|
685
|
+
continue
|
|
686
|
+
prot = protocol['domains'].get(domain, {}).get(operation_src, 1)
|
|
687
|
+
if prot is None or prot == 0:
|
|
688
|
+
tbr.append(protocol)
|
|
689
|
+
for r in tbr:
|
|
690
|
+
src_candidates.remove(r)
|
|
691
|
+
|
|
692
|
+
# Clean up dest_candidates
|
|
693
|
+
tbr = list()
|
|
694
|
+
for protocol in dest_candidates:
|
|
695
|
+
# Check if scheme given and filter if so
|
|
696
|
+
if scheme:
|
|
697
|
+
if not isinstance(scheme, list):
|
|
698
|
+
scheme = scheme.split(',')
|
|
699
|
+
if protocol['scheme'] not in scheme:
|
|
700
|
+
tbr.append(protocol)
|
|
701
|
+
continue
|
|
702
|
+
prot = protocol['domains'].get(domain, {}).get(operation_dest, 1)
|
|
703
|
+
if prot is None or prot == 0:
|
|
704
|
+
tbr.append(protocol)
|
|
705
|
+
for r in tbr:
|
|
706
|
+
dest_candidates.remove(r)
|
|
707
|
+
|
|
708
|
+
if not len(src_candidates) or not len(dest_candidates):
|
|
709
|
+
raise exception.RSEProtocolNotSupported('No protocol for provided settings found : %s.' % str(rse_settings_dest))
|
|
710
|
+
|
|
711
|
+
# Shuffle the candidates to load-balance across equal weights.
|
|
712
|
+
random.shuffle(dest_candidates)
|
|
713
|
+
random.shuffle(src_candidates)
|
|
714
|
+
|
|
715
|
+
# Select the one with the highest priority
|
|
716
|
+
dest_candidates = sorted(dest_candidates, key=lambda k: k['domains'][domain][operation_dest])
|
|
717
|
+
src_candidates = sorted(src_candidates, key=lambda k: k['domains'][domain][operation_src])
|
|
718
|
+
|
|
719
|
+
for dest_protocol in dest_candidates:
|
|
720
|
+
for src_protocol in src_candidates:
|
|
721
|
+
if __check_compatible_scheme(dest_protocol['scheme'], src_protocol['scheme']):
|
|
722
|
+
return (dest_protocol['scheme'], src_protocol['scheme'], dest_protocol['domains'][domain][operation_dest], src_protocol['domains'][domain][operation_src])
|
|
723
|
+
|
|
724
|
+
raise exception.RSEProtocolNotSupported('No protocol for provided settings found : %s.' % str(rse_settings_dest))
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
def _retry_protocol_stat(protocol, pfn):
|
|
728
|
+
"""
|
|
729
|
+
try to stat file, on fail try again 1s, 2s, 4s, 8s, 16s, 32s later. Fail is all fail
|
|
730
|
+
|
|
731
|
+
:param protocol: The protocol to use to reach this file
|
|
732
|
+
:param pfn: Physical file name of the target for the protocol stat
|
|
733
|
+
"""
|
|
734
|
+
retries = config_get_int('client', 'protocol_stat_retries', raise_exception=False, default=6)
|
|
735
|
+
for attempt in range(retries):
|
|
736
|
+
try:
|
|
737
|
+
stats = protocol.stat(pfn)
|
|
738
|
+
return stats
|
|
739
|
+
except exception.RSEChecksumUnavailable as e:
|
|
740
|
+
# The stat succeeded here, but the checksum failed
|
|
741
|
+
raise e
|
|
742
|
+
except NotImplementedError:
|
|
743
|
+
break
|
|
744
|
+
except Exception:
|
|
745
|
+
sleep(2**attempt)
|
|
746
|
+
return protocol.stat(pfn)
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
def __check_compatible_scheme(dest_scheme, src_scheme):
|
|
750
|
+
"""
|
|
751
|
+
Check if two schemes are compatible, such as srm and gsiftp
|
|
752
|
+
|
|
753
|
+
:param dest_scheme: Destination scheme
|
|
754
|
+
:param src_scheme: Source scheme
|
|
755
|
+
:param scheme: List of supported schemes
|
|
756
|
+
:returns: True if schemes are compatible, False otherwise.
|
|
757
|
+
"""
|
|
758
|
+
|
|
759
|
+
if dest_scheme == src_scheme:
|
|
760
|
+
return True
|
|
761
|
+
if src_scheme in constants.SCHEME_MAP.get(dest_scheme, []):
|
|
762
|
+
return True
|
|
763
|
+
|
|
764
|
+
return False
|