Skip to content

Commit cd08c00

Browse files
CSTACKEX-18: Cloudstack snapshot workflow for NFS3 protocol
1 parent 1ae738b commit cd08c00

File tree

9 files changed

+378
-47
lines changed

9 files changed

+378
-47
lines changed

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

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727
import com.cloud.storage.Storage;
2828
import com.cloud.storage.StoragePool;
2929
import com.cloud.storage.Volume;
30+
import com.cloud.storage.VolumeVO;
31+
import com.cloud.storage.dao.SnapshotDetailsDao;
32+
import com.cloud.storage.dao.SnapshotDetailsVO;
33+
import com.cloud.storage.dao.VolumeDao;
34+
import com.cloud.storage.dao.VolumeDetailsDao;
3035
import com.cloud.utils.Pair;
3136
import com.cloud.utils.exception.CloudRuntimeException;
3237
import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
@@ -41,14 +46,18 @@
4146
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
4247
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
4348
import org.apache.cloudstack.storage.command.CommandResult;
49+
import org.apache.cloudstack.storage.command.CreateObjectAnswer;
4450
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
4551
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
4652
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
53+
import org.apache.cloudstack.storage.feign.model.FileInfo;
4754
import org.apache.cloudstack.storage.service.StorageStrategy;
4855
import org.apache.cloudstack.storage.service.model.CloudStackVolume;
4956
import org.apache.cloudstack.storage.service.model.ProtocolType;
57+
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
5058
import org.apache.cloudstack.storage.utils.Constants;
5159
import org.apache.cloudstack.storage.utils.Utility;
60+
import org.apache.commons.lang3.StringUtils;
5261
import org.apache.logging.log4j.LogManager;
5362
import org.apache.logging.log4j.Logger;
5463

