)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":4523,"name":"Eric Harney","email":"eharney@redhat.com","username":"eharney"},"change_message_id":"1b6565b2a5230bf164764ce95a3fcddc2ebc6020","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"213e0792_d3a42b65","updated":"2026-08-06 12:57:35.000000000","message":"Please file a bug for this","commit_id":"a6f20e379b9e9726fb88c562de476daf7e28bfa3"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"c6859503671372ba2c126ef2d4a5bed4ca80155b","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"fdd6c7ff_6e4d79eb","updated":"2026-08-06 03:38:47.000000000","message":"recheck","commit_id":"a6f20e379b9e9726fb88c562de476daf7e28bfa3"},{"author":{"_account_id":4523,"name":"Eric Harney","email":"eharney@redhat.com","username":"eharney"},"change_message_id":"1e30bd0f388473398e6de491021371988a6290b0","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":5,"id":"feadfaa8_f5270eaf","updated":"2026-08-31 16:51:15.000000000","message":"Better to not assign directly in unit tests, use mock_object instead","commit_id":"fd0521cf7def0ce1f140365222c663b564d22aea"},{"author":{"_account_id":34860,"name":"Amit Uniyal","email":"auniyal@redhat.com","username":"auniyal"},"change_message_id":"05502e45b2d3e70a1d2aeb10c2c57ebb7e92b630","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":5,"id":"6b2e2026_fb914161","updated":"2026-08-26 10:31:40.000000000","message":"I was able to reproduce the bug and verify it with this patch fix, using Posfix backup driver .\n\nso before the fix, the restored volume keeps stale base data, as per first backup:\n```\n$ sudo hexdump -C  -n 32 /dev/rbd0\n00000000  aa aa aa aa aa aa aa aa  aa aa aa aa aa aa aa aa  |................|\n*\n00000020\n```\n\nafter the fix, incremental restored with zeroed properly:\n```\n$ sudo hexdump -C -n 32 /dev/rbd0\n00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|\n*\n00000020\n```\n\nI also checked the fix doesn\u0027t skip writes it shouldn\u0027t, like incremental changing to a new value (0xAA --\u003e 0xBB) restores as 0xBB.\nand a longer chain restore correctly too.\n\nthanks for patch, the change looks good to me, my only suggestion is we could add more test coverage.","commit_id":"fd0521cf7def0ce1f140365222c663b564d22aea"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"c5d4406b3065f27f8ce559176f25ea54178775ea","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":6,"id":"dc7ecdd1_6baf385d","updated":"2026-09-10 13:51:06.000000000","message":"Good call, thanks -- dropped both helper classes.\n\ntest_restore_chain_zeroes_data_to_zero_region now uses\ntempfile.NamedTemporaryFile() for the restore target as you suggested, and\nreads the bytes back off the file to assert the result. The object reader is\njust a mock.MagicMock() configured inline (MagicMock already implements the\ncontext-manager protocol, so read() via __enter__ needs no class of its\nown). Net effect is 40 fewer lines and no new module-level fixtures.\n\nI kept the test in test_chunkeddriver.py rather than moving it next to the\nswift/posix ones, since the bug is in the shared chunked base class rather\nthan any single driver -- but the volume_file pattern is now the same as\nthe ones you linked. Happy to move it if you\u0027d rather it sat with those.\n\nI re-checked that it still earns its place: with the is_base gate reverted,\nboth test_restore_chain_treats_only_base_as_new and\ntest_restore_chain_zeroes_data_to_zero_region fail; with the fix in, all 29\ntests in the module pass. So the byte-level assertion you asked for on PS5\ndoes catch a regression the flag assertions alone would miss.","commit_id":"0f6b0d82ac2a33c6ea5aa9bcfa99b2747a22e129"},{"author":{"_account_id":5314,"name":"Brian Rosmaita","email":"rosmaita.fossdev@gmail.com","username":"brian-rosmaita"},"change_message_id":"3ed3dc82692e3d192be28571a920841347818798","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":6,"id":"93e431e2_cec29c2c","updated":"2026-09-03 12:57:33.000000000","message":"We need to get this into Hibiscus.","commit_id":"0f6b0d82ac2a33c6ea5aa9bcfa99b2747a22e129"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"ea46cfeb572b372d4c73e85ad818bf8b59382ddb","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":6,"id":"ee2e721b_8eaa51f0","updated":"2026-08-31 21:27:20.000000000","message":"run Everpure CI","commit_id":"0f6b0d82ac2a33c6ea5aa9bcfa99b2747a22e129"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"49135f6c1490fabf6d7f35512c758f3fa2094b60","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":8,"id":"f5ae1d67_b3db54bb","updated":"2026-09-11 02:26:41.000000000","message":"Thanks @rajatdhasmana@gmail.com -- all five taken, they were good catches.\n\n``is_base`` is now:\n\n    ``is_base \u003d not backup1.parent_id``\n\nAs a bonus this version doesn\u0027t depend on backup_list being in reverse order at all, which is the thing Eric flagged just above.\n\n@eharney@redhat.com -- on the refactor you mentioned for the reverse-ordered backup_list:\nwould you be able to base it on top of this patch rather than the other way\nround?\n\nTwo reasons. This is a data-corruption fix that Brian has asked to land in\nHibiscus, so it\u0027s the piece with a deadline. And it\u0027s now a much smaller\ntarget for you: per Rajat\u0027s suggestion the condition is\n\n    is_base \u003d not backup1.parent_id\n\nwhich no longer depends on the ordering of backup_list, so your refactor can\nreorder that loop freely without having to preserve an index-based check.\n\nThe two regression tests on this patch assert the per-member volume_is_new\nsequence and the actual restored bytes, so they should also give your\nrefactor a reasonable safety net -- both fail if the base/incremental\ndistinction is lost.","commit_id":"c900a0095e79bf04d3b6125cfaffbf63da663341"},{"author":{"_account_id":5314,"name":"Brian Rosmaita","email":"rosmaita.fossdev@gmail.com","username":"brian-rosmaita"},"change_message_id":"5297cae48e11eea13fec46b9846c27392f44ff7a","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":8,"id":"abadfb0f_df5f03f3","updated":"2026-09-11 16:06:51.000000000","message":"Thanks for adopting Rajat\u0027s suggestions, they really did improve the readability of the code.  Good suggestions from Eric and Amit also regarding the tests.  Thanks to Simon for the detailed bug report and analysis.","commit_id":"c900a0095e79bf04d3b6125cfaffbf63da663341"},{"author":{"_account_id":34860,"name":"Amit Uniyal","email":"auniyal@redhat.com","username":"auniyal"},"change_message_id":"5f369fc0b3da536d2f67e55fa8f5081e69178253","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":8,"id":"5ed922ea_40ad53a7","updated":"2026-09-11 13:00:49.000000000","message":"also w.r.t RC-1 I think we can land this now.","commit_id":"c900a0095e79bf04d3b6125cfaffbf63da663341"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"eb3bb9f19e9fed5f2c05d0a85db832504bd74f5a","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":8,"id":"e680c953_802a71d4","updated":"2026-09-11 05:27:20.000000000","message":"run Everpure CI","commit_id":"c900a0095e79bf04d3b6125cfaffbf63da663341"}],"cinder/backup/chunkeddriver.py":[{"author":{"_account_id":27615,"name":"Rajat Dhasmana","email":"rajatdhasmana@gmail.com","username":"whoami-rajat"},"change_message_id":"13298afe3885c5cf2f8cde9b3b0fa916f72af519","unresolved":true,"context_lines":[{"line_number":802,"context_line":"            # too, or a range that changed from data to zero would keep the"},{"line_number":803,"context_line":"            # base\u0027s stale data.  So only the base (the last entry, applied"},{"line_number":804,"context_line":"            # first) is treated as new."},{"line_number":805,"context_line":"            is_base \u003d index \u003d\u003d len(backup_list) - 1"},{"line_number":806,"context_line":"            index \u003d index - 1"},{"line_number":807,"context_line":"            metadata \u003d self._read_metadata(backup1)"},{"line_number":808,"context_line":"            restore_func(backup1, volume_id, metadata, volume_file,"}],"source_content_type":"text/x-python","patch_set":7,"id":"13c48cbb_1e20b3dc","line":805,"range":{"start_line":805,"start_character":0,"end_line":805,"end_character":51},"updated":"2026-09-10 22:24:42.000000000","message":"This condition check is non-intuitive and looking from readability and maintenance perspective, i think there are better ways to check this\n\n    is_base \u003d not current_backup.parent_id\n    \nthis way we know we are fetching the base backup which doesn\u0027t have a parent ID","commit_id":"884d8ff7f7c2179bd4554e6d57bb4b69ed4a5b8a"},{"author":{"_account_id":4523,"name":"Eric Harney","email":"eharney@redhat.com","username":"eharney"},"change_message_id":"0877d6dde27ed2a4a83e0e98edbf4d29fe0af1d3","unresolved":false,"context_lines":[{"line_number":802,"context_line":"            # too, or a range that changed from data to zero would keep the"},{"line_number":803,"context_line":"            # base\u0027s stale data.  So only the base (the last entry, applied"},{"line_number":804,"context_line":"            # first) is treated as new."},{"line_number":805,"context_line":"            is_base \u003d index \u003d\u003d len(backup_list) - 1"},{"line_number":806,"context_line":"            index \u003d index - 1"},{"line_number":807,"context_line":"            metadata \u003d self._read_metadata(backup1)"},{"line_number":808,"context_line":"            restore_func(backup1, volume_id, metadata, volume_file,"}],"source_content_type":"text/x-python","patch_set":7,"id":"37c8ea92_1ea0a9e3","line":805,"updated":"2026-09-10 17:28:02.000000000","message":"This line is correct but it it\u0027s hard to see why until you realize that backup_list is in reverse order. I\u0027m going to submit a follow-up patch to refactor some of this code to work in a more straightforward way.","commit_id":"884d8ff7f7c2179bd4554e6d57bb4b69ed4a5b8a"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"49135f6c1490fabf6d7f35512c758f3fa2094b60","unresolved":false,"context_lines":[{"line_number":802,"context_line":"            # too, or a range that changed from data to zero would keep the"},{"line_number":803,"context_line":"            # base\u0027s stale data.  So only the base (the last entry, applied"},{"line_number":804,"context_line":"            # first) is treated as new."},{"line_number":805,"context_line":"            is_base \u003d index \u003d\u003d len(backup_list) - 1"},{"line_number":806,"context_line":"            index \u003d index - 1"},{"line_number":807,"context_line":"            metadata \u003d self._read_metadata(backup1)"},{"line_number":808,"context_line":"            restore_func(backup1, volume_id, metadata, volume_file,"}],"source_content_type":"text/x-python","patch_set":7,"id":"8851b9ca_9501578d","line":805,"range":{"start_line":805,"start_character":0,"end_line":805,"end_character":51},"in_reply_to":"13c48cbb_1e20b3dc","updated":"2026-09-11 02:26:41.000000000","message":"Done","commit_id":"884d8ff7f7c2179bd4554e6d57bb4b69ed4a5b8a"},{"author":{"_account_id":27615,"name":"Rajat Dhasmana","email":"rajatdhasmana@gmail.com","username":"whoami-rajat"},"change_message_id":"13298afe3885c5cf2f8cde9b3b0fa916f72af519","unresolved":false,"context_lines":[{"line_number":806,"context_line":"            index \u003d index - 1"},{"line_number":807,"context_line":"            metadata \u003d self._read_metadata(backup1)"},{"line_number":808,"context_line":"            restore_func(backup1, volume_id, metadata, volume_file,"},{"line_number":809,"context_line":"                         volume_is_new and is_base, backup)"},{"line_number":810,"context_line":""},{"line_number":811,"context_line":"            volume_meta \u003d metadata.get(\u0027volume_meta\u0027, None)"},{"line_number":812,"context_line":"            try:"}],"source_content_type":"text/x-python","patch_set":7,"id":"7cdfc89f_ecc8efc5","line":809,"range":{"start_line":809,"start_character":25,"end_line":809,"end_character":50},"updated":"2026-09-10 22:24:42.000000000","message":"it is not obvious what is happening behind the scenes so here the code that skips writing zero chunks\nhttps://opendev.org/openstack/cinder/src/commit/437878533baadcf0a1f2445e9192a1ec63f30f84/cinder/backup/chunkeddriver.py#L69-L84","commit_id":"884d8ff7f7c2179bd4554e6d57bb4b69ed4a5b8a"},{"author":{"_account_id":34860,"name":"Amit Uniyal","email":"auniyal@redhat.com","username":"auniyal"},"change_message_id":"5f369fc0b3da536d2f67e55fa8f5081e69178253","unresolved":false,"context_lines":[{"line_number":801,"context_line":"            # zero regions too, or a range that changed from data to zero"},{"line_number":802,"context_line":"            # would keep the base\u0027s stale data."},{"line_number":803,"context_line":"            is_base \u003d not backup1.parent_id"},{"line_number":804,"context_line":"            index \u003d index - 1"},{"line_number":805,"context_line":"            metadata \u003d self._read_metadata(backup1)"},{"line_number":806,"context_line":"            restore_func(backup1, volume_id, metadata, volume_file,"},{"line_number":807,"context_line":"                         volume_is_new and is_base, backup)"}],"source_content_type":"text/x-python","patch_set":8,"id":"bf055d57_72d3c54b","line":804,"updated":"2026-09-11 13:00:49.000000000","message":"this looks better.","commit_id":"c900a0095e79bf04d3b6125cfaffbf63da663341"}],"cinder/tests/unit/backup/test_chunkeddriver.py":[{"author":{"_account_id":36171,"name":"jayaanand borra","display_name":"jayaanand borra","email":"jayaanand.borra@netapp.com","username":"jayaanan","status":"netapp"},"change_message_id":"1e6628d1e59f63153ae227431297b5c7239df8f1","unresolved":true,"context_lines":[{"line_number":490,"context_line":"            self.driver.restore(incr, self.volume, volume_file, True)"},{"line_number":491,"context_line":""},{"line_number":492,"context_line":"        # Base (self.backup) applied first as \"new\"; incremental is not."},{"line_number":493,"context_line":"        self.assertEqual([(self.backup.id, True), (incr.id, False)], calls)"},{"line_number":494,"context_line":""},{"line_number":495,"context_line":"    def test_delete_backup(self):"},{"line_number":496,"context_line":"        with mock.patch.object(self.driver, \u0027delete_object\u0027) as mock_delete:"}],"source_content_type":"text/x-python","patch_set":3,"id":"06c20fbc_162626b3","line":493,"updated":"2026-08-16 09:53:14.000000000","message":"Good to test volume_is_new\u003dTrue scenario as well.","commit_id":"ea29d4895197a7be3ebf386e28b6eed15d7fbadf"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"10c207c2085295f47b782c8a7b00944790199de4","unresolved":false,"context_lines":[{"line_number":490,"context_line":"            self.driver.restore(incr, self.volume, volume_file, True)"},{"line_number":491,"context_line":""},{"line_number":492,"context_line":"        # Base (self.backup) applied first as \"new\"; incremental is not."},{"line_number":493,"context_line":"        self.assertEqual([(self.backup.id, True), (incr.id, False)], calls)"},{"line_number":494,"context_line":""},{"line_number":495,"context_line":"    def test_delete_backup(self):"},{"line_number":496,"context_line":"        with mock.patch.object(self.driver, \u0027delete_object\u0027) as mock_delete:"}],"source_content_type":"text/x-python","patch_set":3,"id":"2d852598_654661ce","line":493,"in_reply_to":"06c20fbc_162626b3","updated":"2026-08-16 15:23:16.000000000","message":"Acknowledged","commit_id":"ea29d4895197a7be3ebf386e28b6eed15d7fbadf"},{"author":{"_account_id":4523,"name":"Eric Harney","email":"eharney@redhat.com","username":"eharney"},"change_message_id":"1e30bd0f388473398e6de491021371988a6290b0","unresolved":true,"context_lines":[{"line_number":483,"context_line":"                            volume_is_new, requested):"},{"line_number":484,"context_line":"            calls.append((backup1.id, volume_is_new))"},{"line_number":485,"context_line":""},{"line_number":486,"context_line":"        self.driver._restore_v1 \u003d fake_restore_v1"},{"line_number":487,"context_line":"        incr \u003d self._create_backup_db_entry("},{"line_number":488,"context_line":"            self.volume, parent_id\u003dself.backup.id)"},{"line_number":489,"context_line":"        with mock.patch.object(self.driver, \u0027put_metadata\u0027):"}],"source_content_type":"text/x-python","patch_set":5,"id":"f685b7e2_40ad8400","line":486,"updated":"2026-08-31 16:51:15.000000000","message":"```suggestion\n        self.mock_object(self.driver, \u0027_restore_v1\u0027, fake_restore_v1)\n```","commit_id":"fd0521cf7def0ce1f140365222c663b564d22aea"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"f48d5817de3ea6024eea9eaafc28ef9c6c9fcafb","unresolved":false,"context_lines":[{"line_number":483,"context_line":"                            volume_is_new, requested):"},{"line_number":484,"context_line":"            calls.append((backup1.id, volume_is_new))"},{"line_number":485,"context_line":""},{"line_number":486,"context_line":"        self.driver._restore_v1 \u003d fake_restore_v1"},{"line_number":487,"context_line":"        incr \u003d self._create_backup_db_entry("},{"line_number":488,"context_line":"            self.volume, parent_id\u003dself.backup.id)"},{"line_number":489,"context_line":"        with mock.patch.object(self.driver, \u0027put_metadata\u0027):"}],"source_content_type":"text/x-python","patch_set":5,"id":"68d13688_1765c14b","line":486,"in_reply_to":"f685b7e2_40ad8400","updated":"2026-08-31 18:39:58.000000000","message":"Done","commit_id":"fd0521cf7def0ce1f140365222c663b564d22aea"},{"author":{"_account_id":4523,"name":"Eric Harney","email":"eharney@redhat.com","username":"eharney"},"change_message_id":"1e30bd0f388473398e6de491021371988a6290b0","unresolved":true,"context_lines":[{"line_number":503,"context_line":"                            volume_is_new, requested):"},{"line_number":504,"context_line":"            calls.append((backup1.id, volume_is_new))"},{"line_number":505,"context_line":""},{"line_number":506,"context_line":"        self.driver._restore_v1 \u003d fake_restore_v1"},{"line_number":507,"context_line":"        incr \u003d self._create_backup_db_entry("},{"line_number":508,"context_line":"            self.volume, parent_id\u003dself.backup.id)"},{"line_number":509,"context_line":"        with mock.patch.object(self.driver, \u0027put_metadata\u0027):"}],"source_content_type":"text/x-python","patch_set":5,"id":"7ab68449_ff859d9a","line":506,"updated":"2026-08-31 16:51:15.000000000","message":"```suggestion\n        self.mock_object(self.driver, \u0027_restore_v1\u0027, fake_restore_v1)\n```","commit_id":"fd0521cf7def0ce1f140365222c663b564d22aea"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"f48d5817de3ea6024eea9eaafc28ef9c6c9fcafb","unresolved":false,"context_lines":[{"line_number":503,"context_line":"                            volume_is_new, requested):"},{"line_number":504,"context_line":"            calls.append((backup1.id, volume_is_new))"},{"line_number":505,"context_line":""},{"line_number":506,"context_line":"        self.driver._restore_v1 \u003d fake_restore_v1"},{"line_number":507,"context_line":"        incr \u003d self._create_backup_db_entry("},{"line_number":508,"context_line":"            self.volume, parent_id\u003dself.backup.id)"},{"line_number":509,"context_line":"        with mock.patch.object(self.driver, \u0027put_metadata\u0027):"}],"source_content_type":"text/x-python","patch_set":5,"id":"d6b76371_7c6bedfb","line":506,"in_reply_to":"7ab68449_ff859d9a","updated":"2026-08-31 18:39:58.000000000","message":"Done","commit_id":"fd0521cf7def0ce1f140365222c663b564d22aea"},{"author":{"_account_id":34860,"name":"Amit Uniyal","email":"auniyal@redhat.com","username":"auniyal"},"change_message_id":"05502e45b2d3e70a1d2aeb10c2c57ebb7e92b630","unresolved":true,"context_lines":[{"line_number":510,"context_line":"            self.driver.restore(incr, self.volume, volume_file, False)"},{"line_number":511,"context_line":""},{"line_number":512,"context_line":"        self.assertEqual([(self.backup.id, False), (incr.id, False)], calls)"},{"line_number":513,"context_line":""},{"line_number":514,"context_line":"    def test_delete_backup(self):"},{"line_number":515,"context_line":"        with mock.patch.object(self.driver, \u0027delete_object\u0027) as mock_delete:"},{"line_number":516,"context_line":"            self.driver.delete_backup(self.backup)"}],"source_content_type":"text/x-python","patch_set":5,"id":"e8cc79d6_05156f62","line":513,"updated":"2026-08-26 10:31:40.000000000","message":"so here in both tests we are asserting if restore_v1 received correct `volume_is_new` flag or not.\nthats fine to check, but it would be nice to also test whats the actual value writen in restored vol.\n\nI feel we could still have regression with this.","commit_id":"fd0521cf7def0ce1f140365222c663b564d22aea"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"618d4ffd15fc09a0d5b4622f1841872073397960","unresolved":false,"context_lines":[{"line_number":510,"context_line":"            self.driver.restore(incr, self.volume, volume_file, False)"},{"line_number":511,"context_line":""},{"line_number":512,"context_line":"        self.assertEqual([(self.backup.id, False), (incr.id, False)], calls)"},{"line_number":513,"context_line":""},{"line_number":514,"context_line":"    def test_delete_backup(self):"},{"line_number":515,"context_line":"        with mock.patch.object(self.driver, \u0027delete_object\u0027) as mock_delete:"},{"line_number":516,"context_line":"            self.driver.delete_backup(self.backup)"}],"source_content_type":"text/x-python","patch_set":5,"id":"a0ce86d4_d1da7e53","line":513,"in_reply_to":"6e374a0e_7829c521","updated":"2026-09-10 13:52:07.000000000","message":"Done","commit_id":"fd0521cf7def0ce1f140365222c663b564d22aea"},{"author":{"_account_id":34860,"name":"Amit Uniyal","email":"auniyal@redhat.com","username":"auniyal"},"change_message_id":"2b485f2a04e596325e040dec80e04ce81742197d","unresolved":true,"context_lines":[{"line_number":510,"context_line":"            self.driver.restore(incr, self.volume, volume_file, False)"},{"line_number":511,"context_line":""},{"line_number":512,"context_line":"        self.assertEqual([(self.backup.id, False), (incr.id, False)], calls)"},{"line_number":513,"context_line":""},{"line_number":514,"context_line":"    def test_delete_backup(self):"},{"line_number":515,"context_line":"        with mock.patch.object(self.driver, \u0027delete_object\u0027) as mock_delete:"},{"line_number":516,"context_line":"            self.driver.delete_backup(self.backup)"}],"source_content_type":"text/x-python","patch_set":5,"id":"6e374a0e_7829c521","line":513,"in_reply_to":"a390b791_115c5645","updated":"2026-09-07 01:12:11.000000000","message":"thanks for adding `test_restore_chain_zeroes_data_to_zero_region`, it\u0027s a good reproducer, and I confirmed it failed without a fix.\n\nthe setup feels heavier than it needs to be though, I think https://github.com/openstack/cinder/blob/master/cinder/tests/unit/backup/drivers/test_backup_posix.py#L294 does something similar -  by just backing up a real file first and restoring it, no metadata/reader mocking at all. might be worth seeing if we could use some from that.","commit_id":"fd0521cf7def0ce1f140365222c663b564d22aea"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"f48d5817de3ea6024eea9eaafc28ef9c6c9fcafb","unresolved":false,"context_lines":[{"line_number":510,"context_line":"            self.driver.restore(incr, self.volume, volume_file, False)"},{"line_number":511,"context_line":""},{"line_number":512,"context_line":"        self.assertEqual([(self.backup.id, False), (incr.id, False)], calls)"},{"line_number":513,"context_line":""},{"line_number":514,"context_line":"    def test_delete_backup(self):"},{"line_number":515,"context_line":"        with mock.patch.object(self.driver, \u0027delete_object\u0027) as mock_delete:"},{"line_number":516,"context_line":"            self.driver.delete_backup(self.backup)"}],"source_content_type":"text/x-python","patch_set":5,"id":"a390b791_115c5645","line":513,"in_reply_to":"e8cc79d6_05156f62","updated":"2026-08-31 18:39:58.000000000","message":"Done","commit_id":"fd0521cf7def0ce1f140365222c663b564d22aea"},{"author":{"_account_id":34860,"name":"Amit Uniyal","email":"auniyal@redhat.com","username":"auniyal"},"change_message_id":"2b485f2a04e596325e040dec80e04ce81742197d","unresolved":true,"context_lines":[{"line_number":113,"context_line":"        return json.dumps(self.metadata).encode(\u0027utf-8\u0027)"},{"line_number":114,"context_line":""},{"line_number":115,"context_line":""},{"line_number":116,"context_line":"class RestoreTargetFile(object):"},{"line_number":117,"context_line":"    \"\"\"A writable file-like restore target backed by a bytearray."},{"line_number":118,"context_line":""},{"line_number":119,"context_line":"    Enough of the file protocol for ``_restore_v1`` to write into, so a test"},{"line_number":120,"context_line":"    can assert the bytes a restore actually leaves on the volume."},{"line_number":121,"context_line":"    \"\"\""},{"line_number":122,"context_line":""},{"line_number":123,"context_line":"    def __init__(self, size):"},{"line_number":124,"context_line":"        self.data \u003d bytearray(size)"},{"line_number":125,"context_line":"        self._pos \u003d 0"},{"line_number":126,"context_line":""},{"line_number":127,"context_line":"    def seek(self, offset):"},{"line_number":128,"context_line":"        self._pos \u003d offset"},{"line_number":129,"context_line":""},{"line_number":130,"context_line":"    def tell(self):"},{"line_number":131,"context_line":"        return self._pos"},{"line_number":132,"context_line":""},{"line_number":133,"context_line":"    def write(self, data):"},{"line_number":134,"context_line":"        self.data[self._pos:self._pos + len(data)] \u003d data"},{"line_number":135,"context_line":"        self._pos +\u003d len(data)"},{"line_number":136,"context_line":""},{"line_number":137,"context_line":"    def flush(self):"},{"line_number":138,"context_line":"        pass"},{"line_number":139,"context_line":""},{"line_number":140,"context_line":"    def fileno(self):"},{"line_number":141,"context_line":"        # _restore_v1 tolerates IOError here and skips the fsync()."},{"line_number":142,"context_line":"        raise IOError(\u0027no fileno\u0027)"},{"line_number":143,"context_line":""},{"line_number":144,"context_line":""},{"line_number":145,"context_line":"class FakeObjectReader(object):"},{"line_number":146,"context_line":"    \"\"\"Context manager returning a fixed backup object payload.\"\"\""},{"line_number":147,"context_line":""},{"line_number":148,"context_line":"    def __init__(self, data):"},{"line_number":149,"context_line":"        self._data \u003d data"},{"line_number":150,"context_line":""},{"line_number":151,"context_line":"    def __enter__(self):"},{"line_number":152,"context_line":"        return self"},{"line_number":153,"context_line":""},{"line_number":154,"context_line":"    def __exit__(self, exc_type, exc_value, traceback):"},{"line_number":155,"context_line":"        return False"},{"line_number":156,"context_line":""},{"line_number":157,"context_line":"    def read(self):"},{"line_number":158,"context_line":"        return self._data"},{"line_number":159,"context_line":""},{"line_number":160,"context_line":""},{"line_number":161,"context_line":"class ChunkedDriverTestCase(test.TestCase):"},{"line_number":162,"context_line":""},{"line_number":163,"context_line":"    def _create_backup_db_entry(self, volume_id\u003dfake.VOLUME_ID,"}],"source_content_type":"text/x-python","patch_set":6,"id":"f506254b_058aa292","line":160,"range":{"start_line":116,"start_character":0,"end_line":160,"end_character":0},"updated":"2026-09-07 01:12:11.000000000","message":"we probably don\u0027t need these two helper classes.\n\n`tempfile.NamedTemporaryFile` is already the pattern for volume_file in the chunked driver tests, see this [1] [2]\n\n\n[1]  https://github.com/openstack/cinder/blob/a4767351ce74612281497cef7cd7a36f8d784f56/cinder/tests/unit/backup/drivers/test_backup_swift.py#L918-L924\n[2] https://github.com/openstack/cinder/blob/a4767351ce74612281497cef7cd7a36f8d784f56/cinder/tests/unit/backup/drivers/test_backup_posix.py#L294","commit_id":"0f6b0d82ac2a33c6ea5aa9bcfa99b2747a22e129"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"618d4ffd15fc09a0d5b4622f1841872073397960","unresolved":false,"context_lines":[{"line_number":113,"context_line":"        return json.dumps(self.metadata).encode(\u0027utf-8\u0027)"},{"line_number":114,"context_line":""},{"line_number":115,"context_line":""},{"line_number":116,"context_line":"class RestoreTargetFile(object):"},{"line_number":117,"context_line":"    \"\"\"A writable file-like restore target backed by a bytearray."},{"line_number":118,"context_line":""},{"line_number":119,"context_line":"    Enough of the file protocol for ``_restore_v1`` to write into, so a test"},{"line_number":120,"context_line":"    can assert the bytes a restore actually leaves on the volume."},{"line_number":121,"context_line":"    \"\"\""},{"line_number":122,"context_line":""},{"line_number":123,"context_line":"    def __init__(self, size):"},{"line_number":124,"context_line":"        self.data \u003d bytearray(size)"},{"line_number":125,"context_line":"        self._pos \u003d 0"},{"line_number":126,"context_line":""},{"line_number":127,"context_line":"    def seek(self, offset):"},{"line_number":128,"context_line":"        self._pos \u003d offset"},{"line_number":129,"context_line":""},{"line_number":130,"context_line":"    def tell(self):"},{"line_number":131,"context_line":"        return self._pos"},{"line_number":132,"context_line":""},{"line_number":133,"context_line":"    def write(self, data):"},{"line_number":134,"context_line":"        self.data[self._pos:self._pos + len(data)] \u003d data"},{"line_number":135,"context_line":"        self._pos +\u003d len(data)"},{"line_number":136,"context_line":""},{"line_number":137,"context_line":"    def flush(self):"},{"line_number":138,"context_line":"        pass"},{"line_number":139,"context_line":""},{"line_number":140,"context_line":"    def fileno(self):"},{"line_number":141,"context_line":"        # _restore_v1 tolerates IOError here and skips the fsync()."},{"line_number":142,"context_line":"        raise IOError(\u0027no fileno\u0027)"},{"line_number":143,"context_line":""},{"line_number":144,"context_line":""},{"line_number":145,"context_line":"class FakeObjectReader(object):"},{"line_number":146,"context_line":"    \"\"\"Context manager returning a fixed backup object payload.\"\"\""},{"line_number":147,"context_line":""},{"line_number":148,"context_line":"    def __init__(self, data):"},{"line_number":149,"context_line":"        self._data \u003d data"},{"line_number":150,"context_line":""},{"line_number":151,"context_line":"    def __enter__(self):"},{"line_number":152,"context_line":"        return self"},{"line_number":153,"context_line":""},{"line_number":154,"context_line":"    def __exit__(self, exc_type, exc_value, traceback):"},{"line_number":155,"context_line":"        return False"},{"line_number":156,"context_line":""},{"line_number":157,"context_line":"    def read(self):"},{"line_number":158,"context_line":"        return self._data"},{"line_number":159,"context_line":""},{"line_number":160,"context_line":""},{"line_number":161,"context_line":"class ChunkedDriverTestCase(test.TestCase):"},{"line_number":162,"context_line":""},{"line_number":163,"context_line":"    def _create_backup_db_entry(self, volume_id\u003dfake.VOLUME_ID,"}],"source_content_type":"text/x-python","patch_set":6,"id":"2a348f42_e0be000a","line":160,"range":{"start_line":116,"start_character":0,"end_line":160,"end_character":0},"in_reply_to":"f506254b_058aa292","updated":"2026-09-10 13:52:07.000000000","message":"Done","commit_id":"0f6b0d82ac2a33c6ea5aa9bcfa99b2747a22e129"}],"releasenotes/notes/fix-incremental-restore-data-to-zero-6f3b9c2d1a4e0b7c.yaml":[{"author":{"_account_id":36171,"name":"jayaanand borra","display_name":"jayaanand borra","email":"jayaanand.borra@netapp.com","username":"jayaanan","status":"netapp"},"change_message_id":"1e6628d1e59f63153ae227431297b5c7239df8f1","unresolved":true,"context_lines":[{"line_number":2,"context_line":"fixes:"},{"line_number":3,"context_line":"  - |"},{"line_number":4,"context_line":"    Fixed a data-corruption bug when restoring an incremental backup chain"},{"line_number":5,"context_line":"    (chunked backup drivers: Swift, S3, Google, NFS, Ceph, ...) to a *new* or"},{"line_number":6,"context_line":"    sparse volume.  The base backup is restored skipping all-zero chunks"},{"line_number":7,"context_line":"    (the fresh target already reads as zero), but that optimisation was also"},{"line_number":8,"context_line":"    applied to the incremental backups layered on top, so a region that"}],"source_content_type":"text/x-yaml","patch_set":3,"id":"04d0e273_0e77d607","line":5,"updated":"2026-08-16 09:53:14.000000000","message":"The bug description already says Ceph is not affected. Is Ceph ChunkedBackupDriver?","commit_id":"ea29d4895197a7be3ebf386e28b6eed15d7fbadf"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"10c207c2085295f47b782c8a7b00944790199de4","unresolved":false,"context_lines":[{"line_number":2,"context_line":"fixes:"},{"line_number":3,"context_line":"  - |"},{"line_number":4,"context_line":"    Fixed a data-corruption bug when restoring an incremental backup chain"},{"line_number":5,"context_line":"    (chunked backup drivers: Swift, S3, Google, NFS, Ceph, ...) to a *new* or"},{"line_number":6,"context_line":"    sparse volume.  The base backup is restored skipping all-zero chunks"},{"line_number":7,"context_line":"    (the fresh target already reads as zero), but that optimisation was also"},{"line_number":8,"context_line":"    applied to the incremental backups layered on top, so a region that"}],"source_content_type":"text/x-yaml","patch_set":3,"id":"b62463fd_daead701","line":5,"in_reply_to":"04d0e273_0e77d607","updated":"2026-08-16 15:23:16.000000000","message":"Yep - missed it - fixed","commit_id":"ea29d4895197a7be3ebf386e28b6eed15d7fbadf"},{"author":{"_account_id":27615,"name":"Rajat Dhasmana","email":"rajatdhasmana@gmail.com","username":"whoami-rajat"},"change_message_id":"13298afe3885c5cf2f8cde9b3b0fa916f72af519","unresolved":true,"context_lines":[{"line_number":1,"context_line":"---"},{"line_number":2,"context_line":"fixes:"},{"line_number":3,"context_line":"  - |"},{"line_number":4,"context_line":"    Fixed a data-corruption bug when restoring an incremental backup chain"},{"line_number":5,"context_line":"    (chunked backup drivers: Swift, S3, Google Cloud Storage, POSIX/NFS) to a"},{"line_number":6,"context_line":"    *new* or sparse volume.  The base backup is restored skipping all-zero"}],"source_content_type":"text/x-yaml","patch_set":7,"id":"843eab7a_4fa27291","line":3,"updated":"2026-09-10 22:24:42.000000000","message":"include the launchpad bug link like the example here\n\nhttps://docs.openstack.org/cinder/latest/contributor/releasenotes.html#bugs","commit_id":"884d8ff7f7c2179bd4554e6d57bb4b69ed4a5b8a"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"49135f6c1490fabf6d7f35512c758f3fa2094b60","unresolved":false,"context_lines":[{"line_number":1,"context_line":"---"},{"line_number":2,"context_line":"fixes:"},{"line_number":3,"context_line":"  - |"},{"line_number":4,"context_line":"    Fixed a data-corruption bug when restoring an incremental backup chain"},{"line_number":5,"context_line":"    (chunked backup drivers: Swift, S3, Google Cloud Storage, POSIX/NFS) to a"},{"line_number":6,"context_line":"    *new* or sparse volume.  The base backup is restored skipping all-zero"}],"source_content_type":"text/x-yaml","patch_set":7,"id":"27918a81_0e2d28fb","line":3,"in_reply_to":"843eab7a_4fa27291","updated":"2026-09-11 02:26:41.000000000","message":"Done","commit_id":"884d8ff7f7c2179bd4554e6d57bb4b69ed4a5b8a"},{"author":{"_account_id":27615,"name":"Rajat Dhasmana","email":"rajatdhasmana@gmail.com","username":"whoami-rajat"},"change_message_id":"13298afe3885c5cf2f8cde9b3b0fa916f72af519","unresolved":true,"context_lines":[{"line_number":3,"context_line":"  - |"},{"line_number":4,"context_line":"    Fixed a data-corruption bug when restoring an incremental backup chain"},{"line_number":5,"context_line":"    (chunked backup drivers: Swift, S3, Google Cloud Storage, POSIX/NFS) to a"},{"line_number":6,"context_line":"    *new* or sparse volume.  The base backup is restored skipping all-zero"},{"line_number":7,"context_line":"    chunks"},{"line_number":8,"context_line":"    (the fresh target already reads as zero), but that optimisation was also"},{"line_number":9,"context_line":"    applied to the incremental backups layered on top, so a region that"}],"source_content_type":"text/x-yaml","patch_set":7,"id":"0f853132_3d59cbec","line":6,"range":{"start_line":6,"start_character":10,"end_line":6,"end_character":27},"updated":"2026-09-10 22:24:42.000000000","message":"this implies that it can also happen to an existing volume that is sparse? I don\u0027t know how that could be the case.","commit_id":"884d8ff7f7c2179bd4554e6d57bb4b69ed4a5b8a"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"49135f6c1490fabf6d7f35512c758f3fa2094b60","unresolved":false,"context_lines":[{"line_number":3,"context_line":"  - |"},{"line_number":4,"context_line":"    Fixed a data-corruption bug when restoring an incremental backup chain"},{"line_number":5,"context_line":"    (chunked backup drivers: Swift, S3, Google Cloud Storage, POSIX/NFS) to a"},{"line_number":6,"context_line":"    *new* or sparse volume.  The base backup is restored skipping all-zero"},{"line_number":7,"context_line":"    chunks"},{"line_number":8,"context_line":"    (the fresh target already reads as zero), but that optimisation was also"},{"line_number":9,"context_line":"    applied to the incremental backups layered on top, so a region that"}],"source_content_type":"text/x-yaml","patch_set":7,"id":"9b31c59f_b1036d57","line":6,"range":{"start_line":6,"start_character":10,"end_line":6,"end_character":27},"in_reply_to":"0f853132_3d59cbec","updated":"2026-09-11 02:26:41.000000000","message":"Done","commit_id":"884d8ff7f7c2179bd4554e6d57bb4b69ed4a5b8a"},{"author":{"_account_id":27615,"name":"Rajat Dhasmana","email":"rajatdhasmana@gmail.com","username":"whoami-rajat"},"change_message_id":"13298afe3885c5cf2f8cde9b3b0fa916f72af519","unresolved":true,"context_lines":[{"line_number":4,"context_line":"    Fixed a data-corruption bug when restoring an incremental backup chain"},{"line_number":5,"context_line":"    (chunked backup drivers: Swift, S3, Google Cloud Storage, POSIX/NFS) to a"},{"line_number":6,"context_line":"    *new* or sparse volume.  The base backup is restored skipping all-zero"},{"line_number":7,"context_line":"    chunks"},{"line_number":8,"context_line":"    (the fresh target already reads as zero), but that optimisation was also"},{"line_number":9,"context_line":"    applied to the incremental backups layered on top, so a region that"},{"line_number":10,"context_line":"    changed from data to zero between backups was skipped instead of being"}],"source_content_type":"text/x-yaml","patch_set":7,"id":"2923bd1d_c918bbfa","line":7,"updated":"2026-09-10 22:24:42.000000000","message":"seems to be some formatting issue here","commit_id":"884d8ff7f7c2179bd4554e6d57bb4b69ed4a5b8a"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"49135f6c1490fabf6d7f35512c758f3fa2094b60","unresolved":false,"context_lines":[{"line_number":4,"context_line":"    Fixed a data-corruption bug when restoring an incremental backup chain"},{"line_number":5,"context_line":"    (chunked backup drivers: Swift, S3, Google Cloud Storage, POSIX/NFS) to a"},{"line_number":6,"context_line":"    *new* or sparse volume.  The base backup is restored skipping all-zero"},{"line_number":7,"context_line":"    chunks"},{"line_number":8,"context_line":"    (the fresh target already reads as zero), but that optimisation was also"},{"line_number":9,"context_line":"    applied to the incremental backups layered on top, so a region that"},{"line_number":10,"context_line":"    changed from data to zero between backups was skipped instead of being"}],"source_content_type":"text/x-yaml","patch_set":7,"id":"5c8074d7_bf16bb4b","line":7,"in_reply_to":"2923bd1d_c918bbfa","updated":"2026-09-11 02:26:41.000000000","message":"Done","commit_id":"884d8ff7f7c2179bd4554e6d57bb4b69ed4a5b8a"},{"author":{"_account_id":27615,"name":"Rajat Dhasmana","email":"rajatdhasmana@gmail.com","username":"whoami-rajat"},"change_message_id":"13298afe3885c5cf2f8cde9b3b0fa916f72af519","unresolved":true,"context_lines":[],"source_content_type":"","patch_set":7,"id":"69820dca_35efe263","line":14,"range":{"start_line":8,"start_character":4,"end_line":14,"end_character":0},"updated":"2026-09-10 22:24:42.000000000","message":"can we summarize this in fewer lines? this looks like we are repeating the problem statement again here","commit_id":"884d8ff7f7c2179bd4554e6d57bb4b69ed4a5b8a"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"49135f6c1490fabf6d7f35512c758f3fa2094b60","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":7,"id":"f3b407c4_68732296","line":14,"range":{"start_line":8,"start_character":4,"end_line":14,"end_character":0},"in_reply_to":"69820dca_35efe263","updated":"2026-09-11 02:26:41.000000000","message":"Done","commit_id":"884d8ff7f7c2179bd4554e6d57bb4b69ed4a5b8a"}]}
