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