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
@@ -16,7 +16,8 @@
16
16
 
17
17
  import eventlet.greenio
18
18
  import eventlet.wsgi
19
- from six.moves import urllib
19
+ from eventlet import sleep
20
+ import urllib
20
21
 
21
22
  from swift.common import exceptions
22
23
  from swift.common import http
@@ -26,11 +27,16 @@ from swift.common import request_helpers
26
27
  from swift.common.utils import Timestamp
27
28
 
28
29
 
30
+ class SsyncClientDisconnected(Exception):
31
+ pass
32
+
33
+
29
34
  def decode_missing(line):
30
35
  """
31
36
  Parse a string of the form generated by
32
37
  :py:func:`~swift.obj.ssync_sender.encode_missing` and return a dict
33
- with keys ``object_hash``, ``ts_data``, ``ts_meta``, ``ts_ctype``.
38
+ with keys ``object_hash``, ``ts_data``, ``ts_meta``, ``ts_ctype``,
39
+ ``durable``.
34
40
 
35
41
  The encoder for this line is
36
42
  :py:func:`~swift.obj.ssync_sender.encode_missing`
@@ -39,17 +45,28 @@ def decode_missing(line):
39
45
  parts = line.decode('ascii').split()
40
46
  result['object_hash'] = urllib.parse.unquote(parts[0])
41
47
  t_data = urllib.parse.unquote(parts[1])
42
- result['ts_data'] = Timestamp(t_data)
43
- result['ts_meta'] = result['ts_ctype'] = result['ts_data']
48
+ result['ts_data'] = ts_data = Timestamp(t_data)
49
+ result['ts_meta'] = result['ts_ctype'] = ts_data
50
+ result['durable'] = True # default to True in case this key isn't sent
44
51
  if len(parts) > 2:
45
52
  # allow for a comma separated list of k:v pairs to future-proof
46
53
  subparts = urllib.parse.unquote(parts[2]).split(',')
47
54
  for item in [subpart for subpart in subparts if ':' in subpart]:
48
55
  k, v = item.split(':')
49
56
  if k == 'm':
50
- result['ts_meta'] = Timestamp(t_data, delta=int(v, 16))
57
+ v, _, o = v.partition('__')
58
+ # ignore ts_data offset when calculating ts_meta
59
+ result['ts_meta'] = Timestamp(ts_data.normal,
60
+ delta=int(v, 16),
61
+ offset=int(o or '0', 16))
51
62
  elif k == 't':
52
- result['ts_ctype'] = Timestamp(t_data, delta=int(v, 16))
63
+ v, _, o = v.partition('__')
64
+ # ignore ts_data offset when calculating ts_ctype
65
+ result['ts_ctype'] = Timestamp(Timestamp(ts_data).normal,
66
+ delta=int(v, 16),
67
+ offset=int(o or '0', 16))
68
+ elif k == 'durable':
69
+ result['durable'] = utils.config_true_value(v)
53
70
  return result
54
71
 
55
72
 
@@ -107,17 +124,17 @@ class Receiver(object):
107
124
 
108
125
  The general process inside an SSYNC request is:
109
126
 
110
- 1. Initialize the request: Basic request validation, mount check,
111
- acquire semaphore lock, etc..
127
+ 1. Initialize the request: Basic request validation, mount check,
128
+ acquire semaphore lock, etc..
112
129
 
113
- 2. Missing check: Sender sends the hashes and timestamps of
114
- the object information it can send, receiver sends back
115
- the hashes it wants (doesn't have or has an older
116
- timestamp).
130
+ 2. Missing check: Sender sends the hashes and timestamps of
131
+ the object information it can send, receiver sends back
132
+ the hashes it wants (doesn't have or has an older
133
+ timestamp).
117
134
 
118
- 3. Updates: Sender sends the object information requested.
135
+ 3. Updates: Sender sends the object information requested.
119
136
 
120
- 4. Close down: Release semaphore lock, etc.
137
+ 4. Close down: Release semaphore lock, etc.
121
138
  """
122
139
 
123
140
  def __init__(self, app, request):
