swift 2.32.0__py2.py3-none-any.whl → 2.34.0__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 (127) hide show
  1. swift/account/auditor.py +11 -0
  2. swift/account/reaper.py +11 -1
  3. swift/account/replicator.py +22 -0
  4. swift/account/server.py +13 -12
  5. swift-2.32.0.data/scripts/swift-account-audit → swift/cli/account_audit.py +6 -2
  6. swift-2.32.0.data/scripts/swift-config → swift/cli/config.py +1 -1
  7. swift-2.32.0.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +6 -2
  8. swift-2.32.0.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +12 -3
  9. swift-2.32.0.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +6 -2
  10. swift/cli/info.py +131 -3
  11. swift-2.32.0.data/scripts/swift-oldies → swift/cli/oldies.py +6 -3
  12. swift-2.32.0.data/scripts/swift-orphans → swift/cli/orphans.py +7 -2
  13. swift-2.32.0.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +9 -18
  14. swift-2.32.0.data/scripts/swift-reconciler-enqueue → swift/cli/reconciler_enqueue.py +2 -3
  15. swift/cli/relinker.py +1 -1
  16. swift/cli/reload.py +141 -0
  17. swift/cli/ringbuilder.py +24 -0
  18. swift/common/daemon.py +12 -2
  19. swift/common/db.py +14 -9
  20. swift/common/db_auditor.py +2 -2
  21. swift/common/db_replicator.py +6 -0
  22. swift/common/exceptions.py +12 -0
  23. swift/common/http_protocol.py +76 -3
  24. swift/common/manager.py +120 -5
  25. swift/common/memcached.py +24 -25
  26. swift/common/middleware/account_quotas.py +144 -43
  27. swift/common/middleware/backend_ratelimit.py +166 -24
  28. swift/common/middleware/catch_errors.py +1 -3
  29. swift/common/middleware/cname_lookup.py +3 -5
  30. swift/common/middleware/container_sync.py +6 -10
  31. swift/common/middleware/crypto/crypto_utils.py +4 -5
  32. swift/common/middleware/crypto/decrypter.py +4 -5
  33. swift/common/middleware/crypto/kms_keymaster.py +2 -1
  34. swift/common/middleware/proxy_logging.py +57 -43
  35. swift/common/middleware/ratelimit.py +6 -7
  36. swift/common/middleware/recon.py +6 -7
  37. swift/common/middleware/s3api/acl_handlers.py +10 -1
  38. swift/common/middleware/s3api/controllers/__init__.py +3 -0
  39. swift/common/middleware/s3api/controllers/acl.py +3 -2
  40. swift/common/middleware/s3api/controllers/logging.py +2 -2
  41. swift/common/middleware/s3api/controllers/multi_upload.py +31 -15
  42. swift/common/middleware/s3api/controllers/obj.py +20 -1
  43. swift/common/middleware/s3api/controllers/object_lock.py +44 -0
  44. swift/common/middleware/s3api/s3api.py +6 -0
  45. swift/common/middleware/s3api/s3request.py +190 -74
  46. swift/common/middleware/s3api/s3response.py +48 -8
  47. swift/common/middleware/s3api/s3token.py +2 -2
  48. swift/common/middleware/s3api/utils.py +2 -1
  49. swift/common/middleware/slo.py +508 -310
  50. swift/common/middleware/staticweb.py +45 -14
  51. swift/common/middleware/tempauth.py +6 -4
  52. swift/common/middleware/tempurl.py +134 -93
  53. swift/common/middleware/x_profile/exceptions.py +1 -4
  54. swift/common/middleware/x_profile/html_viewer.py +9 -10
  55. swift/common/middleware/x_profile/profile_model.py +1 -2
  56. swift/common/middleware/xprofile.py +1 -2
  57. swift/common/request_helpers.py +101 -8
  58. swift/common/statsd_client.py +207 -0
  59. swift/common/storage_policy.py +1 -1
  60. swift/common/swob.py +5 -2
  61. swift/common/utils/__init__.py +331 -1774
  62. swift/common/utils/base.py +138 -0
  63. swift/common/utils/config.py +443 -0
  64. swift/common/utils/logs.py +999 -0
  65. swift/common/utils/timestamp.py +23 -2
  66. swift/common/wsgi.py +19 -3
  67. swift/container/auditor.py +11 -0
  68. swift/container/backend.py +136 -31
  69. swift/container/reconciler.py +11 -2
  70. swift/container/replicator.py +64 -7
  71. swift/container/server.py +276 -146
  72. swift/container/sharder.py +86 -42
  73. swift/container/sync.py +11 -1
  74. swift/container/updater.py +12 -2
  75. swift/obj/auditor.py +20 -3
  76. swift/obj/diskfile.py +63 -25
  77. swift/obj/expirer.py +154 -47
  78. swift/obj/mem_diskfile.py +2 -1
  79. swift/obj/mem_server.py +1 -0
  80. swift/obj/reconstructor.py +28 -4
  81. swift/obj/replicator.py +63 -24
  82. swift/obj/server.py +76 -59
  83. swift/obj/updater.py +12 -2
  84. swift/obj/watchers/dark_data.py +72 -34
  85. swift/proxy/controllers/account.py +3 -2
  86. swift/proxy/controllers/base.py +254 -148
  87. swift/proxy/controllers/container.py +274 -289
  88. swift/proxy/controllers/obj.py +120 -166
  89. swift/proxy/server.py +17 -13
  90. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/AUTHORS +14 -4
  91. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/METADATA +9 -7
  92. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/RECORD +97 -120
  93. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/entry_points.txt +39 -0
  94. swift-2.34.0.dist-info/pbr.json +1 -0
  95. swift-2.32.0.data/scripts/swift-account-auditor +0 -23
  96. swift-2.32.0.data/scripts/swift-account-info +0 -52
  97. swift-2.32.0.data/scripts/swift-account-reaper +0 -23
  98. swift-2.32.0.data/scripts/swift-account-replicator +0 -34
  99. swift-2.32.0.data/scripts/swift-account-server +0 -23
  100. swift-2.32.0.data/scripts/swift-container-auditor +0 -23
  101. swift-2.32.0.data/scripts/swift-container-info +0 -56
  102. swift-2.32.0.data/scripts/swift-container-reconciler +0 -21
  103. swift-2.32.0.data/scripts/swift-container-replicator +0 -34
  104. swift-2.32.0.data/scripts/swift-container-server +0 -23
  105. swift-2.32.0.data/scripts/swift-container-sharder +0 -37
  106. swift-2.32.0.data/scripts/swift-container-sync +0 -23
  107. swift-2.32.0.data/scripts/swift-container-updater +0 -23
  108. swift-2.32.0.data/scripts/swift-dispersion-report +0 -24
  109. swift-2.32.0.data/scripts/swift-form-signature +0 -20
  110. swift-2.32.0.data/scripts/swift-init +0 -119
  111. swift-2.32.0.data/scripts/swift-object-auditor +0 -29
  112. swift-2.32.0.data/scripts/swift-object-expirer +0 -33
  113. swift-2.32.0.data/scripts/swift-object-info +0 -60
  114. swift-2.32.0.data/scripts/swift-object-reconstructor +0 -33
  115. swift-2.32.0.data/scripts/swift-object-relinker +0 -23
  116. swift-2.32.0.data/scripts/swift-object-replicator +0 -37
  117. swift-2.32.0.data/scripts/swift-object-server +0 -27
  118. swift-2.32.0.data/scripts/swift-object-updater +0 -23
  119. swift-2.32.0.data/scripts/swift-proxy-server +0 -23
  120. swift-2.32.0.data/scripts/swift-recon +0 -24
  121. swift-2.32.0.data/scripts/swift-ring-builder +0 -37
  122. swift-2.32.0.data/scripts/swift-ring-builder-analyzer +0 -22
  123. swift-2.32.0.data/scripts/swift-ring-composer +0 -22
  124. swift-2.32.0.dist-info/pbr.json +0 -1
  125. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/LICENSE +0 -0
  126. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/WHEEL +0 -0
  127. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/top_level.txt +0 -0
