Skip to content

Commit 672d7a4

Browse files
CSTACKEX-18_2: we are taking snapshot for volume with flexvolume snapshot with driver orchestrator workflow
1 parent 1020a2c commit 672d7a4

File tree

14 files changed

+673
-1110
lines changed

14 files changed

+673
-1110
lines changed

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

Lines changed: 374 additions & 18 deletions
Large diffs are not rendered by default.

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import org.apache.cloudstack.storage.feign.model.IscsiService;
2424
import org.apache.cloudstack.storage.feign.model.Lun;
2525
import org.apache.cloudstack.storage.feign.model.LunMap;
26+
import org.apache.cloudstack.storage.feign.model.LunRestoreRequest;
27+
import org.apache.cloudstack.storage.feign.model.response.JobResponse;
2628
import org.apache.cloudstack.storage.feign.model.response.OntapResponse;
2729
import feign.Headers;
2830
import feign.Param;
@@ -89,4 +91,24 @@ public interface SANFeignClient {
8991
void deleteLunMap(@Param("authHeader") String authHeader,
9092
@Param("lunUuid") String lunUUID,
9193
@Param("igroupUuid") String igroupUUID);
94+
95+
// LUN Restore API
96+
/**
97+
* Restores a LUN from a FlexVolume snapshot.
98+
*
99+
* <p>ONTAP REST: {@code POST /api/storage/luns/{lun.uuid}/restore}</p>
100+
*
101+
* <p>This API restores the LUN data from a specified snapshot to a destination path.
102+
* The LUN must exist and the snapshot must contain the LUN data.</p>
103+
*
104+
* @param authHeader Basic auth header
105+
* @param lunUuid UUID of the LUN to restore
106+
* @param request Request body with snapshot name and destination path
107+
* @return JobResponse containing the async job reference
108+
*/
109+
@RequestLine("POST /api/storage/luns/{lunUuid}/restore")
110+
@Headers({"Authorization: {authHeader}", "Content-Type: application/json"})
111+
JobResponse restoreLun(@Param("authHeader") String authHeader,
112+
@Param("lunUuid") String lunUuid,
113+
LunRestoreRequest request);
92114
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
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 LUN Restore API.
27+
*
28+
* <p>ONTAP REST endpoint:
29+
* {@code POST /api/storage/luns/{lun.uuid}/restore}</p>
30+
*
31+
* <p>This API restores a LUN from a FlexVolume snapshot to a specified
32+
* destination path. Unlike file restore, this is LUN-specific.</p>
33+
*
34+
* <p>Example payload:
35+
* <pre>
36+
* {
37+
* "snapshot": {
38+
* "name": "snapshot_name"
39+
* },
40+
* "destination": {
41+
* "path": "/vol/volume_name/lun_name"
42+
* }
43+
* }
44+
* </pre>
45+
* </p>
46+
*/
47+
@JsonIgnoreProperties(ignoreUnknown = true)
48+
@JsonInclude(JsonInclude.Include.NON_NULL)
49+
public class LunRestoreRequest {
50+
51+
@JsonProperty("snapshot")
52+
private SnapshotRef snapshot;
53+
54+
@JsonProperty("destination")
55+
private Destination destination;
56+
57+
public LunRestoreRequest() {
58+
}
59+
60+
public LunRestoreRequest(String snapshotName, String destinationPath) {
61+
this.snapshot = new SnapshotRef(snapshotName);
62+
this.destination = new Destination(destinationPath);
63+
}
64+
65+
public SnapshotRef getSnapshot() {
66+
return snapshot;
67+
}
68+
69+
public void setSnapshot(SnapshotRef snapshot) {
70+
this.snapshot = snapshot;
71+
}
72+
73+
public Destination getDestination() {
74+
return destination;
75+
}
76+
77+
public void setDestination(Destination destination) {
78+
this.destination = destination;
79+
}
80+
81+
/**
82+
* Nested class for snapshot reference.
83+
*/
84+
@JsonIgnoreProperties(ignoreUnknown = true)
85+
@JsonInclude(JsonInclude.Include.NON_NULL)
86+
public static class SnapshotRef {
87+
88+
@JsonProperty("name")
89+
private String name;
90+
91+
public SnapshotRef() {
92+
}
93+
94+
public SnapshotRef(String name) {
95+
this.name = name;
96+
}
97+
98+
public String getName() {
99+
return name;
100+
}
101+
102+
public void setName(String name) {
103+
this.name = name;
104+
}
105+
}
106+
107+
/**
108+
* Nested class for destination path.
109+
*/
110+
@JsonIgnoreProperties(ignoreUnknown = true)
111+
@JsonInclude(JsonInclude.Include.NON_NULL)
112+
public static class Destination {
113+
114+
@JsonProperty("path")
115+
private String path;
116+
117+
public Destination() {
118+
}
119+
120+
public Destination(String path) {
121+
this.path = path;
122+
}
123+
124+
public String getPath() {
125+
return path;
126+
}
127+
128+
public void setPath(String path) {
129+
this.path = path;
130+
}
131+
}
132+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,21 @@
1919

2020
package org.apache.cloudstack.storage.service;
2121

22+
import org.apache.cloudstack.storage.feign.client.SANFeignClient;
2223
import org.apache.cloudstack.storage.feign.model.OntapStorage;
2324

2425
public abstract class SANStrategy extends StorageStrategy {
2526
public SANStrategy(OntapStorage ontapStorage) {
2627
super(ontapStorage);
2728
}
2829

30+
/**
31+
* Returns the SAN Feign client for SAN-specific operations.
32+
*
33+
* @return the SANFeignClient instance
34+
*/
35+
public abstract SANFeignClient getSanFeignClient();
36+
2937
/**
3038
* Ensures the LUN is mapped to the specified access group (igroup).
3139
* If a mapping already exists, returns the existing LUN number.

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,29 @@ public String getNetworkInterface() {
524524
*/
525525
public abstract CloudStackVolume snapshotCloudStackVolume(CloudStackVolume cloudstackVolume);
526526

527+
/**
528+
* Reverts a CloudStack volume to a snapshot using protocol-specific ONTAP APIs.
529+
*
530+
* <p>This method encapsulates the snapshot revert behavior based on protocol:</p>
531+
* <ul>
532+
* <li><b>iSCSI/FC:</b> Uses {@code POST /api/storage/luns/{lun.uuid}/restore}
533+
* to restore LUN data from the FlexVolume snapshot.</li>
534+
* <li><b>NFS:</b> Uses {@code POST /api/storage/volumes/{vol.uuid}/snapshots/{snap.uuid}/files/{path}/restore}
535+
* to restore a single file from the FlexVolume snapshot.</li>
536+
* </ul>
537+
*
538+
* @param snapshotName The ONTAP FlexVolume snapshot name
539+
* @param flexVolUuid The FlexVolume UUID containing the snapshot
540+
* @param snapshotUuid The ONTAP snapshot UUID (used for NFS file restore)
541+
* @param volumePath The path of the file/LUN within the FlexVolume
542+
* @param lunUuid The LUN UUID (only for iSCSI, null for NFS)
543+
* @param flexVolName The FlexVolume name (only for iSCSI, for constructing destination path)
544+
* @return JobResponse for the async restore operation
545+
*/
546+
public abstract JobResponse revertSnapshotForCloudStackVolume(String snapshotName, String flexVolUuid,
547+
String snapshotUuid, String volumePath,
548+
String lunUuid, String flexVolName);
549+
527550

528551
/**
529552
* Method encapsulates the behavior based on the opted protocol in subclasses

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +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;
5253
import org.apache.cloudstack.storage.service.model.AccessGroup;
5354
import org.apache.cloudstack.storage.service.model.CloudStackVolume;
5455
import org.apache.cloudstack.storage.utils.Constants;
@@ -637,4 +638,50 @@ private FileInfo getFile(String volumeUuid, String filePath) {
637638
s_logger.info("getFile: File retrieved successfully with name {}", filePath);
638639
return fileResponse.getRecords().get(0);
639640
}
641+
642+
/**
643+
* Reverts a file to a snapshot using the ONTAP single-file restore API.
644+
*
645+
* <p>ONTAP REST API:
646+
* {@code POST /api/storage/volumes/{vol.uuid}/snapshots/{snap.uuid}/files/{path}/restore}</p>
647+
*
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
651+
* @param volumePath The file path within the FlexVolume
652+
* @param lunUuid Not used for NFS (null)
653+
* @param flexVolName Not used for NFS (null)
654+
* @return JobResponse for the async restore operation
655+
*/
656+
@Override
657+
public JobResponse revertSnapshotForCloudStackVolume(String snapshotName, String flexVolUuid,
658+
String snapshotUuid, String volumePath,
659+
String lunUuid, String flexVolName) {
660+
s_logger.info("revertSnapshotForCloudStackVolume [NFS]: Restoring file [{}] from snapshot [{}] on FlexVol [{}]",
661+
volumePath, snapshotUuid, flexVolUuid);
662+
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");
668+
}
669+
if (volumePath == null || volumePath.isEmpty()) {
670+
throw new CloudRuntimeException("revertSnapshotForCloudStackVolume: File path is required for NFS snapshot revert");
671+
}
672+
673+
String authHeader = getAuthHeader();
674+
675+
// Prepare the file path for ONTAP API (ensure it starts with "/")
676+
String ontapFilePath = volumePath.startsWith("/") ? volumePath : "/" + volumePath;
677+
678+
// For single-file restore, destination_path is the same as source (restore in place)
679+
SnapshotFileRestoreRequest restoreRequest = new SnapshotFileRestoreRequest(volumePath);
680+
681+
s_logger.debug("revertSnapshotForCloudStackVolume: Calling file restore API with flexVolUuid={}, snapshotUuid={}, filePath={}",
682+
flexVolUuid, snapshotUuid, ontapFilePath);
683+
684+
return getSnapshotFeignClient().restoreFileFromSnapshot(authHeader, flexVolUuid, snapshotUuid,
685+
ontapFilePath, restoreRequest);
686+
}
640687
}

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

Lines changed: 59 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
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;
36+
import org.apache.cloudstack.storage.feign.model.response.JobResponse;
3537
import org.apache.cloudstack.storage.feign.model.response.OntapResponse;
3638
import org.apache.cloudstack.storage.service.model.AccessGroup;
3739
import org.apache.cloudstack.storage.service.model.CloudStackVolume;
@@ -60,6 +62,11 @@ public UnifiedSANStrategy(OntapStorage ontapStorage) {
6062
this.sanFeignClient = feignClientFactory.createClient(SANFeignClient.class, baseURL);
6163
}
6264

65+
@Override
66+
public SANFeignClient getSanFeignClient() {
67+
return sanFeignClient;
68+
}
69+
6370
public void setOntapStorage(OntapStorage ontapStorage) {
6471
this.storage = ontapStorage;
6572
}
@@ -132,32 +139,7 @@ public void deleteCloudStackVolume(CloudStackVolume cloudstackVolume) {
132139

133140
@Override
134141
public void copyCloudStackVolume(CloudStackVolume cloudstackVolume) {
135-
if (cloudstackVolume == null || cloudstackVolume.getLun() == null) {
136-
s_logger.error("copyCloudStackVolume: Lun clone creation failed. Invalid request: {}", cloudstackVolume);
137-
throw new CloudRuntimeException("copyCloudStackVolume : Failed to create Lun clone, invalid request");
138-
}
139-
s_logger.debug("copyCloudStackVolume: Creating clone of the cloudstack volume: {}", cloudstackVolume.getLun().getName());
140142

141-
try {
142-
// Get AuthHeader
143-
String authHeader = Utility.generateAuthHeader(storage.getUsername(), storage.getPassword());
144-
// Create URI for lun clone creation
145-
Lun lunCloneRequest = cloudstackVolume.getLun();
146-
Lun.Clone clone = new Lun.Clone();
147-
Lun.Source source = new Lun.Source();
148-
source.setName(cloudstackVolume.getLun().getName());
149-
clone.setSource(source);
150-
lunCloneRequest.setClone(clone);
151-
String lunCloneName = cloudstackVolume.getLun().getName() + "_clone";
152-
lunCloneRequest.setName(lunCloneName);
153-
sanFeignClient.createLun(authHeader, true, lunCloneRequest);
154-
} catch (FeignException e) {
155-
s_logger.error("FeignException occurred while creating Lun clone: {}, Status: {}, Exception: {}", cloudstackVolume.getLun().getName(), e.status(), e.getMessage());
156-
throw new CloudRuntimeException("Failed to create Lun clone: " + e.getMessage());
157-
} catch (Exception e) {
158-
s_logger.error("Exception occurred while creating Lun clone: {}, Exception: {}", cloudstackVolume.getLun().getName(), e.getMessage());
159-
throw new CloudRuntimeException("Failed to create Lun clone: " + e.getMessage());
160-
}
161143
}
162144

163145
@Override
@@ -648,4 +630,56 @@ public boolean validateInitiatorInAccessGroup(String hostInitiator, String svmNa
648630
s_logger.warn("validateInitiatorInAccessGroup: Initiator [{}] NOT found in igroup [{}]", hostInitiator, accessGroupName);
649631
return false;
650632
}
633+
634+
/**
635+
* Reverts a LUN to a snapshot using the ONTAP LUN restore API.
636+
*
637+
* <p>ONTAP REST API: {@code POST /api/storage/luns/{lun.uuid}/restore}</p>
638+
*
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>
647+
*
648+
* @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)
654+
* @return JobResponse for the async restore operation
655+
*/
656+
@Override
657+
public JobResponse revertSnapshotForCloudStackVolume(String snapshotName, String flexVolUuid,
658+
String snapshotUuid, String volumePath,
659+
String lunUuid, String flexVolName) {
660+
s_logger.info("revertSnapshotForCloudStackVolume [iSCSI]: Restoring LUN [{}] (uuid={}) from snapshot [{}]",
661+
volumePath, lunUuid, snapshotName);
662+
663+
if (lunUuid == null || lunUuid.isEmpty()) {
664+
throw new CloudRuntimeException("revertSnapshotForCloudStackVolume: LUN UUID is required for iSCSI snapshot revert");
665+
}
666+
if (snapshotName == null || snapshotName.isEmpty()) {
667+
throw new CloudRuntimeException("revertSnapshotForCloudStackVolume: Snapshot name is required for iSCSI snapshot revert");
668+
}
669+
if (flexVolName == null || flexVolName.isEmpty()) {
670+
throw new CloudRuntimeException("revertSnapshotForCloudStackVolume: FlexVolume name is required for iSCSI snapshot revert");
671+
}
672+
673+
String authHeader = getAuthHeader();
674+
675+
// Construct destination path: /vol/<volume_name>/<lun_name>
676+
String destinationPath = "/vol/" + flexVolName + "/" + volumePath;
677+
678+
LunRestoreRequest restoreRequest = new LunRestoreRequest(snapshotName, destinationPath);
679+
680+
s_logger.debug("revertSnapshotForCloudStackVolume: Calling LUN restore API with lunUuid={}, snapshotName={}, destinationPath={}",
681+
lunUuid, snapshotName, destinationPath);
682+
683+
return sanFeignClient.restoreLun(authHeader, lunUuid, restoreRequest);
684+
}
651685
}

0 commit comments

Comments
 (0)