)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"4b2e03918492d4eabd7e1bc22d5c0ed7a7437e16","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"207f43e4_758b5836","updated":"2026-09-24 19:49:45.000000000","message":"recheck","commit_id":"bde07c237ec0a9e1bffe00077d57134d384fab7c"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"cdbe8663e50832a612868cdb6c04a867e6098a8d","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"0ec0e393_e8daf6e2","updated":"2026-09-24 18:10:16.000000000","message":"run Everpure CI","commit_id":"bde07c237ec0a9e1bffe00077d57134d384fab7c"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"c81edbd9a71699ad5f66425c4366809dc74d44e9","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"2243693c_7df2401c","updated":"2026-09-24 21:21:28.000000000","message":"run Everpure CI","commit_id":"bde07c237ec0a9e1bffe00077d57134d384fab7c"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"4767fb22f01f86d2de1f1a13b7c2a6d2f663740e","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"5966a927_57c2a5f5","updated":"2026-09-24 18:30:55.000000000","message":"run Everpure CI","commit_id":"bde07c237ec0a9e1bffe00077d57134d384fab7c"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"9e68919dda93eb057a21048cdac6892471763ec1","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"95a6d259_61510ca8","updated":"2026-09-24 19:44:59.000000000","message":"run Everpure CI","commit_id":"bde07c237ec0a9e1bffe00077d57134d384fab7c"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"86bc04f3209bae5c23d94ede0908af2bacb2e3b8","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"f9416fde_004c61f3","updated":"2026-09-24 22:18:02.000000000","message":"run Everpure CI","commit_id":"bde07c237ec0a9e1bffe00077d57134d384fab7c"}],"cinder/volume/drivers/pure.py":[{"author":{"_account_id":36171,"name":"jayaanand borra","display_name":"jayaanand borra","email":"jayaanand.borra@netapp.com","username":"jayaanan","status":"netapp"},"change_message_id":"cf823a4ead3f105eb68196647db4534be11c7a71","unresolved":true,"context_lines":[{"line_number":3636,"context_line":"                filter\u003d\"name\u003d\u0027%s*\u0027\" % replica_prefix)"},{"line_number":3637,"context_line":"            transfers \u003d list(res.items) if res.status_code \u003d\u003d 200 else []"},{"line_number":3638,"context_line":"            if transfers:"},{"line_number":3639,"context_line":"                transfer \u003d transfers[0]"},{"line_number":3640,"context_line":"                if getattr(transfer, \"completed\", None):"},{"line_number":3641,"context_line":"                    return transfer.name"},{"line_number":3642,"context_line":"                if attempt % log_every \u003d\u003d 0:"}],"source_content_type":"text/x-python","patch_set":4,"id":"bbb09840_b9e8e77e","line":3639,"updated":"2026-09-27 17:02:48.000000000","message":"transfer \u003d transfers[0] is not tied to this send. The suffix at line 3709\nis stable (cinder-migrate-\u003cvolume.id[:8]\u003e), and this returns the first\nlisted transfer as soon as its \"completed\" attribute is set.\n\nCleanup does not establish the \"one match\" claim in the docstring at\nline 3624:\n\n- _destroy_and_eradicate_snapshot() (3559) never raises. A HTTP 400 that\n  is not an already-gone error is logged and the send still starts.\n  Any other status is ignored.\n- _replica_snapshots() (3583) turns a non-200 listing into [] and skips\n  cleanup.\n\nIf an older replica for this prefix is still listed and already completed,\nthis returns that name on the first poll. _materialize_migrated_volume()\nthen copies it, and line 3735 deletes the source.\n\nPlease list the prefix after cleanup and do not send if any replica\nremains. In this loop, ignore names that were present before this send,\nand do not pick transfers[0] when more than one item matches.","commit_id":"bde07c237ec0a9e1bffe00077d57134d384fab7c"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"6ecbacef5534648ad6666d57d3a5f54f5bb7feb0","unresolved":false,"context_lines":[{"line_number":3636,"context_line":"                filter\u003d\"name\u003d\u0027%s*\u0027\" % replica_prefix)"},{"line_number":3637,"context_line":"            transfers \u003d list(res.items) if res.status_code \u003d\u003d 200 else []"},{"line_number":3638,"context_line":"            if transfers:"},{"line_number":3639,"context_line":"                transfer \u003d transfers[0]"},{"line_number":3640,"context_line":"                if getattr(transfer, \"completed\", None):"},{"line_number":3641,"context_line":"                    return transfer.name"},{"line_number":3642,"context_line":"                if attempt % log_every \u003d\u003d 0:"}],"source_content_type":"text/x-python","patch_set":4,"id":"e8d8b894_1c4d235a","line":3639,"in_reply_to":"bbb09840_b9e8e77e","updated":"2026-09-28 10:01:35.000000000","message":"Agreed, and thanks for tracing it through the cleanup. Three changes:\nafter _clean_stale_assisted_objects() the prefix is listed again in\nstrict mode, and a listing failure or any live replica still present\nraises before the send, so the generic path runs against a clean\npeer; destroyed replicas that a delayed eradication has left behind\nare not treated as leftovers, but their names are recorded and the\ntransfer poll ignores them; and the poll now raises if more than one\nname outside that set matches, instead of taking transfers[0]. Tests:\ntest_migrate_volume_live_replica_after_cleanup_declined,\n..._listing_failure_after_cleanup_declined,\n..._ignores_destroyed_replica_left_behind (the completed stale\ntransfer is listed first and never chosen) and\n..._ambiguous_replicas_abandoned.","commit_id":"bde07c237ec0a9e1bffe00077d57134d384fab7c"},{"author":{"_account_id":36171,"name":"jayaanand borra","display_name":"jayaanand borra","email":"jayaanand.borra@netapp.com","username":"jayaanan","status":"netapp"},"change_message_id":"cf823a4ead3f105eb68196647db4534be11c7a71","unresolved":true,"context_lines":[{"line_number":3662,"context_line":"    def _materialize_migrated_volume(self, peer, volume, vol_name,"},{"line_number":3663,"context_line":"                                     replica_name):"},{"line_number":3664,"context_line":"        \"\"\"Copy the arrived replica snapshot into the target volume.\"\"\""},{"line_number":3665,"context_line":"        res \u003d peer.post_volumes("},{"line_number":3666,"context_line":"            names\u003d[vol_name],"},{"line_number":3667,"context_line":"            volume\u003dflasharray.VolumePost("},{"line_number":3668,"context_line":"                source\u003dflasharray.Reference(name\u003dreplica_name)))"}],"source_content_type":"text/x-python","patch_set":4,"id":"ed3afbb5_f626a03e","line":3665,"updated":"2026-09-27 17:02:48.000000000","message":"create_volume_from_snapshot() passes\nwith_default_protection\u003dself._safemode_enabled when\nself._array.safemode is set (1018-1024). This copy from the replica\nsnapshot does not. pure_safemode_enabled is documented as applying to\nobjects this driver creates. Please use the same argument here.","commit_id":"bde07c237ec0a9e1bffe00077d57134d384fab7c"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"6ecbacef5534648ad6666d57d3a5f54f5bb7feb0","unresolved":false,"context_lines":[{"line_number":3662,"context_line":"    def _materialize_migrated_volume(self, peer, volume, vol_name,"},{"line_number":3663,"context_line":"                                     replica_name):"},{"line_number":3664,"context_line":"        \"\"\"Copy the arrived replica snapshot into the target volume.\"\"\""},{"line_number":3665,"context_line":"        res \u003d peer.post_volumes("},{"line_number":3666,"context_line":"            names\u003d[vol_name],"},{"line_number":3667,"context_line":"            volume\u003dflasharray.VolumePost("},{"line_number":3668,"context_line":"                source\u003dflasharray.Reference(name\u003dreplica_name)))"}],"source_content_type":"text/x-python","patch_set":4,"id":"8d47bf6d_9e533700","line":3665,"in_reply_to":"ed3afbb5_f626a03e","updated":"2026-09-28 10:01:35.000000000","message":"Done, with one difference from the suggestion: self._array.safemode\ndescribes the source array\u0027s REST version, not the peer\u0027s, and\n_get_flasharray() does not set the attribute on target arrays. The\ncopy is now created with with_default_protection\u003dself._safemode_enabled\nwhen the peer itself runs the release that accepts it, decided the\nsame way do_setup() decides it for the primary (SAFEMODE_MIN_PURITY,\n6.3.4, now a constant used by both). Below that release the argument\nis omitted, as it must be. ddt test covers enabled/6.10.6,\ndisabled/6.10.6 and enabled/6.2.0.","commit_id":"bde07c237ec0a9e1bffe00077d57134d384fab7c"},{"author":{"_account_id":36171,"name":"jayaanand borra","display_name":"jayaanand borra","email":"jayaanand.borra@netapp.com","username":"jayaanan","status":"netapp"},"change_message_id":"cf823a4ead3f105eb68196647db4534be11c7a71","unresolved":true,"context_lines":[{"line_number":3679,"context_line":"        if volume.volume_type is not None:"},{"line_number":3680,"context_line":"            qos \u003d self._get_qos_settings(volume.volume_type)"},{"line_number":3681,"context_line":"        if qos is not None:"},{"line_number":3682,"context_line":"            self.set_qos(peer, vol_name, volume[\u0027size\u0027], qos)"},{"line_number":3683,"context_line":"        self._tag_volume(volume_name\u003dvol_name,"},{"line_number":3684,"context_line":"                         vol_type\u003d\"Data\","},{"line_number":3685,"context_line":"                         project\u003dvolume.project_id,"}],"source_content_type":"text/x-python","patch_set":4,"id":"7ea76dda_57301f27","line":3682,"updated":"2026-09-27 17:02:48.000000000","message":"set_qos() discards the patch_volumes() response and returns None\n(493-516). A rejected QoS patch still reaches the return (True,\nmodel_update) below. test_migrate_volume_assisted_applies_qos mocks\nset_qos, so it only checks that the helper was called. Please fail this\nmigration if that patch is not HTTP 200. This is the same gap as the\nexisting set_qos() call in create_volume_from_snapshot() at line 1037;\nthis new path reports the migration successful.","commit_id":"bde07c237ec0a9e1bffe00077d57134d384fab7c"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"6ecbacef5534648ad6666d57d3a5f54f5bb7feb0","unresolved":false,"context_lines":[{"line_number":3679,"context_line":"        if volume.volume_type is not None:"},{"line_number":3680,"context_line":"            qos \u003d self._get_qos_settings(volume.volume_type)"},{"line_number":3681,"context_line":"        if qos is not None:"},{"line_number":3682,"context_line":"            self.set_qos(peer, vol_name, volume[\u0027size\u0027], qos)"},{"line_number":3683,"context_line":"        self._tag_volume(volume_name\u003dvol_name,"},{"line_number":3684,"context_line":"                         vol_type\u003d\"Data\","},{"line_number":3685,"context_line":"                         project\u003dvolume.project_id,"}],"source_content_type":"text/x-python","patch_set":4,"id":"d2b28e79_5d7b34ae","line":3682,"in_reply_to":"7ea76dda_57301f27","updated":"2026-09-28 10:01:35.000000000","message":"Done. set_qos() now returns the patch_volumes() response (its four\nbranches only differed in the two limits, so it builds them and makes\none call), and the migration checks it with _raise_unless_ok(), so a\nrejected QoS patch fails the attempt, the copy on the peer is removed\nand the generic path runs. The existing create paths keep their\nbehaviour; tightening them is a separate change. The QoS test now\nreturns a 200 from the mock, and test_migrate_volume_qos_failure_declined\ncovers the 400.","commit_id":"bde07c237ec0a9e1bffe00077d57134d384fab7c"},{"author":{"_account_id":36171,"name":"jayaanand borra","display_name":"jayaanand borra","email":"jayaanand.borra@netapp.com","username":"jayaanan","status":"netapp"},"change_message_id":"cf823a4ead3f105eb68196647db4534be11c7a71","unresolved":true,"context_lines":[{"line_number":3732,"context_line":"        # completed; this covers releases that keep it as a baseline."},{"line_number":3733,"context_line":"        self._destroy_and_eradicate_snapshot(source_array, send_snap)"},{"line_number":3734,"context_line":"        try:"},{"line_number":3735,"context_line":"            self.delete_volume(volume)"},{"line_number":3736,"context_line":"        except Exception as err:"},{"line_number":3737,"context_line":"            LOG.error(\"Volume %(vol)s was migrated to %(dst)s but its \""},{"line_number":3738,"context_line":"                      \"source copy on %(src)s could not be removed: \""}],"source_content_type":"text/x-python","patch_set":4,"id":"a8c97636_d41e39cb","line":3735,"updated":"2026-09-27 17:02:48.000000000","message":"delete_volume() runs before migrate_volume() returns. On the parent\ncommit (803127499), VolumeManager.migrate_volume() writes the new host\nonly after that return (manager.py:2705-2722). A cinder-volume restart\nin between leaves the row on the source backend.\n\nThe retry described in everpure-driver.rst:409 does not cover that\nwindow. _clean_stale_assisted_objects() runs only from\n_assisted_migrate_volume(). If the source get_volumes() is not HTTP 200,\n_is_assisted_migration_eligible() returns False at line 3518 and this\nmethod returns (False, None) at line 3768. The manager then runs generic\nmigration, which reads the source. Nothing in this path returns success\nfor a volume already materialized on the peer.\n\nLine 3730 has already eradicated the replica snapshot by the time the\nsource is deleted, so a retry cannot rely on that snapshot still being\nthe peer volume\u0027s source. Please recognize a completed peer volume\nwithout deleting it, and do not fall through to generic migration when\nthe source is already gone. delete_volume() eradicates only when\npure_eradicate_on_delete is set (1346); the default destroys the source\nand leaves it recoverable.","commit_id":"bde07c237ec0a9e1bffe00077d57134d384fab7c"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"6ecbacef5534648ad6666d57d3a5f54f5bb7feb0","unresolved":false,"context_lines":[{"line_number":3732,"context_line":"        # completed; this covers releases that keep it as a baseline."},{"line_number":3733,"context_line":"        self._destroy_and_eradicate_snapshot(source_array, send_snap)"},{"line_number":3734,"context_line":"        try:"},{"line_number":3735,"context_line":"            self.delete_volume(volume)"},{"line_number":3736,"context_line":"        except Exception as err:"},{"line_number":3737,"context_line":"            LOG.error(\"Volume %(vol)s was migrated to %(dst)s but its \""},{"line_number":3738,"context_line":"                      \"source copy on %(src)s could not be removed: \""}],"source_content_type":"text/x-python","patch_set":4,"id":"aed69a8e_023b9504","line":3735,"in_reply_to":"a8c97636_d41e39cb","updated":"2026-09-28 10:01:35.000000000","message":"You are right about the window, and about the retry: I traced it and\nthe old pre-flight would have destroyed the peer copy as a leftover.\nNow migrate_volume() reads the source first. If it is destroyed, or\nabsent with a \"does not exist\" error, and the peer holds a live volume\nof that name whose Purity source is one of our replica snapshots,\n_finish_interrupted_migration() applies QoS and tags and returns\nsuccess without sending, reading or deleting anything. If there is no\nsuch copy it raises, so the manager records migration_status error\nwith a message naming both arrays instead of running generic\nmigration against a missing source. Any other read failure still\ndeclines to the generic path as before. The source is never touched in\nthat path, so the destroyed copy stays recoverable where eradication\nis off. Docs updated to describe both interruption cases. Tests:\ntest_migrate_volume_source_destroyed_completed_from_peer and\ntest_migrate_volume_source_gone_without_peer_copy_raises (destroyed\nand absent).","commit_id":"bde07c237ec0a9e1bffe00077d57134d384fab7c"},{"author":{"_account_id":36171,"name":"jayaanand borra","display_name":"jayaanand borra","email":"jayaanand.borra@netapp.com","username":"jayaanan","status":"netapp"},"change_message_id":"cf823a4ead3f105eb68196647db4534be11c7a71","unresolved":true,"context_lines":[{"line_number":3761,"context_line":"        peer \u003d self._get_assisted_migration_peer(volume, host)"},{"line_number":3762,"context_line":"        if peer is None:"},{"line_number":3763,"context_line":"            return False, None"},{"line_number":3764,"context_line":"        source_array \u003d self._get_current_array(volume\u003dvolume)"},{"line_number":3765,"context_line":"        vol_name \u003d self._get_vol_name(volume)"},{"line_number":3766,"context_line":"        if not self._is_assisted_migration_eligible(source_array, volume,"},{"line_number":3767,"context_line":"                                                    vol_name):"}],"source_content_type":"text/x-python","patch_set":4,"id":"05ce9cad_fd278b49","line":3764,"updated":"2026-09-27 17:02:48.000000000","message":"This call, and the one in _get_assisted_migration_peer() at line 3478,\nare outside the try at line 3776. _get_current_array() raises\nPureDriverException when ActiveCluster is enabled and get_pods() is not\nHTTP 200 (4819-4824). ActiveCluster is enabled when a sync\nreplication_device exists (812-813). Assisted peers are not sync\ntargets, but a backend can have both.\n\nThe manager calls driver.migrate_volume() inside its own try\n(manager.py:2700-2729) and, on any exception, sets migration_status\u003derror\nand does not run generic migration. An offline sync target does not do\nthis: that branch only logs (4815-4818). A non-200 get_pods() does.\nPlease catch that on these two calls and return (False, None).","commit_id":"bde07c237ec0a9e1bffe00077d57134d384fab7c"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"6ecbacef5534648ad6666d57d3a5f54f5bb7feb0","unresolved":false,"context_lines":[{"line_number":3761,"context_line":"        peer \u003d self._get_assisted_migration_peer(volume, host)"},{"line_number":3762,"context_line":"        if peer is None:"},{"line_number":3763,"context_line":"            return False, None"},{"line_number":3764,"context_line":"        source_array \u003d self._get_current_array(volume\u003dvolume)"},{"line_number":3765,"context_line":"        vol_name \u003d self._get_vol_name(volume)"},{"line_number":3766,"context_line":"        if not self._is_assisted_migration_eligible(source_array, volume,"},{"line_number":3767,"context_line":"                                                    vol_name):"}],"source_content_type":"text/x-python","patch_set":4,"id":"f01b7ed8_e0afceab","line":3764,"in_reply_to":"05ce9cad_fd278b49","updated":"2026-09-28 10:01:35.000000000","message":"Done. _get_current_array() is now called once, first, inside a try in\nmigrate_volume(), and _get_assisted_migration_peer() takes the result\nrather than calling it again; a PureDriverException from it logs and\nreturns (False, None). In practice the generic path would hit the same\nget_pods() failure on its first attach, but the docstring promises a\nfallback and now keeps it.","commit_id":"bde07c237ec0a9e1bffe00077d57134d384fab7c"}]}
