From b85caca74a90a9b9215c9c8a4a6b868f8f300952 Mon Sep 17 00:00:00 2001 From: Patrick East Date: Tue, 2 Feb 2016 11:08:39 -0800 Subject: [PATCH] Allow for eradicating Pure volumes on Cinder delete This change adds in a config option which, when enabled, will cause the Pure Storage volume drivers to eradicate volumes, snapshots, and protection groups on delete for their Cinder counterpart. With this config enabled an admin will NOT be able to recover data once objects are deleted. By default the new config option is disabled and volumes, snapshots, and protection group snapshots will be retained pending eradication where they can be recovered for a period of time. DocImpact Closes-Bug: #1537187 Change-Id: Ib3656bb64e5104f6bd05d28535cf91a46a038b55 --- cinder/tests/unit/test_pure.py | 43 ++++++++++++++++++- cinder/volume/drivers/pure.py | 20 ++++++++- ...-eradicate-on-delete-1e15e1440d5cd4d6.yaml | 9 ++++ 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/pure-eradicate-on-delete-1e15e1440d5cd4d6.yaml diff --git a/cinder/tests/unit/test_pure.py b/cinder/tests/unit/test_pure.py index b35d76e077e..24d02dc883c 100644 --- a/cinder/tests/unit/test_pure.py +++ b/cinder/tests/unit/test_pure.py @@ -338,8 +338,8 @@ class PureDriverTestCase(test.TestCase): self.mock_config.pure_api_token = API_TOKEN self.mock_config.volume_backend_name = VOLUME_BACKEND_NAME self.mock_config.safe_get.return_value = None + self.mock_config.pure_eradicate_on_delete = False self.array = mock.Mock() - self.array self.array.get.return_value = GET_ARRAY_PRIMARY self.array.array_name = GET_ARRAY_PRIMARY["array_name"] self.array.array_id = GET_ARRAY_PRIMARY["id"] @@ -650,6 +650,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase): ) self.driver.delete_volume(VOLUME) self.assertFalse(self.array.destroy_volume.called) + self.assertFalse(self.array.eradicate_volume.called) # Testing case where array.destroy_volume returns an exception # because volume has already been deleted @@ -662,6 +663,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase): ) self.driver.delete_volume(VOLUME) self.assertTrue(self.array.destroy_volume.called) + self.assertFalse(self.array.eradicate_volume.called) def test_delete_volume(self): vol_name = VOLUME["name"] + "-cinder" @@ -669,6 +671,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase): self.driver.delete_volume(VOLUME) expected = [mock.call.destroy_volume(vol_name)] self.array.assert_has_calls(expected) + self.assertFalse(self.array.eradicate_volume.called) self.array.destroy_volume.side_effect = ( self.purestorage_module.PureHTTPError(code=400, text="does not " "exist")) @@ -677,6 +680,15 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase): self.assert_error_propagates([self.array.destroy_volume], self.driver.delete_volume, VOLUME) + def test_delete_volume_eradicate_now(self): + vol_name = VOLUME["name"] + "-cinder" + self.array.list_volume_private_connections.return_value = {} + self.mock_config.pure_eradicate_on_delete = True + self.driver.delete_volume(VOLUME) + expected = [mock.call.destroy_volume(vol_name), + mock.call.eradicate_volume(vol_name)] + self.array.assert_has_calls(expected) + def test_delete_connected_volume(self): vol_name = VOLUME["name"] + "-cinder" host_name_a = "ha" @@ -715,6 +727,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase): self.driver.delete_snapshot(SNAPSHOT) expected = [mock.call.destroy_volume(snap_name)] self.array.assert_has_calls(expected) + self.assertFalse(self.array.eradicate_volume.called) self.array.destroy_volume.side_effect = ( self.purestorage_module.PureHTTPError(code=400, text="does not " "exist")) @@ -723,6 +736,14 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase): self.assert_error_propagates([self.array.destroy_volume], self.driver.delete_snapshot, SNAPSHOT) + def test_delete_snapshot_eradicate_now(self): + snap_name = SNAPSHOT["volume_name"] + "-cinder." + SNAPSHOT["name"] + self.mock_config.pure_eradicate_on_delete = True + self.driver.delete_snapshot(SNAPSHOT) + expected = [mock.call.destroy_volume(snap_name), + mock.call.eradicate_volume(snap_name)] + self.array.assert_has_calls(expected) + @mock.patch(BASE_DRIVER_OBJ + "._get_host", autospec=True) def test_terminate_connection(self, mock_host): vol_name = VOLUME["name"] + "-cinder" @@ -967,6 +988,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase): expected_name = self.driver._get_pgroup_name_from_id(mock_cgroup.id) self.array.destroy_pgroup.assert_called_with(expected_name) + self.assertFalse(self.array.eradicate_pgroup.called) expected_volume_updates = [{ 'id': mock_volume.id, @@ -985,6 +1007,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase): mock_cgroup, [mock_volume]) self.array.destroy_pgroup.assert_called_with(expected_name) + self.assertFalse(self.array.eradicate_pgroup.called) mock_delete_volume.assert_called_with(self.driver, mock_volume) self.array.destroy_pgroup.side_effect = \ @@ -996,6 +1019,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase): mock_cgroup, [mock_volume]) self.array.destroy_pgroup.assert_called_with(expected_name) + self.assertFalse(self.array.eradicate_pgroup.called) mock_delete_volume.assert_called_with(self.driver, mock_volume) self.array.destroy_pgroup.side_effect = \ @@ -1146,6 +1170,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase): [mock_snap]) self.array.destroy_pgroup.assert_called_with(snap_name) + self.assertFalse(self.array.eradicate_pgroup.called) self.assertEqual({'status': mock_cgsnap.status}, model_update) expected_snapshot_update = [{ @@ -1161,6 +1186,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase): ) self.driver.delete_cgsnapshot(mock_context, mock_cgsnap, [mock_snap]) self.array.destroy_pgroup.assert_called_with(snap_name) + self.assertFalse(self.array.eradicate_pgroup.called) self.array.destroy_pgroup.side_effect = \ self.purestorage_module.PureHTTPError( @@ -1169,6 +1195,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase): ) self.driver.delete_cgsnapshot(mock_context, mock_cgsnap, [mock_snap]) self.array.destroy_pgroup.assert_called_with(snap_name) + self.assertFalse(self.array.eradicate_pgroup.called) self.array.destroy_pgroup.side_effect = \ self.purestorage_module.PureHTTPError( @@ -1202,6 +1229,20 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase): [mock_snap] ) + @mock.patch(BASE_DRIVER_OBJ + "._get_pgroup_snap_name", + spec=pure.PureBaseVolumeDriver._get_pgroup_snap_name) + def test_delete_cgsnapshot_eradicate_now(self, mock_get_snap_name): + snap_name = "consisgroup-4a2f7e3a-312a-40c5-96a8-536b8a0f" \ + "e074-cinder.4a2f7e3a-312a-40c5-96a8-536b8a0fe075" + mock_get_snap_name.return_value = snap_name + self.mock_config.pure_eradicate_on_delete = True + model_update, snapshots = self.driver.delete_cgsnapshot(mock.Mock(), + mock.Mock(), + [mock.Mock()]) + + self.array.destroy_pgroup.assert_called_once_with(snap_name) + self.array.eradicate_pgroup.assert_called_once_with(snap_name) + def test_manage_existing(self): ref_name = 'vol1' volume_ref = {'name': ref_name} diff --git a/cinder/volume/drivers/pure.py b/cinder/volume/drivers/pure.py index d329310f488..f207654b0e9 100644 --- a/cinder/volume/drivers/pure.py +++ b/cinder/volume/drivers/pure.py @@ -66,6 +66,15 @@ PURE_OPTS = [ cfg.IntOpt("pure_replica_retention_long_term_default", default=7, help="Retain snapshots per day on target for this time " "(in days.)"), + cfg.BoolOpt("pure_eradicate_on_delete", + default=False, + help="When enabled, all Pure volumes, snapshots, and " + "protection groups will be eradicated at the time of " + "deletion in Cinder. Data will NOT be recoverable after " + "a delete with this set to True! When disabled, volumes " + "and snapshots will go into pending eradication state " + "and can be recovered." + ) ] CONF = cfg.CONF @@ -348,6 +357,8 @@ class PureBaseVolumeDriver(san.SanDriver): host_name = host_info["host"] self._disconnect_host(current_array, host_name, vol_name) current_array.destroy_volume(vol_name) + if self.configuration.pure_eradicate_on_delete: + current_array.eradicate_volume(vol_name) except purestorage.PureHTTPError as err: with excutils.save_and_reraise_exception() as ctxt: if (err.code == 400 and @@ -377,6 +388,8 @@ class PureBaseVolumeDriver(san.SanDriver): snap_name = self._get_snap_name(snapshot) try: current_array.destroy_volume(snap_name) + if self.configuration.pure_eradicate_on_delete: + current_array.eradicate_volume(snap_name) except purestorage.PureHTTPError as err: with excutils.save_and_reraise_exception() as ctxt: if err.code == 400 and ( @@ -654,7 +667,10 @@ class PureBaseVolumeDriver(san.SanDriver): """Deletes a consistency group.""" try: - self._array.destroy_pgroup(self._get_pgroup_name_from_id(group.id)) + pgroup_name = self._get_pgroup_name_from_id(group.id) + self._array.destroy_pgroup(pgroup_name) + if self.configuration.pure_eradicate_on_delete: + self._array.eradicate_pgroup(pgroup_name) except purestorage.PureHTTPError as err: with excutils.save_and_reraise_exception() as ctxt: if (err.code == 400 and @@ -724,6 +740,8 @@ class PureBaseVolumeDriver(san.SanDriver): # FlashArray.destroy_pgroup is also used for deleting # pgroup snapshots. The underlying REST API is identical. self._array.destroy_pgroup(pgsnap_name) + if self.configuration.pure_eradicate_on_delete: + self._array.eradicate_pgroup(pgsnap_name) except purestorage.PureHTTPError as err: with excutils.save_and_reraise_exception() as ctxt: if (err.code == 400 and diff --git a/releasenotes/notes/pure-eradicate-on-delete-1e15e1440d5cd4d6.yaml b/releasenotes/notes/pure-eradicate-on-delete-1e15e1440d5cd4d6.yaml new file mode 100644 index 00000000000..b93f003c173 --- /dev/null +++ b/releasenotes/notes/pure-eradicate-on-delete-1e15e1440d5cd4d6.yaml @@ -0,0 +1,9 @@ +--- +features: + - New config option for Pure Storage volume drivers pure_eradicate_on_delete. + When enabled will permanantly eradicate data instead of placing into + pending eradication state. +fixes: + - Allow for eradicating Pure Storage volumes, snapshots, and pgroups when + deleting their Cinder counterpart. +