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
@@ -20,69 +20,182 @@ Why not swift.common.utils, you ask? Because this way we can import things
20
20
  from swob in here without creating circular imports.
21
21
  """
22
22
 
23
- import hashlib
24
23
  import itertools
25
- import sys
26
24
  import time
27
25
 
28
- import six
29
26
  from swift.common.header_key_dict import HeaderKeyDict
30
27
 
31
- from swift import gettext_ as _
28
+ from swift.common.constraints import AUTO_CREATE_ACCOUNT_PREFIX, \
29
+ CONTAINER_LISTING_LIMIT
32
30
  from swift.common.storage_policy import POLICIES
33
31
  from swift.common.exceptions import ListingIterError, SegmentError
34
- from swift.common.http import is_success
32
+ from swift.common.http import is_success, is_server_error
35
33
  from swift.common.swob import HTTPBadRequest, \
36
34
  HTTPServiceUnavailable, Range, is_chunked, multi_range_iterator, \
37
35
  HTTPPreconditionFailed, wsgi_to_bytes, wsgi_unquote, wsgi_to_str
38
36
  from swift.common.utils import split_path, validate_device_partition, \
39
- close_if_possible, maybe_multipart_byteranges_to_document_iters, \
37
+ close_if_possible, friendly_close, \
38
+ maybe_multipart_byteranges_to_document_iters, \
40
39
  multipart_byteranges_to_document_iters, parse_content_type, \
41
- parse_content_range, csv_append, list_from_csv, Spliterator, quote
42
-
40
+ parse_content_range, csv_append, list_from_csv, Spliterator, quote, \
41
+ RESERVED, config_true_value, md5, CloseableChain, select_ip_port
43
42
  from swift.common.wsgi import make_subrequest
44
43
 
45
44
 
46
45
  OBJECT_TRANSIENT_SYSMETA_PREFIX = 'x-object-transient-sysmeta-'
47
46
  OBJECT_SYSMETA_CONTAINER_UPDATE_OVERRIDE_PREFIX = \
48
47
  'x-object-sysmeta-container-update-override-'
48
+ USE_REPLICATION_NETWORK_HEADER = 'x-backend-use-replication-network'
49
+ MISPLACED_OBJECTS_ACCOUNT = '.misplaced_objects'
49
50
 
50
51
 
51
52
  def get_param(req, name, default=None):
52
53
  """
53
- Get parameters from an HTTP request ensuring proper handling UTF-8
54
+ Get a parameter from an HTTP request ensuring proper handling UTF-8
54
55
  encoding.
55
56
 
56
57
  :param req: request object
57
58
  :param name: parameter name
58
59
  :param default: result to return if the parameter is not found
59
- :returns: HTTP request parameter value, as a native string
60
- (in py2, as UTF-8 encoded str, not unicode object)
60
+ :returns: HTTP request parameter value, as a native (not WSGI) string
61
61
  :raises HTTPBadRequest: if param not valid UTF-8 byte sequence
