Skip to content

Commit 324164b

Browse files
Merge pull request #96 from NetApp/feature/CSTACKEX-259
The ONTAP plugin supports local template caching on the primary storage pool during the creation of the first VM or instance when a template is selected from secondary storage. As part of this process, the template is copied and cached locally on the primary storage pool. For subsequent VM provisioning requests that use the same template and primary storage pool combination, a full template copy from secondary storage is no longer required. Instead, the ONTAP plugin leverages local cloning of the cached template within the primary storage pool, significantly reducing provisioning time and minimizing data transfer overhead.
2 parents 207e4e7 + 7ac5de1 commit 324164b

14 files changed

Lines changed: 2491 additions & 133 deletions

File tree

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

Lines changed: 541 additions & 47 deletions
Large diffs are not rendered by default.

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

Lines changed: 11 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.FileCloneRequest;
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,15 @@ void createFile(@Param("authHeader") String authHeader,
5860
@Param("path") String filePath,
5961
FileInfo file);
6062

63+
/**
64+
* Creates a space-efficient clone of a file within a FlexVolume.
65+
*
66+
* <p>ONTAP REST: {@code POST /api/storage/file/clone}</p>
67+
*/
68+
@RequestLine("POST /api/storage/file/clone")
69+
@Headers({"Authorization: {authHeader}", "Content-Type: application/json"})
70+
JobResponse cloneFile(@Param("authHeader") String authHeader, FileCloneRequest request);
71+
6172
// Export Policy Operations
6273
@RequestLine("POST /api/protocols/nfs/export-policies")
6374
@Headers({"Authorization: {authHeader}"})

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ public interface SANFeignClient {
5050
@Headers({"Authorization: {authHeader}"})
5151
Lun getLunByUUID(@Param("authHeader") String authHeader, @Param("uuid") String uuid);
5252

53-
@RequestLine("PATCH /{uuid}")
54-
@Headers({"Authorization: {authHeader}"})
53+
@RequestLine("PATCH /api/storage/luns/{uuid}")
54+
@Headers({"Authorization: {authHeader}", "Content-Type: application/json"})
5555
void updateLun(@Param("authHeader") String authHeader, @Param("uuid") String uuid, Lun lun);
5656

5757
@RequestLine("DELETE /api/storage/luns/{uuid}")
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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 file clone API.
27+
*
28+
* <p>ONTAP REST endpoint: {@code POST /api/storage/file/clone}</p>
29+
*
30+
* <p>Creates a space-efficient copy of a file. Source and destination paths are relative to the
31+
* root of {@code volume}, and both must live in that same FlexVolume.</p>
32+
*/
33+
@JsonIgnoreProperties(ignoreUnknown = true)
34+
@JsonInclude(JsonInclude.Include.NON_NULL)
35+
public class FileCloneRequest {
36+
37+
@JsonProperty("volume")
38+
private VolumeRef volume;
39+
40+
@JsonProperty("source_path")
41+
private String sourcePath;
42+
43+
@JsonProperty("destination_path")
44+
private String destinationPath;
45+
46+
@JsonProperty("overwrite_destination")
47+
private Boolean overwriteDestination;
48+
49+
public FileCloneRequest() {
50+
}
51+
52+
public FileCloneRequest(String flexVolUuid, String flexVolName, String sourcePath, String destinationPath) {
53+
this.volume = new VolumeRef(flexVolUuid, flexVolName);
54+
this.sourcePath = sourcePath;
55+
this.destinationPath = destinationPath;
56+
}
57+
58+
public VolumeRef getVolume() {
59+
return volume;
60+
}
61+
62+
public void setVolume(VolumeRef volume) {
63+
this.volume = volume;
64+
}
65+
66+
public String getSourcePath() {
67+
return sourcePath;
68+
}
69+
70+
public void setSourcePath(String sourcePath) {
71+
this.sourcePath = sourcePath;
72+
}
73+
74+
public String getDestinationPath() {
75+
return destinationPath;
76+
}
77+
78+
public void setDestinationPath(String destinationPath) {
79+
this.destinationPath = destinationPath;
80+
}
81+
82+
public Boolean getOverwriteDestination() {
83+
return overwriteDestination;
84+
}
85+
86+
public void setOverwriteDestination(Boolean overwriteDestination) {
87+
this.overwriteDestination = overwriteDestination;
88+
}
89+
90+
@JsonIgnoreProperties(ignoreUnknown = true)
91+
@JsonInclude(JsonInclude.Include.NON_NULL)
92+
public static class VolumeRef {
93+
94+
@JsonProperty("uuid")
95+
private String uuid;
96+
97+
@JsonProperty("name")
98+
private String name;
99+
100+
public VolumeRef() {
101+
}
102+
103+
public VolumeRef(String uuid, String name) {
104+
this.uuid = uuid;
105+
this.name = name;
106+
}
107+
108+
public String getUuid() {
109+
return uuid;
110+
}
111+
112+
public void setUuid(String uuid) {
113+
this.uuid = uuid;
114+
}
115+
116+
public String getName() {
117+
return name;
118+
}
119+
120+
public void setName(String name) {
121+
this.name = name;
122+
}
123+
}
124+
125+
@Override
126+
public String toString() {
127+
return "FileCloneRequest{volume=" + (volume != null ? volume.getUuid() : null)
128+
+ ", sourcePath=" + sourcePath
129+
+ ", destinationPath=" + destinationPath + "}";
130+
}
131+
}

plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Lun.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ private String toIndentedString(Object o) {
308308
}
309309

310310

311+
@JsonInclude(JsonInclude.Include.NON_NULL)
311312
public static class Clone {
312313
@JsonProperty("source")
313314
private Source source = null;
@@ -319,6 +320,7 @@ public void setSource(Source source) {
319320
}
320321
}
321322

323+
@JsonInclude(JsonInclude.Include.NON_NULL)
322324
public static class Source {
323325
@JsonProperty("name")
324326
private String name = null;

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

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.apache.cloudstack.storage.feign.model.Volume;
4444
import org.apache.cloudstack.storage.feign.model.response.JobResponse;
4545
import org.apache.cloudstack.storage.feign.model.response.OntapResponse;
46+
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
4647
import org.apache.cloudstack.storage.service.model.AccessGroup;
4748
import org.apache.cloudstack.storage.service.model.CloudStackVolume;
4849
import org.apache.cloudstack.storage.service.model.ProtocolType;
@@ -56,6 +57,8 @@
5657

5758
import feign.FeignException;
5859

60+
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
61+
5962
/**
6063
* Storage Strategy represents the communication path for all the ONTAP storage options
6164
*
@@ -624,6 +627,27 @@ private boolean isIPv4Address(String address) {
624627
*/
625628
abstract public CloudStackVolume createCloudStackVolume(CloudStackVolume cloudstackVolume);
626629

630+
/**
631+
* Creates the protocol-specific backend object that caches a template on this pool.
632+
*
633+
* <p>iSCSI creates an empty LUN ({@code /vol/&lt;flexVol&gt;/cs_tmpl_&lt;id&gt;}) sized to
634+
* {@code sizeInBytes}. NFS is a no-op on the array: the KVM agent later writes the qcow2
635+
* into the mounted FlexVolume.</p>
636+
*
637+
* <p>Returns a {@link CloudStackVolume} so the driver can map it to {@code CreateCmdResult}
638+
* and update {@code template_spool_ref}. SAN populates {@code lun}; NAS returns an empty
639+
* volume (no LUN / file yet).</p>
640+
*
641+
* @param storagePool CloudStack primary storage pool (one FlexVolume)
642+
* @param templateInfo template being cached
643+
* @param details pool details (SVM, protocol, etc.)
644+
* @param sizeInBytes virtual size for the cache object (required for SAN; ignored for NAS)
645+
* @return created cache identity, or an empty {@link CloudStackVolume} when nothing is
646+
* pre-created on the array
647+
*/
648+
abstract public CloudStackVolume createTemplateCache(StoragePoolVO storagePool, TemplateInfo templateInfo,
649+
Map<String, String> details, long sizeInBytes);
650+
627651
/**
628652
* Method encapsulates the behavior based on the opted protocol in subclasses.
629653
* it is going to mimic
@@ -648,14 +672,28 @@ private boolean isIPv4Address(String address) {
648672
abstract public void deleteCloudStackVolume(CloudStackVolume cloudstackVolume);
649673

650674
/**
651-
* Method encapsulates the behavior based on the opted protocol in subclasses.
675+
* Creates a space-efficient clone of an existing object inside the same FlexVolume.
652676
* it is going to mimic
653677
* cloneLun for iSCSI, FC protocols
654678
* cloneFile for NFS3.0 and NFS4.1 protocols
655679
* cloneNameSpace for Nvme/TCP and Nvme/FC protocol
656-
* @param cloudstackVolume the CloudStack volume to copy
680+
*
681+
* <p>ONTAP requires the source and the destination to live in the same FlexVolume, which
682+
* holds because a CloudStack primary storage pool maps one-to-one onto a FlexVolume.</p>
683+
*
684+
* @param cloudstackVolume describes the clone to create; the source is carried in the
685+
* protocol-specific clone reference (for SAN, {@code lun.clone.source})
686+
* @return the created CloudStackVolume, populated with the backend identity of the clone
687+
*/
688+
abstract public CloudStackVolume cloneCloudStackVolume(CloudStackVolume cloudstackVolume);
689+
690+
/**
691+
* Grows an existing backend object to {@code sizeInBytes}.
692+
*
693+
* <p>Needed after cloning a cached template, because a clone inherits the size of its source
694+
* while the service offering may ask for a larger disk.</p>
657695
*/
658-
abstract public void copyCloudStackVolume(CloudStackVolume cloudstackVolume);
696+
abstract public void resizeCloudStackVolume(CloudStackVolume cloudstackVolume, long sizeInBytes);
659697

660698
/**
661699
* Method encapsulates the behavior based on the opted protocol in subclasses.

0 commit comments

Comments
 (0)