swift 2.23.3__py3-none-any.whl → 2.35.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.
Files changed (206) hide show
  1. swift/__init__.py +29 -50
  2. swift/account/auditor.py +21 -118
  3. swift/account/backend.py +33 -28
  4. swift/account/reaper.py +37 -28
  5. swift/account/replicator.py +22 -0
  6. swift/account/server.py +60 -26
  7. swift/account/utils.py +28 -11
  8. swift-2.23.3.data/scripts/swift-account-audit → swift/cli/account_audit.py +23 -13
  9. swift-2.23.3.data/scripts/swift-config → swift/cli/config.py +2 -2
  10. swift/cli/container_deleter.py +5 -11
  11. swift-2.23.3.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +8 -7
  12. swift/cli/dispersion_report.py +10 -9
  13. swift-2.23.3.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +63 -21
  14. swift/cli/form_signature.py +3 -7
  15. swift-2.23.3.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +8 -2
  16. swift/cli/info.py +154 -14
  17. swift/cli/manage_shard_ranges.py +705 -37
  18. swift-2.23.3.data/scripts/swift-oldies → swift/cli/oldies.py +25 -14
  19. swift-2.23.3.data/scripts/swift-orphans → swift/cli/orphans.py +7 -3
  20. swift/cli/recon.py +196 -67
  21. swift-2.23.3.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +17 -20
  22. swift-2.23.3.data/scripts/swift-reconciler-enqueue → swift/cli/reconciler_enqueue.py +2 -3
  23. swift/cli/relinker.py +807 -126
  24. swift/cli/reload.py +135 -0
  25. swift/cli/ringbuilder.py +217 -20
  26. swift/cli/ringcomposer.py +0 -1
  27. swift/cli/shard-info.py +4 -3
  28. swift/common/base_storage_server.py +9 -20
  29. swift/common/bufferedhttp.py +48 -74
  30. swift/common/constraints.py +20 -15
  31. swift/common/container_sync_realms.py +9 -11
  32. swift/common/daemon.py +25 -8
  33. swift/common/db.py +195 -128
  34. swift/common/db_auditor.py +168 -0
  35. swift/common/db_replicator.py +95 -55
  36. swift/common/digest.py +141 -0
  37. swift/common/direct_client.py +144 -33
  38. swift/common/error_limiter.py +93 -0
  39. swift/common/exceptions.py +25 -1
  40. swift/common/header_key_dict.py +2 -9
  41. swift/common/http_protocol.py +373 -0
  42. swift/common/internal_client.py +129 -59
  43. swift/common/linkat.py +3 -4
  44. swift/common/manager.py +284 -67
  45. swift/common/memcached.py +390 -145
  46. swift/common/middleware/__init__.py +4 -0
  47. swift/common/middleware/account_quotas.py +211 -46
  48. swift/common/middleware/acl.py +3 -8
  49. swift/common/middleware/backend_ratelimit.py +230 -0
  50. swift/common/middleware/bulk.py +22 -34
  51. swift/common/middleware/catch_errors.py +1 -3
  52. swift/common/middleware/cname_lookup.py +6 -11
  53. swift/common/middleware/container_quotas.py +1 -1
  54. swift/common/middleware/container_sync.py +39 -17
  55. swift/common/middleware/copy.py +12 -0
  56. swift/common/middleware/crossdomain.py +22 -9
  57. swift/common/middleware/crypto/__init__.py +2 -1
  58. swift/common/middleware/crypto/crypto_utils.py +11 -15
  59. swift/common/middleware/crypto/decrypter.py +28 -11
  60. swift/common/middleware/crypto/encrypter.py +12 -17
  61. swift/common/middleware/crypto/keymaster.py +8 -15
  62. swift/common/middleware/crypto/kms_keymaster.py +2 -1
  63. swift/common/middleware/dlo.py +15 -11
  64. swift/common/middleware/domain_remap.py +5 -4
  65. swift/common/middleware/etag_quoter.py +128 -0
  66. swift/common/middleware/formpost.py +73 -70
  67. swift/common/middleware/gatekeeper.py +8 -1
  68. swift/common/middleware/keystoneauth.py +33 -3
  69. swift/common/middleware/list_endpoints.py +4 -4
  70. swift/common/middleware/listing_formats.py +85 -49
  71. swift/common/middleware/memcache.py +4 -95
  72. swift/common/middleware/name_check.py +3 -2
  73. swift/common/middleware/proxy_logging.py +160 -92
  74. swift/common/middleware/ratelimit.py +17 -10
  75. swift/common/middleware/read_only.py +6 -4
  76. swift/common/middleware/recon.py +59 -22
  77. swift/common/middleware/s3api/acl_handlers.py +25 -3
  78. swift/common/middleware/s3api/acl_utils.py +6 -1
  79. swift/common/middleware/s3api/controllers/__init__.py +6 -0
  80. swift/common/middleware/s3api/controllers/acl.py +3 -2
  81. swift/common/middleware/s3api/controllers/bucket.py +242 -137
  82. swift/common/middleware/s3api/controllers/logging.py +2 -2
  83. swift/common/middleware/s3api/controllers/multi_delete.py +43 -20
  84. swift/common/middleware/s3api/controllers/multi_upload.py +219 -133
  85. swift/common/middleware/s3api/controllers/obj.py +112 -8
  86. swift/common/middleware/s3api/controllers/object_lock.py +44 -0
  87. swift/common/middleware/s3api/controllers/s3_acl.py +2 -2
  88. swift/common/middleware/s3api/controllers/tagging.py +57 -0
  89. swift/common/middleware/s3api/controllers/versioning.py +36 -7
  90. swift/common/middleware/s3api/etree.py +22 -9
  91. swift/common/middleware/s3api/exception.py +0 -4
  92. swift/common/middleware/s3api/s3api.py +113 -41
  93. swift/common/middleware/s3api/s3request.py +384 -218
  94. swift/common/middleware/s3api/s3response.py +126 -23
  95. swift/common/middleware/s3api/s3token.py +16 -17
  96. swift/common/middleware/s3api/schema/delete.rng +1 -1
  97. swift/common/middleware/s3api/subresource.py +7 -10
  98. swift/common/middleware/s3api/utils.py +27 -10
  99. swift/common/middleware/slo.py +665 -358
  100. swift/common/middleware/staticweb.py +64 -37
  101. swift/common/middleware/symlink.py +51 -18
  102. swift/common/middleware/tempauth.py +76 -58
  103. swift/common/middleware/tempurl.py +191 -173
  104. swift/common/middleware/versioned_writes/__init__.py +51 -0
  105. swift/common/middleware/{versioned_writes.py → versioned_writes/legacy.py} +27 -26
  106. swift/common/middleware/versioned_writes/object_versioning.py +1482 -0
  107. swift/common/middleware/x_profile/exceptions.py +1 -4
  108. swift/common/middleware/x_profile/html_viewer.py +18 -19
  109. swift/common/middleware/x_profile/profile_model.py +1 -2
  110. swift/common/middleware/xprofile.py +10 -10
  111. swift-2.23.3.data/scripts/swift-container-server → swift/common/recon.py +13 -8
  112. swift/common/registry.py +147 -0
  113. swift/common/request_helpers.py +324 -57
  114. swift/common/ring/builder.py +67 -25
  115. swift/common/ring/composite_builder.py +1 -1
  116. swift/common/ring/ring.py +177 -51
  117. swift/common/ring/utils.py +1 -1
  118. swift/common/splice.py +10 -6
  119. swift/common/statsd_client.py +205 -0
  120. swift/common/storage_policy.py +49 -44
  121. swift/common/swob.py +86 -102
  122. swift/common/{utils.py → utils/__init__.py} +2163 -2772
  123. swift/common/utils/base.py +131 -0
  124. swift/common/utils/config.py +433 -0
  125. swift/common/utils/ipaddrs.py +256 -0
  126. swift/common/utils/libc.py +345 -0
  127. swift/common/utils/logs.py +859 -0
  128. swift/common/utils/timestamp.py +412 -0
  129. swift/common/wsgi.py +553 -535
  130. swift/container/auditor.py +14 -100
  131. swift/container/backend.py +490 -231
  132. swift/container/reconciler.py +126 -37
  133. swift/container/replicator.py +96 -22
  134. swift/container/server.py +358 -165
  135. swift/container/sharder.py +1540 -684
  136. swift/container/sync.py +94 -88
  137. swift/container/updater.py +53 -32
  138. swift/obj/auditor.py +153 -35
  139. swift/obj/diskfile.py +466 -217
  140. swift/obj/expirer.py +406 -124
  141. swift/obj/mem_diskfile.py +7 -4
  142. swift/obj/mem_server.py +1 -0
  143. swift/obj/reconstructor.py +523 -262
  144. swift/obj/replicator.py +249 -188
  145. swift/obj/server.py +207 -122
  146. swift/obj/ssync_receiver.py +145 -85
  147. swift/obj/ssync_sender.py +113 -54
  148. swift/obj/updater.py +652 -139
  149. swift/obj/watchers/__init__.py +0 -0
  150. swift/obj/watchers/dark_data.py +213 -0
  151. swift/proxy/controllers/account.py +11 -11
  152. swift/proxy/controllers/base.py +848 -604
  153. swift/proxy/controllers/container.py +433 -92
  154. swift/proxy/controllers/info.py +3 -2
  155. swift/proxy/controllers/obj.py +1000 -489
  156. swift/proxy/server.py +185 -112
  157. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/AUTHORS +58 -11
  158. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/METADATA +51 -56
  159. swift-2.35.0.dist-info/RECORD +201 -0
  160. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/WHEEL +1 -1
  161. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/entry_points.txt +43 -0
  162. swift-2.35.0.dist-info/pbr.json +1 -0
  163. swift/locale/de/LC_MESSAGES/swift.po +0 -1216
  164. swift/locale/en_GB/LC_MESSAGES/swift.po +0 -1207
  165. swift/locale/es/LC_MESSAGES/swift.po +0 -1085
  166. swift/locale/fr/LC_MESSAGES/swift.po +0 -909
  167. swift/locale/it/LC_MESSAGES/swift.po +0 -894
  168. swift/locale/ja/LC_MESSAGES/swift.po +0 -965
  169. swift/locale/ko_KR/LC_MESSAGES/swift.po +0 -964
  170. swift/locale/pt_BR/LC_MESSAGES/swift.po +0 -881
  171. swift/locale/ru/LC_MESSAGES/swift.po +0 -891
  172. swift/locale/tr_TR/LC_MESSAGES/swift.po +0 -832
  173. swift/locale/zh_CN/LC_MESSAGES/swift.po +0 -833
  174. swift/locale/zh_TW/LC_MESSAGES/swift.po +0 -838
  175. swift-2.23.3.data/scripts/swift-account-auditor +0 -23
  176. swift-2.23.3.data/scripts/swift-account-info +0 -51
  177. swift-2.23.3.data/scripts/swift-account-reaper +0 -23
  178. swift-2.23.3.data/scripts/swift-account-replicator +0 -34
  179. swift-2.23.3.data/scripts/swift-account-server +0 -23
  180. swift-2.23.3.data/scripts/swift-container-auditor +0 -23
  181. swift-2.23.3.data/scripts/swift-container-info +0 -55
  182. swift-2.23.3.data/scripts/swift-container-reconciler +0 -21
  183. swift-2.23.3.data/scripts/swift-container-replicator +0 -34
  184. swift-2.23.3.data/scripts/swift-container-sharder +0 -37
  185. swift-2.23.3.data/scripts/swift-container-sync +0 -23
  186. swift-2.23.3.data/scripts/swift-container-updater +0 -23
  187. swift-2.23.3.data/scripts/swift-dispersion-report +0 -24
  188. swift-2.23.3.data/scripts/swift-form-signature +0 -20
  189. swift-2.23.3.data/scripts/swift-init +0 -119
  190. swift-2.23.3.data/scripts/swift-object-auditor +0 -29
  191. swift-2.23.3.data/scripts/swift-object-expirer +0 -33
  192. swift-2.23.3.data/scripts/swift-object-info +0 -60
  193. swift-2.23.3.data/scripts/swift-object-reconstructor +0 -33
  194. swift-2.23.3.data/scripts/swift-object-relinker +0 -41
  195. swift-2.23.3.data/scripts/swift-object-replicator +0 -37
  196. swift-2.23.3.data/scripts/swift-object-server +0 -27
  197. swift-2.23.3.data/scripts/swift-object-updater +0 -23
  198. swift-2.23.3.data/scripts/swift-proxy-server +0 -23
  199. swift-2.23.3.data/scripts/swift-recon +0 -24
  200. swift-2.23.3.data/scripts/swift-ring-builder +0 -24
  201. swift-2.23.3.data/scripts/swift-ring-builder-analyzer +0 -22
  202. swift-2.23.3.data/scripts/swift-ring-composer +0 -22
  203. swift-2.23.3.dist-info/RECORD +0 -220
  204. swift-2.23.3.dist-info/pbr.json +0 -1
  205. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/LICENSE +0 -0
  206. {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,131 @@
1
+ # Copyright (c) 2010-2024 OpenStack Foundation
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
12
+ # implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """
17
+ Miscellaneous utility functions that may be used in other utils modules.
18
+
19
+ This module is imported by other utils modules.
20
+ This module should not import from other utils modules.
21
+ """
22
+
23
+ import codecs
24
+ import hashlib
25
+
26
+ from urllib.parse import quote as _quote
27
+
28
+
29
+ try:
30
+ _test_md5 = hashlib.md5(usedforsecurity=False) # nosec
31
+
32
+ def md5(string=b'', usedforsecurity=True):
33
+ """Return an md5 hashlib object using usedforsecurity parameter
34
+
35
+ For python distributions that support the usedforsecurity keyword
36
+ parameter, this passes the parameter through as expected.
37
+ See https://bugs.python.org/issue9216
38
+ """
39
+ return hashlib.md5(string, usedforsecurity=usedforsecurity) # nosec
40
+ except TypeError:
41
+ def md5(string=b'', usedforsecurity=True):
42
+ """Return an md5 hashlib object without usedforsecurity parameter
43
+
44
+ For python distributions that do not yet support this keyword
45
+ parameter, we drop the parameter
46
+ """
47
+ return hashlib.md5(string) # nosec
48
+
49
+
50
+ utf8_decoder = codecs.getdecoder('utf-8')
51
+ utf8_encoder = codecs.getencoder('utf-8')
52
+ # Apparently under py3 we need to go to utf-16 to collapse surrogates?
53
+ utf16_decoder = codecs.getdecoder('utf-16')
54
+ utf16_encoder = codecs.getencoder('utf-16')
55
+
56
+
57
+ def get_valid_utf8_str(str_or_unicode):
58
+ """
59
+ Get valid parts of utf-8 str from str, unicode and even invalid utf-8 str
60
+
61
+ :param str_or_unicode: a string or an unicode which can be invalid utf-8
62
+ """
63
+ if isinstance(str_or_unicode, bytes):
64
+ try:
65
+ (str_or_unicode, _len) = utf8_decoder(str_or_unicode,
66
+ 'surrogatepass')
67
+ except UnicodeDecodeError:
68
+ (str_or_unicode, _len) = utf8_decoder(str_or_unicode,
69
+ 'replace')
70
+ (str_or_unicode, _len) = utf16_encoder(str_or_unicode, 'surrogatepass')
71
+ (valid_unicode_str, _len) = utf16_decoder(str_or_unicode, 'replace')
72
+ return valid_unicode_str.encode('utf-8')
73
+
74
+
75
+ def quote(value, safe='/'):
76
+ """
77
+ Patched version of urllib.quote that encodes utf-8 strings before quoting
78
+ """
79
+ quoted = _quote(get_valid_utf8_str(value), safe)
80
+ if isinstance(value, bytes):
81
+ quoted = quoted.encode('utf-8')
82
+ return quoted
83
+
84
+
85
+ def split_path(path, minsegs=1, maxsegs=None, rest_with_last=False):
86
+ """
87
+ Validate and split the given HTTP request path.
88
+
89
+ **Examples**::
90
+
91
+ ['a'] = split_path('/a')
92
+ ['a', None] = split_path('/a', 1, 2)
93
+ ['a', 'c'] = split_path('/a/c', 1, 2)
94
+ ['a', 'c', 'o/r'] = split_path('/a/c/o/r', 1, 3, True)
95
+
96
+ :param path: HTTP Request path to be split
97
+ :param minsegs: Minimum number of segments to be extracted
98
+ :param maxsegs: Maximum number of segments to be extracted
99
+ :param rest_with_last: If True, trailing data will be returned as part
100
+ of last segment. If False, and there is
101
+ trailing data, raises ValueError.
102
+ :returns: list of segments with a length of maxsegs (non-existent
103
+ segments will return as None)
104
+ :raises ValueError: if given an invalid path
105
+ """
106
+ if not maxsegs:
107
+ maxsegs = minsegs
108
+ if minsegs > maxsegs:
109
+ raise ValueError('minsegs > maxsegs: %d > %d' % (minsegs, maxsegs))
110
+ if rest_with_last:
111
+ segs = path.split('/', maxsegs)
112
+ minsegs += 1
113
+ maxsegs += 1
114
+ count = len(segs)
115
+ if (segs[0] or count < minsegs or count > maxsegs or
116
+ '' in segs[1:minsegs]):
117
+ raise ValueError('Invalid path: %s' % quote(path))
118
+ else:
119
+ minsegs += 1
120
+ maxsegs += 1
121
+ segs = path.split('/', maxsegs)
122
+ count = len(segs)
123
+ if (segs[0] or count < minsegs or count > maxsegs + 1 or
124
+ '' in segs[1:minsegs] or
125
+ (count == maxsegs + 1 and segs[maxsegs])):
126
+ raise ValueError('Invalid path: %s' % quote(path))
127
+ segs = segs[1:maxsegs]
128
+ if not all(segs[:-1]):
129
+ raise ValueError('Invalid path: %s' % quote(path))
130
+ segs.extend([None] * (maxsegs - 1 - len(segs)))
131
+ return segs
@@ -0,0 +1,433 @@
1
+ # Copyright (c) 2010-2012 OpenStack Foundation
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
12
+ # implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ import os
17
+ import operator
18
+ import re
19
+ import configparser
20
+ from configparser import ConfigParser, RawConfigParser
21
+
22
+ # Used when reading config values
23
+ TRUE_VALUES = {'true', '1', 'yes', 'on', 't', 'y'}
24
+
25
+
26
+ def config_true_value(value):
27
+ """
28
+ Returns True if the value is either True or a string in TRUE_VALUES.
29
+ Returns False otherwise.
30
+ """
31
+ return value is True or \
32
+ (isinstance(value, str) and value.lower() in TRUE_VALUES)
33
+
34
+
35
+ def _non_negative_number(value, expected_type_f=float,
36
+ expected_type_description='float number'):
37
+ try:
38
+ value = expected_type_f(value)
39
+ if value < 0:
40
+ raise ValueError
41
+ except (TypeError, ValueError):
42
+ raise ValueError('Value must be a non-negative %s, not "%s".'
43
+ % (expected_type_description, value))
44
+ return value
45
+
46
+
47
+ def non_negative_float(value):
48
+ """
49
+ Check that the value casts to a float and is non-negative.
50
+
51
+ :param value: value to check
52
+ :raises ValueError: if the value cannot be cast to a float or is negative.
53
+ :return: a float
54
+ """
55
+ return _non_negative_number(value)
56
+
57
+
58
+ def non_negative_int(value):
59
+ """
60
+ Check that the value casts to an int and is a whole number.
61
+
62
+ :param value: value to check
63
+ :raises ValueError: if the value cannot be cast to an int or does not
64
+ represent a whole number.
65
+ :return: an int
66
+ """
67
+ return _non_negative_number(value, expected_type_f=int,
68
+ expected_type_description='integer')
69
+
70
+
71
+ def config_positive_int_value(value):
72
+ """
73
+ Returns positive int value if it can be cast by int() and it's an
74
+ integer > 0. (not including zero) Raises ValueError otherwise.
75
+ """
76
+ try:
77
+ result = int(value)
78
+ if result < 1:
79
+ raise ValueError()
80
+ except (TypeError, ValueError):
81
+ raise ValueError(
82
+ 'Config option must be an positive int number, not "%s".' % value)
83
+ return result
84
+
85
+
86
+ def config_float_value(value, minimum=None, maximum=None):
87
+ try:
88
+ val = float(value)
89
+ if minimum is not None and val < minimum:
90
+ raise ValueError()
91
+ if maximum is not None and val > maximum:
92
+ raise ValueError()
93
+ return val
94
+ except (TypeError, ValueError):
95
+ min_ = ', greater than %s' % minimum if minimum is not None else ''
96
+ max_ = ', less than %s' % maximum if maximum is not None else ''
97
+ raise ValueError('Config option must be a number%s%s, not "%s".' %
98
+ (min_, max_, value))
99
+
100
+
101
+ def config_auto_int_value(value, default):
102
+ """
103
+ Returns default if value is None or 'auto'.
104
+ Returns value as an int or raises ValueError otherwise.
105
+ """
106
+ if value is None or \
107
+ (isinstance(value, str) and value.lower() == 'auto'):
108
+ return default
109
+ try:
110
+ value = int(value)
111
+ except (TypeError, ValueError):
112
+ raise ValueError('Config option must be an integer or the '
113
+ 'string "auto", not "%s".' % value)
114
+ return value
115
+
116
+
117
+ def config_percent_value(value):
118
+ try:
119
+ return config_float_value(value, 0, 100) / 100.0
120
+ except ValueError as err:
121
+ raise ValueError("%s: %s" % (str(err), value))
122
+
123
+
124
+ def config_request_node_count_value(value):
125
+ try:
126
+ value_parts = value.lower().split()
127
+ rnc_value = int(value_parts[0])
128
+ except (ValueError, AttributeError):
129
+ pass
130
+ else:
131
+ if len(value_parts) == 1:
132
+ return lambda replicas: rnc_value
133
+ elif (len(value_parts) == 3 and
134
+ value_parts[1] == '*' and
135
+ value_parts[2] == 'replicas'):
136
+ return lambda replicas: rnc_value * replicas
137
+ raise ValueError(
138
+ 'Invalid request_node_count value: %r' % value)
139
+
140
+
141
+ def config_fallocate_value(reserve_value):
142
+ """
143
+ Returns fallocate reserve_value as an int or float.
144
+ Returns is_percent as a boolean.
145
+ Returns a ValueError on invalid fallocate value.
146
+ """
147
+ try:
148
+ if str(reserve_value[-1:]) == '%':
149
+ reserve_value = float(reserve_value[:-1])
150
+ is_percent = True
151
+ else:
152
+ reserve_value = int(reserve_value)
153
+ is_percent = False
154
+ except ValueError:
155
+ raise ValueError('Error: %s is an invalid value for fallocate'
156
+ '_reserve.' % reserve_value)
157
+ return reserve_value, is_percent
158
+
159
+
160
+ def config_read_prefixed_options(conf, prefix_name, defaults):
161
+ """
162
+ Read prefixed options from configuration
163
+
164
+ :param conf: the configuration
165
+ :param prefix_name: the prefix (including, if needed, an underscore)
166
+ :param defaults: a dict of default values. The dict supplies the
167
+ option name and type (string or comma separated string)
168
+ :return: a dict containing the options
169
+ """
170
+ params = {}
171
+ for option_name in defaults.keys():
172
+ value = conf.get('%s%s' % (prefix_name, option_name))
173
+ if value:
174
+ if isinstance(defaults.get(option_name), list):
175
+ params[option_name] = []
176
+ for role in value.lower().split(','):
177
+ params[option_name].append(role.strip())
178
+ else:
179
+ params[option_name] = value.strip()
180
+ return params
181
+
182
+
183
+ def append_underscore(prefix):
184
+ if prefix and not prefix.endswith('_'):
185
+ prefix += '_'
186
+ return prefix
187
+
188
+
189
+ def config_read_reseller_options(conf, defaults):
190
+ """
191
+ Read reseller_prefix option and associated options from configuration
192
+
193
+ Reads the reseller_prefix option, then reads options that may be
194
+ associated with a specific reseller prefix. Reads options such that an
195
+ option without a prefix applies to all reseller prefixes unless an option
196
+ has an explicit prefix.
197
+
198
+ :param conf: the configuration
199
+ :param defaults: a dict of default values. The key is the option
200
+ name. The value is either an array of strings or a string
201
+ :return: tuple of an array of reseller prefixes and a dict of option values
202
+ """
203
+ reseller_prefix_opt = conf.get('reseller_prefix', 'AUTH').split(',')
204
+ reseller_prefixes = []
205
+ for prefix in [pre.strip() for pre in reseller_prefix_opt if pre.strip()]:
206
+ if prefix == "''":
207
+ prefix = ''
208
+ prefix = append_underscore(prefix)
209
+ if prefix not in reseller_prefixes:
210
+ reseller_prefixes.append(prefix)
211
+ if len(reseller_prefixes) == 0:
212
+ reseller_prefixes.append('')
213
+
214
+ # Get prefix-using config options
215
+ associated_options = {}
216
+ for prefix in reseller_prefixes:
217
+ associated_options[prefix] = dict(defaults)
218
+ associated_options[prefix].update(
219
+ config_read_prefixed_options(conf, '', defaults))
220
+ prefix_name = prefix if prefix != '' else "''"
221
+ associated_options[prefix].update(
222
+ config_read_prefixed_options(conf, prefix_name, defaults))
223
+ return reseller_prefixes, associated_options
224
+
225
+
226
+ def affinity_key_function(affinity_str):
227
+ """Turns an affinity config value into a function suitable for passing to
228
+ sort(). After doing so, the array will be sorted with respect to the given
229
+ ordering.
230
+
231
+ For example, if affinity_str is "r1=1, r2z7=2, r2z8=2", then the array
232
+ will be sorted with all nodes from region 1 (r1=1) first, then all the
233
+ nodes from region 2 zones 7 and 8 (r2z7=2 and r2z8=2), then everything
234
+ else.
235
+
236
+ Note that the order of the pieces of affinity_str is irrelevant; the
237
+ priority values are what comes after the equals sign.
238
+
239
+ If affinity_str is empty or all whitespace, then the resulting function
240
+ will not alter the ordering of the nodes.
241
+
242
+ :param affinity_str: affinity config value, e.g. "r1z2=3"
243
+ or "r1=1, r2z1=2, r2z2=2"
244
+ :returns: single-argument function
245
+ :raises ValueError: if argument invalid
246
+ """
247
+ affinity_str = affinity_str.strip()
248
+
249
+ if not affinity_str:
250
+ return lambda x: 0
251
+
252
+ priority_matchers = []
253
+ pieces = [s.strip() for s in affinity_str.split(',')]
254
+ for piece in pieces:
255
+ # matches r<number>=<number> or r<number>z<number>=<number>
256
+ match = re.match(r"r(\d+)(?:z(\d+))?=(\d+)$", piece)
257
+ if match:
258
+ region, zone, priority = match.groups()
259
+ region = int(region)
260
+ priority = int(priority)
261
+ zone = int(zone) if zone else None
262
+
263
+ matcher = {'region': region, 'priority': priority}
264
+ if zone is not None:
265
+ matcher['zone'] = zone
266
+ priority_matchers.append(matcher)
267
+ else:
268
+ raise ValueError("Invalid affinity value: %r" % affinity_str)
269
+
270
+ priority_matchers.sort(key=operator.itemgetter('priority'))
271
+
272
+ def keyfn(ring_node):
273
+ for matcher in priority_matchers:
274
+ if (matcher['region'] == ring_node['region']
275
+ and ('zone' not in matcher
276
+ or matcher['zone'] == ring_node['zone'])):
277
+ return matcher['priority']
278
+ return 4294967296 # 2^32, i.e. "a big number"
279
+ return keyfn
280
+
281
+
282
+ def affinity_locality_predicate(write_affinity_str):
283
+ """
284
+ Turns a write-affinity config value into a predicate function for nodes.
285
+ The returned value will be a 1-arg function that takes a node dictionary
286
+ and returns a true value if it is "local" and a false value otherwise. The
287
+ definition of "local" comes from the affinity_str argument passed in here.
288
+
289
+ For example, if affinity_str is "r1, r2z2", then only nodes where region=1
290
+ or where (region=2 and zone=2) are considered local.
291
+
292
+ If affinity_str is empty or all whitespace, then the resulting function
293
+ will consider everything local
294
+
295
+ :param write_affinity_str: affinity config value, e.g. "r1z2"
296
+ or "r1, r2z1, r2z2"
297
+ :returns: single-argument function, or None if affinity_str is empty
298
+ :raises ValueError: if argument invalid
299
+ """
300
+ affinity_str = write_affinity_str.strip()
301
+
302
+ if not affinity_str:
303
+ return None
304
+
305
+ matchers = []
306
+ pieces = [s.strip() for s in affinity_str.split(',')]
307
+ for piece in pieces:
308
+ # matches r<number> or r<number>z<number>
309
+ match = re.match(r"r(\d+)(?:z(\d+))?$", piece)
310
+ if match:
311
+ region, zone = match.groups()
312
+ region = int(region)
313
+ zone = int(zone) if zone else None
314
+
315
+ matcher = {'region': region}
316
+ if zone is not None:
317
+ matcher['zone'] = zone
318
+ matchers.append(matcher)
319
+ else:
320
+ raise ValueError("Invalid write-affinity value: %r" % affinity_str)
321
+
322
+ def is_local(ring_node):
323
+ for matcher in matchers:
324
+ if (matcher['region'] == ring_node['region']
325
+ and ('zone' not in matcher
326
+ or matcher['zone'] == ring_node['zone'])):
327
+ return True
328
+ return False
329
+ return is_local
330
+
331
+
332
+ def read_conf_dir(parser, conf_dir):
333
+ conf_files = []
334
+ for f in os.listdir(conf_dir):
335
+ if f.endswith('.conf') and not f.startswith('.'):
336
+ conf_files.append(os.path.join(conf_dir, f))
337
+ return parser.read(sorted(conf_files))
338
+
339
+
340
+ class NicerInterpolation(configparser.BasicInterpolation):
341
+ def before_get(self, parser, section, option, value, defaults):
342
+ if '%(' not in value:
343
+ return value
344
+ return super(NicerInterpolation, self).before_get(
345
+ parser, section, option, value, defaults)
346
+
347
+
348
+ def readconf(conf_path, section_name=None, log_name=None, defaults=None,
349
+ raw=False):
350
+ """
351
+ Read config file(s) and return config items as a dict
352
+
353
+ :param conf_path: path to config file/directory, or a file-like object
354
+ (hasattr readline)
355
+ :param section_name: config section to read (will return all sections if
356
+ not defined)
357
+ :param log_name: name to be used with logging (will use section_name if
358
+ not defined)
359
+ :param defaults: dict of default values to pre-populate the config with
360
+ :returns: dict of config items
361
+ :raises ValueError: if section_name does not exist
362
+ :raises IOError: if reading the file failed
363
+ """
364
+ if defaults is None:
365
+ defaults = {}
366
+ if raw:
367
+ c = RawConfigParser(defaults)
368
+ else:
369
+ # In general, we haven't really thought much about interpolation
370
+ # in configs. Python's default ConfigParser has always supported
371
+ # it, though, so *we* got it "for free". Unfortunatley, since we
372
+ # "supported" interpolation, we have to assume there are
373
+ # deployments in the wild that use it, and try not to break them.
374
+ # So, do what we can to mimic the py2 behavior of passing through
375
+ # values like "1%" (which we want to support for
376
+ # fallocate_reserve).
377
+ c = ConfigParser(defaults, interpolation=NicerInterpolation())
378
+ c.optionxform = str # Don't lower-case keys
379
+
380
+ if hasattr(conf_path, 'readline'):
381
+ if hasattr(conf_path, 'seek'):
382
+ conf_path.seek(0)
383
+ c.read_file(conf_path)
384
+ else:
385
+ if os.path.isdir(conf_path):
386
+ # read all configs in directory
387
+ success = read_conf_dir(c, conf_path)
388
+ else:
389
+ success = c.read(conf_path)
390
+ if not success:
391
+ raise IOError("Unable to read config from %s" %
392
+ conf_path)
393
+ if section_name:
394
+ if c.has_section(section_name):
395
+ conf = dict(c.items(section_name))
396
+ else:
397
+ raise ValueError(
398
+ "Unable to find %(section)s config section in %(conf)s" %
399
+ {'section': section_name, 'conf': conf_path})
400
+ if "log_name" not in conf:
401
+ if log_name is not None:
402
+ conf['log_name'] = log_name
403
+ else:
404
+ conf['log_name'] = section_name
405
+ else:
406
+ conf = {}
407
+ for s in c.sections():
408
+ conf.update({s: dict(c.items(s))})
409
+ if 'log_name' not in conf:
410
+ conf['log_name'] = log_name
411
+ conf['__file__'] = conf_path
412
+ return conf
413
+
414
+
415
+ def parse_prefixed_conf(conf_file, prefix):
416
+ """
417
+ Search the config file for any common-prefix sections and load those
418
+ sections to a dict mapping the after-prefix reference to options.
419
+
420
+ :param conf_file: the file name of the config to parse
421
+ :param prefix: the common prefix of the sections
422
+ :return: a dict mapping policy reference -> dict of policy options
423
+ :raises ValueError: if a policy config section has an invalid name
424
+ """
425
+
426
+ ret_config = {}
427
+ all_conf = readconf(conf_file)
428
+ for section, options in all_conf.items():
429
+ if not section.startswith(prefix):
430
+ continue
431
+ target_ref = section[len(prefix):]
432
+ ret_config[target_ref] = options
433
+ return ret_config