@@ -270,11 +270,27 @@ A GET request with the query parameters::
270
270
  will return the contents of the original manifest as it was sent by the client.
271
271
  The main purpose for both calls is solely debugging.
272
272
 
273
- When the manifest object is uploaded you are more or less guaranteed that
274
- every segment in the manifest exists and matched the specifications.
275
- However, there is nothing that prevents the user from breaking the
276
- SLO download by deleting/replacing a segment referenced in the manifest. It is
277
- left to the user to use caution in handling the segments.
273
+ A GET request to a manifest object with the query parameter::
274
+
275
+ ?part-number=<n>
276
+
277
+ will return the contents of the ``nth`` segment. Segments are indexed from 1,
278
+ so ``n`` must be an integer between 1 and the total number of segments in the
279
+ manifest. The response status will be ``206 Partial Content`` and its headers
280
+ will include: an ``X-Parts-Count`` header equal to the total number of
281
+ segments; a ``Content-Length`` header equal to the length of the specified
282
+ segment; a ``Content-Range`` header describing the byte range of the specified
283
+ part within the SLO. A HEAD request with a ``part-number`` parameter will also
284
+ return a response with status ``206 Partial Content`` and the same headers.
285
+
286
+ .. note::
287
+
288
+ When the manifest object is uploaded you are more or less guaranteed that
289
+ every segment in the manifest exists and matched the specifications.
290
+ However, there is nothing that prevents the user from breaking the SLO
291
+ download by deleting/replacing a segment referenced in the manifest. It is
292
+ left to the user to use caution in handling the segments.
293
+
278
294
 
279
295
  -----------------------
280
296
  Deleting a Large Object
@@ -334,6 +350,7 @@ import time
334
350
  import six
335
351
 
336
352
  from swift.cli.container_deleter import make_delete_jobs
353
+ from swift.common.header_key_dict import HeaderKeyDict
337
354
  from swift.common.exceptions import ListingIterError, SegmentError
338
355
  from swift.common.middleware.listing_formats import \
339
356
  MAX_CONTAINER_LISTING_CONTENT_LENGTH
@@ -345,15 +362,16 @@ from swift.common.swob import Request, HTTPBadRequest, HTTPServerError, \
345
362
  RESPONSE_REASONS, str_to_wsgi, bytes_to_wsgi, wsgi_to_str, wsgi_quote
346
363
  from swift.common.utils import get_logger, config_true_value, \
347
364
  get_valid_utf8_str, override_bytes_from_content_type, split_path, \
348
- RateLimitedIterator, quote, close_if_possible, closing_if_possible, \
349
- LRUCache, StreamingPile, strict_b64decode, Timestamp, drain_and_close, \
365
+ RateLimitedIterator, quote, closing_if_possible, \
366
+ LRUCache, StreamingPile, strict_b64decode, Timestamp, friendly_close, \
350
367
  get_expirer_container, md5
351
368
  from swift.common.registry import register_swift_info
352
369
  from swift.common.request_helpers import SegmentedIterable, \
353
370
  get_sys_meta_prefix, update_etag_is_at_header, resolve_etag_is_at_header, \
354
- get_container_update_override_key, update_ignore_range_header
371
+ get_container_update_override_key, update_ignore_range_header, \
372
+ get_param, get_valid_part_num
355
373
  from swift.common.constraints import check_utf8, AUTO_CREATE_ACCOUNT_PREFIX
356
- from swift.common.http import HTTP_NOT_FOUND, HTTP_UNAUTHORIZED, is_success
374
+ from swift.common.http import HTTP_NOT_FOUND, HTTP_UNAUTHORIZED
357
375
  from swift.common.wsgi import WSGIContext, make_subrequest, make_env, \
358
376
  make_pre_authed_request
359
377
  from swift.common.middleware.bulk import get_response_body, \
@@ -530,6 +548,183 @@ def parse_and_validate_input(req_body, req_path):
530
548
  return parsed_data
531
549
 
532
550
 
