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.

Files changed (88) hide show
  1. rucio/__init__.py +17 -0
  2. rucio/alembicrevision.py +15 -0
  3. rucio/client/__init__.py +15 -0
  4. rucio/client/accountclient.py +433 -0
  5. rucio/client/accountlimitclient.py +183 -0
  6. rucio/client/baseclient.py +974 -0
  7. rucio/client/client.py +76 -0
  8. rucio/client/configclient.py +126 -0
  9. rucio/client/credentialclient.py +59 -0
  10. rucio/client/didclient.py +866 -0
  11. rucio/client/diracclient.py +56 -0
  12. rucio/client/downloadclient.py +1785 -0
  13. rucio/client/exportclient.py +44 -0
  14. rucio/client/fileclient.py +50 -0
  15. rucio/client/importclient.py +42 -0
  16. rucio/client/lifetimeclient.py +90 -0
  17. rucio/client/lockclient.py +109 -0
  18. rucio/client/metaconventionsclient.py +140 -0
  19. rucio/client/pingclient.py +44 -0
  20. rucio/client/replicaclient.py +454 -0
  21. rucio/client/requestclient.py +125 -0
  22. rucio/client/rseclient.py +746 -0
  23. rucio/client/ruleclient.py +294 -0
  24. rucio/client/scopeclient.py +90 -0
  25. rucio/client/subscriptionclient.py +173 -0
  26. rucio/client/touchclient.py +82 -0
  27. rucio/client/uploadclient.py +955 -0
  28. rucio/common/__init__.py +13 -0
  29. rucio/common/cache.py +74 -0
  30. rucio/common/config.py +801 -0
  31. rucio/common/constants.py +159 -0
  32. rucio/common/constraints.py +17 -0
  33. rucio/common/didtype.py +189 -0
  34. rucio/common/exception.py +1151 -0
  35. rucio/common/extra.py +36 -0
  36. rucio/common/logging.py +420 -0
  37. rucio/common/pcache.py +1408 -0
  38. rucio/common/plugins.py +153 -0
  39. rucio/common/policy.py +84 -0
  40. rucio/common/schema/__init__.py +150 -0
  41. rucio/common/schema/atlas.py +413 -0
  42. rucio/common/schema/belleii.py +408 -0
  43. rucio/common/schema/domatpc.py +401 -0
  44. rucio/common/schema/escape.py +426 -0
  45. rucio/common/schema/generic.py +433 -0
  46. rucio/common/schema/generic_multi_vo.py +412 -0
  47. rucio/common/schema/icecube.py +406 -0
  48. rucio/common/stomp_utils.py +159 -0
  49. rucio/common/stopwatch.py +55 -0
  50. rucio/common/test_rucio_server.py +148 -0
  51. rucio/common/types.py +403 -0
  52. rucio/common/utils.py +2238 -0
  53. rucio/rse/__init__.py +96 -0
  54. rucio/rse/protocols/__init__.py +13 -0
  55. rucio/rse/protocols/bittorrent.py +184 -0
  56. rucio/rse/protocols/cache.py +122 -0
  57. rucio/rse/protocols/dummy.py +111 -0
  58. rucio/rse/protocols/gfal.py +703 -0
  59. rucio/rse/protocols/globus.py +243 -0
  60. rucio/rse/protocols/gsiftp.py +92 -0
  61. rucio/rse/protocols/http_cache.py +82 -0
  62. rucio/rse/protocols/mock.py +123 -0
  63. rucio/rse/protocols/ngarc.py +209 -0
  64. rucio/rse/protocols/posix.py +250 -0
  65. rucio/rse/protocols/protocol.py +594 -0
  66. rucio/rse/protocols/rclone.py +364 -0
  67. rucio/rse/protocols/rfio.py +136 -0
  68. rucio/rse/protocols/srm.py +338 -0
  69. rucio/rse/protocols/ssh.py +413 -0
  70. rucio/rse/protocols/storm.py +206 -0
  71. rucio/rse/protocols/webdav.py +550 -0
  72. rucio/rse/protocols/xrootd.py +301 -0
  73. rucio/rse/rsemanager.py +764 -0
  74. rucio/vcsversion.py +11 -0
  75. rucio/version.py +38 -0
  76. rucio_clients-35.7.0.data/data/etc/rse-accounts.cfg.template +25 -0
  77. rucio_clients-35.7.0.data/data/etc/rucio.cfg.atlas.client.template +42 -0
  78. rucio_clients-35.7.0.data/data/etc/rucio.cfg.template +257 -0
  79. rucio_clients-35.7.0.data/data/requirements.client.txt +15 -0
  80. rucio_clients-35.7.0.data/data/rucio_client/merge_rucio_configs.py +144 -0
  81. rucio_clients-35.7.0.data/scripts/rucio +2542 -0
  82. rucio_clients-35.7.0.data/scripts/rucio-admin +2447 -0
  83. rucio_clients-35.7.0.dist-info/METADATA +50 -0
  84. rucio_clients-35.7.0.dist-info/RECORD +88 -0
  85. rucio_clients-35.7.0.dist-info/WHEEL +5 -0
  86. rucio_clients-35.7.0.dist-info/licenses/AUTHORS.rst +97 -0
  87. rucio_clients-35.7.0.dist-info/licenses/LICENSE +201 -0
  88. rucio_clients-35.7.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,703 @@
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 errno
16
+ import json
17
+ import logging
18
+ import os
19
+ import re
20
+ import subprocess
21
+ import urllib.parse as urlparse
22
+ from threading import Timer
23
+
24
+ from rucio.common import config, exception
25
+ from rucio.common.constraints import STRING_TYPES
26
+ from rucio.common.utils import GLOBALLY_SUPPORTED_CHECKSUMS, PREFERRED_CHECKSUM
27
+ from rucio.rse.protocols import protocol
28
+
29
+ try:
30
+ import gfal2 # pylint: disable=import-error
31
+ except:
32
+ if 'RUCIO_CLIENT_MODE' not in os.environ:
33
+ if not config.config_has_section('database'):
34
+ raise exception.MissingDependency('Missing dependency : gfal2')
35
+ else:
36
+ if os.environ['RUCIO_CLIENT_MODE']:
37
+ raise exception.MissingDependency('Missing dependency : gfal2')
38
+
39
+ TIMEOUT = config.config_get('deletion', 'timeout', False, None)
40
+
41
+
42
+ class Default(protocol.RSEProtocol):
43
+ """ Implementing access to RSEs using the srm protocol."""
44
+
45
+ def lfns2pfns(self, lfns):
46
+ """
47
+ Returns a fully qualified PFN for the file referred by path.
48
+
49
+ :param path: The path to the file.
50
+
51
+ :returns: Fully qualified PFN.
52
+ """
53
+ lfns = [lfns] if isinstance(lfns, dict) else lfns
54
+
55
+ pfns = {}
56
+ prefix = self.attributes['prefix']
57
+ if self.attributes['extended_attributes'] is not None and 'web_service_path' in list(self.attributes['extended_attributes'].keys()):
58
+ web_service_path = self.attributes['extended_attributes']['web_service_path']
59
+ else:
60
+ web_service_path = ''
61
+
62
+ if not prefix.startswith('/'):
63
+ prefix = ''.join(['/', prefix])
64
+ if not prefix.endswith('/'):
65
+ prefix = ''.join([prefix, '/'])
66
+
67
+ hostname = self.attributes['hostname']
68
+ if '://' in hostname:
69
+ hostname = hostname.split("://")[1]
70
+
71
+ if self.attributes['port'] == 0:
72
+ for lfn in lfns:
73
+ scope, name = str(lfn['scope']), lfn['name']
74
+ path = lfn['path'] if 'path' in lfn and lfn['path'] else self._get_path(scope=scope, name=name)
75
+ if self.attributes['scheme'] != 'root' and path.startswith('/'): # do not modify path if it is root
76
+ path = path[1:]
77
+ pfns['%s:%s' % (scope, name)] = ''.join([self.attributes['scheme'], '://', hostname, web_service_path, prefix, path])
78
+ else:
79
+ for lfn in lfns:
80
+ scope, name = str(lfn['scope']), lfn['name']
81
+ path = lfn['path'] if 'path' in lfn and lfn['path'] else self._get_path(scope=scope, name=name)
82
+ if self.attributes['scheme'] != 'root' and path.startswith('/'): # do not modify path if it is root
83
+ path = path[1:]
84
+ if re.match(r'^\w+://', path): # This is already a URL
85
+ pfns['%s:%s' % (scope, name)] = path
86
+ else:
87
+ pfns['%s:%s' % (scope, name)] = ''.join([self.attributes['scheme'], '://', hostname, ':', str(self.attributes['port']), web_service_path, prefix, path])
88
+
89
+ return pfns
90
+
91
+ def parse_pfns(self, pfns):
92
+ """
93
+ Splits the given PFN into the parts known by the protocol. During parsing the PFN is also checked for
94
+ validity on the given RSE with the given protocol.
95
+
96
+ :param pfn: a fully qualified PFN
97
+
98
+ :returns: a dict containing all known parts of the PFN for the protocol e.g. scheme, path, filename
99
+
100
+ :raises RSEFileNameNotSupported: if the provided PFN doesn't match with the protocol settings
101
+ """
102
+
103
+ self.logger(logging.DEBUG, 'parsing {} pfns'.format(len(list(pfns))))
104
+ ret = dict()
105
+ pfns = [pfns] if isinstance(pfns, STRING_TYPES) else pfns
106
+ for pfn in pfns:
107
+ parsed = urlparse.urlparse(pfn)
108
+ if parsed.path.startswith('/srm/managerv2') or parsed.path.startswith('/srm/managerv1') or parsed.path.startswith('/srm/v2/server'):
109
+ scheme, hostname, port, service_path, path = re.findall(r"([^:]+)://([^:/]+):?(\d+)?([^:]+=)?([^:]+)", pfn)[0]
110
+ else:
111
+ scheme = parsed.scheme
112
+ hostname = parsed.netloc.partition(':')[0]
113
+ port = parsed.netloc.partition(':')[2]
114
+ path = parsed.path
115
+ service_path = ''
116
+
117
+ if self.attributes['hostname'] != hostname and self.attributes['hostname'] != scheme + "://" + hostname:
118
+ raise exception.RSEFileNameNotSupported('Invalid hostname: provided \'%s\', expected \'%s\'' % (hostname, self.attributes['hostname']))
119
+
120
+ if port != '' and str(self.attributes['port']) != str(port):
121
+ raise exception.RSEFileNameNotSupported('Invalid port: provided \'%s\', expected \'%s\'' % (port, self.attributes['port']))
122
+ elif port == '':
123
+ port = self.attributes['port']
124
+
125
+ if not path.startswith(self.attributes['prefix']):
126
+ raise exception.RSEFileNameNotSupported('Invalid prefix: provided \'%s\', expected \'%s\'' % ('/'.join(path.split('/')[0:len(self.attributes['prefix'].split('/')) - 1]),
127
+ self.attributes['prefix'])) # len(...)-1 due to the leading '/
128
+ # Splitting path into prefix, path, filename
129
+ prefix = self.attributes['prefix']
130
+ path = path.partition(self.attributes['prefix'])[2]
131
+ name = path.split('/')[-1]
132
+ path = '/'.join(path.split('/')[:-1])
133
+ if not path.startswith('/'):
134
+ path = '/' + path
135
+ if path != '/' and not path.endswith('/'):
136
+ path = path + '/'
137
+ ret[pfn] = {'scheme': scheme, 'port': port, 'hostname': hostname, 'path': path, 'name': name, 'prefix': prefix, 'web_service_path': service_path}
138
+
139
+ return ret
140
+
141
+ def path2pfn(self, path):
142
+ """
143
+ Returns a fully qualified PFN for the file referred by path.
144
+
145
+ :param path: The path to the file.
146
+
147
+ :returns: Fully qualified PFN.
148
+ """
149
+ self.logger(logging.DEBUG, 'getting pfn for {}'.format(path))
150
+
151
+ if '://' in path:
152
+ return path
153
+
154
+ hostname = self.attributes['hostname']
155
+ if '://' in hostname:
156
+ hostname = hostname.split("://")[1]
157
+
158
+ if 'extended_attributes' in list(self.attributes.keys()) and self.attributes['extended_attributes'] is not None and 'web_service_path' in list(self.attributes['extended_attributes'].keys()):
159
+ web_service_path = self.attributes['extended_attributes']['web_service_path']
160
+ else:
161
+ web_service_path = ''
162
+
163
+ if not path.startswith('srm'):
164
+ if self.attributes['port'] > 0:
165
+ return ''.join([self.attributes['scheme'], '://', hostname, ':', str(self.attributes['port']), web_service_path, path])
166
+ else:
167
+ return ''.join([self.attributes['scheme'], '://', hostname, web_service_path, path])
168
+ else:
169
+ return path
170
+
171
+ def connect(self):
172
+ """
173
+ Establishes the actual connection to the referred RSE.
174
+ If we decide to use gfal, init should be done here.
175
+
176
+ :raises RSEAccessDenied
177
+ """
178
+ self.logger(logging.DEBUG, 'connecting to storage')
179
+
180
+ if 'RUCIO_CLIENT_MODE' in os.environ:
181
+ gfal2.set_verbose(gfal2.verbose_level.verbose)
182
+ else:
183
+ gfal2.set_verbose(gfal2.verbose_level.warning)
184
+
185
+ self.__ctx = gfal2.creat_context() # pylint: disable=no-member
186
+ self.__ctx.set_opt_string_list("SRM PLUGIN", "TURL_PROTOCOLS", ["gsiftp", "rfio", "gsidcap", "dcap", "kdcap"])
187
+ self.__ctx.set_opt_string("XROOTD PLUGIN", "XRD.WANTPROT", "gsi,unix")
188
+ self.__ctx.set_opt_boolean("XROOTD PLUGIN", "NORMALIZE_PATH", False)
189
+ auth_configured = False
190
+ if self.auth_token:
191
+ self.__ctx.set_opt_string("BEARER", "TOKEN", self.auth_token)
192
+ auth_configured = True
193
+ # Configure gfal authentication to use the rucio client proxy if and only if gfal didn't initialize its credentials already
194
+ # (https://gitlab.cern.ch/dmc/gfal2/-/blob/48cfe3476392c884b53d00799198b1238603a406/src/core/common/gfal_common.c#L79)
195
+ if not auth_configured:
196
+ try:
197
+ self.__ctx.get_opt_string("X509", "CERT")
198
+ self.__ctx.get_opt_string("X509", "KEY")
199
+ auth_configured = True
200
+ except gfal2.GError: # pylint: disable=no-member
201
+ pass
202
+ if not auth_configured:
203
+ try:
204
+ self.__ctx.get_opt_string("BEARER", "TOKEN")
205
+ auth_configured = True
206
+ except gfal2.GError: # pylint: disable=no-member
207
+ pass
208
+ if not auth_configured:
209
+ proxy = config.config_get('client', 'client_x509_proxy', default=None, raise_exception=False)
210
+ if proxy:
211
+ self.logger(logging.INFO, 'Configuring authentication to use {}'.format(proxy))
212
+ self.__ctx.set_opt_string("X509", "CERT", proxy)
213
+ self.__ctx.set_opt_string("X509", "KEY", proxy)
214
+
215
+ if TIMEOUT:
216
+ try:
217
+ timeout = int(TIMEOUT)
218
+ self.__ctx.set_opt_integer("HTTP PLUGIN", "OPERATION_TIMEOUT", timeout)
219
+ self.__ctx.set_opt_integer("SRM PLUGIN", "OPERATION_TIMEOUT", timeout)
220
+ self.__ctx.set_opt_integer("GRIDFTP PLUGIN", "OPERATION_TIMEOUT", timeout)
221
+ except ValueError:
222
+ self.logger(logging.ERROR, 'wrong timeout value %s', TIMEOUT)
223
+
224
+ def get(self, path, dest, transfer_timeout=None):
225
+ """
226
+ Provides access to files stored inside connected the RSE.
227
+
228
+ :param path: Physical file name of requested file
229
+ :param dest: Name and path of the files when stored at the client
230
+ :param transfer_timeout: Transfer timeout (in seconds)
231
+
232
+ :raises DestinationNotAccessible: if the destination storage was not accessible.
233
+ :raises ServiceUnavailable: if some generic error occurred in the library.
234
+ :raises SourceNotFound: if the source file was not found on the referred storage.
235
+ """
236
+ self.logger(logging.DEBUG, 'downloading file from {} to {}'.format(path, dest))
237
+
238
+ dest = os.path.abspath(dest)
239
+ if ':' not in dest:
240
+ dest = "file://" + dest
241
+
242
+ try:
243
+ status = self.__gfal2_copy(path, dest, transfer_timeout=transfer_timeout)
244
+ if status:
245
+ raise exception.RucioException()
246
+ except exception.DestinationNotAccessible as error:
247
+ raise exception.DestinationNotAccessible(str(error))
248
+ except exception.SourceNotFound as error:
249
+ raise exception.SourceNotFound(str(error))
250
+ except Exception as error:
251
+ raise exception.ServiceUnavailable(error)
252
+
253
+ def put(self, source, target, source_dir, transfer_timeout=None):
254
+ """
255
+ Allows to store files inside the referred RSE.
256
+
257
+ :param source: path to the source file on the client file system
258
+ :param target: path to the destination file on the storage
259
+ :param source_dir: Path where the to be transferred files are stored in the local file system
260
+ :param transfer_timeout: Transfer timeout (in seconds)
261
+
262
+ :raises DestinationNotAccessible: if the destination storage was not accessible.
263
+ :raises ServiceUnavailable: if some generic error occurred in the library.
264
+ :raises SourceNotFound: if the source file was not found on the referred storage.
265
+ """
266
+ self.logger(logging.DEBUG, 'uploading file from {} to {}'.format(source, target))
267
+
268
+ source_url = '%s/%s' % (source_dir, source) if source_dir else source
269
+ source_url = os.path.abspath(source_url)
270
+ if not os.path.exists(source_url):
271
+ raise exception.SourceNotFound()
272
+ if ':' not in source_url:
273
+ source_url = "file://" + source_url
274
+
275
+ space_token = None
276
+ if self.attributes['extended_attributes'] is not None and 'space_token' in list(self.attributes['extended_attributes'].keys()):
277
+ space_token = self.attributes['extended_attributes']['space_token']
278
+
279
+ try:
280
+ status = self.__gfal2_copy(str(source_url), str(target), None, space_token, transfer_timeout=transfer_timeout)
281
+ if status:
282
+ raise exception.RucioException()
283
+ except exception.DestinationNotAccessible as error:
284
+ raise exception.DestinationNotAccessible(str(error))
285
+ except exception.SourceNotFound as error:
286
+ raise exception.DestinationNotAccessible(str(error))
287
+ except Exception as error:
288
+ raise exception.ServiceUnavailable(error)
289
+
290
+ def delete(self, path):
291
+ """
292
+ Deletes a file from the connected RSE.
293
+
294
+ :param path: path to the to be deleted file
295
+
296
+ :raises ServiceUnavailable: if some generic error occurred in the library.
297
+ :raises SourceNotFound: if the source file was not found on the referred storage.
298
+ """
299
+ self.logger(logging.DEBUG, 'deleting file {}'.format(path))
300
+
301
+ pfns = [path] if isinstance(path, STRING_TYPES) else path
302
+
303
+ try:
304
+ status = self.__gfal2_rm(pfns)
305
+ if status:
306
+ raise exception.RucioException()
307
+ except exception.SourceNotFound as error:
308
+ raise exception.SourceNotFound(str(error))
309
+ except Exception as error:
310
+ raise exception.ServiceUnavailable(error)
311
+
312
+ def rename(self, path, new_path):
313
+ """
314
+ Allows to rename a file stored inside the connected RSE.
315
+
316
+ :param path: path to the current file on the storage
317
+ :param new_path: path to the new file on the storage
318
+
319
+ :raises DestinationNotAccessible: if the destination storage was not accessible.
320
+ :raises ServiceUnavailable: if some generic error occurred in the library.
321
+ :raises SourceNotFound: if the source file was not found on the referred storage.
322
+ """
323
+ self.logger(logging.DEBUG, 'renaming file from {} to {}'.format(path, new_path))
324
+
325
+ try:
326
+ status = self.__gfal2_rename(path, new_path)
327
+ if status:
328
+ raise exception.RucioException()
329
+ except exception.DestinationNotAccessible as error:
330
+ raise exception.DestinationNotAccessible(str(error))
331
+ except exception.SourceNotFound as error:
332
+ raise exception.SourceNotFound(str(error))
333
+ except Exception as error:
334
+ raise exception.ServiceUnavailable(error)
335
+
336
+ def exists(self, path):
337
+ """
338
+ Checks if the requested file is known by the referred RSE.
339
+
340
+ :param path: Physical file name
341
+
342
+ :returns: True if the file exists, False if it doesn't
343
+
344
+ :raises SourceNotFound: if the source file was not found on the referred storage.
345
+ """
346
+ self.logger(logging.DEBUG, 'checking if file exists {}'.format(path))
347
+
348
+ try:
349
+ status = self.__gfal2_exist(path)
350
+ if status:
351
+ return False
352
+ return True
353
+ except exception.SourceNotFound:
354
+ return False
355
+ except Exception as error:
356
+ raise exception.ServiceUnavailable(error)
357
+
358
+ def close(self):
359
+ """
360
+ Closes the connection to RSE.
361
+ """
362
+ self.logger(logging.DEBUG, 'closing protocol connection')
363
+ del self.__ctx
364
+ self.__ctx = None
365
+
366
+ def stat(self, path):
367
+ """
368
+ Returns the stats of a file.
369
+
370
+ :param path: path to file
371
+
372
+ :raises ServiceUnavailable: if some generic error occurred in the library.
373
+
374
+ :returns: a dict with two keys, filesize and an element of GLOBALLY_SUPPORTED_CHECKSUMS.
375
+ """
376
+ self.logger(logging.DEBUG, 'getting stats of file {}'.format(path))
377
+
378
+ ret = {}
379
+ ctx = self.__ctx
380
+
381
+ path = str(path)
382
+
383
+ try:
384
+ stat_str = str(ctx.stat(path))
385
+ except Exception as error:
386
+ msg = 'Error while processing gfal stat call. Error: %s'
387
+ raise exception.ServiceUnavailable(msg % str(error))
388
+
389
+ stats = stat_str.split()
390
+ if len(stats) < 8:
391
+ msg = 'gfal stat call result has unknown format. Result: %s'
392
+ raise exception.ServiceUnavailable(msg % stat_str)
393
+
394
+ ret['filesize'] = stats[7]
395
+
396
+ if not self.rse.get('verify_checksum', True):
397
+ return ret
398
+
399
+ message = "\n"
400
+ try:
401
+ ret[PREFERRED_CHECKSUM] = ctx.checksum(path, str(PREFERRED_CHECKSUM.upper()))
402
+ return ret
403
+ except Exception as error:
404
+ message += 'Error while processing gfal checksum call (%s). Error: %s \n' % (PREFERRED_CHECKSUM, str(error))
405
+
406
+ for checksum_name in GLOBALLY_SUPPORTED_CHECKSUMS:
407
+ if checksum_name == PREFERRED_CHECKSUM:
408
+ continue
409
+ try:
410
+ ret[checksum_name] = ctx.checksum(path, str(checksum_name.upper()))
411
+ return ret
412
+ except Exception as error:
413
+ message += 'Error while processing gfal checksum call (%s). Error: %s \n' % (checksum_name, str(error))
414
+
415
+ raise exception.RSEChecksumUnavailable(message)
416
+
417
+ def __gfal2_cancel(self):
418
+ """
419
+ Cancel all gfal operations in progress.
420
+ """
421
+ self.logger(logging.DEBUG, 'gfal: cancelling all operations')
422
+
423
+ ctx = self.__ctx
424
+ if ctx:
425
+ ctx.cancel()
426
+
427
+ def __gfal2_copy(self, src, dest, src_spacetoken=None, dest_spacetoken=None, transfer_timeout=None):
428
+ """
429
+ Uses gfal2 to copy file from src to dest.
430
+
431
+ :param src: Physical source file name
432
+ :param src_spacetoken: The source file's space token
433
+ :param dest: Physical destination file name
434
+ :param dest_spacetoken: The destination file's space token
435
+ :param transfer_timeout: Transfer timeout (in seconds)
436
+
437
+ :returns: 0 if copied successfully, other than 0 if failed
438
+
439
+ :raises SourceNotFound: if source file cannot be found.
440
+ :raises RucioException: if it failed to copy the file.
441
+ """
442
+ ctx = self.__ctx
443
+ if transfer_timeout:
444
+ ctx.set_opt_integer("HTTP PLUGIN", "OPERATION_TIMEOUT", int(transfer_timeout))
445
+ ctx.set_opt_integer("SRM PLUGIN", "OPERATION_TIMEOUT", int(transfer_timeout))
446
+ ctx.set_opt_integer("GRIDFTP PLUGIN", "OPERATION_TIMEOUT", int(transfer_timeout))
447
+ watchdog = Timer(int(transfer_timeout) + 60, self.__gfal2_cancel)
448
+ params = ctx.transfer_parameters()
449
+ if src_spacetoken:
450
+ params.src_spacetoken = str(src_spacetoken)
451
+ if dest_spacetoken:
452
+ params.dst_spacetoken = str(dest_spacetoken)
453
+
454
+ if not (self.renaming and dest.startswith('https')):
455
+ params.create_parent = True
456
+
457
+ if not self.renaming:
458
+ params.strict_copy = True
459
+
460
+ try:
461
+ if transfer_timeout:
462
+ params.timeout = int(transfer_timeout)
463
+ watchdog.start()
464
+ ret = ctx.filecopy(params, str(src), str(dest))
465
+ if transfer_timeout:
466
+ watchdog.cancel()
467
+ return ret
468
+ except gfal2.GError as error: # pylint: disable=no-member
469
+ if transfer_timeout:
470
+ watchdog.cancel()
471
+ if error.code == errno.ENOENT or 'No such file' in str(error):
472
+ raise exception.SourceNotFound(error)
473
+ raise exception.RucioException(error)
474
+
475
+ def __gfal2_rm(self, paths):
476
+ """
477
+ Uses gfal2 to remove the file.
478
+
479
+ :param path: Physical file name
480
+
481
+ :returns: 0 if removed successfully, other than 0 if failed
482
+
483
+ :raises SourceNotFound: if the source file was not found.
484
+ :raises RucioException: if it failed to remove the file.
485
+ """
486
+
487
+ ctx = self.__ctx
488
+
489
+ try:
490
+ for path in paths:
491
+ if self.__gfal2_exist(path) == 0:
492
+ ret = ctx.unlink(str(path))
493
+ if ret:
494
+ return ret
495
+ else:
496
+ raise exception.SourceNotFound
497
+ return ret
498
+ except gfal2.GError as error: # pylint: disable=no-member
499
+ if error.code == errno.ENOENT or 'No such file' in str(error):
500
+ raise exception.SourceNotFound(error)
501
+ raise exception.RucioException(error)
502
+
503
+ def __gfal2_exist(self, path):
504
+ """
505
+ Uses gfal2 to check whether the file exists.
506
+
507
+ :param path: Physical file name
508
+
509
+ :returns: 0 if it exists, -1 if it doesn't
510
+
511
+ :raises RucioException: if the error is not source not found.
512
+ """
513
+ ctx = self.__ctx
514
+ try:
515
+ if ctx.stat(str(path)):
516
+ return 0
517
+ return -1
518
+ except gfal2.GError as error: # pylint: disable=no-member
519
+ if error.code == errno.ENOENT or 'No such file' in str(error): # pylint: disable=no-member
520
+ return -1
521
+ raise exception.RucioException(error)
522
+
523
+ def __gfal2_rename(self, path, new_path):
524
+ """
525
+ Uses gfal2 to rename a file.
526
+
527
+ :param path: path to the current file on the storage
528
+ :param new_path: path to the new file on the storage
529
+
530
+ :returns: 0 if it exists, -1 if it doesn't
531
+
532
+ :raises RucioException: if failed.
533
+ """
534
+ ctx = self.__ctx
535
+
536
+ try:
537
+ dir_name = os.path.dirname(new_path)
538
+ # This function will be removed soon. gfal2 will create parent dir automatically.
539
+ try:
540
+ ctx.mkdir_rec(str(dir_name), 0o775)
541
+ except Exception:
542
+ pass
543
+ ret = ctx.rename(str(path), str(new_path))
544
+ return ret
545
+ except gfal2.GError as error: # pylint: disable=no-member
546
+ if error.code == errno.ENOENT or 'No such file' in str(error):
547
+ raise exception.SourceNotFound(error)
548
+ raise exception.RucioException(error)
549
+
550
+ def get_space_usage(self):
551
+ """
552
+ Get RSE space usage information.
553
+
554
+ :returns: a list with dict containing 'totalsize' and 'unusedsize'
555
+
556
+ :raises ServiceUnavailable: if some generic error occurred in the library.
557
+ """
558
+ endpoint_basepath = self.path2pfn(self.attributes['prefix'])
559
+ self.logger(logging.DEBUG, 'getting space usage from {}'.format(endpoint_basepath))
560
+
561
+ space_token = None
562
+ if self.attributes['extended_attributes'] is not None and 'space_token' in list(self.attributes['extended_attributes'].keys()):
563
+ space_token = self.attributes['extended_attributes']['space_token']
564
+
565
+ if space_token is None or space_token == "":
566
+ raise exception.RucioException("Space token is not defined for protocol: %s" % (self.attributes['scheme']))
567
+
568
+ try:
569
+ totalsize, unusedsize = self.__gfal2_get_space_usage(endpoint_basepath, space_token)
570
+ return totalsize, unusedsize
571
+ except Exception as error:
572
+ raise exception.ServiceUnavailable(error)
573
+
574
+ def __gfal2_get_space_usage(self, path, space_token):
575
+ """
576
+ Uses gfal2 to get space usage info with space token.
577
+
578
+ :param path: the endpoint path
579
+ :param space_token: a string space token. E.g. "ATLASDATADISK"
580
+
581
+ :returns: a list with dict containing 'totalsize' and 'unusedsize'
582
+
583
+ :raises ServiceUnavailable: if failed.
584
+ """
585
+ ctx = self.__ctx
586
+
587
+ try:
588
+ ret_usage = ctx.getxattr(str(path), str("spacetoken.description?" + space_token))
589
+ usage = json.loads(ret_usage)
590
+ totalsize = usage[0]["totalsize"]
591
+ unusedsize = usage[0]["unusedsize"]
592
+ return totalsize, unusedsize
593
+ except gfal2.GError as error: # pylint: disable=no-member
594
+ raise Exception(str(error))
595
+
596
+
597
+ class NoRename(Default):
598
+
599
+ """ Implementing access to RSEs using the srm protocol without renaming files on upload/download. Necessary for some storage endpoints. """
600
+
601
+ def __init__(self, protocol_attr, rse_settings, logger=logging.log):
602
+ """ Initializes the object with information about the referred RSE.
603
+
604
+ :param protocol_attr: Properties of the requested protocol.
605
+ :param rse_settting: The RSE settings.
606
+ :param logger: Optional decorated logger that can be passed from the calling daemons or servers.
607
+ """
608
+ super(NoRename, self).__init__(protocol_attr, rse_settings, logger=logger)
609
+ self.renaming = False
610
+ self.attributes.pop('determinism_type', None)
611
+ self.files = []
612
+
613
+ def rename(self, pfn, new_pfn):
614
+ """ Allows to rename a file stored inside the connected RSE.
615
+
616
+ :param pfn: Current physical file name
617
+ :param new_pfn New physical file name
618
+
619
+ :raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound
620
+ """
621
+ raise NotImplementedError
622
+
623
+
624
+ class CLI(Default):
625
+
626
+ """ Implementing access to RSEs using the srm protocol through CLI with 'gfal' commands. """
627
+
628
+ def __init__(self, protocol_attr, rse_settings, logger=logging.log):
629
+ """ Initializes the object with information about the referred RSE.
630
+
631
+ :param props: Properties derived from the RSE Repository
632
+ """
633
+
634
+ super(CLI, self).__init__(protocol_attr, rse_settings, logger=logger)
635
+ if not logger:
636
+ logger = logging.getLogger('%s.null' % __name__)
637
+ self.logger = logger
638
+
639
+ def get(self, path, dest, transfer_timeout=None):
640
+ """
641
+ Provides access to files stored inside connected the RSE.
642
+
643
+ :param path: Physical file name of requested file
644
+ :param dest: Name and path of the files when stored at the client
645
+ :param transfer_timeout: Transfer timeout (in seconds)
646
+
647
+ :raises RucioException: Passthrough of gfal-copy error message.
648
+ """
649
+
650
+ dest = os.path.abspath(dest)
651
+ if ':' not in dest:
652
+ dest = "file://" + dest
653
+
654
+ cmd = 'gfal-copy -vf -p -t %s -T %s %s %s' % (transfer_timeout, transfer_timeout, path, dest)
655
+ self.logger(logging.DEBUG, 'Command: ' + cmd)
656
+ cmd = cmd.split()
657
+
658
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
659
+ stdout, stderr = p.communicate()
660
+
661
+ if p.returncode:
662
+ self.logger(logging.DEBUG, 'Error STDOUT: ' + str(stdout))
663
+ self.logger(logging.DEBUG, 'Error STDERR: ' + str(stderr))
664
+ raise exception.RucioException(str(stderr))
665
+
666
+ def put(self, source, target, source_dir, transfer_timeout=None):
667
+ """
668
+ Allows to store files inside the referred RSE.
669
+
670
+ :param source: path to the source file on the client file system
671
+ :param target: path to the destination file on the storage
672
+ :param source_dir: Path where the to be transferred files are stored in the local file system
673
+ :param transfer_timeout: Transfer timeout (in seconds)
674
+
675
+ :raises RucioException: Passthrough of gfal-copy error message.
676
+ """
677
+
678
+ source_dir = source_dir or '.'
679
+ source_url = '%s/%s' % (source_dir, source)
680
+ self.logger(logging.DEBUG, 'source: ' + str(source_url))
681
+ source_url = os.path.abspath(source_url)
682
+ if not os.path.exists(source_url):
683
+ raise exception.SourceNotFound()
684
+ if ':' not in source_url:
685
+ source_url = "file://" + source_url
686
+
687
+ cmd = 'gfal-copy -vf -p -t %s -T %s %s %s ' % (transfer_timeout, transfer_timeout, source, target)
688
+
689
+ space_token = None
690
+ if self.attributes['extended_attributes'] is not None and 'space_token' in list(self.attributes['extended_attributes'].keys()):
691
+ space_token = self.attributes['extended_attributes']['space_token']
692
+ cmd = 'gfal-copy -vf -p -t %s -T %s -S %s %s %s ' % (transfer_timeout, transfer_timeout, space_token, source, target)
693
+
694
+ self.logger(logging.DEBUG, 'Command: ' + cmd)
695
+ cmd = cmd.split()
696
+
697
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
698
+ stdout, stderr = p.communicate()
699
+
700
+ if p.returncode:
701
+ self.logger(logging.DEBUG, 'Error STDOUT: ' + str(stdout))
702
+ self.logger(logging.DEBUG, 'Error STDERR: ' + str(stderr))
703
+ raise exception.RucioException(str(stderr))