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
This commit is contained in:
parent
4cfed22bf8
commit
b85caca74a
@ -338,8 +338,8 @@ class PureDriverTestCase(test.TestCase):
|
|||||||
self.mock_config.pure_api_token = API_TOKEN
|
self.mock_config.pure_api_token = API_TOKEN
|
||||||
self.mock_config.volume_backend_name = VOLUME_BACKEND_NAME
|
self.mock_config.volume_backend_name = VOLUME_BACKEND_NAME
|
||||||
self.mock_config.safe_get.return_value = None
|
self.mock_config.safe_get.return_value = None
|
||||||
|
self.mock_config.pure_eradicate_on_delete = False
|
||||||
self.array = mock.Mock()
|
self.array = mock.Mock()
|
||||||
self.array
|
|
||||||
self.array.get.return_value = GET_ARRAY_PRIMARY
|
self.array.get.return_value = GET_ARRAY_PRIMARY
|
||||||
self.array.array_name = GET_ARRAY_PRIMARY["array_name"]
|
self.array.array_name = GET_ARRAY_PRIMARY["array_name"]
|
||||||
self.array.array_id = GET_ARRAY_PRIMARY["id"]
|
self.array.array_id = GET_ARRAY_PRIMARY["id"]
|
||||||
@ -650,6 +650,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
|||||||
)
|
)
|
||||||
self.driver.delete_volume(VOLUME)
|
self.driver.delete_volume(VOLUME)
|
||||||
self.assertFalse(self.array.destroy_volume.called)
|
self.assertFalse(self.array.destroy_volume.called)
|
||||||
|
self.assertFalse(self.array.eradicate_volume.called)
|
||||||
|
|
||||||
# Testing case where array.destroy_volume returns an exception
|
# Testing case where array.destroy_volume returns an exception
|
||||||
# because volume has already been deleted
|
# because volume has already been deleted
|
||||||
@ -662,6 +663,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
|||||||
)
|
)
|
||||||
self.driver.delete_volume(VOLUME)
|
self.driver.delete_volume(VOLUME)
|
||||||
self.assertTrue(self.array.destroy_volume.called)
|
self.assertTrue(self.array.destroy_volume.called)
|
||||||
|
self.assertFalse(self.array.eradicate_volume.called)
|
||||||
|
|
||||||
def test_delete_volume(self):
|
def test_delete_volume(self):
|
||||||
vol_name = VOLUME["name"] + "-cinder"
|
vol_name = VOLUME["name"] + "-cinder"
|
||||||
@ -669,6 +671,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
|||||||
self.driver.delete_volume(VOLUME)
|
self.driver.delete_volume(VOLUME)
|
||||||
expected = [mock.call.destroy_volume(vol_name)]
|
expected = [mock.call.destroy_volume(vol_name)]
|
||||||
self.array.assert_has_calls(expected)
|
self.array.assert_has_calls(expected)
|
||||||
|
self.assertFalse(self.array.eradicate_volume.called)
|
||||||
self.array.destroy_volume.side_effect = (
|
self.array.destroy_volume.side_effect = (
|
||||||
self.purestorage_module.PureHTTPError(code=400, text="does not "
|
self.purestorage_module.PureHTTPError(code=400, text="does not "
|
||||||
"exist"))
|
"exist"))
|
||||||
@ -677,6 +680,15 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
|||||||
self.assert_error_propagates([self.array.destroy_volume],
|
self.assert_error_propagates([self.array.destroy_volume],
|
||||||
self.driver.delete_volume, 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):
|
def test_delete_connected_volume(self):
|
||||||
vol_name = VOLUME["name"] + "-cinder"
|
vol_name = VOLUME["name"] + "-cinder"
|
||||||
host_name_a = "ha"
|
host_name_a = "ha"
|
||||||
@ -715,6 +727,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
|||||||
self.driver.delete_snapshot(SNAPSHOT)
|
self.driver.delete_snapshot(SNAPSHOT)
|
||||||
expected = [mock.call.destroy_volume(snap_name)]
|
expected = [mock.call.destroy_volume(snap_name)]
|
||||||
self.array.assert_has_calls(expected)
|
self.array.assert_has_calls(expected)
|
||||||
|
self.assertFalse(self.array.eradicate_volume.called)
|
||||||
self.array.destroy_volume.side_effect = (
|
self.array.destroy_volume.side_effect = (
|
||||||
self.purestorage_module.PureHTTPError(code=400, text="does not "
|
self.purestorage_module.PureHTTPError(code=400, text="does not "
|
||||||
"exist"))
|
"exist"))
|
||||||
@ -723,6 +736,14 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
|||||||
self.assert_error_propagates([self.array.destroy_volume],
|
self.assert_error_propagates([self.array.destroy_volume],
|
||||||
self.driver.delete_snapshot, SNAPSHOT)
|
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)
|
@mock.patch(BASE_DRIVER_OBJ + "._get_host", autospec=True)
|
||||||
def test_terminate_connection(self, mock_host):
|
def test_terminate_connection(self, mock_host):
|
||||||
vol_name = VOLUME["name"] + "-cinder"
|
vol_name = VOLUME["name"] + "-cinder"
|
||||||
@ -967,6 +988,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
|||||||
|
|
||||||
expected_name = self.driver._get_pgroup_name_from_id(mock_cgroup.id)
|
expected_name = self.driver._get_pgroup_name_from_id(mock_cgroup.id)
|
||||||
self.array.destroy_pgroup.assert_called_with(expected_name)
|
self.array.destroy_pgroup.assert_called_with(expected_name)
|
||||||
|
self.assertFalse(self.array.eradicate_pgroup.called)
|
||||||
|
|
||||||
expected_volume_updates = [{
|
expected_volume_updates = [{
|
||||||
'id': mock_volume.id,
|
'id': mock_volume.id,
|
||||||
@ -985,6 +1007,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
|||||||
mock_cgroup,
|
mock_cgroup,
|
||||||
[mock_volume])
|
[mock_volume])
|
||||||
self.array.destroy_pgroup.assert_called_with(expected_name)
|
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)
|
mock_delete_volume.assert_called_with(self.driver, mock_volume)
|
||||||
|
|
||||||
self.array.destroy_pgroup.side_effect = \
|
self.array.destroy_pgroup.side_effect = \
|
||||||
@ -996,6 +1019,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
|||||||
mock_cgroup,
|
mock_cgroup,
|
||||||
[mock_volume])
|
[mock_volume])
|
||||||
self.array.destroy_pgroup.assert_called_with(expected_name)
|
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)
|
mock_delete_volume.assert_called_with(self.driver, mock_volume)
|
||||||
|
|
||||||
self.array.destroy_pgroup.side_effect = \
|
self.array.destroy_pgroup.side_effect = \
|
||||||
@ -1146,6 +1170,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
|||||||
[mock_snap])
|
[mock_snap])
|
||||||
|
|
||||||
self.array.destroy_pgroup.assert_called_with(snap_name)
|
self.array.destroy_pgroup.assert_called_with(snap_name)
|
||||||
|
self.assertFalse(self.array.eradicate_pgroup.called)
|
||||||
self.assertEqual({'status': mock_cgsnap.status}, model_update)
|
self.assertEqual({'status': mock_cgsnap.status}, model_update)
|
||||||
|
|
||||||
expected_snapshot_update = [{
|
expected_snapshot_update = [{
|
||||||
@ -1161,6 +1186,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
|||||||
)
|
)
|
||||||
self.driver.delete_cgsnapshot(mock_context, mock_cgsnap, [mock_snap])
|
self.driver.delete_cgsnapshot(mock_context, mock_cgsnap, [mock_snap])
|
||||||
self.array.destroy_pgroup.assert_called_with(snap_name)
|
self.array.destroy_pgroup.assert_called_with(snap_name)
|
||||||
|
self.assertFalse(self.array.eradicate_pgroup.called)
|
||||||
|
|
||||||
self.array.destroy_pgroup.side_effect = \
|
self.array.destroy_pgroup.side_effect = \
|
||||||
self.purestorage_module.PureHTTPError(
|
self.purestorage_module.PureHTTPError(
|
||||||
@ -1169,6 +1195,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
|||||||
)
|
)
|
||||||
self.driver.delete_cgsnapshot(mock_context, mock_cgsnap, [mock_snap])
|
self.driver.delete_cgsnapshot(mock_context, mock_cgsnap, [mock_snap])
|
||||||
self.array.destroy_pgroup.assert_called_with(snap_name)
|
self.array.destroy_pgroup.assert_called_with(snap_name)
|
||||||
|
self.assertFalse(self.array.eradicate_pgroup.called)
|
||||||
|
|
||||||
self.array.destroy_pgroup.side_effect = \
|
self.array.destroy_pgroup.side_effect = \
|
||||||
self.purestorage_module.PureHTTPError(
|
self.purestorage_module.PureHTTPError(
|
||||||
@ -1202,6 +1229,20 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
|||||||
[mock_snap]
|
[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):
|
def test_manage_existing(self):
|
||||||
ref_name = 'vol1'
|
ref_name = 'vol1'
|
||||||
volume_ref = {'name': ref_name}
|
volume_ref = {'name': ref_name}
|
||||||
|
@ -66,6 +66,15 @@ PURE_OPTS = [
|
|||||||
cfg.IntOpt("pure_replica_retention_long_term_default", default=7,
|
cfg.IntOpt("pure_replica_retention_long_term_default", default=7,
|
||||||
help="Retain snapshots per day on target for this time "
|
help="Retain snapshots per day on target for this time "
|
||||||
"(in days.)"),
|
"(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
|
CONF = cfg.CONF
|
||||||
@ -348,6 +357,8 @@ class PureBaseVolumeDriver(san.SanDriver):
|
|||||||
host_name = host_info["host"]
|
host_name = host_info["host"]
|
||||||
self._disconnect_host(current_array, host_name, vol_name)
|
self._disconnect_host(current_array, host_name, vol_name)
|
||||||
current_array.destroy_volume(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:
|
except purestorage.PureHTTPError as err:
|
||||||
with excutils.save_and_reraise_exception() as ctxt:
|
with excutils.save_and_reraise_exception() as ctxt:
|
||||||
if (err.code == 400 and
|
if (err.code == 400 and
|
||||||
@ -377,6 +388,8 @@ class PureBaseVolumeDriver(san.SanDriver):
|
|||||||
snap_name = self._get_snap_name(snapshot)
|
snap_name = self._get_snap_name(snapshot)
|
||||||
try:
|
try:
|
||||||
current_array.destroy_volume(snap_name)
|
current_array.destroy_volume(snap_name)
|
||||||
|
if self.configuration.pure_eradicate_on_delete:
|
||||||
|
current_array.eradicate_volume(snap_name)
|
||||||
except purestorage.PureHTTPError as err:
|
except purestorage.PureHTTPError as err:
|
||||||
with excutils.save_and_reraise_exception() as ctxt:
|
with excutils.save_and_reraise_exception() as ctxt:
|
||||||
if err.code == 400 and (
|
if err.code == 400 and (
|
||||||
@ -654,7 +667,10 @@ class PureBaseVolumeDriver(san.SanDriver):
|
|||||||
"""Deletes a consistency group."""
|
"""Deletes a consistency group."""
|
||||||
|
|
||||||
try:
|
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:
|
except purestorage.PureHTTPError as err:
|
||||||
with excutils.save_and_reraise_exception() as ctxt:
|
with excutils.save_and_reraise_exception() as ctxt:
|
||||||
if (err.code == 400 and
|
if (err.code == 400 and
|
||||||
@ -724,6 +740,8 @@ class PureBaseVolumeDriver(san.SanDriver):
|
|||||||
# FlashArray.destroy_pgroup is also used for deleting
|
# FlashArray.destroy_pgroup is also used for deleting
|
||||||
# pgroup snapshots. The underlying REST API is identical.
|
# pgroup snapshots. The underlying REST API is identical.
|
||||||
self._array.destroy_pgroup(pgsnap_name)
|
self._array.destroy_pgroup(pgsnap_name)
|
||||||
|
if self.configuration.pure_eradicate_on_delete:
|
||||||
|
self._array.eradicate_pgroup(pgsnap_name)
|
||||||
except purestorage.PureHTTPError as err:
|
except purestorage.PureHTTPError as err:
|
||||||
with excutils.save_and_reraise_exception() as ctxt:
|
with excutils.save_and_reraise_exception() as ctxt:
|
||||||
if (err.code == 400 and
|
if (err.code == 400 and
|
||||||
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user