rucio-clients 35.8.2__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.
- rucio/__init__.py +17 -0
- rucio/alembicrevision.py +15 -0
- rucio/client/__init__.py +15 -0
- rucio/client/accountclient.py +433 -0
- rucio/client/accountlimitclient.py +183 -0
- rucio/client/baseclient.py +974 -0
- rucio/client/client.py +76 -0
- rucio/client/configclient.py +126 -0
- rucio/client/credentialclient.py +59 -0
- rucio/client/didclient.py +866 -0
- rucio/client/diracclient.py +56 -0
- rucio/client/downloadclient.py +1785 -0
- rucio/client/exportclient.py +44 -0
- rucio/client/fileclient.py +50 -0
- rucio/client/importclient.py +42 -0
- rucio/client/lifetimeclient.py +90 -0
- rucio/client/lockclient.py +109 -0
- rucio/client/metaconventionsclient.py +140 -0
- rucio/client/pingclient.py +44 -0
- rucio/client/replicaclient.py +454 -0
- rucio/client/requestclient.py +125 -0
- rucio/client/rseclient.py +746 -0
- rucio/client/ruleclient.py +294 -0
- rucio/client/scopeclient.py +90 -0
- rucio/client/subscriptionclient.py +173 -0
- rucio/client/touchclient.py +82 -0
- rucio/client/uploadclient.py +955 -0
- rucio/common/__init__.py +13 -0
- rucio/common/cache.py +74 -0
- rucio/common/config.py +801 -0
- rucio/common/constants.py +159 -0
- rucio/common/constraints.py +17 -0
- rucio/common/didtype.py +189 -0
- rucio/common/exception.py +1151 -0
- rucio/common/extra.py +36 -0
- rucio/common/logging.py +420 -0
- rucio/common/pcache.py +1408 -0
- rucio/common/plugins.py +153 -0
- rucio/common/policy.py +84 -0
- rucio/common/schema/__init__.py +150 -0
- rucio/common/schema/atlas.py +413 -0
- rucio/common/schema/belleii.py +408 -0
- rucio/common/schema/domatpc.py +401 -0
- rucio/common/schema/escape.py +426 -0
- rucio/common/schema/generic.py +433 -0
- rucio/common/schema/generic_multi_vo.py +412 -0
- rucio/common/schema/icecube.py +406 -0
- rucio/common/stomp_utils.py +159 -0
- rucio/common/stopwatch.py +55 -0
- rucio/common/test_rucio_server.py +148 -0
- rucio/common/types.py +403 -0
- rucio/common/utils.py +2238 -0
- rucio/rse/__init__.py +96 -0
- rucio/rse/protocols/__init__.py +13 -0
- rucio/rse/protocols/bittorrent.py +184 -0
- rucio/rse/protocols/cache.py +122 -0
- rucio/rse/protocols/dummy.py +111 -0
- rucio/rse/protocols/gfal.py +703 -0
- rucio/rse/protocols/globus.py +243 -0
- rucio/rse/protocols/gsiftp.py +92 -0
- rucio/rse/protocols/http_cache.py +82 -0
- rucio/rse/protocols/mock.py +123 -0
- rucio/rse/protocols/ngarc.py +209 -0
- rucio/rse/protocols/posix.py +250 -0
- rucio/rse/protocols/protocol.py +594 -0
- rucio/rse/protocols/rclone.py +364 -0
- rucio/rse/protocols/rfio.py +136 -0
- rucio/rse/protocols/srm.py +338 -0
- rucio/rse/protocols/ssh.py +413 -0
- rucio/rse/protocols/storm.py +206 -0
- rucio/rse/protocols/webdav.py +550 -0
- rucio/rse/protocols/xrootd.py +301 -0
- rucio/rse/rsemanager.py +764 -0
- rucio/vcsversion.py +11 -0
- rucio/version.py +38 -0
- rucio_clients-35.8.2.data/data/etc/rse-accounts.cfg.template +25 -0
- rucio_clients-35.8.2.data/data/etc/rucio.cfg.atlas.client.template +42 -0
- rucio_clients-35.8.2.data/data/etc/rucio.cfg.template +257 -0
- rucio_clients-35.8.2.data/data/requirements.client.txt +15 -0
- rucio_clients-35.8.2.data/data/rucio_client/merge_rucio_configs.py +144 -0
- rucio_clients-35.8.2.data/scripts/rucio +2542 -0
- rucio_clients-35.8.2.data/scripts/rucio-admin +2447 -0
- rucio_clients-35.8.2.dist-info/METADATA +50 -0
- rucio_clients-35.8.2.dist-info/RECORD +88 -0
- rucio_clients-35.8.2.dist-info/WHEEL +5 -0
- rucio_clients-35.8.2.dist-info/licenses/AUTHORS.rst +97 -0
- rucio_clients-35.8.2.dist-info/licenses/LICENSE +201 -0
- rucio_clients-35.8.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,243 @@
|
|
|
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
|
+
from urllib.parse import urlparse
|
|
17
|
+
|
|
18
|
+
from rucio.common import exception
|
|
19
|
+
from rucio.common.constants import RseAttr
|
|
20
|
+
from rucio.common.extra import import_extras
|
|
21
|
+
from rucio.core.rse import get_rse_attribute
|
|
22
|
+
from rucio.rse.protocols.protocol import RSEProtocol
|
|
23
|
+
from rucio.transfertool.globus_library import get_transfer_client, send_bulk_delete_task, send_delete_task
|
|
24
|
+
|
|
25
|
+
EXTRA_MODULES = import_extras(['globus_sdk'])
|
|
26
|
+
|
|
27
|
+
if EXTRA_MODULES['globus_sdk']:
|
|
28
|
+
from globus_sdk import TransferAPIError # pylint: disable=import-error
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class GlobusRSEProtocol(RSEProtocol):
|
|
32
|
+
""" Implementing access to RSEs using the Globus service as a Rucio RSE protocol. """
|
|
33
|
+
|
|
34
|
+
def __init__(self, protocol_attr, rse_settings, logger=logging.log):
|
|
35
|
+
""" Initializes the object with information about the referred RSE.
|
|
36
|
+
|
|
37
|
+
:param props: Properties of the requested protocol
|
|
38
|
+
"""
|
|
39
|
+
super(GlobusRSEProtocol, self).__init__(protocol_attr, rse_settings, logger=logger)
|
|
40
|
+
self.globus_endpoint_id = get_rse_attribute(self.rse.get('id'), RseAttr.GLOBUS_ENDPOINT_ID)
|
|
41
|
+
self.logger = logger
|
|
42
|
+
|
|
43
|
+
def lfns2pfns(self, lfns):
|
|
44
|
+
"""
|
|
45
|
+
Returns a fully qualified PFN for the file referred by path.
|
|
46
|
+
|
|
47
|
+
:param path: The path to the file.
|
|
48
|
+
|
|
49
|
+
:returns: Fully qualified PFN.
|
|
50
|
+
"""
|
|
51
|
+
pfns = {}
|
|
52
|
+
prefix = self.attributes['prefix']
|
|
53
|
+
|
|
54
|
+
if not prefix.startswith('/'):
|
|
55
|
+
prefix = ''.join(['/', prefix])
|
|
56
|
+
if not prefix.endswith('/'):
|
|
57
|
+
prefix = ''.join([prefix, '/'])
|
|
58
|
+
|
|
59
|
+
lfns = [lfns] if isinstance(lfns, dict) else lfns
|
|
60
|
+
for lfn in lfns:
|
|
61
|
+
scope, name = lfn['scope'], lfn['name']
|
|
62
|
+
|
|
63
|
+
if 'path' in lfn and lfn['path'] is not None:
|
|
64
|
+
pfns['%s:%s' % (scope, name)] = ''.join([prefix, lfn['path'] if not lfn['path'].startswith('/') else lfn['path'][1:]])
|
|
65
|
+
else:
|
|
66
|
+
pfns['%s:%s' % (scope, name)] = ''.join([prefix, self._get_path(scope=scope, name=name)])
|
|
67
|
+
return pfns
|
|
68
|
+
|
|
69
|
+
def _get_path(self, scope, name):
|
|
70
|
+
""" Transforms the logical file name into a PFN.
|
|
71
|
+
Suitable for sites implementing the RUCIO naming convention.
|
|
72
|
+
This implementation is only invoked if the RSE is deterministic.
|
|
73
|
+
|
|
74
|
+
:param scope: scope
|
|
75
|
+
:param name: filename
|
|
76
|
+
|
|
77
|
+
:returns: RSE specific URI of the physical file
|
|
78
|
+
"""
|
|
79
|
+
return self.translator.path(scope, name)
|
|
80
|
+
|
|
81
|
+
def parse_pfns(self, pfns):
|
|
82
|
+
"""
|
|
83
|
+
Splits the given PFN into the parts known by the protocol. It is also checked if the provided protocol supports the given PFNs.
|
|
84
|
+
|
|
85
|
+
:param pfns: a list of a fully qualified PFNs
|
|
86
|
+
|
|
87
|
+
:returns: dic with PFN as key and a dict with path and name as value
|
|
88
|
+
|
|
89
|
+
:raises RSEFileNameNotSupported: if the provided PFN doesn't match with the protocol settings
|
|
90
|
+
"""
|
|
91
|
+
ret = dict()
|
|
92
|
+
pfns = [pfns] if isinstance(pfns, str) else pfns
|
|
93
|
+
|
|
94
|
+
for pfn in pfns:
|
|
95
|
+
parsed = urlparse(pfn)
|
|
96
|
+
scheme = parsed.scheme
|
|
97
|
+
hostname = parsed.netloc.partition(':')[0]
|
|
98
|
+
port = int(parsed.netloc.partition(':')[2]) if parsed.netloc.partition(':')[2] != '' else 0
|
|
99
|
+
while '//' in parsed.path:
|
|
100
|
+
parsed = parsed._replace(path=parsed.path.replace('//', '/'))
|
|
101
|
+
path = parsed.path
|
|
102
|
+
|
|
103
|
+
# Protect against 'lazy' defined prefixes for RSEs in the repository
|
|
104
|
+
if not self.attributes['prefix'].startswith('/'):
|
|
105
|
+
self.attributes['prefix'] = '/' + self.attributes['prefix']
|
|
106
|
+
if not self.attributes['prefix'].endswith('/'):
|
|
107
|
+
self.attributes['prefix'] += '/'
|
|
108
|
+
|
|
109
|
+
if self.attributes['hostname'] != hostname:
|
|
110
|
+
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
|
|
111
|
+
raise exception.RSEFileNameNotSupported('Invalid hostname: provided \'%s\', expected \'%s\'' % (hostname, self.attributes['hostname']))
|
|
112
|
+
|
|
113
|
+
if self.attributes['port'] != port:
|
|
114
|
+
raise exception.RSEFileNameNotSupported('Invalid port: provided \'%s\', expected \'%s\'' % (port, self.attributes['port']))
|
|
115
|
+
|
|
116
|
+
if not path.startswith(self.attributes['prefix']):
|
|
117
|
+
raise exception.RSEFileNameNotSupported('Invalid prefix: provided \'%s\', expected \'%s\'' % ('/'.join(path.split('/')[0:len(self.attributes['prefix'].split('/')) - 1]),
|
|
118
|
+
self.attributes['prefix'])) # len(...)-1 due to the leading '/
|
|
119
|
+
|
|
120
|
+
# Splitting parsed.path into prefix, path, filename
|
|
121
|
+
prefix = self.attributes['prefix']
|
|
122
|
+
path = path.partition(self.attributes['prefix'])[2]
|
|
123
|
+
name = path.split('/')[-1]
|
|
124
|
+
path = '/'.join(path.split('/')[:-1])
|
|
125
|
+
if not path.startswith('/'):
|
|
126
|
+
path = '/' + path
|
|
127
|
+
if path != '/' and not path.endswith('/'):
|
|
128
|
+
path = path + '/'
|
|
129
|
+
ret[pfn] = {'path': path, 'name': name, 'scheme': scheme, 'prefix': prefix, 'port': port, 'hostname': hostname, }
|
|
130
|
+
|
|
131
|
+
return ret
|
|
132
|
+
|
|
133
|
+
def exists(self, path):
|
|
134
|
+
"""
|
|
135
|
+
Checks if the requested file is known by the referred RSE.
|
|
136
|
+
|
|
137
|
+
:param path: Physical file name
|
|
138
|
+
|
|
139
|
+
:returns: True if the file exists, False if it doesn't
|
|
140
|
+
|
|
141
|
+
:raises SourceNotFound: if the source file was not found on the referred storage.
|
|
142
|
+
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
filepath = '/'.join(path.split('/')[0:-1]) + '/'
|
|
146
|
+
filename = path.split('/')[-1]
|
|
147
|
+
|
|
148
|
+
transfer_client = get_transfer_client()
|
|
149
|
+
exists = False
|
|
150
|
+
|
|
151
|
+
if self.globus_endpoint_id:
|
|
152
|
+
try:
|
|
153
|
+
resp = transfer_client.operation_ls(endpoint_id=self.globus_endpoint_id, path=filepath)
|
|
154
|
+
exists = len([r for r in resp if r['name'] == filename]) > 0
|
|
155
|
+
except TransferAPIError as err:
|
|
156
|
+
print(err)
|
|
157
|
+
else:
|
|
158
|
+
print('No rse attribute found for globus endpoint id.')
|
|
159
|
+
|
|
160
|
+
return exists
|
|
161
|
+
|
|
162
|
+
def list(self, path):
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
Checks if the requested path is known by the referred RSE and returns a list of items
|
|
166
|
+
|
|
167
|
+
:param path: Physical file name
|
|
168
|
+
|
|
169
|
+
:returns: List of items
|
|
170
|
+
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
transfer_client = get_transfer_client()
|
|
174
|
+
items = []
|
|
175
|
+
|
|
176
|
+
if self.globus_endpoint_id:
|
|
177
|
+
try:
|
|
178
|
+
resp = transfer_client.operation_ls(endpoint_id=self.globus_endpoint_id, path=path)
|
|
179
|
+
items = resp['DATA']
|
|
180
|
+
except TransferAPIError as err:
|
|
181
|
+
print(err)
|
|
182
|
+
else:
|
|
183
|
+
print('No rse attribute found for globus endpoint id.')
|
|
184
|
+
|
|
185
|
+
return items
|
|
186
|
+
|
|
187
|
+
def delete(self, path):
|
|
188
|
+
"""
|
|
189
|
+
Deletes a file from the connected RSE.
|
|
190
|
+
|
|
191
|
+
:param path: path to the to be deleted file
|
|
192
|
+
|
|
193
|
+
:raises ServiceUnavailable: if some generic error occurred in the library.
|
|
194
|
+
:raises SourceNotFound: if the source file was not found on the referred storage.
|
|
195
|
+
"""
|
|
196
|
+
if self.globus_endpoint_id:
|
|
197
|
+
try:
|
|
198
|
+
delete_response = send_delete_task(endpoint_id=self.globus_endpoint_id, path=path, logger=self.logger)
|
|
199
|
+
except TransferAPIError as err:
|
|
200
|
+
self.logger(logging.WARNING, str(err))
|
|
201
|
+
raise exception.RucioException(err)
|
|
202
|
+
else:
|
|
203
|
+
raise exception.RucioException('No rse attribute found for globus endpoint id.')
|
|
204
|
+
|
|
205
|
+
if delete_response['code'] != 'Accepted':
|
|
206
|
+
self.logger(logging.DEBUG, 'delete_response: %s' % delete_response)
|
|
207
|
+
raise exception.RucioException('delete_task not accepted by Globus')
|
|
208
|
+
|
|
209
|
+
def bulk_delete(self, pfns):
|
|
210
|
+
"""
|
|
211
|
+
Submits an async task to bulk delete files on globus endpoint.
|
|
212
|
+
|
|
213
|
+
:param pfns: list of pfns to delete
|
|
214
|
+
|
|
215
|
+
:raises TransferAPIError: if unexpected response from the service.
|
|
216
|
+
"""
|
|
217
|
+
if self.globus_endpoint_id:
|
|
218
|
+
try:
|
|
219
|
+
bulk_delete_response = send_bulk_delete_task(endpoint_id=self.globus_endpoint_id, pfns=pfns, logger=self.logger)
|
|
220
|
+
except TransferAPIError as err:
|
|
221
|
+
raise exception.RucioException(err)
|
|
222
|
+
else:
|
|
223
|
+
raise exception.RucioException('No rse attribute found for globus endpoint id.')
|
|
224
|
+
|
|
225
|
+
if bulk_delete_response['code'] != 'Accepted':
|
|
226
|
+
self.logger(logging.DEBUG, 'delete_response: %s' % bulk_delete_response)
|
|
227
|
+
raise exception.RucioException('delete_task not accepted by Globus')
|
|
228
|
+
|
|
229
|
+
def connect(self):
|
|
230
|
+
"""
|
|
231
|
+
Establishes the actual connection to the referred RSE.
|
|
232
|
+
|
|
233
|
+
reaper2 daemon requires implementation of protocol.connect
|
|
234
|
+
"""
|
|
235
|
+
pass
|
|
236
|
+
|
|
237
|
+
def close(self):
|
|
238
|
+
"""
|
|
239
|
+
Closes the connection to RSE.
|
|
240
|
+
|
|
241
|
+
reaper2 daemon requires implementation of protocol.close
|
|
242
|
+
"""
|
|
243
|
+
pass
|
|
@@ -0,0 +1,92 @@
|
|
|
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 json
|
|
16
|
+
import os
|
|
17
|
+
|
|
18
|
+
import requests
|
|
19
|
+
|
|
20
|
+
from rucio.common import exception
|
|
21
|
+
from rucio.rse.protocols import protocol
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Default(protocol.RSEProtocol):
|
|
25
|
+
""" Implementing access to RSEs using gsiftp."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, protocol_attr, rse_settings, logger=None):
|
|
28
|
+
""" Initializes the object with information about the referred RSE.
|
|
29
|
+
|
|
30
|
+
:param props: Properties derived from the RSE Repository
|
|
31
|
+
"""
|
|
32
|
+
super(Default, self).__init__(protocol_attr, rse_settings, logger=logger)
|
|
33
|
+
|
|
34
|
+
def connect(self):
|
|
35
|
+
"""
|
|
36
|
+
Establishes the actual connection to the referred RSE.
|
|
37
|
+
If we decide to use gfal, init should be done here.
|
|
38
|
+
|
|
39
|
+
:raises RSEAccessDenied
|
|
40
|
+
"""
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
def close(self):
|
|
44
|
+
"""
|
|
45
|
+
Closes the connection to RSE.
|
|
46
|
+
"""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
def get_space_usage(self):
|
|
50
|
+
"""
|
|
51
|
+
Get RSE space usage information.
|
|
52
|
+
|
|
53
|
+
:returns: a list with dict containing 'totalsize' and 'unusedsize'
|
|
54
|
+
|
|
55
|
+
:raises ServiceUnavailable: if some generic error occurred in the library.
|
|
56
|
+
"""
|
|
57
|
+
rse_name = self.rse['rse']
|
|
58
|
+
dest = '/tmp/rucio-gsiftp-site-size_' + rse_name
|
|
59
|
+
space_usage_url = ''
|
|
60
|
+
# url of space usage json, would be nicer to have it in rse_settings
|
|
61
|
+
agis = requests.get('http://atlas-agis-api.cern.ch/request/ddmendpoint/query/list/?json').json()
|
|
62
|
+
agis_token = ''
|
|
63
|
+
for res in agis:
|
|
64
|
+
if rse_name == res['name']:
|
|
65
|
+
agis_token = res['token']
|
|
66
|
+
space_usage_url = res['space_usage_url']
|
|
67
|
+
|
|
68
|
+
import gfal2 # pylint: disable=import-error
|
|
69
|
+
gfal2.set_verbose(gfal2.verbose_level.normal) # pylint: disable=no-member
|
|
70
|
+
try:
|
|
71
|
+
if os.path.exists(dest):
|
|
72
|
+
os.remove(dest)
|
|
73
|
+
ctx = gfal2.creat_context() # pylint: disable=no-member
|
|
74
|
+
ctx.set_opt_string_list("SRM PLUGIN", "TURL_PROTOCOLS", ["gsiftp", "rfio", "gsidcap", "dcap", "kdcap"])
|
|
75
|
+
params = ctx.transfer_parameters()
|
|
76
|
+
params.timeout = 3600
|
|
77
|
+
ret = ctx.filecopy(params, str(space_usage_url), str('file://' + dest))
|
|
78
|
+
|
|
79
|
+
if ret == 0:
|
|
80
|
+
data_file = open(dest)
|
|
81
|
+
data = json.load(data_file)
|
|
82
|
+
data_file.close()
|
|
83
|
+
if agis_token not in list(data.keys()):
|
|
84
|
+
print('ERROR: space usage json has different token as key')
|
|
85
|
+
else:
|
|
86
|
+
totalsize = int(data[agis_token]['total_space'])
|
|
87
|
+
used = int(data[agis_token]['used_space'])
|
|
88
|
+
unusedsize = totalsize - used
|
|
89
|
+
return totalsize, unusedsize
|
|
90
|
+
except Exception as error:
|
|
91
|
+
print(error)
|
|
92
|
+
raise exception.ServiceUnavailable(error)
|
|
@@ -0,0 +1,82 @@
|
|
|
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
|
+
from rucio.rse.protocols import ngarc
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Default(ngarc.Default):
|
|
19
|
+
|
|
20
|
+
""" Implementing access to RSEs using the ngarc protocol."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, protocol_attr, rse_settings, logger=None):
|
|
23
|
+
""" Initializes the object with information about the referred RSE.
|
|
24
|
+
|
|
25
|
+
:param props: Properties derived from the RSE Repository
|
|
26
|
+
"""
|
|
27
|
+
super(Default, self).__init__(protocol_attr, rse_settings, logger=logger)
|
|
28
|
+
self.attributes.pop('determinism_type', None)
|
|
29
|
+
self.files = []
|
|
30
|
+
|
|
31
|
+
def _get_path(self, scope, name):
|
|
32
|
+
""" Transforms the physical file name into the local URI in the referred RSE.
|
|
33
|
+
Suitable for sites implementoing the RUCIO naming convention.
|
|
34
|
+
|
|
35
|
+
:param name: filename
|
|
36
|
+
:param scope: scope
|
|
37
|
+
|
|
38
|
+
:returns: RSE specific URI of the physical file
|
|
39
|
+
"""
|
|
40
|
+
return '%s/%s' % (scope, name)
|
|
41
|
+
|
|
42
|
+
def path2pfn(self, path):
|
|
43
|
+
"""
|
|
44
|
+
Returns a fully qualified PFN for the file referred by path.
|
|
45
|
+
|
|
46
|
+
:param path: The path to the file.
|
|
47
|
+
|
|
48
|
+
:returns: Fully qualified PFN.
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
return ''.join([self.attributes['scheme'], '://%s' % self.attributes['hostname'], path])
|
|
52
|
+
|
|
53
|
+
def put(self, source, target, source_dir=None, transfer_timeout=None):
|
|
54
|
+
""" Allows to store files inside the referred RSE.
|
|
55
|
+
|
|
56
|
+
:param source: Physical file name
|
|
57
|
+
:param target: Name of the file on the storage system e.g. with prefixed scope
|
|
58
|
+
:param source_dir Path where the to be transferred files are stored in the local file system
|
|
59
|
+
:param transfer_timeout Transfer timeout (in seconds)
|
|
60
|
+
|
|
61
|
+
:raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound
|
|
62
|
+
"""
|
|
63
|
+
raise NotImplementedError
|
|
64
|
+
|
|
65
|
+
def delete(self, pfn):
|
|
66
|
+
""" Deletes a file from the connected RSE.
|
|
67
|
+
|
|
68
|
+
:param pfn: Physical file name
|
|
69
|
+
|
|
70
|
+
:raises ServiceUnavailable, SourceNotFound
|
|
71
|
+
"""
|
|
72
|
+
raise NotImplementedError
|
|
73
|
+
|
|
74
|
+
def rename(self, pfn, new_pfn):
|
|
75
|
+
""" Allows to rename a file stored inside the connected RSE.
|
|
76
|
+
|
|
77
|
+
:param pfn: Current physical file name
|
|
78
|
+
:param new_pfn New physical file name
|
|
79
|
+
|
|
80
|
+
:raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound
|
|
81
|
+
"""
|
|
82
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,123 @@
|
|
|
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
|
+
from rucio.common import exception
|
|
16
|
+
from rucio.rse.protocols import protocol
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Default(protocol.RSEProtocol):
|
|
20
|
+
""" Implementing access to RSEs using the local filesystem."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, protocol_attr, rse_settings, logger=None):
|
|
23
|
+
""" Initializes the object with information about the referred RSE.
|
|
24
|
+
|
|
25
|
+
:param props: Properties derived from the RSE Repository
|
|
26
|
+
"""
|
|
27
|
+
super(Default, self).__init__(protocol_attr, rse_settings, logger=logger)
|
|
28
|
+
self.attributes.pop('determinism_type', None)
|
|
29
|
+
self.files = []
|
|
30
|
+
|
|
31
|
+
def path2pfn(self, path):
|
|
32
|
+
"""
|
|
33
|
+
Returns a fully qualified PFN for the file referred by path.
|
|
34
|
+
|
|
35
|
+
:param path: The path to the file.
|
|
36
|
+
|
|
37
|
+
:returns: Fully qualified PFN.
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
return ''.join([self.rse['scheme'], '://%s' % self.rse['hostname'], path])
|
|
41
|
+
|
|
42
|
+
def exists(self, pfn):
|
|
43
|
+
""" Checks if the requested file is known by the referred RSE.
|
|
44
|
+
|
|
45
|
+
:param pfn: Physical file name
|
|
46
|
+
|
|
47
|
+
:returns: True if the file exists, False if it doesn't
|
|
48
|
+
|
|
49
|
+
:raise ServiceUnavailable
|
|
50
|
+
"""
|
|
51
|
+
return pfn in self.files
|
|
52
|
+
|
|
53
|
+
def connect(self):
|
|
54
|
+
""" Establishes the actual connection to the referred RSE.
|
|
55
|
+
|
|
56
|
+
:param credentials: Provide all necessary information to establish a connection
|
|
57
|
+
to the referred storage system. Some is loaded from the repository inside the
|
|
58
|
+
RSE class and some must be provided specific for the SFTP protocol like
|
|
59
|
+
username, password, private_key, private_key_pass, port.
|
|
60
|
+
For details about possible additional parameters and details about their usage
|
|
61
|
+
see the pysftp.Connection() documentation.
|
|
62
|
+
NOTE: the host parametrer is overwritten with the value provided by the repository
|
|
63
|
+
|
|
64
|
+
:raise RSEAccessDenied
|
|
65
|
+
"""
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
def close(self):
|
|
69
|
+
""" Closes the connection to RSE."""
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
def get(self, pfn, dest, transfer_timeout=None):
|
|
73
|
+
""" Provides access to files stored inside connected the RSE.
|
|
74
|
+
|
|
75
|
+
:param pfn: Physical file name of requested file
|
|
76
|
+
:param dest: Name and path of the files when stored at the client
|
|
77
|
+
:param transfer_timeout Transfer timeout (in seconds) - dummy
|
|
78
|
+
|
|
79
|
+
:raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound
|
|
80
|
+
"""
|
|
81
|
+
if pfn not in self.files:
|
|
82
|
+
raise exception.SourceNotFound(pfn)
|
|
83
|
+
|
|
84
|
+
def put(self, source, target, source_dir=None, transfer_timeout=None):
|
|
85
|
+
""" Allows to store files inside the referred RSE.
|
|
86
|
+
|
|
87
|
+
:param source: Physical file name
|
|
88
|
+
:param target: Name of the file on the storage system e.g. with prefixed scope
|
|
89
|
+
:param source_dir Path where the to be transferred files are stored in the local file system
|
|
90
|
+
:param transfer_timeout Transfer timeout (in seconds) - dummy
|
|
91
|
+
|
|
92
|
+
:raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound
|
|
93
|
+
"""
|
|
94
|
+
self.files.append(target)
|
|
95
|
+
|
|
96
|
+
def delete(self, pfn):
|
|
97
|
+
""" Deletes a file from the connected RSE.
|
|
98
|
+
|
|
99
|
+
:param pfn: Physical file name
|
|
100
|
+
|
|
101
|
+
:raises ServiceUnavailable, SourceNotFound
|
|
102
|
+
"""
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
def bulk_delete(self, pfns):
|
|
106
|
+
"""
|
|
107
|
+
Submits an async task to bulk delete files.
|
|
108
|
+
|
|
109
|
+
:param pfns: list of pfns to delete
|
|
110
|
+
|
|
111
|
+
:raises TransferAPIError: if unexpected response from the service.
|
|
112
|
+
"""
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
def rename(self, pfn, new_pfn):
|
|
116
|
+
""" Allows to rename a file stored inside the connected RSE.
|
|
117
|
+
|
|
118
|
+
:param pfn: Current physical file name
|
|
119
|
+
:param new_pfn New physical file name
|
|
120
|
+
|
|
121
|
+
:raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound
|
|
122
|
+
"""
|
|
123
|
+
pass
|