551
+ def _annotate_segments(segments, logger=None):
552
+ """
553
+ Decode any inlined data and update sub_slo segments bytes from content-type
554
+ when available; then annotate segment dicts in segments list with
555
+ 'segment_length'.
556
+
557
+ N.B. raw_data segments don't have a bytes key and range-segments need to
558
+ calculate their length from their range key but afterwards all segments
559
+ dicts will have 'segment_length' representing the length of the segment.
560
+ """
561
+ for seg_dict in segments:
562
+ if 'data' in seg_dict:
563
+ seg_dict['raw_data'] = base64.b64decode(seg_dict.pop('data'))
564
+ segment_length = len(seg_dict['raw_data'])
565
+ else:
566
+ if config_true_value(seg_dict.get('sub_slo')):
567
+ override_bytes_from_content_type(
568
+ seg_dict, logger=logger)
569
+ seg_range = seg_dict.get('range')
570
+ if seg_range is not None:
571
+ # The range is of the form N-M, where N and M are both
572
+ # positive decimal integers. We know this because this
573
+ # middleware is the only thing that creates the SLO
574
+ # manifests stored in the cluster.
575
+ range_start, range_end = [
576
+ int(x) for x in seg_range.split('-')]
577
+ segment_length = (range_end - range_start) + 1
578
+ else:
579
+ segment_length = int(seg_dict['bytes'])
580
+ seg_dict['segment_length'] = segment_length
581
+
582
+
583
+ def calculate_byterange_for_part_num(req, segments, part_num):
584
+ """
585
+ Helper function to calculate the byterange for a part_num response.
586
+
587
+ N.B. as a side-effect of calculating the single tuple representing the
588
+ byterange required for a part_num response this function will also mutate
589
+ the request's Range header so that swob knows to return 206.
590
+
591
+ :param req: the request object
592
+ :param segments: the list of seg_dicts
593
+ :param part_num: the part number of the object to return
594
+
595
+ :returns: a tuple representing the byterange
596
+ """
597
+ start = 0
598
+ for seg in segments[:part_num - 1]:
599
+ start += seg['segment_length']
600
+ last = start + segments[part_num - 1]['segment_length']
601
+ # We need to mutate the request's Range header so that swob knows to
602
+ # handle these partial content requests correctly.
603
+ req.range = "bytes=%d-%d" % (start, last - 1)
604
+ return start, last - 1
605
+
606
+
607
+ def calculate_byteranges(req, segments, resp_attrs, part_num):
608
+ """
609
+ Calculate the byteranges based on the request, segments, and part number.
610
+
611
+ N.B. as a side-effect of calculating the single tuple representing the
612
+ byterange required for a part_num response this function will also mutate
613
+ the request's Range header so that swob knows to return 206.
614
+
615
+ :param req: the request object
616
+ :param segments: the list of seg_dicts
617
+ :param resp_attrs: the slo response attributes
618
+ :param part_num: the part number of the object to return
619
+
620
+ :returns: a list of tuples representing byteranges
621
+ """
622
+ if req.range:
623
+ byteranges = [
624
+ # For some reason, swob.Range.ranges_for_length adds 1 to the
625
+ # last byte's position.
626
+ (start, end - 1) for start, end
627
+ in req.range.ranges_for_length(resp_attrs.slo_size)]
628
+ elif part_num:
629
+ byteranges = [
630
+ calculate_byterange_for_part_num(req, segments, part_num)]
631
+ else:
632
+ byteranges = [(0, resp_attrs.slo_size - 1)]
633
+
634
+ return byteranges
635
+
636
+
637
+ class RespAttrs(object):
638
+ """
639
+ Encapsulate properties of a GET or HEAD response that are pertinent to
640
+ handling a potential SLO response.
641
+
642
+ Instances of this class are typically constructed using the
643
+ ``from_headers`` method.
644
+
645
+ :param is_slo: True if the response appears to be an SLO manifest, False
646
+ otherwise.
647
+ :param timestamp: an instance of :class:`~swift.common.utils.Timestamp`.
648
+ :param manifest_etag: the Etag of the manifest object, or None if
649
+ ``is_slo`` is False.
650
+ :param slo_etag: the Etag of the SLO.
651
+ :param slo_size: the size of the SLO.
652
+ """
653
+ def __init__(self, is_slo, timestamp, manifest_etag, slo_etag, slo_size):
654
+ self.is_slo = bool(is_slo)
655
+ self.timestamp = Timestamp(timestamp or 0)
656
+ # manifest_etag is unambiguous, but json_md5 is even more explicit
657
+ self.json_md5 = manifest_etag or ''
658
+ self.slo_etag = slo_etag or ''
659
+ try:
660
+ # even though it's from sysmeta, we have to worry about empty
661
+ # values - see test_get_invalid_sysmeta_passthrough
662
+ self.slo_size = int(slo_size)
663
+ except (ValueError, TypeError):
664
+ self.slo_size = -1
665
+ self.is_legacy = not self._has_size_and_etag()
666
+
667
+ def _has_size_and_etag(self):
668
+ return self.slo_size >= 0 and self.slo_etag
669
+
670
+ @classmethod
671
+ def from_headers(cls, response_headers):
672
+ """
673
+ Inspect response headers and extract any resp_attrs we can find.
674
+
675
+ :param response_headers: list of tuples from a object response
676
+ :returns: an instance of RespAttrs to represent the response headers
677
+ """
678
+ is_slo = False
679
+ timestamp = None
680
+ found_etag = None
681
+ slo_etag = None
682
+ slo_size = None
683
+ for header, value in response_headers:
684
+ header = header.lower()
685
+ if header == 'x-static-large-object':
686
+ is_slo = config_true_value(value)
687
+ elif header == 'x-backend-timestamp':
688
+ timestamp = value
689
+ elif header == 'etag':
690
+ found_etag = value
691
+ elif header == SYSMETA_SLO_ETAG:
692
+ slo_etag = value
693
+ elif header == SYSMETA_SLO_SIZE:
694
+ slo_size = value
695
+ manifest_etag = found_etag if is_slo else None
696
+ return cls(is_slo, timestamp, manifest_etag, slo_etag, slo_size)
697
+
698
+ def update_from_segments(self, segments):
699
+ """
700
+ Always called if SLO has fetched the manifest response body, for
701
+ legacy manifests we'll calculate size/etag values we wouldn't have
702
+ gotten from sys-meta headers.
703
+ """
704
+ # we only have to set size/etag once; it doesn't matter if we got the
705
+ # values from sysmeta headers or segments
706
+ if self._has_size_and_etag():
707
+ return
708
+
709
+ calculated_size = 0
710
+ calculated_etag = md5(usedforsecurity=False)
711
+
712
+ for seg_dict in segments:
713
+ calculated_size += seg_dict['segment_length']
714
+
715
+ if 'raw_data' in seg_dict:
716
+ r = md5(seg_dict['raw_data'],
717
+ usedforsecurity=False).hexdigest()
718
+ elif seg_dict.get('range'):
719
+ r = '%s:%s;' % (seg_dict['hash'], seg_dict['range'])
720
+ else:
721
+ r = seg_dict['hash']
722
+ calculated_etag.update(r.encode('ascii'))
723
+
724
+ self.slo_size = calculated_size
725
+ self.slo_etag = calculated_etag.hexdigest()
726
+
727
+
533
728
  class SloGetContext(WSGIContext):
534
729
 
535
730
  max_slo_recursion_depth = 10
@@ -537,6 +732,8 @@ class SloGetContext(WSGIContext):
537
732
  def __init__(self, slo):
538
733
  self.slo = slo
539
734
  super(SloGetContext, self).__init__(slo.app)
735
+ # we'll know more after we look at the response metadata
736
+ self.segment_listing_needed = False
540
737
 
541
738
  def _fetch_sub_slo_segments(self, req, version, acc, con, obj):
