Skip to content

Commit f42552b

Browse files
CSTACKEX-18_2: NFS3 snapshot changes
1 parent eace4ee commit f42552b

File tree

10 files changed

+418
-4
lines changed

10 files changed

+418
-4
lines changed

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

Lines changed: 168 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import com.cloud.storage.Volume;
3030
import com.cloud.storage.VolumeVO;
3131
import com.cloud.storage.ScopeType;
32+
import com.cloud.storage.dao.SnapshotDetailsDao;
33+
import com.cloud.storage.dao.SnapshotDetailsVO;
3234
import com.cloud.storage.dao.VolumeDao;
3335
import com.cloud.storage.dao.VolumeDetailsDao;
3436
import com.cloud.utils.Pair;
@@ -47,18 +49,22 @@
4749
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
4850
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
4951
import org.apache.cloudstack.storage.command.CommandResult;
52+
import org.apache.cloudstack.storage.command.CreateObjectAnswer;
5053
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
5154
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
5255
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
56+
import org.apache.cloudstack.storage.feign.model.FileInfo;
5357
import org.apache.cloudstack.storage.feign.model.Lun;
5458
import org.apache.cloudstack.storage.service.SANStrategy;
5559
import org.apache.cloudstack.storage.service.StorageStrategy;
5660
import org.apache.cloudstack.storage.service.UnifiedSANStrategy;
5761
import org.apache.cloudstack.storage.service.model.AccessGroup;
5862
import org.apache.cloudstack.storage.service.model.CloudStackVolume;
5963
import org.apache.cloudstack.storage.service.model.ProtocolType;
64+
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
6065
import org.apache.cloudstack.storage.utils.Constants;
6166
import org.apache.cloudstack.storage.utils.Utility;
67+
import org.apache.commons.lang3.StringUtils;
6268
import org.apache.logging.log4j.LogManager;
6369
import org.apache.logging.log4j.Logger;
6470

@@ -80,14 +86,16 @@ public class OntapPrimaryDatastoreDriver implements PrimaryDataStoreDriver {
8086
@Inject private VMInstanceDao vmDao;
8187
@Inject private VolumeDao volumeDao;
8288
@Inject private VolumeDetailsDao volumeDetailsDao;
89+
@Inject private SnapshotDetailsDao snapshotDetailsDao;
90+
8391
@Override
8492
public Map<String, String> getCapabilities() {
8593
s_logger.trace("OntapPrimaryDatastoreDriver: getCapabilities: Called");
8694
Map<String, String> mapCapabilities = new HashMap<>();
8795
// RAW managed initial implementation: snapshot features not yet supported
8896
// TODO Set it to false once we start supporting snapshot feature
89-
mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.FALSE.toString());
90-
mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.FALSE.toString());
97+
mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.TRUE.toString());
98+
mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.TRUE.toString());
9199
return mapCapabilities;
92100
}
93101

