Merge "s3api: include error response reason in log_info"

This commit is contained in:
Zuul 2024-06-25 07:21:11 +00:00 committed by Gerrit Code Review
commit d1a7bc6afd
9 changed files with 179 additions and 30 deletions

View File

@ -17,6 +17,7 @@ import os
from swift.common.constraints import valid_api_version from swift.common.constraints import valid_api_version
from swift.common.container_sync_realms import ContainerSyncRealms from swift.common.container_sync_realms import ContainerSyncRealms
from swift.common.request_helpers import append_log_info
from swift.common.swob import HTTPBadRequest, HTTPUnauthorized, wsgify from swift.common.swob import HTTPBadRequest, HTTPUnauthorized, wsgify
from swift.common.utils import ( from swift.common.utils import (
config_true_value, get_logger, streq_const_time) config_true_value, get_logger, streq_const_time)
@ -109,20 +110,17 @@ class ContainerSync(object):
valid = False valid = False
auth = auth.split() auth = auth.split()
if len(auth) != 3: if len(auth) != 3:
req.environ.setdefault('swift.log_info', []).append( append_log_info(req.environ, 'cs:not-3-args')
'cs:not-3-args')
else: else:
realm, nonce, sig = auth realm, nonce, sig = auth
realm_key = self.realms_conf.key(realm) realm_key = self.realms_conf.key(realm)
realm_key2 = self.realms_conf.key2(realm) realm_key2 = self.realms_conf.key2(realm)
if not realm_key: if not realm_key:
req.environ.setdefault('swift.log_info', []).append( append_log_info(req.environ, 'cs:no-local-realm-key')
'cs:no-local-realm-key')
else: else:
user_key = info.get('sync_key') user_key = info.get('sync_key')
if not user_key: if not user_key:
req.environ.setdefault('swift.log_info', []).append( append_log_info(req.environ, 'cs:no-local-user-key')
'cs:no-local-user-key')
else: else:
# x-timestamp headers get shunted by gatekeeper # x-timestamp headers get shunted by gatekeeper
if 'x-backend-inbound-x-timestamp' in req.headers: if 'x-backend-inbound-x-timestamp' in req.headers:
@ -139,11 +137,9 @@ class ContainerSync(object):
realm_key2, user_key) if realm_key2 else expected realm_key2, user_key) if realm_key2 else expected
if not streq_const_time(sig, expected) and \ if not streq_const_time(sig, expected) and \
not streq_const_time(sig, expected2): not streq_const_time(sig, expected2):
req.environ.setdefault( append_log_info(req.environ, 'cs:invalid-sig')
'swift.log_info', []).append('cs:invalid-sig')
else: else:
req.environ.setdefault( append_log_info(req.environ, 'cs:valid')
'swift.log_info', []).append('cs:valid')
valid = True valid = True
if not valid: if not valid:
exc = HTTPUnauthorized( exc = HTTPUnauthorized(

View File

@ -89,6 +89,7 @@ import os
import time import time
from swift.common.middleware.catch_errors import enforce_byte_count from swift.common.middleware.catch_errors import enforce_byte_count
from swift.common.request_helpers import get_log_info
from swift.common.swob import Request from swift.common.swob import Request
from swift.common.utils import (get_logger, get_remote_client, from swift.common.utils import (get_logger, get_remote_client,
config_true_value, reiterate, config_true_value, reiterate,
@ -318,8 +319,7 @@ class ProxyLoggingMiddleware(object):
'transaction_id': req.environ.get('swift.trans_id'), 'transaction_id': req.environ.get('swift.trans_id'),
'request_time': duration_time_str, 'request_time': duration_time_str,
'source': req.environ.get('swift.source'), 'source': req.environ.get('swift.source'),
'log_info': 'log_info': get_log_info(req.environ),
','.join(req.environ.get('swift.log_info', '')),
'policy_index': policy_index, 'policy_index': policy_index,
'ttfb': ttfb, 'ttfb': ttfb,
'pid': self.pid, 'pid': self.pid,

View File

@ -149,6 +149,7 @@ from six.moves.urllib.parse import parse_qs
from swift.common.constraints import valid_api_version from swift.common.constraints import valid_api_version
from swift.common.middleware.listing_formats import \ from swift.common.middleware.listing_formats import \
MAX_CONTAINER_LISTING_CONTENT_LENGTH MAX_CONTAINER_LISTING_CONTENT_LENGTH
from swift.common.request_helpers import append_log_info
from swift.common.wsgi import PipelineWrapper, loadcontext, WSGIContext from swift.common.wsgi import PipelineWrapper, loadcontext, WSGIContext
from swift.common.middleware import app_property from swift.common.middleware import app_property
@ -353,6 +354,7 @@ class S3ApiMiddleware(object):
self.logger.debug(e.cause) self.logger.debug(e.cause)
except ErrorResponse as err_resp: except ErrorResponse as err_resp:
self.logger.increment(err_resp.metric_name) self.logger.increment(err_resp.metric_name)
append_log_info(env, 's3:err:%s' % err_resp.summary)
if isinstance(err_resp, InternalError): if isinstance(err_resp, InternalError):
self.logger.exception(err_resp) self.logger.exception(err_resp)
resp = err_resp resp = err_resp

View File

@ -252,12 +252,17 @@ class ErrorResponse(S3ResponseBase, swob.HTTPException):
self.headers = HeaderKeyDict(self.headers) self.headers = HeaderKeyDict(self.headers)
@property @property
def metric_name(self): def summary(self):
parts = [str(self.status_int), self._code] """Provide a summary of the error code and reason."""
if self.reason: if self.reason:
parts.append(self.reason) summary = '.'.join([self._code, self.reason])
metric = '.'.join(parts) else:
return metric.replace(' ', '_') summary = self._code
return summary.replace(' ', '_')
@property
def metric_name(self):
return '.'.join([str(self.status_int), self.summary])
def _body_iter(self): def _body_iter(self):
error_elem = Element('Error') error_elem = Element('Error')

View File

@ -1021,3 +1021,11 @@ def is_backend_open_expired(request):
x_backend_replication = config_true_value(request.headers.get( x_backend_replication = config_true_value(request.headers.get(
'x-backend-replication', 'false')) 'x-backend-replication', 'false'))
return x_backend_open_expired or x_backend_replication return x_backend_open_expired or x_backend_replication
def append_log_info(environ, log_info):
environ.setdefault('swift.log_info', []).append(log_info)
def get_log_info(environ):
return ','.join(environ.get('swift.log_info', []))

View File

@ -78,7 +78,7 @@ from swift.common.swob import HTTPAccepted, HTTPBadRequest, HTTPNotFound, \
normalize_etag, str_to_wsgi normalize_etag, str_to_wsgi
from swift.common.request_helpers import update_etag_is_at_header, \ from swift.common.request_helpers import update_etag_is_at_header, \
resolve_etag_is_at_header, validate_internal_obj, get_ip_port, \ resolve_etag_is_at_header, validate_internal_obj, get_ip_port, \
is_open_expired is_open_expired, append_log_info
def check_content_type(req): def check_content_type(req):
@ -631,8 +631,7 @@ class BaseObjectController(Controller):
int(req.headers['x-delete-at'])) int(req.headers['x-delete-at']))
x_delete_at = int(req.headers['x-delete-at']) x_delete_at = int(req.headers['x-delete-at'])
req.environ.setdefault('swift.log_info', []).append( append_log_info(req.environ, 'x-delete-at:%s' % x_delete_at)
'x-delete-at:%s' % x_delete_at)
delete_at_container = get_expirer_container( delete_at_container = get_expirer_container(
x_delete_at, self.app.expiring_objects_container_divisor, x_delete_at, self.app.expiring_objects_container_divisor,

View File

@ -27,11 +27,13 @@ from paste.deploy import loadwsgi
from six.moves.urllib.parse import unquote, quote from six.moves.urllib.parse import unquote, quote
import swift.common.middleware.s3api import swift.common.middleware.s3api
from swift.common.middleware.proxy_logging import ProxyLoggingMiddleware
from swift.common.middleware.s3api.s3response import ErrorResponse, \ from swift.common.middleware.s3api.s3response import ErrorResponse, \
AccessDenied AccessDenied
from swift.common.middleware.s3api.utils import Config from swift.common.middleware.s3api.utils import Config
from swift.common.middleware.keystoneauth import KeystoneAuth from swift.common.middleware.keystoneauth import KeystoneAuth
from swift.common import swob, registry from swift.common import swob, registry
from swift.common.request_helpers import get_log_info
from swift.common.swob import Request from swift.common.swob import Request
from swift.common.utils import md5, get_logger, UTC from swift.common.utils import md5, get_logger, UTC
@ -255,6 +257,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'403.AccessDenied.invalid_header_auth': 1}, {'403.AccessDenied.invalid_header_auth': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_header_auth',
get_log_info(req.environ))
def test_bad_method(self): def test_bad_method(self):
req = Request.blank('/', req = Request.blank('/',
@ -266,6 +270,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'405.MethodNotAllowed': 1}, {'405.MethodNotAllowed': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:MethodNotAllowed',
get_log_info(req.environ))
def test_bad_method_but_method_exists_in_controller(self): def test_bad_method_but_method_exists_in_controller(self):
req = Request.blank( req = Request.blank(
@ -278,6 +284,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'405.MethodNotAllowed': 1}, {'405.MethodNotAllowed': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:MethodNotAllowed',
get_log_info(req.environ))
def test_path_info_encode(self): def test_path_info_encode(self):
bucket_name = 'b%75cket' bucket_name = 'b%75cket'
@ -412,6 +420,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'403.AccessDenied.expired': 1}, {'403.AccessDenied.expired': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.expired',
get_log_info(req.environ))
def test_signed_urls(self): def test_signed_urls(self):
# Set expire to last 32b timestamp value # Set expire to last 32b timestamp value
@ -461,6 +471,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'403.AccessDenied.invalid_expires': 1}, {'403.AccessDenied.invalid_expires': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_expires',
get_log_info(req.environ))
def test_signed_urls_no_sign(self): def test_signed_urls_no_sign(self):
expire = '2147483647' # 19 Jan 2038 03:14:07 expire = '2147483647' # 19 Jan 2038 03:14:07
@ -475,6 +487,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'403.AccessDenied.invalid_query_auth': 1}, {'403.AccessDenied.invalid_query_auth': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_query_auth',
get_log_info(req.environ))
def test_signed_urls_no_access(self): def test_signed_urls_no_access(self):
expire = '2147483647' # 19 Jan 2038 03:14:07 expire = '2147483647' # 19 Jan 2038 03:14:07
@ -488,6 +502,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'403.AccessDenied.invalid_query_auth': 1}, {'403.AccessDenied.invalid_query_auth': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_query_auth',
get_log_info(req.environ))
def test_signed_urls_v4(self): def test_signed_urls_v4(self):
req = Request.blank( req = Request.blank(
@ -537,6 +553,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'400.AuthorizationQueryParametersError': 1}, {'400.AuthorizationQueryParametersError': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AuthorizationQueryParametersError',
get_log_info(req.environ))
dt = self.get_v4_amz_date_header().split('T', 1)[0] dt = self.get_v4_amz_date_header().split('T', 1)[0]
test('test:tester/not-a-date/us-east-1/s3/aws4_request', test('test:tester/not-a-date/us-east-1/s3/aws4_request',
@ -568,6 +586,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'403.AccessDenied.invalid_date': 1}, {'403.AccessDenied.invalid_date': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_date',
get_log_info(req.environ))
def test_signed_urls_v4_invalid_algorithm(self): def test_signed_urls_v4_invalid_algorithm(self):
req = Request.blank( req = Request.blank(
@ -586,6 +606,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'400.InvalidArgument': 1}, {'400.InvalidArgument': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:InvalidArgument',
get_log_info(req.environ))
def test_signed_urls_v4_missing_signed_headers(self): def test_signed_urls_v4_missing_signed_headers(self):
req = Request.blank( req = Request.blank(
@ -604,6 +626,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'400.AuthorizationHeaderMalformed': 1}, {'400.AuthorizationHeaderMalformed': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AuthorizationHeaderMalformed',
get_log_info(req.environ))
def test_signed_urls_v4_invalid_credentials(self): def test_signed_urls_v4_invalid_credentials(self):
req = Request.blank('/bucket/object' req = Request.blank('/bucket/object'
@ -621,6 +645,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'403.AccessDenied.invalid_credential': 1}, {'403.AccessDenied.invalid_credential': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_credential',
get_log_info(req.environ))
def test_signed_urls_v4_missing_signature(self): def test_signed_urls_v4_missing_signature(self):
req = Request.blank( req = Request.blank(
@ -638,6 +664,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'403.AccessDenied.invalid_query_auth': 1}, {'403.AccessDenied.invalid_query_auth': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_query_auth',
get_log_info(req.environ))
def test_bucket_virtual_hosted_style(self): def test_bucket_virtual_hosted_style(self):
req = Request.blank('/', req = Request.blank('/',
@ -737,6 +765,7 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'400.InvalidURI': 1}, {'400.InvalidURI': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:InvalidURI', get_log_info(req.environ))
def test_object_create_bad_md5_unreadable(self): def test_object_create_bad_md5_unreadable(self):
req = Request.blank('/bucket/object', req = Request.blank('/bucket/object',
@ -749,6 +778,7 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'400.InvalidDigest': 1}, {'400.InvalidDigest': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:InvalidDigest', get_log_info(req.environ))
def test_object_create_bad_md5_too_short(self): def test_object_create_bad_md5_too_short(self):
too_short_digest = md5(b'hey', usedforsecurity=False).digest()[:-1] too_short_digest = md5(b'hey', usedforsecurity=False).digest()[:-1]
@ -766,6 +796,7 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'400.InvalidDigest': 1}, {'400.InvalidDigest': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:InvalidDigest', get_log_info(req.environ))
def test_object_create_bad_md5_bad_padding(self): def test_object_create_bad_md5_bad_padding(self):
too_short_digest = md5(b'hey', usedforsecurity=False).digest() too_short_digest = md5(b'hey', usedforsecurity=False).digest()
@ -783,6 +814,7 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'400.InvalidDigest': 1}, {'400.InvalidDigest': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:InvalidDigest', get_log_info(req.environ))
def test_object_create_bad_md5_too_long(self): def test_object_create_bad_md5_too_long(self):
too_long_digest = md5( too_long_digest = md5(
@ -801,6 +833,7 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'400.InvalidDigest': 1}, {'400.InvalidDigest': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:InvalidDigest', get_log_info(req.environ))
def test_invalid_metadata_directive(self): def test_invalid_metadata_directive(self):
req = Request.blank('/', req = Request.blank('/',
@ -814,6 +847,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'400.InvalidArgument': 1}, {'400.InvalidArgument': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:InvalidArgument',
get_log_info(req.environ))
def test_invalid_storage_class(self): def test_invalid_storage_class(self):
req = Request.blank('/', req = Request.blank('/',
@ -826,6 +861,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'400.InvalidStorageClass': 1}, {'400.InvalidStorageClass': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:InvalidStorageClass',
get_log_info(req.environ))
def test_invalid_ssc(self): def test_invalid_ssc(self):
req = Request.blank('/', req = Request.blank('/',
@ -838,6 +875,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'400.InvalidArgument': 1}, {'400.InvalidArgument': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:InvalidArgument',
get_log_info(req.environ))
def _test_unsupported_header(self, header, value=None): def _test_unsupported_header(self, header, value=None):
if value is None: if value is None:
@ -853,6 +892,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'501.NotImplemented': 1}, {'501.NotImplemented': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:NotImplemented',
get_log_info(req.environ))
def test_mfa(self): def test_mfa(self):
self._test_unsupported_header('x-amz-mfa') self._test_unsupported_header('x-amz-mfa')
@ -915,6 +956,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'501.NotImplemented': 1}, {'501.NotImplemented': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:NotImplemented',
get_log_info(req.environ))
def test_notification(self): def test_notification(self):
self._test_unsupported_resource('notification') self._test_unsupported_resource('notification')
@ -955,6 +998,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'501.NotImplemented': 1}, {'501.NotImplemented': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:NotImplemented',
get_log_info(req.environ))
req = Request.blank('/bucket?tagging', req = Request.blank('/bucket?tagging',
environ={'REQUEST_METHOD': 'DELETE'}, environ={'REQUEST_METHOD': 'DELETE'},
@ -966,6 +1011,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'501.NotImplemented': 1}, {'501.NotImplemented': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:NotImplemented',
get_log_info(req.environ))
def test_restore(self): def test_restore(self):
self._test_unsupported_resource('restore') self._test_unsupported_resource('restore')
@ -983,6 +1030,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'405.MethodNotAllowed': 1}, {'405.MethodNotAllowed': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:MethodNotAllowed',
get_log_info(req.environ))
@mock.patch.object(registry, '_sensitive_headers', set()) @mock.patch.object(registry, '_sensitive_headers', set())
@mock.patch.object(registry, '_sensitive_params', set()) @mock.patch.object(registry, '_sensitive_params', set())
@ -1118,6 +1167,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'403.AccessDenied.invalid_date': 1}, {'403.AccessDenied.invalid_date': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_date',
get_log_info(req.environ))
def test_signature_v4_no_payload(self): def test_signature_v4_no_payload(self):
environ = { environ = {
@ -1140,6 +1191,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'400.InvalidRequest': 1}, {'400.InvalidRequest': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:InvalidRequest',
get_log_info(req.environ))
def test_signature_v4_bad_authorization_string(self): def test_signature_v4_bad_authorization_string(self):
def test(auth_str, error, msg, metric, extra=b''): def test(auth_str, error, msg, metric, extra=b''):
@ -1160,6 +1213,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{metric: 1}, {metric: 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:%s' % metric[4:],
get_log_info(req.environ))
auth_str = ('AWS4-HMAC-SHA256 ' auth_str = ('AWS4-HMAC-SHA256 '
'SignedHeaders=host;x-amz-date,' 'SignedHeaders=host;x-amz-date,'
@ -1424,6 +1479,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'403.AccessDenied.invalid_expires': 1}, {'403.AccessDenied.invalid_expires': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_expires',
get_log_info(req.environ))
# But if we are missing Signature in query param # But if we are missing Signature in query param
req = Request.blank( req = Request.blank(
@ -1440,6 +1497,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'403.AccessDenied.invalid_expires': 1}, {'403.AccessDenied.invalid_expires': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_expires',
get_log_info(req.environ))
def test_s3api_with_only_s3_token(self): def test_s3api_with_only_s3_token(self):
self.swift = FakeSwift() self.swift = FakeSwift()
@ -1555,6 +1614,8 @@ class TestS3ApiMiddleware(S3ApiTestCase):
self.assertEqual( self.assertEqual(
{'403.SignatureDoesNotMatch': 1}, {'403.SignatureDoesNotMatch': 1},
statsd_client.get_increment_counts()) statsd_client.get_increment_counts())
self.assertEqual('s3:err:SignatureDoesNotMatch',
get_log_info(req.environ))
def test_s3api_with_only_s3_token_in_s3acl(self): def test_s3api_with_only_s3_token_in_s3acl(self):
self.swift = FakeSwift() self.swift = FakeSwift()
@ -1600,39 +1661,46 @@ class TestS3ApiMiddleware(S3ApiTestCase):
'AWS test:tester:hmac'}, 'AWS test:tester:hmac'},
headers={'Date': self.get_date_header(skew=skew)}) headers={'Date': self.get_date_header(skew=skew)})
self.s3api.logger.logger.clear() self.s3api.logger.logger.clear()
return self.call_s3api(req) status, headers, body = self.call_s3api(req)
return req, status, headers, body
status, _, body = do_test(800) req, status, _, body = do_test(800)
self.assertEqual('200 OK', status) self.assertEqual('200 OK', status)
self.assertFalse( self.assertFalse(
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
status, _, body = do_test(-800) req, status, _, body = do_test(-800)
self.assertEqual('200 OK', status) self.assertEqual('200 OK', status)
self.assertFalse( self.assertFalse(
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
status, _, body = do_test(1000) req, status, _, body = do_test(1000)
self.assertEqual('403 Forbidden', status) self.assertEqual('403 Forbidden', status)
self.assertEqual(self._get_error_code(body), 'RequestTimeTooSkewed') self.assertEqual(self._get_error_code(body), 'RequestTimeTooSkewed')
self.assertEqual( self.assertEqual(
{'403.RequestTimeTooSkewed': 1}, {'403.RequestTimeTooSkewed': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:RequestTimeTooSkewed',
get_log_info(req.environ))
status, _, body = do_test(-1000) req, status, _, body = do_test(-1000)
self.assertEqual('403 Forbidden', status) self.assertEqual('403 Forbidden', status)
self.assertEqual(self._get_error_code(body), 'RequestTimeTooSkewed') self.assertEqual(self._get_error_code(body), 'RequestTimeTooSkewed')
self.assertEqual( self.assertEqual(
{'403.RequestTimeTooSkewed': 1}, {'403.RequestTimeTooSkewed': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:RequestTimeTooSkewed',
get_log_info(req.environ))
self.s3api.conf.allowable_clock_skew = 100 self.s3api.conf.allowable_clock_skew = 100
status, _, body = do_test(800) req, status, _, body = do_test(800)
self.assertEqual('403 Forbidden', status) self.assertEqual('403 Forbidden', status)
self.assertEqual(self._get_error_code(body), 'RequestTimeTooSkewed') self.assertEqual(self._get_error_code(body), 'RequestTimeTooSkewed')
self.assertEqual( self.assertEqual(
{'403.RequestTimeTooSkewed': 1}, {'403.RequestTimeTooSkewed': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:RequestTimeTooSkewed',
get_log_info(req.environ))
def test_s3api_error_metric(self): def test_s3api_error_metric(self):
class KaboomResponse(ErrorResponse): class KaboomResponse(ErrorResponse):
@ -1650,22 +1718,55 @@ class TestS3ApiMiddleware(S3ApiTestCase):
with mock.patch.object( with mock.patch.object(
self.s3api, 'handle_request', side_effect=err_response): self.s3api, 'handle_request', side_effect=err_response):
self.call_s3api(req) self.call_s3api(req)
return req
do_test(ErrorResponse(status=403, msg='not good', reason='bad')) req = do_test(ErrorResponse(status=403, msg='not good', reason='bad'))
self.assertEqual( self.assertEqual(
{'403.ErrorResponse.bad': 1}, {'403.ErrorResponse.bad': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:ErrorResponse.bad',
get_log_info(req.environ))
do_test(AccessDenied(msg='no entry', reason='invalid_date')) req = do_test(AccessDenied(msg='no entry', reason='invalid_date'))
self.assertEqual( self.assertEqual(
{'403.AccessDenied.invalid_date': 1}, {'403.AccessDenied.invalid_date': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_date',
get_log_info(req.environ))
# check whitespace replaced with underscore # check whitespace replaced with underscore
do_test(KaboomResponse(status=400, msg='boom', reason='boom boom')) req = do_test(KaboomResponse(status=400, msg='boom',
reason='boom boom'))
self.assertEqual( self.assertEqual(
{'400.ka_boom.boom_boom': 1}, {'400.ka_boom.boom_boom': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts()) self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:ka_boom.boom_boom',
get_log_info(req.environ))
def test_error_response_reason_logging(self):
# verify that proxy logging gets error reason in log_info
environ = {'REQUEST_METHOD': 'HEAD'}
headers = {
'Authorization':
'AWS4-HMAC-SHA256 SignedHeaders=host;x-amz-date,Signature=X',
'X-Amz-Date': self.get_v4_amz_date_header(),
# invalid sha
'X-Amz-Content-SHA256': '0123456789'}
req = Request.blank('/bucket/object', environ=environ,
headers=headers)
req.content_type = 'text/plain'
log_conf = {'log_msg_template': '{method} {path} {log_info}'}
app = ProxyLoggingMiddleware(self.s3api, log_conf, self.logger)
status, headers, body = self.call_app(req, app=app)
self.assertEqual(
{'403.AccessDenied.invalid_credential': 1},
self.s3api.logger.logger.statsd_client.get_increment_counts())
self.assertEqual('s3:err:AccessDenied.invalid_credential',
get_log_info(req.environ))
self.assertEqual(
['HEAD /bucket/object s3:err:AccessDenied.invalid_credential'],
self.logger.get_lines_for_level('info'))
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -17,7 +17,7 @@ import unittest
from swift.common.swob import Response from swift.common.swob import Response
from swift.common.utils import HeaderKeyDict from swift.common.utils import HeaderKeyDict
from swift.common.middleware.s3api.s3response import S3Response from swift.common.middleware.s3api.s3response import S3Response, ErrorResponse
from swift.common.middleware.s3api.utils import sysmeta_prefix from swift.common.middleware.s3api.utils import sysmeta_prefix
@ -103,5 +103,27 @@ class TestResponse(unittest.TestCase):
self.assertEqual(expected_headers, s3resp.sysmeta_headers) self.assertEqual(expected_headers, s3resp.sysmeta_headers)
class DummyErrorResponse(ErrorResponse):
_status = "418 I'm a teapot"
class TestErrorResponse(unittest.TestCase):
def test_error_response(self):
resp = DummyErrorResponse(msg='my-msg', reason='my reason')
self.assertEqual("418 I'm a teapot", str(resp))
self.assertEqual("418 I'm a teapot", resp.status)
self.assertEqual(418, resp.status_int)
self.assertEqual('my reason', resp.reason)
self.assertEqual('DummyErrorResponse.my_reason', resp.summary)
self.assertEqual('418.DummyErrorResponse.my_reason', resp.metric_name)
self.assertEqual(
b"<?xml version='1.0' encoding='UTF-8'?>\n"
b"<Error>"
b"<Code>DummyErrorResponse</Code>"
b"<Message>my-msg</Message>"
b"</Error>",
resp.body)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -32,6 +32,22 @@ server_types = ['account', 'container', 'object']
class TestRequestHelpers(unittest.TestCase): class TestRequestHelpers(unittest.TestCase):
def test_append_log_info(self):
req = Request.blank('/v/a/c/o')
self.assertNotIn('swift.log_info', req.environ)
rh.append_log_info(req.environ, 'msg1')
self.assertEqual(['msg1'], req.environ.get('swift.log_info'))
rh.append_log_info(req.environ, 'msg2')
self.assertEqual(['msg1', 'msg2'], req.environ.get('swift.log_info'))
def test_get_log_info(self):
req = Request.blank('/v/a/c/o')
self.assertEqual('', rh.get_log_info(req.environ))
req.environ['swift.log_info'] = ['msg1']
self.assertEqual('msg1', rh.get_log_info(req.environ))
rh.append_log_info(req.environ, 'msg2')
self.assertEqual('msg1,msg2', rh.get_log_info(req.environ))
def test_constrain_req_limit(self): def test_constrain_req_limit(self):
req = Request.blank('') req = Request.blank('')
self.assertEqual(10, rh.constrain_req_limit(req, 10)) self.assertEqual(10, rh.constrain_req_limit(req, 10))