542
739
  """
@@ -557,6 +754,9 @@ class SloGetContext(WSGIContext):
557
754
  method='GET',
558
755
  headers={'x-auth-token': req.headers.get('x-auth-token')},
559
756
  agent='%(orig)s SLO MultipartGET', swift_source='SLO')
757
+ params_copy = dict(req.params)
758
+ params_copy.pop('part-number', None)
759
+ sub_req.params = params_copy
560
760
  sub_resp = sub_req.get_response(self.slo.app)
561
761
 
562
762
  if not sub_resp.is_success:
@@ -571,9 +771,8 @@ class SloGetContext(WSGIContext):
571
771
  body if len(body) <= 60 else body[:57] + '...'))
572
772
 
573
773
  try:
574
- with closing_if_possible(sub_resp.app_iter):
575
- return json.loads(b''.join(sub_resp.app_iter))
576
- except ValueError as err:
774
+ return self._parse_segments(sub_resp.app_iter)
775
+ except HTTPException as err:
577
776
  raise ListingIterError(
578
777
  'while fetching %s, JSON-decoding of submanifest %s '
579
778
  'failed with %s' % (req.path, sub_req.path, err))
@@ -584,32 +783,8 @@ class SloGetContext(WSGIContext):
584
783
  conobj=seg_dict['name'].lstrip('/')
585
784
  )
586
785
 
587
- def _segment_length(self, seg_dict):
588
- """
589
- Returns the number of bytes that will be fetched from the specified
590
- segment on a plain GET request for this SLO manifest.
591
- """
592
- if 'raw_data' in seg_dict:
593
- return len(seg_dict['raw_data'])
594
-
595
- seg_range = seg_dict.get('range')
596
- if seg_range is not None:
597
- # The range is of the form N-M, where N and M are both positive
598
- # decimal integers. We know this because this middleware is the
599
- # only thing that creates the SLO manifests stored in the
600
- # cluster.
601
- range_start, range_end = [int(x) for x in seg_range.split('-')]
602
- return (range_end - range_start) + 1
603
- else:
604
- return int(seg_dict['bytes'])
605
-
606
786
  def _segment_listing_iterator(self, req, version, account, segments,
607
787
  byteranges):
608
- for seg_dict in segments:
609
- if config_true_value(seg_dict.get('sub_slo')):
610
- override_bytes_from_content_type(seg_dict,
611
- logger=self.slo.logger)
612
-
613
788
  # We handle the range stuff here so that we can be smart about
614
789
  # skipping unused submanifests. For example, if our first segment is a
615
790
  # submanifest referencing 50 MiB total, but start_byte falls in
@@ -617,9 +792,6 @@ class SloGetContext(WSGIContext):
617
792
  #
618
793
  # If we were to make SegmentedIterable handle all the range
619
794
  # calculations, we would be unable to make this optimization.
620
- total_length = sum(self._segment_length(seg) for seg in segments)
621
- if not byteranges:
622
- byteranges = [(0, total_length - 1)]
623
795
 
624
796
  # Cache segments from sub-SLOs in case more than one byterange
625
797
  # includes data from a particular sub-SLO. We only cache a few sets
@@ -646,12 +818,26 @@ class SloGetContext(WSGIContext):
646
818
  first_byte, last_byte,
647
819
  cached_fetch_sub_slo_segments,
648
820
  recursion_depth=1):
821
+ """
822
+ Iterable that generates a filtered and annotated stream of segment
823
+ dicts describing the sub-segment ranges that would be used by the
824
+ SegmentedIterable to construct the bytes for a ranged response.
825
+
826
+ :param req: original request object
827
+ :param version: version
828
+ :param account: account
829
+ :param segments: segments dictionary
830
+ :param first_byte: offset into the large object for the first byte
831
+ that is returned to the client
832
+ :param last_byte: offset into the large object for the last byte
833
+ that is returned to the client
834
+ :param cached_fetch_sub_slo_segments: LRU cache used for fetching
835
+ sub-segments
836
+ :param recursion_depth: max number of recursive sub_slo calls
837
+ """
649
838
  last_sub_path = None
650
839
  for seg_dict in segments:
651
- if 'data' in seg_dict:
652
- seg_dict['raw_data'] = strict_b64decode(seg_dict.pop('data'))
653
-
654
- seg_length = self._segment_length(seg_dict)
840
+ seg_length = seg_dict['segment_length']
655
841
  if first_byte >= seg_length:
656
842
  # don't need any bytes from this segment
657
843
  first_byte -= seg_length
@@ -718,50 +904,219 @@ class SloGetContext(WSGIContext):
718
904
  first_byte -= seg_length
719
905
  last_byte -= seg_length
720
906
 
721
- def _need_to_refetch_manifest(self, req):
907
+ def _is_body_complete(self):
908
+ content_range = ''
909
+ for header, value in self._response_headers:
910
+ if header.lower() == 'content-range':
911
+ content_range = value
912
+ break
913
+ # e.g. Content-Range: bytes 0-14289/14290
914
+ match = re.match(r'bytes (\d+)-(\d+)/(\d+)$', content_range)
915
+ if not match:
916
+ # Malformed or missing, so we don't know what we got.
917
+ return False
918
+ first_byte, last_byte, length = [int(x) for x in match.groups()]
919
+ # If and only if we actually got back the full manifest body, then
920
+ # we can avoid re-fetching the object.
921
+ return first_byte == 0 and last_byte == length - 1
922
+
923
+ def _need_to_refetch_manifest(self, req, resp_attrs, is_part_num_request):
722
924
  """
723
- Just because a response shows that an object is a SLO manifest does not
724
- mean that response's body contains the entire SLO manifest. If it
725
- doesn't, we need to make a second request to actually get the whole
726
- thing.
925
+ Check if the segments will be needed to service the request and update
926
+ the segment_listing_needed attribute.
727
927
 