62
62
  """
63
63
  value = req.params.get(name, default)
64
- if six.PY2:
65
- if value and not isinstance(value, six.text_type):
66
- try:
67
- value.decode('utf8') # Ensure UTF8ness
68
- except UnicodeDecodeError:
69
- raise HTTPBadRequest(
70
- request=req, content_type='text/plain',
71
- body='"%s" parameter not valid UTF-8' % name)
72
- else:
73
- if value:
74
- # req.params is a dict of WSGI strings, so encoding will succeed
75
- value = value.encode('latin1')
76
- try:
77
- # Ensure UTF8ness since we're at it
78
- value = value.decode('utf8')
79
- except UnicodeDecodeError:
80
- raise HTTPBadRequest(
81
- request=req, content_type='text/plain',
82
- body='"%s" parameter not valid UTF-8' % name)
64
+ if value:
65
+ # req.params is a dict of WSGI strings, so encoding will succeed
66
+ value = value.encode('latin1')
67
+ try:
68
+ # Ensure UTF8ness since we're at it
69
+ value = value.decode('utf8')
70
+ except UnicodeDecodeError:
71
+ raise HTTPBadRequest(
72
+ request=req, content_type='text/plain',
73
+ body='"%s" parameter not valid UTF-8' % name)
83
74
  return value
84
75
 
85
76
 
77
+ def get_valid_part_num(req):
78
+ """
79
+ Any non-range GET or HEAD request for a SLO object may include a
80
+ part-number parameter in query string. If the passed in request
81
+ includes a part-number parameter it will be parsed into a valid integer
82
+ and returned. If the passed in request does not include a part-number
83
+ param we will return None. If the part-number parameter is invalid for
84
+ the given request we will raise the appropriate HTTP exception
85
+
86
+ :param req: the request object
87
+
88
+ :returns: validated part-number value or None
89
+ :raises HTTPBadRequest: if request or part-number param is not valid
90
+ """
91
+ part_number_param = get_param(req, 'part-number')
92
+ if part_number_param is None:
93
+ return None
94
+ try:
95
+ part_number = int(part_number_param)
96
+ if part_number <= 0:
97
+ raise ValueError
98
+ except ValueError:
99
+ raise HTTPBadRequest('Part number must be an integer greater '
100
+ 'than 0')
101
+
102
+ if req.range:
103
+ raise HTTPBadRequest(req=req,
104
+ body='Range requests are not supported '
105
+ 'with part number queries')
106
+
107
+ return part_number
108
+
109
+
110
+ def validate_params(req, names):
111
+ """
112
+ Get list of parameters from an HTTP request, validating the encoding of
113
+ each parameter.
114
+
115
+ :param req: request object
116
+ :param names: parameter names
117
+ :returns: a dict mapping parameter names to values for each name that
118
+ appears in the request parameters
119
+ :raises HTTPBadRequest: if any parameter value is not a valid UTF-8 byte
120
+ sequence
121
+ """
122
+ params = {}
123
+ for name in names:
124
+ value = get_param(req, name)
125
+ if value is None:
126
+ continue
127
+ params[name] = value
128
+ return params
129
+
130
+
131
+ def constrain_req_limit(req, constrained_limit):
132
+ given_limit = get_param(req, 'limit')
133
+ limit = constrained_limit
134
+ if given_limit and given_limit.isdigit():
135
+ limit = int(given_limit)
136
+ if limit > constrained_limit:
137
+ raise HTTPPreconditionFailed(
138
+ request=req, body='Maximum limit is %d' % constrained_limit)
139
+ return limit
140
+
141
+
142
+ def validate_container_params(req):
143
+ params = validate_params(req, ('marker', 'end_marker', 'prefix',
144
+ 'delimiter', 'path', 'format', 'reverse',
145
+ 'states', 'includes'))
146
+ params['limit'] = constrain_req_limit(req, CONTAINER_LISTING_LIMIT)
147
+ return params
148
+
149
+
150
+ def _validate_internal_name(name, type_='name'):
151
+ if RESERVED in name and not name.startswith(RESERVED):
152
+ raise HTTPBadRequest(body='Invalid reserved-namespace %s' % (type_))
153
+
154
+
155
+ def validate_internal_account(account):
156
+ """
157
+ Validate internal account name.
158
+
159
+ :raises: HTTPBadRequest
160
+ """
161
+ _validate_internal_name(account, 'account')
162
+
163
+
164
+ def validate_internal_container(account, container):
165
+ """
166
+ Validate internal account and container names.
167
+
168
+ :raises: HTTPBadRequest
169
+ """
170
+ if not account:
171
+ raise ValueError('Account is required')
172
+ validate_internal_account(account)
173
+ if container:
174
+ _validate_internal_name(container, 'container')
175
+
176
+
177
+ def validate_internal_obj(account, container, obj):
178
+ """
179
+ Validate internal account, container and object names.
180
+
181
+ :raises: HTTPBadRequest
182
+ """
183
+ if not account:
184
+ raise ValueError('Account is required')
185
+ if not container:
186
+ raise ValueError('Container is required')
187
+ validate_internal_container(account, container)
188
+ if obj and not (account.startswith(AUTO_CREATE_ACCOUNT_PREFIX) or
189
+ account == MISPLACED_OBJECTS_ACCOUNT):
190
+ _validate_internal_name(obj, 'object')
191
+ if container.startswith(RESERVED) and not obj.startswith(RESERVED):
192
+ raise HTTPBadRequest(body='Invalid user-namespace object '
193
+ 'in reserved-namespace container')
194
+ elif obj.startswith(RESERVED) and not container.startswith(RESERVED):
195
+ raise HTTPBadRequest(body='Invalid reserved-namespace object '
196
+ 'in user-namespace container')
197
+
198
+
86
199
  def get_name_and_placement(request, minsegs=1, maxsegs=None,
87
200
  rest_with_last=False):
88
201
  """