@@ -141,6 +158,7 @@ class Receiver(object):
141
158
  """
142
159
  # The general theme for functions __call__ calls is that they should
143
160
  # raise exceptions.MessageTimeout for client timeouts (logged locally),
161
+ # exceptions.ChunkReadError for client disconnects (logged locally),
144
162
  # swob.HTTPException classes for exceptions to return to the caller but
145
163
  # not log locally (unmounted, for example), and any other Exceptions
146
164
  # will be logged with a full stack trace.
@@ -172,7 +190,10 @@ class Receiver(object):
172
190
  finally:
173
191
  if self.app.replication_semaphore:
174
192
  self.app.replication_semaphore.release()
175
- except exceptions.ReplicationLockTimeout as err:
193
+ except SsyncClientDisconnected:
194
+ self.app.logger.error('ssync client disconnected')
195
+ self.disconnect = True
196
+ except exceptions.LockTimeout as err:
176
197
  self.app.logger.debug(
177
198
  '%s/%s/%s SSYNC LOCK TIMEOUT: %s' % (
178
199
  self.request.remote_addr, self.device, self.partition,
@@ -184,6 +205,11 @@ class Receiver(object):
184
205
  self.request.remote_addr, self.device, self.partition,
185
206
  err))
186
207
  yield (':ERROR: %d %r\n' % (408, str(err))).encode('utf8')
208
+ except exceptions.ChunkReadError as err:
209
+ self.app.logger.error(
210
+ '%s/%s/%s read failed in ssync.Receiver: %s' % (
211
+ self.request.remote_addr, self.device, self.partition,
212
+ err))
187
213
  except swob.HTTPException as err:
188
214
  body = b''.join(err({}, lambda *args: None))
189
215
  yield (':ERROR: %d %r\n' % (
@@ -236,6 +262,17 @@ class Receiver(object):
236
262
  raise swob.HTTPInsufficientStorage(drive=self.device)
237
263
  self.fp = self.request.environ['wsgi.input']
238
264
 
265
+ def _readline(self, context):
266
+ # try to read a line from the wsgi input; annotate any timeout or read
267
+ # errors with a description of the calling context
268
+ with exceptions.MessageTimeout(
269
+ self.app.client_timeout, context):
270
+ try:
271
+ line = self.fp.readline(self.app.network_chunk_size)
272
+ except (eventlet.wsgi.ChunkReadError, IOError) as err:
273
+ raise exceptions.ChunkReadError('%s: %s' % (context, err))
274
+ return line
275
+
239
276
  def _check_local(self, remote, make_durable=True):
240
277
  """
241
278
  Parse local diskfile and return results of current
@@ -254,6 +291,7 @@ class Receiver(object):
254
291
  except exceptions.DiskFileDeleted as err:
255
292
  result = {'ts_data': err.timestamp}
256
293
  except exceptions.DiskFileError:
294
+ # e.g. a non-durable EC frag
257
295
  result = {}
258
296
  else:
259
297
  result = {
@@ -261,25 +299,35 @@ class Receiver(object):
261
299
  'ts_meta': df.timestamp,
262
300
  'ts_ctype': df.content_type_timestamp,
263
301
  }
264
- if (make_durable and df.fragments and
265
- remote['ts_data'] in df.fragments and
266
- self.frag_index in df.fragments[remote['ts_data']] and
267
- (df.durable_timestamp is None or
268
- df.durable_timestamp < remote['ts_data'])):
269
- # We have the frag, just missing durable state, so make the frag
270
- # durable now. Try this just once to avoid looping if it fails.
271
- try:
272
- with df.create() as writer:
273
- writer.commit(remote['ts_data'])
274
- return self._check_local(remote, make_durable=False)
275
- except Exception:
276
- # if commit fails then log exception and fall back to wanting
277
- # a full update
278
- self.app.logger.exception(
279
- '%s/%s/%s EXCEPTION in ssync.Receiver while '
280
- 'attempting commit of %s'
281
- % (self.request.remote_addr, self.device, self.partition,
282
- df._datadir))
302
+ if ((df.durable_timestamp is None or
303
+ df.durable_timestamp < remote['ts_data']) and
304
+ df.fragments and
305
+ remote['ts_data'] in df.fragments and
306
+ self.frag_index in df.fragments[remote['ts_data']]):
307
+ # The remote is offering a fragment that we already have but is
308
+ # *newer* than anything *durable* that we have
309
+ if remote['durable']:
310
+ # We have the frag, just missing durable state, so make the
311
+ # frag durable now. Try this just once to avoid looping if
312
+ # it fails.
313
+ if make_durable:
314
+ try:
315
+ with df.create() as writer:
316
+ writer.commit(remote['ts_data'])
317
+ return self._check_local(remote, make_durable=False)
318
+ except Exception:
319
+ # if commit fails then log exception and fall back to
320
+ # wanting a full update
321
+ self.app.logger.exception(
322
+ '%s/%s/%s EXCEPTION in ssync.Receiver while '
323
+ 'attempting commit of %s'
324
+ % (self.request.remote_addr, self.device,
325
+ self.partition, df._datadir))
326
+ else:
327
+ # We have the non-durable frag that is on offer, but our
328
+ # ts_data may currently be set to an older durable frag, so
329
+ # bump our ts_data to prevent the remote frag being wanted.
330
+ result['ts_data'] = remote['ts_data']
283
331
  return result
