From 88c62c1ca290c1d2a33ff192a302e492a4b11cb3 Mon Sep 17 00:00:00 2001 From: "Erlon R. Cruz" Date: Tue, 16 Oct 2018 14:24:06 -0300 Subject: [PATCH] NetApp SolidFire: Revert volume to snapshot This implements the optimized version of revert_to_snapshot function on NetApp SolidFire driver. Change-Id: Id5e907aeabe9415a7342999bb00227ed8881d60a Implements: blueprint netapp-solidfire-revert-to-snapshot --- .../drivers/solidfire/test_solidfire.py | 122 ++++++++++++++++++ cinder/volume/drivers/solidfire.py | 33 +++++ ...e-revert-to-snapshot-741b7c204cc99546.yaml | 5 + 3 files changed, 160 insertions(+) create mode 100644 releasenotes/notes/bp-netapp-solidfire-revert-to-snapshot-741b7c204cc99546.yaml diff --git a/cinder/tests/unit/volume/drivers/solidfire/test_solidfire.py b/cinder/tests/unit/volume/drivers/solidfire/test_solidfire.py index 2908504a0f9..8b9ceb6af66 100644 --- a/cinder/tests/unit/volume/drivers/solidfire/test_solidfire.py +++ b/cinder/tests/unit/volume/drivers/solidfire/test_solidfire.py @@ -111,6 +111,35 @@ class SolidFireVolumeTestCase(test.TestCase): 'owner': 'testprjid'} self.fake_image_service = fake_image.FakeImageService() + self.vol = test_utils.create_volume( + self.ctxt, volume_id='b831c4d1-d1f0-11e1-9b23-0800200c9a66') + self.snap = test_utils.create_snapshot( + self.ctxt, volume_id=self.vol.id) + + self.fake_sfaccount = {'accountID': 25, + 'name': 'testprjid', + 'targetSecret': 'shhhh', + 'username': 'john-wayne', + 'volumes': [6, 7, 20]} + + self.fake_sfvol = {'volumeID': 6, + 'name': 'test_volume', + 'accountID': 25, + 'sliceCount': 1, + 'totalSize': 1 * units.Gi, + 'enable512e': True, + 'access': "readWrite", + 'status': "active", + 'attributes': {'uuid': f_uuid[0]}, + 'qos': None, + 'iqn': 'super_fake_iqn'} + + self.fake_sfsnap_name = '%s%s' % (self.configuration.sf_volume_prefix, + self.snap.id) + self.fake_sfsnaps = [{'snapshotID': '5', + 'name': self.fake_sfsnap_name, + 'volumeID': 6}] + def fake_init_cluster_pairs(*args, **kwargs): return None @@ -246,6 +275,31 @@ class SolidFireVolumeTestCase(test.TestCase): return {'result': {'clusterAPIVersion': '8.0'}} elif method is 'StartVolumePairing': return {'result': {'volumePairingKey': 'fake-pairing-key'}} + elif method is 'RollbackToSnapshot': + return { + "id": 1, + "result": { + "checksum": "0x0", + "snapshot": { + "attributes": {}, + "checksum": "0x0", + "createTime": "2016-04-04T17:27:32Z", + "enableRemoteReplication": "false", + "expirationReason": "None", + "expirationTime": "null", + "groupID": 0, + "groupSnapshotUUID": f_uuid[0], + "name": "test1-copy", + "snapshotID": 1, + "snapshotUUID": f_uuid[1], + "status": "done", + "totalSize": 5000658944, + "virtualVolumeID": "null", + "volumeID": 1 + }, + "snapshotID": 1 + } + } else: # Crap, unimplemented API call in Fake return None @@ -2291,3 +2345,71 @@ class SolidFireVolumeTestCase(test.TestCase): a = sfv._generate_random_string(12) self.assertEqual(len(a), 12) self.assertIsNotNone(re.match(r'[A-Z0-9]{12}', a), a) + + @mock.patch.object(solidfire.SolidFireDriver, '_get_sfaccount') + @mock.patch.object(solidfire.SolidFireDriver, '_get_sf_volume') + @mock.patch.object(solidfire.SolidFireDriver, '_get_sf_snapshots') + @mock.patch.object(solidfire.SolidFireDriver, '_issue_api_request') + def test_revert_to_snapshot_success(self, mock_issue_api_request, + mock_get_sf_snapshots, + mock_get_sf_volume, + mock_get_sfaccount): + mock_issue_api_request.side_effect = self.fake_issue_api_request + + mock_get_sfaccount.return_value = self.fake_sfaccount + mock_get_sf_volume.return_value = self.fake_sfvol + mock_get_sf_snapshots.return_value = self.fake_sfsnaps + + expected_params = {'accountID': 25, + 'volumeID': 6, + 'snapshotID': '5', + 'saveCurrentState': 'false'} + + sfv = solidfire.SolidFireDriver(configuration=self.configuration) + + # Success path + sfv.revert_to_snapshot(self.ctxt, self.vol, self.snap) + mock_issue_api_request.assert_called_with( + 'RollbackToSnapshot', expected_params, version='6.0') + + @mock.patch.object(solidfire.SolidFireDriver, '_get_sfaccount') + @mock.patch.object(solidfire.SolidFireDriver, '_get_sf_volume') + @mock.patch.object(solidfire.SolidFireDriver, '_get_sf_snapshots') + @mock.patch.object(solidfire.SolidFireDriver, '_issue_api_request') + def test_revert_to_snapshot_fail_vol_not_found( + self, mock_issue_api_request, mock_get_sf_snapshots, + mock_get_sf_volume, mock_get_sfaccount): + mock_issue_api_request.side_effect = self.fake_issue_api_request + + mock_get_sfaccount.return_value = self.fake_sfaccount + mock_get_sf_volume.return_value = None + mock_get_sf_snapshots.return_value = [] + + sfv = solidfire.SolidFireDriver(configuration=self.configuration) + + # Volume not found + mock_get_sf_volume.return_value = None + self.assertRaises(exception.VolumeNotFound, + sfv.revert_to_snapshot, + self.ctxt, self.vol, self.snap) + + @mock.patch.object(solidfire.SolidFireDriver, '_get_sfaccount') + @mock.patch.object(solidfire.SolidFireDriver, '_get_sf_volume') + @mock.patch.object(solidfire.SolidFireDriver, '_get_sf_snapshots') + @mock.patch.object(solidfire.SolidFireDriver, '_issue_api_request') + def test_revert_to_snapshot_fail_snap_not_found( + self, mock_issue_api_request, mock_get_sf_snapshots, + mock_get_sf_volume, mock_get_sfaccount): + mock_issue_api_request.side_effect = self.fake_issue_api_request + + mock_get_sfaccount.return_value = self.fake_sfaccount + mock_get_sf_volume.return_value = self.fake_sfvol + mock_get_sf_snapshots.return_value = [] + + sfv = solidfire.SolidFireDriver(configuration=self.configuration) + + # Snapshot not found + mock_get_sf_snapshots.return_value = [] + self.assertRaises(exception.VolumeSnapshotNotFound, + sfv.revert_to_snapshot, + self.ctxt, self.vol, self.snap) diff --git a/cinder/volume/drivers/solidfire.py b/cinder/volume/drivers/solidfire.py index 547f5b8a891..a1cbfadf79f 100644 --- a/cinder/volume/drivers/solidfire.py +++ b/cinder/volume/drivers/solidfire.py @@ -2288,6 +2288,39 @@ class SolidFireDriver(san.SanISCSIDriver): """Thaw backend notification.""" pass + def revert_to_snapshot(self, context, volume, snapshot): + """Revert a volume to a given snapshot.""" + + sfaccount = self._get_sfaccount(volume.project_id) + params = {'accountID': sfaccount['accountID']} + + sf_vol = self._get_sf_volume(volume.id, params) + if sf_vol is None: + LOG.error("Volume ID %s was not found on " + "the SolidFire Cluster while attempting " + "revert_to_snapshot operation!", volume.id) + raise exception.VolumeNotFound(volume_id=volume['id']) + + params['volumeID'] = sf_vol['volumeID'] + + sf_snap_name = '%s%s' % (self.configuration.sf_volume_prefix, + snapshot.id) + sf_snaps = self._get_sf_snapshots(sf_vol['volumeID']) + snap = next((s for s in sf_snaps if s["name"] == sf_snap_name), + None) + if not snap: + LOG.error("Snapshot ID %s was not found on " + "the SolidFire Cluster while attempting " + "revert_to_snapshot operation!", snapshot.id) + raise exception.VolumeSnapshotNotFound(volume_id=volume.id) + + params['snapshotID'] = snap['snapshotID'] + params['saveCurrentState'] = 'false' + + self._issue_api_request('RollbackToSnapshot', + params, + version='6.0') + class SolidFireISCSI(iscsi_driver.SanISCSITarget): def __init__(self, *args, **kwargs): diff --git a/releasenotes/notes/bp-netapp-solidfire-revert-to-snapshot-741b7c204cc99546.yaml b/releasenotes/notes/bp-netapp-solidfire-revert-to-snapshot-741b7c204cc99546.yaml new file mode 100644 index 00000000000..fc36dbab4c7 --- /dev/null +++ b/releasenotes/notes/bp-netapp-solidfire-revert-to-snapshot-741b7c204cc99546.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + NetApp SolidFire driver now supports optimized revert to snapshot + operations.