]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Allow for eradicating Pure volumes on Cinder delete
authorPatrick East <patrick.east@purestorage.com>
Tue, 2 Feb 2016 19:08:39 +0000 (11:08 -0800)
committerPatrick East <patrick.east@purestorage.com>
Thu, 4 Feb 2016 23:35:52 +0000 (15:35 -0800)
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
cinder/volume/drivers/pure.py
releasenotes/notes/pure-eradicate-on-delete-1e15e1440d5cd4d6.yaml [new file with mode: 0644]

index b35d76e077e21382e305939bda470657263de4f0..24d02dc883c806c16815cfcb3835830c9339fcea 100644 (file)
@@ -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}
index d329310f488f769eb44e3ca96e37ea1c920e004b..f207654b0e9f4719e7aa1f35e4ebd919e483e9bb 100644 (file)
@@ -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 (file)
index 0000000..b93f003
--- /dev/null
@@ -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.
+