284
332
 
285
333
  def _check_missing(self, line):
@@ -306,50 +354,54 @@ class Receiver(object):
306
354
 
307
355
  The process is generally:
308
356
 
309
- 1. Sender sends `:MISSING_CHECK: START` and begins
310
- sending `hash timestamp` lines.
357
+ 1. Sender sends ``:MISSING_CHECK: START`` and begins
358
+ sending `hash timestamp` lines.
311
359
 
312
- 2. Receiver gets `:MISSING_CHECK: START` and begins
313
- reading the `hash timestamp` lines, collecting the
314
- hashes of those it desires.
360
+ 2. Receiver gets ``:MISSING_CHECK: START`` and begins
361
+ reading the `hash timestamp` lines, collecting the
362
+ hashes of those it desires.
315
363
 
316
- 3. Sender sends `:MISSING_CHECK: END`.
364
+ 3. Sender sends ``:MISSING_CHECK: END``.
317
365
 
318
- 4. Receiver gets `:MISSING_CHECK: END`, responds with
319
- `:MISSING_CHECK: START`, followed by the list of
320
- <wanted_hash> specifiers it collected as being wanted
321
- (one per line), `:MISSING_CHECK: END`, and flushes any
322
- buffers.
366
+ 4. Receiver gets ``:MISSING_CHECK: END``, responds with
367
+ ``:MISSING_CHECK: START``, followed by the list of
368
+ <wanted_hash> specifiers it collected as being wanted
369
+ (one per line), ``:MISSING_CHECK: END``, and flushes any
370
+ buffers.
323
371
 
324
- Each <wanted_hash> specifier has the form <hash>[ <parts>] where
325
- <parts> is a string containing characters 'd' and/or 'm'
326
- indicating that only data or meta part of object respectively is
327
- required to be sync'd.
372
+ Each <wanted_hash> specifier has the form <hash>[ <parts>] where
373
+ <parts> is a string containing characters 'd' and/or 'm'
374
+ indicating that only data or meta part of object respectively is
375
+ required to be sync'd.
328
376
 
329
- 5. Sender gets `:MISSING_CHECK: START` and reads the list
330
- of hashes desired by the receiver until reading
331
- `:MISSING_CHECK: END`.
377
+ 5. Sender gets ``:MISSING_CHECK: START`` and reads the list
378
+ of hashes desired by the receiver until reading
379
+ ``:MISSING_CHECK: END``.
332
380
 
333
381
  The collection and then response is so the sender doesn't
334
382
  have to read while it writes to ensure network buffers don't
335
383
  fill up and block everything.
336
384
  """
337
- with exceptions.MessageTimeout(
338
- self.app.client_timeout, 'missing_check start'):
339
- line = self.fp.readline(self.app.network_chunk_size)
385
+ line = self._readline('missing_check start')
386
+ if not line:
387
+ # Guess they hung up
388
+ raise SsyncClientDisconnected
340
389
  if line.strip() != b':MISSING_CHECK: START':
341
390
  raise Exception(
342
- 'Looking for :MISSING_CHECK: START got %r' % line[:1024])
391
+ 'Looking for :MISSING_CHECK: START got %r'
392
+ % utils.cap_length(line, 1024))
343
393
  object_hashes = []
394
+ nlines = 0
344
395
  while True:
345
- with exceptions.MessageTimeout(
346
- self.app.client_timeout, 'missing_check line'):
347
- line = self.fp.readline(self.app.network_chunk_size)
396
+ line = self._readline('missing_check line')
348
397
  if not line or line.strip() == b':MISSING_CHECK: END':
349
398
  break
350
399
  want = self._check_missing(line)
351
400
  if want:
352
401
  object_hashes.append(want)
402
+ if nlines % 5 == 0:
403
+ sleep() # Gives a chance for other greenthreads to run
404
+ nlines += 1
353
405
  yield b':MISSING_CHECK: START\r\n'
354
406
  if object_hashes:
355
407
  yield b'\r\n'.join(hsh.encode('ascii') for hsh in object_hashes)
@@ -370,18 +422,18 @@ class Receiver(object):
370
422
 
371
423
  The process is generally:
372
424
 
373
- 1. Sender sends `:UPDATES: START` and begins sending the
374
- PUT and DELETE subrequests.
425
+ 1. Sender sends ``:UPDATES: START`` and begins sending the
426
+ PUT and DELETE subrequests.
375
427
 
376
- 2. Receiver gets `:UPDATES: START` and begins routing the
377
- subrequests to the object server.
428
+ 2. Receiver gets ``:UPDATES: START`` and begins routing the
429
+ subrequests to the object server.
378
430
 
379
- 3. Sender sends `:UPDATES: END`.
431
+ 3. Sender sends ``:UPDATES: END``.
380
432
 
381
- 4. Receiver gets `:UPDATES: END` and sends `:UPDATES:
382
- START` and `:UPDATES: END` (assuming no errors).
433
+ 4. Receiver gets ``:UPDATES: END`` and sends ``:UPDATES:
434
+ START`` and ``:UPDATES: END`` (assuming no errors).
383
435
 
384
- 5. Sender gets `:UPDATES: START` and `:UPDATES: END`.
436
+ 5. Sender gets ``:UPDATES: START`` and ``:UPDATES: END``.
385
437
 
386
438
  If too many subrequests fail, as configured by
387
439
  replication_failure_threshold and replication_failure_ratio,
@@ -394,17 +446,18 @@ class Receiver(object):
394
446
  success. This is so the sender knows if it can remove an out
395
447
  of place partition, for example.
396
448
  """
