rucio-clients 35.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rucio-clients might be problematic. Click here for more details.
- 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.7.0.data/data/etc/rse-accounts.cfg.template +25 -0
- rucio_clients-35.7.0.data/data/etc/rucio.cfg.atlas.client.template +42 -0
- rucio_clients-35.7.0.data/data/etc/rucio.cfg.template +257 -0
- rucio_clients-35.7.0.data/data/requirements.client.txt +15 -0
- rucio_clients-35.7.0.data/data/rucio_client/merge_rucio_configs.py +144 -0
- rucio_clients-35.7.0.data/scripts/rucio +2542 -0
- rucio_clients-35.7.0.data/scripts/rucio-admin +2447 -0
- rucio_clients-35.7.0.dist-info/METADATA +50 -0
- rucio_clients-35.7.0.dist-info/RECORD +88 -0
- rucio_clients-35.7.0.dist-info/WHEEL +5 -0
- rucio_clients-35.7.0.dist-info/licenses/AUTHORS.rst +97 -0
- rucio_clients-35.7.0.dist-info/licenses/LICENSE +201 -0
- rucio_clients-35.7.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,338 @@
|
|
|
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 os
|
|
16
|
+
import re
|
|
17
|
+
import urllib.parse as urlparse
|
|
18
|
+
from subprocess import getstatusoutput
|
|
19
|
+
|
|
20
|
+
from rucio.common import exception
|
|
21
|
+
from rucio.common.utils import execute
|
|
22
|
+
from rucio.rse.protocols import protocol
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Default(protocol.RSEProtocol):
|
|
26
|
+
""" Implementing access to RSEs using the SRM protocol. """
|
|
27
|
+
|
|
28
|
+
def lfns2pfns(self, lfns):
|
|
29
|
+
"""
|
|
30
|
+
Returns a fully qualified PFN for the file referred by path.
|
|
31
|
+
|
|
32
|
+
:param path: The path to the file.
|
|
33
|
+
:returns: Fully qualified PFN.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
pfns = {}
|
|
37
|
+
prefix = self.attributes['prefix']
|
|
38
|
+
if self.attributes['extended_attributes'] is not None and\
|
|
39
|
+
'web_service_path' in list(self.attributes['extended_attributes'].keys()):
|
|
40
|
+
web_service_path = self.attributes['extended_attributes']['web_service_path']
|
|
41
|
+
else:
|
|
42
|
+
web_service_path = ''
|
|
43
|
+
|
|
44
|
+
if not prefix.startswith('/'):
|
|
45
|
+
prefix = ''.join(['/', prefix])
|
|
46
|
+
if not prefix.endswith('/'):
|
|
47
|
+
prefix = ''.join([prefix, '/'])
|
|
48
|
+
|
|
49
|
+
hostname = self.attributes['hostname']
|
|
50
|
+
if '://' in hostname:
|
|
51
|
+
hostname = hostname.split("://")[1]
|
|
52
|
+
|
|
53
|
+
lfns = [lfns] if isinstance(lfns, dict) else lfns
|
|
54
|
+
if not self.attributes['port']:
|
|
55
|
+
for lfn in lfns:
|
|
56
|
+
scope, name, path = lfn['scope'], lfn['name'], lfn.get('path')
|
|
57
|
+
if not path:
|
|
58
|
+
path = self._get_path(scope=scope, name=name)
|
|
59
|
+
if path.startswith('/'):
|
|
60
|
+
path = path[1:]
|
|
61
|
+
pfns['%s:%s' % (scope, name)] = ''.join([self.attributes['scheme'], '://',
|
|
62
|
+
hostname, web_service_path, prefix, path])
|
|
63
|
+
else:
|
|
64
|
+
for lfn in lfns:
|
|
65
|
+
scope, name, path = lfn['scope'], lfn['name'], lfn.get('path')
|
|
66
|
+
if not path:
|
|
67
|
+
path = self._get_path(scope=scope, name=name)
|
|
68
|
+
if path.startswith('/'):
|
|
69
|
+
path = path[1:]
|
|
70
|
+
pfns['%s:%s' % (scope, name)] = ''.join([self.attributes['scheme'], '://',
|
|
71
|
+
hostname, ':', str(self.attributes['port']),
|
|
72
|
+
web_service_path, prefix, path])
|
|
73
|
+
|
|
74
|
+
return pfns
|
|
75
|
+
|
|
76
|
+
def parse_pfns(self, pfns):
|
|
77
|
+
"""
|
|
78
|
+
Splits the given PFN into the parts known by the protocol. During parsing the PFN is also checked for
|
|
79
|
+
validity on the given RSE with the given protocol.
|
|
80
|
+
|
|
81
|
+
:param pfn: a fully qualified PFN
|
|
82
|
+
:returns: a dict containing all known parts of the PFN for the protocol e.g. scheme, path, filename
|
|
83
|
+
:raises RSEFileNameNotSupported: if the provided PFN doesn't match with the protocol settings
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
ret = dict()
|
|
87
|
+
pfns = [pfns] if isinstance(pfns, str) else pfns
|
|
88
|
+
for pfn in pfns:
|
|
89
|
+
parsed = urlparse.urlparse(pfn)
|
|
90
|
+
if parsed.path.startswith('/srm/managerv2') or\
|
|
91
|
+
parsed.path.startswith('/srm/managerv1') or\
|
|
92
|
+
parsed.path.startswith('/srm/v2/server'):
|
|
93
|
+
scheme, hostname, port, service_path, path = re.findall(r"([^:]+)://([^:/]+):?(\d+)?([^:]+=)?([^:]+)", pfn)[0]
|
|
94
|
+
else:
|
|
95
|
+
scheme = parsed.scheme
|
|
96
|
+
hostname = parsed.netloc.partition(':')[0]
|
|
97
|
+
port = parsed.netloc.partition(':')[2]
|
|
98
|
+
path = parsed.path
|
|
99
|
+
service_path = ''
|
|
100
|
+
|
|
101
|
+
# force type conversion
|
|
102
|
+
try:
|
|
103
|
+
port = int(port)
|
|
104
|
+
except:
|
|
105
|
+
port = ''
|
|
106
|
+
|
|
107
|
+
if self.attributes['hostname'] != hostname and\
|
|
108
|
+
self.attributes['hostname'] != scheme + "://" + hostname:
|
|
109
|
+
raise exception.RSEFileNameNotSupported('Invalid hostname: provided \'%s\', expected \'%s\'' % (hostname,
|
|
110
|
+
self.attributes['hostname']))
|
|
111
|
+
|
|
112
|
+
if port != '' and str(self.attributes['port']) != str(port):
|
|
113
|
+
raise exception.RSEFileNameNotSupported('Invalid port: provided \'%s\', expected \'%s\'' % (port,
|
|
114
|
+
self.attributes['port']))
|
|
115
|
+
elif port == '':
|
|
116
|
+
port = self.attributes['port']
|
|
117
|
+
|
|
118
|
+
if not path.startswith(self.attributes['prefix']):
|
|
119
|
+
raise exception.RSEFileNameNotSupported('Invalid prefix: provided \'%s\', expected \'%s\'' % ('/'.join(path.split('/')[0:len(self.attributes['prefix'].split('/')) - 1]),
|
|
120
|
+
self.attributes['prefix'])) # len(...)-1 due to the leading '/
|
|
121
|
+
|
|
122
|
+
# Splitting path into prefix, path, filename
|
|
123
|
+
prefix = self.attributes['prefix']
|
|
124
|
+
path = path.partition(self.attributes['prefix'])[2]
|
|
125
|
+
name = path.split('/')[-1]
|
|
126
|
+
path = '/' + '/'.join(path.split('/')[:-1]) if not self.rse['staging_area'] else None
|
|
127
|
+
|
|
128
|
+
if path != '/' and path[:-1] != '/':
|
|
129
|
+
path += '/'
|
|
130
|
+
|
|
131
|
+
ret[pfn] = {'scheme': scheme, 'port': port, 'hostname': hostname,
|
|
132
|
+
'path': path, 'name': name, 'prefix': prefix,
|
|
133
|
+
'web_service_path': service_path}
|
|
134
|
+
|
|
135
|
+
return ret
|
|
136
|
+
|
|
137
|
+
def path2pfn(self, path):
|
|
138
|
+
"""
|
|
139
|
+
Returns a fully qualified PFN for the file referred by path.
|
|
140
|
+
|
|
141
|
+
:param path: The path to the file.
|
|
142
|
+
:returns: Fully qualified PFN.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
if path.startswith("srm://"):
|
|
146
|
+
return path
|
|
147
|
+
|
|
148
|
+
hostname = self.attributes['hostname']
|
|
149
|
+
if '://' in hostname:
|
|
150
|
+
hostname = hostname.split("://")[1]
|
|
151
|
+
|
|
152
|
+
if 'extended_attributes' in list(self.attributes.keys()) and\
|
|
153
|
+
self.attributes['extended_attributes'] is not None and\
|
|
154
|
+
'web_service_path' in list(self.attributes['extended_attributes'].keys()):
|
|
155
|
+
web_service_path = self.attributes['extended_attributes']['web_service_path']
|
|
156
|
+
else:
|
|
157
|
+
web_service_path = ''
|
|
158
|
+
|
|
159
|
+
if not path.startswith('srm'):
|
|
160
|
+
if self.attributes['port'] > 0:
|
|
161
|
+
return ''.join([self.attributes['scheme'], '://', hostname, ':', str(self.attributes['port']), web_service_path, path])
|
|
162
|
+
else:
|
|
163
|
+
return ''.join([self.attributes['scheme'], '://', hostname, web_service_path, path])
|
|
164
|
+
else:
|
|
165
|
+
return path
|
|
166
|
+
|
|
167
|
+
def connect(self):
|
|
168
|
+
"""
|
|
169
|
+
Establishes the actual connection to the referred RSE.
|
|
170
|
+
As a quick and dirty implementation we just use this method to check if the lcg tools are available.
|
|
171
|
+
If we decide to use gfal, init should be done here.
|
|
172
|
+
|
|
173
|
+
:raises RSEAccessDenied: Cannot connect.
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
status, lcglscommand = getstatusoutput('which lcg-ls') # noqa: S605, S607
|
|
177
|
+
if status:
|
|
178
|
+
raise exception.RSEAccessDenied('Cannot find lcg tools')
|
|
179
|
+
endpoint_basepath = self.path2pfn(self.attributes['prefix'])
|
|
180
|
+
status, result = getstatusoutput('%s -vv $LCGVO -b --srm-timeout 60 -D srmv2 -l %s' % (lcglscommand, endpoint_basepath)) # noqa: S605
|
|
181
|
+
if status:
|
|
182
|
+
if result == '':
|
|
183
|
+
raise exception.RSEAccessDenied('Endpoint not reachable. lcg-ls failed with status code %s but no further details.' % (str(status)))
|
|
184
|
+
else:
|
|
185
|
+
raise exception.RSEAccessDenied('Endpoint not reachable : %s' % str(result))
|
|
186
|
+
|
|
187
|
+
def get(self, path, dest, transfer_timeout=None):
|
|
188
|
+
"""
|
|
189
|
+
Provides access to files stored inside connected the RSE.
|
|
190
|
+
|
|
191
|
+
:param path: Physical file name of requested file
|
|
192
|
+
:param dest: Name and path of the files when stored at the client
|
|
193
|
+
:param transfer_timeout: Transfer timeout (in seconds)
|
|
194
|
+
|
|
195
|
+
:raises DestinationNotAccessible: if the destination storage was not accessible.
|
|
196
|
+
:raises ServiceUnavailable: if some generic error occurred in the library.
|
|
197
|
+
:raises SourceNotFound: if the source file was not found on the referred storage.
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
timeout_option = ''
|
|
201
|
+
if transfer_timeout:
|
|
202
|
+
timeout_option = '--sendreceive-timeout %s' % transfer_timeout
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
cmd = 'lcg-cp $LCGVO -v -b --srm-timeout 3600 %s -D srmv2 %s file:%s' % (timeout_option, path, dest)
|
|
206
|
+
status, out, err = execute(cmd)
|
|
207
|
+
if status:
|
|
208
|
+
if self.__parse_srm_error__("SRM_INVALID_PATH", out, err):
|
|
209
|
+
raise exception.SourceNotFound(err)
|
|
210
|
+
raise exception.RucioException(err)
|
|
211
|
+
except exception.SourceNotFound as error:
|
|
212
|
+
raise exception.SourceNotFound(str(error))
|
|
213
|
+
except Exception as error:
|
|
214
|
+
raise exception.ServiceUnavailable(error)
|
|
215
|
+
|
|
216
|
+
def put(self, source, target, source_dir, transfer_timeout=None):
|
|
217
|
+
"""
|
|
218
|
+
Allows to store files inside the referred RSE.
|
|
219
|
+
|
|
220
|
+
:param source: path to the source file on the client file system
|
|
221
|
+
:param target: path to the destination file on the storage
|
|
222
|
+
:param source_dir: Path where the to be transferred files are stored in the local file system
|
|
223
|
+
:param transfer_timeout: Transfer timeout (in seconds)
|
|
224
|
+
|
|
225
|
+
:raises DestinationNotAccessible: if the destination storage was not accessible.
|
|
226
|
+
:raises ServiceUnavailable: if some generic error occurred in the library.
|
|
227
|
+
:raises SourceNotFound: if the source file was not found on the referred storage.
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
source_url = '%s/%s' % (source_dir, source) if source_dir else source
|
|
231
|
+
|
|
232
|
+
if not os.path.exists(source_url):
|
|
233
|
+
raise exception.SourceNotFound()
|
|
234
|
+
|
|
235
|
+
space_token = ''
|
|
236
|
+
if self.attributes['extended_attributes'] is not None and 'space_token' in list(self.attributes['extended_attributes'].keys()):
|
|
237
|
+
space_token = '--dst %s' % self.attributes['extended_attributes']['space_token']
|
|
238
|
+
|
|
239
|
+
timeout_option = ''
|
|
240
|
+
if transfer_timeout:
|
|
241
|
+
timeout_option = '--sendreceive-timeout %s' % transfer_timeout
|
|
242
|
+
|
|
243
|
+
try:
|
|
244
|
+
cmd = 'lcg-cp $LCGVO -v -b --srm-timeout 3600 %s -D srmv2 %s file:%s %s' % (timeout_option, space_token, source_url, target)
|
|
245
|
+
status, out, err = execute(cmd)
|
|
246
|
+
if status:
|
|
247
|
+
raise exception.RucioException(err)
|
|
248
|
+
except Exception as error:
|
|
249
|
+
raise exception.ServiceUnavailable(error)
|
|
250
|
+
|
|
251
|
+
def delete(self, path):
|
|
252
|
+
"""
|
|
253
|
+
Deletes a file from the connected RSE.
|
|
254
|
+
|
|
255
|
+
:param path: path to the to be deleted file
|
|
256
|
+
:raises ServiceUnavailable: if some generic error occurred in the library.
|
|
257
|
+
:raises SourceNotFound: if the source file was not found on the referred storage.
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
pfns = [path] if isinstance(path, str) else path
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
pfn_chunks = [pfns[i:i + 20] for i in range(0, len(pfns), 20)]
|
|
264
|
+
for pfn_chunk in pfn_chunks:
|
|
265
|
+
cmd = 'lcg-del $LCGVO -v -b -l --srm-timeout 600 -D srmv2'
|
|
266
|
+
for pfn in pfn_chunk:
|
|
267
|
+
cmd += ' ' + pfn
|
|
268
|
+
status, out, err = execute(cmd)
|
|
269
|
+
if status:
|
|
270
|
+
if self.__parse_srm_error__("SRM_INVALID_PATH", out, err):
|
|
271
|
+
raise exception.SourceNotFound(err)
|
|
272
|
+
raise exception.RucioException(err)
|
|
273
|
+
except exception.SourceNotFound as error:
|
|
274
|
+
raise exception.SourceNotFound(str(error))
|
|
275
|
+
except Exception as error:
|
|
276
|
+
raise exception.ServiceUnavailable(error)
|
|
277
|
+
|
|
278
|
+
def rename(self, path, new_path):
|
|
279
|
+
"""
|
|
280
|
+
Allows to rename a file stored inside the connected RSE.
|
|
281
|
+
|
|
282
|
+
:param path: path to the current file on the storage
|
|
283
|
+
:param new_path: path to the new file on the storage
|
|
284
|
+
:raises DestinationNotAccessible: if the destination storage was not accessible.
|
|
285
|
+
:raises ServiceUnavailable: if some generic error occurred in the library.
|
|
286
|
+
:raises SourceNotFound: if the source file was not found on the referred storage.
|
|
287
|
+
"""
|
|
288
|
+
|
|
289
|
+
space_token = ''
|
|
290
|
+
if self.attributes['extended_attributes'] is not None and 'space_token' in list(self.attributes['extended_attributes'].keys()):
|
|
291
|
+
space_token = '--dst %s' % self.attributes['extended_attributes']['space_token']
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
cmd = 'lcg-cp $LCGVO -v -b --srm-timeout 3600 -D srmv2 %s %s %s' % (space_token, path, new_path)
|
|
295
|
+
status, out, err = execute(cmd)
|
|
296
|
+
if status:
|
|
297
|
+
raise exception.RucioException(err)
|
|
298
|
+
|
|
299
|
+
cmd = 'lcg-del $LCGVO -v -b -l --srm-timeout 600 -D srmv2 %s' % (path)
|
|
300
|
+
status, out, err = execute(cmd)
|
|
301
|
+
if status:
|
|
302
|
+
raise exception.RucioException(err)
|
|
303
|
+
except Exception as error:
|
|
304
|
+
raise exception.ServiceUnavailable(error)
|
|
305
|
+
|
|
306
|
+
def exists(self, path):
|
|
307
|
+
"""
|
|
308
|
+
Checks if the requested file is known by the referred RSE.
|
|
309
|
+
|
|
310
|
+
:param path: Physical file name
|
|
311
|
+
:returns: True if the file exists, False if it doesn't
|
|
312
|
+
:raises SourceNotFound: if the source file was not found on the referred storage.
|
|
313
|
+
"""
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
cmd = 'lcg-ls $LCGVO -v -b --srm-timeout 60 -D srmv2 %s' % (path)
|
|
317
|
+
status, out, err = execute(cmd)
|
|
318
|
+
if status:
|
|
319
|
+
return False
|
|
320
|
+
return True
|
|
321
|
+
except Exception as error:
|
|
322
|
+
raise exception.ServiceUnavailable(error)
|
|
323
|
+
|
|
324
|
+
def __parse_srm_error__(self, err_code, out, err):
|
|
325
|
+
"""Parse the error message to return error code."""
|
|
326
|
+
if out is not None and len(out) > 0:
|
|
327
|
+
if out.count(err_code) > 0:
|
|
328
|
+
return True
|
|
329
|
+
if err is not None and len(err) > 0:
|
|
330
|
+
if err.count(err_code) > 0:
|
|
331
|
+
return True
|
|
332
|
+
return False
|
|
333
|
+
|
|
334
|
+
def close(self):
|
|
335
|
+
"""
|
|
336
|
+
Closes the connection to RSE.
|
|
337
|
+
"""
|
|
338
|
+
pass
|