diff options
author | Janie Richling <jrichli@us.ibm.com> | 2016-02-19 18:07:18 -0600 |
---|---|---|
committer | Janie Richling <jrichli@us.ibm.com> | 2016-02-22 15:27:53 -0600 |
commit | b5a243e75a8033988063d2c1c90ac373bc0050d2 (patch) | |
tree | 3a12b7c46a72c69cf98ae82973b31301e3eac6b3 | |
parent | 99bc2a4a7d75f06b62908388e8890af2d7486085 (diff) | |
download | swift-b5a243e75a8033988063d2c1c90ac373bc0050d2.tar.gz |
Set backend content length for fallocate - EC Policy
Currently, the ECObjectController removes the 'content-length' header.
This part is ok, except that value is being used to set
'X-Backend-Obj-Content-Length', so it is always 0. This leads to not
calling fallocate (details on bug) on a PUT since the size is 0.
This change makes use of some numbers returned from the EC Driver
get_segment_info method in order to calculate the expected on-disk
size that should be allocated. The EC controller will now set the
'X-Backend-Obj-Content-Length' value appropriately.
Co-Authored-By: Kota Tsuyuzaki
Co-Authored-By: John Dickinson
Co-Authored-By: Tim Burke
Change-Id: Ifd16c1438539e6fd9bb2dbcd053d11bea2e09fee
Fixes: bug 1532008
-rw-r--r-- | swift/proxy/controllers/obj.py | 38 | ||||
-rwxr-xr-x | test/unit/proxy/controllers/test_obj.py | 12 |
2 files changed, 44 insertions, 6 deletions
diff --git a/swift/proxy/controllers/obj.py b/swift/proxy/controllers/obj.py index f3c13d589..9b5756725 100644 --- a/swift/proxy/controllers/obj.py +++ b/swift/proxy/controllers/obj.py @@ -1769,7 +1769,7 @@ class ECPutter(object): @classmethod def connect(cls, node, part, path, headers, conn_timeout, node_timeout, - chunked=False): + chunked=False, expected_frag_archive_size=None): """ Connect to a backend node and send the headers. @@ -1791,9 +1791,10 @@ class ECPutter(object): # we must use chunked encoding. headers['Transfer-Encoding'] = 'chunked' headers['Expect'] = '100-continue' - if 'Content-Length' in headers: - headers['X-Backend-Obj-Content-Length'] = \ - headers.pop('Content-Length') + + # make sure this isn't there + headers.pop('Content-Length') + headers['X-Backend-Obj-Content-Length'] = expected_frag_archive_size headers['X-Backend-Obj-Multipart-Mime-Boundary'] = mime_boundary @@ -2105,16 +2106,41 @@ class ECObjectController(BaseObjectController): # the object server will get different bytes, so these # values do not apply (Content-Length might, in general, but # in the specific case of replication vs. EC, it doesn't). - headers.pop('Content-Length', None) + client_cl = headers.pop('Content-Length', None) headers.pop('Etag', None) + expected_frag_size = None + if client_cl: + policy_index = int(headers.get('X-Backend-Storage-Policy-Index')) + policy = POLICIES.get_by_index(policy_index) + # TODO: PyECLib <= 1.2.0 looks to return the segment info + # different from the input for aligned data efficiency but + # Swift never does. So calculate the fragment length Swift + # will actually send to object sever by making two different + # get_segment_info calls (until PyECLib fixed). + # policy.fragment_size makes the call using segment size, + # and the next call is to get info for the last segment + + # get number of fragments except the tail - use truncation // + num_fragments = int(client_cl) // policy.ec_segment_size + expected_frag_size = policy.fragment_size * num_fragments + + # calculate the tail fragment_size by hand and add it to + # expected_frag_size + last_segment_size = int(client_cl) % policy.ec_segment_size + if last_segment_size: + last_info = policy.pyeclib_driver.get_segment_info( + last_segment_size, policy.ec_segment_size) + expected_frag_size += last_info['fragment_size'] + self.app.logger.thread_locals = logger_thread_locals for node in node_iter: try: putter = ECPutter.connect( node, part, path, headers, conn_timeout=self.app.conn_timeout, - node_timeout=self.app.node_timeout) + node_timeout=self.app.node_timeout, + expected_frag_archive_size=expected_frag_size) self.app.set_node_timing(node, putter.connect_duration) return putter except InsufficientStorage: diff --git a/test/unit/proxy/controllers/test_obj.py b/test/unit/proxy/controllers/test_obj.py index c39cd1950..ea4f6d150 100755 --- a/test/unit/proxy/controllers/test_obj.py +++ b/test/unit/proxy/controllers/test_obj.py @@ -1483,6 +1483,8 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase): conn_id = kwargs['connection_id'] put_requests[conn_id]['boundary'] = headers[ 'X-Backend-Obj-Multipart-Mime-Boundary'] + put_requests[conn_id]['backend-content-length'] = headers[ + 'X-Backend-Obj-Content-Length'] with set_http_connect(*codes, expect_headers=expect_headers, give_send=capture_body, @@ -1496,6 +1498,9 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase): self.assertTrue(info['boundary'] is not None, "didn't get boundary for conn %r" % ( connection_id,)) + self.assertTrue(size > int(info['backend-content-length']) > 0, + "invalid backend-content-length for conn %r" % ( + connection_id,)) # email.parser.FeedParser doesn't know how to take a multipart # message and boundary together and parse it; it only knows how @@ -1517,6 +1522,13 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase): self.assertEqual(obj_part['X-Document'], 'object body') frag_archives.append(obj_part.get_payload()) + # assert length was correct for this connection + self.assertEqual(int(info['backend-content-length']), + len(frag_archives[-1])) + # assert length was the same for all connections + self.assertEqual(int(info['backend-content-length']), + len(frag_archives[0])) + # validate some footer metadata self.assertEqual(footer_part['X-Document'], 'object metadata') footer_metadata = json.loads(footer_part.get_payload()) |