diff --git a/swift/proxy/controllers/obj.py b/swift/proxy/controllers/obj.py index a4bd0733c8..e86b35debe 100644 --- a/swift/proxy/controllers/obj.py +++ b/swift/proxy/controllers/obj.py @@ -657,13 +657,17 @@ class BaseObjectController(Controller): if any(conn for conn in conns if conn.resp and conn.resp.status == HTTP_CONFLICT): - timestamps = [HeaderKeyDict(conn.resp.getheaders()).get( - 'X-Backend-Timestamp') for conn in conns if conn.resp] + status_times = ['%(status)s (%(timestamp)s)' % { + 'status': conn.resp.status, + 'timestamp': HeaderKeyDict( + conn.resp.getheaders()).get( + 'X-Backend-Timestamp', 'unknown') + } for conn in conns if conn.resp] self.app.logger.debug( _('Object PUT returning 202 for 409: ' '%(req_timestamp)s <= %(timestamps)r'), {'req_timestamp': req.timestamp.internal, - 'timestamps': ', '.join(timestamps)}) + 'timestamps': ', '.join(status_times)}) raise HTTPAccepted(request=req) self._check_min_conn(req, conns, min_conns) diff --git a/test/unit/__init__.py b/test/unit/__init__.py index b67f44342c..fd9411b942 100644 --- a/test/unit/__init__.py +++ b/test/unit/__init__.py @@ -860,7 +860,9 @@ def fake_http_connect(*code_iter, **kwargs): headers = dict(self.expect_headers) if expect_status == 409: headers['X-Backend-Timestamp'] = self.timestamp - response = FakeConn(expect_status, headers=headers) + response = FakeConn(expect_status, + timestamp=self.timestamp, + headers=headers) response.status = expect_status return response diff --git a/test/unit/proxy/controllers/test_obj.py b/test/unit/proxy/controllers/test_obj.py index 2684927d02..22685ad178 100755 --- a/test/unit/proxy/controllers/test_obj.py +++ b/test/unit/proxy/controllers/test_obj.py @@ -771,6 +771,43 @@ class TestReplicatedObjController(BaseObjectControllerMixin, resp = req.get_response(self.app) self.assertEqual(resp.status_int, 202) + def test_put_x_timestamp_conflict_with_missing_backend_timestamp(self): + ts = (utils.Timestamp(t) for t in itertools.count(int(time.time()))) + req = swob.Request.blank( + '/v1/a/c/o', method='PUT', headers={ + 'Content-Length': 0, + 'X-Timestamp': ts.next().internal}) + ts_iter = iter([None, None, None]) + codes = [409] * self.obj_ring.replicas + with set_http_connect(*codes, timestamps=ts_iter): + resp = req.get_response(self.app) + self.assertEqual(resp.status_int, 202) + + def test_put_x_timestamp_conflict_with_other_weird_success_response(self): + ts = (utils.Timestamp(t) for t in itertools.count(int(time.time()))) + req = swob.Request.blank( + '/v1/a/c/o', method='PUT', headers={ + 'Content-Length': 0, + 'X-Timestamp': ts.next().internal}) + ts_iter = iter([ts.next().internal, None, None]) + codes = [409] + [(201, 'notused')] * (self.obj_ring.replicas - 1) + with set_http_connect(*codes, timestamps=ts_iter): + resp = req.get_response(self.app) + self.assertEqual(resp.status_int, 202) + + def test_put_x_timestamp_conflict_with_if_none_match(self): + ts = (utils.Timestamp(t) for t in itertools.count(int(time.time()))) + req = swob.Request.blank( + '/v1/a/c/o', method='PUT', headers={ + 'Content-Length': 0, + 'If-None-Match': '*', + 'X-Timestamp': ts.next().internal}) + ts_iter = iter([ts.next().internal, None, None]) + codes = [409] + [(412, 'notused')] * (self.obj_ring.replicas - 1) + with set_http_connect(*codes, timestamps=ts_iter): + resp = req.get_response(self.app) + self.assertEqual(resp.status_int, 412) + def test_container_sync_put_x_timestamp_race(self): ts = (utils.Timestamp(t) for t in itertools.count(int(time.time()))) test_indexes = [None] + [int(p) for p in POLICIES]