swift 2.33.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.
- swift/account/auditor.py +11 -0
- swift/account/reaper.py +11 -1
- swift/account/replicator.py +22 -0
- swift/account/server.py +12 -1
- swift-2.33.0.data/scripts/swift-account-audit → swift/cli/account_audit.py +6 -2
- swift-2.33.0.data/scripts/swift-config → swift/cli/config.py +1 -1
- swift-2.33.0.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +6 -2
- swift-2.33.0.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +12 -3
- swift-2.33.0.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +6 -2
- swift/cli/info.py +103 -2
- swift-2.33.0.data/scripts/swift-oldies → swift/cli/oldies.py +6 -3
- swift-2.33.0.data/scripts/swift-orphans → swift/cli/orphans.py +7 -2
- swift/cli/recon_cron.py +5 -5
- swift-2.33.0.data/scripts/swift-reconciler-enqueue → swift/cli/reconciler_enqueue.py +2 -3
- swift/cli/relinker.py +1 -1
- swift/cli/ringbuilder.py +24 -0
- swift/common/db.py +2 -1
- swift/common/db_auditor.py +2 -2
- swift/common/db_replicator.py +6 -0
- swift/common/exceptions.py +12 -0
- swift/common/manager.py +102 -0
- swift/common/memcached.py +6 -13
- swift/common/middleware/account_quotas.py +144 -43
- swift/common/middleware/backend_ratelimit.py +166 -24
- swift/common/middleware/catch_errors.py +1 -3
- swift/common/middleware/cname_lookup.py +3 -5
- swift/common/middleware/container_sync.py +6 -10
- swift/common/middleware/crypto/crypto_utils.py +4 -5
- swift/common/middleware/crypto/decrypter.py +4 -5
- swift/common/middleware/crypto/kms_keymaster.py +2 -1
- swift/common/middleware/proxy_logging.py +22 -16
- swift/common/middleware/ratelimit.py +6 -7
- swift/common/middleware/recon.py +6 -7
- swift/common/middleware/s3api/acl_handlers.py +9 -0
- swift/common/middleware/s3api/controllers/multi_upload.py +1 -9
- swift/common/middleware/s3api/controllers/obj.py +20 -1
- swift/common/middleware/s3api/s3api.py +2 -0
- swift/common/middleware/s3api/s3request.py +171 -62
- swift/common/middleware/s3api/s3response.py +35 -6
- swift/common/middleware/s3api/s3token.py +2 -2
- swift/common/middleware/s3api/utils.py +1 -0
- swift/common/middleware/slo.py +153 -52
- swift/common/middleware/tempauth.py +6 -4
- swift/common/middleware/tempurl.py +2 -2
- swift/common/middleware/x_profile/exceptions.py +1 -4
- swift/common/middleware/x_profile/html_viewer.py +9 -10
- swift/common/middleware/x_profile/profile_model.py +1 -2
- swift/common/middleware/xprofile.py +1 -2
- swift/common/request_helpers.py +69 -0
- swift/common/statsd_client.py +207 -0
- swift/common/utils/__init__.py +97 -1635
- swift/common/utils/base.py +138 -0
- swift/common/utils/config.py +443 -0
- swift/common/utils/logs.py +999 -0
- swift/common/wsgi.py +11 -3
- swift/container/auditor.py +11 -0
- swift/container/backend.py +10 -10
- swift/container/reconciler.py +11 -2
- swift/container/replicator.py +22 -1
- swift/container/server.py +12 -1
- swift/container/sharder.py +36 -12
- swift/container/sync.py +11 -1
- swift/container/updater.py +11 -2
- swift/obj/auditor.py +18 -2
- swift/obj/diskfile.py +8 -6
- swift/obj/expirer.py +155 -36
- swift/obj/reconstructor.py +28 -4
- swift/obj/replicator.py +61 -22
- swift/obj/server.py +64 -36
- swift/obj/updater.py +11 -2
- swift/proxy/controllers/base.py +38 -22
- swift/proxy/controllers/obj.py +23 -26
- swift/proxy/server.py +15 -1
- {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/AUTHORS +11 -3
- {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/METADATA +6 -5
- {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/RECORD +81 -107
- {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/entry_points.txt +38 -0
- swift-2.34.0.dist-info/pbr.json +1 -0
- swift-2.33.0.data/scripts/swift-account-auditor +0 -23
- swift-2.33.0.data/scripts/swift-account-info +0 -52
- swift-2.33.0.data/scripts/swift-account-reaper +0 -23
- swift-2.33.0.data/scripts/swift-account-replicator +0 -34
- swift-2.33.0.data/scripts/swift-account-server +0 -23
- swift-2.33.0.data/scripts/swift-container-auditor +0 -23
- swift-2.33.0.data/scripts/swift-container-info +0 -59
- swift-2.33.0.data/scripts/swift-container-reconciler +0 -21
- swift-2.33.0.data/scripts/swift-container-replicator +0 -34
- swift-2.33.0.data/scripts/swift-container-server +0 -23
- swift-2.33.0.data/scripts/swift-container-sharder +0 -37
- swift-2.33.0.data/scripts/swift-container-sync +0 -23
- swift-2.33.0.data/scripts/swift-container-updater +0 -23
- swift-2.33.0.data/scripts/swift-dispersion-report +0 -24
- swift-2.33.0.data/scripts/swift-form-signature +0 -20
- swift-2.33.0.data/scripts/swift-init +0 -119
- swift-2.33.0.data/scripts/swift-object-auditor +0 -29
- swift-2.33.0.data/scripts/swift-object-expirer +0 -33
- swift-2.33.0.data/scripts/swift-object-info +0 -60
- swift-2.33.0.data/scripts/swift-object-reconstructor +0 -33
- swift-2.33.0.data/scripts/swift-object-relinker +0 -23
- swift-2.33.0.data/scripts/swift-object-replicator +0 -37
- swift-2.33.0.data/scripts/swift-object-server +0 -27
- swift-2.33.0.data/scripts/swift-object-updater +0 -23
- swift-2.33.0.data/scripts/swift-proxy-server +0 -23
- swift-2.33.0.data/scripts/swift-recon +0 -24
- swift-2.33.0.data/scripts/swift-recon-cron +0 -24
- swift-2.33.0.data/scripts/swift-ring-builder +0 -37
- swift-2.33.0.data/scripts/swift-ring-builder-analyzer +0 -22
- swift-2.33.0.data/scripts/swift-ring-composer +0 -22
- swift-2.33.0.dist-info/pbr.json +0 -1
- {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/LICENSE +0 -0
- {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/WHEEL +0 -0
- {swift-2.33.0.dist-info → swift-2.34.0.dist-info}/top_level.txt +0 -0
swift/common/middleware/slo.py
CHANGED
@@ -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
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
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
|
@@ -353,7 +369,7 @@ from swift.common.registry import register_swift_info
|
|
353
369
|
from swift.common.request_helpers import SegmentedIterable, \
|
354
370
|
get_sys_meta_prefix, update_etag_is_at_header, resolve_etag_is_at_header, \
|
355
371
|
get_container_update_override_key, update_ignore_range_header, \
|
356
|
-
get_param
|
372
|
+
get_param, get_valid_part_num
|
357
373
|
from swift.common.constraints import check_utf8, AUTO_CREATE_ACCOUNT_PREFIX
|
358
374
|
from swift.common.http import HTTP_NOT_FOUND, HTTP_UNAUTHORIZED
|
359
375
|
from swift.common.wsgi import WSGIContext, make_subrequest, make_env, \
|
@@ -564,6 +580,60 @@ def _annotate_segments(segments, logger=None):
|
|
564
580
|
seg_dict['segment_length'] = segment_length
|
565
581
|
|
566
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
|
+
|
567
637
|
class RespAttrs(object):
|
568
638
|
"""
|
569
639
|
Encapsulate properties of a GET or HEAD response that are pertinent to
|
@@ -684,6 +754,9 @@ class SloGetContext(WSGIContext):
|
|
684
754
|
method='GET',
|
685
755
|
headers={'x-auth-token': req.headers.get('x-auth-token')},
|
686
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
|
687
760
|
sub_resp = sub_req.get_response(self.slo.app)
|
688
761
|
|
689
762
|
if not sub_resp.is_success:
|
@@ -847,8 +920,7 @@ class SloGetContext(WSGIContext):
|
|
847
920
|
# we can avoid re-fetching the object.
|
848
921
|
return first_byte == 0 and last_byte == length - 1
|
849
922
|
|
850
|
-
def
|
851
|
-
is_manifest_get):
|
923
|
+
def _need_to_refetch_manifest(self, req, resp_attrs, is_part_num_request):
|
852
924
|
"""
|
853
925
|
Check if the segments will be needed to service the request and update
|
854
926
|
the segment_listing_needed attribute.
|
@@ -856,19 +928,11 @@ class SloGetContext(WSGIContext):
|
|
856
928
|
:return: boolean indicating if we need to refetch, only if the segments
|
857
929
|
ARE needed we MAY need to refetch them!
|
858
930
|
"""
|
859
|
-
if not resp_attrs.is_slo:
|
860
|
-
# Not a static large object manifest, maybe an error, regardless
|
861
|
-
# no refetch needed
|
862
|
-
return False
|
863
|
-
|
864
|
-
if is_manifest_get:
|
865
|
-
# Any manifest json object response will do
|
866
|
-
return False
|
867
|
-
|
868
931
|
if req.method == 'HEAD':
|
869
932
|
# There may be some cases in the future where a HEAD resp on even a
|
870
933
|
# modern manifest should refetch, e.g. lp bug #2029174
|
871
|
-
self.segment_listing_needed = resp_attrs.is_legacy
|
934
|
+
self.segment_listing_needed = (resp_attrs.is_legacy or
|
935
|
+
is_part_num_request)
|
872
936
|
# it will always be the case that a HEAD must re-fetch iff
|
873
937
|
# segment_listing_needed
|
874
938
|
return self.segment_listing_needed
|
@@ -965,22 +1029,56 @@ class SloGetContext(WSGIContext):
|
|
965
1029
|
replace_headers)
|
966
1030
|
|
967
1031
|
def _return_slo_response(self, req, start_response, resp_iter, resp_attrs):
|
968
|
-
if self.segment_listing_needed:
|
969
|
-
# consume existing resp_iter; we'll create a new one
|
970
|
-
segments = self._parse_segments(resp_iter)
|
971
|
-
resp_attrs.update_from_segments(segments)
|
972
|
-
if req.method == 'HEAD':
|
973
|
-
resp_iter = []
|
974
|
-
else:
|
975
|
-
resp_iter = self._build_resp_iter(req, segments, resp_attrs)
|
976
1032
|
headers = {
|
977
1033
|
'Etag': '"%s"' % resp_attrs.slo_etag,
|
978
1034
|
'X-Manifest-Etag': resp_attrs.json_md5,
|
979
|
-
#
|
1035
|
+
# swob will fix this for a GET with Range
|
980
1036
|
'Content-Length': str(resp_attrs.slo_size),
|
981
1037
|
# ignore bogus content-range, make swob figure it out
|
982
|
-
'Content-Range': None
|
1038
|
+
'Content-Range': None,
|
983
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)
|
984
1082
|
return self._return_response(req, start_response, resp_iter,
|
985
1083
|
replace_headers=headers)
|
986
1084
|
|
@@ -1046,21 +1144,32 @@ class SloGetContext(WSGIContext):
|
|
1046
1144
|
update_ignore_range_header(req, 'X-Static-Large-Object')
|
1047
1145
|
|
1048
1146
|
# process original request
|
1147
|
+
orig_path_info = req.path_info
|
1049
1148
|
resp_iter = self._app_call(req.environ)
|
1050
1149
|
resp_attrs = RespAttrs.from_headers(self._response_headers)
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
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)
|
1064
1173
|
|
1065
1174
|
if not resp_attrs.is_slo:
|
1066
1175
|
# even if the original resp_attrs may have been SLO we may have
|
@@ -1115,24 +1224,16 @@ class SloGetContext(WSGIContext):
|
|
1115
1224
|
raise HTTPServerError(msg)
|
1116
1225
|
return segments
|
1117
1226
|
|
1118
|
-
def _build_resp_iter(self, req, segments,
|
1227
|
+
def _build_resp_iter(self, req, segments, byteranges):
|
1119
1228
|
"""
|
1120
1229
|
Build a response iterable for a GET request.
|
1121
1230
|
|
1122
1231
|
:param req: the request object
|
1123
|
-
:param
|
1232
|
+
:param segments: the list of seg_dicts
|
1233
|
+
:param byteranges: a list of tuples representing byteranges
|
1124
1234
|
|
1125
1235
|
:returns: a segmented iterable
|
1126
1236
|
"""
|
1127
|
-
if req.range:
|
1128
|
-
byteranges = [
|
1129
|
-
# For some reason, swob.Range.ranges_for_length adds 1 to the
|
1130
|
-
# last byte's position.
|
1131
|
-
(start, end - 1) for start, end
|
1132
|
-
in req.range.ranges_for_length(resp_attrs.slo_size)]
|
1133
|
-
else:
|
1134
|
-
byteranges = [(0, resp_attrs.slo_size - 1)]
|
1135
|
-
|
1136
1237
|
ver, account, _junk = req.split_path(3, 3, rest_with_last=True)
|
1137
1238
|
account = wsgi_to_str(account)
|
1138
1239
|
plain_listing_iter = self._segment_listing_iterator(
|
@@ -184,9 +184,11 @@ import base64
|
|
184
184
|
from eventlet import Timeout
|
185
185
|
import six
|
186
186
|
from swift.common.memcached import MemcacheConnectionError
|
187
|
-
from swift.common.swob import
|
188
|
-
|
189
|
-
|
187
|
+
from swift.common.swob import (
|
188
|
+
Response, Request, wsgi_to_str, str_to_wsgi, wsgi_unquote,
|
189
|
+
HTTPBadRequest, HTTPForbidden, HTTPNotFound,
|
190
|
+
HTTPUnauthorized, HTTPMethodNotAllowed, HTTPServiceUnavailable,
|
191
|
+
)
|
190
192
|
|
191
193
|
from swift.common.request_helpers import get_sys_meta_prefix
|
192
194
|
from swift.common.middleware.acl import (
|
@@ -469,7 +471,7 @@ class TempAuth(object):
|
|
469
471
|
if not s3_auth_details['check_signature'](user['key']):
|
470
472
|
return None
|
471
473
|
env['PATH_INFO'] = env['PATH_INFO'].replace(
|
472
|
-
account_user, account_id, 1)
|
474
|
+
str_to_wsgi(account_user), wsgi_unquote(account_id), 1)
|
473
475
|
groups = self._get_user_groups(account, account_user, account_id)
|
474
476
|
|
475
477
|
return groups
|
@@ -255,7 +255,7 @@ This middleware understands the following configuration settings:
|
|
255
255
|
incoming requests. Names may optionally end with ``*`` to
|
256
256
|
indicate a prefix match. ``incoming_allow_headers`` is a
|
257
257
|
list of exceptions to these removals.
|
258
|
-
Default: ``x-timestamp``
|
258
|
+
Default: ``x-timestamp x-open-expired``
|
259
259
|
|
260
260
|
``incoming_allow_headers``
|
261
261
|
A whitespace-delimited list of the headers allowed as
|
@@ -326,7 +326,7 @@ DISALLOWED_INCOMING_HEADERS = 'x-object-manifest x-symlink-target'
|
|
326
326
|
#: delimited list of header names and names can optionally end with '*' to
|
327
327
|
#: indicate a prefix match. DEFAULT_INCOMING_ALLOW_HEADERS is a list of
|
328
328
|
#: exceptions to these removals.
|
329
|
-
DEFAULT_INCOMING_REMOVE_HEADERS = 'x-timestamp'
|
329
|
+
DEFAULT_INCOMING_REMOVE_HEADERS = 'x-timestamp x-open-expired'
|
330
330
|
|
331
331
|
#: Default headers as exceptions to DEFAULT_INCOMING_REMOVE_HEADERS. Simply a
|
332
332
|
#: whitespace delimited list of header names and names can optionally end with
|
@@ -13,16 +13,13 @@
|
|
13
13
|
# See the License for the specific language governing permissions and
|
14
14
|
# limitations under the License.
|
15
15
|
|
16
|
-
from swift import gettext_ as _
|
17
|
-
|
18
|
-
|
19
16
|
class ProfileException(Exception):
|
20
17
|
|
21
18
|
def __init__(self, msg):
|
22
19
|
self.msg = msg
|
23
20
|
|
24
21
|
def __str__(self):
|
25
|
-
return
|
22
|
+
return 'Profiling Error: %s' % self.msg
|
26
23
|
|
27
24
|
|
28
25
|
class NotFoundException(ProfileException):
|
@@ -19,7 +19,6 @@ import re
|
|
19
19
|
import string
|
20
20
|
import tempfile
|
21
21
|
|
22
|
-
from swift import gettext_ as _
|
23
22
|
from swift.common.middleware.x_profile.exceptions import PLOTLIBNotInstalled
|
24
23
|
from swift.common.middleware.x_profile.exceptions import ODFLIBNotInstalled
|
25
24
|
from swift.common.middleware.x_profile.exceptions import NotFoundException
|
@@ -307,7 +306,7 @@ class HTMLViewer(object):
|
|
307
306
|
nfl_filter, download_format)
|
308
307
|
headers.append(('Access-Control-Allow-Origin', '*'))
|
309
308
|
else:
|
310
|
-
raise MethodNotAllowed(
|
309
|
+
raise MethodNotAllowed('method %s is not allowed.' % method)
|
311
310
|
return content, headers
|
312
311
|
|
313
312
|
def index_page(self, log_files=None, sort='time', limit=-1,
|
@@ -318,7 +317,7 @@ class HTMLViewer(object):
|
|
318
317
|
try:
|
319
318
|
stats = Stats2(*log_files)
|
320
319
|
except (IOError, ValueError):
|
321
|
-
raise DataLoadFailure(
|
320
|
+
raise DataLoadFailure('Can not load profile data from %s.'
|
322
321
|
% log_files)
|
323
322
|
if not fulldirs:
|
324
323
|
stats.strip_dirs()
|
@@ -369,7 +368,7 @@ class HTMLViewer(object):
|
|
369
368
|
def download(self, log_files, sort='time', limit=-1, nfl_filter='',
|
370
369
|
output_format='default'):
|
371
370
|
if len(log_files) == 0:
|
372
|
-
raise NotFoundException(
|
371
|
+
raise NotFoundException('no log file found')
|
373
372
|
try:
|
374
373
|
nfl_esc = nfl_filter.replace(r'(', r'\(').replace(r')', r'\)')
|
375
374
|
# remove the slash that is intentionally added in the URL
|
@@ -392,14 +391,14 @@ class HTMLViewer(object):
|
|
392
391
|
except ODFLIBNotInstalled:
|
393
392
|
raise
|
394
393
|
except Exception as ex:
|
395
|
-
raise ProfileException(
|
394
|
+
raise ProfileException('Data download error: %s' % ex)
|
396
395
|
|
397
396
|
def plot(self, log_files, sort='time', limit=10, nfl_filter='',
|
398
397
|
metric_selected='cc', plot_type='bar'):
|
399
398
|
if not PLOTLIB_INSTALLED:
|
400
|
-
raise PLOTLIBNotInstalled(
|
399
|
+
raise PLOTLIBNotInstalled('python-matplotlib not installed.')
|
401
400
|
if len(log_files) == 0:
|
402
|
-
raise NotFoundException(
|
401
|
+
raise NotFoundException('no log file found')
|
403
402
|
try:
|
404
403
|
stats = Stats2(*log_files)
|
405
404
|
stats.sort_stats(sort)
|
@@ -433,7 +432,7 @@ class HTMLViewer(object):
|
|
433
432
|
data = profile_img.read()
|
434
433
|
return data, [('content-type', 'image/jpg')]
|
435
434
|
except Exception as ex:
|
436
|
-
raise ProfileException(
|
435
|
+
raise ProfileException('plotting results failed due to %s' % ex)
|
437
436
|
|
438
437
|
def format_source_code(self, nfl):
|
439
438
|
nfls = re.split('[:()]', nfl)
|
@@ -444,7 +443,7 @@ class HTMLViewer(object):
|
|
444
443
|
lineno = 0
|
445
444
|
# for security reason, this need to be fixed.
|
446
445
|
if not file_path.endswith('.py'):
|
447
|
-
return
|
446
|
+
return 'The file type are forbidden to access!'
|
448
447
|
try:
|
449
448
|
data = []
|
450
449
|
i = 0
|
@@ -465,7 +464,7 @@ class HTMLViewer(object):
|
|
465
464
|
data.append(fmt % (i, i, i, el))
|
466
465
|
data = ''.join(data)
|
467
466
|
except Exception:
|
468
|
-
return
|
467
|
+
return 'Can not access the file %s.' % file_path
|
469
468
|
return '<pre>%s</pre>' % data
|
470
469
|
|
471
470
|
def generate_stats_html(self, stats, app_path, profile_id, *selection):
|
@@ -20,7 +20,6 @@ import pstats
|
|
20
20
|
import tempfile
|
21
21
|
import time
|
22
22
|
|
23
|
-
from swift import gettext_ as _
|
24
23
|
from swift.common.middleware.x_profile.exceptions import ODFLIBNotInstalled
|
25
24
|
|
26
25
|
|
@@ -125,7 +124,7 @@ class Stats2(pstats.Stats):
|
|
125
124
|
|
126
125
|
def to_ods(self, *selection):
|
127
126
|
if not ODFLIB_INSTALLED:
|
128
|
-
raise ODFLIBNotInstalled(
|
127
|
+
raise ODFLIBNotInstalled('odfpy not installed.')
|
129
128
|
if self.fcn_list:
|
130
129
|
stat_list = self.fcn_list[:]
|
131
130
|
order_text = " Ordered by: " + self.sort_type + '\n'
|
@@ -83,7 +83,6 @@ import eventlet.green.profile as eprofile
|
|
83
83
|
import six
|
84
84
|
from six.moves import urllib
|
85
85
|
|
86
|
-
from swift import gettext_ as _
|
87
86
|
from swift.common.utils import get_logger, config_true_value
|
88
87
|
from swift.common.swob import Request
|
89
88
|
from swift.common.middleware.x_profile.exceptions import MethodNotAllowed
|
@@ -227,7 +226,7 @@ class ProfileMiddleware(object):
|
|
227
226
|
return '%s' % pf
|
228
227
|
except Exception as ex:
|
229
228
|
start_response('500 Internal Server Error', [])
|
230
|
-
return
|
229
|
+
return 'Error on render profiling results: %s' % ex
|
231
230
|
else:
|
232
231
|
_locals = locals()
|
233
232
|
code = self.unwind and PROFILE_EXEC_EAGER or\
|
swift/common/request_helpers.py
CHANGED
@@ -95,6 +95,39 @@ def get_param(req, name, default=None):
|
|
95
95
|
return value
|
96
96
|
|
97
97
|
|
98
|
+
def get_valid_part_num(req):
|
99
|
+
"""
|
100
|
+
Any non-range GET or HEAD request for a SLO object may include a
|
101
|
+
part-number parameter in query string. If the passed in request
|
102
|
+
includes a part-number parameter it will be parsed into a valid integer
|
103
|
+
and returned. If the passed in request does not include a part-number
|
104
|
+
param we will return None. If the part-number parameter is invalid for
|
105
|
+
the given request we will raise the appropriate HTTP exception
|
106
|
+
|
107
|
+
:param req: the request object
|
108
|
+
|
109
|
+
:returns: validated part-number value or None
|
110
|
+
:raises HTTPBadRequest: if request or part-number param is not valid
|
111
|
+
"""
|
112
|
+
part_number_param = get_param(req, 'part-number')
|
113
|
+
if part_number_param is None:
|
114
|
+
return None
|
115
|
+
try:
|
116
|
+
part_number = int(part_number_param)
|
117
|
+
if part_number <= 0:
|
118
|
+
raise ValueError
|
119
|
+
except ValueError:
|
120
|
+
raise HTTPBadRequest('Part number must be an integer greater '
|
121
|
+
'than 0')
|
122
|
+
|
123
|
+
if req.range:
|
124
|
+
raise HTTPBadRequest(req=req,
|
125
|
+
body='Range requests are not supported '
|
126
|
+
'with part number queries')
|
127
|
+
|
128
|
+
return part_number
|
129
|
+
|
130
|
+
|
98
131
|
def validate_params(req, names):
|
99
132
|
"""
|
100
133
|
Get list of parameters from an HTTP request, validating the encoding of
|
@@ -960,3 +993,39 @@ def get_ip_port(node, headers):
|
|
960
993
|
"""
|
961
994
|
return select_ip_port(
|
962
995
|
node, use_replication=is_use_replication_network(headers))
|
996
|
+
|
997
|
+
|
998
|
+
def is_open_expired(app, req):
|
999
|
+
"""
|
1000
|
+
Helper function to check if a request with the header 'x-open-expired'
|
1001
|
+
can access an object that has not yet been reaped by the object-expirer
|
1002
|
+
based on the allow_open_expired global config.
|
1003
|
+
|
1004
|
+
:param app: the application instance
|
1005
|
+
:param req: request object
|
1006
|
+
"""
|
1007
|
+
return (config_true_value(app.allow_open_expired) and
|
1008
|
+
config_true_value(req.headers.get('x-open-expired')))
|
1009
|
+
|
1010
|
+
|
1011
|
+
def is_backend_open_expired(request):
|
1012
|
+
"""
|
1013
|
+
Helper function to check if a request has either the headers
|
1014
|
+
'x-backend-open-expired' or 'x-backend-replication' for the backend
|
1015
|
+
to access expired objects.
|
1016
|
+
|
1017
|
+
:param request: request object
|
1018
|
+
"""
|
1019
|
+
x_backend_open_expired = config_true_value(request.headers.get(
|
1020
|
+
'x-backend-open-expired', 'false'))
|
1021
|
+
x_backend_replication = config_true_value(request.headers.get(
|
1022
|
+
'x-backend-replication', 'false'))
|
1023
|
+
return x_backend_open_expired or x_backend_replication
|
1024
|
+
|
1025
|
+
|
1026
|
+
def append_log_info(environ, log_info):
|
1027
|
+
environ.setdefault('swift.log_info', []).append(log_info)
|
1028
|
+
|
1029
|
+
|
1030
|
+
def get_log_info(environ):
|
1031
|
+
return ','.join(environ.get('swift.log_info', []))
|