rucio-clients 37.0.0rc1__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 (104) hide show
  1. rucio/__init__.py +17 -0
  2. rucio/alembicrevision.py +15 -0
  3. rucio/cli/__init__.py +14 -0
  4. rucio/cli/account.py +216 -0
  5. rucio/cli/bin_legacy/__init__.py +13 -0
  6. rucio/cli/bin_legacy/rucio.py +2825 -0
  7. rucio/cli/bin_legacy/rucio_admin.py +2500 -0
  8. rucio/cli/command.py +272 -0
  9. rucio/cli/config.py +72 -0
  10. rucio/cli/did.py +191 -0
  11. rucio/cli/download.py +128 -0
  12. rucio/cli/lifetime_exception.py +33 -0
  13. rucio/cli/replica.py +162 -0
  14. rucio/cli/rse.py +293 -0
  15. rucio/cli/rule.py +158 -0
  16. rucio/cli/scope.py +40 -0
  17. rucio/cli/subscription.py +73 -0
  18. rucio/cli/upload.py +60 -0
  19. rucio/cli/utils.py +226 -0
  20. rucio/client/__init__.py +15 -0
  21. rucio/client/accountclient.py +432 -0
  22. rucio/client/accountlimitclient.py +183 -0
  23. rucio/client/baseclient.py +983 -0
  24. rucio/client/client.py +120 -0
  25. rucio/client/configclient.py +126 -0
  26. rucio/client/credentialclient.py +59 -0
  27. rucio/client/didclient.py +868 -0
  28. rucio/client/diracclient.py +56 -0
  29. rucio/client/downloadclient.py +1783 -0
  30. rucio/client/exportclient.py +44 -0
  31. rucio/client/fileclient.py +50 -0
  32. rucio/client/importclient.py +42 -0
  33. rucio/client/lifetimeclient.py +90 -0
  34. rucio/client/lockclient.py +109 -0
  35. rucio/client/metaconventionsclient.py +140 -0
  36. rucio/client/pingclient.py +44 -0
  37. rucio/client/replicaclient.py +452 -0
  38. rucio/client/requestclient.py +125 -0
  39. rucio/client/richclient.py +317 -0
  40. rucio/client/rseclient.py +746 -0
  41. rucio/client/ruleclient.py +294 -0
  42. rucio/client/scopeclient.py +90 -0
  43. rucio/client/subscriptionclient.py +173 -0
  44. rucio/client/touchclient.py +82 -0
  45. rucio/client/uploadclient.py +969 -0
  46. rucio/common/__init__.py +13 -0
  47. rucio/common/bittorrent.py +234 -0
  48. rucio/common/cache.py +111 -0
  49. rucio/common/checksum.py +168 -0
  50. rucio/common/client.py +122 -0
  51. rucio/common/config.py +788 -0
  52. rucio/common/constants.py +217 -0
  53. rucio/common/constraints.py +17 -0
  54. rucio/common/didtype.py +237 -0
  55. rucio/common/exception.py +1208 -0
  56. rucio/common/extra.py +31 -0
  57. rucio/common/logging.py +420 -0
  58. rucio/common/pcache.py +1409 -0
  59. rucio/common/plugins.py +185 -0
  60. rucio/common/policy.py +93 -0
  61. rucio/common/schema/__init__.py +200 -0
  62. rucio/common/schema/generic.py +416 -0
  63. rucio/common/schema/generic_multi_vo.py +395 -0
  64. rucio/common/stomp_utils.py +423 -0
  65. rucio/common/stopwatch.py +55 -0
  66. rucio/common/test_rucio_server.py +154 -0
  67. rucio/common/types.py +483 -0
  68. rucio/common/utils.py +1688 -0
  69. rucio/rse/__init__.py +96 -0
  70. rucio/rse/protocols/__init__.py +13 -0
  71. rucio/rse/protocols/bittorrent.py +194 -0
  72. rucio/rse/protocols/cache.py +111 -0
  73. rucio/rse/protocols/dummy.py +100 -0
  74. rucio/rse/protocols/gfal.py +708 -0
  75. rucio/rse/protocols/globus.py +243 -0
  76. rucio/rse/protocols/http_cache.py +82 -0
  77. rucio/rse/protocols/mock.py +123 -0
  78. rucio/rse/protocols/ngarc.py +209 -0
  79. rucio/rse/protocols/posix.py +250 -0
  80. rucio/rse/protocols/protocol.py +361 -0
  81. rucio/rse/protocols/rclone.py +365 -0
  82. rucio/rse/protocols/rfio.py +145 -0
  83. rucio/rse/protocols/srm.py +338 -0
  84. rucio/rse/protocols/ssh.py +414 -0
  85. rucio/rse/protocols/storm.py +195 -0
  86. rucio/rse/protocols/webdav.py +594 -0
  87. rucio/rse/protocols/xrootd.py +302 -0
  88. rucio/rse/rsemanager.py +881 -0
  89. rucio/rse/translation.py +260 -0
  90. rucio/vcsversion.py +11 -0
  91. rucio/version.py +45 -0
  92. rucio_clients-37.0.0rc1.data/data/etc/rse-accounts.cfg.template +25 -0
  93. rucio_clients-37.0.0rc1.data/data/etc/rucio.cfg.atlas.client.template +43 -0
  94. rucio_clients-37.0.0rc1.data/data/etc/rucio.cfg.template +241 -0
  95. rucio_clients-37.0.0rc1.data/data/requirements.client.txt +19 -0
  96. rucio_clients-37.0.0rc1.data/data/rucio_client/merge_rucio_configs.py +144 -0
  97. rucio_clients-37.0.0rc1.data/scripts/rucio +133 -0
  98. rucio_clients-37.0.0rc1.data/scripts/rucio-admin +97 -0
  99. rucio_clients-37.0.0rc1.dist-info/METADATA +54 -0
  100. rucio_clients-37.0.0rc1.dist-info/RECORD +104 -0
  101. rucio_clients-37.0.0rc1.dist-info/WHEEL +5 -0
  102. rucio_clients-37.0.0rc1.dist-info/licenses/AUTHORS.rst +100 -0
  103. rucio_clients-37.0.0rc1.dist-info/licenses/LICENSE +201 -0
  104. rucio_clients-37.0.0rc1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,708 @@
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.checksum import GLOBALLY_SUPPORTED_CHECKSUMS, PREFERRED_CHECKSUM
26
+ from rucio.common.constraints import STRING_TYPES
27
+ from rucio.rse.protocols import protocol
28
+
29
+ try:
30
+ import gfal2 # pylint: disable=import-error
31
+ except Exception:
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
+ self.__ctx.set_opt_boolean("HTTP PLUGIN", "RETRIEVE_BEARER_TOKEN", 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 occurred 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 occurred 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 occurred 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 occurred 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
+
348
+ self.logger(logging.DEBUG, 'checking if file exists {}'.format(path))
349
+
350
+ if path is None:
351
+ # Action not supported
352
+ raise exception.RSEOperationNotSupported()
353
+
354
+ try:
355
+ status = self.__gfal2_exist(path)
356
+ if status:
357
+ return False
358
+ return True
359
+ except exception.SourceNotFound:
360
+ return False
361
+ except Exception as error:
362
+ raise exception.ServiceUnavailable(error)
363
+
364
+ def close(self):
365
+ """
366
+ Closes the connection to RSE.
367
+ """
368
+ self.logger(logging.DEBUG, 'closing protocol connection')
369
+ del self.__ctx
370
+ self.__ctx = None
371
+
372
+ def stat(self, path):
373
+ """
374
+ Returns the stats of a file.
375
+
376
+ :param path: path to file
377
+
378
+ :raises ServiceUnavailable: if some generic error occurred in the library.
379
+
380
+ :returns: a dict with two keys, filesize and an element of GLOBALLY_SUPPORTED_CHECKSUMS.
381
+ """
382
+ self.logger(logging.DEBUG, 'getting stats of file {}'.format(path))
383
+
384
+ ret = {}
385
+ ctx = self.__ctx
386
+
387
+ path = str(path)
388
+
389
+ try:
390
+ stat_str = str(ctx.stat(path))
391
+ except Exception as error:
392
+ msg = 'Error while processing gfal stat call. Error: %s'
393
+ raise exception.ServiceUnavailable(msg % str(error))
394
+
395
+ stats = stat_str.split()
396
+ if len(stats) < 8:
397
+ msg = 'gfal stat call result has unknown format. Result: %s'
398
+ raise exception.ServiceUnavailable(msg % stat_str)
399
+
400
+ ret['filesize'] = stats[7]
401
+
402
+ if not self.rse.get('verify_checksum', True):
403
+ return ret
404
+
405
+ message = "\n"
406
+ try:
407
+ ret[PREFERRED_CHECKSUM] = ctx.checksum(path, str(PREFERRED_CHECKSUM.upper()))
408
+ return ret
409
+ except Exception as error:
410
+ message += 'Error while processing gfal checksum call (%s). Error: %s \n' % (PREFERRED_CHECKSUM, str(error))
411
+
412
+ for checksum_name in GLOBALLY_SUPPORTED_CHECKSUMS:
413
+ if checksum_name == PREFERRED_CHECKSUM:
414
+ continue
415
+ try:
416
+ ret[checksum_name] = ctx.checksum(path, str(checksum_name.upper()))
417
+ return ret
418
+ except Exception as error:
419
+ message += 'Error while processing gfal checksum call (%s). Error: %s \n' % (checksum_name, str(error))
420
+
421
+ raise exception.RSEChecksumUnavailable(message)
422
+
423
+ def __gfal2_cancel(self):
424
+ """
425
+ Cancel all gfal operations in progress.
426
+ """
427
+ self.logger(logging.DEBUG, 'gfal: cancelling all operations')
428
+
429
+ ctx = self.__ctx
430
+ if ctx:
431
+ ctx.cancel()
432
+
433
+ def __gfal2_copy(self, src, dest, src_spacetoken=None, dest_spacetoken=None, transfer_timeout=None):
434
+ """
435
+ Uses gfal2 to copy file from src to dest.
436
+
437
+ :param src: Physical source file name
438
+ :param src_spacetoken: The source file's space token
439
+ :param dest: Physical destination file name
440
+ :param dest_spacetoken: The destination file's space token
441
+ :param transfer_timeout: Transfer timeout (in seconds)
442
+
443
+ :returns: 0 if copied successfully, other than 0 if failed
444
+
445
+ :raises SourceNotFound: if source file cannot be found.
446
+ :raises RucioException: if it failed to copy the file.
447
+ """
448
+ ctx = self.__ctx
449
+ if transfer_timeout:
450
+ ctx.set_opt_integer("HTTP PLUGIN", "OPERATION_TIMEOUT", int(transfer_timeout))
451
+ ctx.set_opt_integer("SRM PLUGIN", "OPERATION_TIMEOUT", int(transfer_timeout))
452
+ ctx.set_opt_integer("GRIDFTP PLUGIN", "OPERATION_TIMEOUT", int(transfer_timeout))
453
+ watchdog = Timer(int(transfer_timeout) + 60, self.__gfal2_cancel)
454
+ params = ctx.transfer_parameters()
455
+ if src_spacetoken:
456
+ params.src_spacetoken = str(src_spacetoken)
457
+ if dest_spacetoken:
458
+ params.dst_spacetoken = str(dest_spacetoken)
459
+
460
+ if not (self.renaming and dest.startswith('https')):
461
+ params.create_parent = True
462
+
463
+ if not self.renaming:
464
+ params.strict_copy = True
465
+
466
+ try:
467
+ if transfer_timeout:
468
+ params.timeout = int(transfer_timeout)
469
+ watchdog.start()
470
+ ret = ctx.filecopy(params, str(src), str(dest))
471
+ if transfer_timeout:
472
+ watchdog.cancel()
473
+ return ret
474
+ except gfal2.GError as error: # pylint: disable=no-member
475
+ if transfer_timeout:
476
+ watchdog.cancel()
477
+ if error.code == errno.ENOENT or 'No such file' in str(error):
478
+ raise exception.SourceNotFound(error)
479
+ raise exception.RucioException(error)
480
+
481
+ def __gfal2_rm(self, paths):
482
+ """
483
+ Uses gfal2 to remove the file.
484
+
485
+ :param path: Physical file name
486
+
487
+ :returns: 0 if removed successfully, other than 0 if failed
488
+
489
+ :raises SourceNotFound: if the source file was not found.
490
+ :raises RucioException: if it failed to remove the file.
491
+ """
492
+
493
+ ctx = self.__ctx
494
+
495
+ try:
496
+ for path in paths:
497
+ # GFAL does a PROPFIND request before DELETE when the scheme is
498
+ # davs://, which is wasteful.
499
+ path = re.sub('^davs://', 'https://', str(path))
500
+ ret = ctx.unlink(path)
501
+ if ret:
502
+ return ret
503
+ return ret
504
+ except gfal2.GError as error: # pylint: disable=no-member
505
+ if error.code == errno.ENOENT or 'No such file' in str(error):
506
+ raise exception.SourceNotFound(error)
507
+ raise exception.RucioException(error)
508
+
509
+ def __gfal2_exist(self, path):
510
+ """
511
+ Uses gfal2 to check whether the file exists.
512
+
513
+ :param path: Physical file name
514
+
515
+ :returns: 0 if it exists, -1 if it doesn't
516
+
517
+ :raises RucioException: if the error is not source not found.
518
+ """
519
+ ctx = self.__ctx
520
+ try:
521
+ if ctx.stat(str(path)):
522
+ return 0
523
+ except gfal2.GError as error: # pylint: disable=no-member
524
+ if error.code == errno.ENOENT or 'No such file' in str(error): # pylint: disable=no-member
525
+ return -1
526
+ raise exception.RucioException(error)
527
+
528
+ def __gfal2_rename(self, path, new_path):
529
+ """
530
+ Uses gfal2 to rename a file.
531
+
532
+ :param path: path to the current file on the storage
533
+ :param new_path: path to the new file on the storage
534
+
535
+ :returns: 0 if it exists, -1 if it doesn't
536
+
537
+ :raises RucioException: if failed.
538
+ """
539
+ ctx = self.__ctx
540
+
541
+ try:
542
+ dir_name = os.path.dirname(new_path)
543
+ # This function will be removed soon. gfal2 will create parent dir automatically.
544
+ try:
545
+ ctx.mkdir_rec(str(dir_name), 0o775)
546
+ except Exception:
547
+ pass
548
+ ret = ctx.rename(str(path), str(new_path))
549
+ return ret
550
+ except gfal2.GError as error: # pylint: disable=no-member
551
+ if error.code == errno.ENOENT or 'No such file' in str(error):
552
+ raise exception.SourceNotFound(error)
553
+ raise exception.RucioException(error)
554
+
555
+ def get_space_usage(self):
556
+ """
557
+ Get RSE space usage information.
558
+
559
+ :returns: a list with dict containing 'totalsize' and 'unusedsize'
560
+
561
+ :raises ServiceUnavailable: if some generic error occurred in the library.
562
+ """
563
+ endpoint_basepath = self.path2pfn(self.attributes['prefix'])
564
+ self.logger(logging.DEBUG, 'getting space usage from {}'.format(endpoint_basepath))
565
+
566
+ space_token = None
567
+ if self.attributes['extended_attributes'] is not None and 'space_token' in list(self.attributes['extended_attributes'].keys()):
568
+ space_token = self.attributes['extended_attributes']['space_token']
569
+
570
+ if space_token is None or space_token == "":
571
+ raise exception.RucioException("Space token is not defined for protocol: %s" % (self.attributes['scheme']))
572
+
573
+ try:
574
+ totalsize, unusedsize = self.__gfal2_get_space_usage(endpoint_basepath, space_token)
575
+ return totalsize, unusedsize
576
+ except Exception as error:
577
+ raise exception.ServiceUnavailable(error)
578
+
579
+ def __gfal2_get_space_usage(self, path, space_token):
580
+ """
581
+ Uses gfal2 to get space usage info with space token.
582
+
583
+ :param path: the endpoint path
584
+ :param space_token: a string space token. E.g. "ATLASDATADISK"
585
+
586
+ :returns: a list with dict containing 'totalsize' and 'unusedsize'
587
+
588
+ :raises ServiceUnavailable: if failed.
589
+ """
590
+ ctx = self.__ctx
591
+
592
+ try:
593
+ ret_usage = ctx.getxattr(str(path), str("spacetoken.description?" + space_token))
594
+ usage = json.loads(ret_usage)
595
+ totalsize = usage[0]["totalsize"]
596
+ unusedsize = usage[0]["unusedsize"]
597
+ return totalsize, unusedsize
598
+ except gfal2.GError as error: # pylint: disable=no-member
599
+ raise Exception(str(error))
600
+
601
+
602
+ class NoRename(Default):
603
+
604
+ """ Implementing access to RSEs using the srm protocol without renaming files on upload/download. Necessary for some storage endpoints. """
605
+
606
+ def __init__(self, protocol_attr, rse_settings, logger=logging.log):
607
+ """ Initializes the object with information about the referred RSE.
608
+
609
+ :param protocol_attr: Properties of the requested protocol.
610
+ :param rse_settting: The RSE settings.
611
+ :param logger: Optional decorated logger that can be passed from the calling daemons or servers.
612
+ """
613
+ super(NoRename, self).__init__(protocol_attr, rse_settings, logger=logger)
614
+ self.renaming = False
615
+ self.attributes.pop('determinism_type', None)
616
+ self.files = []
617
+
618
+ def rename(self, pfn, new_pfn):
619
+ """ Allows to rename a file stored inside the connected RSE.
620
+
621
+ :param pfn: Current physical file name
622
+ :param new_pfn New physical file name
623
+
624
+ :raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound
625
+ """
626
+ raise NotImplementedError
627
+
628
+
629
+ class CLI(Default):
630
+
631
+ """ Implementing access to RSEs using the srm protocol through CLI with 'gfal' commands. """
632
+
633
+ def __init__(self, protocol_attr, rse_settings, logger=logging.log):
634
+ """ Initializes the object with information about the referred RSE.
635
+
636
+ :param props: Properties derived from the RSE Repository
637
+ """
638
+
639
+ super(CLI, self).__init__(protocol_attr, rse_settings, logger=logger)
640
+ if not logger:
641
+ logger = logging.getLogger('%s.null' % __name__)
642
+ self.logger = logger
643
+
644
+ def get(self, path, dest, transfer_timeout=None):
645
+ """
646
+ Provides access to files stored inside connected the RSE.
647
+
648
+ :param path: Physical file name of requested file
649
+ :param dest: Name and path of the files when stored at the client
650
+ :param transfer_timeout: Transfer timeout (in seconds)
651
+
652
+ :raises RucioException: Passthrough of gfal-copy error message.
653
+ """
654
+
655
+ dest = os.path.abspath(dest)
656
+ if ':' not in dest:
657
+ dest = "file://" + dest
658
+
659
+ cmd = 'gfal-copy -vf -p -t %s -T %s %s %s' % (transfer_timeout, transfer_timeout, path, dest)
660
+ self.logger(logging.DEBUG, 'Command: ' + cmd)
661
+ cmd = cmd.split()
662
+
663
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
664
+ stdout, stderr = p.communicate()
665
+
666
+ if p.returncode:
667
+ self.logger(logging.DEBUG, 'Error STDOUT: ' + str(stdout))
668
+ self.logger(logging.DEBUG, 'Error STDERR: ' + str(stderr))
669
+ raise exception.RucioException(str(stderr))
670
+
671
+ def put(self, source, target, source_dir, transfer_timeout=None):
672
+ """
673
+ Allows to store files inside the referred RSE.
674
+
675
+ :param source: path to the source file on the client file system
676
+ :param target: path to the destination file on the storage
677
+ :param source_dir: Path where the to be transferred files are stored in the local file system
678
+ :param transfer_timeout: Transfer timeout (in seconds)
679
+
680
+ :raises RucioException: Passthrough of gfal-copy error message.
681
+ """
682
+
683
+ source_dir = source_dir or '.'
684
+ source_url = '%s/%s' % (source_dir, source)
685
+ self.logger(logging.DEBUG, 'source: ' + str(source_url))
686
+ source_url = os.path.abspath(source_url)
687
+ if not os.path.exists(source_url):
688
+ raise exception.SourceNotFound()
689
+ if ':' not in source_url:
690
+ source_url = "file://" + source_url
691
+
692
+ cmd = 'gfal-copy -vf -p -t %s -T %s %s %s ' % (transfer_timeout, transfer_timeout, source, target)
693
+
694
+ space_token = None
695
+ if self.attributes['extended_attributes'] is not None and 'space_token' in list(self.attributes['extended_attributes'].keys()):
696
+ space_token = self.attributes['extended_attributes']['space_token']
697
+ cmd = 'gfal-copy -vf -p -t %s -T %s -S %s %s %s ' % (transfer_timeout, transfer_timeout, space_token, source, target)
698
+
699
+ self.logger(logging.DEBUG, 'Command: ' + cmd)
700
+ cmd = cmd.split()
701
+
702
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
703
+ stdout, stderr = p.communicate()
704
+
705
+ if p.returncode:
706
+ self.logger(logging.DEBUG, 'Error STDOUT: ' + str(stdout))
707
+ self.logger(logging.DEBUG, 'Error STDERR: ' + str(stderr))
708
+ raise exception.RucioException(str(stderr))