)]}'
{"/COMMIT_MSG":[{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"7be7f1a12e0efbb0c9620d9f61f791387beb1015","unresolved":true,"context_lines":[{"line_number":15,"context_line":"Implement copy_volume_data_to_existing_volume() so the driver"},{"line_number":16,"context_line":"overwrites the destination volume from the source volume on the array"},{"line_number":17,"context_line":"(post_volumes with overwrite), which only copies metadata in Purity."},{"line_number":18,"context_line":"The destination keeps its Purity name, so its QoS, volume group, pod"},{"line_number":19,"context_line":"and protection group membership are unchanged, and no Cinder identity"},{"line_number":20,"context_line":"or provider field moves."},{"line_number":21,"context_line":""}],"source_content_type":"text/x-gerrit-commit-message","patch_set":3,"id":"5f9447df_f5c20a1a","line":18,"updated":"2026-11-10 19:21:09.000000000","message":"Question: \"its QoS, volume group, pod and protection group membership are unchanged\" — was that confirmed on an array? An overwrite replaces the volume\u0027s contents and keeps the object, so it is very likely true, but it is the kind of claim worth one sentence saying how it was checked.","commit_id":"8f92868304d01c0632fd4e3cf50a48b4d3c44620"}],"/PATCHSET_LEVEL":[{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"7be7f1a12e0efbb0c9620d9f61f791387beb1015","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"210c7440_596b4b87","updated":"2026-11-10 19:21:09.000000000","message":"A refused overwrite raises and fails the whole retype with full rollback, where returning `False` would let the host copy finish it; Purity refuses overwrites for container-state reasons that cannot be screened up front (proven on hardware). Deliberate per the commit message, so argue rather than block if you prefer. Plus the one assertion that matters in the copy tests is vacuous.","commit_id":"8f92868304d01c0632fd4e3cf50a48b4d3c44620"}],"cinder/tests/unit/volume/drivers/test_pure.py":[{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"7be7f1a12e0efbb0c9620d9f61f791387beb1015","unresolved":true,"context_lines":[{"line_number":2850,"context_line":"                                                 mock_enc_changed):"},{"line_number":2851,"context_line":"        src_vol, src_name, dest_vol, dest_name \u003d ("},{"line_number":2852,"context_line":"            self._setup_existing_volume_copy())"},{"line_number":2853,"context_line":"        mock_data \u003d self.flasharray.VolumePost("},{"line_number":2854,"context_line":"            source\u003dself.flasharray.Reference(name\u003dsrc_name))"},{"line_number":2855,"context_line":""},{"line_number":2856,"context_line":"        copied \u003d self.driver.copy_volume_data_to_existing_volume("}],"source_content_type":"text/x-python","patch_set":3,"id":"132ec302_9663af76","line":2853,"updated":"2026-11-10 19:21:09.000000000","message":"With `VolumePost` patched, this `mock_data` and the driver\u0027s `VolumePost(...)` are the same `return_value` whatever arguments are passed, and `Reference` is the unpatched module `Mock`, so the `volume\u003dmock_data` comparison at 2863 cannot detect a wrong source volume (on PS1, changing the driver to copy `dest_name` into itself left every copy test green). Please also patch `Reference` and assert `mock_ref.assert_called_once_with(name\u003dsrc_name)` and `mock_fa.assert_called_once_with(source\u003dmock_ref.return_value)`, the way `restores_size` pins `provisioned\u003d3 * units.Gi`.","commit_id":"8f92868304d01c0632fd4e3cf50a48b4d3c44620"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"7be7f1a12e0efbb0c9620d9f61f791387beb1015","unresolved":true,"context_lines":[{"line_number":2857,"context_line":"            mock.MagicMock(), src_vol, dest_vol, remote\u003d\u0027dest\u0027)"},{"line_number":2858,"context_line":""},{"line_number":2859,"context_line":"        self.assertTrue(copied)"},{"line_number":2860,"context_line":"        self.array.get_volumes.assert_called_once_with(names\u003d[dest_name])"},{"line_number":2861,"context_line":"        self.array.post_volumes.assert_called_once_with(names\u003d[dest_name],"},{"line_number":2862,"context_line":"                                                        overwrite\u003dTrue,"},{"line_number":2863,"context_line":"                                                        volume\u003dmock_data)"}],"source_content_type":"text/x-python","patch_set":3,"id":"87f6b68f_edad0f41","line":2860,"updated":"2026-11-10 19:21:09.000000000","message":"Nit: consider also asserting `self.driver._get_current_array.assert_called_with(volume\u003dsrc_vol)`, as the group-failover routing tests do, so the per-volume array resolution at `pure.py` L3018 cannot be dropped silently.","commit_id":"8f92868304d01c0632fd4e3cf50a48b4d3c44620"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"7be7f1a12e0efbb0c9620d9f61f791387beb1015","unresolved":true,"context_lines":[{"line_number":2901,"context_line":""},{"line_number":2902,"context_line":"    @mock.patch.object(volume_types, \u0027volume_types_encryption_changed\u0027,"},{"line_number":2903,"context_line":"                       return_value\u003dFalse)"},{"line_number":2904,"context_line":"    def test_copy_volume_data_to_existing_volume_other_array("},{"line_number":2905,"context_line":"            self, mock_enc_changed):"},{"line_number":2906,"context_line":"        \"\"\"A destination on another array falls back to the host copy.\"\"\""},{"line_number":2907,"context_line":"        src_vol, src_name, dest_vol, dest_name \u003d ("}],"source_content_type":"text/x-python","patch_set":3,"id":"b53439ae_fda28195","line":2904,"updated":"2026-11-10 19:21:09.000000000","message":"Nit: the early-return tests from here down assert only that `post_volumes` was not called. Asserting `patch_volumes` was not called either, and (for the guards that precede the lookup) that `get_volumes` was not called, would pin the guard order; and `_no_provider_id` covers only the `dest_name` half of the condition at `pure.py` L2986.","commit_id":"8f92868304d01c0632fd4e3cf50a48b4d3c44620"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"7be7f1a12e0efbb0c9620d9f61f791387beb1015","unresolved":true,"context_lines":[{"line_number":2928,"context_line":""},{"line_number":2929,"context_line":"    @mock.patch.object(volume_types, \u0027volume_types_encryption_changed\u0027,"},{"line_number":2930,"context_line":"                       return_value\u003dTrue)"},{"line_number":2931,"context_line":"    def test_copy_volume_data_to_existing_volume_encryption_changed("},{"line_number":2932,"context_line":"            self, mock_enc_changed):"},{"line_number":2933,"context_line":"        \"\"\"Re-encryption needs dm-crypt on the host, not a block copy.\"\"\""},{"line_number":2934,"context_line":"        src_vol, src_name, dest_vol, dest_name \u003d ("}],"source_content_type":"text/x-python","patch_set":3,"id":"4021b060_4e8da055","line":2931,"updated":"2026-11-10 19:21:09.000000000","message":"Nit: this pins the outcome but not the call; with the production call changed to `volume_types_encryption_changed(context, None, None)` the test still passes. Consider giving `dest_vol` a different `volume_type_id` (`fake.VOLUME_TYPE2_ID`) and asserting `mock_enc_changed.assert_called_once_with(mock.ANY, src_vol.volume_type_id, dest_vol.volume_type_id)`.","commit_id":"8f92868304d01c0632fd4e3cf50a48b4d3c44620"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"7be7f1a12e0efbb0c9620d9f61f791387beb1015","unresolved":true,"context_lines":[{"line_number":2966,"context_line":"                       return_value\u003dFalse)"},{"line_number":2967,"context_line":"    def test_copy_volume_data_to_existing_volume_copy_fails("},{"line_number":2968,"context_line":"            self, mock_enc_changed):"},{"line_number":2969,"context_line":"        \"\"\"A failed overwrite must fail the copy, not fall back silently."},{"line_number":2970,"context_line":""},{"line_number":2971,"context_line":"        Any non-200 answer counts, not only the 400 that _raise_if_failed"},{"line_number":2972,"context_line":"        handles, since the destination would otherwise lack the data."}],"source_content_type":"text/x-python","patch_set":3,"id":"94faa0fb_8fdb2f08","line":2969,"updated":"2026-11-10 19:21:09.000000000","message":"The premise in this docstring is not right: when the hook returns `False` the manager runs `_copy_volume_data`, so the destination does receive the data, through the host. If the hook is changed to fall back (see `pure.py` L3039), this test should `assertFalse`, keep the 400/409 loop, and check that `post_volumes` was attempted and `patch_volumes` was not called; if the raise stays, the docstring should at least drop the \"would otherwise lack the data\" justification.","commit_id":"8f92868304d01c0632fd4e3cf50a48b4d3c44620"}],"cinder/volume/drivers/pure.py":[{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"7be7f1a12e0efbb0c9620d9f61f791387beb1015","unresolved":true,"context_lines":[{"line_number":3000,"context_line":"            return False"},{"line_number":3001,"context_line":""},{"line_number":3002,"context_line":"        # The generic copy would not fit a larger source either, let it"},{"line_number":3003,"context_line":"        # report the error instead of shrinking the destination here."},{"line_number":3004,"context_line":"        if src_vol[\u0027size\u0027] \u003e dest_vol[\u0027size\u0027]:"},{"line_number":3005,"context_line":"            LOG.info(\"Source %(src)s (%(src_size)s GiB) is larger than \""},{"line_number":3006,"context_line":"                     \"destination %(dest)s (%(dest_size)s GiB), using \""}],"source_content_type":"text/x-python","patch_set":3,"id":"408c534c_88a4dfce","line":3003,"updated":"2026-11-10 19:21:09.000000000","message":"Nit: an overwrite from a larger source would grow the destination to the source size rather than shrink it, so \"instead of shrinking the destination here\" reads the wrong way round. The branch is unreachable in the series flow, so this is a comment fix only.","commit_id":"8f92868304d01c0632fd4e3cf50a48b4d3c44620"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"7be7f1a12e0efbb0c9620d9f61f791387beb1015","unresolved":true,"context_lines":[{"line_number":3016,"context_line":"        # are on that array; it also holds if the two backends reach the"},{"line_number":3017,"context_line":"        # array through different management addresses."},{"line_number":3018,"context_line":"        current_array \u003d self._get_current_array(volume\u003dsrc_vol)"},{"line_number":3019,"context_line":"        res \u003d current_array.get_volumes(names\u003d[dest_name])"},{"line_number":3020,"context_line":"        dest_info \u003d list(res.items)[0] if res.status_code \u003d\u003d 200 else None"},{"line_number":3021,"context_line":"        if dest_info is None or getattr(dest_info, \u0027destroyed\u0027, False):"},{"line_number":3022,"context_line":"            LOG.info(\"Destination %(dest)s (%(dest_name)s) is not on array \""}],"source_content_type":"text/x-python","patch_set":3,"id":"bf26b58b_60187945","line":3019,"updated":"2026-11-10 19:21:09.000000000","message":"Nit: a 5xx/401/403 from `get_volumes` and a `destroyed\u003dTrue` namesake both land in the \"Destination ... is not on array\" message at 3022. The fallback is the right outcome for both, but the message is wrong for both and the error details are dropped; `logging _get_error_details(res)` when status_code !\u003d 200 and saying \"destroyed\" when that is the reason would make the fallback diagnosable.","commit_id":"8f92868304d01c0632fd4e3cf50a48b4d3c44620"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"7be7f1a12e0efbb0c9620d9f61f791387beb1015","unresolved":true,"context_lines":[{"line_number":3036,"context_line":"        # _raise_if_failed only catches 400. Here any other failure would"},{"line_number":3037,"context_line":"        # leave the destination silently without the source data, so treat"},{"line_number":3038,"context_line":"        # every non-200 answer as a failed copy."},{"line_number":3039,"context_line":"        if res.status_code !\u003d 200:"},{"line_number":3040,"context_line":"            raise PureDriverException("},{"line_number":3041,"context_line":"                reason\u003d_(\"Unable to copy %(src)s into %(dest)s: %(err)s\") %"},{"line_number":3042,"context_line":"                {\"src\": src_name, \"dest\": dest_name,"}],"source_content_type":"text/x-python","patch_set":3,"id":"017ee3d4_19c4e98c","line":3039,"updated":"2026-11-10 19:21:09.000000000","message":"Returning `False` here is not a silent failure: the manager runs `_copy_volume_data` whenever the hook declines, so the destination still gets the data, only via the host. Raising instead escapes `_copy_volume_data_with_driver_assist` (it catches only `NotImplementedError`), rolls back every staged snapshot and the destination volume, and fails the retype. On a FlashArray an overwrite can be refused for container-state reasons that cannot be screened up front (during testing a root-to-pod overwrite was refused with \"Cannot copy a volume into a promoted source\" while the target was left untouched), so please `LOG.warning` with `_get_error_details(res)`, the two Purity names and the array name, and return `False`; the docstring at L2981 already says the hook returns `False` whenever the copy cannot be done on the array alone. The regrow failure below is different — the destination has been modified by then — and raising there is right.","commit_id":"8f92868304d01c0632fd4e3cf50a48b4d3c44620"}],"releasenotes/notes/pure-snapshot-retype-array-copy-3c9e1f0b7a2d4e65.yaml":[{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"7be7f1a12e0efbb0c9620d9f61f791387beb1015","unresolved":true,"context_lines":[{"line_number":1,"context_line":"---"},{"line_number":2,"context_line":"features:"},{"line_number":3,"context_line":"  - |"},{"line_number":4,"context_line":"    [Pure Storage] When a volume is retyped together with its snapshots to"},{"line_number":5,"context_line":"    another backend on the same FlashArray, the FlashArray driver now copies"},{"line_number":6,"context_line":"    the snapshot data and, for a detached volume, the volume data on the"},{"line_number":7,"context_line":"    array instead of through the cinder-volume host. The copy falls back to"}],"source_content_type":"text/x-yaml","patch_set":3,"id":"070104be_0bb772b2","line":4,"updated":"2026-11-10 19:21:09.000000000","message":"Nit: same as on the first change, please use \"[Everpure]\" rather than \"[Pure Storage]\".","commit_id":"8f92868304d01c0632fd4e3cf50a48b4d3c44620"},{"author":{"_account_id":13425,"name":"Simon Dodsley","email":"simon@everpuredata.com","username":"sdodsley"},"change_message_id":"7be7f1a12e0efbb0c9620d9f61f791387beb1015","unresolved":true,"context_lines":[{"line_number":5,"context_line":"    another backend on the same FlashArray, the FlashArray driver now copies"},{"line_number":6,"context_line":"    the snapshot data and, for a detached volume, the volume data on the"},{"line_number":7,"context_line":"    array instead of through the cinder-volume host. The copy falls back to"},{"line_number":8,"context_line":"    the host-assisted copy when the destination is on a different array or"},{"line_number":9,"context_line":"    when the encryption of the volume changes. For an attached volume the"},{"line_number":10,"context_line":"    volume data is still copied by Nova."}],"source_content_type":"text/x-yaml","patch_set":3,"id":"fb33bc9a_bbd5c312","line":8,"updated":"2026-11-10 19:21:09.000000000","message":"Nit: if the `raise` at `pure.py` L3039 stays, this note should also say that a refused array-side overwrite fails the retype rather than falling back, since that is what operators will see; if the hook is changed to fall back, the sentence is fine as written.","commit_id":"8f92868304d01c0632fd4e3cf50a48b4d3c44620"}]}
