swift 2.31.1__py2.py3-none-any.whl → 2.32.1__py2.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 (104) hide show
  1. swift/cli/info.py +9 -2
  2. swift/cli/ringbuilder.py +5 -1
  3. swift/common/container_sync_realms.py +6 -7
  4. swift/common/daemon.py +7 -3
  5. swift/common/db.py +22 -7
  6. swift/common/db_replicator.py +19 -20
  7. swift/common/direct_client.py +63 -14
  8. swift/common/internal_client.py +24 -3
  9. swift/common/manager.py +43 -44
  10. swift/common/memcached.py +168 -74
  11. swift/common/middleware/__init__.py +4 -0
  12. swift/common/middleware/account_quotas.py +98 -40
  13. swift/common/middleware/backend_ratelimit.py +6 -4
  14. swift/common/middleware/crossdomain.py +21 -8
  15. swift/common/middleware/listing_formats.py +26 -38
  16. swift/common/middleware/proxy_logging.py +12 -9
  17. swift/common/middleware/s3api/controllers/bucket.py +8 -2
  18. swift/common/middleware/s3api/s3api.py +9 -4
  19. swift/common/middleware/s3api/s3request.py +32 -24
  20. swift/common/middleware/s3api/s3response.py +10 -1
  21. swift/common/middleware/tempauth.py +9 -10
  22. swift/common/middleware/versioned_writes/__init__.py +0 -3
  23. swift/common/middleware/versioned_writes/object_versioning.py +22 -5
  24. swift/common/middleware/x_profile/html_viewer.py +1 -1
  25. swift/common/middleware/xprofile.py +5 -0
  26. swift/common/request_helpers.py +1 -2
  27. swift/common/ring/ring.py +22 -19
  28. swift/common/swob.py +2 -1
  29. swift/common/{utils.py → utils/__init__.py} +610 -1146
  30. swift/common/utils/ipaddrs.py +256 -0
  31. swift/common/utils/libc.py +345 -0
  32. swift/common/utils/timestamp.py +399 -0
  33. swift/common/wsgi.py +70 -39
  34. swift/container/backend.py +106 -38
  35. swift/container/server.py +11 -2
  36. swift/container/sharder.py +34 -15
  37. swift/locale/de/LC_MESSAGES/swift.po +1 -320
  38. swift/locale/en_GB/LC_MESSAGES/swift.po +1 -347
  39. swift/locale/es/LC_MESSAGES/swift.po +1 -279
  40. swift/locale/fr/LC_MESSAGES/swift.po +1 -209
  41. swift/locale/it/LC_MESSAGES/swift.po +1 -207
  42. swift/locale/ja/LC_MESSAGES/swift.po +2 -278
  43. swift/locale/ko_KR/LC_MESSAGES/swift.po +3 -303
  44. swift/locale/pt_BR/LC_MESSAGES/swift.po +1 -204
  45. swift/locale/ru/LC_MESSAGES/swift.po +1 -203
  46. swift/locale/tr_TR/LC_MESSAGES/swift.po +1 -192
  47. swift/locale/zh_CN/LC_MESSAGES/swift.po +1 -192
  48. swift/locale/zh_TW/LC_MESSAGES/swift.po +1 -193
  49. swift/obj/diskfile.py +19 -6
  50. swift/obj/server.py +20 -6
  51. swift/obj/ssync_receiver.py +19 -9
  52. swift/obj/ssync_sender.py +10 -10
  53. swift/proxy/controllers/account.py +7 -7
  54. swift/proxy/controllers/base.py +374 -366
  55. swift/proxy/controllers/container.py +112 -53
  56. swift/proxy/controllers/obj.py +254 -390
  57. swift/proxy/server.py +3 -8
  58. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-server +1 -1
  59. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-server +1 -1
  60. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-drive-audit +45 -14
  61. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-server +1 -1
  62. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-proxy-server +1 -1
  63. {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/AUTHORS +4 -0
  64. {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/METADATA +32 -35
  65. {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/RECORD +103 -100
  66. {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/WHEEL +1 -1
  67. {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/entry_points.txt +0 -1
  68. swift-2.32.1.dist-info/pbr.json +1 -0
  69. swift-2.31.1.dist-info/pbr.json +0 -1
  70. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-audit +0 -0
  71. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-auditor +0 -0
  72. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-info +0 -0
  73. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-reaper +0 -0
  74. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-replicator +0 -0
  75. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-config +0 -0
  76. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-auditor +0 -0
  77. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-info +0 -0
  78. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-reconciler +0 -0
  79. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-replicator +0 -0
  80. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-sharder +0 -0
  81. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-sync +0 -0
  82. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-updater +0 -0
  83. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-dispersion-populate +0 -0
  84. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-dispersion-report +0 -0
  85. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-form-signature +0 -0
  86. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-get-nodes +0 -0
  87. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-init +0 -0
  88. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-auditor +0 -0
  89. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-expirer +0 -0
  90. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-info +0 -0
  91. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-reconstructor +0 -0
  92. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-relinker +0 -0
  93. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-replicator +0 -0
  94. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-updater +0 -0
  95. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-oldies +0 -0
  96. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-orphans +0 -0
  97. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-recon +0 -0
  98. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-recon-cron +0 -0
  99. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-reconciler-enqueue +0 -0
  100. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-ring-builder +0 -0
  101. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-ring-builder-analyzer +0 -0
  102. {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-ring-composer +0 -0
  103. {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/LICENSE +0 -0
  104. {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/top_level.txt +0 -0
@@ -36,7 +36,6 @@ import random
36
36
  from copy import deepcopy
37
37
  from sys import exc_info
38
38
 
39
- from eventlet import sleep
40
39
  from eventlet.timeout import Timeout
41
40
  import six
42
41
 
@@ -45,7 +44,7 @@ from swift.common.utils import Timestamp, WatchdogTimeout, config_true_value, \
45
44
  public, split_path, list_from_csv, GreenthreadSafeIterator, \
46
45
  GreenAsyncPile, quorum_size, parse_content_type, drain_and_close, \
47
46
  document_iters_to_http_response_body, ShardRange, cache_from_env, \
48
- MetricsPrefixLoggerAdapter
47
+ MetricsPrefixLoggerAdapter, CooperativeIterator
49
48
  from swift.common.bufferedhttp import http_connect
50
49
  from swift.common import constraints
51
50
  from swift.common.exceptions import ChunkReadTimeout, ChunkWriteTimeout, \
@@ -54,7 +53,8 @@ from swift.common.header_key_dict import HeaderKeyDict
54
53
  from swift.common.http import is_informational, is_success, is_redirection, \
55
54
  is_server_error, HTTP_OK, HTTP_PARTIAL_CONTENT, HTTP_MULTIPLE_CHOICES, \
56
55
  HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVICE_UNAVAILABLE, \
57
- HTTP_UNAUTHORIZED, HTTP_CONTINUE, HTTP_GONE
56
+ HTTP_UNAUTHORIZED, HTTP_CONTINUE, HTTP_GONE, \
57
+ HTTP_REQUESTED_RANGE_NOT_SATISFIABLE
58
58
  from swift.common.swob import Request, Response, Range, \
59
59
  HTTPException, HTTPRequestedRangeNotSatisfiable, HTTPServiceUnavailable, \
60
60
  status_map, wsgi_to_str, str_to_wsgi, wsgi_quote, wsgi_unquote, \
@@ -90,19 +90,6 @@ def update_headers(response, headers):
90
90
  response.headers[name] = value
91
91
 
92
92
 
93
- def source_key(resp):
94
- """
95
- Provide the timestamp of the swift http response as a floating
96
- point value. Used as a sort key.
97
-
98
- :param resp: bufferedhttp response object
99
- """
100
- return Timestamp(resp.getheader('x-backend-data-timestamp') or
101
- resp.getheader('x-backend-timestamp') or
102
- resp.getheader('x-put-timestamp') or
103
- resp.getheader('x-timestamp') or 0)
104
-
105
-
106
93
  def delay_denial(func):
107
94
  """
108
95
  Decorator to declare which methods should have any swift.authorize call
@@ -417,11 +404,43 @@ def get_object_info(env, app, path=None, swift_source=None):
417
404
  return info
418
405
 
419
406
 
420
- def get_container_info(env, app, swift_source=None):
407
+ def _record_ac_info_cache_metrics(
408
+ app, cache_state, container=None, resp=None):
409
+ """
410
+ Record a single cache operation by account or container lookup into its
411
+ corresponding metrics.
412
+
413
+ :param app: the application object
414
+ :param cache_state: the state of this cache operation, includes
415
+ infocache_hit, memcache hit, miss, error, skip, force_skip
416
+ and disabled.
417
+ :param container: the container name
418
+ :param resp: the response from either backend or cache hit.
419
+ """
420
+ try:
421
+ proxy_app = app._pipeline_final_app
422
+ except AttributeError:
423
+ logger = None
424
+ else:
425
+ logger = proxy_app.logger
426
+ op_type = 'container.info' if container else 'account.info'
427
+ if logger:
428
+ record_cache_op_metrics(logger, op_type, cache_state, resp)
429
+
430
+
431
+ def get_container_info(env, app, swift_source=None, cache_only=False):
421
432
  """
422
433
  Get the info structure for a container, based on env and app.
423
434
  This is useful to middlewares.
424
435
 
436
+ :param env: the environment used by the current request
437
+ :param app: the application object
438
+ :param swift_source: Used to mark the request as originating out of
439
+ middleware. Will be logged in proxy logs.
440
+ :param cache_only: If true, indicates that caller doesn't want to HEAD the
441
+ backend container when cache miss.
442
+ :returns: the object info
443
+
425
444
  .. note::
426
445
 
427
446
  This call bypasses auth. Success does not imply that the request has
@@ -439,14 +458,18 @@ def get_container_info(env, app, swift_source=None):
439
458
  container = wsgi_to_str(wsgi_container)
440
459
 
441
460
  # Try to cut through all the layers to the proxy app
461
+ # (while also preserving logging)
442
462
  try:
443
- app = app._pipeline_final_app
463
+ logged_app = app._pipeline_request_logging_app
464
+ proxy_app = app._pipeline_final_app
444
465
  except AttributeError:
445
- pass
466
+ logged_app = proxy_app = app
446
467
  # Check in environment cache and in memcache (in that order)
447
- info = _get_info_from_caches(app, env, account, container)
468
+ info, cache_state = _get_info_from_caches(
469
+ proxy_app, env, account, container)
448
470
 
449
- if not info:
471
+ resp = None
472
+ if not info and not cache_only:
450
473
  # Cache miss; go HEAD the container and populate the caches
451
474
  env.setdefault('swift.infocache', {})
452
475
  # Before checking the container, make sure the account exists.
@@ -456,11 +479,13 @@ def get_container_info(env, app, swift_source=None):
456
479
  # account is successful whether the account actually has .db files
457
480
  # on disk or not.
458
481
  is_autocreate_account = account.startswith(
459
- getattr(app, 'auto_create_account_prefix',
482
+ getattr(proxy_app, 'auto_create_account_prefix',
460
483
  constraints.AUTO_CREATE_ACCOUNT_PREFIX))
461
484
  if not is_autocreate_account:
462
- account_info = get_account_info(env, app, swift_source)
485
+ account_info = get_account_info(env, logged_app, swift_source)
463
486
  if not account_info or not is_success(account_info['status']):
487
+ _record_ac_info_cache_metrics(
488
+ logged_app, cache_state, container)
464
489
  return headers_to_container_info({}, 0)
465
490
 
466
491
  req = _prepare_pre_auth_info_request(
@@ -469,7 +494,7 @@ def get_container_info(env, app, swift_source=None):
469
494
  # *Always* allow reserved names for get-info requests -- it's on the
470
495
  # caller to keep the result private-ish
471
496
  req.headers['X-Backend-Allow-Reserved-Names'] = 'true'
472
- resp = req.get_response(app)
497
+ resp = req.get_response(logged_app)
473
498
  drain_and_close(resp)
474
499
  # Check in infocache to see if the proxy (or anyone else) already
475
500
  # populated the cache for us. If they did, just use what's there.
@@ -477,12 +502,13 @@ def get_container_info(env, app, swift_source=None):
477
502
  # See similar comment in get_account_info() for justification.
478
503
  info = _get_info_from_infocache(env, account, container)
479
504
  if info is None:
480
- info = set_info_cache(app, env, account, container, resp)
505
+ info = set_info_cache(env, account, container, resp)
481
506
 
482
507
  if info:
483
508
  info = deepcopy(info) # avoid mutating what's in swift.infocache
484
509
  else:
485
- info = headers_to_container_info({}, 503)
510
+ status_int = 0 if cache_only else 503
511
+ info = headers_to_container_info({}, status_int)
486
512
 
487
513
  # Old data format in memcache immediately after a Swift upgrade; clean
488
514
  # it up so consumers of get_container_info() aren't exposed to it.
@@ -509,6 +535,7 @@ def get_container_info(env, app, swift_source=None):
509
535
  versions_info = get_container_info(versions_req.environ, app)
510
536
  info['bytes'] = info['bytes'] + versions_info['bytes']
511
537
 
538
+ _record_ac_info_cache_metrics(logged_app, cache_state, container, resp)
512
539
  return info
513
540
 
514
541
 
@@ -532,15 +559,18 @@ def get_account_info(env, app, swift_source=None):
532
559
  account = wsgi_to_str(wsgi_account)
533
560
 
534
561
  # Try to cut through all the layers to the proxy app
562
+ # (while also preserving logging)
535
563
  try:
536
- app = app._pipeline_final_app
564
+ app = app._pipeline_request_logging_app
537
565
  except AttributeError:
538
566
  pass
539
567
  # Check in environment cache and in memcache (in that order)
540
- info = _get_info_from_caches(app, env, account)
568
+ info, cache_state = _get_info_from_caches(app, env, account)
541
569
 
542
570
  # Cache miss; go HEAD the account and populate the caches
543
- if not info:
571
+ if info:
572
+ resp = None
573
+ else:
544
574
  env.setdefault('swift.infocache', {})
545
575
  req = _prepare_pre_auth_info_request(
546
576
  env, "/%s/%s" % (version, wsgi_account),
@@ -566,7 +596,7 @@ def get_account_info(env, app, swift_source=None):
566
596
  # memcache would defeat the purpose.
567
597
  info = _get_info_from_infocache(env, account)
568
598
  if info is None:
569
- info = set_info_cache(app, env, account, None, resp)
599
+ info = set_info_cache(env, account, None, resp)
570
600
 
571
601
  if info:
572
602
  info = info.copy() # avoid mutating what's in swift.infocache
@@ -579,6 +609,7 @@ def get_account_info(env, app, swift_source=None):
579
609
  else:
580
610
  info[field] = int(info[field])
581
611
 
612
+ _record_ac_info_cache_metrics(app, cache_state, container=None, resp=resp)
582
613
  return info
583
614
 
584
615
 
@@ -615,7 +646,7 @@ def get_cache_key(account, container=None, obj=None, shard=None):
615
646
  raise ValueError('Shard cache key requires account and container')
616
647
  if obj:
617
648
  raise ValueError('Shard cache key cannot have obj')
618
- cache_key = 'shard-%s/%s/%s' % (shard, account, container)
649
+ cache_key = 'shard-%s-v2/%s/%s' % (shard, account, container)
619
650
  elif obj:
620
651
  if not (account and container):
621
652
  raise ValueError('Object cache key requires account and container')
@@ -633,11 +664,11 @@ def get_cache_key(account, container=None, obj=None, shard=None):
633
664
  return cache_key
634
665
 
635
666
 
636
- def set_info_cache(app, env, account, container, resp):
667
+ def set_info_cache(env, account, container, resp):
637
668
  """
638
669
  Cache info in both memcache and env.
639
670
 
640
- :param app: the application object
671
+ :param env: the WSGI request environment
641
672
  :param account: the unquoted account name
642
673
  :param container: the unquoted container name or None
643
674
  :param resp: the response received or None if info cache should be cleared
@@ -649,7 +680,7 @@ def set_info_cache(app, env, account, container, resp):
649
680
  infocache = env.setdefault('swift.infocache', {})
650
681
  memcache = cache_from_env(env, True)
651
682
  if resp is None:
652
- clear_info_cache(app, env, account, container)
683
+ clear_info_cache(env, account, container)
653
684
  return
654
685
 
655
686
  if container:
@@ -706,12 +737,11 @@ def set_object_info_cache(app, env, account, container, obj, resp):
706
737
  return info
707
738
 
708
739
 
709
- def clear_info_cache(app, env, account, container=None, shard=None):
740
+ def clear_info_cache(env, account, container=None, shard=None):
710
741
  """
711
742
  Clear the cached info in both memcache and env
712
743
 
713
- :param app: the application object
714
- :param env: the WSGI environment
744
+ :param env: the WSGI request environment
715
745
  :param account: the account name
716
746
  :param container: the container name if clearing info for containers, or
717
747
  None
@@ -765,10 +795,13 @@ def record_cache_op_metrics(
765
795
  else:
766
796
  # the cases of cache_state is memcache miss, error, skip, force_skip
767
797
  # or disabled.
768
- if resp is not None:
769
- # Note: currently there is no case that 'resp' will be None.
798
+ if resp:
770
799
  logger.increment(
771
800
  '%s.cache.%s.%d' % (op_type, cache_state, resp.status_int))
801
+ else:
802
+ # In some situation, we choose not to lookup backend after cache
803
+ # miss.
804
+ logger.increment('%s.cache.%s' % (op_type, cache_state))
772
805
 
773
806
 
774
807
  def _get_info_from_memcache(app, env, account, container=None):
@@ -780,61 +813,58 @@ def _get_info_from_memcache(app, env, account, container=None):
780
813
  :param account: the account name
781
814
  :param container: the container name
782
815
 
783
- :returns: a dictionary of cached info on cache hit, None on miss. Also
784
- returns None if memcache is not in use.
816
+ :returns: a tuple of two values, the first is a dictionary of cached info
817
+ on cache hit, None on miss or if memcache is not in use; the second is
818
+ cache state.
785
819
  """
786
- cache_key = get_cache_key(account, container)
787
820
  memcache = cache_from_env(env, True)
788
- if memcache:
789
- try:
790
- proxy_app = app._pipeline_final_app
791
- except AttributeError:
792
- # Only the middleware entry-points get a reference to the
793
- # proxy-server app; if a middleware composes itself as multiple
794
- # filters, we'll just have to choose a reasonable default
795
- skip_chance = 0.0
796
- logger = None
821
+ if not memcache:
822
+ return None, 'disabled'
823
+
824
+ try:
825
+ proxy_app = app._pipeline_final_app
826
+ except AttributeError:
827
+ # Only the middleware entry-points get a reference to the
828
+ # proxy-server app; if a middleware composes itself as multiple
829
+ # filters, we'll just have to choose a reasonable default
830
+ skip_chance = 0.0
831
+ else:
832
+ if container:
833
+ skip_chance = proxy_app.container_existence_skip_cache
797
834
  else:
798
- if container:
799
- skip_chance = proxy_app.container_existence_skip_cache
835
+ skip_chance = proxy_app.account_existence_skip_cache
836
+
837
+ cache_key = get_cache_key(account, container)
838
+ if skip_chance and random.random() < skip_chance:
839
+ info = None
840
+ cache_state = 'skip'
841
+ else:
842
+ info = memcache.get(cache_key)
843
+ cache_state = 'hit' if info else 'miss'
844
+ if info and six.PY2:
845
+ # Get back to native strings
846
+ new_info = {}
847
+ for key in info:
848
+ new_key = key.encode("utf-8") if isinstance(
849
+ key, six.text_type) else key
850
+ if isinstance(info[key], six.text_type):
851
+ new_info[new_key] = info[key].encode("utf-8")
852
+ elif isinstance(info[key], dict):
853
+ new_info[new_key] = {}
854
+ for subkey, value in info[key].items():
855
+ new_subkey = subkey.encode("utf-8") if isinstance(
856
+ subkey, six.text_type) else subkey
857
+ if isinstance(value, six.text_type):
858
+ new_info[new_key][new_subkey] = \
859
+ value.encode("utf-8")
860
+ else:
861
+ new_info[new_key][new_subkey] = value
800
862
  else:
801
- skip_chance = proxy_app.account_existence_skip_cache
802
- logger = proxy_app.logger
803
- info_type = 'container' if container else 'account'
804
- if skip_chance and random.random() < skip_chance:
805
- info = None
806
- if logger:
807
- logger.increment('%s.info.cache.skip' % info_type)
808
- else:
809
- info = memcache.get(cache_key)
810
- if logger:
811
- logger.increment('%s.info.cache.%s' % (
812
- info_type, 'hit' if info else 'miss'))
813
- if info and six.PY2:
814
- # Get back to native strings
815
- new_info = {}
816
- for key in info:
817
- new_key = key.encode("utf-8") if isinstance(
818
- key, six.text_type) else key
819
- if isinstance(info[key], six.text_type):
820
- new_info[new_key] = info[key].encode("utf-8")
821
- elif isinstance(info[key], dict):
822
- new_info[new_key] = {}
823
- for subkey, value in info[key].items():
824
- new_subkey = subkey.encode("utf-8") if isinstance(
825
- subkey, six.text_type) else subkey
826
- if isinstance(value, six.text_type):
827
- new_info[new_key][new_subkey] = \
828
- value.encode("utf-8")
829
- else:
830
- new_info[new_key][new_subkey] = value
831
- else:
832
- new_info[new_key] = info[key]
833
- info = new_info
834
- if info:
835
- env.setdefault('swift.infocache', {})[cache_key] = info
836
- return info
837
- return None
863
+ new_info[new_key] = info[key]
864
+ info = new_info
865
+ if info:
866
+ env.setdefault('swift.infocache', {})[cache_key] = info
867
+ return info, cache_state
838
868
 
839
869
 
840
870
  def _get_info_from_caches(app, env, account, container=None):
@@ -844,13 +874,16 @@ def _get_info_from_caches(app, env, account, container=None):
844
874
 
845
875
  :param app: the application object
846
876
  :param env: the environment used by the current request
847
- :returns: the cached info or None if not cached
877
+ :returns: a tuple of (the cached info or None if not cached, cache state)
848
878
  """
849
879
 
850
880
  info = _get_info_from_infocache(env, account, container)
851
- if info is None:
852
- info = _get_info_from_memcache(app, env, account, container)
853
- return info
881
+ if info:
882
+ cache_state = 'infocache_hit'
883
+ else:
884
+ info, cache_state = _get_info_from_memcache(
885
+ app, env, account, container)
886
+ return info, cache_state
854
887
 
855
888
 
856
889
  def _prepare_pre_auth_info_request(env, path, swift_source):
@@ -985,6 +1018,21 @@ def bytes_to_skip(record_size, range_start):
985
1018
  return (record_size - (range_start % record_size)) % record_size
986
1019
 
987
1020
 
1021
+ def is_good_source(status, server_type):
1022
+ """
1023
+ Indicates whether or not the request made to the backend found
1024
+ what it was looking for.
1025
+
1026
+ :param resp: the response from the backend.
1027
+ :param server_type: the type of server: 'Account', 'Container' or 'Object'.
1028
+ :returns: True if the response status code is acceptable, False if not.
1029
+ """
1030
+ if (server_type == 'Object' and
1031
+ status == HTTP_REQUESTED_RANGE_NOT_SATISFIABLE):
1032
+ return True
1033
+ return is_success(status) or is_redirection(status)
1034
+
1035
+
988
1036
  class ByteCountEnforcer(object):
989
1037
  """
990
1038
  Enforces that successive calls to file_like.read() give at least
@@ -1016,49 +1064,76 @@ class ByteCountEnforcer(object):
1016
1064
  return chunk
1017
1065
 
1018
1066
 
1019
- class GetOrHeadHandler(object):
1020
- def __init__(self, app, req, server_type, node_iter, partition, path,
1021
- backend_headers, concurrency=1, policy=None,
1022
- client_chunk_size=None, newest=None, logger=None):
1067
+ class GetterSource(object):
1068
+ __slots__ = ('app', 'resp', 'node', '_parts_iter')
1069
+
1070
+ def __init__(self, app, resp, node):
1023
1071
  self.app = app
1072
+ self.resp = resp
1073
+ self.node = node
1074
+ self._parts_iter = None
1075
+
1076
+ @property
1077
+ def timestamp(self):
1078
+ """
1079
+ Provide the timestamp of the swift http response as a floating
1080
+ point value. Used as a sort key.
1081
+
1082
+ :return: an instance of ``utils.Timestamp``
1083
+ """
1084
+ return Timestamp(self.resp.getheader('x-backend-data-timestamp') or
1085
+ self.resp.getheader('x-backend-timestamp') or
1086
+ self.resp.getheader('x-put-timestamp') or
1087
+ self.resp.getheader('x-timestamp') or 0)
1088
+
1089
+ @property
1090
+ def parts_iter(self):
1091
+ # lazy load a source response body parts iter if and when the source is
1092
+ # actually read
1093
+ if self.resp and not self._parts_iter:
1094
+ self._parts_iter = http_response_to_document_iters(
1095
+ self.resp, read_chunk_size=self.app.object_chunk_size)
1096
+ return self._parts_iter
1097
+
1098
+ def close(self):
1099
+ # Close-out the connection as best as possible.
1100
+ close_swift_conn(self.resp)
1101
+
1102
+
1103
+ class GetterBase(object):
1104
+ def __init__(self, app, req, node_iter, partition, policy,
1105
+ path, backend_headers, logger=None):
1106
+ self.app = app
1107
+ self.req = req
1024
1108
  self.node_iter = node_iter
1025
- self.server_type = server_type
1026
1109
  self.partition = partition
1110
+ self.policy = policy
1027
1111
  self.path = path
1028
1112
  self.backend_headers = backend_headers
1029
- self.client_chunk_size = client_chunk_size
1030
1113
  self.logger = logger or app.logger
1031
- self.skip_bytes = 0
1032
1114
  self.bytes_used_from_backend = 0
1033
- self.used_nodes = []
1034
- self.used_source_etag = ''
1035
- self.concurrency = concurrency
1036
- self.policy = policy
1037
- self.node = None
1038
- self.latest_404_timestamp = Timestamp(0)
1039
- policy_options = self.app.get_policy_options(self.policy)
1040
- self.rebalance_missing_suppression_count = min(
1041
- policy_options.rebalance_missing_suppression_count,
1042
- node_iter.num_primary_nodes - 1)
1115
+ self.source = None
1043
1116
 
1044
- # stuff from request
1045
- self.req_method = req.method
1046
- self.req_path = req.path
1047
- self.req_query_string = req.query_string
1048
- if newest is None:
1049
- self.newest = config_true_value(req.headers.get('x-newest', 'f'))
1050
- else:
1051
- self.newest = newest
1117
+ def _find_source(self):
1118
+ """
1119
+ Look for a suitable new source and if one is found then set
1120
+ ``self.source``.
1052
1121
 
1053
- # populated when finding source
1054
- self.statuses = []
1055
- self.reasons = []
1056
- self.bodies = []
1057
- self.source_headers = []
1058
- self.sources = []
1122
+ :return: ``True`` if ``self.source`` has been updated, ``False``
1123
+ otherwise.
1124
+ """
1125
+ # Subclasses must implement this method
1126
+ raise NotImplementedError()
1059
1127
 
1060
- # populated from response headers
1061
- self.start_byte = self.end_byte = self.length = None
1128
+ def _replace_source(self, err_msg):
1129
+ # _find_source can modify self.source so stash current source
1130
+ old_source = self.source
1131
+ if not self._find_source():
1132
+ return False
1133
+
1134
+ self.app.error_occurred(old_source.node, err_msg)
1135
+ old_source.close()
1136
+ return True
1062
1137
 
1063
1138
  def fast_forward(self, num_bytes):
1064
1139
  """
@@ -1072,6 +1147,9 @@ class GetOrHeadHandler(object):
1072
1147
  > end of range + 1
1073
1148
  :raises RangeAlreadyComplete: if begin + num_bytes == end of range + 1
1074
1149
  """
1150
+ self.backend_headers.pop(
1151
+ 'X-Backend-Ignore-Range-If-Metadata-Present', None)
1152
+
1075
1153
  try:
1076
1154
  req_range = Range(self.backend_headers.get('Range'))
1077
1155
  except ValueError:
@@ -1134,9 +1212,6 @@ class GetOrHeadHandler(object):
1134
1212
 
1135
1213
  def learn_size_from_content_range(self, start, end, length):
1136
1214
  """
1137
- If client_chunk_size is set, makes sure we yield things starting on
1138
- chunk boundaries based on the Content-Range header in the response.
1139
-
1140
1215
  Sets our Range header's first byterange to the value learned from
1141
1216
  the Content-Range header in the response; if we were given a
1142
1217
  fully-specified range (e.g. "bytes=123-456"), this is a no-op.
@@ -1149,9 +1224,6 @@ class GetOrHeadHandler(object):
1149
1224
  if length == 0:
1150
1225
  return
1151
1226
 
1152
- if self.client_chunk_size:
1153
- self.skip_bytes = bytes_to_skip(self.client_chunk_size, start)
1154
-
1155
1227
  if 'Range' in self.backend_headers:
1156
1228
  try:
1157
1229
  req_range = Range(self.backend_headers['Range'])
@@ -1166,201 +1238,133 @@ class GetOrHeadHandler(object):
1166
1238
  e if e is not None else '')
1167
1239
  for s, e in new_ranges)))
1168
1240
 
1169
- def is_good_source(self, src):
1170
- """
1171
- Indicates whether or not the request made to the backend found
1172
- what it was looking for.
1173
1241
 
1174
- :param src: the response from the backend
1175
- :returns: True if found, False if not
1176
- """
1177
- if self.server_type == 'Object' and src.status == 416:
1178
- return True
1179
- return is_success(src.status) or is_redirection(src.status)
1242
+ class GetOrHeadHandler(GetterBase):
1243
+ def __init__(self, app, req, server_type, node_iter, partition, path,
1244
+ backend_headers, concurrency=1, policy=None,
1245
+ newest=None, logger=None):
1246
+ super(GetOrHeadHandler, self).__init__(
1247
+ app=app, req=req, node_iter=node_iter,
1248
+ partition=partition, policy=policy, path=path,
1249
+ backend_headers=backend_headers, logger=logger)
1250
+ self.server_type = server_type
1251
+ self.used_nodes = []
1252
+ self.used_source_etag = None
1253
+ self.concurrency = concurrency
1254
+ self.latest_404_timestamp = Timestamp(0)
1255
+ if self.server_type == 'Object':
1256
+ self.node_timeout = self.app.recoverable_node_timeout
1257
+ else:
1258
+ self.node_timeout = self.app.node_timeout
1259
+ policy_options = self.app.get_policy_options(self.policy)
1260
+ self.rebalance_missing_suppression_count = min(
1261
+ policy_options.rebalance_missing_suppression_count,
1262
+ node_iter.num_primary_nodes - 1)
1180
1263
 
1181
- def _get_response_parts_iter(self, req, node, source):
1182
- # Someday we can replace this [mess] with python 3's "nonlocal"
1183
- source = [source]
1184
- node = [node]
1264
+ if newest is None:
1265
+ self.newest = config_true_value(req.headers.get('x-newest', 'f'))
1266
+ else:
1267
+ self.newest = newest
1185
1268
 
1186
- try:
1187
- client_chunk_size = self.client_chunk_size
1188
- node_timeout = self.app.node_timeout
1189
- if self.server_type == 'Object':
1190
- node_timeout = self.app.recoverable_node_timeout
1191
-
1192
- # This is safe; it sets up a generator but does not call next()
1193
- # on it, so no IO is performed.
1194
- parts_iter = [
1195
- http_response_to_document_iters(
1196
- source[0], read_chunk_size=self.app.object_chunk_size)]
1197
-
1198
- def get_next_doc_part():
1199
- while True:
1200
- try:
1201
- # This call to next() performs IO when we have a
1202
- # multipart/byteranges response; it reads the MIME
1203
- # boundary and part headers.
1204
- #
1205
- # If we don't have a multipart/byteranges response,
1206
- # but just a 200 or a single-range 206, then this
1207
- # performs no IO, and either just returns source or
1208
- # raises StopIteration.
1209
- with WatchdogTimeout(self.app.watchdog, node_timeout,
1210
- ChunkReadTimeout):
1211
- # if StopIteration is raised, it escapes and is
1212
- # handled elsewhere
1213
- start_byte, end_byte, length, headers, part = next(
1214
- parts_iter[0])
1215
- return (start_byte, end_byte, length, headers, part)
1216
- except ChunkReadTimeout:
1217
- new_source, new_node = self._get_source_and_node()
1218
- if new_source:
1219
- self.app.error_occurred(
1220
- node[0], 'Trying to read object during '
1221
- 'GET (retrying)')
1222
- # Close-out the connection as best as possible.
1223
- if getattr(source[0], 'swift_conn', None):
1224
- close_swift_conn(source[0])
1225
- source[0] = new_source
1226
- node[0] = new_node
1227
- # This is safe; it sets up a generator but does
1228
- # not call next() on it, so no IO is performed.
1229
- parts_iter[0] = http_response_to_document_iters(
1230
- new_source,
1231
- read_chunk_size=self.app.object_chunk_size)
1232
- else:
1233
- raise StopIteration()
1234
-
1235
- def iter_bytes_from_response_part(part_file, nbytes):
1236
- nchunks = 0
1237
- buf = b''
1238
- part_file = ByteCountEnforcer(part_file, nbytes)
1239
- while True:
1240
- try:
1241
- with WatchdogTimeout(self.app.watchdog, node_timeout,
1242
- ChunkReadTimeout):
1243
- chunk = part_file.read(self.app.object_chunk_size)
1244
- nchunks += 1
1245
- # NB: this append must be *inside* the context
1246
- # manager for test.unit.SlowBody to do its thing
1247
- buf += chunk
1248
- if nbytes is not None:
1249
- nbytes -= len(chunk)
1250
- except (ChunkReadTimeout, ShortReadError):
1251
- exc_type, exc_value, exc_traceback = exc_info()
1252
- if self.newest or self.server_type != 'Object':
1253
- raise
1254
- try:
1255
- self.fast_forward(self.bytes_used_from_backend)
1256
- except (HTTPException, ValueError):
1257
- six.reraise(exc_type, exc_value, exc_traceback)
1258
- except RangeAlreadyComplete:
1259
- break
1260
- buf = b''
1261
- new_source, new_node = self._get_source_and_node()
1262
- if new_source:
1263
- self.app.error_occurred(
1264
- node[0], 'Trying to read object during '
1265
- 'GET (retrying)')
1266
- # Close-out the connection as best as possible.
1267
- if getattr(source[0], 'swift_conn', None):
1268
- close_swift_conn(source[0])
1269
- source[0] = new_source
1270
- node[0] = new_node
1271
- # This is safe; it just sets up a generator but
1272
- # does not call next() on it, so no IO is
1273
- # performed.
1274
- parts_iter[0] = http_response_to_document_iters(
1275
- new_source,
1276
- read_chunk_size=self.app.object_chunk_size)
1277
-
1278
- try:
1279
- _junk, _junk, _junk, _junk, part_file = \
1280
- get_next_doc_part()
1281
- except StopIteration:
1282
- # Tried to find a new node from which to
1283
- # finish the GET, but failed. There's
1284
- # nothing more we can do here.
1285
- six.reraise(exc_type, exc_value, exc_traceback)
1286
- part_file = ByteCountEnforcer(part_file, nbytes)
1287
- else:
1288
- six.reraise(exc_type, exc_value, exc_traceback)
1289
- else:
1290
- if buf and self.skip_bytes:
1291
- if self.skip_bytes < len(buf):
1292
- buf = buf[self.skip_bytes:]
1293
- self.bytes_used_from_backend += self.skip_bytes
1294
- self.skip_bytes = 0
1295
- else:
1296
- self.skip_bytes -= len(buf)
1297
- self.bytes_used_from_backend += len(buf)
1298
- buf = b''
1299
-
1300
- if not chunk:
1301
- if buf:
1302
- with WatchdogTimeout(self.app.watchdog,
1303
- self.app.client_timeout,
1304
- ChunkWriteTimeout):
1305
- self.bytes_used_from_backend += len(buf)
1306
- yield buf
1307
- buf = b''
1308
- break
1309
-
1310
- if client_chunk_size is not None:
1311
- while len(buf) >= client_chunk_size:
1312
- client_chunk = buf[:client_chunk_size]
1313
- buf = buf[client_chunk_size:]
1314
- with WatchdogTimeout(self.app.watchdog,
1315
- self.app.client_timeout,
1316
- ChunkWriteTimeout):
1317
- self.bytes_used_from_backend += \
1318
- len(client_chunk)
1319
- yield client_chunk
1320
- else:
1321
- with WatchdogTimeout(self.app.watchdog,
1322
- self.app.client_timeout,
1323
- ChunkWriteTimeout):
1324
- self.bytes_used_from_backend += len(buf)
1325
- yield buf
1326
- buf = b''
1327
-
1328
- # This is for fairness; if the network is outpacing
1329
- # the CPU, we'll always be able to read and write
1330
- # data without encountering an EWOULDBLOCK, and so
1331
- # eventlet will not switch greenthreads on its own.
1332
- # We do it manually so that clients don't starve.
1333
- #
1334
- # The number 5 here was chosen by making stuff up.
1335
- # It's not every single chunk, but it's not too big
1336
- # either, so it seemed like it would probably be an
1337
- # okay choice.
1338
- #
1339
- # Note that we may trampoline to other greenthreads
1340
- # more often than once every 5 chunks, depending on
1341
- # how blocking our network IO is; the explicit sleep
1342
- # here simply provides a lower bound on the rate of
1343
- # trampolining.
1344
- if nchunks % 5 == 0:
1345
- sleep()
1269
+ # populated when finding source
1270
+ self.statuses = []
1271
+ self.reasons = []
1272
+ self.bodies = []
1273
+ self.source_headers = []
1274
+ self.sources = []
1346
1275
 
1276
+ # populated from response headers
1277
+ self.start_byte = self.end_byte = self.length = None
1278
+
1279
+ def _get_next_response_part(self):
1280
+ # return the next part of the response body; there may only be one part
1281
+ # unless it's a multipart/byteranges response
1282
+ while True:
1283
+ try:
1284
+ # This call to next() performs IO when we have a
1285
+ # multipart/byteranges response; it reads the MIME
1286
+ # boundary and part headers.
1287
+ #
1288
+ # If we don't have a multipart/byteranges response,
1289
+ # but just a 200 or a single-range 206, then this
1290
+ # performs no IO, and either just returns source or
1291
+ # raises StopIteration.
1292
+ with WatchdogTimeout(self.app.watchdog, self.node_timeout,
1293
+ ChunkReadTimeout):
1294
+ # if StopIteration is raised, it escapes and is
1295
+ # handled elsewhere
1296
+ start_byte, end_byte, length, headers, part = next(
1297
+ self.source.parts_iter)
1298
+ return (start_byte, end_byte, length, headers, part)
1299
+ except ChunkReadTimeout:
1300
+ if not self._replace_source(
1301
+ 'Trying to read object during GET (retrying)'):
1302
+ raise StopIteration()
1303
+
1304
+ def _iter_bytes_from_response_part(self, part_file, nbytes):
1305
+ # yield chunks of bytes from a single response part; if an error
1306
+ # occurs, try to resume yielding bytes from a different source
1307
+ part_file = ByteCountEnforcer(part_file, nbytes)
1308
+ while True:
1309
+ try:
1310
+ with WatchdogTimeout(self.app.watchdog, self.node_timeout,
1311
+ ChunkReadTimeout):
1312
+ chunk = part_file.read(self.app.object_chunk_size)
1313
+ if nbytes is not None:
1314
+ nbytes -= len(chunk)
1315
+ except (ChunkReadTimeout, ShortReadError):
1316
+ exc_type, exc_value, exc_traceback = exc_info()
1317
+ if self.newest or self.server_type != 'Object':
1318
+ raise
1319
+ try:
1320
+ self.fast_forward(self.bytes_used_from_backend)
1321
+ except (HTTPException, ValueError):
1322
+ six.reraise(exc_type, exc_value, exc_traceback)
1323
+ except RangeAlreadyComplete:
1324
+ break
1325
+ if self._replace_source(
1326
+ 'Trying to read object during GET (retrying)'):
1327
+ try:
1328
+ _junk, _junk, _junk, _junk, part_file = \
1329
+ self._get_next_response_part()
1330
+ except StopIteration:
1331
+ # Tried to find a new node from which to
1332
+ # finish the GET, but failed. There's
1333
+ # nothing more we can do here.
1334
+ six.reraise(exc_type, exc_value, exc_traceback)
1335
+ part_file = ByteCountEnforcer(part_file, nbytes)
1336
+ else:
1337
+ six.reraise(exc_type, exc_value, exc_traceback)
1338
+ else:
1339
+ if not chunk:
1340
+ break
1341
+
1342
+ with WatchdogTimeout(self.app.watchdog,
1343
+ self.app.client_timeout,
1344
+ ChunkWriteTimeout):
1345
+ self.bytes_used_from_backend += len(chunk)
1346
+ yield chunk
1347
+
1348
+ def _iter_parts_from_response(self, req):
1349
+ # iterate over potentially multiple response body parts; for each
1350
+ # part, yield an iterator over the part's bytes
1351
+ try:
1347
1352
  part_iter = None
1348
1353
  try:
1349
1354
  while True:
1350
1355
  start_byte, end_byte, length, headers, part = \
1351
- get_next_doc_part()
1352
- # note: learn_size_from_content_range() sets
1353
- # self.skip_bytes
1356
+ self._get_next_response_part()
1354
1357
  self.learn_size_from_content_range(
1355
1358
  start_byte, end_byte, length)
1356
1359
  self.bytes_used_from_backend = 0
1357
1360
  # not length; that refers to the whole object, so is the
1358
1361
  # wrong value to use for GET-range responses
1359
- byte_count = ((end_byte - start_byte + 1) - self.skip_bytes
1362
+ byte_count = ((end_byte - start_byte + 1)
1360
1363
  if (end_byte is not None
1361
1364
  and start_byte is not None)
1362
1365
  else None)
1363
- part_iter = iter_bytes_from_response_part(part, byte_count)
1366
+ part_iter = CooperativeIterator(
1367
+ self._iter_bytes_from_response_part(part, byte_count))
1364
1368
  yield {'start_byte': start_byte, 'end_byte': end_byte,
1365
1369
  'entity_length': length, 'headers': headers,
1366
1370
  'part_iter': part_iter}
@@ -1372,7 +1376,7 @@ class GetOrHeadHandler(object):
1372
1376
  part_iter.close()
1373
1377
 
1374
1378
  except ChunkReadTimeout:
1375
- self.app.exception_occurred(node[0], 'Object',
1379
+ self.app.exception_occurred(self.source.node, 'Object',
1376
1380
  'Trying to read during GET')
1377
1381
  raise
1378
1382
  except ChunkWriteTimeout:
@@ -1398,9 +1402,7 @@ class GetOrHeadHandler(object):
1398
1402
  self.logger.exception('Trying to send to client')
1399
1403
  raise
1400
1404
  finally:
1401
- # Close-out the connection as best as possible.
1402
- if getattr(source[0], 'swift_conn', None):
1403
- close_swift_conn(source[0])
1405
+ self.source.close()
1404
1406
 
1405
1407
  @property
1406
1408
  def last_status(self):
@@ -1417,6 +1419,10 @@ class GetOrHeadHandler(object):
1417
1419
  return None
1418
1420
 
1419
1421
  def _make_node_request(self, node, node_timeout, logger_thread_locals):
1422
+ # make a backend request; return True if the response is deemed good
1423
+ # (has an acceptable status code), useful (matches any previously
1424
+ # discovered etag) and sufficient (a single good response is
1425
+ # insufficient when we're searching for the newest timestamp)
1420
1426
  self.logger.thread_locals = logger_thread_locals
1421
1427
  if node in self.used_nodes:
1422
1428
  return False
@@ -1427,9 +1433,9 @@ class GetOrHeadHandler(object):
1427
1433
  with ConnectionTimeout(self.app.conn_timeout):
1428
1434
  conn = http_connect(
1429
1435
  ip, port, node['device'],
1430
- self.partition, self.req_method, self.path,
1436
+ self.partition, self.req.method, self.path,
1431
1437
  headers=req_headers,
1432
- query_string=self.req_query_string)
1438
+ query_string=self.req.query_string)
1433
1439
  self.app.set_node_timing(node, time.time() - start_node_timing)
1434
1440
 
1435
1441
  with Timeout(node_timeout):
@@ -1440,13 +1446,13 @@ class GetOrHeadHandler(object):
1440
1446
  self.app.exception_occurred(
1441
1447
  node, self.server_type,
1442
1448
  'Trying to %(method)s %(path)s' %
1443
- {'method': self.req_method, 'path': self.req_path})
1449
+ {'method': self.req.method, 'path': self.req.path})
1444
1450
  return False
1445
1451
 
1446
1452
  src_headers = dict(
1447
1453
  (k.lower(), v) for k, v in
1448
1454
  possible_source.getheaders())
1449
- if self.is_good_source(possible_source):
1455
+ if is_good_source(possible_source.status, self.server_type):
1450
1456
  # 404 if we know we don't have a synced copy
1451
1457
  if not float(possible_source.getheader('X-PUT-Timestamp', 1)):
1452
1458
  self.statuses.append(HTTP_NOT_FOUND)
@@ -1476,7 +1482,8 @@ class GetOrHeadHandler(object):
1476
1482
  self.reasons.append(possible_source.reason)
1477
1483
  self.bodies.append(None)
1478
1484
  self.source_headers.append(possible_source.getheaders())
1479
- self.sources.append((possible_source, node))
1485
+ self.sources.append(
1486
+ GetterSource(self.app, possible_source, node))
1480
1487
  if not self.newest: # one good source is enough
1481
1488
  return True
1482
1489
  else:
@@ -1510,11 +1517,11 @@ class GetOrHeadHandler(object):
1510
1517
  if ts > self.latest_404_timestamp:
1511
1518
  self.latest_404_timestamp = ts
1512
1519
  self.app.check_response(node, self.server_type, possible_source,
1513
- self.req_method, self.path,
1520
+ self.req.method, self.path,
1514
1521
  self.bodies[-1])
1515
1522
  return False
1516
1523
 
1517
- def _get_source_and_node(self):
1524
+ def _find_source(self):
1518
1525
  self.statuses = []
1519
1526
  self.reasons = []
1520
1527
  self.bodies = []
@@ -1545,41 +1552,38 @@ class GetOrHeadHandler(object):
1545
1552
  # and added to the list in the case of x-newest.
1546
1553
  if self.sources:
1547
1554
  self.sources = [s for s in self.sources
1548
- if source_key(s[0]) >= self.latest_404_timestamp]
1555
+ if s.timestamp >= self.latest_404_timestamp]
1549
1556
 
1550
1557
  if self.sources:
1551
- self.sources.sort(key=lambda s: source_key(s[0]))
1552
- source, node = self.sources.pop()
1553
- for src, _junk in self.sources:
1554
- close_swift_conn(src)
1555
- self.used_nodes.append(node)
1556
- src_headers = dict(
1557
- (k.lower(), v) for k, v in
1558
- source.getheaders())
1558
+ self.sources.sort(key=operator.attrgetter('timestamp'))
1559
+ source = self.sources.pop()
1560
+ for unused_source in self.sources:
1561
+ unused_source.close()
1562
+ self.used_nodes.append(source.node)
1559
1563
 
1560
1564
  # Save off the source etag so that, if we lose the connection
1561
1565
  # and have to resume from a different node, we can be sure that
1562
1566
  # we have the same object (replication). Otherwise, if the cluster
1563
1567
  # has two versions of the same object, we might end up switching
1564
1568
  # between old and new mid-stream and giving garbage to the client.
1565
- self.used_source_etag = normalize_etag(src_headers.get('etag', ''))
1566
- self.node = node
1567
- return source, node
1568
- return None, None
1569
+ if self.used_source_etag is None:
1570
+ self.used_source_etag = normalize_etag(
1571
+ source.resp.getheader('etag', ''))
1572
+ self.source = source
1573
+ return True
1574
+ return False
1569
1575
 
1570
- def _make_app_iter(self, req, node, source):
1576
+ def _make_app_iter(self, req):
1571
1577
  """
1572
1578
  Returns an iterator over the contents of the source (via its read
1573
1579
  func). There is also quite a bit of cleanup to ensure garbage
1574
1580
  collection works and the underlying socket of the source is closed.
1575
1581
 
1576
1582
  :param req: incoming request object
1577
- :param source: The httplib.Response object this iterator should read
1578
- from.
1579
- :param node: The node the source is reading from, for logging purposes.
1583
+ :return: an iterator that yields chunks of response body bytes
1580
1584
  """
1581
1585
 
1582
- ct = source.getheader('Content-Type')
1586
+ ct = self.source.resp.getheader('Content-Type')
1583
1587
  if ct:
1584
1588
  content_type, content_type_attrs = parse_content_type(ct)
1585
1589
  is_multipart = content_type == 'multipart/byteranges'
@@ -1592,7 +1596,7 @@ class GetOrHeadHandler(object):
1592
1596
  # furnished one for us, so we'll just re-use it
1593
1597
  boundary = dict(content_type_attrs)["boundary"]
1594
1598
 
1595
- parts_iter = self._get_response_parts_iter(req, node, source)
1599
+ parts_iter = self._iter_parts_from_response(req)
1596
1600
 
1597
1601
  def add_content_type(response_part):
1598
1602
  response_part["content_type"] = \
@@ -1604,25 +1608,25 @@ class GetOrHeadHandler(object):
1604
1608
  boundary, is_multipart, self.logger)
1605
1609
 
1606
1610
  def get_working_response(self, req):
1607
- source, node = self._get_source_and_node()
1608
1611
  res = None
1609
- if source:
1612
+ if self._find_source():
1610
1613
  res = Response(request=req)
1611
- res.status = source.status
1612
- update_headers(res, source.getheaders())
1614
+ res.status = self.source.resp.status
1615
+ update_headers(res, self.source.resp.getheaders())
1613
1616
  if req.method == 'GET' and \
1614
- source.status in (HTTP_OK, HTTP_PARTIAL_CONTENT):
1615
- res.app_iter = self._make_app_iter(req, node, source)
1617
+ self.source.resp.status in (HTTP_OK, HTTP_PARTIAL_CONTENT):
1618
+ res.app_iter = self._make_app_iter(req)
1616
1619
  # See NOTE: swift_conn at top of file about this.
1617
- res.swift_conn = source.swift_conn
1620
+ res.swift_conn = self.source.resp.swift_conn
1618
1621
  if not res.environ:
1619
1622
  res.environ = {}
1620
- res.environ['swift_x_timestamp'] = source.getheader('x-timestamp')
1623
+ res.environ['swift_x_timestamp'] = self.source.resp.getheader(
1624
+ 'x-timestamp')
1621
1625
  res.accept_ranges = 'bytes'
1622
- res.content_length = source.getheader('Content-Length')
1623
- if source.getheader('Content-Type'):
1626
+ res.content_length = self.source.resp.getheader('Content-Length')
1627
+ if self.source.resp.getheader('Content-Type'):
1624
1628
  res.charset = None
1625
- res.content_type = source.getheader('Content-Type')
1629
+ res.content_type = self.source.resp.getheader('Content-Type')
1626
1630
  return res
1627
1631
 
1628
1632
 
@@ -1845,16 +1849,22 @@ class Controller(object):
1845
1849
  :param transfer: If True, transfer headers from original client request
1846
1850
  :returns: a dictionary of headers
1847
1851
  """
1848
- # Use the additional headers first so they don't overwrite the headers
1849
- # we require.
1850
- headers = HeaderKeyDict(additional) if additional else HeaderKeyDict()
1851
- if transfer:
1852
- self.transfer_headers(orig_req.headers, headers)
1853
- headers.setdefault('x-timestamp', Timestamp.now().internal)
1852
+ headers = HeaderKeyDict()
1854
1853
  if orig_req:
1854
+ headers.update((k.lower(), v)
1855
+ for k, v in orig_req.headers.items()
1856
+ if k.lower().startswith('x-backend-'))
1855
1857
  referer = orig_req.as_referer()
1856
1858
  else:
1857
1859
  referer = ''
1860
+ # additional headers can override x-backend-* headers from orig_req
1861
+ if additional:
1862
+ headers.update(additional)
1863
+ if orig_req and transfer:
1864
+ # transfer headers from orig_req can override additional headers
1865
+ self.transfer_headers(orig_req.headers, headers)
1866
+ headers.setdefault('x-timestamp', Timestamp.now().internal)
1867
+ # orig_req and additional headers cannot override the following...
1858
1868
  headers['x-trans-id'] = self.trans_id
1859
1869
  headers['connection'] = 'close'
1860
1870
  headers['user-agent'] = self.app.backend_user_agent
@@ -1997,7 +2007,7 @@ class Controller(object):
1997
2007
  :returns: a swob.Response object
1998
2008
  """
1999
2009
  nodes = GreenthreadSafeIterator(
2000
- node_iterator or self.app.iter_nodes(ring, part, self.logger, req)
2010
+ node_iterator or NodeIter(self.app, ring, part, self.logger, req)
2001
2011
  )
2002
2012
  node_number = node_count or len(ring.get_part_nodes(part))
2003
2013
  pile = GreenAsyncPile(node_number)
@@ -2167,19 +2177,19 @@ class Controller(object):
2167
2177
  headers.update((k, v)
2168
2178
  for k, v in req.headers.items()
2169
2179
  if is_sys_meta('account', k))
2170
- resp = self.make_requests(Request.blank('/v1' + path),
2180
+ resp = self.make_requests(Request.blank(str_to_wsgi('/v1' + path)),
2171
2181
  self.app.account_ring, partition, 'PUT',
2172
2182
  path, [headers] * len(nodes))
2173
2183
  if is_success(resp.status_int):
2174
2184
  self.logger.info('autocreate account %r', path)
2175
- clear_info_cache(self.app, req.environ, account)
2185
+ clear_info_cache(req.environ, account)
2176
2186
  return True
2177
2187
  else:
2178
2188
  self.logger.warning('Could not autocreate account %r', path)
2179
2189
  return False
2180
2190
 
2181
2191
  def GETorHEAD_base(self, req, server_type, node_iter, partition, path,
2182
- concurrency=1, policy=None, client_chunk_size=None):
2192
+ concurrency=1, policy=None):
2183
2193
  """
2184
2194
  Base handler for HTTP GET or HEAD requests.
2185
2195
 
@@ -2190,7 +2200,6 @@ class Controller(object):
2190
2200
  :param path: path for the request
2191
2201
  :param concurrency: number of requests to run concurrently
2192
2202
  :param policy: the policy instance, or None if Account or Container
2193
- :param client_chunk_size: chunk size for response body iterator
2194
2203
  :returns: swob.Response object
2195
2204
  """
2196
2205
  backend_headers = self.generate_request_headers(
@@ -2199,7 +2208,6 @@ class Controller(object):
2199
2208
  handler = GetOrHeadHandler(self.app, req, self.server_type, node_iter,
2200
2209
  partition, path, backend_headers,
2201
2210
  concurrency, policy=policy,
2202
- client_chunk_size=client_chunk_size,
2203
2211
  logger=self.logger)
2204
2212
  res = handler.get_working_response(req)
2205
2213
 
@@ -2417,7 +2425,7 @@ class Controller(object):
2417
2425
  params.pop('limit', None)
2418
2426
  params['format'] = 'json'
2419
2427
  if includes:
2420
- params['includes'] = includes
2428
+ params['includes'] = str_to_wsgi(includes)
2421
2429
  if states:
2422
2430
  params['states'] = states
2423
2431
  headers = {'X-Backend-Record-Type': 'shard'}