728
- Note: this assumes that X-Static-Large-Object has already been found.
928
+ :return: boolean indicating if we need to refetch, only if the segments
929
+ ARE needed we MAY need to refetch them!
729
930
  """
730
931
  if req.method == 'HEAD':
731
- # We've already looked for SYSMETA_SLO_ETAG/SIZE in the response
732
- # and didn't find them. We have to fetch the whole manifest and
733
- # recompute.
932
+ # There may be some cases in the future where a HEAD resp on even a
933
+ # modern manifest should refetch, e.g. lp bug #2029174
934
+ self.segment_listing_needed = (resp_attrs.is_legacy or
935
+ is_part_num_request)
936
+ # it will always be the case that a HEAD must re-fetch iff
937
+ # segment_listing_needed
938
+ return self.segment_listing_needed
939
+
940
+ last_resp_status_int = self._get_status_int()
941
+ # These are based on etag (or last-modified), but the SLO's etag is
942
+ # almost certainly not the manifest object's etag. Still, it's highly
943
+ # likely that the submitted If-None-Match won't match the manifest
944
+ # object's etag, so we can avoid re-fetching the manifest if we got a
945
+ # successful response.
946
+ if last_resp_status_int in (412, 304):
947
+ # a conditional response from a modern manifest would have an
948
+ # accurate SLO etag, AND comparison with the etag-is-at header, but
949
+ # for legacy manifests responses (who always need to calculate the
950
+ # correct etag, even for if-[un]modified-since errors) we can't say
951
+ # what the etag is or if it matches unless we calculate it from
952
+ # segments - so we always need them
953
+ self.segment_listing_needed = resp_attrs.is_legacy
954
+ # if we need them; we can't get them from the error
955
+ return self.segment_listing_needed
956
+
957
+ # This is GET request for an SLO object, if we're going to return a
958
+ # successful response we're going to need the segments, but this
959
+ # resp_iter may not contain the entire SLO manifest.
960
+ self.segment_listing_needed = True
961
+
962
+ # modern swift object-servers should ignore Range headers on manifests,
963
+ # but during upgrade if we get a range response we'll probably have to
964
+ # refetch
965
+ if last_resp_status_int == 416:
966
+ # if the range wasn't satisfiable we need to refetch
734
967
  return True
968
+ elif last_resp_status_int == 206:
969
+ # a partial response might included the whole content-range?!
970
+ return not self._is_body_complete()
971
+ else:
972
+ # a good number of error responses would have returned earlier for
973
+ # lacking is_slo sys-meta, at this point we've filtered all the
974
+ # other response codes, so this is a prefectly normal 200 response,
975
+ # no need to refetch
976
+ return False
977
+
978
+ def _refetch_manifest(self, req, resp_iter, orig_resp_attrs):
979
+ req.environ['swift.non_client_disconnect'] = True
980
+ friendly_close(resp_iter)
981
+ del req.environ['swift.non_client_disconnect']
982
+
983
+ get_req = make_subrequest(
984
+ req.environ, method='GET',
985
+ headers={'x-auth-token': req.headers.get('x-auth-token')},
986
+ agent='%(orig)s SLO MultipartGET', swift_source='SLO')
987
+ resp_iter = self._app_call(get_req.environ)
988
+ new_resp_attrs = RespAttrs.from_headers(self._response_headers)
989
+ if new_resp_attrs.timestamp < orig_resp_attrs.timestamp and \
990
+ not new_resp_attrs.is_slo:
991
+ # Our *orig_resp_attrs* saw *newer* data that indicated it was an
992
+ # SLO, but on refetch it's an older object or error; 503 seems
993
+ # reasonable?
994
+ friendly_close(resp_iter)
995
+ raise HTTPServiceUnavailable(request=req)
996
+ # else, the caller will know how to return this response
997
+ return new_resp_attrs, resp_iter
998
+
999
+ def _parse_segments(self, resp_iter):
1000
+ """
1001
+ Read the manifest body and parse segments.
735
1002
 
736
- response_status = int(self._response_status[:3])
1003
+ :returns: segments
1004
+ :raises: HTTPServerError
1005
+ """
1006
+ segments = self._get_manifest_read(resp_iter)
1007
+ _annotate_segments(segments, logger=self.slo.logger)
1008
+ return segments
737
1009
 
738
- # These are based on etag, and the SLO's etag is almost certainly not
739
- # the manifest object's etag. Still, it's highly likely that the
740
- # submitted If-None-Match won't match the manifest object's etag, so
741
- # we can avoid re-fetching the manifest if we got a successful
742
- # response.
743
- if ((req.if_match or req.if_none_match) and
744
- not is_success(response_status)):
745
- return True
1010
+ def _return_manifest_response(self, req, start_response, resp_iter,
1011
+ is_format_raw):
1012
+ if is_format_raw:
1013
+ json_data = self.convert_segment_listing(resp_iter)
1014
+ # we've created a new response body
1015
+ resp_iter = [json_data]
1016
+ replace_headers = {
1017
+ # Note that we have to return the large object's content-type
1018
+ # (not application/json) so it's like what the client sent on
1019
+ # PUT. Otherwise, server-side copy won't work.
1020
+ 'Content-Length': len(json_data),
1021
+ 'Etag': md5(json_data, usedforsecurity=False).hexdigest(),
1022
+ }
1023
+ else:
1024
+ # we're going to return the manifest resp_iter as-is
1025
+ replace_headers = {
1026
+ 'Content-Type': 'application/json; charset=utf-8',
1027
+ }
1028
+ return self._return_response(req, start_response, resp_iter,
1029
+ replace_headers)
1030
+
1031
+ def _return_slo_response(self, req, start_response, resp_iter, resp_attrs):
1032
+ headers = {
1033
+ 'Etag': '"%s"' % resp_attrs.slo_etag,
1034
+ 'X-Manifest-Etag': resp_attrs.json_md5,
1035
+ # swob will fix this for a GET with Range
1036
+ 'Content-Length': str(resp_attrs.slo_size),
1037
+ # ignore bogus content-range, make swob figure it out
1038
+ 'Content-Range': None,
1039
+ }
1040
+ if self.segment_listing_needed:
1041
+ # consume existing resp_iter; we'll create a new one
1042
+ segments = self._parse_segments(resp_iter)
1043
+ resp_attrs.update_from_segments(segments)
1044
+ headers['Etag'] = '"%s"' % resp_attrs.slo_etag
1045
+ headers['Content-Length'] = str(resp_attrs.slo_size)
1046
+ part_num = get_valid_part_num(req)
1047
+ if part_num:
1048
+ headers['X-Parts-Count'] = len(segments)
1049
+
1050
+ if part_num and part_num > len(segments):
1051
+ if req.method == 'HEAD':
1052
+ resp_iter = []
1053
+ headers['Content-Length'] = '0'
1054
+ else:
1055
+ body = b'The requested part number is not satisfiable'
1056
+ resp_iter = [body]
1057
+ headers['Content-Length'] = len(body)
1058
+ headers['Content-Range'] = 'bytes */%d' % resp_attrs.slo_size
1059
+ self._response_status = '416 Requested Range Not Satisfiable'
1060
+ elif part_num and req.method == 'HEAD':
1061
+ resp_iter = []
1062
+ headers['Content-Length'] = \
1063
+ segments[part_num - 1].get('segment_length')
1064
+ start, end = calculate_byterange_for_part_num(
1065
+ req, segments, part_num)
1066
+ headers['Content-Range'] = \
1067
+ 'bytes {}-{}/{}'.format(start, end,
1068
+ resp_attrs.slo_size)
1069
+ # The RFC specifies 206 in the context of Range requests, and
1070
+ # Range headers MUST be ignored for HEADs [1], so a HEAD will
1071
+ # not normally return a 206. However, a part-number HEAD
1072
+ # returns Content-Length equal to the part size, rather than
1073
+ # the whole object size, so in this case we do return 206.
1074
+ # [1] https://www.rfc-editor.org/rfc/rfc9110#name-range
1075
+ self._response_status = '206 Partial Content'
1076
+ elif req.method == 'HEAD':
1077
+ resp_iter = []
1078
+ else:
1079
+ byteranges = calculate_byteranges(
1080
+ req, segments, resp_attrs, part_num)
1081
+ resp_iter = self._build_resp_iter(req, segments, byteranges)
1082
+ return self._return_response(req, start_response, resp_iter,
1083
+ replace_headers=headers)
1084
+
1085
+ def _return_response(self, req, start_response, resp_iter,
1086
+ replace_headers):
1087
+ if req.method == 'HEAD' or self._get_status_int() in (412, 304):
1088
+ # we should drain HEAD and unmet condition responses since they
1089
+ # don't have bodies
1090
+ friendly_close(resp_iter)
1091
+ resp_iter = b''
1092
+ resp_headers = HeaderKeyDict(self._response_headers, **replace_headers)
1093
+ resp = Response(
1094
+ status=self._response_status,
1095
+ headers=resp_headers,
1096
+ app_iter=resp_iter,
1097
+ request=req,
1098
+ conditional_response=True,
1099
+ conditional_etag=resolve_etag_is_at_header(req, resp_headers))
1100
+ return resp(req.environ, start_response)
746
1101
 
747
- if req.range and response_status in (206, 416):
748
- content_range = ''
749
- for header, value in self._response_headers:
750
- if header.lower() == 'content-range':
751
- content_range = value
752
- break
753
- # e.g. Content-Range: bytes 0-14289/14290
754
- match = re.match(r'bytes (\d+)-(\d+)/(\d+)$', content_range)
755
- if not match:
756
- # Malformed or missing, so we don't know what we got.
757
- return True
758
- first_byte, last_byte, length = [int(x) for x in match.groups()]
759
- # If and only if we actually got back the full manifest body, then
760
- # we can avoid re-fetching the object.
761
- got_everything = (first_byte == 0 and last_byte == length - 1)
762
- return not got_everything
763
-
764
- return False
1102
+ def _return_non_slo_response(self, req, start_response, resp_iter):
1103
+ # our "pass-through" response may have been from a manifest refetch w/o
1104
+ # range/conditional headers that turned out to be a real object, and
1105
+ # now we want out. But if the original client request included Range
1106
+ # or Conditional headers we can trust swob to do the right conversion
1107
+ # back into a 206/416/304/412 (as long as the response we have is a
1108
+ # normal successful response and we respect any forwarding middleware's
1109
+ # etag-is-at header that we stripped off for the refetch!)
1110
+ resp = Response(
1111
+ status=self._response_status,
1112
+ headers=self._response_headers,
1113
+ app_iter=resp_iter,
1114
+ request=req,
1115
+ conditional_response=self._get_status_int() == 200,
1116
+ conditional_etag=resolve_etag_is_at_header(
1117
+ req, self._response_headers)
1118
+ )
1119
+ return resp(req.environ, start_response)
765
1120
 
766
1121
  def handle_slo_get_or_head(self, req, start_response):
767
1122
  """
