Skip to content

Commit 5bff41f

Browse files
CSTACKEC-18_2: revertsnapshot workflow using private cli REST endpoint
1 parent 7780a93 commit 5bff41f

File tree

7 files changed

+240
-75
lines changed

7 files changed

+240
-75
lines changed

plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -829,16 +829,16 @@ public void revertSnapshot(SnapshotInfo snapshotOnImageStore, SnapshotInfo snaps
829829

830830
StorageStrategy storageStrategy = Utility.getStrategyByStoragePoolDetails(poolDetails);
831831

832-
// Prepare protocol-specific parameters
833-
String lunUuid = null;
834-
String flexVolName = null;
832+
// Get the FlexVolume name (required for CLI-based restore API for all protocols)
833+
String flexVolName = poolDetails.get(Constants.VOLUME_NAME);
834+
if (flexVolName == null || flexVolName.isEmpty()) {
835+
throw new CloudRuntimeException("revertSnapshot: FlexVolume name not found in pool details for pool " + poolId);
836+
}
835837

838+
// Prepare protocol-specific parameters (lunUuid is only needed for backward compatibility)
839+
String lunUuid = null;
836840
if (ProtocolType.ISCSI.name().equalsIgnoreCase(protocol)) {
837841
lunUuid = getSnapshotDetail(snapshotId, Constants.LUN_DOT_UUID);
838-
if (lunUuid == null || lunUuid.isEmpty()) {
839-
throw new CloudRuntimeException("revertSnapshot: LUN UUID not found in snapshot details for iSCSI snapshot " + snapshotId);
840-
}
841-
flexVolName = poolDetails.get(Constants.VOLUME_NAME);
842842
}
843843

844844
// Delegate to strategy class for protocol-specific restore

plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SnapshotFeignClient.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import feign.Param;
2323
import feign.QueryMap;
2424
import feign.RequestLine;
25+
import org.apache.cloudstack.storage.feign.model.CliSnapshotRestoreRequest;
2526
import org.apache.cloudstack.storage.feign.model.FlexVolSnapshot;
2627
import org.apache.cloudstack.storage.feign.model.SnapshotFileRestoreRequest;
2728
import org.apache.cloudstack.storage.feign.model.response.JobResponse;
@@ -151,4 +152,33 @@ JobResponse restoreFileFromSnapshot(@Param("authHeader") String authHeader,
151152
@Param("snapshotUuid") String snapshotUuid,
152153
@Param("filePath") String filePath,
153154
SnapshotFileRestoreRequest request);
155+
156+
/**
157+
* Restores a single file or LUN from a FlexVolume snapshot using the CLI native API.
158+
*
159+
* <p>ONTAP REST (CLI passthrough):
160+
* {@code POST /api/private/cli/volume/snapshot/restore-file}</p>
161+
*
162+
* <p>This CLI-based API is more reliable and works for both NFS files and iSCSI LUNs.
163+
* The request body contains all required parameters: vserver, volume, snapshot, and path.</p>
164+
*
165+
* <p>Example payload:
166+
* <pre>
167+
* {
168+
* "vserver": "vs0",
169+
* "volume": "rajiv_ONTAP_SP1",
170+
* "snapshot": "DATA-3-428726fe-7440-4b41-8d47-3f654e5d9814",
171+
* "path": "/d266bb2c-d479-47ad-81c3-a070e8bb58c0"
172+
* }
173+
* </pre>
174+
* </p>
175+
*
176+
* @param authHeader Basic auth header
177+
* @param request CLI snapshot restore request containing vserver, volume, snapshot, and path
178+
* @return JobResponse containing the async job reference (if applicable)
179+
*/
180+
@RequestLine("POST /api/private/cli/volume/snapshot/restore-file")
181+
@Headers({"Authorization: {authHeader}", "Content-Type: application/json"})
182+
JobResponse restoreFileFromSnapshotCli(@Param("authHeader") String authHeader,
183+
CliSnapshotRestoreRequest request);
154184
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.cloudstack.storage.feign.model;
20+
21+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
22+
import com.fasterxml.jackson.annotation.JsonInclude;
23+
import com.fasterxml.jackson.annotation.JsonProperty;
24+
25+
/**
26+
* Request body for the ONTAP CLI-based Snapshot File Restore API.
27+
*
28+
* <p>ONTAP REST endpoint (CLI passthrough):
29+
* {@code POST /api/private/cli/volume/snapshot/restore-file}</p>
30+
*
31+
* <p>This API restores a single file or LUN from a FlexVolume snapshot to a
32+
* specified destination path using the CLI native implementation.
33+
* It works for both NFS files and iSCSI LUNs.</p>
34+
*
35+
* <p>Example payload:
36+
* <pre>
37+
* {
38+
* "vserver": "vs0",
39+
* "volume": "rajiv_ONTAP_SP1",
40+
* "snapshot": "DATA-3-428726fe-7440-4b41-8d47-3f654e5d9814",
41+
* "path": "/d266bb2c-d479-47ad-81c3-a070e8bb58c0"
42+
* }
43+
* </pre>
44+
* </p>
45+
*/
46+
@JsonIgnoreProperties(ignoreUnknown = true)
47+
@JsonInclude(JsonInclude.Include.NON_NULL)
48+
public class CliSnapshotRestoreRequest {
49+
50+
@JsonProperty("vserver")
51+
private String vserver;
52+
53+
@JsonProperty("volume")
54+
private String volume;
55+
56+
@JsonProperty("snapshot")
57+
private String snapshot;
58+
59+
@JsonProperty("path")
60+
private String path;
61+
62+
public CliSnapshotRestoreRequest() {
63+
}
64+
65+
/**
66+
* Creates a CLI snapshot restore request.
67+
*
68+
* @param vserver The SVM (vserver) name
69+
* @param volume The FlexVolume name
70+
* @param snapshot The snapshot name
71+
* @param path The file/LUN path to restore (e.g., "/uuid.qcow2" or "/lun_name")
72+
*/
73+
public CliSnapshotRestoreRequest(String vserver, String volume, String snapshot, String path) {
74+
this.vserver = vserver;
75+
this.volume = volume;
76+
this.snapshot = snapshot;
77+
this.path = path;
78+
}
79+
80+
public String getVserver() {
81+
return vserver;
82+
}
83+
84+
public void setVserver(String vserver) {
85+
this.vserver = vserver;
86+
}
87+
88+
public String getVolume() {
89+
return volume;
90+
}
91+
92+
public void setVolume(String volume) {
93+
this.volume = volume;
94+
}
95+
96+
public String getSnapshot() {
97+
return snapshot;
98+
}
99+
100+
public void setSnapshot(String snapshot) {
101+
this.snapshot = snapshot;
102+
}
103+
104+
public String getPath() {
105+
return path;
106+
}
107+
108+
public void setPath(String path) {
109+
this.path = path;
110+
}
111+
112+
@Override
113+
public String toString() {
114+
return "CliSnapshotRestoreRequest{" +
115+
"vserver='" + vserver + '\'' +
116+
", volume='" + volume + '\'' +
117+
", snapshot='" + snapshot + '\'' +
118+
", path='" + path + '\'' +
119+
'}';
120+
}
121+
}

plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
import org.apache.cloudstack.storage.feign.model.VolumeConcise;
5050
import org.apache.cloudstack.storage.feign.model.response.JobResponse;
5151
import org.apache.cloudstack.storage.feign.model.response.OntapResponse;
52-
import org.apache.cloudstack.storage.feign.model.SnapshotFileRestoreRequest;
52+
import org.apache.cloudstack.storage.feign.model.CliSnapshotRestoreRequest;
5353
import org.apache.cloudstack.storage.service.model.AccessGroup;
5454
import org.apache.cloudstack.storage.service.model.CloudStackVolume;
5555
import org.apache.cloudstack.storage.utils.Constants;
@@ -640,48 +640,52 @@ private FileInfo getFile(String volumeUuid, String filePath) {
640640
}
641641

642642
/**
643-
* Reverts a file to a snapshot using the ONTAP single-file restore API.
643+
* Reverts a file to a snapshot using the ONTAP CLI-based snapshot file restore API.
644644
*
645-
* <p>ONTAP REST API:
646-
* {@code POST /api/storage/volumes/{vol.uuid}/snapshots/{snap.uuid}/files/{path}/restore}</p>
645+
* <p>ONTAP REST API (CLI passthrough):
646+
* {@code POST /api/private/cli/volume/snapshot/restore-file}</p>
647647
*
648-
* @param snapshotName The ONTAP FlexVolume snapshot name (not used for NFS, but kept for interface consistency)
649-
* @param flexVolUuid The FlexVolume UUID containing the snapshot
650-
* @param snapshotUuid The ONTAP snapshot UUID
648+
* <p>This method uses the CLI native API which is more reliable and works
649+
* consistently for both NFS files and iSCSI LUNs.</p>
650+
*
651+
* @param snapshotName The ONTAP FlexVolume snapshot name
652+
* @param flexVolUuid The FlexVolume UUID (not used in CLI API, kept for interface consistency)
653+
* @param snapshotUuid The ONTAP snapshot UUID (not used in CLI API, kept for interface consistency)
651654
* @param volumePath The file path within the FlexVolume
652655
* @param lunUuid Not used for NFS (null)
653-
* @param flexVolName Not used for NFS (null)
656+
* @param flexVolName The FlexVolume name (required for CLI API)
654657
* @return JobResponse for the async restore operation
655658
*/
656659
@Override
657660
public JobResponse revertSnapshotForCloudStackVolume(String snapshotName, String flexVolUuid,
658661
String snapshotUuid, String volumePath,
659662
String lunUuid, String flexVolName) {
660663
s_logger.info("revertSnapshotForCloudStackVolume [NFS]: Restoring file [{}] from snapshot [{}] on FlexVol [{}]",
661-
volumePath, snapshotUuid, flexVolUuid);
664+
volumePath, snapshotName, flexVolName);
662665

663-
if (flexVolUuid == null || flexVolUuid.isEmpty()) {
664-
throw new CloudRuntimeException("revertSnapshotForCloudStackVolume: FlexVolume UUID is required for NFS snapshot revert");
665-
}
666-
if (snapshotUuid == null || snapshotUuid.isEmpty()) {
667-
throw new CloudRuntimeException("revertSnapshotForCloudStackVolume: Snapshot UUID is required for NFS snapshot revert");
666+
if (snapshotName == null || snapshotName.isEmpty()) {
667+
throw new CloudRuntimeException("revertSnapshotForCloudStackVolume: Snapshot name is required for NFS snapshot revert");
668668
}
669669
if (volumePath == null || volumePath.isEmpty()) {
670670
throw new CloudRuntimeException("revertSnapshotForCloudStackVolume: File path is required for NFS snapshot revert");
671671
}
672+
if (flexVolName == null || flexVolName.isEmpty()) {
673+
throw new CloudRuntimeException("revertSnapshotForCloudStackVolume: FlexVolume name is required for NFS snapshot revert");
674+
}
672675

673676
String authHeader = getAuthHeader();
677+
String svmName = storage.getSvmName();
674678

675-
// Prepare the file path for ONTAP API (ensure it starts with "/")
679+
// Prepare the file path for ONTAP CLI API (ensure it starts with "/")
676680
String ontapFilePath = volumePath.startsWith("/") ? volumePath : "/" + volumePath;
677681

678-
// For single-file restore, destination_path is the same as source (restore in place)
679-
SnapshotFileRestoreRequest restoreRequest = new SnapshotFileRestoreRequest(volumePath);
682+
// Create CLI snapshot restore request
683+
CliSnapshotRestoreRequest restoreRequest = new CliSnapshotRestoreRequest(
684+
svmName, flexVolName, snapshotName, ontapFilePath);
680685

681-
s_logger.debug("revertSnapshotForCloudStackVolume: Calling file restore API with flexVolUuid={}, snapshotUuid={}, filePath={}",
682-
flexVolUuid, snapshotUuid, ontapFilePath);
686+
s_logger.info("revertSnapshotForCloudStackVolume: Calling CLI file restore API with vserver={}, volume={}, snapshot={}, path={}",
687+
svmName, flexVolName, snapshotName, ontapFilePath);
683688

684-
return getSnapshotFeignClient().restoreFileFromSnapshot(authHeader, flexVolUuid, snapshotUuid,
685-
ontapFilePath, restoreRequest);
689+
return getSnapshotFeignClient().restoreFileFromSnapshotCli(authHeader, restoreRequest);
686690
}
687691
}

plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import org.apache.cloudstack.storage.feign.model.OntapStorage;
3333
import org.apache.cloudstack.storage.feign.model.Lun;
3434
import org.apache.cloudstack.storage.feign.model.LunMap;
35-
import org.apache.cloudstack.storage.feign.model.LunRestoreRequest;
35+
import org.apache.cloudstack.storage.feign.model.CliSnapshotRestoreRequest;
3636
import org.apache.cloudstack.storage.feign.model.response.JobResponse;
3737
import org.apache.cloudstack.storage.feign.model.response.OntapResponse;
3838
import org.apache.cloudstack.storage.service.model.AccessGroup;
@@ -632,54 +632,52 @@ public boolean validateInitiatorInAccessGroup(String hostInitiator, String svmNa
632632
}
633633

634634
/**
635-
* Reverts a LUN to a snapshot using the ONTAP LUN restore API.
635+
* Reverts a LUN to a snapshot using the ONTAP CLI-based snapshot file restore API.
636636
*
637-
* <p>ONTAP REST API: {@code POST /api/storage/luns/{lun.uuid}/restore}</p>
637+
* <p>ONTAP REST API (CLI passthrough):
638+
* {@code POST /api/private/cli/volume/snapshot/restore-file}</p>
638639
*
639-
* <p>Request payload:
640-
* <pre>
641-
* {
642-
* "snapshot": { "name": "snapshot_name" },
643-
* "destination": { "path": "/vol/volume_name/lun_name" }
644-
* }
645-
* </pre>
646-
* </p>
640+
* <p>This method uses the CLI native API which is more reliable and works
641+
* consistently for both NFS files and iSCSI LUNs.</p>
647642
*
648643
* @param snapshotName The ONTAP FlexVolume snapshot name
649-
* @param flexVolUuid The FlexVolume UUID (not used for LUN restore, but kept for interface consistency)
650-
* @param snapshotUuid The ONTAP snapshot UUID (not used for LUN restore)
651-
* @param volumePath The LUN name (used to construct destination path)
652-
* @param lunUuid The LUN UUID to restore
653-
* @param flexVolName The FlexVolume name (for constructing destination path)
644+
* @param flexVolUuid The FlexVolume UUID (not used in CLI API, kept for interface consistency)
645+
* @param snapshotUuid The ONTAP snapshot UUID (not used in CLI API, kept for interface consistency)
646+
* @param volumePath The LUN name (used to construct the path)
647+
* @param lunUuid The LUN UUID (not used in CLI API, kept for interface consistency)
648+
* @param flexVolName The FlexVolume name (required for CLI API)
654649
* @return JobResponse for the async restore operation
655650
*/
656651
@Override
657652
public JobResponse revertSnapshotForCloudStackVolume(String snapshotName, String flexVolUuid,
658653
String snapshotUuid, String volumePath,
659654
String lunUuid, String flexVolName) {
660-
s_logger.info("revertSnapshotForCloudStackVolume [iSCSI]: Restoring LUN [{}] (uuid={}) from snapshot [{}]",
661-
volumePath, lunUuid, snapshotName);
655+
s_logger.info("revertSnapshotForCloudStackVolume [iSCSI]: Restoring LUN [{}] from snapshot [{}] on FlexVol [{}]",
656+
volumePath, snapshotName, flexVolName);
662657

663-
if (lunUuid == null || lunUuid.isEmpty()) {
664-
throw new CloudRuntimeException("revertSnapshotForCloudStackVolume: LUN UUID is required for iSCSI snapshot revert");
665-
}
666658
if (snapshotName == null || snapshotName.isEmpty()) {
667659
throw new CloudRuntimeException("revertSnapshotForCloudStackVolume: Snapshot name is required for iSCSI snapshot revert");
668660
}
669661
if (flexVolName == null || flexVolName.isEmpty()) {
670662
throw new CloudRuntimeException("revertSnapshotForCloudStackVolume: FlexVolume name is required for iSCSI snapshot revert");
671663
}
664+
if (volumePath == null || volumePath.isEmpty()) {
665+
throw new CloudRuntimeException("revertSnapshotForCloudStackVolume: LUN path is required for iSCSI snapshot revert");
666+
}
672667

673668
String authHeader = getAuthHeader();
669+
String svmName = storage.getSvmName();
674670

675-
// Construct destination path: /vol/<volume_name>/<lun_name>
676-
String destinationPath = "/vol/" + flexVolName + "/" + volumePath;
671+
// Prepare the LUN path for ONTAP CLI API (ensure it starts with "/")
672+
String ontapLunPath = volumePath.startsWith("/") ? volumePath : "/" + volumePath;
677673

678-
LunRestoreRequest restoreRequest = new LunRestoreRequest(snapshotName, destinationPath);
674+
// Create CLI snapshot restore request
675+
CliSnapshotRestoreRequest restoreRequest = new CliSnapshotRestoreRequest(
676+
svmName, flexVolName, snapshotName, ontapLunPath);
679677

680-
s_logger.debug("revertSnapshotForCloudStackVolume: Calling LUN restore API with lunUuid={}, snapshotName={}, destinationPath={}",
681-
lunUuid, snapshotName, destinationPath);
678+
s_logger.info("revertSnapshotForCloudStackVolume: Calling CLI file restore API with vserver={}, volume={}, snapshot={}, path={}",
679+
svmName, flexVolName, snapshotName, ontapLunPath);
682680

683-
return sanFeignClient.restoreLun(authHeader, lunUuid, restoreRequest);
681+
return getSnapshotFeignClient().restoreFileFromSnapshotCli(authHeader, restoreRequest);
684682
}
685683
}

0 commit comments

Comments
 (0)