rucio-clients 35.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of rucio-clients might be problematic. Click here for more details.

Files changed (88) hide show
  1. rucio/__init__.py +17 -0
  2. rucio/alembicrevision.py +15 -0
  3. rucio/client/__init__.py +15 -0
  4. rucio/client/accountclient.py +433 -0
  5. rucio/client/accountlimitclient.py +183 -0
  6. rucio/client/baseclient.py +974 -0
  7. rucio/client/client.py +76 -0
  8. rucio/client/configclient.py +126 -0
  9. rucio/client/credentialclient.py +59 -0
  10. rucio/client/didclient.py +866 -0
  11. rucio/client/diracclient.py +56 -0
  12. rucio/client/downloadclient.py +1785 -0
  13. rucio/client/exportclient.py +44 -0
  14. rucio/client/fileclient.py +50 -0
  15. rucio/client/importclient.py +42 -0
  16. rucio/client/lifetimeclient.py +90 -0
  17. rucio/client/lockclient.py +109 -0
  18. rucio/client/metaconventionsclient.py +140 -0
  19. rucio/client/pingclient.py +44 -0
  20. rucio/client/replicaclient.py +454 -0
  21. rucio/client/requestclient.py +125 -0
  22. rucio/client/rseclient.py +746 -0
  23. rucio/client/ruleclient.py +294 -0
  24. rucio/client/scopeclient.py +90 -0
  25. rucio/client/subscriptionclient.py +173 -0
  26. rucio/client/touchclient.py +82 -0
  27. rucio/client/uploadclient.py +955 -0
  28. rucio/common/__init__.py +13 -0
  29. rucio/common/cache.py +74 -0
  30. rucio/common/config.py +801 -0
  31. rucio/common/constants.py +159 -0
  32. rucio/common/constraints.py +17 -0
  33. rucio/common/didtype.py +189 -0
  34. rucio/common/exception.py +1151 -0
  35. rucio/common/extra.py +36 -0
  36. rucio/common/logging.py +420 -0
  37. rucio/common/pcache.py +1408 -0
  38. rucio/common/plugins.py +153 -0
  39. rucio/common/policy.py +84 -0
  40. rucio/common/schema/__init__.py +150 -0
  41. rucio/common/schema/atlas.py +413 -0
  42. rucio/common/schema/belleii.py +408 -0
  43. rucio/common/schema/domatpc.py +401 -0
  44. rucio/common/schema/escape.py +426 -0
  45. rucio/common/schema/generic.py +433 -0
  46. rucio/common/schema/generic_multi_vo.py +412 -0
  47. rucio/common/schema/icecube.py +406 -0
  48. rucio/common/stomp_utils.py +159 -0
  49. rucio/common/stopwatch.py +55 -0
  50. rucio/common/test_rucio_server.py +148 -0
  51. rucio/common/types.py +403 -0
  52. rucio/common/utils.py +2238 -0
  53. rucio/rse/__init__.py +96 -0
  54. rucio/rse/protocols/__init__.py +13 -0
  55. rucio/rse/protocols/bittorrent.py +184 -0
  56. rucio/rse/protocols/cache.py +122 -0
  57. rucio/rse/protocols/dummy.py +111 -0
  58. rucio/rse/protocols/gfal.py +703 -0
  59. rucio/rse/protocols/globus.py +243 -0
  60. rucio/rse/protocols/gsiftp.py +92 -0
  61. rucio/rse/protocols/http_cache.py +82 -0
  62. rucio/rse/protocols/mock.py +123 -0
  63. rucio/rse/protocols/ngarc.py +209 -0
  64. rucio/rse/protocols/posix.py +250 -0
  65. rucio/rse/protocols/protocol.py +594 -0
  66. rucio/rse/protocols/rclone.py +364 -0
  67. rucio/rse/protocols/rfio.py +136 -0
  68. rucio/rse/protocols/srm.py +338 -0
  69. rucio/rse/protocols/ssh.py +413 -0
  70. rucio/rse/protocols/storm.py +206 -0
  71. rucio/rse/protocols/webdav.py +550 -0
  72. rucio/rse/protocols/xrootd.py +301 -0
  73. rucio/rse/rsemanager.py +764 -0
  74. rucio/vcsversion.py +11 -0
  75. rucio/version.py +38 -0
  76. rucio_clients-35.7.0.data/data/etc/rse-accounts.cfg.template +25 -0
  77. rucio_clients-35.7.0.data/data/etc/rucio.cfg.atlas.client.template +42 -0
  78. rucio_clients-35.7.0.data/data/etc/rucio.cfg.template +257 -0
  79. rucio_clients-35.7.0.data/data/requirements.client.txt +15 -0
  80. rucio_clients-35.7.0.data/data/rucio_client/merge_rucio_configs.py +144 -0
  81. rucio_clients-35.7.0.data/scripts/rucio +2542 -0
  82. rucio_clients-35.7.0.data/scripts/rucio-admin +2447 -0
  83. rucio_clients-35.7.0.dist-info/METADATA +50 -0
  84. rucio_clients-35.7.0.dist-info/RECORD +88 -0
  85. rucio_clients-35.7.0.dist-info/WHEEL +5 -0
  86. rucio_clients-35.7.0.dist-info/licenses/AUTHORS.rst +97 -0
  87. rucio_clients-35.7.0.dist-info/licenses/LICENSE +201 -0
  88. rucio_clients-35.7.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,594 @@
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
+ """
16
+ This module defines the base class for implementing a transfer protocol,
17
+ along with some of the default methods for LFN2PFN translations.
18
+ """
19
+ import hashlib
20
+ import logging
21
+ from collections.abc import Callable, Mapping
22
+ from configparser import NoOptionError, NoSectionError
23
+ from typing import Any, Optional, TypeVar
24
+ from urllib.parse import urlparse
25
+
26
+ from rucio.common import config, exception
27
+ from rucio.common.constants import RseAttr
28
+ from rucio.common.plugins import PolicyPackageAlgorithms
29
+ from rucio.rse import rsemanager
30
+
31
+ if getattr(rsemanager, 'CLIENT_MODE', None):
32
+ from rucio.client.rseclient import RSEClient
33
+
34
+ if getattr(rsemanager, 'SERVER_MODE', None):
35
+ from rucio.common.types import InternalScope
36
+ from rucio.core import replica
37
+ from rucio.core.rse import get_rse_vo
38
+
39
+
40
+ class RSEDeterministicScopeTranslation(PolicyPackageAlgorithms):
41
+ """
42
+ Translates a pfn dictionary into a scope and name
43
+ """
44
+
45
+ _algorithm_type = "pfn2lfn"
46
+
47
+ def __init__(self, vo: str = 'def'):
48
+ super().__init__()
49
+
50
+ self.register(RSEDeterministicScopeTranslation._default, "def")
51
+ self.register(RSEDeterministicScopeTranslation._atlas, "atlas")
52
+
53
+ logger = logging.getLogger(__name__)
54
+
55
+ try:
56
+ # Use the function defined in the policy package if it's configured so
57
+ algorithm_name = config.config_get('policy', self._algorithm_type)
58
+ except (NoOptionError, NoSectionError, RuntimeError):
59
+ # Don't use a function from the policy package. Use one defined in this class according to vo
60
+ logger.debug("PFN2LFN function will not be fetched from the policy package")
61
+ if super()._supports(self._algorithm_type, vo):
62
+ algorithm_name = vo
63
+ else:
64
+ algorithm_name = "def"
65
+
66
+ self.parser = self.get_parser(algorithm_name)
67
+
68
+ @classmethod
69
+ def get_parser(cls, algorithm_name: str) -> Callable[..., Any]:
70
+ return super()._get_one_algorithm(cls._algorithm_type, algorithm_name)
71
+
72
+ @classmethod
73
+ def register(
74
+ cls,
75
+ pfn2lfn_callable: Callable,
76
+ name: Optional[str] = None
77
+ ) -> None:
78
+ """
79
+ Provided a callable function, register it as one of the valid PFN2LFN algorithms.
80
+
81
+
82
+ :param pfn2lfn_callable: Callable function to use.
83
+ :param name: Algorithm name used for registration.
84
+ """
85
+ if name is None:
86
+ name = pfn2lfn_callable.__name__
87
+ algorithm_dict = {name: pfn2lfn_callable}
88
+ super()._register(cls._algorithm_type, algorithm_dict)
89
+
90
+ @staticmethod
91
+ def _default(parsed_pfn: Mapping[str, str]) -> tuple[str, str]:
92
+ """ Translate pfn to name/scope pair
93
+
94
+ :param parsed_pfn: dictionary representing pfn containing:
95
+ - path: str,
96
+ - name: str
97
+ :return: tuple containing name, scope
98
+ """
99
+ path = parsed_pfn['path']
100
+ scope = path.lstrip('/').split('/')[0]
101
+ name = parsed_pfn['name']
102
+ return name, scope
103
+
104
+ @staticmethod
105
+ def _atlas(parsed_pfn: Mapping[str, str]) -> tuple[str, str]:
106
+ """ Translate pfn to name/scope pair
107
+
108
+ :param parsed_pfn: dictionary representing pfn containing:
109
+ - path: str,
110
+ - name: str
111
+ :return: tuple containing name, scope
112
+ """
113
+ path = parsed_pfn['path']
114
+ if path.startswith('/user') or path.startswith('/group'):
115
+ scope = '%s.%s' % (path.split('/')[1], path.split('/')[2])
116
+ name = parsed_pfn['name']
117
+ else:
118
+ name, scope = RSEDeterministicScopeTranslation._default(parsed_pfn)
119
+
120
+ return name, scope
121
+
122
+
123
+ RSEDeterministicScopeTranslation()
124
+
125
+
126
+ RSEDeterministicTranslationT = TypeVar('RSEDeterministicTranslationT', bound='RSEDeterministicTranslation')
127
+
128
+
129
+ class RSEDeterministicTranslation(PolicyPackageAlgorithms):
130
+ """
131
+ Execute the logic for translating a LFN to a path.
132
+ """
133
+
134
+ _DEFAULT_LFN2PFN = "hash"
135
+ _algorithm_type = "lfn2pfn"
136
+
137
+ def __init__(self, rse=None, rse_attributes=None, protocol_attributes=None):
138
+ """
139
+ Initialize a translator object from the RSE, its attributes, and the protocol-specific
140
+ attributes.
141
+
142
+ :param rse: Name of RSE for this translation.
143
+ :param rse_attributes: A dictionary of RSE-specific attributes for use in the translation.
144
+ :param protocol_attributes: A dictionary of RSE/protocol-specific attributes.
145
+ """
146
+ super().__init__()
147
+ self.rse = rse
148
+ self.rse_attributes = rse_attributes if rse_attributes else {}
149
+ self.protocol_attributes = protocol_attributes if protocol_attributes else {}
150
+
151
+ @classmethod
152
+ def supports(cls, name):
153
+ """
154
+ Check to see if a specific algorithm is supported.
155
+
156
+ :param name: Name of the deterministic algorithm.
157
+ :returns: True if `name` is an algorithm supported by the translator class, False otherwise
158
+ """
159
+ return super()._supports(cls._algorithm_type, name)
160
+
161
+ @classmethod
162
+ def register(cls, lfn2pfn_callable, name=None):
163
+ """
164
+ Provided a callable function, register it as one of the valid LFN2PFN algorithms.
165
+
166
+ The callable will receive five arguments:
167
+ - scope: Scope of the LFN.
168
+ - name: LFN's path name
169
+ - rse: RSE name the translation is being done for.
170
+ - rse_attributes: Attributes of the RSE.
171
+ - protocol_attributes: Attributes of the RSE's protocol
172
+ The return value should be the last part of the PFN - it will be appended to the
173
+ rest of the URL.
174
+
175
+ :param lfn2pfn_callable: Callable function to use for generating paths.
176
+ :param name: Algorithm name used for registration. If None, then `lfn2pfn_callable.__name__` is used.
177
+ """
178
+ if name is None:
179
+ name = lfn2pfn_callable.__name__
180
+ algorithm_dict = {name: lfn2pfn_callable}
181
+ super()._register(cls._algorithm_type, algorithm_dict)
182
+
183
+ @staticmethod
184
+ def __hash(scope, name, rse, rse_attrs, protocol_attrs):
185
+ """
186
+ Given a LFN, turn it into a sub-directory structure using a hash function.
187
+
188
+ This takes the MD5 of the LFN and uses the first four characters as a subdirectory
189
+ name.
190
+
191
+ :param scope: Scope of the LFN.
192
+ :param name: File name of the LFN.
193
+ :param rse: RSE for PFN (ignored)
194
+ :param rse_attrs: RSE attributes for PFN (ignored)
195
+ :param protocol_attrs: RSE protocol attributes for PFN (ignored)
196
+ :returns: Path for use in the PFN generation.
197
+ """
198
+ del rse
199
+ del rse_attrs
200
+ del protocol_attrs
201
+ hstr = hashlib.md5(('%s:%s' % (scope, name)).encode('utf-8')).hexdigest()
202
+ if scope.startswith('user') or scope.startswith('group'):
203
+ scope = scope.replace('.', '/')
204
+ return '%s/%s/%s/%s' % (scope, hstr[0:2], hstr[2:4], name)
205
+
206
+ @staticmethod
207
+ def __identity(scope, name, rse, rse_attrs, protocol_attrs):
208
+ """
209
+ Given a LFN, convert it directly to a path using the mapping:
210
+
211
+ scope:path -> scope/path
212
+
213
+ :param scope: Scope of the LFN.
214
+ :param name: File name of the LFN.
215
+ :param rse: RSE for PFN (ignored)
216
+ :param rse_attrs: RSE attributes for PFN (ignored)
217
+ :param protocol_attrs: RSE protocol attributes for PFN (ignored)
218
+ :returns: Path for use in the PFN generation.
219
+ """
220
+ del rse
221
+ del rse_attrs
222
+ del protocol_attrs
223
+ if scope.startswith('user') or scope.startswith('group'):
224
+ scope = scope.replace('.', '/')
225
+ return '%s/%s' % (scope, name)
226
+
227
+ @staticmethod
228
+ def __belleii(scope, name, rse, rse_attrs, protocol_attrs):
229
+ """
230
+ Given a LFN, convert it directly to a path using the mapping:
231
+
232
+ path -> path
233
+ This is valid only for the belleii convention where the scope can be determined
234
+ from the LFN using a determinitic function.
235
+
236
+ :param scope: Scope of the LFN.
237
+ :param name: File name of the LFN.
238
+ :param rse: RSE for PFN (ignored)
239
+ :param rse_attrs: RSE attributes for PFN (ignored)
240
+ :param protocol_attrs: RSE protocol attributes for PFN (ignored)
241
+ :returns: Path for use in the PFN generation.
242
+ """
243
+ del scope
244
+ del rse
245
+ del rse_attrs
246
+ del protocol_attrs
247
+ return name
248
+
249
+ @staticmethod
250
+ def __ligo(scope, name, rse, rse_attrs, protocol_attrs):
251
+ """
252
+ Given a LFN, convert it directly to a path using the Caltech schema
253
+
254
+ e.g.,: ER8:H-H1_HOFT_C02-1126256640-4096 ->
255
+ ER8/hoft_C02/H1/H-H1_HOFT_C02-11262/H-H1_HOFT_C02-1126256640-4096
256
+
257
+ :param scope: Scope of the LFN (observing run: ER8, O2, postO1, ...)
258
+ :param name: File name of the LFN (E.g., H-H1_HOFT_C02-1126256640-4096.gwf)
259
+ :param rse: RSE for PFN (ignored)
260
+ :param rse_attrs: RSE attributes for PFN (ignored)
261
+ :param protocol_attrs: RSE protocol attributes for PFN (ignored)
262
+ :returns: Path for use in the PFN generation.
263
+ """
264
+ del rse
265
+ del rse_attrs
266
+ del protocol_attrs
267
+ from ligo_rucio import lfn2pfn as ligo_lfn2pfn # pylint: disable=import-error
268
+ return ligo_lfn2pfn.ligo_lab(scope, name, None, None, None)
269
+
270
+ @staticmethod
271
+ def __xenon(scope, name, rse, rse_attrs, protocol_attrs):
272
+ """
273
+ Given a LFN, turn it into a two level sub-directory structure based on the scope
274
+ plus a third level based on the name
275
+ :param scope: Scope of the LFN.
276
+ :param name: File name of the LFN.
277
+ :param rse: RSE for PFN (ignored)
278
+ :param rse_attrs: RSE attributes for PFN (ignored)
279
+ :param protocol_attrs: RSE protocol attributes for PFN (ignored)
280
+ :returns: Path for use in the PFN generation.
281
+ """
282
+ del rse
283
+ del rse_attrs
284
+ del protocol_attrs
285
+
286
+ return '%s/%s/%s/%s' % (scope[0:7], scope[4:len(scope)], name.split('-')[0] + "-" + name.split('-')[1], name)
287
+
288
+ @classmethod
289
+ def _module_init_(cls):
290
+ """
291
+ Initialize the class object on first module load.
292
+ """
293
+ cls.register(cls.__hash, "hash")
294
+ cls.register(cls.__identity, "identity")
295
+ cls.register(cls.__ligo, "ligo")
296
+ cls.register(cls.__belleii, "belleii")
297
+ cls.register(cls.__xenon, "xenon")
298
+ policy_module = None
299
+ try:
300
+ policy_module = config.config_get('policy', 'lfn2pfn_module')
301
+ except (NoOptionError, NoSectionError):
302
+ pass
303
+ if policy_module:
304
+ # TODO: The import of importlib is done like this due to a dependency issue with python 2.6 and incompatibility of the module with py3.x
305
+ # More information https://github.com/rucio/rucio/issues/875
306
+ import importlib
307
+ importlib.import_module(policy_module)
308
+
309
+ cls._DEFAULT_LFN2PFN = config.get_lfn2pfn_algorithm_default()
310
+
311
+ def path(self, scope, name):
312
+ """ Transforms the logical file name into a PFN's path.
313
+
314
+ :param lfn: filename
315
+ :param scope: scope
316
+
317
+ :returns: RSE specific URI of the physical file
318
+ """
319
+ algorithm = self.rse_attributes.get(RseAttr.LFN2PFN_ALGORITHM, 'default')
320
+ if algorithm == 'default':
321
+ algorithm = RSEDeterministicTranslation._DEFAULT_LFN2PFN
322
+ algorithm_callable = super()._get_one_algorithm(RSEDeterministicTranslation._algorithm_type, algorithm)
323
+ return algorithm_callable(scope, name, self.rse, self.rse_attributes, self.protocol_attributes)
324
+
325
+
326
+ RSEDeterministicTranslation._module_init_() # pylint: disable=protected-access
327
+
328
+
329
+ class RSEProtocol:
330
+ """ This class is virtual and acts as a base to inherit new protocols from. It further provides some common functionality which applies for the amjority of the protocols."""
331
+
332
+ def __init__(self, protocol_attr, rse_settings, logger=logging.log):
333
+ """ Initializes the object with information about the referred RSE.
334
+
335
+ :param protocol_attr: Properties of the requested protocol.
336
+ :param rse_settting: The RSE settings.
337
+ :param logger: Optional decorated logger that can be passed from the calling daemons or servers.
338
+ """
339
+ self.auth_token = protocol_attr['auth_token']
340
+ protocol_attr.pop('auth_token')
341
+ self.attributes = protocol_attr
342
+ self.translator = None
343
+ self.renaming = True
344
+ self.overwrite = False
345
+ self.rse = rse_settings
346
+ self.logger = logger
347
+ if self.rse['deterministic']:
348
+ self.translator = RSEDeterministicTranslation(self.rse['rse'], rse_settings, self.attributes)
349
+ if getattr(rsemanager, 'CLIENT_MODE', None) and \
350
+ not RSEDeterministicTranslation.supports(self.rse.get('lfn2pfn_algorithm')):
351
+ # Remote server has an algorithm we don't understand; always make the server do the lookup.
352
+ setattr(self, 'lfns2pfns', self.__lfns2pfns_client)
353
+ else:
354
+ if getattr(rsemanager, 'CLIENT_MODE', None):
355
+ setattr(self, 'lfns2pfns', self.__lfns2pfns_client)
356
+ if getattr(rsemanager, 'SERVER_MODE', None):
357
+ setattr(self, '_get_path', self._get_path_nondeterministic_server)
358
+
359
+ def lfns2pfns(self, lfns):
360
+ """
361
+ Returns a fully qualified PFN for the file referred by path.
362
+
363
+ :param path: The path to the file.
364
+
365
+ :returns: Fully qualified PFN.
366
+ """
367
+ pfns = {}
368
+ prefix = self.attributes['prefix']
369
+
370
+ if not prefix.startswith('/'):
371
+ prefix = ''.join(['/', prefix])
372
+ if not prefix.endswith('/'):
373
+ prefix = ''.join([prefix, '/'])
374
+
375
+ lfns = [lfns] if isinstance(lfns, dict) else lfns
376
+ for lfn in lfns:
377
+ scope, name = str(lfn['scope']), lfn['name']
378
+ if 'path' in lfn and lfn['path'] is not None:
379
+ pfns['%s:%s' % (scope, name)] = ''.join([self.attributes['scheme'],
380
+ '://',
381
+ self.attributes['hostname'],
382
+ ':',
383
+ str(self.attributes['port']),
384
+ prefix,
385
+ lfn['path'] if not lfn['path'].startswith('/') else lfn['path'][1:]
386
+ ])
387
+ else:
388
+ try:
389
+ pfns['%s:%s' % (scope, name)] = ''.join([self.attributes['scheme'],
390
+ '://',
391
+ self.attributes['hostname'],
392
+ ':',
393
+ str(self.attributes['port']),
394
+ prefix,
395
+ self._get_path(scope=scope, name=name)
396
+ ])
397
+ except exception.ReplicaNotFound as e:
398
+ self.logger(logging.WARNING, str(e))
399
+ return pfns
400
+
401
+ def __lfns2pfns_client(self, lfns):
402
+ """ Provides the path of a replica for non-deterministic sites. Will be assigned to get path by the __init__ method if necessary.
403
+
404
+ :param scope: list of DIDs
405
+
406
+ :returns: dict with scope:name as keys and PFN as value (in case of errors the Rucio exception si assigned to the key)
407
+ """
408
+ client = RSEClient() # pylint: disable=E0601
409
+
410
+ lfns = [lfns] if isinstance(lfns, dict) else lfns
411
+ lfn_query = ["%s:%s" % (lfn['scope'], lfn['name']) for lfn in lfns]
412
+ return client.lfns2pfns(self.rse['rse'], lfn_query, scheme=self.attributes['scheme'])
413
+
414
+ def _get_path(self, scope, name):
415
+ """ Transforms the logical file name into a PFN.
416
+ Suitable for sites implementing the RUCIO naming convention.
417
+ This implementation is only invoked if the RSE is deterministic.
418
+
419
+ :param scope: scope
420
+ :param name: filename
421
+
422
+ :returns: RSE specific URI of the physical file
423
+ """
424
+ return self.translator.path(scope, name)
425
+
426
+ def _get_path_nondeterministic_server(self, scope, name): # pylint: disable=invalid-name
427
+ """ Provides the path of a replica for non-deterministic sites. Will be assigned to get path by the __init__ method if necessary. """
428
+ vo = get_rse_vo(self.rse['id']) # pylint: disable=E0601
429
+ scope = InternalScope(scope, vo=vo) # pylint: disable=E0601
430
+ rep = replica.get_replica(scope=scope, name=name, rse_id=self.rse['id']) # pylint: disable=E0601
431
+ if 'path' in rep and rep['path'] is not None:
432
+ path = rep['path']
433
+ elif 'state' in rep and (rep['state'] is None or rep['state'] == 'UNAVAILABLE'):
434
+ raise exception.ReplicaUnAvailable('Missing path information and state is UNAVAILABLE for replica %s:%s on non-deterministic storage named %s' % (scope, name, self.rse['rse']))
435
+ else:
436
+ raise exception.ReplicaNotFound('Missing path information for replica %s:%s on non-deterministic storage named %s' % (scope, name, self.rse['rse']))
437
+ if path.startswith('/'):
438
+ path = path[1:]
439
+ if path.endswith('/'):
440
+ path = path[:-1]
441
+ return path
442
+
443
+ def parse_pfns(self, pfns):
444
+ """
445
+ Splits the given PFN into the parts known by the protocol. It is also checked if the provided protocol supports the given PFNs.
446
+
447
+ :param pfns: a list of a fully qualified PFNs
448
+
449
+ :returns: dic with PFN as key and a dict with path and name as value
450
+
451
+ :raises RSEFileNameNotSupported: if the provided PFN doesn't match with the protocol settings
452
+ """
453
+ ret = dict()
454
+ pfns = [pfns] if isinstance(pfns, str) else pfns
455
+
456
+ for pfn in pfns:
457
+ parsed = urlparse(pfn)
458
+ scheme = parsed.scheme
459
+ hostname = parsed.netloc.partition(':')[0]
460
+ port = int(parsed.netloc.partition(':')[2]) if parsed.netloc.partition(':')[2] != '' else 0
461
+ while '//' in parsed.path:
462
+ parsed = parsed._replace(path=parsed.path.replace('//', '/'))
463
+ path = parsed.path
464
+ prefix = self.attributes['prefix']
465
+ while '//' in prefix:
466
+ prefix = prefix.replace('//', '/')
467
+
468
+ # Protect against 'lazy' defined prefixes for RSEs in the repository
469
+ if not prefix.startswith('/'):
470
+ prefix = '/' + prefix
471
+ if not prefix.endswith('/'):
472
+ prefix += '/'
473
+
474
+ if self.attributes['hostname'] != hostname:
475
+ if self.attributes['hostname'] != 'localhost': # In the database empty hostnames are replaced with localhost but for some URIs (e.g. file) a hostname is not included
476
+ raise exception.RSEFileNameNotSupported('Invalid hostname: provided \'%s\', expected \'%s\'' % (hostname, self.attributes['hostname']))
477
+
478
+ if self.attributes['port'] != port:
479
+ raise exception.RSEFileNameNotSupported('Invalid port: provided \'%s\', expected \'%s\'' % (port, self.attributes['port']))
480
+
481
+ if not path.startswith(prefix):
482
+ raise exception.RSEFileNameNotSupported('Invalid prefix: provided \'%s\', expected \'%s\'' % ('/'.join(path.split('/')[0:len(prefix.split('/')) - 1]),
483
+ prefix)) # len(...)-1 due to the leading '/
484
+
485
+ # Splitting parsed.path into prefix, path, filename
486
+ path = path.partition(prefix)[2]
487
+ name = path.split('/')[-1]
488
+ path = '/'.join(path.split('/')[:-1])
489
+ if not path.startswith('/'):
490
+ path = '/' + path
491
+ if path != '/' and not path.endswith('/'):
492
+ path = path + '/'
493
+ ret[pfn] = {'path': path, 'name': name, 'scheme': scheme, 'prefix': prefix, 'port': port, 'hostname': hostname, }
494
+
495
+ return ret
496
+
497
+ def exists(self, path):
498
+ """
499
+ Checks if the requested file is known by the referred RSE.
500
+
501
+ :param path: Physical file name
502
+
503
+ :returns: True if the file exists, False if it doesn't
504
+
505
+ :raises SourceNotFound: if the source file was not found on the referred storage.
506
+ """
507
+ raise NotImplementedError
508
+
509
+ def connect(self):
510
+ """
511
+ Establishes the actual connection to the referred RSE.
512
+
513
+ :raises RSEAccessDenied: if no connection could be established.
514
+ """
515
+ raise NotImplementedError
516
+
517
+ def close(self):
518
+ """ Closes the connection to RSE."""
519
+ raise NotImplementedError
520
+
521
+ def get(self, path, dest, transfer_timeout=None):
522
+ """
523
+ Provides access to files stored inside connected the RSE.
524
+
525
+ :param path: Physical file name of requested file
526
+ :param dest: Name and path of the files when stored at the client
527
+ :param transfer_timeout: Transfer timeout (in seconds)
528
+
529
+ :raises DestinationNotAccessible: if the destination storage was not accessible.
530
+ :raises ServiceUnavailable: if some generic error occurred in the library.
531
+ :raises SourceNotFound: if the source file was not found on the referred storage.
532
+ """
533
+ raise NotImplementedError
534
+
535
+ def put(self, source, target, source_dir, transfer_timeout=None):
536
+ """
537
+ Allows to store files inside the referred RSE.
538
+
539
+ :param source: path to the source file on the client file system
540
+ :param target: path to the destination file on the storage
541
+ :param source_dir: Path where the to be transferred files are stored in the local file system
542
+ :param transfer_timeout: Transfer timeout (in seconds)
543
+
544
+ :raises DestinationNotAccessible: if the destination storage was not accessible.
545
+ :raises ServiceUnavailable: if some generic error occurred in the library.
546
+ :raises SourceNotFound: if the source file was not found on the referred storage.
547
+ """
548
+ raise NotImplementedError
549
+
550
+ def delete(self, path):
551
+ """
552
+ Deletes a file from the connected RSE.
553
+
554
+ :param path: path to the to be deleted file
555
+
556
+ :raises ServiceUnavailable: if some generic error occurred in the library.
557
+ :raises SourceNotFound: if the source file was not found on the referred storage.
558
+ """
559
+ raise NotImplementedError
560
+
561
+ def rename(self, path, new_path):
562
+ """ Allows to rename a file stored inside the connected RSE.
563
+
564
+ :param path: path to the current file on the storage
565
+ :param new_path: path to the new file on the storage
566
+
567
+ :raises DestinationNotAccessible: if the destination storage was not accessible.
568
+ :raises ServiceUnavailable: if some generic error occurred in the library.
569
+ :raises SourceNotFound: if the source file was not found on the referred storage.
570
+ """
571
+ raise NotImplementedError
572
+
573
+ def get_space_usage(self):
574
+ """
575
+ Get RSE space usage information.
576
+
577
+ :returns: a list with dict containing 'totalsize' and 'unusedsize'
578
+
579
+ :raises ServiceUnavailable: if some generic error occurred in the library.
580
+ """
581
+ raise NotImplementedError
582
+
583
+ def stat(self, path):
584
+ """
585
+ Returns the stats of a file.
586
+
587
+ :param path: path to file
588
+
589
+ :raises ServiceUnavailable: if some generic error occurred in the library.
590
+ :raises SourceNotFound: if the source file was not found on the referred storage.
591
+
592
+ :returns: a dict with two keys, filesize and adler32 of the file provided in path.
593
+ """
594
+ raise NotImplementedError