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,414 @@
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 logging
16
+ import os
17
+ import re
18
+
19
+ from rucio.common import exception
20
+ from rucio.common.checksum import PREFERRED_CHECKSUM
21
+ from rucio.common.utils import execute
22
+ from rucio.rse.protocols import protocol
23
+
24
+
25
+ class Default(protocol.RSEProtocol):
26
+ """ Implementing access to RSEs using the SSH protocol."""
27
+
28
+ def __init__(self, protocol_attr, rse_settings, logger=logging.log):
29
+ """ Initializes the object with information about the referred RSE.
30
+
31
+ :param props: Properties derived from the RSE Repository
32
+ """
33
+ super(Default, self).__init__(protocol_attr, rse_settings, logger=logger)
34
+
35
+ self.scheme = self.attributes['scheme']
36
+ self.hostname = self.attributes['hostname']
37
+ self.port = str(self.attributes['port'])
38
+ self.path = None
39
+ if self.attributes['extended_attributes'] is not None and\
40
+ 'user' in list(self.attributes['extended_attributes'].keys()):
41
+ self.sshuser = self.attributes['extended_attributes']['user'] + '@'
42
+ else:
43
+ self.sshuser = ''
44
+ self.logger = logger
45
+
46
+ def path2pfn(self, path):
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
+ """
55
+ self.logger(logging.DEBUG, 'ssh.path2pfn: path: {}'.format(path))
56
+ if not path.startswith(str(self.scheme) + '://'):
57
+ return '%s://%s%s:%s/%s' % (self.scheme, self.sshuser, self.hostname, self.port, path)
58
+ else:
59
+ return path
60
+
61
+ def exists(self, pfn):
62
+ """ Checks if the requested file is known by the referred RSE.
63
+
64
+ :param pfn: Physical file name
65
+
66
+ :returns: True if the file exists, False if it doesn't
67
+
68
+ :raise ServiceUnavailable
69
+ """
70
+ self.logger(logging.DEBUG, 'ssh.exists: pfn: {}'.format(pfn))
71
+ try:
72
+ path = self.pfn2path(pfn)
73
+ cmd = 'ssh -p %s %s%s find %s' % (self.port, self.sshuser, self.hostname, path)
74
+ self.logger(logging.DEBUG, 'ssh.exists: cmd: {}'.format(cmd))
75
+ status, out, err = execute(cmd)
76
+ if status:
77
+ return False
78
+ except Exception as e:
79
+ raise exception.ServiceUnavailable(e)
80
+
81
+ return True
82
+
83
+ def stat(self, path):
84
+ """
85
+ Returns the stats of a file.
86
+
87
+ :param path: path to file
88
+
89
+ :raises ServiceUnavailable: if some generic error occurred in the library.
90
+
91
+ :returns: a dict with two keys, filesize and an element of GLOBALLY_SUPPORTED_CHECKSUMS.
92
+ """
93
+ self.logger(logging.DEBUG, 'ssh.stat: path: {}'.format(path))
94
+ ret = {}
95
+ chsum = None
96
+ path = self.pfn2path(path)
97
+
98
+ try:
99
+ # ssh stat for getting filesize
100
+ cmd = 'ssh -p {0} {1}{2} stat --printf="%s" {3}'.format(self.port, self.sshuser, self.hostname, path)
101
+ self.logger(logging.DEBUG, 'ssh.stat: filesize cmd: {}'.format(cmd))
102
+ status_stat, out, err = execute(cmd)
103
+ if status_stat == 0:
104
+ ret['filesize'] = out
105
+
106
+ # ssh query checksum for getting md5 checksum
107
+ cmd = 'ssh -p %s %s%s md5sum %s' % (self.port, self.sshuser, self.hostname, path)
108
+ self.logger(logging.DEBUG, 'ssh.stat: checksum cmd: {}'.format(cmd))
109
+ status_query, out, err = execute(cmd)
110
+
111
+ if status_query == 0:
112
+ chsum = 'md5'
113
+ val = out.strip(' ').split()
114
+ ret[chsum] = val[0]
115
+
116
+ except Exception as e:
117
+ raise exception.ServiceUnavailable(e)
118
+
119
+ if 'filesize' not in ret:
120
+ raise exception.ServiceUnavailable('Filesize could not be retrieved.')
121
+ if PREFERRED_CHECKSUM != chsum or not chsum:
122
+ msg = '{} does not match with {}'.format(chsum, PREFERRED_CHECKSUM)
123
+ raise exception.RSEChecksumUnavailable(msg)
124
+
125
+ return ret
126
+
127
+ def pfn2path(self, pfn):
128
+ """
129
+ Returns the path of a file given the pfn, i.e. scheme, user and hostname are subtracted from the pfn.
130
+
131
+ :param path: pfn of a file
132
+
133
+ :returns: path.
134
+ """
135
+ path = pfn
136
+ if pfn.startswith(str(self.scheme) + '://'):
137
+ self.logger(logging.DEBUG, 'ssh.pfn2path: pfn: {}'.format(pfn))
138
+ prefix = self.attributes['prefix']
139
+ path = pfn.partition(self.attributes['prefix'])[2]
140
+ path = prefix + path
141
+ return path
142
+
143
+ def lfns2pfns(self, lfns):
144
+ """
145
+ Returns a fully qualified PFN for the file referred by path.
146
+
147
+ :param path: The path to the file.
148
+
149
+ :returns: Fully qualified PFN.
150
+ """
151
+ self.logger(logging.DEBUG, 'ssh.lfns2pfns: lfns: {}'.format(lfns))
152
+ pfns = {}
153
+ prefix = self.attributes['prefix']
154
+
155
+ if not prefix.startswith('/'):
156
+ prefix = ''.join(['/', prefix])
157
+ if not prefix.endswith('/'):
158
+ prefix = ''.join([prefix, '/'])
159
+
160
+ lfns = [lfns] if isinstance(lfns, dict) else lfns
161
+ for lfn in lfns:
162
+ scope, name = lfn['scope'], lfn['name']
163
+ if 'path' in lfn and lfn['path'] is not None:
164
+ pfns['%s:%s' % (scope, name)] = ''.join([self.attributes['scheme'], '://', self.sshuser, self.hostname, ':', self.port, prefix, lfn['path']])
165
+ else:
166
+ pfns['%s:%s' % (scope, name)] = ''.join([self.attributes['scheme'], '://', self.sshuser, self.hostname, ':', self.port, prefix, self._get_path(scope=scope, name=name)])
167
+ return pfns
168
+
169
+ def connect(self):
170
+ """ Establishes the actual connection to the referred RSE.
171
+
172
+ :raises RSEAccessDenied
173
+ """
174
+ self.logger(logging.DEBUG, 'ssh.connect: port: {}, hostname {}, ssh-user {}'.format(self.port, self.hostname, self.sshuser))
175
+ try:
176
+ cmd = 'ssh -p %s %s%s echo ok 2>&1' % (self.port, self.sshuser, self.hostname)
177
+ status, out, err = execute(cmd)
178
+ checker = re.search(r'ok', out)
179
+ if not checker:
180
+ raise exception.RSEAccessDenied(err)
181
+ except Exception as e:
182
+ raise exception.RSEAccessDenied(e)
183
+
184
+ def close(self):
185
+ """ Closes the connection to RSE."""
186
+ pass
187
+
188
+ def get(self, pfn, dest, transfer_timeout=None):
189
+ """ Provides access to files stored inside connected the RSE.
190
+
191
+ :param pfn: Physical file name of requested file
192
+ :param dest: Name and path of the files when stored at the client
193
+ :param transfer_timeout: Transfer timeout (in seconds) - dummy
194
+
195
+ :raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound
196
+ """
197
+ self.logger(logging.DEBUG, 'ssh.get: pfn: {}'.format(pfn))
198
+ try:
199
+ path = self.pfn2path(pfn)
200
+ destdir = os.path.dirname(dest)
201
+ cmd = 'mkdir -p %s' % (destdir)
202
+ self.logger(logging.DEBUG, 'ssh.get: cmd: {}'.format(cmd))
203
+ status, out, err = execute(cmd)
204
+ cmd = 'scp %s%s:%s %s' % (self.sshuser, self.hostname, path, dest)
205
+ self.logger(logging.DEBUG, 'ssh.get: cmd: {}'.format(cmd))
206
+ status, out, err = execute(cmd)
207
+ if status:
208
+ raise exception.RucioException(err)
209
+ except Exception as e:
210
+ raise exception.ServiceUnavailable(e)
211
+
212
+ def put(self, filename, target, source_dir, transfer_timeout=None):
213
+ """
214
+ Allows to store files inside the referred RSE.
215
+
216
+ :param source: path to the source file on the client file system
217
+ :param target: path to the destination file on the storage
218
+ :param source_dir: Path where the to be transferred files are stored in the local file system
219
+ :param transfer_timeout: Transfer timeout (in seconds) - dummy
220
+
221
+ :raises DestinationNotAccessible: if the destination storage was not accessible.
222
+ :raises ServiceUnavailable: if some generic error occurred in the library.
223
+ :raises SourceNotFound: if the source file was not found on the referred storage.
224
+ """
225
+ self.logger(logging.DEBUG, 'ssh.put: filename: {} target: {}'.format(filename, target))
226
+ source_dir = source_dir or '.'
227
+ source_url = '%s/%s' % (source_dir, filename)
228
+ self.logger(logging.DEBUG, 'ssh.put: source url: {}'.format(source_url))
229
+
230
+ path = self.pfn2path(target)
231
+ pathdir = os.path.dirname(path)
232
+ if not os.path.exists(source_url):
233
+ raise exception.SourceNotFound()
234
+ try:
235
+ cmd = 'ssh %s%s "mkdir -p %s" && scp %s %s%s:%s' % (self.sshuser, self.hostname, pathdir, source_url, self.sshuser, self.hostname, path)
236
+ self.logger(logging.DEBUG, 'ssh.put: cmd: {}'.format(cmd))
237
+ status, out, err = execute(cmd)
238
+ if status:
239
+ raise exception.RucioException(err)
240
+ except Exception as e:
241
+ raise exception.ServiceUnavailable(e)
242
+
243
+ def delete(self, pfn):
244
+ """
245
+ Deletes a file from the connected RSE.
246
+
247
+ :param pfn: Physical file name
248
+
249
+ :raises ServiceUnavailable: if some generic error occurred in the library.
250
+ :raises SourceNotFound: if the source file was not found on the referred storage.
251
+ """
252
+ self.logger(logging.DEBUG, 'ssh.delete: pfn: {}'.format(pfn))
253
+ if not self.exists(pfn):
254
+ raise exception.SourceNotFound()
255
+ try:
256
+ path = self.pfn2path(pfn)
257
+ cmd = 'ssh -p %s %s%s rm %s' % (self.port, self.sshuser, self.hostname, path)
258
+ self.logger(logging.DEBUG, 'ssh.delete: cmd: {}'.format(cmd))
259
+ status, out, err = execute(cmd)
260
+ if status != 0:
261
+ raise exception.RucioException(err)
262
+ except Exception as e:
263
+ raise exception.ServiceUnavailable(e)
264
+
265
+ def rename(self, pfn, new_pfn):
266
+ """ Allows to rename a file stored inside the connected RSE.
267
+
268
+ :param pfn: Current physical file name
269
+ :param new_pfn New physical file name
270
+ :raises DestinationNotAccessible: if the destination storage was not accessible.
271
+ :raises ServiceUnavailable: if some generic error occurred in the library.
272
+ :raises SourceNotFound: if the source file was not found on the referred storage.
273
+ """
274
+ self.logger(logging.DEBUG, 'ssh.rename: pfn: {}'.format(pfn))
275
+ if not self.exists(pfn):
276
+ raise exception.SourceNotFound()
277
+ try:
278
+ path = self.pfn2path(pfn)
279
+ new_path = self.pfn2path(new_pfn)
280
+ new_dir = new_path[:new_path.rindex('/') + 1]
281
+ cmd = 'ssh -p %s %s%s "mkdir -p %s"' % (self.port, self.sshuser, self.hostname, new_dir)
282
+ self.logger(logging.DEBUG, 'ssh.rename: mkdir cmd: {}'.format(cmd))
283
+ status, out, err = execute(cmd)
284
+ cmd = 'ssh -p %s %s%s mv %s %s' % (self.port, self.sshuser, self.hostname, path, new_path)
285
+ self.logger(logging.DEBUG, 'ssh.rename: rename cmd: {}'.format(cmd))
286
+ status, out, err = execute(cmd)
287
+ if status != 0:
288
+ raise exception.RucioException(err)
289
+ except Exception as e:
290
+ raise exception.ServiceUnavailable(e)
291
+
292
+
293
+ class Rsync(Default):
294
+ """ Implementing access to RSEs using the ssh.Rsync implementation."""
295
+
296
+ def stat(self, path):
297
+ """
298
+ Returns the stats of a file.
299
+
300
+ :param path: path to file
301
+
302
+ :raises ServiceUnavailable: if some generic error occurred in the library.
303
+
304
+ :returns: a dict with two keys, filesize and an element of GLOBALLY_SUPPORTED_CHECKSUMS.
305
+ """
306
+ self.logger(logging.DEBUG, 'rsync.stat: path: {}'.format(path))
307
+ ret = {}
308
+ chsum = None
309
+ path = self.pfn2path(path)
310
+
311
+ try:
312
+ # rsync stat for getting filesize
313
+ cmd = "rsync -an --size-only -e 'ssh -p {0}' --remove-source-files {1}{2}:{3}".format(self.port, self.sshuser, self.hostname, path)
314
+ self.logger(logging.DEBUG, 'rsync.stat: filesize cmd: {}'.format(cmd))
315
+ status_stat, out, err = execute(cmd)
316
+ if status_stat == 0:
317
+ sizestr = out.split(" ")[-4]
318
+ ret['filesize'] = sizestr.replace(',', '')
319
+
320
+ # rsync query checksum for getting md5 checksum
321
+ cmd = 'ssh -p %s %s%s md5sum %s' % (self.port, self.sshuser, self.hostname, path)
322
+ self.logger(logging.DEBUG, 'rsync.stat: checksum cmd: {}'.format(cmd))
323
+ status_query, out, err = execute(cmd)
324
+
325
+ if status_query == 0:
326
+ chsum = 'md5'
327
+ val = out.strip(' ').split()
328
+ ret[chsum] = val[0]
329
+
330
+ except Exception as e:
331
+ raise exception.ServiceUnavailable(e)
332
+
333
+ if 'filesize' not in ret:
334
+ raise exception.ServiceUnavailable('Filesize could not be retrieved.')
335
+ if PREFERRED_CHECKSUM != chsum or not chsum:
336
+ msg = '{} does not match with {}'.format(chsum, PREFERRED_CHECKSUM)
337
+ raise exception.RSEChecksumUnavailable(msg)
338
+
339
+ return ret
340
+
341
+ def connect(self):
342
+ """ Establishes the actual connection to the referred RSE.
343
+
344
+ :raises RSEAccessDenied
345
+ """
346
+ self.logger(logging.DEBUG, 'rsync.connect: port: {}, hostname {}, ssh-user {}'.format(self.port, self.hostname, self.sshuser))
347
+ try:
348
+ cmd = 'ssh -p %s %s%s echo ok 2>&1' % (self.port, self.sshuser, self.hostname)
349
+ status, out, err = execute(cmd)
350
+ checker = re.search(r'ok', out)
351
+ if not checker:
352
+ raise exception.RSEAccessDenied(err)
353
+ cmd = 'ssh -p %s %s%s type rsync' % (self.port, self.sshuser, self.hostname)
354
+ status, out, err = execute(cmd)
355
+ checker = re.search(r'rsync is', out)
356
+ if not checker:
357
+ raise exception.RSEAccessDenied(err)
358
+ self.path = out.split(" ")[2][:-1]
359
+
360
+ except Exception as e:
361
+ raise exception.RSEAccessDenied(e)
362
+
363
+ def get(self, pfn, dest, transfer_timeout=None):
364
+ """ Provides access to files stored inside connected the RSE.
365
+
366
+ :param pfn: Physical file name of requested file
367
+ :param dest: Name and path of the files when stored at the client
368
+ :param transfer_timeout: Transfer timeout (in seconds) - dummy
369
+
370
+ :raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound
371
+ """
372
+ self.logger(logging.DEBUG, 'rsync.get: pfn: {}'.format(pfn))
373
+ try:
374
+ path = self.pfn2path(pfn)
375
+ destdir = os.path.dirname(dest)
376
+ cmd = 'mkdir -p %s && rsync -az -e "ssh -p %s" --append-verify %s%s:%s %s' % (destdir, self.port, self.sshuser, self.hostname, path, dest)
377
+ self.logger(logging.DEBUG, 'rsync.get: cmd: {}'.format(cmd))
378
+ status, out, err = execute(cmd)
379
+ if status:
380
+ raise exception.RucioException(err)
381
+ except Exception as e:
382
+ raise exception.ServiceUnavailable(e)
383
+
384
+ def put(self, filename, target, source_dir, transfer_timeout=None):
385
+ """
386
+ Allows to store files inside the referred RSE.
387
+
388
+ :param source: path to the source file on the client file system
389
+ :param target: path to the destination file on the storage
390
+ :param source_dir: Path where the to be transferred files are stored in the local file system
391
+ :param transfer_timeout: Transfer timeout (in seconds) - dummy
392
+
393
+ :raises DestinationNotAccessible: if the destination storage was not accessible.
394
+ :raises ServiceUnavailable: if some generic error occurred in the library.
395
+ :raises SourceNotFound: if the source file was not found on the referred storage.
396
+ """
397
+ self.logger(logging.DEBUG, 'rsync.put: filename: {} target: {}'.format(filename, target))
398
+ source_dir = source_dir or '.'
399
+ source_url = '%s/%s' % (source_dir, filename)
400
+ self.logger(logging.DEBUG, 'rsync.put: source url: {}'.format(source_url))
401
+
402
+ path = self.pfn2path(target)
403
+ pathdir = os.path.dirname(path)
404
+ if not os.path.exists(source_url):
405
+ raise exception.SourceNotFound()
406
+
407
+ try:
408
+ cmd = 'ssh -p %s %s%s "mkdir -p %s" && rsync -az -e "ssh -p %s" --append-verify %s %s%s:%s' % (self.port, self.sshuser, self.hostname, pathdir, self.port, source_url, self.sshuser, self.hostname, path)
409
+ self.logger(logging.DEBUG, 'rsync.put: cmd: {}'.format(cmd))
410
+ status, out, err = execute(cmd)
411
+ if status:
412
+ raise exception.RucioException(err)
413
+ except Exception as e:
414
+ raise exception.ServiceUnavailable(e)
@@ -0,0 +1,195 @@
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 logging
16
+ import os
17
+ from xml.dom import minidom
18
+
19
+ import requests
20
+
21
+ from rucio.common import exception
22
+ from rucio.common.utils import run_cmd_process
23
+ from rucio.rse.protocols import protocol
24
+
25
+
26
+ class Default(protocol.RSEProtocol):
27
+ """ Implementing access to RSEs using the local filesystem."""
28
+
29
+ def __init__(self, protocol_attr, rse_settings, logger=None):
30
+ """ Initializes the object with information about the referred RSE.
31
+
32
+ :param props: Properties derived from the RSE Repository
33
+ """
34
+ super(Default, self).__init__(protocol_attr, rse_settings, logger=logger)
35
+ self.attributes.pop('determinism_type', None)
36
+ self.files = []
37
+
38
+ def lfns2pfns(self, lfns):
39
+ """ Create fake storm:// path. Will be resolved at the get() stage later. """
40
+ pfns = {}
41
+
42
+ hostname = self.attributes['hostname']
43
+ if '://' in hostname:
44
+ hostname = hostname.split("://")[1]
45
+
46
+ prefix = self.attributes['prefix']
47
+ if not prefix.startswith('/'):
48
+ prefix = ''.join(['/', prefix])
49
+ if not prefix.endswith('/'):
50
+ prefix = ''.join([prefix, '/'])
51
+
52
+ lfns = [lfns] if isinstance(lfns, dict) else lfns
53
+ for lfn in lfns:
54
+ path = lfn['path'] if 'path' in lfn and lfn['path'] else self._get_path(scope=lfn['scope'],
55
+ name=lfn['name'])
56
+ pfns['%s:%s' % (lfn['scope'], lfn['name'])] = ''.join(['storm://', hostname, ':', str(self.attributes['port']), prefix, path])
57
+
58
+ return pfns
59
+
60
+ def path2pfn(self, path):
61
+ """
62
+ Returns a fully qualified PFN for the file referred by path.
63
+
64
+ :param path: The path to the file.
65
+
66
+ :returns: Fully qualified PFN.
67
+
68
+ """
69
+ return ''.join([self.rse['scheme'], '://%s' % self.rse['hostname'], path])
70
+
71
+ def connect(self):
72
+ """ Establishes the actual connection to the referred RSE.
73
+
74
+ :param credentials: Provide all necessary information to establish a connection
75
+ to the referred storage system. Some is loaded from the repository inside the
76
+ RSE class and some must be provided specific for the SFTP protocol like
77
+ username, password, private_key, private_key_pass, port.
78
+ For details about possible additional parameters and details about their usage
79
+ see the pysftp.Connection() documentation.
80
+ NOTE: the host parametrer is overwritten with the value provided by the repository
81
+
82
+ :raise RSEAccessDenied
83
+ """
84
+ pass
85
+
86
+ def close(self):
87
+ """ Closes the connection to RSE."""
88
+ pass
89
+
90
+ def get(self, pfn, dest, transfer_timeout=None):
91
+ """ Provides access to files stored inside connected the RSE.
92
+
93
+ :param pfn: Physical file name of requested file
94
+ :param dest: Name and path of the files when stored at the client
95
+ :param transfer_timeout Transfer timeout (in seconds)
96
+
97
+ :raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound
98
+ """
99
+
100
+ # retrieve the TURL from the webdav etag, REQUESTS
101
+ def requests_etag(pfn, timeout):
102
+ x509 = os.environ.get('X509_USER_PROXY')
103
+ pfn = 'https' + pfn[:5]
104
+ session = requests.Session()
105
+ output = session.request('PROPFIND', pfn, verify=False, timeout=timeout, cert=(x509, x509))
106
+ session.close()
107
+ return output.status_code, output.text
108
+
109
+ # retrieve the TURL from the webdav etag, DAVIX
110
+ def davix_etag(pfn, timeout):
111
+ pfn = 'davs' + pfn[5:]
112
+ cmd = 'davix-http --capath /cvmfs/atlas.cern.ch/repo/ATLASLocalRootBase/etc/grid-security-emi/certificates --cert $X509_USER_PROXY -X PROPFIND %s' % pfn
113
+ try:
114
+ rcode, output = run_cmd_process(cmd, timeout=timeout)
115
+ if rcode != 0:
116
+ if output:
117
+ raise exception.ServiceUnavailable("{}/n{}".format(str(output), cmd))
118
+ else:
119
+ raise exception.ServiceUnavailable('Error message from subprocess davix-http call is missing./n{}'.format(cmd))
120
+ except Exception as e:
121
+ raise exception.ServiceUnavailable('Could not retrieve STORM WebDAV ETag: {}/n{}'.format(str(e), cmd))
122
+ return rcode, output
123
+
124
+ # using prefix from AGIS primarily (ATLAS spec.)
125
+ target = None
126
+ try:
127
+ target = self.pfn2path(pfn)
128
+ name = pfn.split('/')[-1]
129
+ if name not in target:
130
+ target = None
131
+ except Exception:
132
+ target = None
133
+ pass
134
+
135
+ # if AGIS setting failed
136
+ etag_meta = None
137
+ if not target:
138
+ # requests preferable
139
+ try:
140
+ rcode, etag_meta = requests_etag(pfn, 300)
141
+ except Exception:
142
+ pass
143
+ # fallback to davix
144
+ if rcode != 207:
145
+ rcode, etag_meta = davix_etag(pfn, 300)
146
+
147
+ p_output = minidom.parseString(etag_meta) # noqa: S318
148
+ # we need to strip off the quotation marks and the <timestamp> from the etag
149
+ # but since we can have multiple underscores, we have to rely on the uniqueness
150
+ # of the full LFN to make the split
151
+ target = p_output.getElementsByTagName('d:getetag')[0].childNodes[0].nodeValue.replace('"', '')
152
+ target_ending = '_' + target.split('_')[-1]
153
+ target = target.split(target_ending)[0]
154
+
155
+ # make the symlink
156
+ try:
157
+ os.symlink(target, dest)
158
+ self.logger(logging.INFO, 'StoRM protocol: {}->{}'.format(target, dest))
159
+ except Exception as e:
160
+ exception.ServiceUnavailable('Could not create symlink: %s for target %s' % (str(e), str(target)))
161
+
162
+ def pfn2path(self, pfn):
163
+ tmp = list(self.parse_pfns(pfn).values())[0]
164
+ return '/'.join([tmp['prefix'], tmp['path'], tmp['name']])
165
+
166
+ def put(self, source, target, source_dir=None, transfer_timeout=None):
167
+ """ Allows to store files inside the referred RSE.
168
+
169
+ :param source: Physical file name
170
+ :param target: Name of the file on the storage system e.g. with prefixed scope
171
+ :param source_dir Path where the to be transferred files are stored in the local file system
172
+ :param transfer_timeout Transfer timeout (in seconds)
173
+
174
+ :raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound
175
+ """
176
+ raise NotImplementedError
177
+
178
+ def delete(self, pfn):
179
+ """ Deletes a file from the connected RSE.
180
+
181
+ :param pfn: Physical file name
182
+
183
+ :raises ServiceUnavailable, SourceNotFound
184
+ """
185
+ raise NotImplementedError
186
+
187
+ def rename(self, pfn, new_pfn):
188
+ """ Allows to rename a file stored inside the connected RSE.
189
+
190
+ :param pfn: Current physical file name
191
+ :param new_pfn New physical file name
192
+
193
+ :raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound
194
+ """
195
+ raise NotImplementedError