diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java index bd35fc4f9112..2bc06c12ca6d 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java @@ -204,7 +204,8 @@ public void createAsync(DataStore dataStore, DataObject dataObject, AsyncComplet * - DATA or ROOT snapshot → new attachable data volume (ROOT is never bootable * via this path; bootable recovery is createTemplate(snapshotid) → deploy). * - Backend: iSCSI → POST /api/storage/luns (clone.source in .snapshot/); - * NFS → POST /api/storage/file/clone with snapshot.name. + * NFS → POST /api/storage/file/clone with source_path + * ./.snapshot//. * - IOPS: the new volume's min/max (snapshot values, or the root offering when one * is supplied). Applied here; the temporary template clone does not get a policy. * @@ -517,7 +518,8 @@ private Long getSnapshotIdForCloning(long volumeId) { * {@code createTemplate(snapshotid)} then deploy. *
  • iSCSI — {@code POST /api/storage/luns} with * {@code clone.source.name=/vol/<fv>/.snapshot/<snap>/<lun>}
  • - *
  • NFS3 — {@code POST /api/storage/file/clone} with {@code snapshot.name}
  • + *
  • NFS3 — {@code POST /api/storage/file/clone} with + * {@code source_path=./.snapshot/<snapshot-name>/<source-file>}
  • *
  • Not this path — in-place revert ({@code revertSnapshot}); VM/instance snapshots; * secondary-storage archive restore.
  • * @@ -2390,14 +2392,17 @@ private CloudStackVolume createCloneFileFromSnapshotRequest(StoragePoolVO storag String flexVolName = details.get(OntapStorageConstants.VOLUME_NAME); FileInfo file = new FileInfo(); - file.setPath(OntapStorageUtils.toFlexVolRelativePath(sourceVolumePath, flexVolName)); + // FlexVol-relative source path, for example "". + String relativeSourcePath = OntapStorageUtils.toFlexVolRelativePath(sourceVolumePath, flexVolName); + // Path assigned to file.setPath(), for example "./.snapshot//". + file.setPath(OntapStorageConstants.DOT + OntapStorageConstants.SNAPSHOT_PATH_SEGMENT + snapshotName + + OntapStorageConstants.SLASH + relativeSourcePath); CloudStackVolume request = new CloudStackVolume(); request.setDatastoreId(String.valueOf(storagePool.getId())); request.setVolumeInfo(volumeInfo); request.setFile(file); request.setDestinationPath(OntapStorageUtils.toFlexVolRelativePath(volumeInfo.getUuid(), flexVolName)); - request.setSnapshotName(snapshotName); return request; } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/FileCloneRequest.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/FileCloneRequest.java index cefff9fd282f..a9f2a106e9a8 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/FileCloneRequest.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/FileCloneRequest.java @@ -46,15 +46,6 @@ public class FileCloneRequest { @JsonProperty("overwrite_destination") private Boolean overwriteDestination; - /** - * Optional FlexVolume snapshot to clone from. When set, ONTAP clones {@code source_path} - * as it existed in that snapshot rather than from the live file/LUN. - * - *

    Used by create-volume-from-snapshot (same FlexVol). Omitted for live template-cache clones.

    - */ - @JsonProperty("snapshot") - private SnapshotRef snapshot; - public FileCloneRequest() { } @@ -64,14 +55,6 @@ public FileCloneRequest(String flexVolUuid, String flexVolName, String sourcePat this.destinationPath = destinationPath; } - public FileCloneRequest(String flexVolUuid, String flexVolName, String sourcePath, String destinationPath, - String snapshotName) { - this(flexVolUuid, flexVolName, sourcePath, destinationPath); - if (snapshotName != null && !snapshotName.isEmpty()) { - this.snapshot = new SnapshotRef(snapshotName); - } - } - public VolumeRef getVolume() { return volume; } @@ -104,14 +87,6 @@ public void setOverwriteDestination(Boolean overwriteDestination) { this.overwriteDestination = overwriteDestination; } - public SnapshotRef getSnapshot() { - return snapshot; - } - - public void setSnapshot(SnapshotRef snapshot) { - this.snapshot = snapshot; - } - @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public static class VolumeRef { @@ -147,37 +122,10 @@ public void setName(String name) { } } - /** - * Snapshot identity for {@code POST /api/storage/file/clone} when cloning from a FlexVol snapshot. - */ - @JsonIgnoreProperties(ignoreUnknown = true) - @JsonInclude(JsonInclude.Include.NON_NULL) - public static class SnapshotRef { - - @JsonProperty("name") - private String name; - - public SnapshotRef() { - } - - public SnapshotRef(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - } - @Override public String toString() { return "FileCloneRequest{volume=" + (volume != null ? volume.getUuid() : null) + ", sourcePath=" + sourcePath - + ", destinationPath=" + destinationPath - + ", snapshot=" + (snapshot != null ? snapshot.getName() : null) + "}"; + + ", destinationPath=" + destinationPath + "}"; } } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java index 5991ad7aef09..a19a90c59641 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java @@ -934,8 +934,9 @@ abstract public CloudStackVolume createTemplateCache(StoragePoolVO storagePool, *
      *
    • SAN — {@code lun.clone.source.name} set to * {@code /vol/<fv>/.snapshot/<snap>/<lun>}
    • - *
    • NAS — {@link CloudStackVolume#getSnapshotName()} set; it is sent as - * {@code snapshot.name} on {@code POST /api/storage/file/clone}
    • + *
    • NAS — {@code file.path} set to + * {@code ./.snapshot/<snapshot-name>/<source-file>} and sent as + * {@code source_path} on {@code POST /api/storage/file/clone}
    • *
    * * @param cloudstackVolume describes the clone to create; the source is carried in the diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java index 3025727d8e78..7973412feb5e 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java @@ -181,8 +181,7 @@ public void deleteCloudStackVolume(CloudStackVolume cloudstackVolume) { * Clones a file inside the FlexVolume using ONTAP's file clone API. * *

    The source is taken from {@code file.path} and the destination from - * {@code destinationPath}, both relative to the root of the FlexVolume backing the pool. - * When {@code snapshotName} is set, the source is read from that FlexVolume snapshot.

    + * {@code destinationPath}, both relative to the root of the FlexVolume backing the pool.

    */ @Override public CloudStackVolume cloneCloudStackVolume(CloudStackVolume cloudstackVolume) { @@ -203,12 +202,11 @@ public CloudStackVolume cloneCloudStackVolume(CloudStackVolume cloudstackVolume) } String sourcePath = cloudstackVolume.getFile().getPath(); String destinationPath = cloudstackVolume.getDestinationPath(); - String snapshotName = cloudstackVolume.getSnapshotName(); - logger.info("cloneCloudStackVolume: Cloning file [{}] to [{}] in FlexVol [{}] from snapshot [{}]", - sourcePath, destinationPath, flexVolName, snapshotName); + logger.info("cloneCloudStackVolume: Cloning file [{}] to [{}] in FlexVol [{}]", + sourcePath, destinationPath, flexVolName); try { - FileCloneRequest request = new FileCloneRequest(flexVolUuid, flexVolName, sourcePath, destinationPath, snapshotName); + FileCloneRequest request = new FileCloneRequest(flexVolUuid, flexVolName, sourcePath, destinationPath); JobResponse jobResponse = nasFeignClient.cloneFile(getAuthHeader(), request); pollJobIfPresent(jobResponse, "clone file [" + sourcePath + "] to [" + destinationPath + "]"); @@ -221,7 +219,6 @@ public CloudStackVolume cloneCloudStackVolume(CloudStackVolume cloudstackVolume) clonedCloudStackVolume.setFile(clonedFile); clonedCloudStackVolume.setDatastoreId(cloudstackVolume.getDatastoreId()); clonedCloudStackVolume.setVolumeInfo(cloudstackVolume.getVolumeInfo()); - clonedCloudStackVolume.setSnapshotName(snapshotName); return clonedCloudStackVolume; } catch (FeignException e) { logger.error("FeignException occurred while cloning file [{}], Status: {}, Exception: {}", diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/model/CloudStackVolume.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/model/CloudStackVolume.java index 5147e9ff7ac2..ab38e3045f51 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/model/CloudStackVolume.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/model/CloudStackVolume.java @@ -51,12 +51,6 @@ public class CloudStackVolume { */ private String destinationPath; - /** - * ONTAP FlexVolume snapshot name when cloning a new file/LUN from a snapshot - * (create-volume-from-snapshot). Null for live clones (e.g. template cache). - */ - private String snapshotName; - private DataObject volumeInfo; // This is needed as we need DataObject to be passed to agent to create volume public FileInfo getFile() { @@ -107,12 +101,4 @@ public void setDestinationPath(String destinationPath) { this.destinationPath = destinationPath; } - public String getSnapshotName() { - return snapshotName; - } - - public void setSnapshotName(String snapshotName) { - this.snapshotName = snapshotName; - } - } diff --git a/plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriverTest.java b/plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriverTest.java index ec55c72b9ccc..b675618ae2a8 100644 --- a/plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriverTest.java +++ b/plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriverTest.java @@ -1236,9 +1236,8 @@ void testCreateAsync_VolumeClonedFromSnapshot_NfsSuccess() { ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(CloudStackVolume.class); verify(nasStrategy).cloneCloudStackVolume(requestCaptor.capture()); CloudStackVolume request = requestCaptor.getValue(); - assertEquals("source-file-uuid", request.getFile().getPath()); + assertEquals("./.snapshot/snap_cs200/source-file-uuid", request.getFile().getPath()); assertEquals("new-volume-uuid", request.getDestinationPath()); - assertEquals("snap_cs200", request.getSnapshotName()); assertEquals("1", request.getDatastoreId()); verify(nasStrategy, never()).createCloudStackVolume(any()); } @@ -3461,9 +3460,9 @@ void testCopyAsync_NfsTemporarySnapshotCopy_SetsFilePath() { ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(CloudStackVolume.class); verify(nasStrategy).cloneCloudStackVolume(requestCaptor.capture()); - assertEquals("source-file-uuid", requestCaptor.getValue().getFile().getPath()); + assertEquals("./.snapshot/snap_cs200/source-file-uuid", + requestCaptor.getValue().getFile().getPath()); assertEquals("tmp-volume-uuid", requestCaptor.getValue().getDestinationPath()); - assertEquals("snap_cs200", requestCaptor.getValue().getSnapshotName()); assertEquals("tmp-volume-uuid", dbVolume.getPath()); assertEquals(Storage.ImageFormat.QCOW2, dbVolume.getFormat()); diff --git a/plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/service/UnifiedNASStrategyTest.java b/plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/service/UnifiedNASStrategyTest.java index 315ee7ed2127..60bdf64855e3 100755 --- a/plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/service/UnifiedNASStrategyTest.java +++ b/plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/service/UnifiedNASStrategyTest.java @@ -1176,11 +1176,10 @@ public void testCloneCloudStackVolume_Success() { assertEquals("volume-uuid", captor.getValue().getDestinationPath()); assertEquals("flexvol1", captor.getValue().getVolume().getName()); assertEquals("flexvol-uuid-1", captor.getValue().getVolume().getUuid()); - assertNull(captor.getValue().getSnapshot()); } @Test - public void testCloneCloudStackVolume_SnapshotSource_SendsSnapshotName() { + public void testCloneCloudStackVolume_SnapshotSource_UsesSnapshotSourcePath() { VolumeObject volumeObject = mock(VolumeObject.class); VolumeVO volumeVO = mock(VolumeVO.class); when(volumeObject.getId()).thenReturn(100L); @@ -1193,26 +1192,22 @@ public void testCloneCloudStackVolume_SnapshotSource_SendsSnapshotName() { when(storagePoolDetailsDao.listDetailsKeyPairs(1L)).thenReturn(details); FileInfo source = new FileInfo(); - source.setPath("source-file-uuid"); + source.setPath("./.snapshot/snap_cs200/source-file-uuid"); CloudStackVolume request = new CloudStackVolume(); request.setDatastoreId("1"); request.setVolumeInfo(volumeObject); request.setFile(source); request.setDestinationPath("new-volume-uuid"); - request.setSnapshotName("snap_cs200"); when(nasFeignClient.cloneFile(anyString(), any(FileCloneRequest.class))).thenReturn(new JobResponse()); CloudStackVolume result = strategy.cloneCloudStackVolume(request); assertEquals("new-volume-uuid", result.getFile().getPath()); - assertEquals("snap_cs200", result.getSnapshotName()); ArgumentCaptor captor = ArgumentCaptor.forClass(FileCloneRequest.class); verify(nasFeignClient).cloneFile(anyString(), captor.capture()); - assertEquals("source-file-uuid", captor.getValue().getSourcePath()); + assertEquals("./.snapshot/snap_cs200/source-file-uuid", captor.getValue().getSourcePath()); assertEquals("new-volume-uuid", captor.getValue().getDestinationPath()); - assertNotNull(captor.getValue().getSnapshot()); - assertEquals("snap_cs200", captor.getValue().getSnapshot().getName()); } @Test