@@ -774,137 +1129,72 @@ class SloGetContext(WSGIContext):
774
1129
  large object manifest.
775
1130
  :param start_response: WSGI start_response callable
776
1131
  """
777
- if req.params.get('multipart-manifest') != 'get':
1132
+ is_manifest_get = get_param(req, 'multipart-manifest') == 'get'
1133
+ is_format_raw = is_manifest_get and get_param(req, 'format') == 'raw'
1134
+
1135
+ if not is_manifest_get:
778
1136
  # If this object is an SLO manifest, we may have saved off the
779
1137
  # large object etag during the original PUT. Send an
780
- # X-Backend-Etag-Is-At header so that, if the SLO etag *was*
781
- # saved, we can trust the object-server to respond appropriately
782
- # to If-Match/If-None-Match requests.
1138
+ # X-Backend-Etag-Is-At header so that, if the SLO etag *was* saved,
1139
+ # we can trust the object-server to respond appropriately to
1140
+ # If-Match/If-None-Match requests.
783
1141
  update_etag_is_at_header(req, SYSMETA_SLO_ETAG)
784
1142
  # Tell the object server that if it's a manifest,
785
1143
  # we want the whole thing
786
1144
  update_ignore_range_header(req, 'X-Static-Large-Object')
787
- resp_iter = self._app_call(req.environ)
788
-
789
- # make sure this response is for a static large object manifest
790
- slo_marker = slo_etag = slo_size = slo_timestamp = None
791
- for header, value in self._response_headers:
792
- header = header.lower()
793
- if header == SYSMETA_SLO_ETAG:
794
- slo_etag = value
795
- elif header == SYSMETA_SLO_SIZE:
796
- slo_size = value
797
- elif (header == 'x-static-large-object' and
798
- config_true_value(value)):
799
- slo_marker = value
800
- elif header == 'x-backend-timestamp':
801
- slo_timestamp = value
802
-
803
- if slo_marker and slo_etag and slo_size and slo_timestamp:
804
- break
805
-
806
- if not slo_marker:
807
- # Not a static large object manifest. Just pass it through.
808
- start_response(self._response_status,
809
- self._response_headers,
810
- self._response_exc_info)
811
- return resp_iter
812
-
813
- # Handle pass-through request for the manifest itself
814
- if req.params.get('multipart-manifest') == 'get':
815
- if req.params.get('format') == 'raw':
816
- resp_iter = self.convert_segment_listing(
817
- self._response_headers, resp_iter)
818
- else:
819
- new_headers = []
820
- for header, value in self._response_headers:
821
- if header.lower() == 'content-type':
822
- new_headers.append(('Content-Type',
823
- 'application/json; charset=utf-8'))
824
- else:
825
- new_headers.append((header, value))
826
- self._response_headers = new_headers
827
- start_response(self._response_status,
828
- self._response_headers,
829
- self._response_exc_info)
830
- return resp_iter
831
-
832
- is_conditional = self._response_status.startswith(('304', '412')) and (
833
- req.if_match or req.if_none_match)
834
- if slo_etag and slo_size and (
835
- req.method == 'HEAD' or is_conditional):
836
- # Since we have length and etag, we can respond immediately
837
- resp = Response(
838
- status=self._response_status,
839
- headers=self._response_headers,
840
- app_iter=resp_iter,
841
- request=req,
842
- conditional_etag=resolve_etag_is_at_header(
843
- req, self._response_headers),
844
- conditional_response=True)
845
- resp.headers.update({
846
- 'Etag': '"%s"' % slo_etag,
847
- 'X-Manifest-Etag': self._response_header_value('etag'),
848
- 'Content-Length': slo_size,
849
- })
850
- return resp(req.environ, start_response)
851
1145
 
852
- if self._need_to_refetch_manifest(req):
853
- req.environ['swift.non_client_disconnect'] = True
854
- close_if_possible(resp_iter)
855
- del req.environ['swift.non_client_disconnect']
856
-
857
- get_req = make_subrequest(
858
- req.environ, method='GET',
859
- headers={'x-auth-token': req.headers.get('x-auth-token')},
860
- agent='%(orig)s SLO MultipartGET', swift_source='SLO')
861
- resp_iter = self._app_call(get_req.environ)
862
- slo_marker = config_true_value(self._response_header_value(
863
- 'x-static-large-object'))
864
- if not slo_marker: # will also catch non-2xx responses
865
- got_timestamp = self._response_header_value(
866
- 'x-backend-timestamp') or '0'
867
- if Timestamp(got_timestamp) >= Timestamp(slo_timestamp):
868
- # We've got a newer response available, so serve that.
869
- # Note that if there's data, it's going to be a 200 now,
870
- # not a 206, and we're not going to drop bytes in the
871
- # proxy on the client's behalf. Fortunately, the RFC is
872
- # pretty forgiving for a server; there's no guarantee that
873
- # a Range header will be respected.
874
- resp = Response(
875
- status=self._response_status,
876
- headers=self._response_headers,
877
- app_iter=resp_iter,
878
- request=req,
879
- conditional_etag=resolve_etag_is_at_header(
880
- req, self._response_headers),
881
- conditional_response=is_success(
882
- int(self._response_status[:3])))
883
- return resp(req.environ, start_response)
884
- else:
885
- # We saw newer data that indicated it's an SLO, but
886
- # couldn't fetch the whole thing; 503 seems reasonable?
887
- close_if_possible(resp_iter)
888
- raise HTTPServiceUnavailable(request=req)
889
- # NB: we might have gotten an out-of-date manifest -- that's OK;
890
- # we'll just try to serve the old data
891
-
892
- # Any Content-Range from a manifest is almost certainly wrong for the
893
- # full large object.
894
- resp_headers = [(h, v) for h, v in self._response_headers
895
- if not h.lower() == 'content-range']
896
-
897
- response = self.get_or_head_response(
898
- req, resp_headers, resp_iter)
899
- return response(req.environ, start_response)
900
-
901
- def convert_segment_listing(self, resp_headers, resp_iter):
1146
+ # process original request
1147
+ orig_path_info = req.path_info
1148
+ resp_iter = self._app_call(req.environ)
1149
+ resp_attrs = RespAttrs.from_headers(self._response_headers)
1150
+ if resp_attrs.is_slo and not is_manifest_get:
1151
+ try:
1152
+ # only validate part-number if the request is to an SLO
1153
+ part_num = get_valid_part_num(req)
1154
+ except HTTPException:
1155
+ friendly_close(resp_iter)
1156
+ raise
1157
+ # the next two calls hide a couple side effects, sorry:
1158
+ #
1159
+ # 1) regardless of the return value the "need_to_refetch" check
1160
+ # *may* also set self.segment_listing_needed = True (it's
1161
+ # commented to help you wrap your head around that one,
1162
+ # good luck)
1163
+ # 2) if we refetch, we overwrite the current resp_iter and
1164
+ # resp_attrs variables, partly because we *might* get back a NOT
1165
+ # resp_attrs.is_slo response (even if we had one to start), but
1166
+ # hopefully they're just the manifest resp we needed to refetch!
1167
+ if self._need_to_refetch_manifest(req, resp_attrs, part_num):
1168
+ # reset path in case it was modified during original request
1169
+ # (e.g. object versioning might re-write the path)
1170
+ req.path_info = orig_path_info
1171
+ resp_attrs, resp_iter = self._refetch_manifest(
1172
+ req, resp_iter, resp_attrs)
1173
+
1174
+ if not resp_attrs.is_slo:
1175
+ # even if the original resp_attrs may have been SLO we may have
1176
+ # refetched, this also handles the server error case
1177
+ return self._return_non_slo_response(
1178
+ req, start_response, resp_iter)
1179
+
1180
+ if is_manifest_get:
1181
+ # manifest pass through doesn't require resp_attrs
1182
+ return self._return_manifest_response(req, start_response,
1183
+ resp_iter, is_format_raw)
1184
+
1185
+ # this a GET/HEAD response for the SLO object (not the manifest)
1186
+ return self._return_slo_response(req, start_response, resp_iter,
1187
+ resp_attrs)
1188
+
1189
+ def convert_segment_listing(self, resp_iter):
902
1190
  """