397
- with exceptions.MessageTimeout(
398
- self.app.client_timeout, 'updates start'):
399
- line = self.fp.readline(self.app.network_chunk_size)
449
+ line = self._readline('updates start')
450
+ if not line:
451
+ # Guess they hung up waiting for us to process the missing check
452
+ raise SsyncClientDisconnected
400
453
  if line.strip() != b':UPDATES: START':
401
- raise Exception('Looking for :UPDATES: START got %r' % line[:1024])
454
+ raise Exception('Looking for :UPDATES: START got %r'
455
+ % utils.cap_length(line, 1024))
402
456
  successes = 0
403
457
  failures = 0
458
+ updates = 0
404
459
  while True:
405
- with exceptions.MessageTimeout(
406
- self.app.client_timeout, 'updates line'):
407
- line = self.fp.readline(self.app.network_chunk_size)
460
+ line = self._readline('updates line')
408
461
  if not line or line.strip() == b':UPDATES: END':
409
462
  break
410
463
  # Read first line METHOD PATH of subrequest.
@@ -416,22 +469,26 @@ class Receiver(object):
416
469
  content_length = None
417
470
  replication_headers = []
418
471
  while True:
419
- with exceptions.MessageTimeout(self.app.client_timeout):
420
- line = self.fp.readline(self.app.network_chunk_size)
472
+ line = self._readline('updates line')
421
473
  if not line:
422
474
  raise Exception(
423
475
  'Got no headers for %s %s' % (method, path))
424
476
  line = line.strip()
425
477
  if not line:
426
478
  break
427
- header, value = swob.bytes_to_wsgi(line).split(':', 1)
428
- header = header.strip().lower()
429
- value = value.strip()
479
+ header, value = line.split(b':', 1)
480
+ header = swob.bytes_to_wsgi(header.strip().lower())
481
+ value = swob.bytes_to_wsgi(value.strip())
430
482
  subreq.headers[header] = value
431
- if header != 'etag':
432
- # make sure ssync doesn't cause 'Etag' to be added to
433
- # obj metadata in addition to 'ETag' which object server
434
- # sets (note capitalization)
483
+ if header not in ('etag', 'x-backend-no-commit'):
484
+ # we'll use X-Backend-Replication-Headers to force the
485
+ # object server to write all sync'd metadata, but with some
486
+ # exceptions:
487
+ # - make sure ssync doesn't cause 'Etag' to be added to
488
+ # obj metadata in addition to 'ETag' which object server
489
+ # sets (note capitalization)
490
+ # - filter out x-backend-no-commit which ssync sender may
491
+ # have added to the subrequest
435
492
  replication_headers.append(header)
436
493
  if header == 'content-length':
437
494
  content_length = int(value)
@@ -478,8 +535,8 @@ class Receiver(object):
478
535
  successes += 1
479
536
  else:
480
537
  self.app.logger.warning(
481
- 'ssync subrequest failed with %s: %s %s' %
482
- (resp.status_int, method, subreq.path))
538
+ 'ssync subrequest failed with %s: %s %s (%s)' %
539
+ (resp.status_int, method, subreq.path, resp.body))
483
540
  failures += 1
484
541
  if failures >= self.app.replication_failure_threshold and (
485
542
  not successes or
@@ -493,6 +550,9 @@ class Receiver(object):
493
550
  # subreq.
494
551
  for junk in subreq.environ['wsgi.input']:
495
552
  pass
553
+ if updates % 5 == 0:
554
+ sleep() # Gives a chance for other greenthreads to run
555
+ updates += 1
496
556
  if failures:
497
557
  raise swob.HTTPInternalServerError(
498
558
  'ERROR: With :UPDATES: %d failures to %d successes' %