@@ -525,6 +533,81 @@ public long getUsedIops(StoragePool storagePool) {
525533
@Override
526534
public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CreateCmdResult> callback) {
527535

536+
CreateCmdResult result;
537+
538+
try {
539+
VolumeInfo volumeInfo = snapshot.getBaseVolume();
540+
541+
VolumeVO volumeVO = volumeDao.findById(volumeInfo.getId());
542+
if(volumeVO == null) {
543+
throw new CloudRuntimeException("takeSnapshot: VolumeVO not found for id: " + volumeInfo.getId());
544+
}
545+
546+
/** we are keeping file path at volumeVO.getPath() */
547+
548+
StoragePoolVO storagePool = storagePoolDao.findById(volumeVO.getPoolId());
549+
if(storagePool == null) {
550+
s_logger.error("takeSnapshot : Storage Pool not found for id: " + volumeVO.getPoolId());
551+
throw new CloudRuntimeException("takeSnapshot : Storage Pool not found for id: " + volumeVO.getPoolId());
552+
}
553+
Map<String, String> poolDetails = storagePoolDetailsDao.listDetailsKeyPairs(volumeVO.getPoolId());
554+
StorageStrategy storageStrategy = Utility.getStrategyByStoragePoolDetails(poolDetails);
555+
556+
Map<String, String> cloudStackVolumeRequestMap = new HashMap<>();
557+
cloudStackVolumeRequestMap.put(Constants.VOLUME_UUID, poolDetails.get(Constants.VOLUME_UUID));
558+
cloudStackVolumeRequestMap.put(Constants.FILE_PATH, volumeVO.getPath());
559+
CloudStackVolume cloudStackVolume = storageStrategy.getCloudStackVolume(cloudStackVolumeRequestMap);
560+
if (cloudStackVolume == null || cloudStackVolume.getFile() == null) {
561+
throw new CloudRuntimeException("takeSnapshot: Failed to get source file to take snapshot");
562+
}
563+
long capacityBytes = storagePool.getCapacityBytes();
564+
565+
long usedBytes = getUsedBytes(storagePool);
566+
long fileSize = cloudStackVolume.getFile().getSize();
567+
568+
usedBytes += fileSize;
569+
570+
if (usedBytes > capacityBytes) {
571+
throw new CloudRuntimeException("Insufficient space remains in this primary storage to take a snapshot");
572+
}
573+
574+
storagePool.setUsedBytes(usedBytes);
575+
576+
SnapshotObjectTO snapshotObjectTo = (SnapshotObjectTO)snapshot.getTO();
577+
578+
String fileSnapshotName = volumeInfo.getName() + "-" + snapshot.getUuid();
579+
580+
int maxSnapshotNameLength = 64;
581+
int trimRequired = fileSnapshotName.length() - maxSnapshotNameLength;
582+
583+
if (trimRequired > 0) {
584+
fileSnapshotName = StringUtils.left(volumeInfo.getName(), (volumeInfo.getName().length() - trimRequired)) + "-" + snapshot.getUuid();
585+
}
586+
587+
CloudStackVolume snapCloudStackVolumeRequest = snapshotCloudStackVolumeRequestByProtocol(poolDetails, volumeVO.getPath(), fileSnapshotName);
588+
CloudStackVolume cloneCloudStackVolume = storageStrategy.snapshotCloudStackVolume(snapCloudStackVolumeRequest);
589+
590+
updateSnapshotDetails(snapshot.getId(), volumeInfo.getId(), poolDetails.get(Constants.VOLUME_UUID), cloneCloudStackVolume.getFile().getPath(), volumeVO.getPoolId(), fileSize);
591+
592+
snapshotObjectTo.setPath(Constants.ONTAP_SNAP_ID +"="+cloneCloudStackVolume.getFile().getPath());
593+
594+
/** Update size for the storage-pool including snapshot size */
595+
storagePoolDao.update(volumeVO.getPoolId(), storagePool);
596+
597+
CreateObjectAnswer createObjectAnswer = new CreateObjectAnswer(snapshotObjectTo);
598+
599+
result = new CreateCmdResult(null, createObjectAnswer);
600+
601+
result.setResult(null);
602+
}
603+
catch (Exception ex) {
604+
s_logger.error("takeSnapshot: Failed due to ", ex);
605+
result = new CreateCmdResult(null, new CreateObjectAnswer(ex.toString()));
606+
607+
result.setResult(ex.toString());
608+
}
609+
610+
callback.complete(result);
528611
}
529612