903
1191
  Converts the manifest data to match with the format
904
1192
  that was put in through ?multipart-manifest=put
905
1193
 
906
- :param resp_headers: response headers
907
1194
  :param resp_iter: a response iterable
1195
+
1196
+ :raises HTTPServerError:
1197
+ :returns: the json-serialized raw format (as bytes)
908
1198
  """
909
1199
  segments = self._get_manifest_read(resp_iter)
910
1200
 
@@ -921,109 +1211,29 @@ class SloGetContext(WSGIContext):
921
1211
  json_data = json.dumps(segments, sort_keys=True) # convert to string
922
1212
  if six.PY3:
923
1213
  json_data = json_data.encode('utf-8')
924
-
925
- new_headers = []
926
- for header, value in resp_headers:
927
- if header.lower() == 'content-length':
928
- new_headers.append(('Content-Length', len(json_data)))
929
- elif header.lower() == 'etag':
930
- new_headers.append(
931
- ('Etag', md5(json_data, usedforsecurity=False)
932
- .hexdigest()))
933
- else:
934
- new_headers.append((header, value))
935
- self._response_headers = new_headers
936
-
937
- return [json_data]
1214
+ return json_data
938
1215
 
939
1216
  def _get_manifest_read(self, resp_iter):
940
1217
  with closing_if_possible(resp_iter):
941
1218
  resp_body = b''.join(resp_iter)
942
1219
  try:
943
1220
  segments = json.loads(resp_body)
944
- except ValueError:
945
- segments = []
946
-
1221
+ except ValueError as e:
1222
+ msg = 'Unable to load SLO manifest'
1223
+ self.slo.logger.error('%s: %s', msg, e)
1224
+ raise HTTPServerError(msg)
947
1225
  return segments
948
1226
 
949
- def get_or_head_response(self, req, resp_headers, resp_iter):
950
- segments = self._get_manifest_read(resp_iter)
951
- slo_etag = None
952
- content_length = None
953
- response_headers = []
954
- for header, value in resp_headers:
955
- lheader = header.lower()
956
- if lheader == 'etag':
957
- response_headers.append(('X-Manifest-Etag', value))
958
- elif lheader != 'content-length':
959
- response_headers.append((header, value))
960
-
961
- if lheader == SYSMETA_SLO_ETAG:
962
- slo_etag = value
963
- elif lheader == SYSMETA_SLO_SIZE:
964
- # it's from sysmeta, so we don't worry about non-integer
965
- # values here
966
- content_length = int(value)
967
-
968
- # Prep to calculate content_length & etag if necessary
969
- if slo_etag is None:
970
- calculated_etag = md5(usedforsecurity=False)
971
- if content_length is None:
972
- calculated_content_length = 0
973
-
974
- for seg_dict in segments:
975
- # Decode any inlined data; it's important that we do this *before*
976
- # calculating the segment length and etag
977
- if 'data' in seg_dict:
978
- seg_dict['raw_data'] = base64.b64decode(seg_dict.pop('data'))
979
-
980
- if slo_etag is None:
981
- if 'raw_data' in seg_dict:
982
- r = md5(seg_dict['raw_data'],
983
- usedforsecurity=False).hexdigest()
984
- elif seg_dict.get('range'):
985
- r = '%s:%s;' % (seg_dict['hash'], seg_dict['range'])
986
- else:
987
- r = seg_dict['hash']
988
- calculated_etag.update(r.encode('ascii'))
989
-
990
- if content_length is None:
991
- if config_true_value(seg_dict.get('sub_slo')):
992
- override_bytes_from_content_type(
993
- seg_dict, logger=self.slo.logger)
994
- calculated_content_length += self._segment_length(seg_dict)
995
-
996
- if slo_etag is None:
997
- slo_etag = calculated_etag.hexdigest()
998
- if content_length is None:
999
- content_length = calculated_content_length
1000
-
1001
- response_headers.append(('Content-Length', str(content_length)))
1002
- response_headers.append(('Etag', '"%s"' % slo_etag))
1227
+ def _build_resp_iter(self, req, segments, byteranges):
1228
+ """
1229
+ Build a response iterable for a GET request.
1003
1230
 
