diff --git a/swift/common/direct_client.py b/swift/common/direct_client.py index a507901edc..94be486d74 100644 --- a/swift/common/direct_client.py +++ b/swift/common/direct_client.py @@ -39,13 +39,16 @@ from swift.common.utils import quote class DirectClientException(ClientException): - def __init__(self, stype, method, node, part, path, resp): + def __init__(self, stype, method, node, part, path, resp, host=None): + # host can be used to override the node ip and port reported in + # the exception + host = host if host is not None else node full_path = quote('/%s/%s%s' % (node['device'], part, path)) msg = '%s server %s:%s direct %s %r gave status %s' % ( - stype, node['ip'], node['port'], method, full_path, resp.status) + stype, host['ip'], host['port'], method, full_path, resp.status) headers = HeaderKeyDict(resp.getheaders()) super(DirectClientException, self).__init__( - msg, http_host=node['ip'], http_port=node['port'], + msg, http_host=host['ip'], http_port=host['port'], http_device=node['device'], http_status=resp.status, http_reason=resp.reason, http_headers=headers) @@ -489,7 +492,10 @@ def direct_get_suffix_hashes(node, part, suffixes, conn_timeout=5, resp = conn.getresponse() if not is_success(resp.status): raise DirectClientException('Object', 'REPLICATE', - node, part, path, resp) + node, part, path, resp, + host={'ip': node['replication_ip'], + 'port': node['replication_port']} + ) return pickle.loads(resp.read()) diff --git a/test/unit/common/test_direct_client.py b/test/unit/common/test_direct_client.py index 2bcc94c13d..503a941186 100644 --- a/test/unit/common/test_direct_client.py +++ b/test/unit/common/test_direct_client.py @@ -26,9 +26,10 @@ import six from six.moves import urllib from swift.common import direct_client +from swift.common.direct_client import DirectClientException from swift.common.exceptions import ClientException from swift.common.header_key_dict import HeaderKeyDict -from swift.common.utils import Timestamp +from swift.common.utils import Timestamp, quote from swift.common.swob import RESPONSE_REASONS from swift.common.storage_policy import POLICIES from six.moves.http_client import HTTPException @@ -631,6 +632,28 @@ class TestDirectClient(unittest.TestCase): self.assertEqual(conn.port, '7000') self.assertEqual(data, resp) + def _test_direct_get_suffix_hashes_fail(self, status_code): + with mocked_http_conn(status_code): + with self.assertRaises(DirectClientException) as cm: + direct_client.direct_get_suffix_hashes( + self.node, self.part, ['a83', 'b52']) + self.assertIn('REPLICATE', cm.exception.message) + self.assertIn(quote('/%s/%s/a83-b52' + % (self.node['device'], self.part)), + cm.exception.message) + self.assertIn(self.node['replication_ip'], cm.exception.message) + self.assertIn(self.node['replication_port'], cm.exception.message) + self.assertEqual(self.node['replication_ip'], cm.exception.http_host) + self.assertEqual(self.node['replication_port'], cm.exception.http_port) + self.assertEqual(self.node['device'], cm.exception.http_device) + self.assertEqual(status_code, cm.exception.http_status) + + def test_direct_get_suffix_hashes_503(self): + self._test_direct_get_suffix_hashes_fail(503) + + def test_direct_get_suffix_hashes_507(self): + self._test_direct_get_suffix_hashes_fail(507) + def test_direct_put_object_with_content_length(self): contents = six.StringIO('123456') @@ -720,6 +743,17 @@ class TestDirectClient(unittest.TestCase): retries=2, error_log=logger.error) self.assertEqual('DELETE', conn.method) self.assertEqual(err_ctx.exception.http_status, 500) + self.assertIn('DELETE', err_ctx.exception.message) + self.assertIn(quote('/%s/%s/%s/%s/%s' + % (self.node['device'], self.part, self.account, + self.container, self.obj)), + err_ctx.exception.message) + self.assertIn(self.node['ip'], err_ctx.exception.message) + self.assertIn(self.node['port'], err_ctx.exception.message) + self.assertEqual(self.node['ip'], err_ctx.exception.http_host) + self.assertEqual(self.node['port'], err_ctx.exception.http_port) + self.assertEqual(self.node['device'], err_ctx.exception.http_device) + self.assertEqual(500, err_ctx.exception.http_status) self.assertEqual([mock.call(1), mock.call(2)], mock_sleep.call_args_list) error_lines = logger.get_lines_for_level('error')