530613
@Override
@@ -622,4 +705,87 @@ private CloudStackVolume createDeleteCloudStackVolumeRequest(StoragePool storage
622705
return cloudStackVolumeDeleteRequest;
623706

624707
}
708+
709+
private CloudStackVolume getCloudStackVolumeRequestByProtocol(Map<String, String> details, String filePath) {
710+
CloudStackVolume cloudStackVolumeRequest = null;
711+
ProtocolType protocolType = null;
712+
String protocol = null;
713+
714+
try {
715+
protocol = details.get(Constants.PROTOCOL);
716+
protocolType = ProtocolType.valueOf(protocol);
717+
} catch (IllegalArgumentException e) {
718+
throw new CloudRuntimeException("getCloudStackVolumeRequestByProtocol: Protocol: "+ protocol +" is not valid");
719+
}
720+
switch (protocolType) {
721+
case NFS3:
722+
cloudStackVolumeRequest = new CloudStackVolume();
723+
FileInfo fileInfo = new FileInfo();
724+
fileInfo.setPath(filePath);
725+
cloudStackVolumeRequest.setFile(fileInfo);
726+
String volumeUuid = details.get(Constants.VOLUME_UUID);
727+
cloudStackVolumeRequest.setFlexVolumeUuid(volumeUuid);
728+
break;
729+
default:
730+
throw new CloudRuntimeException("createCloudStackVolumeRequestByProtocol: Unsupported protocol " + protocol);
731+
}
732+
return cloudStackVolumeRequest;
733+
}
734+
735+
private CloudStackVolume snapshotCloudStackVolumeRequestByProtocol(Map<String, String> details,
736+
String sourcePath,
737+
String destinationPath) {
738+
CloudStackVolume cloudStackVolumeRequest = null;
739+
ProtocolType protocolType = null;
740+
String protocol = null;
741+
742+
try {
743+
protocol = details.get(Constants.PROTOCOL);
744+
protocolType = ProtocolType.valueOf(protocol);
745+
} catch (IllegalArgumentException e) {
746+
throw new CloudRuntimeException("getCloudStackVolumeRequestByProtocol: Protocol: "+ protocol +" is not valid");
747+
}
748+
switch (protocolType) {
749+
case NFS3:
750+
cloudStackVolumeRequest = new CloudStackVolume();
751+
FileInfo fileInfo = new FileInfo();
752+
fileInfo.setPath(sourcePath);
753+
cloudStackVolumeRequest.setFile(fileInfo);
754+
String volumeUuid = details.get(Constants.VOLUME_UUID);
755+
cloudStackVolumeRequest.setFlexVolumeUuid(volumeUuid);
756+
cloudStackVolumeRequest.setDestinationPath(destinationPath);
757+
break;
758+
default:
759+
throw new CloudRuntimeException("createCloudStackVolumeRequestByProtocol: Unsupported protocol " + protocol);
760+
761+
}
762+
return cloudStackVolumeRequest;
763+
}
764+
765+
/**
766+
*
767+
* @param csSnapshotId: generated snapshot id from cloudstack
768+
* @param csVolumeId: Source CS volume id
769+
* @param ontapVolumeUuid: storage flexvolume id
770+
* @param ontapNewSnapshot: generated snapshot id from ONTAP
771+
* @param storagePoolId: primary storage pool id
772+
* @param ontapSnapSize: Size of snapshot CS volume(LUN/file)
773+
*/
774+
private void updateSnapshotDetails(long csSnapshotId, long csVolumeId, String ontapVolumeUuid, String ontapNewSnapshot, long storagePoolId, long ontapSnapSize) {
775+
SnapshotDetailsVO snapshotDetail = new SnapshotDetailsVO(csSnapshotId, Constants.SRC_CS_VOLUME_ID, String.valueOf(csVolumeId), false);
776+
snapshotDetailsDao.persist(snapshotDetail);
777+
778+
snapshotDetail = new SnapshotDetailsVO(csSnapshotId, Constants.BASE_ONTAP_FV_ID, String.valueOf(ontapVolumeUuid), false);
779+
snapshotDetailsDao.persist(snapshotDetail);
780+
781+
snapshotDetail = new SnapshotDetailsVO(csSnapshotId, Constants.ONTAP_SNAP_ID, String.valueOf(ontapNewSnapshot), false);
782+
snapshotDetailsDao.persist(snapshotDetail);
783+
784+
snapshotDetail = new SnapshotDetailsVO(csSnapshotId, Constants.PRIMARY_POOL_ID, String.valueOf(storagePoolId), false);
785+
snapshotDetailsDao.persist(snapshotDetail);
786+
787+
snapshotDetail = new SnapshotDetailsVO(csSnapshotId, Constants.ONTAP_SNAP_SIZE, String.valueOf(ontapSnapSize), false);
788+
snapshotDetailsDao.persist(snapshotDetail);
789+
}
790+
625791
}

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: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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+
20+
package org.apache.cloudstack.storage.feign.model;
21+
22+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
23+
import com.fasterxml.jackson.annotation.JsonInclude;
24+
import com.fasterxml.jackson.annotation.JsonProperty;
25+
26+
@JsonIgnoreProperties(ignoreUnknown = true)
27+
@JsonInclude(JsonInclude.Include.NON_NULL)
28+
public class FileClone {
29+
@JsonProperty("source_path")
30+
private String sourcePath;
31+
@JsonProperty("destination_path")
32+
private String destinationPath;
33+
@JsonProperty("volume")
34+
private VolumeConcise volume;
35+
public VolumeConcise getVolume() {
36+
return volume;
37+
}
38+
public void setVolume(VolumeConcise volume) {
39+
this.volume = volume;
40+
}
41+
public String getSourcePath() {
42+
return sourcePath;
43+
}
44+
public void setSourcePath(String sourcePath) {
45+
this.sourcePath = sourcePath;
46+
}
47+
public String getDestinationPath() {
48+
return destinationPath;
49+
}
50+
public void setDestinationPath(String destinationPath) {}
51+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
20+
package org.apache.cloudstack.storage.feign.model;
21+
22+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
23+
import com.fasterxml.jackson.annotation.JsonInclude;
24+
import com.fasterxml.jackson.annotation.JsonProperty;
25+
26+
@JsonIgnoreProperties(ignoreUnknown = true)
27+
@JsonInclude(JsonInclude.Include.NON_NULL)
28+
public class VolumeConcise {
29+
@JsonProperty("uuid")
30+
private String uuid;
31+
@JsonProperty("name")
32+
private String name;
33+
public String getUuid() {
34+
return uuid;
35+
}
36+
public void setUuid(String uuid) {
37+
this.uuid = uuid;
38+
}
39+
public String getName() {
40+
return name;
41+
}
42+
public void setName(String name) {}
43+
}

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,19 @@ public String getNetworkInterface() {
506506
*/
507507
abstract public CloudStackVolume getCloudStackVolume(Map<String, String> cloudStackVolumeMap);
508508

509+
/**
510+
* Method encapsulates the behavior based on the opted protocol in subclasses.
511+
* it is going to mimic
512+
* snapshotLun for iSCSI, FC protocols
513+
* snapshotFile for NFS3.0 and NFS4.1 protocols
514+
*
515+
*
516+
* @param cloudstackVolume the source CloudStack volume
517+
* @return the retrieved snapshot CloudStackVolume object
518+
*/
519+
public abstract CloudStackVolume snapshotCloudStackVolume(CloudStackVolume cloudstackVolume);
520+
521+
509522
/**
510523
* Method encapsulates the behavior based on the opted protocol in subclasses
511524
* createiGroup for iSCSI and FC protocols
@@ -569,7 +582,7 @@ public String getNetworkInterface() {
569582
*/
570583
abstract public Map<String, String> getLogicalAccess(Map<String, String> values);
571584

572-
private Boolean jobPollForSuccess(String jobUUID, int maxRetries, int sleepTimeInSecs) {
585+
protected Boolean jobPollForSuccess(String jobUUID, int maxRetries, int sleepTimeInSecs) {
573586
//Create URI for GET Job API
574587
int jobRetryCount = 0;
575588
Job jobResp = null;

0 commit comments

Comments
 (0)