@@ -62,6 +71,9 @@ public class OntapPrimaryDatastoreDriver implements PrimaryDataStoreDriver {
6271

6372
@Inject private StoragePoolDetailsDao storagePoolDetailsDao;
6473
@Inject private PrimaryDataStoreDao storagePoolDao;
74+
@Inject private VolumeDao volumeDao;
75+
@Inject private VolumeDetailsDao volumeDetailsDao;
76+
@Inject private SnapshotDetailsDao snapshotDetailsDao;
6577

6678
@Override
6779
public Map<String, String> getCapabilities() {
@@ -227,6 +239,79 @@ public long getUsedIops(StoragePool storagePool) {
227239
@Override
228240
public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CreateCmdResult> callback) {
229241

242+
CreateCmdResult result;
243+
244+
try {
245+
VolumeInfo volumeInfo = snapshot.getBaseVolume();
246+
247+
VolumeVO volumeVO = volumeDao.findById(volumeInfo.getId());
248+
if(volumeVO == null) {
249+
throw new CloudRuntimeException("takeSnapshot: VolumeVO not found for id: " + volumeInfo.getId());
250+
}
251+
252+
/** we are keeping file path at volumeVO.getPath() */
253+
254+
StoragePoolVO storagePool = storagePoolDao.findById(volumeVO.getPoolId());
255+
if(storagePool == null) {
256+
s_logger.error("takeSnapshot : Storage Pool not found for id: " + volumeVO.getPoolId());
257+
throw new CloudRuntimeException("takeSnapshot : Storage Pool not found for id: " + volumeVO.getPoolId());
258+
}
259+
Map<String, String> poolDetails = storagePoolDetailsDao.listDetailsKeyPairs(volumeVO.getPoolId());
260+
StorageStrategy storageStrategy = Utility.getStrategyByStoragePoolDetails(poolDetails);
261+
262+
CloudStackVolume cloudStackVolumeRequest = getCloudStackVolumeRequestByProtocol(poolDetails, volumeVO.getPath());
263+
CloudStackVolume cloudStackVolume = storageStrategy.getCloudStackVolume(cloudStackVolumeRequest);
264+
if (cloudStackVolume == null || cloudStackVolume.getFile() == null) {
265+
throw new CloudRuntimeException("takeSnapshot: Failed to get source file to take snapshot");
266+
}
267+
long capacityBytes = storagePool.getCapacityBytes();
268+
269+
long usedBytes = getUsedBytes(storagePool);
270+
long fileSize = cloudStackVolume.getFile().getSize();
271+
272+
usedBytes += fileSize;
273+
274+
if (usedBytes > capacityBytes) {
275+
throw new CloudRuntimeException("Insufficient space remains in this primary storage to take a snapshot");
276+
}
277+
278+
storagePool.setUsedBytes(usedBytes);
279+
280+
SnapshotObjectTO snapshotObjectTo = (SnapshotObjectTO)snapshot.getTO();
281+
282+
String fileSnapshotName = volumeInfo.getName() + "-" + snapshot.getUuid();
283+
284+
int maxSnapshotNameLength = 64;
285+
int trimRequired = fileSnapshotName.length() - maxSnapshotNameLength;
286+
287+
if (trimRequired > 0) {
288+
fileSnapshotName = StringUtils.left(volumeInfo.getName(), (volumeInfo.getName().length() - trimRequired)) + "-" + snapshot.getUuid();
289+
}
290+
291+
CloudStackVolume snapCloudStackVolumeRequest = snapshotCloudStackVolumeRequestByProtocol(poolDetails, volumeVO.getPath(), fileSnapshotName);
292+
CloudStackVolume cloneCloudStackVolume = storageStrategy.snapshotCloudStackVolume(snapCloudStackVolumeRequest);
293+
294+
updateSnapshotDetails(snapshot.getId(), volumeInfo.getId(), poolDetails.get(Constants.VOLUME_UUID), cloneCloudStackVolume.getFile().getPath(), volumeVO.getPoolId(), fileSize);
295+
296+
snapshotObjectTo.setPath(Constants.ONTAP_SNAP_ID +"="+cloneCloudStackVolume.getFile().getPath());
297+
298+
/** Update size for the storage-pool including snapshot size */
299+
storagePoolDao.update(volumeVO.getPoolId(), storagePool);
300+
301+
CreateObjectAnswer createObjectAnswer = new CreateObjectAnswer(snapshotObjectTo);
302+
303+
result = new CreateCmdResult(null, createObjectAnswer);
304+
305+
result.setResult(null);
306+
}
307+
catch (Exception ex) {
308+
s_logger.error("takeSnapshot: Failed due to ", ex);
309+
result = new CreateCmdResult(null, new CreateObjectAnswer(ex.toString()));
310+
311+
result.setResult(ex.toString());
312+
}
313+
314+
callback.complete(result);
230315
}
231316

232317
@Override
@@ -293,4 +378,87 @@ public boolean isStorageSupportHA(Storage.StoragePoolType type) {
293378
public void detachVolumeFromAllStorageNodes(Volume volume) {
294379

295380
}
381+
382+
383+
private CloudStackVolume getCloudStackVolumeRequestByProtocol(Map<String, String> details, String filePath) {
384+
CloudStackVolume cloudStackVolumeRequest = null;
385+
ProtocolType protocolType = null;
386+
String protocol = null;
387+
388+
try {
389+
protocol = details.get(Constants.PROTOCOL);
390+
protocolType = ProtocolType.valueOf(protocol);
391+
} catch (IllegalArgumentException e) {
392+
throw new CloudRuntimeException("getCloudStackVolumeRequestByProtocol: Protocol: "+ protocol +" is not valid");
393+
}
394+
switch (protocolType) {
395+
case NFS3:
396+
cloudStackVolumeRequest = new CloudStackVolume();
397+
FileInfo fileInfo = new FileInfo();
398+
fileInfo.setPath(filePath);
399+
cloudStackVolumeRequest.setFile(fileInfo);
400+
String volumeUuid = details.get(Constants.VOLUME_UUID);
401+
cloudStackVolumeRequest.setFlexVolumeUuid(volumeUuid);
402+
break;
403+
default:
404+
throw new CloudRuntimeException("createCloudStackVolumeRequestByProtocol: Unsupported protocol " + protocol);
405+
}
406+
return cloudStackVolumeRequest;
407+
}
408+
409+
private CloudStackVolume snapshotCloudStackVolumeRequestByProtocol(Map<String, String> details,
410+
String sourcePath,
411+
String destinationPath) {
412+
CloudStackVolume cloudStackVolumeRequest = null;
413+
ProtocolType protocolType = null;
414+
String protocol = null;
415+
416+
try {
417+
protocol = details.get(Constants.PROTOCOL);
418+
protocolType = ProtocolType.valueOf(protocol);
419+
} catch (IllegalArgumentException e) {
420+
throw new CloudRuntimeException("getCloudStackVolumeRequestByProtocol: Protocol: "+ protocol +" is not valid");
421+
}
422+
switch (protocolType) {
423+
case NFS3:
424+
cloudStackVolumeRequest = new CloudStackVolume();
425+
FileInfo fileInfo = new FileInfo();
426+
fileInfo.setPath(sourcePath);
427+
cloudStackVolumeRequest.setFile(fileInfo);
428+
String volumeUuid = details.get(Constants.VOLUME_UUID);
429+
cloudStackVolumeRequest.setFlexVolumeUuid(volumeUuid);
430+
cloudStackVolumeRequest.setDestinationPath(destinationPath);
431+
break;
432+
default:
433+
throw new CloudRuntimeException("createCloudStackVolumeRequestByProtocol: Unsupported protocol " + protocol);
434+
435+
}
436+
return cloudStackVolumeRequest;
437+
}
438+
439+
/**
440+
*
441+
* @param csSnapshotId: generated snapshot id from cloudstack
442+
* @param csVolumeId: Source CS volume id
443+
* @param ontapVolumeUuid: storage flexvolume id
444+
* @param ontapNewSnapshot: generated snapshot id from ONTAP
445+
* @param storagePoolId: primary storage pool id
446+
* @param ontapSnapSize: Size of snapshot CS volume(LUN/file)
447+
*/
448+
private void updateSnapshotDetails(long csSnapshotId, long csVolumeId, String ontapVolumeUuid, String ontapNewSnapshot, long storagePoolId, long ontapSnapSize) {
449+
SnapshotDetailsVO snapshotDetail = new SnapshotDetailsVO(csSnapshotId, Constants.SRC_CS_VOLUME_ID, String.valueOf(csVolumeId), false);
450+
snapshotDetailsDao.persist(snapshotDetail);
451+
452+
snapshotDetail = new SnapshotDetailsVO(csSnapshotId, Constants.BASE_ONTAP_FV_ID, String.valueOf(ontapVolumeUuid), false);
453+
snapshotDetailsDao.persist(snapshotDetail);
454+
455+
snapshotDetail = new SnapshotDetailsVO(csSnapshotId, Constants.ONTAP_SNAP_ID, String.valueOf(ontapNewSnapshot), false);
456+
snapshotDetailsDao.persist(snapshotDetail);
457+
458+
snapshotDetail = new SnapshotDetailsVO(csSnapshotId, Constants.PRIMARY_POOL_ID, String.valueOf(storagePoolId), false);
459+
snapshotDetailsDao.persist(snapshotDetail);
460+
461+
snapshotDetail = new SnapshotDetailsVO(csSnapshotId, Constants.ONTAP_SNAP_SIZE, String.valueOf(ontapSnapSize), false);
462+
snapshotDetailsDao.persist(snapshotDetail);
463+
}
296464
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121

2222
import feign.QueryMap;
2323
import org.apache.cloudstack.storage.feign.model.ExportPolicy;
24+
import org.apache.cloudstack.storage.feign.model.FileClone;
2425
import org.apache.cloudstack.storage.feign.model.FileInfo;
26+
import org.apache.cloudstack.storage.feign.model.response.JobResponse;
2527
import org.apache.cloudstack.storage.feign.model.response.OntapResponse;
2628
import feign.Headers;
2729
import feign.Param;
@@ -58,6 +60,11 @@ void createFile(@Param("authHeader") String authHeader,
5860
@Param("path") String filePath,
5961
FileInfo file);
6062

63+
@RequestLine("POST /api/storage/volumes/{volumeUuid}/files/{path}")
64+
@Headers({"Authorization: {authHeader}"})
65+
JobResponse cloneFile(@Param("authHeader") String authHeader,
66+
FileClone fileClone);
67+
6168
// Export Policy Operations
6269
@RequestLine("POST /api/protocols/nfs/export-policies")
6370
@Headers({"Authorization: {authHeader}"})
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package org.apache.cloudstack.storage.feign.model;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
import com.fasterxml.jackson.annotation.JsonInclude;
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
7+
@JsonIgnoreProperties(ignoreUnknown = true)
8+
@JsonInclude(JsonInclude.Include.NON_NULL)
9+
public class FileClone {
10+
@JsonProperty("source_path")
11+
private String sourcePath;
12+
@JsonProperty("destination_path")
13+
private String destinationPath;
14+
@JsonProperty("volume")
15+
private VolumeConcise volume;
16+
public VolumeConcise getVolume() {
17+
return volume;
18+
}
19+
public void setVolume(VolumeConcise volume) {
20+
this.volume = volume;
21+
}
22+
public String getSourcePath() {
23+
return sourcePath;
24+
}
25+
public void setSourcePath(String sourcePath) {
26+
this.sourcePath = sourcePath;
27+
}
28+
public String getDestinationPath() {
29+
return destinationPath;
30+
}
31+
public void setDestinationPath(String destinationPath) {}
32+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.apache.cloudstack.storage.feign.model;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
import com.fasterxml.jackson.annotation.JsonInclude;
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
7+
@JsonIgnoreProperties(ignoreUnknown = true)
8+
@JsonInclude(JsonInclude.Include.NON_NULL)
9+
public class VolumeConcise {
10+
@JsonProperty("uuid")
11+
private String uuid;
12+
@JsonProperty("name")
13+
private String name;
14+
public String getUuid() {
15+
return uuid;
16+
}
17+
public void setUuid(String uuid) {
18+
this.uuid = uuid;
19+
}
20+
public String getName() {
21+
return name;
22+
}
23+
public void setName(String name) {}
24+
}

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -393,8 +393,6 @@ public String getStoragePath() {
393393
return targetIqn;
394394
}
395395

396-
397-
398396
/**
399397
* Get the network ip interface
400398
*
@@ -484,7 +482,19 @@ public String getNetworkInterface() {
484482
* @param cloudstackVolume the CloudStack volume to retrieve
485483
* @return the retrieved CloudStackVolume object
486484
*/
487-
abstract CloudStackVolume getCloudStackVolume(CloudStackVolume cloudstackVolume);
485+
public abstract CloudStackVolume getCloudStackVolume(CloudStackVolume cloudstackVolume);
486+
487+
/**
488+
* Method encapsulates the behavior based on the opted protocol in subclasses.
489+
* it is going to mimic
490+
* snapshotLun for iSCSI, FC protocols
491+
* snapshotFile for NFS3.0 and NFS4.1 protocols
492+
*
493+
*
494+
* @param cloudstackVolume the source CloudStack volume
495+
* @return the retrieved snapshot CloudStackVolume object
496+
*/
497+
public abstract CloudStackVolume snapshotCloudStackVolume(CloudStackVolume cloudstackVolume);
488498

489499
/**
490500
* Method encapsulates the behavior based on the opted protocol in subclasses
@@ -547,7 +557,7 @@ public String getNetworkInterface() {
547557
*/
548558
abstract void disableLogicalAccess(Map<String, String> values);
549559

550-
private Boolean jobPollForSuccess(String jobUUID) {
560+
public Boolean jobPollForSuccess(String jobUUID) {
551561
//Create URI for GET Job API
552562
int jobRetryCount = 0;
553563
Job jobResp = null;

0 commit comments

Comments
 (0)