1004
- if req.method == 'HEAD':
1005
- return self._manifest_head_response(req, response_headers)
1006
- else:
1007
- return self._manifest_get_response(
1008
- req, content_length, response_headers, segments)
1009
-
1010
- def _manifest_head_response(self, req, response_headers):
1011
- conditional_etag = resolve_etag_is_at_header(req, response_headers)
1012
- return HTTPOk(request=req, headers=response_headers, body=b'',
1013
- conditional_etag=conditional_etag,
1014
- conditional_response=True)
1015
-
1016
- def _manifest_get_response(self, req, content_length, response_headers,
1017
- segments):
1018
- if req.range:
1019
- byteranges = [
1020
- # For some reason, swob.Range.ranges_for_length adds 1 to the
1021
- # last byte's position.
1022
- (start, end - 1) for start, end
1023
- in req.range.ranges_for_length(content_length)]
1024
- else:
1025
- byteranges = []
1231
+ :param req: the request object
1232
+ :param segments: the list of seg_dicts
1233
+ :param byteranges: a list of tuples representing byteranges
1026
1234
 
1235
+ :returns: a segmented iterable
1236
+ """
1027
1237
  ver, account, _junk = req.split_path(3, 3, rest_with_last=True)
1028
1238
  account = wsgi_to_str(account)
1029
1239
  plain_listing_iter = self._segment_listing_iterator(
@@ -1067,15 +1277,8 @@ class SloGetContext(WSGIContext):
1067
1277
  # their Etag/Content Length no longer match the connection
1068
1278
  # will drop. In this case a 409 Conflict will be logged in
1069
1279
  # the proxy logs and the user will receive incomplete results.
1070
- return HTTPConflict(request=req)
1071
-
1072
- conditional_etag = resolve_etag_is_at_header(req, response_headers)
1073
- response = Response(request=req, content_length=content_length,
1074
- headers=response_headers,
1075
- conditional_response=True,
1076
- conditional_etag=conditional_etag,
1077
- app_iter=segmented_iter)
1078
- return response
1280
+ raise HTTPConflict(request=req)
1281
+ return segmented_iter
1079
1282
 
1080
1283
 
1081
1284
  class StaticLargeObject(object):
@@ -1128,12 +1331,7 @@ class StaticLargeObject(object):
1128
1331
  delete_concurrency=delete_concurrency,
1129
1332
  logger=self.logger)
1130
1333
 
1131
- # Need to know how to expire things to do async deletes
1132
- if conf.get('auto_create_account_prefix'):
1133
- # proxy app will log about how this should get moved to swift.conf
1134
- prefix = conf['auto_create_account_prefix']
1135
- else:
1136
- prefix = AUTO_CREATE_ACCOUNT_PREFIX
1334
+ prefix = AUTO_CREATE_ACCOUNT_PREFIX
1137
1335
  self.expiring_objects_account = prefix + (
1138
1336
  conf.get('expiring_objects_account_name') or 'expiring_objects')
1139
1337
  self.expiring_objects_container_divisor = int(
@@ -1505,7 +1703,7 @@ class StaticLargeObject(object):
1505
1703
  vrs, account, _junk = req.split_path(2, 3, True)
1506
1704
  new_env = req.environ.copy()
1507
1705
  new_env['REQUEST_METHOD'] = 'GET'
1508
- del(new_env['wsgi.input'])
1706
+ del new_env['wsgi.input']
1509
1707
  new_env['QUERY_STRING'] = 'multipart-manifest=get'
1510
1708
  if 'version-id' in req.params:
1511
1709
  new_env['QUERY_STRING'] += \
@@ -1524,7 +1722,7 @@ class StaticLargeObject(object):
1524
1722
  '/%s/%s/%s' % (vrs, account, str_to_wsgi(obj_name.lstrip('/')))
1525
1723
  )
1526
1724
  # Just request the last byte of non-SLO objects so we don't waste
1527
- # a bunch of resources in drain_and_close() below
1725
+ # a resources in friendly_close() below
1528
1726
  manifest_req = Request.blank('', new_env, range='bytes=-1')
1529
1727
  update_ignore_range_header(manifest_req, 'X-Static-Large-Object')
1530
1728
  resp = manifest_req.get_response(self.app)
@@ -1543,7 +1741,7 @@ class StaticLargeObject(object):
1543
1741
  raise HTTPServerError('Unable to load SLO manifest')
1544
1742
  else:
1545
1743
  # Drain and close GET request (prevents socket leaks)
1546
- drain_and_close(resp)
1744
+ friendly_close(resp)
1547
1745
  raise HTTPBadRequest('Not an SLO manifest')
1548
1746
  elif resp.status_int == HTTP_NOT_FOUND:
1549
1747
  raise HTTPNotFound('SLO manifest not found')
@@ -1624,7 +1822,7 @@ class StaticLargeObject(object):
1624
1822
  resp.status, resp.body)
1625
1823
  return HTTPServiceUnavailable()
1626
1824
  # consume the response (should be short)
1627
- drain_and_close(resp)
1825
+ friendly_close(resp)
1628
1826
 
1629
1827
  # Finally, delete the manifest
1630
1828
  return self.app