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.
- swift/cli/info.py +9 -2
- swift/cli/ringbuilder.py +5 -1
- swift/common/container_sync_realms.py +6 -7
- swift/common/daemon.py +7 -3
- swift/common/db.py +22 -7
- swift/common/db_replicator.py +19 -20
- swift/common/direct_client.py +63 -14
- swift/common/internal_client.py +24 -3
- swift/common/manager.py +43 -44
- swift/common/memcached.py +168 -74
- swift/common/middleware/__init__.py +4 -0
- swift/common/middleware/account_quotas.py +98 -40
- swift/common/middleware/backend_ratelimit.py +6 -4
- swift/common/middleware/crossdomain.py +21 -8
- swift/common/middleware/listing_formats.py +26 -38
- swift/common/middleware/proxy_logging.py +12 -9
- swift/common/middleware/s3api/controllers/bucket.py +8 -2
- swift/common/middleware/s3api/s3api.py +9 -4
- swift/common/middleware/s3api/s3request.py +32 -24
- swift/common/middleware/s3api/s3response.py +10 -1
- swift/common/middleware/tempauth.py +9 -10
- swift/common/middleware/versioned_writes/__init__.py +0 -3
- swift/common/middleware/versioned_writes/object_versioning.py +22 -5
- swift/common/middleware/x_profile/html_viewer.py +1 -1
- swift/common/middleware/xprofile.py +5 -0
- swift/common/request_helpers.py +1 -2
- swift/common/ring/ring.py +22 -19
- swift/common/swob.py +2 -1
- swift/common/{utils.py → utils/__init__.py} +610 -1146
- swift/common/utils/ipaddrs.py +256 -0
- swift/common/utils/libc.py +345 -0
- swift/common/utils/timestamp.py +399 -0
- swift/common/wsgi.py +70 -39
- swift/container/backend.py +106 -38
- swift/container/server.py +11 -2
- swift/container/sharder.py +34 -15
- swift/locale/de/LC_MESSAGES/swift.po +1 -320
- swift/locale/en_GB/LC_MESSAGES/swift.po +1 -347
- swift/locale/es/LC_MESSAGES/swift.po +1 -279
- swift/locale/fr/LC_MESSAGES/swift.po +1 -209
- swift/locale/it/LC_MESSAGES/swift.po +1 -207
- swift/locale/ja/LC_MESSAGES/swift.po +2 -278
- swift/locale/ko_KR/LC_MESSAGES/swift.po +3 -303
- swift/locale/pt_BR/LC_MESSAGES/swift.po +1 -204
- swift/locale/ru/LC_MESSAGES/swift.po +1 -203
- swift/locale/tr_TR/LC_MESSAGES/swift.po +1 -192
- swift/locale/zh_CN/LC_MESSAGES/swift.po +1 -192
- swift/locale/zh_TW/LC_MESSAGES/swift.po +1 -193
- swift/obj/diskfile.py +19 -6
- swift/obj/server.py +20 -6
- swift/obj/ssync_receiver.py +19 -9
- swift/obj/ssync_sender.py +10 -10
- swift/proxy/controllers/account.py +7 -7
- swift/proxy/controllers/base.py +374 -366
- swift/proxy/controllers/container.py +112 -53
- swift/proxy/controllers/obj.py +254 -390
- swift/proxy/server.py +3 -8
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-server +1 -1
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-server +1 -1
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-drive-audit +45 -14
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-server +1 -1
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-proxy-server +1 -1
- {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/AUTHORS +4 -0
- {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/METADATA +32 -35
- {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/RECORD +103 -100
- {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/WHEEL +1 -1
- {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/entry_points.txt +0 -1
- swift-2.32.1.dist-info/pbr.json +1 -0
- swift-2.31.1.dist-info/pbr.json +0 -1
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-audit +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-auditor +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-info +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-reaper +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-account-replicator +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-config +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-auditor +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-info +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-reconciler +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-replicator +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-sharder +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-sync +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-container-updater +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-dispersion-populate +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-dispersion-report +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-form-signature +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-get-nodes +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-init +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-auditor +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-expirer +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-info +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-reconstructor +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-relinker +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-replicator +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-object-updater +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-oldies +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-orphans +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-recon +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-recon-cron +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-reconciler-enqueue +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-ring-builder +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-ring-builder-analyzer +0 -0
- {swift-2.31.1.data → swift-2.32.1.data}/scripts/swift-ring-composer +0 -0
- {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/LICENSE +0 -0
- {swift-2.31.1.dist-info → swift-2.32.1.dist-info}/top_level.txt +0 -0
swift/proxy/controllers/base.py
CHANGED
@@ -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
|
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
|
-
|
463
|
+
logged_app = app._pipeline_request_logging_app
|
464
|
+
proxy_app = app._pipeline_final_app
|
444
465
|
except AttributeError:
|
445
|
-
|
466
|
+
logged_app = proxy_app = app
|
446
467
|
# Check in environment cache and in memcache (in that order)
|
447
|
-
info = _get_info_from_caches(
|
468
|
+
info, cache_state = _get_info_from_caches(
|
469
|
+
proxy_app, env, account, container)
|
448
470
|
|
449
|
-
|
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(
|
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,
|
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(
|
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(
|
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
|
-
|
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.
|
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
|
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(
|
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(
|
667
|
+
def set_info_cache(env, account, container, resp):
|
637
668
|
"""
|
638
669
|
Cache info in both memcache and env.
|
639
670
|
|
640
|
-
:param
|
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(
|
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(
|
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
|
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
|
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
|
784
|
-
|
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
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
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
|
-
|
799
|
-
|
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
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
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
|
852
|
-
|
853
|
-
|
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
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
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.
|
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
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
self.
|
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
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
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
|
-
|
1061
|
-
|
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
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
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
|
-
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
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
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
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
|
-
|
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)
|
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 =
|
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
|
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
|
-
|
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.
|
1436
|
+
self.partition, self.req.method, self.path,
|
1431
1437
|
headers=req_headers,
|
1432
|
-
query_string=self.
|
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.
|
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
|
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(
|
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.
|
1520
|
+
self.req.method, self.path,
|
1514
1521
|
self.bodies[-1])
|
1515
1522
|
return False
|
1516
1523
|
|
1517
|
-
def
|
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
|
1555
|
+
if s.timestamp >= self.latest_404_timestamp]
|
1549
1556
|
|
1550
1557
|
if self.sources:
|
1551
|
-
self.sources.sort(key=
|
1552
|
-
source
|
1553
|
-
for
|
1554
|
-
|
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
|
1566
|
-
|
1567
|
-
|
1568
|
-
|
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
|
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
|
-
:
|
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.
|
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
|
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
|
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(
|
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
|
-
|
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
|
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(
|
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
|
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'}
|