)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"d42133d13626cadb9cadcb47bdf24d306a824081","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"8d48d2f1_07460ad8","updated":"2026-03-12 16:15:03.000000000","message":"I don\u0027t understand the strategy of simply logging that we have corrupted data but not fixing it.","commit_id":"2c633b03f7136b9c696eb844a2296608e611d0fb"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"59406d9d5d4bef6e5e9949ee2ab0078c3a16136d","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"203ed450_e9d15782","updated":"2026-03-15 20:06:40.000000000","message":"Thanks for the review!","commit_id":"2c633b03f7136b9c696eb844a2296608e611d0fb"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"0573ad958a408887e6c52bae95449fa0c6bf154a","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"de9beb76_59bf50c8","updated":"2026-03-18 12:36:21.000000000","message":"IIUC the plan is to squash in a fix so my +1 is just indicating that there\u0027s nothing broken here. The warning message does seem to have more timestamps than necessary.","commit_id":"485e9f98a7d38d2cd9792fa748d2798416b483ad"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"9ccdf6934026cbceb6a0552fc87e62960c2564a6","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"68335998_0a012e1b","updated":"2026-03-25 03:39:45.000000000","message":"thanks for the review!","commit_id":"485e9f98a7d38d2cd9792fa748d2798416b483ad"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"4e6920d9d371d46628d4a75aec25497e66af41b6","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":6,"id":"64e1869f_adede39c","updated":"2026-03-25 11:40:12.000000000","message":"Obviously a fix for the corruption would be better than a warning, but this at least adds something to gauge the scale of the problem and test coverage that will repurpose when we have a fix.","commit_id":"d0b321a8e6a197531abca9f442fd9c5538f10d62"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c7218d479790855ce99c0500ec507a44bbc60d21","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":6,"id":"7ad0a40e_81ef1fb0","updated":"2026-03-31 00:11:11.000000000","message":"on reflection I think this change is sort of \"reductio ad absurdum\" - there\u0027s no reason to log this as a \"warning\" - `reconstruct_fa` it turns out is already well documented/supported to raise `DiskFileError` and we can and *should* `raise DiskFileError` in this case.\n\nIMHO, this is already the best place to implement the detection and we can just do it and be done with it:\n\n982735: raise DiskfileError on \"bad\" useful_bucket | https://review.opendev.org/c/openstack/swift/+/982735","commit_id":"d0b321a8e6a197531abca9f442fd9c5538f10d62"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"b4fe22383f3e0558ded6aeb82c0a5a9934dfa421","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":6,"id":"1cc810d1_36620d90","updated":"2026-04-01 23:37:35.000000000","message":"we need to get a fix for Related-Bug: #2143202 merged; we should be looking for a minimal and obvious changes to reconstructor.py with a targeted and obvious test.\n\nI think we have all the pieces - but it\u0027s spread across 3 patches\n\nIMHO the closet most direct option would be to a squash of essentially:\n\n982735: raise DiskfileError on \"bad\" useful_bucket | https://review.opendev.org/c/openstack/swift/+/982735\n\nnotably:\n\n```\n- test_reconstruct_fa_warns_on_mismatched_timestamp\n+ test_reconstruct_fa_rejects_mismatched_timestamp\n```","commit_id":"d0b321a8e6a197531abca9f442fd9c5538f10d62"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"a10f0e795da99c438ee11ab8aa6ffbd11ea58796","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":6,"id":"aa2edf73_96d4e288","in_reply_to":"1cc810d1_36620d90","updated":"2026-04-05 18:24:37.000000000","message":"the continued iteration costs almost nothing (no new network I/O, just processing already-in-flight responses), but gives two concrete benefits — better error logs for operators, and correctness for duplication_factor \u003e 1 deployments. see comments and tests at: https://review.opendev.org/c/openstack/swift/+/978845/comment/9d9f5208_007cddbe/ and https://review.opendev.org/c/openstack/swift/+/978845/comment/1badef98_53202f70/","commit_id":"d0b321a8e6a197531abca9f442fd9c5538f10d62"}],"swift/obj/reconstructor.py":[{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"d42133d13626cadb9cadcb47bdf24d306a824081","unresolved":true,"context_lines":[{"line_number":683,"context_line":"                     local_timestamp.internal,"},{"line_number":684,"context_line":"                     useful_bucket.etag,"},{"line_number":685,"context_line":"                     local_etag,"},{"line_number":686,"context_line":"                     full_path, fi_to_rebuild))"},{"line_number":687,"context_line":"            frag_indexes \u003d list(useful_bucket.useful_responses.keys())"},{"line_number":688,"context_line":"            self.logger.debug(\u0027Reconstruct frag #%s with frag indexes %s\u0027"},{"line_number":689,"context_line":"                              % (fi_to_rebuild, frag_indexes))"}],"source_content_type":"text/x-python","patch_set":2,"id":"915ccc97_cbbf3422","line":686,"updated":"2026-03-12 16:15:03.000000000","message":"why would we ever proceed here rather than break? This is going to write corrupt data that will never be fixed, correct? And we have another very similar patch that fixes the corruption, so why are we not investing in THAT patch?","commit_id":"2c633b03f7136b9c696eb844a2296608e611d0fb"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"59406d9d5d4bef6e5e9949ee2ab0078c3a16136d","unresolved":false,"context_lines":[{"line_number":683,"context_line":"                     local_timestamp.internal,"},{"line_number":684,"context_line":"                     useful_bucket.etag,"},{"line_number":685,"context_line":"                     local_etag,"},{"line_number":686,"context_line":"                     full_path, fi_to_rebuild))"},{"line_number":687,"context_line":"            frag_indexes \u003d list(useful_bucket.useful_responses.keys())"},{"line_number":688,"context_line":"            self.logger.debug(\u0027Reconstruct frag #%s with frag indexes %s\u0027"},{"line_number":689,"context_line":"                              % (fi_to_rebuild, frag_indexes))"}],"source_content_type":"text/x-python","patch_set":2,"id":"6695241f_2267d6f2","line":686,"in_reply_to":"915ccc97_cbbf3422","updated":"2026-03-15 20:06:40.000000000","message":"as discussed offline, I will stack the fix patch on top of this logging patch.","commit_id":"2c633b03f7136b9c696eb844a2296608e611d0fb"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"a0d55ac297e2e8ec1b9ef7bfc1a7b3681cf2fb07","unresolved":true,"context_lines":[{"line_number":696,"context_line":"        for timestamp, bucket in sorted(buckets.items()):"},{"line_number":697,"context_line":"            if (len(bucket.useful_responses) \u003e\u003d policy.ec_ndata"},{"line_number":698,"context_line":"                    and (bucket.timestamp !\u003d local_timestamp"},{"line_number":699,"context_line":"                         or bucket.etag !\u003d local_etag)):"},{"line_number":700,"context_line":"                self.logger.warning("},{"line_number":701,"context_line":"                    \u0027Received enough fragments at timestamp %s \u0027"},{"line_number":702,"context_line":"                    \u0027(bucket.timestamp\u003d%s) but local timestamp is %s, \u0027"}],"source_content_type":"text/x-python","patch_set":2,"id":"556d6948_95e753f2","line":699,"updated":"2026-03-13 14:31:08.000000000","message":"I\u0027m not sure how we would get here: this bucket would have been the useful_bucket that was used at line 671 and the method returned at line 693","commit_id":"2c633b03f7136b9c696eb844a2296608e611d0fb"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"59406d9d5d4bef6e5e9949ee2ab0078c3a16136d","unresolved":false,"context_lines":[{"line_number":696,"context_line":"        for timestamp, bucket in sorted(buckets.items()):"},{"line_number":697,"context_line":"            if (len(bucket.useful_responses) \u003e\u003d policy.ec_ndata"},{"line_number":698,"context_line":"                    and (bucket.timestamp !\u003d local_timestamp"},{"line_number":699,"context_line":"                         or bucket.etag !\u003d local_etag)):"},{"line_number":700,"context_line":"                self.logger.warning("},{"line_number":701,"context_line":"                    \u0027Received enough fragments at timestamp %s \u0027"},{"line_number":702,"context_line":"                    \u0027(bucket.timestamp\u003d%s) but local timestamp is %s, \u0027"}],"source_content_type":"text/x-python","patch_set":2,"id":"a7e2c4da_125f685e","line":699,"in_reply_to":"556d6948_95e753f2","updated":"2026-03-15 20:06:40.000000000","message":"I copied this warning log from the following fix patch, I thought it doesn\u0027t hurt to have it. But let\u0027s remove it since it won\u0027t be reached.","commit_id":"2c633b03f7136b9c696eb844a2296608e611d0fb"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"a0d55ac297e2e8ec1b9ef7bfc1a7b3681cf2fb07","unresolved":true,"context_lines":[{"line_number":717,"context_line":"                    (len(bucket.useful_responses), policy.ec_ndata,"},{"line_number":718,"context_line":"                     bucket.num_responses,"},{"line_number":719,"context_line":"                     \u0027durable\u0027 if bucket.durable else \u0027non-durable\u0027,"},{"line_number":720,"context_line":"                     full_path, fi_to_rebuild, bucket.etag,"},{"line_number":721,"context_line":"                     timestamp.internal))"},{"line_number":722,"context_line":""},{"line_number":723,"context_line":"        if error_responses:"},{"line_number":724,"context_line":"            durable \u003d buckets[local_timestamp].durable"}],"source_content_type":"text/x-python","patch_set":2,"id":"eb4e8010_81cb9e13","line":721,"range":{"start_line":720,"start_character":47,"end_line":721,"end_character":39},"updated":"2026-03-13 14:31:08.000000000","message":"this message seems wrong or at least confusing now that we understand the bug: the reconstructor isn\u0027t necessarily going to reconstruct using bucket.timestamp so it\u0027s a mis-steer to say \"Unable to get enough responses to reconstruct with \u003cbucket.etag\u003e and \u003cbucket.timestamp\u003e\". The message *would* be appropriate if the reconstructor were to rebuild using the bucket etag and timestamp.","commit_id":"2c633b03f7136b9c696eb844a2296608e611d0fb"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"0573ad958a408887e6c52bae95449fa0c6bf154a","unresolved":false,"context_lines":[{"line_number":717,"context_line":"                    (len(bucket.useful_responses), policy.ec_ndata,"},{"line_number":718,"context_line":"                     bucket.num_responses,"},{"line_number":719,"context_line":"                     \u0027durable\u0027 if bucket.durable else \u0027non-durable\u0027,"},{"line_number":720,"context_line":"                     full_path, fi_to_rebuild, bucket.etag,"},{"line_number":721,"context_line":"                     timestamp.internal))"},{"line_number":722,"context_line":""},{"line_number":723,"context_line":"        if error_responses:"},{"line_number":724,"context_line":"            durable \u003d buckets[local_timestamp].durable"}],"source_content_type":"text/x-python","patch_set":2,"id":"56aac218_36954df9","line":721,"range":{"start_line":720,"start_character":47,"end_line":721,"end_character":39},"in_reply_to":"33372844_ee58bd63","updated":"2026-03-18 12:36:21.000000000","message":"Done","commit_id":"2c633b03f7136b9c696eb844a2296608e611d0fb"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"59406d9d5d4bef6e5e9949ee2ab0078c3a16136d","unresolved":true,"context_lines":[{"line_number":717,"context_line":"                    (len(bucket.useful_responses), policy.ec_ndata,"},{"line_number":718,"context_line":"                     bucket.num_responses,"},{"line_number":719,"context_line":"                     \u0027durable\u0027 if bucket.durable else \u0027non-durable\u0027,"},{"line_number":720,"context_line":"                     full_path, fi_to_rebuild, bucket.etag,"},{"line_number":721,"context_line":"                     timestamp.internal))"},{"line_number":722,"context_line":""},{"line_number":723,"context_line":"        if error_responses:"},{"line_number":724,"context_line":"            durable \u003d buckets[local_timestamp].durable"}],"source_content_type":"text/x-python","patch_set":2,"id":"33372844_ee58bd63","line":721,"range":{"start_line":720,"start_character":47,"end_line":721,"end_character":39},"in_reply_to":"eb4e8010_81cb9e13","updated":"2026-03-15 20:06:40.000000000","message":"after I fixed your above comment within the same for loop, this message is correct now. And I think it\u0027s also correct with the actual fix patch which I will upload later.","commit_id":"2c633b03f7136b9c696eb844a2296608e611d0fb"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"0573ad958a408887e6c52bae95449fa0c6bf154a","unresolved":true,"context_lines":[{"line_number":677,"context_line":"                    \u0027(bucket.timestamp\u003d%s) but local timestamp is %s, \u0027"},{"line_number":678,"context_line":"                    \u0027received etag %s but local etag is %s \u0027"},{"line_number":679,"context_line":"                    \u0027for %s frag#%s\u0027 %"},{"line_number":680,"context_line":"                    (useful_bucket.timestamp.internal"},{"line_number":681,"context_line":"                     if useful_bucket.timestamp else None,"},{"line_number":682,"context_line":"                     useful_bucket.timestamp.internal"},{"line_number":683,"context_line":"                     if useful_bucket.timestamp else None,"},{"line_number":684,"context_line":"                     local_timestamp.internal,"},{"line_number":685,"context_line":"                     useful_bucket.etag,"},{"line_number":686,"context_line":"                     local_etag,"}],"source_content_type":"text/x-python","patch_set":4,"id":"d695bb11_91420667","line":683,"range":{"start_line":680,"start_character":21,"end_line":683,"end_character":58},"updated":"2026-03-18 12:36:21.000000000","message":"why log the same timestamp twice?\n\nAlso, can bucket.timestamp ever be None? it seems like buckets should always have timestamp set before we use them?","commit_id":"485e9f98a7d38d2cd9792fa748d2798416b483ad"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"9ccdf6934026cbceb6a0552fc87e62960c2564a6","unresolved":false,"context_lines":[{"line_number":677,"context_line":"                    \u0027(bucket.timestamp\u003d%s) but local timestamp is %s, \u0027"},{"line_number":678,"context_line":"                    \u0027received etag %s but local etag is %s \u0027"},{"line_number":679,"context_line":"                    \u0027for %s frag#%s\u0027 %"},{"line_number":680,"context_line":"                    (useful_bucket.timestamp.internal"},{"line_number":681,"context_line":"                     if useful_bucket.timestamp else None,"},{"line_number":682,"context_line":"                     useful_bucket.timestamp.internal"},{"line_number":683,"context_line":"                     if useful_bucket.timestamp else None,"},{"line_number":684,"context_line":"                     local_timestamp.internal,"},{"line_number":685,"context_line":"                     useful_bucket.etag,"},{"line_number":686,"context_line":"                     local_etag,"}],"source_content_type":"text/x-python","patch_set":4,"id":"5e5e9516_d891f28b","line":683,"range":{"start_line":680,"start_character":21,"end_line":683,"end_character":58},"in_reply_to":"d695bb11_91420667","updated":"2026-03-25 03:39:45.000000000","message":"Acknowledged","commit_id":"485e9f98a7d38d2cd9792fa748d2798416b483ad"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"0573ad958a408887e6c52bae95449fa0c6bf154a","unresolved":true,"context_lines":[{"line_number":684,"context_line":"                     local_timestamp.internal,"},{"line_number":685,"context_line":"                     useful_bucket.etag,"},{"line_number":686,"context_line":"                     local_etag,"},{"line_number":687,"context_line":"                     full_path, fi_to_rebuild))"},{"line_number":688,"context_line":"            frag_indexes \u003d list(useful_bucket.useful_responses.keys())"},{"line_number":689,"context_line":"            self.logger.debug(\u0027Reconstruct frag #%s with frag indexes %s\u0027"},{"line_number":690,"context_line":"                              % (fi_to_rebuild, frag_indexes))"}],"source_content_type":"text/x-python","patch_set":4,"id":"60cca357_6e23f527","line":687,"updated":"2026-03-18 12:36:21.000000000","message":"the most obvious and expedient fix is to write ``else:`` here","commit_id":"485e9f98a7d38d2cd9792fa748d2798416b483ad"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"b4fe22383f3e0558ded6aeb82c0a5a9934dfa421","unresolved":true,"context_lines":[{"line_number":684,"context_line":"                     local_timestamp.internal,"},{"line_number":685,"context_line":"                     useful_bucket.etag,"},{"line_number":686,"context_line":"                     local_etag,"},{"line_number":687,"context_line":"                     full_path, fi_to_rebuild))"},{"line_number":688,"context_line":"            frag_indexes \u003d list(useful_bucket.useful_responses.keys())"},{"line_number":689,"context_line":"            self.logger.debug(\u0027Reconstruct frag #%s with frag indexes %s\u0027"},{"line_number":690,"context_line":"                              % (fi_to_rebuild, frag_indexes))"}],"source_content_type":"text/x-python","patch_set":4,"id":"b248b461_dd298f82","line":687,"in_reply_to":"60cca357_6e23f527","updated":"2026-04-01 23:37:35.000000000","message":"actually we should just not enter this block unless\n\n```\n        if (useful_bucket\n                and useful_bucket.timestamp \u003d\u003d local_timestamp\n                and useful_bucket.etag \u003d\u003d local_etag):\n```\n\nThen we get the sorted(buckets) logging and DiskfileError for free!\n\n983143: not MORE logging; BETTER logging | https://review.opendev.org/c/openstack/swift/+/983143","commit_id":"485e9f98a7d38d2cd9792fa748d2798416b483ad"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"4e6920d9d371d46628d4a75aec25497e66af41b6","unresolved":true,"context_lines":[{"line_number":96,"context_line":"    \"\"\""},{"line_number":97,"context_line":"    Encapsulates fragment GET response data related to a single timestamp."},{"line_number":98,"context_line":"    \"\"\""},{"line_number":99,"context_line":""},{"line_number":100,"context_line":"    def __init__(self):"},{"line_number":101,"context_line":"        # count of all responses associated with this Bucket"},{"line_number":102,"context_line":"        self.num_responses \u003d 0"}],"source_content_type":"text/x-python","patch_set":6,"id":"d61bcd88_47d3eda8","line":99,"updated":"2026-03-25 11:40:12.000000000","message":"nit: there\u0027s a couple of places in this patch where three\u0027s a spurious empty line added ... is this an IDE auto-formatting thing? Is there a style preference for a line between the doc-string and the first method?","commit_id":"d0b321a8e6a197531abca9f442fd9c5538f10d62"}],"test/unit/obj/common.py":[{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"0573ad958a408887e6c52bae95449fa0c6bf154a","unresolved":true,"context_lines":[{"line_number":23,"context_line":""},{"line_number":24,"context_line":""},{"line_number":25,"context_line":"def write_diskfile(df, timestamp, data\u003db\u0027test data\u0027, frag_index\u003dNone,"},{"line_number":26,"context_line":"                   commit\u003dTrue, legacy_durable\u003dFalse, extra_metadata\u003dNone):"},{"line_number":27,"context_line":"    # Helper method to write some data and metadata to a diskfile."},{"line_number":28,"context_line":"    # Optionally do not commit the diskfile, or commit but using a legacy"},{"line_number":29,"context_line":"    # durable file"}],"source_content_type":"text/x-python","patch_set":4,"id":"0d4abd45_4383188a","line":26,"updated":"2026-03-18 12:36:21.000000000","message":"alternatively, we could add an arg ``ec_etag\u003dfake-etag`` then line 39 becomes:\n```\nmetadata[\u0027X-Object-Sysmeta-Ec-Etag\u0027] \u003d ec_etag\n```\n\nwhich possibly makes it more explicit/easier to understand the plumbing??","commit_id":"485e9f98a7d38d2cd9792fa748d2798416b483ad"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"9ccdf6934026cbceb6a0552fc87e62960c2564a6","unresolved":false,"context_lines":[{"line_number":23,"context_line":""},{"line_number":24,"context_line":""},{"line_number":25,"context_line":"def write_diskfile(df, timestamp, data\u003db\u0027test data\u0027, frag_index\u003dNone,"},{"line_number":26,"context_line":"                   commit\u003dTrue, legacy_durable\u003dFalse, extra_metadata\u003dNone):"},{"line_number":27,"context_line":"    # Helper method to write some data and metadata to a diskfile."},{"line_number":28,"context_line":"    # Optionally do not commit the diskfile, or commit but using a legacy"},{"line_number":29,"context_line":"    # durable file"}],"source_content_type":"text/x-python","patch_set":4,"id":"de16591b_65e554b9","line":26,"in_reply_to":"0d4abd45_4383188a","updated":"2026-03-25 03:39:45.000000000","message":"Acknowledged","commit_id":"485e9f98a7d38d2cd9792fa748d2798416b483ad"}],"test/unit/obj/test_reconstructor.py":[{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"0573ad958a408887e6c52bae95449fa0c6bf154a","unresolved":true,"context_lines":[{"line_number":5144,"context_line":"        self.obj_path \u003d b\u0027/a/c/\u0027 + self.obj_name"},{"line_number":5145,"context_line":"        self.obj_timestamp \u003d self.ts()"},{"line_number":5146,"context_line":""},{"line_number":5147,"context_line":"    def _create_fragment(self, frag_index, body\u003db\u0027test data\u0027, ec_etag\u003dNone):"},{"line_number":5148,"context_line":"        utils.mkdirs(os.path.join(self.devices, \u0027sda1\u0027))"},{"line_number":5149,"context_line":"        df_mgr \u003d self.reconstructor._df_router[self.policy]"},{"line_number":5150,"context_line":"        obj_name \u003d self.obj_name.decode(\u0027utf8\u0027)"}],"source_content_type":"text/x-python","patch_set":4,"id":"69d3b97f_c3e224fa","line":5147,"range":{"start_line":5147,"start_character":62,"end_line":5147,"end_character":74},"updated":"2026-03-18 12:36:21.000000000","message":"IIUC a fragment should always have ``X-Object-Sysmeta-Ec-Etag`` and tests were previously just lazy in not adding it, in which case we should at least now have a default value and always add the metadata. Otherwise the tests suggest a behaviour that is never real-world.\n\n```\ndiff --git a/test/unit/obj/test_reconstructor.py b/test/unit/obj/test_reconstructor.py\nindex e3e4be1f3..dc9674b87 100644\n--- a/test/unit/obj/test_reconstructor.py\n+++ b/test/unit/obj/test_reconstructor.py\n@@ -5144,15 +5144,14 @@ class TestReconstructFragmentArchive(BaseTestObjectReconstructor):\n         self.obj_path \u003d b\u0027/a/c/\u0027 + self.obj_name\n         self.obj_timestamp \u003d self.ts()\n \n-    def _create_fragment(self, frag_index, body\u003db\u0027test data\u0027, ec_etag\u003dNone):\n+    def _create_fragment(self, frag_index, body\u003db\u0027test data\u0027,\n+                         ec_etag\u003d\u0027fake-etag\u0027):\n         utils.mkdirs(os.path.join(self.devices, \u0027sda1\u0027))\n         df_mgr \u003d self.reconstructor._df_router[self.policy]\n         obj_name \u003d self.obj_name.decode(\u0027utf8\u0027)\n         self.df \u003d df_mgr.get_diskfile(\u0027sda1\u0027, 9, \u0027a\u0027, \u0027c\u0027, obj_name,\n                                       policy\u003dself.policy)\n-        extra_metadata \u003d None\n-        if ec_etag is not None:\n-            extra_metadata \u003d {\u0027X-Object-Sysmeta-Ec-Etag\u0027: ec_etag}\n+        extra_metadata \u003d {\u0027X-Object-Sysmeta-Ec-Etag\u0027: ec_etag}\n         write_diskfile(self.df, self.obj_timestamp, data\u003dbody,\n                        frag_index\u003dfrag_index, extra_metadata\u003dextra_metadata)\n         self.df.open()\n\n```","commit_id":"485e9f98a7d38d2cd9792fa748d2798416b483ad"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"9ccdf6934026cbceb6a0552fc87e62960c2564a6","unresolved":false,"context_lines":[{"line_number":5144,"context_line":"        self.obj_path \u003d b\u0027/a/c/\u0027 + self.obj_name"},{"line_number":5145,"context_line":"        self.obj_timestamp \u003d self.ts()"},{"line_number":5146,"context_line":""},{"line_number":5147,"context_line":"    def _create_fragment(self, frag_index, body\u003db\u0027test data\u0027, ec_etag\u003dNone):"},{"line_number":5148,"context_line":"        utils.mkdirs(os.path.join(self.devices, \u0027sda1\u0027))"},{"line_number":5149,"context_line":"        df_mgr \u003d self.reconstructor._df_router[self.policy]"},{"line_number":5150,"context_line":"        obj_name \u003d self.obj_name.decode(\u0027utf8\u0027)"}],"source_content_type":"text/x-python","patch_set":4,"id":"e8860395_301bb0e6","line":5147,"range":{"start_line":5147,"start_character":62,"end_line":5147,"end_character":74},"in_reply_to":"69d3b97f_c3e224fa","updated":"2026-03-25 03:39:45.000000000","message":"Done","commit_id":"485e9f98a7d38d2cd9792fa748d2798416b483ad"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"0573ad958a408887e6c52bae95449fa0c6bf154a","unresolved":true,"context_lines":[{"line_number":5169,"context_line":"        node[\u0027backend_index\u0027] \u003d self.policy.get_backend_index(node[\u0027index\u0027])"},{"line_number":5170,"context_line":""},{"line_number":5171,"context_line":"        test_data \u003d (b\u0027rebuild\u0027 * self.policy.ec_segment_size)[:-777]"},{"line_number":5172,"context_line":"        etag \u003d md5(test_data, usedforsecurity\u003dFalse).hexdigest()"},{"line_number":5173,"context_line":"        ec_archive_bodies \u003d encode_frag_archive_bodies(self.policy, test_data)"},{"line_number":5174,"context_line":"        broken_body \u003d ec_archive_bodies.pop(1)"},{"line_number":5175,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"ef16ec64_cbd22b99","line":5172,"updated":"2026-03-18 12:36:21.000000000","message":"nit: I\u0027d be happy for this to be renamed ``ec_etag`` for consistency with the arg passed into the helper functions","commit_id":"485e9f98a7d38d2cd9792fa748d2798416b483ad"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"0573ad958a408887e6c52bae95449fa0c6bf154a","unresolved":true,"context_lines":[{"line_number":5234,"context_line":"        self.obj_timestamp \u003d Timestamp(self.obj_timestamp, offset\u003d1)"},{"line_number":5235,"context_line":"        self._do_test_reconstruct_fa_no_errors()"},{"line_number":5236,"context_line":""},{"line_number":5237,"context_line":"    def test_reconstruct_fa_warns_on_mismatched_timestamp(self):"},{"line_number":5238,"context_line":"        # verify that when remote fragments have a different timestamp than"},{"line_number":5239,"context_line":"        # the local fragment, reconstruction still succeeds (old behavior)"},{"line_number":5240,"context_line":"        # but a warning is emitted about the mismatch"}],"source_content_type":"text/x-python","patch_set":4,"id":"d1c9da2c_1dcd5169","line":5237,"updated":"2026-03-18 12:36:21.000000000","message":"ok, when I \u0027fixed\u0027 the issue these two new tests are the only ones that fail, which confirms that the other tests have been cleaned up to correctly setup the fragment metadata.\n\n```\ndiff --git a/swift/obj/reconstructor.py b/swift/obj/reconstructor.py\nindex 0467440f0..8f546868b 100644\n--- a/swift/obj/reconstructor.py\n+++ b/swift/obj/reconstructor.py\n@@ -685,14 +685,15 @@ class ObjectReconstructor(Daemon):\n                      useful_bucket.etag,\n                      local_etag,\n                      full_path, fi_to_rebuild))\n-            frag_indexes \u003d list(useful_bucket.useful_responses.keys())\n-            self.logger.debug(\u0027Reconstruct frag #%s with frag indexes %s\u0027\n-                              % (fi_to_rebuild, frag_indexes))\n-            responses \u003d list(useful_bucket.useful_responses.values())\n-            rebuilt_fragment_iter \u003d self.make_rebuilt_fragment_iter(\n-                responses[:policy.ec_ndata], path, policy, fi_to_rebuild)\n-            return RebuildingECDiskFileStream(datafile_metadata, fi_to_rebuild,\n-                                              rebuilt_fragment_iter)\n+            else:\n+                frag_indexes \u003d list(useful_bucket.useful_responses.keys())\n+                self.logger.debug(\u0027Reconstruct frag #%s with frag indexes %s\u0027\n+                                  % (fi_to_rebuild, frag_indexes))\n+                responses \u003d list(useful_bucket.useful_responses.values())\n+                rebuilt_fragment_iter \u003d self.make_rebuilt_fragment_iter(\n+                    responses[:policy.ec_ndata], path, policy, fi_to_rebuild)\n+                return RebuildingECDiskFileStream(datafile_metadata, fi_to_rebuild,\n+                                                  rebuilt_fragment_iter)\n \n         for timestamp, bucket in sorted(buckets.items()):\n             self.logger.error(\n```\n\n```\nFAILED test_reconstructor.py::TestReconstructFragmentArchive::test_reconstruct_fa_warns_on_mismatched_etag - swift.common.exceptions.DiskFileError: Unable to reconstruct EC archive\nFAILED test_reconstructor.py::TestReconstructFragmentArchive::test_reconstruct_fa_warns_on_mismatched_timestamp - swift.common.exceptions.DiskFileError: Unable to reconstruct EC archive\n```","commit_id":"485e9f98a7d38d2cd9792fa748d2798416b483ad"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"9ccdf6934026cbceb6a0552fc87e62960c2564a6","unresolved":false,"context_lines":[{"line_number":5234,"context_line":"        self.obj_timestamp \u003d Timestamp(self.obj_timestamp, offset\u003d1)"},{"line_number":5235,"context_line":"        self._do_test_reconstruct_fa_no_errors()"},{"line_number":5236,"context_line":""},{"line_number":5237,"context_line":"    def test_reconstruct_fa_warns_on_mismatched_timestamp(self):"},{"line_number":5238,"context_line":"        # verify that when remote fragments have a different timestamp than"},{"line_number":5239,"context_line":"        # the local fragment, reconstruction still succeeds (old behavior)"},{"line_number":5240,"context_line":"        # but a warning is emitted about the mismatch"}],"source_content_type":"text/x-python","patch_set":4,"id":"5cda7e45_d257229f","line":5237,"in_reply_to":"d1c9da2c_1dcd5169","updated":"2026-03-25 03:39:45.000000000","message":"Acknowledged","commit_id":"485e9f98a7d38d2cd9792fa748d2798416b483ad"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"0573ad958a408887e6c52bae95449fa0c6bf154a","unresolved":true,"context_lines":[{"line_number":5280,"context_line":"                         wrong_timestamp.internal,"},{"line_number":5281,"context_line":"                         self.obj_timestamp.internal,"},{"line_number":5282,"context_line":"                         etag, etag),"},{"line_number":5283,"context_line":"                      warning_lines[0])"},{"line_number":5284,"context_line":""},{"line_number":5285,"context_line":"    def test_reconstruct_fa_warns_on_mismatched_etag(self):"},{"line_number":5286,"context_line":"        # verify that when remote fragments have a matching timestamp but"}],"source_content_type":"text/x-python","patch_set":4,"id":"ce141e60_db4ce6af","line":5283,"updated":"2026-03-18 12:36:21.000000000","message":"```\nobject-reconstructor WARNING: Received enough fragments at timestamp 1773832743.00000 (bucket.timestamp\u003d1773832743.00000) but local timestamp is 1773832742.00000, received etag f0510d5479985e9523dc1688b0bc7d63 but local etag is f0510d5479985e9523dc1688b0bc7d63 for 10.0.0.1:1001/sdb/0/a/c/o policy#0 frag#1\n```\n\nnit: the \u0027but local etag\u0027 doesn\u0027t need a \u0027but\u0027, because the etags are in fact the same, whereas \u0027but\u0027 implies a difference","commit_id":"485e9f98a7d38d2cd9792fa748d2798416b483ad"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"0573ad958a408887e6c52bae95449fa0c6bf154a","unresolved":true,"context_lines":[{"line_number":5329,"context_line":"                         self.obj_timestamp.internal,"},{"line_number":5330,"context_line":"                         self.obj_timestamp.internal,"},{"line_number":5331,"context_line":"                         wrong_etag, etag),"},{"line_number":5332,"context_line":"                      warning_lines[0])"},{"line_number":5333,"context_line":""},{"line_number":5334,"context_line":"    def test_reconstruct_fa_errors_works(self):"},{"line_number":5335,"context_line":"        job \u003d {"}],"source_content_type":"text/x-python","patch_set":4,"id":"4aa4aa6a_928ac4dc","line":5332,"updated":"2026-03-18 12:36:21.000000000","message":"```\nReceived enough fragments at timestamp 1773833268.00000 (bucket.timestamp\u003d1773833268.00000) but local timestamp is 1773833268.00000, received etag eca04c02876e71327ae3710a178067c6 but local etag is f0510d5479985e9523dc1688b0bc7d63 for 10.0.0.1:1001/sdb/0/a/c/o policy#0 frag#1\n```\n\nnit: timestamps are all the same; using \u0027but local timestamp\u0027 implies they differe","commit_id":"485e9f98a7d38d2cd9792fa748d2798416b483ad"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"0573ad958a408887e6c52bae95449fa0c6bf154a","unresolved":true,"context_lines":[{"line_number":5656,"context_line":""},{"line_number":5657,"context_line":"        # bad response"},{"line_number":5658,"context_line":"        broken_body \u003d ec_archive_bodies.pop(1)"},{"line_number":5659,"context_line":"        bad_timestamp \u003d self.ts()"},{"line_number":5660,"context_line":"        bad_headers \u003d get_header_frag_index(self, broken_body)"},{"line_number":5661,"context_line":"        bad_headers.update({"},{"line_number":5662,"context_line":"            \u0027X-Object-Sysmeta-Ec-Etag\u0027: \u0027some garbage\u0027,"}],"source_content_type":"text/x-python","patch_set":4,"id":"3c9b05f5_6015e4ea","line":5659,"updated":"2026-03-18 12:36:21.000000000","message":"this timestamp is newer than self.obj_timestamp so the test has become more like the next one ``test_reconstruct_fa_with_mixed_new_etag``","commit_id":"485e9f98a7d38d2cd9792fa748d2798416b483ad"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"9ccdf6934026cbceb6a0552fc87e62960c2564a6","unresolved":false,"context_lines":[{"line_number":5656,"context_line":""},{"line_number":5657,"context_line":"        # bad response"},{"line_number":5658,"context_line":"        broken_body \u003d ec_archive_bodies.pop(1)"},{"line_number":5659,"context_line":"        bad_timestamp \u003d self.ts()"},{"line_number":5660,"context_line":"        bad_headers \u003d get_header_frag_index(self, broken_body)"},{"line_number":5661,"context_line":"        bad_headers.update({"},{"line_number":5662,"context_line":"            \u0027X-Object-Sysmeta-Ec-Etag\u0027: \u0027some garbage\u0027,"}],"source_content_type":"text/x-python","patch_set":4,"id":"750c083a_54731c8f","line":5659,"in_reply_to":"3c9b05f5_6015e4ea","updated":"2026-03-25 03:39:45.000000000","message":"Done","commit_id":"485e9f98a7d38d2cd9792fa748d2798416b483ad"}],"test/unit/obj/test_ssync.py":[{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"0573ad958a408887e6c52bae95449fa0c6bf154a","unresolved":true,"context_lines":[{"line_number":870,"context_line":"    def getheaders(self):"},{"line_number":871,"context_line":"        return {"},{"line_number":872,"context_line":"            \u0027X-Object-Sysmeta-Ec-Frag-Index\u0027: str(self.frag_index),"},{"line_number":873,"context_line":"            \u0027X-Object-Sysmeta-Ec-Etag\u0027: \u0027fake-etag\u0027,"},{"line_number":874,"context_line":"            \u0027X-Backend-Timestamp\u0027: self.conf[\u0027timestamp\u0027].internal"},{"line_number":875,"context_line":"        }"},{"line_number":876,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"441b7ac6_58f916e9","line":873,"range":{"start_line":873,"start_character":40,"end_line":873,"end_character":51},"updated":"2026-03-18 12:36:21.000000000","message":"presumably this has to be the same as on line 143.\n\nIdeally we\u0027d be using the actual hash of the obj_data. Failing that I think a module constant might be better:\n\n```\ndiff --git a/test/unit/obj/test_ssync.py b/test/unit/obj/test_ssync.py\nindex 9b6a092fb..ccf8c8ccc 100644\n--- a/test/unit/obj/test_ssync.py\n+++ b/test/unit/obj/test_ssync.py\n@@ -38,6 +38,9 @@ from test.unit import patch_policies, encode_frag_archive_bodies, \\\n     skip_if_no_xattrs, quiet_eventlet_exceptions, make_timestamp_iter\n \n \n+FAKE_ETAG \u003d \u0027fake-etag\u0027\n+\n+\n class TestBaseSsync(BaseTest):\n     \"\"\"\n     Provides a framework to test end to end interactions between sender and\n@@ -140,7 +143,7 @@ class TestBaseSsync(BaseTest):\n                                                 frag_index\u003dfrag_index)\n             if policy.policy_type \u003d\u003d EC_POLICY:\n                 metadata[\u0027X-Object-Sysmeta-Ec-Frag-Index\u0027] \u003d str(frag_index)\n-                metadata[\u0027X-Object-Sysmeta-Ec-Etag\u0027] \u003d \u0027fake-etag\u0027\n+                metadata[\u0027X-Object-Sysmeta-Ec-Etag\u0027] \u003d FAKE_ETAG\n             df \u003d self._make_diskfile(\n                 device\u003dself.device, partition\u003dself.partition, account\u003d\u0027a\u0027,\n                 container\u003d\u0027c\u0027, obj\u003dobj_name, body\u003dobject_data,\n@@ -870,7 +873,7 @@ class FakeResponse(object):\n     def getheaders(self):\n         return {\n             \u0027X-Object-Sysmeta-Ec-Frag-Index\u0027: str(self.frag_index),\n-            \u0027X-Object-Sysmeta-Ec-Etag\u0027: \u0027fake-etag\u0027,\n+            \u0027X-Object-Sysmeta-Ec-Etag\u0027: FAKE_ETAG,\n             \u0027X-Backend-Timestamp\u0027: self.conf[\u0027timestamp\u0027].internal\n         }\n \n\n```\n\nor just use MD5_OF_EMPTY_STRING","commit_id":"485e9f98a7d38d2cd9792fa748d2798416b483ad"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"9ccdf6934026cbceb6a0552fc87e62960c2564a6","unresolved":false,"context_lines":[{"line_number":870,"context_line":"    def getheaders(self):"},{"line_number":871,"context_line":"        return {"},{"line_number":872,"context_line":"            \u0027X-Object-Sysmeta-Ec-Frag-Index\u0027: str(self.frag_index),"},{"line_number":873,"context_line":"            \u0027X-Object-Sysmeta-Ec-Etag\u0027: \u0027fake-etag\u0027,"},{"line_number":874,"context_line":"            \u0027X-Backend-Timestamp\u0027: self.conf[\u0027timestamp\u0027].internal"},{"line_number":875,"context_line":"        }"},{"line_number":876,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"2a43a895_20058dd7","line":873,"range":{"start_line":873,"start_character":40,"end_line":873,"end_character":51},"in_reply_to":"441b7ac6_58f916e9","updated":"2026-03-25 03:39:45.000000000","message":"Done","commit_id":"485e9f98a7d38d2cd9792fa748d2798416b483ad"}]}
