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.
- swift/__init__.py +29 -50
- swift/account/auditor.py +21 -118
- swift/account/backend.py +33 -28
- swift/account/reaper.py +37 -28
- swift/account/replicator.py +22 -0
- swift/account/server.py +60 -26
- swift/account/utils.py +28 -11
- swift-2.23.3.data/scripts/swift-account-audit → swift/cli/account_audit.py +23 -13
- swift-2.23.3.data/scripts/swift-config → swift/cli/config.py +2 -2
- swift/cli/container_deleter.py +5 -11
- swift-2.23.3.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +8 -7
- swift/cli/dispersion_report.py +10 -9
- swift-2.23.3.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +63 -21
- swift/cli/form_signature.py +3 -7
- swift-2.23.3.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +8 -2
- swift/cli/info.py +154 -14
- swift/cli/manage_shard_ranges.py +705 -37
- swift-2.23.3.data/scripts/swift-oldies → swift/cli/oldies.py +25 -14
- swift-2.23.3.data/scripts/swift-orphans → swift/cli/orphans.py +7 -3
- swift/cli/recon.py +196 -67
- swift-2.23.3.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +17 -20
- swift-2.23.3.data/scripts/swift-reconciler-enqueue → swift/cli/reconciler_enqueue.py +2 -3
- swift/cli/relinker.py +807 -126
- swift/cli/reload.py +135 -0
- swift/cli/ringbuilder.py +217 -20
- swift/cli/ringcomposer.py +0 -1
- swift/cli/shard-info.py +4 -3
- swift/common/base_storage_server.py +9 -20
- swift/common/bufferedhttp.py +48 -74
- swift/common/constraints.py +20 -15
- swift/common/container_sync_realms.py +9 -11
- swift/common/daemon.py +25 -8
- swift/common/db.py +195 -128
- swift/common/db_auditor.py +168 -0
- swift/common/db_replicator.py +95 -55
- swift/common/digest.py +141 -0
- swift/common/direct_client.py +144 -33
- swift/common/error_limiter.py +93 -0
- swift/common/exceptions.py +25 -1
- swift/common/header_key_dict.py +2 -9
- swift/common/http_protocol.py +373 -0
- swift/common/internal_client.py +129 -59
- swift/common/linkat.py +3 -4
- swift/common/manager.py +284 -67
- swift/common/memcached.py +390 -145
- swift/common/middleware/__init__.py +4 -0
- swift/common/middleware/account_quotas.py +211 -46
- swift/common/middleware/acl.py +3 -8
- swift/common/middleware/backend_ratelimit.py +230 -0
- swift/common/middleware/bulk.py +22 -34
- swift/common/middleware/catch_errors.py +1 -3
- swift/common/middleware/cname_lookup.py +6 -11
- swift/common/middleware/container_quotas.py +1 -1
- swift/common/middleware/container_sync.py +39 -17
- swift/common/middleware/copy.py +12 -0
- swift/common/middleware/crossdomain.py +22 -9
- swift/common/middleware/crypto/__init__.py +2 -1
- swift/common/middleware/crypto/crypto_utils.py +11 -15
- swift/common/middleware/crypto/decrypter.py +28 -11
- swift/common/middleware/crypto/encrypter.py +12 -17
- swift/common/middleware/crypto/keymaster.py +8 -15
- swift/common/middleware/crypto/kms_keymaster.py +2 -1
- swift/common/middleware/dlo.py +15 -11
- swift/common/middleware/domain_remap.py +5 -4
- swift/common/middleware/etag_quoter.py +128 -0
- swift/common/middleware/formpost.py +73 -70
- swift/common/middleware/gatekeeper.py +8 -1
- swift/common/middleware/keystoneauth.py +33 -3
- swift/common/middleware/list_endpoints.py +4 -4
- swift/common/middleware/listing_formats.py +85 -49
- swift/common/middleware/memcache.py +4 -95
- swift/common/middleware/name_check.py +3 -2
- swift/common/middleware/proxy_logging.py +160 -92
- swift/common/middleware/ratelimit.py +17 -10
- swift/common/middleware/read_only.py +6 -4
- swift/common/middleware/recon.py +59 -22
- swift/common/middleware/s3api/acl_handlers.py +25 -3
- swift/common/middleware/s3api/acl_utils.py +6 -1
- swift/common/middleware/s3api/controllers/__init__.py +6 -0
- swift/common/middleware/s3api/controllers/acl.py +3 -2
- swift/common/middleware/s3api/controllers/bucket.py +242 -137
- swift/common/middleware/s3api/controllers/logging.py +2 -2
- swift/common/middleware/s3api/controllers/multi_delete.py +43 -20
- swift/common/middleware/s3api/controllers/multi_upload.py +219 -133
- swift/common/middleware/s3api/controllers/obj.py +112 -8
- swift/common/middleware/s3api/controllers/object_lock.py +44 -0
- swift/common/middleware/s3api/controllers/s3_acl.py +2 -2
- swift/common/middleware/s3api/controllers/tagging.py +57 -0
- swift/common/middleware/s3api/controllers/versioning.py +36 -7
- swift/common/middleware/s3api/etree.py +22 -9
- swift/common/middleware/s3api/exception.py +0 -4
- swift/common/middleware/s3api/s3api.py +113 -41
- swift/common/middleware/s3api/s3request.py +384 -218
- swift/common/middleware/s3api/s3response.py +126 -23
- swift/common/middleware/s3api/s3token.py +16 -17
- swift/common/middleware/s3api/schema/delete.rng +1 -1
- swift/common/middleware/s3api/subresource.py +7 -10
- swift/common/middleware/s3api/utils.py +27 -10
- swift/common/middleware/slo.py +665 -358
- swift/common/middleware/staticweb.py +64 -37
- swift/common/middleware/symlink.py +51 -18
- swift/common/middleware/tempauth.py +76 -58
- swift/common/middleware/tempurl.py +191 -173
- swift/common/middleware/versioned_writes/__init__.py +51 -0
- swift/common/middleware/{versioned_writes.py → versioned_writes/legacy.py} +27 -26
- swift/common/middleware/versioned_writes/object_versioning.py +1482 -0
- swift/common/middleware/x_profile/exceptions.py +1 -4
- swift/common/middleware/x_profile/html_viewer.py +18 -19
- swift/common/middleware/x_profile/profile_model.py +1 -2
- swift/common/middleware/xprofile.py +10 -10
- swift-2.23.3.data/scripts/swift-container-server → swift/common/recon.py +13 -8
- swift/common/registry.py +147 -0
- swift/common/request_helpers.py +324 -57
- swift/common/ring/builder.py +67 -25
- swift/common/ring/composite_builder.py +1 -1
- swift/common/ring/ring.py +177 -51
- swift/common/ring/utils.py +1 -1
- swift/common/splice.py +10 -6
- swift/common/statsd_client.py +205 -0
- swift/common/storage_policy.py +49 -44
- swift/common/swob.py +86 -102
- swift/common/{utils.py → utils/__init__.py} +2163 -2772
- swift/common/utils/base.py +131 -0
- swift/common/utils/config.py +433 -0
- swift/common/utils/ipaddrs.py +256 -0
- swift/common/utils/libc.py +345 -0
- swift/common/utils/logs.py +859 -0
- swift/common/utils/timestamp.py +412 -0
- swift/common/wsgi.py +553 -535
- swift/container/auditor.py +14 -100
- swift/container/backend.py +490 -231
- swift/container/reconciler.py +126 -37
- swift/container/replicator.py +96 -22
- swift/container/server.py +358 -165
- swift/container/sharder.py +1540 -684
- swift/container/sync.py +94 -88
- swift/container/updater.py +53 -32
- swift/obj/auditor.py +153 -35
- swift/obj/diskfile.py +466 -217
- swift/obj/expirer.py +406 -124
- swift/obj/mem_diskfile.py +7 -4
- swift/obj/mem_server.py +1 -0
- swift/obj/reconstructor.py +523 -262
- swift/obj/replicator.py +249 -188
- swift/obj/server.py +207 -122
- swift/obj/ssync_receiver.py +145 -85
- swift/obj/ssync_sender.py +113 -54
- swift/obj/updater.py +652 -139
- swift/obj/watchers/__init__.py +0 -0
- swift/obj/watchers/dark_data.py +213 -0
- swift/proxy/controllers/account.py +11 -11
- swift/proxy/controllers/base.py +848 -604
- swift/proxy/controllers/container.py +433 -92
- swift/proxy/controllers/info.py +3 -2
- swift/proxy/controllers/obj.py +1000 -489
- swift/proxy/server.py +185 -112
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/AUTHORS +58 -11
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/METADATA +51 -56
- swift-2.35.0.dist-info/RECORD +201 -0
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/WHEEL +1 -1
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/entry_points.txt +43 -0
- swift-2.35.0.dist-info/pbr.json +1 -0
- swift/locale/de/LC_MESSAGES/swift.po +0 -1216
- swift/locale/en_GB/LC_MESSAGES/swift.po +0 -1207
- swift/locale/es/LC_MESSAGES/swift.po +0 -1085
- swift/locale/fr/LC_MESSAGES/swift.po +0 -909
- swift/locale/it/LC_MESSAGES/swift.po +0 -894
- swift/locale/ja/LC_MESSAGES/swift.po +0 -965
- swift/locale/ko_KR/LC_MESSAGES/swift.po +0 -964
- swift/locale/pt_BR/LC_MESSAGES/swift.po +0 -881
- swift/locale/ru/LC_MESSAGES/swift.po +0 -891
- swift/locale/tr_TR/LC_MESSAGES/swift.po +0 -832
- swift/locale/zh_CN/LC_MESSAGES/swift.po +0 -833
- swift/locale/zh_TW/LC_MESSAGES/swift.po +0 -838
- swift-2.23.3.data/scripts/swift-account-auditor +0 -23
- swift-2.23.3.data/scripts/swift-account-info +0 -51
- swift-2.23.3.data/scripts/swift-account-reaper +0 -23
- swift-2.23.3.data/scripts/swift-account-replicator +0 -34
- swift-2.23.3.data/scripts/swift-account-server +0 -23
- swift-2.23.3.data/scripts/swift-container-auditor +0 -23
- swift-2.23.3.data/scripts/swift-container-info +0 -55
- swift-2.23.3.data/scripts/swift-container-reconciler +0 -21
- swift-2.23.3.data/scripts/swift-container-replicator +0 -34
- swift-2.23.3.data/scripts/swift-container-sharder +0 -37
- swift-2.23.3.data/scripts/swift-container-sync +0 -23
- swift-2.23.3.data/scripts/swift-container-updater +0 -23
- swift-2.23.3.data/scripts/swift-dispersion-report +0 -24
- swift-2.23.3.data/scripts/swift-form-signature +0 -20
- swift-2.23.3.data/scripts/swift-init +0 -119
- swift-2.23.3.data/scripts/swift-object-auditor +0 -29
- swift-2.23.3.data/scripts/swift-object-expirer +0 -33
- swift-2.23.3.data/scripts/swift-object-info +0 -60
- swift-2.23.3.data/scripts/swift-object-reconstructor +0 -33
- swift-2.23.3.data/scripts/swift-object-relinker +0 -41
- swift-2.23.3.data/scripts/swift-object-replicator +0 -37
- swift-2.23.3.data/scripts/swift-object-server +0 -27
- swift-2.23.3.data/scripts/swift-object-updater +0 -23
- swift-2.23.3.data/scripts/swift-proxy-server +0 -23
- swift-2.23.3.data/scripts/swift-recon +0 -24
- swift-2.23.3.data/scripts/swift-ring-builder +0 -24
- swift-2.23.3.data/scripts/swift-ring-builder-analyzer +0 -22
- swift-2.23.3.data/scripts/swift-ring-composer +0 -22
- swift-2.23.3.dist-info/RECORD +0 -220
- swift-2.23.3.dist-info/pbr.json +0 -1
- {swift-2.23.3.dist-info → swift-2.35.0.dist-info}/LICENSE +0 -0
- {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
|