diff --git a/cinder/tests/unit/volume/drivers/dell_emc/vxflexos/test_revert_volume_to_snapshot.py b/cinder/tests/unit/volume/drivers/dell_emc/vxflexos/test_revert_volume_to_snapshot.py new file mode 100644 index 00000000000..5ae59067b5b --- /dev/null +++ b/cinder/tests/unit/volume/drivers/dell_emc/vxflexos/test_revert_volume_to_snapshot.py @@ -0,0 +1,104 @@ +# Copyright (c) 2020 Dell Inc. or its subsidiaries. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from unittest import mock + +from cinder import context +from cinder import exception +from cinder.tests.unit import fake_constants as fake +from cinder.tests.unit import fake_snapshot +from cinder.tests.unit import fake_volume +from cinder.tests.unit.volume.drivers.dell_emc import vxflexos + + +class TestRevertVolume(vxflexos.TestVxFlexOSDriver): + """Test cases for ``VxFlexOSDriver.revert_to_snapshot()``""" + + def setUp(self): + """Setup a test case environment. + + Creates a fake volume object and sets up the required API responses. + """ + + super(TestRevertVolume, self).setUp() + ctx = context.RequestContext('fake', 'fake', auth_token=True) + host = 'host@backend#{}:{}'.format( + self.PROT_DOMAIN_NAME, + self.STORAGE_POOL_NAME) + self.volume = fake_volume.fake_volume_obj( + ctx, **{'provider_id': fake.PROVIDER_ID, 'host': host, + 'volume_type_id': fake.VOLUME_TYPE_ID, + 'size': 8}) + self.snapshot = fake_snapshot.fake_snapshot_obj( + ctx, **{'volume_id': self.volume.id, + 'volume_size': self.volume.size} + ) + self.HTTPS_MOCK_RESPONSES = { + self.RESPONSE_MODE.Valid: { + 'instances/Volume::{}/action/overwriteVolumeContent'.format( + self.volume.provider_id + ): {}, + }, + self.RESPONSE_MODE.Invalid: { + 'version': "2.6", + }, + self.RESPONSE_MODE.BadStatus: { + 'instances/Volume::{}/action/overwriteVolumeContent'.format( + self.volume.provider_id + ): self.BAD_STATUS_RESPONSE + }, + } + + self.volume_is_replicated_mock = self.mock_object( + self.volume, 'is_replicated', + return_value=False + ) + + def test_revert_to_snapshot(self): + self.driver.revert_to_snapshot(None, self.volume, self.snapshot) + + def test_revert_to_snapshot_badstatus_response(self): + self.set_https_response_mode(self.RESPONSE_MODE.BadStatus) + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.revert_to_snapshot, + None, self.volume, self.snapshot) + + def test_revert_to_snapshot_use_generic(self): + self.set_https_response_mode(self.RESPONSE_MODE.Invalid) + self.assertRaises(NotImplementedError, + self.driver.revert_to_snapshot, + None, self.volume, self.snapshot) + + def test_revert_to_snapshot_replicated_volume(self): + self.volume_is_replicated_mock.return_value = True + self.assertRaisesRegexp( + exception.InvalidVolume, + 'Reverting replicated volume is not allowed.', + self.driver.revert_to_snapshot, + None, self.volume, self.snapshot + ) + + def test_revert_to_snapshot_size_not_equal(self): + patched_volume = mock.MagicMock() + patched_volume.id = self.volume.id + patched_volume.size = 16 + patched_volume.is_replicated.return_value = False + self.assertRaisesRegexp( + exception.InvalidVolume, + ('Volume %s size is not equal to snapshot %s size.' % + (self.volume.id, self.snapshot.id)), + self.driver.revert_to_snapshot, + None, patched_volume, self.snapshot + ) diff --git a/cinder/volume/drivers/dell_emc/vxflexos/driver.py b/cinder/volume/drivers/dell_emc/vxflexos/driver.py index dfd46fcbbd3..2aecdc4d22d 100644 --- a/cinder/volume/drivers/dell_emc/vxflexos/driver.py +++ b/cinder/volume/drivers/dell_emc/vxflexos/driver.py @@ -89,9 +89,10 @@ class VxFlexOSDriver(driver.VolumeDriver): 3.5.0 - Add support for VxFlex OS 3.5.x 3.5.1 - Add volume replication v2.1 support for VxFlex OS 3.5.x 3.5.2 - Add volume migration support + 3.5.3 - Add revert volume to snapshot support """ - VERSION = "3.5.2" + VERSION = "3.5.3" # ThirdPartySystems wiki CI_WIKI_NAME = "DellEMC_VxFlexOS_CI" @@ -1502,6 +1503,33 @@ class VxFlexOSDriver(driver.VolumeDriver): location = new_volume.provider_location return {"_name_id": name_id, "provider_location": location} + def revert_to_snapshot(self, context, volume, snapshot): + """Revert VxFlex OS volume to the specified snapshot.""" + + LOG.info("Revert volume %(vol_id)s to snapshot %(snap_id)s.", + {"vol_id": volume.id, "snap_id": snapshot.id}) + + client = self._get_client() + + if not flex_utils.version_gte(client.query_rest_api_version(), "3.0"): + LOG.debug("VxFlex OS versions less than v3.0 do not " + "support reverting volume to snapshot. " + "Falling back to generic revert to snapshot method.") + raise NotImplementedError + elif volume.is_replicated(): + msg = _("Reverting replicated volume is not allowed.") + LOG.error(msg) + raise exception.InvalidVolume(reason=msg) + elif snapshot.volume_size != volume.size: + msg = (_("Volume %(vol_id)s size is not equal to snapshot " + "%(snap_id)s size. Revert to snapshot operation is not " + "allowed.") % + {"vol_id": volume.id, "snap_id": snapshot.id}) + LOG.error(msg) + raise exception.InvalidVolume(reason=msg) + + client.overwrite_volume_content(volume, snapshot) + def _query_vxflexos_volume(self, volume, existing_ref): type_id = volume.get("volume_type_id") if "source-id" not in existing_ref: diff --git a/cinder/volume/drivers/dell_emc/vxflexos/rest_client.py b/cinder/volume/drivers/dell_emc/vxflexos/rest_client.py index 3d6ed76ee47..da65f673f71 100644 --- a/cinder/volume/drivers/dell_emc/vxflexos/rest_client.py +++ b/cinder/volume/drivers/dell_emc/vxflexos/rest_client.py @@ -686,3 +686,20 @@ class RestClient(object): LOG.error(msg) raise exception.VolumeBackendAPIException(msg) return response + + def overwrite_volume_content(self, volume, snapshot): + url = "/instances/Volume::%(vol_id)s/action/overwriteVolumeContent" + + params = {"srcVolumeId": snapshot.provider_id} + r, response = self.execute_vxflexos_post_request( + url, + params=params, + vol_id=volume.provider_id + ) + if r.status_code != http_client.OK: + msg = (_("Failed to revert volume %(vol_id)s to snapshot " + "%(snap_id)s: %(err)s.") % {"vol_id": volume.id, + "snap_id": snapshot.id, + "err": response["message"]}) + LOG.error(msg) + raise exception.VolumeBackendAPIException(msg) diff --git a/doc/source/configuration/block-storage/drivers/dell-emc-vxflex-driver.rst b/doc/source/configuration/block-storage/drivers/dell-emc-vxflex-driver.rst index 07e77c3bea0..b4bc8373216 100644 --- a/doc/source/configuration/block-storage/drivers/dell-emc-vxflex-driver.rst +++ b/doc/source/configuration/block-storage/drivers/dell-emc-vxflex-driver.rst @@ -73,6 +73,8 @@ Supported operations * Create a volume from a snapshot +* Revert a volume to a snapshot + * Copy an image to a volume * Copy a volume to an image diff --git a/doc/source/reference/support-matrix.ini b/doc/source/reference/support-matrix.ini index 3f803ffb7ca..e8bdc4fdad9 100644 --- a/doc/source/reference/support-matrix.ini +++ b/doc/source/reference/support-matrix.ini @@ -774,7 +774,7 @@ driver.dell_emc_unity=complete driver.dell_emc_vmax_af=complete driver.dell_emc_vmax_3=complete driver.dell_emc_vnx=complete -driver.dell_emc_vxflexos=missing +driver.dell_emc_vxflexos=complete driver.dell_emc_xtremio=missing driver.fujitsu_eternus=missing driver.hpe_3par=complete diff --git a/releasenotes/notes/vxflexos-revert-to-snapshot-a90c40ec476cc2bd.yaml b/releasenotes/notes/vxflexos-revert-to-snapshot-a90c40ec476cc2bd.yaml new file mode 100644 index 00000000000..b63e4525bbb --- /dev/null +++ b/releasenotes/notes/vxflexos-revert-to-snapshot-a90c40ec476cc2bd.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + VxFlex OS driver now supports storage-assisted revert volume to snapshot.