diff --git a/swift/proxy/controllers/base.py b/swift/proxy/controllers/base.py index 70940f9c16..28d213ac5a 100644 --- a/swift/proxy/controllers/base.py +++ b/swift/proxy/controllers/base.py @@ -927,6 +927,7 @@ class ResumingGetter(object): 'part_iter': part_iter} self.pop_range() except StopIteration: + req.environ['swift.non_client_disconnect'] = True return except ChunkReadTimeout: diff --git a/swift/proxy/controllers/obj.py b/swift/proxy/controllers/obj.py index c82c83150d..148f2d0aa8 100644 --- a/swift/proxy/controllers/obj.py +++ b/swift/proxy/controllers/obj.py @@ -1290,6 +1290,8 @@ class ECAppIter(object): # 100-byte object with 1024-byte segments. That's not # what we're dealing with here, though. if client_asked_for_range and not satisfiable: + req.environ[ + 'swift.non_client_disconnect'] = True raise HTTPRequestedRangeNotSatisfiable( request=req, headers=resp_headers) self.learned_content_type = content_type diff --git a/test/unit/proxy/test_server.py b/test/unit/proxy/test_server.py index 539d6ef102..4081f3a025 100644 --- a/test/unit/proxy/test_server.py +++ b/test/unit/proxy/test_server.py @@ -1133,6 +1133,8 @@ class TestObjectController(unittest.TestCase): logger=debug_logger('proxy-ut'), account_ring=FakeRing(), container_ring=FakeRing()) + # clear proxy logger result for each test + _test_servers[0].logger._clear() def tearDown(self): self.app.account_ring.set_replicas(3) @@ -2055,6 +2057,7 @@ class TestObjectController(unittest.TestCase): obj = '0123456' * 11 * 17 prolis = _test_sockets[0] + prosrv = _test_servers[0] sock = connect_tcp(('localhost', prolis.getsockname()[1])) fd = sock.makefile() fd.write('PUT /v1/a/ec-con/go-get-it HTTP/1.1\r\n' @@ -2094,6 +2097,10 @@ class TestObjectController(unittest.TestCase): break gotten_obj += buf self.assertEqual(gotten_obj, obj) + error_lines = prosrv.logger.get_lines_for_level('error') + warn_lines = prosrv.logger.get_lines_for_level('warning') + self.assertEquals(len(error_lines), 0) # sanity + self.assertEquals(len(warn_lines), 0) # sanity @unpatch_policies def test_conditional_GET_ec(self): @@ -2119,7 +2126,7 @@ class TestObjectController(unittest.TestCase): exp = 'HTTP/1.1 201' self.assertEqual(headers[:len(exp)], exp) - for verb in ('GET', 'HEAD'): + for verb, body in (('GET', obj), ('HEAD', '')): # If-Match req = Request.blank( '/v1/a/ec-con/conditionals', @@ -2127,6 +2134,7 @@ class TestObjectController(unittest.TestCase): headers={'If-Match': etag}) resp = req.get_response(prosrv) self.assertEqual(resp.status_int, 200) + self.assertEqual(resp.body, body) req = Request.blank( '/v1/a/ec-con/conditionals', @@ -2141,6 +2149,7 @@ class TestObjectController(unittest.TestCase): headers={'If-Match': "*"}) resp = req.get_response(prosrv) self.assertEqual(resp.status_int, 200) + self.assertEqual(resp.body, body) # If-None-Match req = Request.blank( @@ -2156,6 +2165,7 @@ class TestObjectController(unittest.TestCase): headers={'If-None-Match': not_etag}) resp = req.get_response(prosrv) self.assertEqual(resp.status_int, 200) + self.assertEqual(resp.body, body) req = Request.blank( '/v1/a/ec-con/conditionals', @@ -2163,6 +2173,10 @@ class TestObjectController(unittest.TestCase): headers={'If-None-Match': "*"}) resp = req.get_response(prosrv) self.assertEqual(resp.status_int, 304) + error_lines = prosrv.logger.get_lines_for_level('error') + warn_lines = prosrv.logger.get_lines_for_level('warning') + self.assertEquals(len(error_lines), 0) # sanity + self.assertEquals(len(warn_lines), 0) # sanity @unpatch_policies def test_GET_ec_big(self): @@ -2176,6 +2190,7 @@ class TestObjectController(unittest.TestCase): "object is too small for proper testing") prolis = _test_sockets[0] + prosrv = _test_servers[0] sock = connect_tcp(('localhost', prolis.getsockname()[1])) fd = sock.makefile() fd.write('PUT /v1/a/ec-con/big-obj-get HTTP/1.1\r\n' @@ -2217,6 +2232,10 @@ class TestObjectController(unittest.TestCase): # of garbage and demolishes your terminal's scrollback buffer. self.assertEqual(len(gotten_obj), len(obj)) self.assertEqual(gotten_obj, obj) + error_lines = prosrv.logger.get_lines_for_level('error') + warn_lines = prosrv.logger.get_lines_for_level('warning') + self.assertEquals(len(error_lines), 0) # sanity + self.assertEquals(len(warn_lines), 0) # sanity @unpatch_policies def test_GET_ec_failure_handling(self): @@ -2301,6 +2320,7 @@ class TestObjectController(unittest.TestCase): obj = '0123456' * 11 * 17 prolis = _test_sockets[0] + prosrv = _test_servers[0] sock = connect_tcp(('localhost', prolis.getsockname()[1])) fd = sock.makefile() fd.write('PUT /v1/a/ec-con/go-head-it HTTP/1.1\r\n' @@ -2332,12 +2352,17 @@ class TestObjectController(unittest.TestCase): self.assertEqual(str(len(obj)), headers['Content-Length']) self.assertEqual(md5(obj).hexdigest(), headers['Etag']) self.assertEqual('chartreuse', headers['X-Object-Meta-Color']) + error_lines = prosrv.logger.get_lines_for_level('error') + warn_lines = prosrv.logger.get_lines_for_level('warning') + self.assertEquals(len(error_lines), 0) # sanity + self.assertEquals(len(warn_lines), 0) # sanity @unpatch_policies def test_GET_ec_404(self): self.put_container("ec", "ec-con") prolis = _test_sockets[0] + prosrv = _test_servers[0] sock = connect_tcp(('localhost', prolis.getsockname()[1])) fd = sock.makefile() fd.write('GET /v1/a/ec-con/yes-we-have-no-bananas HTTP/1.1\r\n' @@ -2349,12 +2374,17 @@ class TestObjectController(unittest.TestCase): headers = readuntil2crlfs(fd) exp = 'HTTP/1.1 404' self.assertEqual(headers[:len(exp)], exp) + error_lines = prosrv.logger.get_lines_for_level('error') + warn_lines = prosrv.logger.get_lines_for_level('warning') + self.assertEquals(len(error_lines), 0) # sanity + self.assertEquals(len(warn_lines), 0) # sanity @unpatch_policies def test_HEAD_ec_404(self): self.put_container("ec", "ec-con") prolis = _test_sockets[0] + prosrv = _test_servers[0] sock = connect_tcp(('localhost', prolis.getsockname()[1])) fd = sock.makefile() fd.write('HEAD /v1/a/ec-con/yes-we-have-no-bananas HTTP/1.1\r\n' @@ -2366,6 +2396,10 @@ class TestObjectController(unittest.TestCase): headers = readuntil2crlfs(fd) exp = 'HTTP/1.1 404' self.assertEqual(headers[:len(exp)], exp) + error_lines = prosrv.logger.get_lines_for_level('error') + warn_lines = prosrv.logger.get_lines_for_level('warning') + self.assertEquals(len(error_lines), 0) # sanity + self.assertEquals(len(warn_lines), 0) # sanity def test_PUT_expect_header_zero_content_length(self): test_errors = [] @@ -5421,6 +5455,62 @@ class TestObjectController(unittest.TestCase): finally: time.time = orig_time + @unpatch_policies + def test_ec_client_disconnect(self): + prolis = _test_sockets[0] + + # create connection + sock = connect_tcp(('localhost', prolis.getsockname()[1])) + fd = sock.makefile() + + # create container + fd.write('PUT /v1/a/ec-discon HTTP/1.1\r\n' + 'Host: localhost\r\n' + 'Content-Length: 0\r\n' + 'X-Storage-Token: t\r\n' + 'X-Storage-Policy: ec\r\n' + '\r\n') + fd.flush() + headers = readuntil2crlfs(fd) + exp = 'HTTP/1.1 2' + self.assertEqual(headers[:len(exp)], exp) + + # create object + obj = 'a' * 4 * 64 * 2 ** 10 + fd.write('PUT /v1/a/ec-discon/test HTTP/1.1\r\n' + 'Host: localhost\r\n' + 'Content-Length: %d\r\n' + 'X-Storage-Token: t\r\n' + 'Content-Type: donuts\r\n' + '\r\n%s' % (len(obj), obj)) + fd.flush() + headers = readuntil2crlfs(fd) + exp = 'HTTP/1.1 201' + self.assertEqual(headers[:len(exp)], exp) + + # get object + fd.write('GET /v1/a/ec-discon/test HTTP/1.1\r\n' + 'Host: localhost\r\n' + 'Connection: close\r\n' + 'X-Storage-Token: t\r\n' + '\r\n') + fd.flush() + headers = readuntil2crlfs(fd) + exp = 'HTTP/1.1 200' + self.assertEqual(headers[:len(exp)], exp) + + # read most of the object, and disconnect + fd.read(10) + fd.close() + sock.close() + sleep(0) + + # check for disconnect message! + expected = ['Client disconnected on read'] * 2 + self.assertEqual( + _test_servers[0].logger.get_lines_for_level('warning'), + expected) + @unpatch_policies def test_leak_1(self): _request_instances = weakref.WeakKeyDictionary() @@ -5984,12 +6074,18 @@ class TestECMismatchedFA(unittest.TestCase): class TestObjectECRangedGET(unittest.TestCase): def setUp(self): + _test_servers[0].logger._clear() self.app = proxy_server.Application( None, FakeMemcache(), logger=debug_logger('proxy-ut'), account_ring=FakeRing(), container_ring=FakeRing()) + def tearDown(self): + prosrv = _test_servers[0] + self.assertFalse(prosrv.logger.get_lines_for_level('error')) + self.assertFalse(prosrv.logger.get_lines_for_level('warning')) + @classmethod def setUpClass(cls): cls.obj_name = 'range-get-test'