Skip to content

Commit 3efaa7a

Browse files
CSTACKEX:200 - added temp CG logic for VM snapshots if VM span across multiple volumes at storage
1 parent 5af4bf9 commit 3efaa7a

6 files changed

Lines changed: 671 additions & 123 deletions

File tree

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

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@
6767
import org.apache.cloudstack.storage.service.model.ProtocolType;
6868
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
6969
import org.apache.cloudstack.storage.utils.OntapStorageUtils;
70-
import org.apache.commons.lang3.StringUtils;
7170
import org.apache.logging.log4j.LogManager;
7271
import org.apache.logging.log4j.Logger;
7372
import org.jetbrains.annotations.Nullable;
@@ -670,8 +669,8 @@ public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CreateCm
670669

671670
SnapshotObjectTO snapshotObjectTo = (SnapshotObjectTO) snapshot.getTO();
672671

673-
// Build snapshot name using volume name and snapshot UUID
674-
String snapshotName = buildSnapshotName(volumeInfo.getName(), snapshot.getUuid());
672+
// Preserve CloudStack UI snapshot name with stable uniqueness suffix.
673+
String snapshotName = buildSnapshotName(snapshot.getName(), snapshot.getId());
675674

676675
// Resolve the volume path for storing in snapshot details (for revert operation)
677676
String volumePath = resolveVolumePathOnOntap(volumeVO, protocol, poolDetails);
@@ -975,18 +974,10 @@ private CloudStackVolume createDeleteCloudStackVolumeRequest(StoragePool storage
975974
// ──────────────────────────────────────────────────────────────────────────
976975

977976
/**
978-
* Builds a snapshot name with proper length constraints.
979-
* Format: {@code <volumeName>-<snapshotUuid>}
977+
* Builds an ONTAP-safe snapshot name from the CloudStack UI name with uniqueness suffix.
980978
*/
981-
private String buildSnapshotName(String volumeName, String snapshotUuid) {
982-
String name = volumeName + "-" + snapshotUuid;
983-
int maxLength = OntapStorageConstants.MAX_SNAPSHOT_NAME_LENGTH;
984-
int trimRequired = name.length() - maxLength;
985-
986-
if (trimRequired > 0) {
987-
name = StringUtils.left(volumeName, volumeName.length() - trimRequired) + "-" + snapshotUuid;
988-
}
989-
return name;
979+
private String buildSnapshotName(String cloudStackSnapshotName, long snapshotId) {
980+
return OntapStorageUtils.buildOntapSnapshotName(cloudStackSnapshotName, "cs" + snapshotId);
990981
}
991982

992983
/**

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

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,4 +181,96 @@ JobResponse restoreFileFromSnapshot(@Param("authHeader") String authHeader,
181181
@Headers({"Authorization: {authHeader}", "Content-Type: application/json"})
182182
JobResponse restoreFileFromSnapshotCli(@Param("authHeader") String authHeader,
183183
CliSnapshotRestoreRequest request);
184+
185+
/**
186+
* Creates a consistency group.
187+
*
188+
* <p>ONTAP REST: {@code POST /api/application/consistency-groups}</p>
189+
*
190+
* @param authHeader Basic auth header
191+
* @param request consistency group create request body
192+
* @return JobResponse containing the async job reference
193+
*/
194+
@RequestLine("POST /api/application/consistency-groups")
195+
@Headers({"Authorization: {authHeader}", "Content-Type: application/json"})
196+
JobResponse createConsistencyGroup(@Param("authHeader") String authHeader,
197+
Map<String, Object> request);
198+
199+
/**
200+
* Lists consistency groups.
201+
*
202+
* <p>ONTAP REST: {@code GET /api/application/consistency-groups}</p>
203+
*
204+
* @param authHeader Basic auth header
205+
* @param queryParams Optional query parameters
206+
* @return Paginated consistency group records
207+
*/
208+
@RequestLine("GET /api/application/consistency-groups")
209+
@Headers({"Authorization: {authHeader}"})
210+
OntapResponse<Map<String, Object>> getConsistencyGroups(@Param("authHeader") String authHeader,
211+
@QueryMap Map<String, Object> queryParams);
212+
213+
/**
214+
* Creates (starts) a consistency group snapshot.
215+
*
216+
* <p>ONTAP REST: {@code POST /api/application/consistency-groups/{cgUuid}/snapshots}</p>
217+
*
218+
* @param authHeader Basic auth header
219+
* @param cgUuid consistency group UUID
220+
* @param request snapshot start request body
221+
* @return JobResponse containing the async job reference
222+
*/
223+
@RequestLine("POST /api/application/consistency-groups/{cgUuid}/snapshots")
224+
@Headers({"Authorization: {authHeader}", "Content-Type: application/json"})
225+
JobResponse createConsistencyGroupSnapshot(@Param("authHeader") String authHeader,
226+
@Param("cgUuid") String cgUuid,
227+
Map<String, Object> request);
228+
229+
/**
230+
* Lists snapshots for a consistency group.
231+
*
232+
* <p>ONTAP REST: {@code GET /api/application/consistency-groups/{cgUuid}/snapshots}</p>
233+
*
234+
* @param authHeader Basic auth header
235+
* @param cgUuid consistency group UUID
236+
* @param queryParams Optional query parameters
237+
* @return Paginated consistency group snapshot records
238+
*/
239+
@RequestLine("GET /api/application/consistency-groups/{cgUuid}/snapshots")
240+
@Headers({"Authorization: {authHeader}"})
241+
OntapResponse<Map<String, Object>> getConsistencyGroupSnapshots(@Param("authHeader") String authHeader,
242+
@Param("cgUuid") String cgUuid,
243+
@QueryMap Map<String, Object> queryParams);
244+
245+
/**
246+
* Commits a started consistency group snapshot.
247+
*
248+
* <p>ONTAP REST: {@code PATCH /api/application/consistency-groups/{cgUuid}/snapshots/{snapshotUuid}}</p>
249+
*
250+
* @param authHeader Basic auth header
251+
* @param cgUuid consistency group UUID
252+
* @param snapshotUuid consistency group snapshot UUID
253+
* @param request commit request body
254+
* @return JobResponse containing the async job reference
255+
*/
256+
@RequestLine("PATCH /api/application/consistency-groups/{cgUuid}/snapshots/{snapshotUuid}")
257+
@Headers({"Authorization: {authHeader}", "Content-Type: application/json"})
258+
JobResponse commitConsistencyGroupSnapshot(@Param("authHeader") String authHeader,
259+
@Param("cgUuid") String cgUuid,
260+
@Param("snapshotUuid") String snapshotUuid,
261+
Map<String, Object> request);
262+
263+
/**
264+
* Deletes a consistency group.
265+
*
266+
* <p>ONTAP REST: {@code DELETE /api/application/consistency-groups/{cgUuid}}</p>
267+
*
268+
* @param authHeader Basic auth header
269+
* @param cgUuid consistency group UUID
270+
* @return JobResponse containing the async job reference
271+
*/
272+
@RequestLine("DELETE /api/application/consistency-groups/{cgUuid}")
273+
@Headers({"Authorization: {authHeader}"})
274+
JobResponse deleteConsistencyGroup(@Param("authHeader") String authHeader,
275+
@Param("cgUuid") String cgUuid);
184276
}

plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/OntapStorageConstants.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ public class OntapStorageConstants {
105105
public static final String ONTAP_SNAP_SIZE = "ontap_snap_size";
106106
public static final String FILE_PATH = "file_path";
107107
public static final int MAX_SNAPSHOT_NAME_LENGTH = 64;
108+
public static final String ONTAP_TEMP_CG_PREFIX = "cs-temp-cg-";
109+
public static final int ONTAP_CG_JOB_MAX_RETRIES = 60;
110+
public static final int ONTAP_CG_JOB_POLL_INTERVAL_MS = 2000;
108111

109112
/** vm_snapshot_details key for ONTAP FlexVolume-level VM snapshots. */
110113
public static final String ONTAP_FLEXVOL_SNAPSHOT = "ontapFlexVolSnapshot";

plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/OntapStorageUtils.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,46 @@ public static String getLunName(String volName, String lunName) {
154154
return OntapStorageConstants.VOLUME_PATH_PREFIX + volName + OntapStorageConstants.SLASH + lunName;
155155
}
156156

157+
/**
158+
* Builds an ONTAP-safe name token from user-provided snapshot text.
159+
*/
160+
public static String getOntapCloneName(String cloudStackSnapshotName) {
161+
if (cloudStackSnapshotName == null || cloudStackSnapshotName.trim().isEmpty()) {
162+
throw new InvalidParameterValueException("Snapshot name cannot be null or blank");
163+
}
164+
String normalized = cloudStackSnapshotName.replaceAll("[^a-zA-Z0-9_]", "_");
165+
if (normalized.isEmpty()) {
166+
normalized = "snapshot";
167+
}
168+
if (!Character.isLetter(normalized.charAt(0))) {
169+
normalized = "s_" + normalized;
170+
}
171+
if (normalized.length() > OntapStorageConstants.MAX_SNAPSHOT_NAME_LENGTH) {
172+
normalized = normalized.substring(0, OntapStorageConstants.MAX_SNAPSHOT_NAME_LENGTH);
173+
}
174+
return normalized;
175+
}
176+
177+
/**
178+
* Builds an ONTAP-safe snapshot name that preserves the CloudStack UI snapshot name
179+
* and appends a uniqueness suffix.
180+
*/
181+
public static String buildOntapSnapshotName(String cloudStackSnapshotName, String uniquenessSuffix) {
182+
String normalizedBase = (cloudStackSnapshotName == null || cloudStackSnapshotName.trim().isEmpty())
183+
? "snapshot"
184+
: getOntapCloneName(cloudStackSnapshotName);
185+
String suffix = (uniquenessSuffix == null || uniquenessSuffix.isEmpty())
186+
? ""
187+
: "_" + uniquenessSuffix.replaceAll("[^a-zA-Z0-9_]", "_");
188+
int maxLength = OntapStorageConstants.MAX_SNAPSHOT_NAME_LENGTH;
189+
int maxBaseLength = maxLength - suffix.length();
190+
if (maxBaseLength <= 0) {
191+
return normalizedBase.substring(0, maxLength);
192+
}
193+
if (normalizedBase.length() > maxBaseLength) {
194+
normalizedBase = normalizedBase.substring(0, maxBaseLength);
195+
}
196+
return normalizedBase + suffix;
197+
}
198+
157199
}

0 commit comments

Comments
 (0)