rucio-clients 35.7.0__py3-none-any.whl → 37.0.0rc2__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 (86) hide show
  1. rucio/alembicrevision.py +1 -1
  2. rucio/cli/__init__.py +14 -0
  3. rucio/cli/account.py +216 -0
  4. rucio/cli/bin_legacy/__init__.py +13 -0
  5. rucio_clients-35.7.0.data/scripts/rucio → rucio/cli/bin_legacy/rucio.py +769 -486
  6. rucio_clients-35.7.0.data/scripts/rucio-admin → rucio/cli/bin_legacy/rucio_admin.py +476 -423
  7. rucio/cli/command.py +272 -0
  8. rucio/cli/config.py +72 -0
  9. rucio/cli/did.py +191 -0
  10. rucio/cli/download.py +128 -0
  11. rucio/cli/lifetime_exception.py +33 -0
  12. rucio/cli/replica.py +162 -0
  13. rucio/cli/rse.py +293 -0
  14. rucio/cli/rule.py +158 -0
  15. rucio/cli/scope.py +40 -0
  16. rucio/cli/subscription.py +73 -0
  17. rucio/cli/upload.py +60 -0
  18. rucio/cli/utils.py +226 -0
  19. rucio/client/accountclient.py +0 -1
  20. rucio/client/baseclient.py +33 -24
  21. rucio/client/client.py +45 -1
  22. rucio/client/didclient.py +5 -3
  23. rucio/client/downloadclient.py +6 -8
  24. rucio/client/replicaclient.py +0 -2
  25. rucio/client/richclient.py +317 -0
  26. rucio/client/rseclient.py +4 -4
  27. rucio/client/uploadclient.py +26 -12
  28. rucio/common/bittorrent.py +234 -0
  29. rucio/common/cache.py +66 -29
  30. rucio/common/checksum.py +168 -0
  31. rucio/common/client.py +122 -0
  32. rucio/common/config.py +22 -35
  33. rucio/common/constants.py +61 -3
  34. rucio/common/didtype.py +72 -24
  35. rucio/common/exception.py +65 -8
  36. rucio/common/extra.py +5 -10
  37. rucio/common/logging.py +13 -13
  38. rucio/common/pcache.py +8 -7
  39. rucio/common/plugins.py +59 -27
  40. rucio/common/policy.py +12 -3
  41. rucio/common/schema/__init__.py +84 -34
  42. rucio/common/schema/generic.py +0 -17
  43. rucio/common/schema/generic_multi_vo.py +0 -17
  44. rucio/common/stomp_utils.py +383 -119
  45. rucio/common/test_rucio_server.py +12 -6
  46. rucio/common/types.py +132 -52
  47. rucio/common/utils.py +93 -643
  48. rucio/rse/__init__.py +3 -3
  49. rucio/rse/protocols/bittorrent.py +11 -1
  50. rucio/rse/protocols/cache.py +0 -11
  51. rucio/rse/protocols/dummy.py +0 -11
  52. rucio/rse/protocols/gfal.py +14 -9
  53. rucio/rse/protocols/globus.py +1 -1
  54. rucio/rse/protocols/http_cache.py +1 -1
  55. rucio/rse/protocols/posix.py +2 -2
  56. rucio/rse/protocols/protocol.py +84 -317
  57. rucio/rse/protocols/rclone.py +2 -1
  58. rucio/rse/protocols/rfio.py +10 -1
  59. rucio/rse/protocols/ssh.py +2 -1
  60. rucio/rse/protocols/storm.py +2 -13
  61. rucio/rse/protocols/webdav.py +74 -30
  62. rucio/rse/protocols/xrootd.py +2 -1
  63. rucio/rse/rsemanager.py +170 -53
  64. rucio/rse/translation.py +260 -0
  65. rucio/vcsversion.py +4 -4
  66. rucio/version.py +7 -0
  67. {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/etc/rucio.cfg.atlas.client.template +3 -2
  68. {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/etc/rucio.cfg.template +3 -19
  69. {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/requirements.client.txt +11 -7
  70. rucio_clients-37.0.0rc2.data/scripts/rucio +133 -0
  71. rucio_clients-37.0.0rc2.data/scripts/rucio-admin +97 -0
  72. {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/METADATA +18 -14
  73. rucio_clients-37.0.0rc2.dist-info/RECORD +104 -0
  74. {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/licenses/AUTHORS.rst +3 -0
  75. rucio/common/schema/atlas.py +0 -413
  76. rucio/common/schema/belleii.py +0 -408
  77. rucio/common/schema/domatpc.py +0 -401
  78. rucio/common/schema/escape.py +0 -426
  79. rucio/common/schema/icecube.py +0 -406
  80. rucio/rse/protocols/gsiftp.py +0 -92
  81. rucio_clients-35.7.0.dist-info/RECORD +0 -88
  82. {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/etc/rse-accounts.cfg.template +0 -0
  83. {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/rucio_client/merge_rucio_configs.py +0 -0
  84. {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/WHEEL +0 -0
  85. {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/licenses/LICENSE +0 -0
  86. {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,234 @@
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
+ import copy
15
+ import hashlib
16
+ import itertools
17
+ import math
18
+ import os
19
+ import time
20
+ from typing import TYPE_CHECKING, Any, Optional, Union
21
+
22
+ from rucio.common.exception import RucioException
23
+
24
+ if TYPE_CHECKING:
25
+ from _typeshed import FileDescriptorOrPath
26
+
27
+
28
+ def _next_pow2(num: int) -> int:
29
+ if not num:
30
+ return 0
31
+ return math.ceil(math.log2(num))
32
+
33
+
34
+ def _bittorrent_v2_piece_length_pow2(file_size: int) -> int:
35
+ """
36
+ Automatically chooses the `piece size` so that `piece layers`
37
+ is kept small(er) than usually. This is a balancing act:
38
+ having a big piece_length requires more work on bittorrent client
39
+ side to validate hashes, but having it small requires more
40
+ place to store the `piece layers` in the database.
41
+
42
+ Returns the result as the exponent 'x' for power of 2.
43
+ To get the actual length in bytes, the caller should compute 2^x.
44
+ """
45
+
46
+ # by the bittorrent v2 specification, the piece size is equal to block size = 16KiB
47
+ min_piece_len_pow2 = 14 # 2 ** 14 == 16 KiB
48
+ if not file_size:
49
+ return min_piece_len_pow2
50
+ # Limit the maximum size of pieces_layers hash chain for bittorrent v2,
51
+ # because we'll have to store it in the database
52
+ max_pieces_layers_size_pow2 = 20 # 2 ** 20 == 1 MiB
53
+ # sha256 requires 2 ** 5 == 32 Bytes == 256 bits
54
+ hash_size_pow2 = 5
55
+
56
+ # The closest power of two bigger than the file size
57
+ file_size_pow2 = _next_pow2(file_size)
58
+
59
+ # Compute the target size for the 'pieces layers' in the torrent
60
+ # (as power of two: the closest power-of-two smaller than the number)
61
+ # Will cap at max_pieces_layers_size for files larger than 1TB.
62
+ target_pieces_layers_size = math.sqrt(file_size)
63
+ target_pieces_layers_size_pow2 = min(math.floor(math.log2(target_pieces_layers_size)), max_pieces_layers_size_pow2)
64
+ target_piece_num_pow2 = max(target_pieces_layers_size_pow2 - hash_size_pow2, 0)
65
+
66
+ piece_length_pow2 = max(file_size_pow2 - target_piece_num_pow2, min_piece_len_pow2)
67
+ return piece_length_pow2
68
+
69
+
70
+ def bittorrent_v2_piece_length(file_size: int) -> int:
71
+ return 2 ** _bittorrent_v2_piece_length_pow2(file_size)
72
+
73
+
74
+ def bittorrent_v2_merkle_sha256(file: "FileDescriptorOrPath") -> tuple[bytes, bytes, int]:
75
+ """
76
+ Compute the .torrent v2 hash tree for the given file.
77
+ (http://www.bittorrent.org/beps/bep_0052.html)
78
+ In particular, it will return the root of the merkle hash
79
+ tree of the file, the 'piece layers' as described in the
80
+ previous BEP, and the chosen `piece size`
81
+
82
+ This function will read the file in chunks of 16KiB
83
+ (which is the imposed block size by bittorrent v2) and compute
84
+ the sha256 hash of each block. When enough blocks are read
85
+ to form a `piece`, will compute the merkle hash root of the
86
+ piece from the hashes of its blocks. At the end, the hashes
87
+ of pieces are combined to create the global pieces_root.
88
+ """
89
+
90
+ # by the bittorrent v2 specification, the block size and the
91
+ # minimum piece size are both fixed to 16KiB
92
+ block_size = 16384
93
+ block_size_pow2 = 14 # 2 ** 14 == 16 KiB
94
+ # sha256 requires 2 ** 5 == 32 Bytes == 256 bits
95
+ hash_size = 32
96
+
97
+ def _merkle_root(leafs: list[bytes], nb_levels: int, padding: bytes) -> bytes:
98
+ """
99
+ Build the root of the merkle hash tree from the (possibly incomplete) leafs layer.
100
+ If len(leafs) < 2 ** nb_levels, it will be padded with the padding repeated as many times
101
+ as needed to have 2 ** nb_levels leafs in total.
102
+ """
103
+ nodes = copy.copy(leafs)
104
+ level = nb_levels
105
+
106
+ while level > 0:
107
+ for i in range(2 ** (level - 1)):
108
+ node1 = nodes[2 * i] if 2 * i < len(nodes) else padding
109
+ node2 = nodes[2 * i + 1] if 2 * i + 1 < len(nodes) else padding
110
+ h = hashlib.sha256(node1)
111
+ h.update(node2)
112
+ if i < len(nodes):
113
+ nodes[i] = h.digest()
114
+ else:
115
+ nodes.append(h.digest())
116
+ level -= 1
117
+ return nodes[0] if nodes else padding
118
+
119
+ file_size = os.stat(file).st_size
120
+ piece_length_pow2 = _bittorrent_v2_piece_length_pow2(file_size)
121
+
122
+ block_per_piece_pow2 = piece_length_pow2 - block_size_pow2
123
+ piece_length = 2 ** piece_length_pow2
124
+ block_per_piece = 2 ** block_per_piece_pow2
125
+ piece_num = math.ceil(file_size / piece_length)
126
+
127
+ remaining = file_size
128
+ remaining_in_block = min(file_size, block_size)
129
+ block_hashes = []
130
+ piece_hashes = []
131
+ current_hash = hashlib.sha256()
132
+ block_padding = bytes(hash_size)
133
+ with open(file, 'rb') as f:
134
+ while True:
135
+ data = f.read(remaining_in_block)
136
+ if not data:
137
+ break
138
+
139
+ current_hash.update(data)
140
+
141
+ remaining_in_block -= len(data)
142
+ remaining -= len(data)
143
+
144
+ if not remaining_in_block:
145
+ block_hashes.append(current_hash.digest())
146
+ if len(block_hashes) == block_per_piece or not remaining:
147
+ piece_hashes.append(_merkle_root(block_hashes, nb_levels=block_per_piece_pow2, padding=block_padding))
148
+ block_hashes = []
149
+ current_hash = hashlib.sha256()
150
+ remaining_in_block = min(block_size, remaining)
151
+
152
+ if not remaining:
153
+ break
154
+
155
+ if remaining or remaining_in_block or len(piece_hashes) != piece_num:
156
+ raise RucioException(f'Error while computing merkle sha256 of {file}')
157
+
158
+ piece_padding = _merkle_root([], nb_levels=block_per_piece_pow2, padding=block_padding)
159
+ pieces_root = _merkle_root(piece_hashes, nb_levels=_next_pow2(piece_num), padding=piece_padding)
160
+ pieces_layers = b''.join(piece_hashes) if len(piece_hashes) > 1 else b''
161
+
162
+ return pieces_root, pieces_layers, piece_length
163
+
164
+
165
+ def bencode(obj: Union[int, bytes, str, list, dict[bytes, Any]]) -> bytes:
166
+ """
167
+ Copied from the reference implementation of v2 bittorrent:
168
+ http://bittorrent.org/beps/bep_0052_torrent_creator.py
169
+ """
170
+
171
+ if isinstance(obj, int):
172
+ return b"i" + str(obj).encode() + b"e"
173
+ elif isinstance(obj, bytes):
174
+ return str(len(obj)).encode() + b":" + obj
175
+ elif isinstance(obj, str):
176
+ return bencode(obj.encode("utf-8"))
177
+ elif isinstance(obj, list):
178
+ return b"l" + b"".join(map(bencode, obj)) + b"e"
179
+ elif isinstance(obj, dict):
180
+ if all(isinstance(i, bytes) for i in obj.keys()):
181
+ items = list(obj.items())
182
+ items.sort()
183
+ return b"d" + b"".join(map(bencode, itertools.chain(*items))) + b"e"
184
+ else:
185
+ raise ValueError("dict keys should be bytes " + str(obj.keys()))
186
+ raise ValueError("Allowed types: int, bytes, str, list, dict; not %s", type(obj))
187
+
188
+
189
+ def construct_torrent(
190
+ scope: str,
191
+ name: str,
192
+ length: int,
193
+ piece_length: int,
194
+ pieces_root: bytes,
195
+ pieces_layers: "Optional[bytes]" = None,
196
+ trackers: "Optional[list[str]]" = None,
197
+ ) -> "tuple[str, bytes]":
198
+
199
+ torrent_dict = {
200
+ b'creation date': int(time.time()),
201
+ b'info': {
202
+ b'meta version': 2,
203
+ b'private': 1,
204
+ b'name': f'{scope}:{name}'.encode(),
205
+ b'piece length': piece_length,
206
+ b'file tree': {
207
+ name.encode(): {
208
+ b'': {
209
+ b'length': length,
210
+ b'pieces root': pieces_root,
211
+ }
212
+ }
213
+ }
214
+ },
215
+ b'piece layers': {},
216
+ }
217
+ if trackers:
218
+ torrent_dict[b'announce'] = trackers[0].encode()
219
+ if len(trackers) > 1:
220
+ torrent_dict[b'announce-list'] = [t.encode() for t in trackers]
221
+ if pieces_layers:
222
+ torrent_dict[b'piece layers'][pieces_root] = pieces_layers
223
+
224
+ torrent_id = hashlib.sha256(bencode(torrent_dict[b'info'])).hexdigest()[:40]
225
+ torrent = bencode(torrent_dict)
226
+ return torrent_id, torrent
227
+
228
+
229
+ def merkle_sha256(file: "FileDescriptorOrPath") -> str:
230
+ """
231
+ The root of the sha256 merkle hash tree with leaf size of 16 KiB.
232
+ """
233
+ pieces_root, _, _ = bittorrent_v2_merkle_sha256(file)
234
+ return pieces_root.hex()
rucio/common/cache.py CHANGED
@@ -12,18 +12,16 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- from typing import TYPE_CHECKING
15
+ from typing import TYPE_CHECKING, Optional
16
16
 
17
- from dogpile.cache import make_region
17
+ from dogpile.cache.region import CacheRegion
18
18
 
19
+ from rucio.common.client import is_client
19
20
  from rucio.common.config import config_get
20
- from rucio.common.utils import is_client
21
21
 
22
22
  if TYPE_CHECKING:
23
23
  from collections.abc import Callable
24
- from typing import Optional
25
24
 
26
- from dogpile.cache.region import CacheRegion
27
25
 
28
26
  CACHE_URL = config_get('cache', 'url', False, '127.0.0.1:11211', check_config_table=False)
29
27
 
@@ -45,30 +43,69 @@ finally:
45
43
  _mc_client.close()
46
44
 
47
45
 
48
- def make_region_memcached(
49
- expiration_time: int,
50
- function_key_generator: "Optional[Callable]" = None,
51
- memcached_expire_time: "Optional[int]" = None
52
- ) -> "CacheRegion":
46
+ class MemcacheRegion(CacheRegion):
53
47
  """
54
- Make and configure a dogpile.cache.pymemcache region
48
+ Subclass of CacheRegion.
49
+ It uses pymemcache as backend if ENABLE_CACHING is True,
50
+ otherwise it it configured to null.
55
51
  """
56
- if function_key_generator:
57
- region = make_region(function_key_generator=function_key_generator)
58
- else:
59
- region = make_region()
60
-
61
- if ENABLE_CACHING:
62
- region.configure(
63
- 'dogpile.cache.pymemcache',
64
- expiration_time=expiration_time,
65
- arguments={
66
- 'url': CACHE_URL,
67
- 'distributed_lock': True,
68
- 'memcached_expire_time': memcached_expire_time if memcached_expire_time else expiration_time + 60, # must be bigger than expiration_time
69
- }
70
- )
71
- else:
72
- region.configure('dogpile.cache.null')
52
+ def __init__(
53
+ self,
54
+ expiration_time: int,
55
+ function_key_generator: Optional['Callable'] = None,
56
+ memcached_expire_time: Optional[int] = None
57
+ ):
58
+ if function_key_generator:
59
+ super().__init__(function_key_generator=function_key_generator)
60
+ else:
61
+ super().__init__()
62
+ self._configure_region(expiration_time, memcached_expire_time)
63
+
64
+ def _configure_region(
65
+ self,
66
+ expiration_time: int,
67
+ memcached_expire_time: Optional[int]
68
+ ) -> None:
69
+ if ENABLE_CACHING:
70
+ self.configure(
71
+ 'dogpile.cache.pymemcache',
72
+ expiration_time=expiration_time,
73
+ arguments={
74
+ 'url': CACHE_URL,
75
+ 'distributed_lock': True,
76
+ 'memcached_expire_time': memcached_expire_time if memcached_expire_time else expiration_time + 60, # must be bigger than expiration_time
77
+ }
78
+ )
79
+ else:
80
+ self.configure('dogpile.cache.null')
81
+
82
+
83
+ class CacheKey:
84
+ """
85
+ Helper class to generate cache keys
86
+ based on sections and options.
87
+ """
88
+
89
+ @staticmethod
90
+ def _generate_key(*args: str) -> str:
91
+ return '_'.join(args)
92
+
93
+ @staticmethod
94
+ def has_section(section: str) -> str:
95
+ return CacheKey._generate_key('has_section', section)
96
+
97
+ @staticmethod
98
+ def options(section: str) -> str:
99
+ return CacheKey._generate_key('options', section)
100
+
101
+ @staticmethod
102
+ def has_option(section: str, option: str) -> str:
103
+ return CacheKey._generate_key('has_option', section, option)
104
+
105
+ @staticmethod
106
+ def items(section: str) -> str:
107
+ return CacheKey._generate_key('items', section)
73
108
 
74
- return region
109
+ @staticmethod
110
+ def value(section: str, option: str) -> str:
111
+ return CacheKey._generate_key('get', section, option)
@@ -0,0 +1,168 @@
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
+ import hashlib
15
+ import io
16
+ import mmap
17
+ import zlib
18
+ from functools import partial
19
+ from typing import TYPE_CHECKING
20
+
21
+ from rucio.common.bittorrent import merkle_sha256
22
+ from rucio.common.exception import ChecksumCalculationError
23
+
24
+ if TYPE_CHECKING:
25
+ from _typeshed import FileDescriptorOrPath
26
+
27
+ # GLOBALLY_SUPPORTED_CHECKSUMS = ['adler32', 'md5', 'sha256', 'crc32']
28
+ GLOBALLY_SUPPORTED_CHECKSUMS = ['adler32', 'md5']
29
+ PREFERRED_CHECKSUM = GLOBALLY_SUPPORTED_CHECKSUMS[0]
30
+ CHECKSUM_KEY = 'supported_checksums'
31
+
32
+
33
+ def is_checksum_valid(checksum_name: str) -> bool:
34
+ """
35
+ A simple function to check whether a checksum algorithm is supported.
36
+ Relies on GLOBALLY_SUPPORTED_CHECKSUMS to allow for expandability.
37
+
38
+ :param checksum_name: The name of the checksum to be verified.
39
+ :returns: True if checksum_name is in GLOBALLY_SUPPORTED_CHECKSUMS list, False otherwise.
40
+ """
41
+
42
+ return checksum_name in GLOBALLY_SUPPORTED_CHECKSUMS
43
+
44
+
45
+ def set_preferred_checksum(checksum_name: str) -> None:
46
+ """
47
+ If the input checksum name is valid,
48
+ set it as PREFERRED_CHECKSUM.
49
+
50
+ :param checksum_name: The name of the checksum to be verified.
51
+ """
52
+ if is_checksum_valid(checksum_name):
53
+ global PREFERRED_CHECKSUM
54
+ PREFERRED_CHECKSUM = checksum_name
55
+
56
+
57
+ def _iter_blocks(fobj):
58
+ """Iterate over blocks in a binary file-like object.
59
+
60
+ Uses blocks of size ``io.DEFAULT_BUFFER_SIZE * 8``.
61
+ """
62
+ block_size = io.DEFAULT_BUFFER_SIZE * 8
63
+ return iter(partial(fobj.read, block_size), b'')
64
+
65
+
66
+ def adler32(file: "FileDescriptorOrPath") -> str:
67
+ """
68
+ An Adler-32 checksum is obtained by calculating two 16-bit checksums A and B
69
+ and concatenating their bits into a 32-bit integer. A is the sum of all bytes in the
70
+ stream plus one, and B is the sum of the individual values of A from each step.
71
+
72
+ :param file: file name
73
+ :returns: Hexified string, padded to 8 values.
74
+ """
75
+
76
+ # adler starting value is _not_ 0
77
+ adler = 1
78
+
79
+ can_mmap = False
80
+ # try:
81
+ # with open(file, 'r+b') as f:
82
+ # can_mmap = True
83
+ # except:
84
+ # pass
85
+
86
+ try:
87
+ # use mmap if possible
88
+ if can_mmap:
89
+ with open(file, 'r+b') as f:
90
+ m = mmap.mmap(f.fileno(), 0)
91
+ # partial block reads at slightly increased buffer sizes
92
+ for block in _iter_blocks(m):
93
+ adler = zlib.adler32(block, adler)
94
+ else:
95
+ with open(file, 'rb') as f:
96
+ # partial block reads at slightly increased buffer sizes
97
+ for block in _iter_blocks(f):
98
+ adler = zlib.adler32(block, adler)
99
+
100
+ except Exception as e:
101
+ raise ChecksumCalculationError('adler32', str(file), e)
102
+
103
+ # backflip on 32bit -- can be removed once everything is fully migrated to 64bit
104
+ if adler < 0:
105
+ adler = adler + 2 ** 32
106
+
107
+ return str('%08x' % adler)
108
+
109
+
110
+ def md5(file: "FileDescriptorOrPath") -> str:
111
+ """
112
+ Runs the MD5 algorithm (RFC-1321) on the binary content of the file named file and returns the hexadecimal digest
113
+
114
+ :param file: file name
115
+ :returns: string of 32 hexadecimal digits
116
+ """
117
+ hash_md5 = hashlib.md5()
118
+ try:
119
+ with open(file, "rb") as f:
120
+ for block in _iter_blocks(f):
121
+ hash_md5.update(block)
122
+ except Exception as e:
123
+ raise ChecksumCalculationError('md5', str(file), e)
124
+
125
+ return hash_md5.hexdigest()
126
+
127
+
128
+ def sha256(file: "FileDescriptorOrPath") -> str:
129
+ """
130
+ Runs the SHA256 algorithm on the binary content of the file named file and returns the hexadecimal digest
131
+
132
+ :param file: file name
133
+ :returns: string of 32 hexadecimal digits
134
+ """
135
+ checksum = hashlib.sha256()
136
+ try:
137
+ with open(file, "rb") as f:
138
+ for block in _iter_blocks(f):
139
+ checksum.update(block)
140
+ except Exception as e:
141
+ raise ChecksumCalculationError('sha256', str(file), e)
142
+ return checksum.hexdigest()
143
+
144
+
145
+ def crc32(file: "FileDescriptorOrPath") -> str:
146
+ """
147
+ Runs the CRC32 algorithm on the binary content of the file named file and returns the hexadecimal digest
148
+
149
+ :param file: file name
150
+ :returns: string of 32 hexadecimal digits
151
+ """
152
+ prev = 0
153
+ try:
154
+ with open(file, "rb") as f:
155
+ for block in _iter_blocks(f):
156
+ prev = zlib.crc32(block, prev)
157
+ except Exception as e:
158
+ raise ChecksumCalculationError('crc32', str(file), e)
159
+ return "%X" % (prev & 0xFFFFFFFF)
160
+
161
+
162
+ CHECKSUM_ALGO_DICT = {
163
+ 'adler32': adler32,
164
+ 'md5': md5,
165
+ 'sha256': sha256,
166
+ 'crc32': crc32,
167
+ 'merkle_sha256': merkle_sha256
168
+ }
rucio/common/client.py ADDED
@@ -0,0 +1,122 @@
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 os
16
+ import socket
17
+ from configparser import NoOptionError, NoSectionError
18
+ from typing import TYPE_CHECKING
19
+
20
+ from rucio.common.config import config_get, config_has_section
21
+ from rucio.common.exception import ConfigNotFound
22
+
23
+ if TYPE_CHECKING:
24
+ from rucio.common.types import IPWithLocationDict
25
+
26
+
27
+ def is_client() -> bool:
28
+ """"
29
+ Checks if the function is called from a client or from a server/daemon
30
+
31
+ :returns client_mode: True if is called from a client, False if it is called from a server/daemon
32
+ """
33
+ if 'RUCIO_CLIENT_MODE' not in os.environ:
34
+ try:
35
+ if config_has_section('database'):
36
+ client_mode = False
37
+ elif config_has_section('client'):
38
+ client_mode = True
39
+ else:
40
+ client_mode = False
41
+ except (RuntimeError, ConfigNotFound):
42
+ # If no configuration file is found the default value should be True
43
+ client_mode = True
44
+ else:
45
+ if os.environ['RUCIO_CLIENT_MODE']:
46
+ client_mode = True
47
+ else:
48
+ client_mode = False
49
+
50
+ return client_mode
51
+
52
+
53
+ def get_client_vo() -> str:
54
+ """
55
+ Get the client VO from the environment or the configuration file.
56
+
57
+ :returns vo: The client VO as a string; default = 'def'.
58
+ """
59
+ if 'RUCIO_VO' in os.environ:
60
+ vo = os.environ['RUCIO_VO']
61
+ else:
62
+ try:
63
+ vo = str(config_get('client', 'vo'))
64
+ except (NoOptionError, NoSectionError):
65
+ vo = 'def'
66
+ return vo
67
+
68
+
69
+ def detect_client_location() -> "IPWithLocationDict":
70
+ """
71
+ Normally client IP will be set on the server side (request.remote_addr)
72
+ Here setting ip on the one seen by the host itself. There is no connection
73
+ to Google DNS servers.
74
+ Try to determine the sitename automatically from common environment variables,
75
+ in this order: SITE_NAME, ATLAS_SITE_NAME, OSG_SITE_NAME. If none of these exist
76
+ use the fixed string 'ROAMING'.
77
+
78
+ If environment variables sets location, it uses it.
79
+ """
80
+
81
+ ip = None
82
+
83
+ try:
84
+ with socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) as s:
85
+ s.connect(("2001:4860:4860:0:0:0:0:8888", 80))
86
+ ip = s.getsockname()[0]
87
+ except Exception:
88
+ pass
89
+
90
+ if not ip:
91
+ try:
92
+ with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
93
+ s.connect(("8.8.8.8", 80))
94
+ ip = s.getsockname()[0]
95
+ except Exception:
96
+ pass
97
+
98
+ if not ip:
99
+ ip = '0.0.0.0' # noqa: S104
100
+
101
+ site = os.environ.get('SITE_NAME',
102
+ os.environ.get('ATLAS_SITE_NAME',
103
+ os.environ.get('OSG_SITE_NAME',
104
+ 'ROAMING')))
105
+
106
+ latitude = os.environ.get('RUCIO_LATITUDE')
107
+ longitude = os.environ.get('RUCIO_LONGITUDE')
108
+ if latitude and longitude:
109
+ try:
110
+ latitude = float(latitude)
111
+ longitude = float(longitude)
112
+ except ValueError:
113
+ latitude = longitude = 0
114
+ print('Client set latitude and longitude are not valid.')
115
+ else:
116
+ latitude = longitude = None
117
+
118
+ return {'ip': ip,
119
+ 'fqdn': socket.getfqdn(),
120
+ 'site': site,
121
+ 'latitude': latitude,
122
+ 'longitude': longitude}