@@ -101,7 +214,7 @@ def get_name_and_placement(request, minsegs=1, maxsegs=None,
101
214
  policy = POLICIES.get_by_index(policy_index)
102
215
  if not policy:
103
216
  raise HTTPServiceUnavailable(
104
- body=_("No policy with index %s") % policy_index,
217
+ body="No policy with index %s" % policy_index,
105
218
  request=request, content_type='text/plain')
106
219
  results = split_and_validate_path(request, minsegs=minsegs,
107
220
  maxsegs=maxsegs,
@@ -273,6 +386,28 @@ def get_container_update_override_key(key):
273
386
  return header.title()
274
387
 
275
388
 
389
+ def get_reserved_name(*parts):
390
+ """
391
+ Generate a valid reserved name that joins the component parts.
392
+
393
+ :returns: a string
394
+ """
395
+ if any(RESERVED in p for p in parts):
396
+ raise ValueError('Invalid reserved part in components')
397
+ return RESERVED + RESERVED.join(parts)
398
+
399
+
400
+ def split_reserved_name(name):
401
+ """
402
+ Separate a valid reserved name into the component parts.
403
+
404
+ :returns: a list of strings
405
+ """
406
+ if not name.startswith(RESERVED):
407
+ raise ValueError('Invalid reserved name')
408
+ return name.split(RESERVED)[1:]
409
+
410
+
276
411
  def remove_items(headers, condition):
277
412
  """
278
413
  Removes items from a dict whose keys satisfy
@@ -338,15 +473,17 @@ class SegmentedIterable(object):
338
473
  :param app: WSGI application from which segments will come
339
474
 
340
475
  :param listing_iter: iterable yielding the object segments to fetch,
341
- along with the byte subranges to fetch, in the form of a 5-tuple
342
- (object-path, object-etag, object-size, first-byte, last-byte).
476
+ along with the byte sub-ranges to fetch. Each yielded item should be a
477
+ dict with the following keys: ``path`` or ``raw_data``,
478
+ ``first-byte``, ``last-byte``, ``hash`` (optional), ``bytes``
479
+ (optional).
343
480
 
344
- If object-etag is None, no MD5 verification will be done.
481
+ If ``hash`` is None, no MD5 verification will be done.
345
482
 
346
- If object-size is None, no length verification will be done.
483
+ If ``bytes`` is None, no length verification will be done.
347
484
 
348
- If first-byte and last-byte are None, then the entire object will be
349
- fetched.
485
+ If ``first-byte`` and ``last-byte`` are None, then the entire object
486
+ will be fetched.
350
487
 
351
488
  :param max_get_time: maximum permitted duration of a GET request (seconds)
352
489
  :param logger: logger object
@@ -405,8 +542,8 @@ class SegmentedIterable(object):
405
542
  path = quote(seg_path) + '?multipart-manifest=get'
406
543
  seg_req = make_subrequest(
407
544
  self.req.environ, path=path, method='GET',
408
- headers={'x-auth-token': self.req.headers.get(
409
- 'x-auth-token')},
545
+ headers={h: self.req.headers.get(h)
546
+ for h in ('x-auth-token', 'x-open-expired')},
410
547
  agent=('%(orig)s ' + self.ua_suffix),
411
548
  swift_source=self.swift_source)
412
549
 
@@ -448,11 +585,10 @@ class SegmentedIterable(object):
448
585
  pending_etag = seg_etag
449
586
  pending_size = seg_size
450
587
 
451
- except ListingIterError:
452
- e_type, e_value, e_traceback = sys.exc_info()
588
+ except ListingIterError as e:
453
589
  if pending_req:
454
590
  yield pending_req, pending_etag, pending_size
455
- six.reraise(e_type, e_value, e_traceback)
591
+ raise e
456
592
 
457
593
  if pending_req:
458
594
  yield pending_req, pending_etag, pending_size
@@ -470,12 +606,18 @@ class SegmentedIterable(object):
470
606
  seg_req = data_or_req
471
607
  seg_resp = seg_req.get_response(self.app)
472
608
  if not is_success(seg_resp.status_int):
473
- close_if_possible(seg_resp.app_iter)
474
- raise SegmentError(
475
- 'While processing manifest %s, '
476
- 'got %d while retrieving %s' %
477
- (self.name, seg_resp.status_int, seg_req.path))
478
-
609
+ # Error body should be short
610
+ body = seg_resp.body.decode('utf8')
611
+ msg = 'While processing manifest %s, got %d (%s) ' \
612
+ 'while retrieving %s' % (
613
+ self.name, seg_resp.status_int,
614
+ body if len(body) <= 60 else body[:57] + '...',
615
+ seg_req.path)
616
+ if is_server_error(seg_resp.status_int):
617
+ self.logger.error(msg)
618
+ raise HTTPServiceUnavailable(
619
+ request=seg_req, content_type='text/plain')
620
+ raise SegmentError(msg)
479
621
  elif ((seg_etag and (seg_resp.etag != seg_etag)) or
480
622
  (seg_size and (seg_resp.content_length != seg_size) and
481
623
  not seg_req.range)):
@@ -498,10 +640,11 @@ class SegmentedIterable(object):
498
640
  else:
499
641
  self.current_resp = seg_resp
500
642
 
643
+ resp_len = 0
501
644
  seg_hash = None
502
645
  if seg_resp.etag and not seg_req.headers.get('Range'):
503
646
  # Only calculate the MD5 if it we can use it to validate
504
- seg_hash = hashlib.md5()
647
+ seg_hash = md5(usedforsecurity=False)
505
648
 
506
649
  document_iters = maybe_multipart_byteranges_to_document_iters(
507
650
  seg_resp.app_iter,
@@ -510,15 +653,26 @@ class SegmentedIterable(object):
510
653
  for chunk in itertools.chain.from_iterable(document_iters):
511
654
  if seg_hash:
512
655
  seg_hash.update(chunk)
656
+ resp_len += len(chunk)
513
657
  yield (seg_req.path, chunk)
514
658
  close_if_possible(seg_resp.app_iter)
515
659
 
516
- if seg_hash and seg_hash.hexdigest() != seg_resp.etag:
517
- raise SegmentError(
518
- "Bad MD5 checksum in %(name)s for %(seg)s: headers had"
519
- " %(etag)s, but object MD5 was actually %(actual)s" %
520
- {'seg': seg_req.path, 'etag': seg_resp.etag,
521
- 'name': self.name, 'actual': seg_hash.hexdigest()})
660
+ if seg_hash:
661
+ if resp_len != seg_resp.content_length:
662
+ raise SegmentError(
663
+ "Bad response length for %(seg)s as part of %(name)s: "
664
+ "headers had %(from_headers)s, but response length "
665
+ "was actually %(actual)s" %
666
+ {'seg': seg_req.path,
667
+ 'from_headers': seg_resp.content_length,
668
+ 'name': self.name, 'actual': resp_len})
669
+ if seg_hash.hexdigest() != seg_resp.etag:
670
+ raise SegmentError(
671
+ "Bad MD5 checksum for %(seg)s as part of %(name)s: "
672
+ "headers had %(etag)s, but object MD5 was actually "
673
+ "%(actual)s" %
674
+ {'seg': seg_req.path, 'etag': seg_resp.etag,
675
+ 'name': self.name, 'actual': seg_hash.hexdigest()})
522
676
 
523
677
  def _byte_counting_iter(self):
524
678
  # Checks that we give the client the right number of bytes. Raises
@@ -598,7 +752,10 @@ class SegmentedIterable(object):
598
752
  for x in mri:
599
753
  yield x
600
754
  finally:
601
- self.close()
755
+ # Spliterator and multi_range_iterator can't possibly know we've
756
+ # consumed the whole of the app_iter, but we want to read/close the
757
+ # final segment response
758
+ friendly_close(self.app_iter)
602
759
 
603
760
  def validate_first_segment(self):
604
761
  """
@@ -623,7 +780,7 @@ class SegmentedIterable(object):
623
780
  if self.peeked_chunk is not None:
624
781
  pc = self.peeked_chunk
625
782
  self.peeked_chunk = None
626
- return itertools.chain([pc], self.app_iter)
783
+ return CloseableChain([pc], self.app_iter)
627
784
  else:
628
785
  return self.app_iter
629
786
 
@@ -738,3 +895,113 @@ def resolve_etag_is_at_header(req, metadata):
738
895
  alternate_etag = metadata[name]
739
896
  break
740
897
  return alternate_etag
898
+
899
+
900
+ def update_ignore_range_header(req, name):
901
+ """
902
+ Helper function to update an X-Backend-Ignore-Range-If-Metadata-Present
903
+ header whose value is a list of header names which, if any are present
904
+ on an object, mean the object server should respond with a 200 instead
905
+ of a 206 or 416.
906
+
907
+ :param req: a swob Request
908
+ :param name: name of a header which, if found, indicates the proxy will
909
+ want the whole object
910
+ """
911
+ if ',' in name:
912
+ # HTTP header names should not have commas but we'll check anyway
913
+ raise ValueError('Header name must not contain commas')
914
+ hdr = 'X-Backend-Ignore-Range-If-Metadata-Present'
915
+ req.headers[hdr] = csv_append(req.headers.get(hdr), name)
916
+
917
+
918
+ def resolve_ignore_range_header(req, metadata):
919
+ """
920
+ Helper function to remove Range header from request if metadata matching
921
+ the X-Backend-Ignore-Range-If-Metadata-Present header is found.
922
+
923
+ :param req: a swob Request
924
+ :param metadata: dictionary of object metadata
925
+ """
926
+ ignore_range_headers = set(
927
+ h.strip().lower()
928
+ for h in req.headers.get(
929
+ 'X-Backend-Ignore-Range-If-Metadata-Present',
930
+ '').split(','))
931
+ if ignore_range_headers.intersection(
932
+ h.lower() for h in metadata):
933
+ req.headers.pop('Range', None)
934
+
935
+
936
+ def is_use_replication_network(headers=None):
937
+ """
938
+ Determine if replication network should be used.
939
+
940
+ :param headers: a dict of headers
941
+ :return: the value of the ``x-backend-use-replication-network`` item from
942
+ ``headers``. If no ``headers`` are given or the item is not found then
943
+ False is returned.
944
+ """
945
+ if headers:
946
+ for h, v in headers.items():
947
+ if h.lower() == USE_REPLICATION_NETWORK_HEADER:
948
+ return config_true_value(v)
949
+ return False
950
+
951
+
952
+ def get_ip_port(node, headers):
953
+ """
954
+ Get the ip address and port that should be used for the given ``node``.
955
+ The normal ip address and port are returned unless the ``node`` or
956
+ ``headers`` indicate that the replication ip address and port should be
957
+ used.
958
+
959
+ If the ``headers`` dict has an item with key
960
+ ``x-backend-use-replication-network`` and a truthy value then the
961
+ replication ip address and port are returned. Otherwise if the ``node``
962
+ dict has an item with key ``use_replication`` and truthy value then the
963
+ replication ip address and port are returned. Otherwise the normal ip
964
+ address and port are returned.
965
+
966
+ :param node: a dict describing a node
967
+ :param headers: a dict of headers
968
+ :return: a tuple of (ip address, port)
969
+ """
970
+ return select_ip_port(
971
+ node, use_replication=is_use_replication_network(headers))
972
+
973
+
974
+ def is_open_expired(app, req):
975
+ """
976
+ Helper function to check if a request with the header 'x-open-expired'
977
+ can access an object that has not yet been reaped by the object-expirer
978
+ based on the allow_open_expired global config.
979
+
980
+ :param app: the application instance
981
+ :param req: request object
982
+ """
983
+ return (config_true_value(app.allow_open_expired) and
984
+ config_true_value(req.headers.get('x-open-expired')))
985
+
986
+
987
+ def is_backend_open_expired(request):
988
+ """
989
+ Helper function to check if a request has either the headers
990
+ 'x-backend-open-expired' or 'x-backend-replication' for the backend
991
+ to access expired objects.
992
+
993
+ :param request: request object
994
+ """
995
+ x_backend_open_expired = config_true_value(request.headers.get(
996
+ 'x-backend-open-expired', 'false'))
997
+ x_backend_replication = config_true_value(request.headers.get(
998
+ 'x-backend-replication', 'false'))
999
+ return x_backend_open_expired or x_backend_replication
1000
+
1001
+
1002
+ def append_log_info(environ, log_info):
1003
+ environ.setdefault('swift.log_info', []).append(log_info)
1004
+
1005
+
1006
+ def get_log_info(environ):
1007
+ return ','.join(environ.get('swift.log_info', []))
@@ -22,18 +22,16 @@ import math
22
22
  import random
23
23
  import uuid
24
24
 
25
- import six.moves.cPickle as pickle
25
+ import pickle # nosec: B403
26
26
  from copy import deepcopy
27
27
  from contextlib import contextmanager
28
28
 
29
29
  from array import array
30
30
  from collections import defaultdict
31
- import six
32
- from six.moves import range
33
31
  from time import time
34
32
 
35
33
  from swift.common import exceptions
36
- from swift.common.ring import RingData
34
+ from swift.common.ring.ring import RingData
37
35
  from swift.common.ring.utils import tiers_for_dev, build_tier_tree, \
38
36
  validate_and_normalize_address, validate_replicas_by_tier, pretty_dev
39
37
 
@@ -87,6 +85,9 @@ class RingBuilder(object):
87
85
  if part_power > 32:
88
86
  raise ValueError("part_power must be at most 32 (was %d)"
89
87
  % (part_power,))
88
+ if part_power < 0:
89
+ raise ValueError("part_power must be at least 0 (was %d)"
90
+ % (part_power,))
90
91
  if replicas < 1:
91
92
  raise ValueError("replicas must be at least 1 (was %.6f)"
92
93
  % (replicas,))
@@ -178,16 +179,16 @@ class RingBuilder(object):
178
179
  @contextmanager
179
180
  def debug(self):
180
181
  """
181
- Temporarily enables debug logging, useful in tests, e.g.
182
+ Temporarily enables debug logging, useful in tests, e.g.::
182
183
 
183
184
  with rb.debug():
184
185
  rb.rebalance()
185
186
  """
186
- self.logger.disabled = False
187
+ old_val, self.logger.disabled = self.logger.disabled, False
187
188
  try:
188
189
  yield
189
190
  finally:
190
- self.logger.disabled = True
191
+ self.logger.disabled = old_val
191
192
 
192
193
  @property
193
194
  def min_part_seconds_left(self):
@@ -364,13 +365,15 @@ class RingBuilder(object):
364
365
  # shift an unsigned int >I right to obtain the partition for the
365
366
  # int).
366
367
  if not self._replica2part2dev:
367
- self._ring = RingData([], devs, self.part_shift)
368
+ self._ring = RingData([], devs, self.part_shift,
369
+ version=self.version)
368
370
  else:
369
371
  self._ring = \
370
372
  RingData([array('H', p2d) for p2d in
371
373
  self._replica2part2dev],
372
374
  devs, self.part_shift,
373
- self.next_part_power)
375
+ self.next_part_power,
376
+ self.version)
374
377
  return self._ring
375
378
 
376
379
  def add_dev(self, dev):
@@ -417,11 +420,11 @@ class RingBuilder(object):
417
420
  # Add holes to self.devs to ensure self.devs[dev['id']] will be the dev
418
421
  while dev['id'] >= len(self.devs):
419
422
  self.devs.append(None)
420
- required_keys = ('ip', 'port', 'weight')
421
- if any(required not in dev for required in required_keys):
422
- raise ValueError(
423
- '%r is missing at least one the required key %r' % (
424
- dev, required_keys))
423
+ required_keys = ('region', 'zone', 'ip', 'port', 'device', 'weight')
424
+ missing = tuple(key for key in required_keys if key not in dev)
425
+ if missing:
426
+ raise ValueError('%r is missing required key(s): %s' % (
427
+ dev, ', '.join(missing)))
425
428
  dev['weight'] = float(dev['weight'])
426
429
  dev['parts'] = 0
427
430
  dev.setdefault('meta', '')
@@ -450,6 +453,46 @@ class RingBuilder(object):
450
453
  self.devs_changed = True
451
454
  self.version += 1
452
455
 
456
+ def set_dev_region(self, dev_id, region):
457
+ """
458
+ Set the region of a device. This should be called rather than just
459
+ altering the region key in the device dict directly, as the builder
460
+ will need to rebuild some internal state to reflect the change.
461
+
462
+ .. note::
463
+ This will not rebalance the ring immediately as you may want to
464
+ make multiple changes for a single rebalance.
465
+
466
+ :param dev_id: device id
467
+ :param region: new region for device
468
+ """
469
+ if any(dev_id == d['id'] for d in self._remove_devs):
470
+ raise ValueError("Can not set region of dev_id %s because it "
471
+ "is marked for removal" % (dev_id,))
472
+ self.devs[dev_id]['region'] = region
473
+ self.devs_changed = True
474
+ self.version += 1
475
+
476
+ def set_dev_zone(self, dev_id, zone):
477
+ """
478
+ Set the zone of a device. This should be called rather than just
479
+ altering the zone key in the device dict directly, as the builder
480
+ will need to rebuild some internal state to reflect the change.
481
+
482
+ .. note::
483
+ This will not rebalance the ring immediately as you may want to
484
+ make multiple changes for a single rebalance.
485
+
486
+ :param dev_id: device id
487
+ :param zone: new zone for device
488
+ """
489
+ if any(dev_id == d['id'] for d in self._remove_devs):
490
+ raise ValueError("Can not set zone of dev_id %s because it "
491
+ "is marked for removal" % (dev_id,))
492
+ self.devs[dev_id]['zone'] = zone
493
+ self.devs_changed = True
494
+ self.version += 1
495
+
453
496
  def remove_dev(self, dev_id):
454
497
  """
455
498
  Remove a device from the ring.
@@ -601,8 +644,7 @@ class RingBuilder(object):
601
644
 
602
645
  dispersion_graph = {}
603
646
  # go over all the devices holding each replica part by part
604
- for part_id, dev_ids in enumerate(
605
- six.moves.zip(*self._replica2part2dev)):
647
+ for part_id, dev_ids in enumerate(zip(*self._replica2part2dev)):
606
648
  # count the number of replicas of this part for each tier of each
607
649
  # device, some devices may have overlapping tiers!
608
650
  replicas_at_tier = defaultdict(int)
@@ -1395,17 +1437,17 @@ class RingBuilder(object):
1395
1437
  {(): 3.0,
1396
1438
  (1,): 3.0,
1397
1439
  (1, 1): 1.0,
1398
- (1, 1, '127.0.0.1:6010'): 1.0,
1399
- (1, 1, '127.0.0.1:6010', 0): 1.0,
1440
+ (1, 1, '127.0.0.1:6210'): 1.0,
1441
+ (1, 1, '127.0.0.1:6210', 0): 1.0,
1400
1442
  (1, 2): 1.0,
1401
- (1, 2, '127.0.0.1:6020'): 1.0,
1402
- (1, 2, '127.0.0.1:6020', 1): 1.0,
1443
+ (1, 2, '127.0.0.1:6220'): 1.0,
1444
+ (1, 2, '127.0.0.1:6220', 1): 1.0,
1403
1445
  (1, 3): 1.0,
1404
- (1, 3, '127.0.0.1:6030'): 1.0,
1405
- (1, 3, '127.0.0.1:6030', 2): 1.0,
1446
+ (1, 3, '127.0.0.1:6230'): 1.0,
1447
+ (1, 3, '127.0.0.1:6230', 2): 1.0,
1406
1448
  (1, 4): 1.0,
1407
- (1, 4, '127.0.0.1:6040'): 1.0,
1408
- (1, 4, '127.0.0.1:6040', 3): 1.0}
1449
+ (1, 4, '127.0.0.1:6240'): 1.0,
1450
+ (1, 4, '127.0.0.1:6240', 3): 1.0}
1409
1451
 
1410
1452
  """
1411
1453
  # Used by walk_tree to know what entries to create for each recursive
@@ -1696,7 +1738,7 @@ class RingBuilder(object):
1696
1738
  else:
1697
1739
  with fp:
1698
1740
  try:
1699
- builder = pickle.load(fp)
1741
+ builder = pickle.load(fp) # nosec: B301
1700
1742
  except Exception:
1701
1743
  # raise error during unpickling as UnPicklingError
1702
1744
  raise exceptions.UnPicklingError(