Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@
import org.apache.cloudstack.storage.service.model.ProtocolType;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.cloudstack.storage.utils.OntapStorageUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -670,8 +669,8 @@ public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CreateCm

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

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

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

/**
* Builds a snapshot name with proper length constraints.
* Format: {@code <volumeName>-<snapshotUuid>}
* Builds an ONTAP-safe snapshot name from the CloudStack UI name with uniqueness suffix.
*/
private String buildSnapshotName(String volumeName, String snapshotUuid) {
String name = volumeName + "-" + snapshotUuid;
int maxLength = OntapStorageConstants.MAX_SNAPSHOT_NAME_LENGTH;
int trimRequired = name.length() - maxLength;

if (trimRequired > 0) {
name = StringUtils.left(volumeName, volumeName.length() - trimRequired) + "-" + snapshotUuid;
}
return name;
private String buildSnapshotName(String cloudStackSnapshotName, long snapshotId) {
return OntapStorageUtils.buildOntapSnapshotName(cloudStackSnapshotName, "cs" + snapshotId);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,96 @@ JobResponse restoreFileFromSnapshot(@Param("authHeader") String authHeader,
@Headers({"Authorization: {authHeader}", "Content-Type: application/json"})
JobResponse restoreFileFromSnapshotCli(@Param("authHeader") String authHeader,
CliSnapshotRestoreRequest request);

/**
* Creates a consistency group.
*
* <p>ONTAP REST: {@code POST /api/application/consistency-groups}</p>
*
* @param authHeader Basic auth header
* @param request consistency group create request body
* @return JobResponse containing the async job reference
*/
@RequestLine("POST /api/application/consistency-groups")
@Headers({"Authorization: {authHeader}", "Content-Type: application/json"})
JobResponse createConsistencyGroup(@Param("authHeader") String authHeader,
Map<String, Object> request);

/**
* Lists consistency groups.
*
* <p>ONTAP REST: {@code GET /api/application/consistency-groups}</p>
*
* @param authHeader Basic auth header
* @param queryParams Optional query parameters
* @return Paginated consistency group records
*/
@RequestLine("GET /api/application/consistency-groups")
@Headers({"Authorization: {authHeader}"})
OntapResponse<Map<String, Object>> getConsistencyGroups(@Param("authHeader") String authHeader,
@QueryMap Map<String, Object> queryParams);

/**
* Creates (starts) a consistency group snapshot.
*
* <p>ONTAP REST: {@code POST /api/application/consistency-groups/{cgUuid}/snapshots}</p>
*
* @param authHeader Basic auth header
* @param cgUuid consistency group UUID
* @param request snapshot start request body
* @return JobResponse containing the async job reference
*/
@RequestLine("POST /api/application/consistency-groups/{cgUuid}/snapshots")
@Headers({"Authorization: {authHeader}", "Content-Type: application/json"})
JobResponse createConsistencyGroupSnapshot(@Param("authHeader") String authHeader,
@Param("cgUuid") String cgUuid,
Map<String, Object> request);

/**
* Lists snapshots for a consistency group.
*
* <p>ONTAP REST: {@code GET /api/application/consistency-groups/{cgUuid}/snapshots}</p>
*
* @param authHeader Basic auth header
* @param cgUuid consistency group UUID
* @param queryParams Optional query parameters
* @return Paginated consistency group snapshot records
*/
@RequestLine("GET /api/application/consistency-groups/{cgUuid}/snapshots")
@Headers({"Authorization: {authHeader}"})
OntapResponse<Map<String, Object>> getConsistencyGroupSnapshots(@Param("authHeader") String authHeader,
@Param("cgUuid") String cgUuid,
@QueryMap Map<String, Object> queryParams);

/**
* Commits a started consistency group snapshot.
*
* <p>ONTAP REST: {@code PATCH /api/application/consistency-groups/{cgUuid}/snapshots/{snapshotUuid}}</p>
*
* @param authHeader Basic auth header
* @param cgUuid consistency group UUID
* @param snapshotUuid consistency group snapshot UUID
* @param request commit request body
* @return JobResponse containing the async job reference
*/
@RequestLine("PATCH /api/application/consistency-groups/{cgUuid}/snapshots/{snapshotUuid}")
@Headers({"Authorization: {authHeader}", "Content-Type: application/json"})
JobResponse commitConsistencyGroupSnapshot(@Param("authHeader") String authHeader,
@Param("cgUuid") String cgUuid,
@Param("snapshotUuid") String snapshotUuid,
Map<String, Object> request);

/**
* Deletes a consistency group.
*
* <p>ONTAP REST: {@code DELETE /api/application/consistency-groups/{cgUuid}}</p>
*
* @param authHeader Basic auth header
* @param cgUuid consistency group UUID
* @return JobResponse containing the async job reference
*/
@RequestLine("DELETE /api/application/consistency-groups/{cgUuid}")
@Headers({"Authorization: {authHeader}"})
JobResponse deleteConsistencyGroup(@Param("authHeader") String authHeader,
@Param("cgUuid") String cgUuid);
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ public class OntapStorageConstants {
public static final String ONTAP_SNAP_SIZE = "ontap_snap_size";
public static final String FILE_PATH = "file_path";
public static final int MAX_SNAPSHOT_NAME_LENGTH = 64;
public static final String ONTAP_TEMP_CG_PREFIX = "cs-temp-cg-";
public static final int ONTAP_CG_JOB_MAX_RETRIES = 60;
public static final int ONTAP_CG_JOB_POLL_INTERVAL_MS = 2000;

/** vm_snapshot_details key for ONTAP FlexVolume-level VM snapshots. */
public static final String ONTAP_FLEXVOL_SNAPSHOT = "ontapFlexVolSnapshot";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,46 @@ public static String getLunName(String volName, String lunName) {
return OntapStorageConstants.VOLUME_PATH_PREFIX + volName + OntapStorageConstants.SLASH + lunName;
}

/**
* Builds an ONTAP-safe name token from user-provided snapshot text.
*/
public static String getOntapCloneName(String cloudStackSnapshotName) {
if (cloudStackSnapshotName == null || cloudStackSnapshotName.trim().isEmpty()) {
throw new InvalidParameterValueException("Snapshot name cannot be null or blank");
}
String normalized = cloudStackSnapshotName.replaceAll("[^a-zA-Z0-9_]", "_");
if (normalized.isEmpty()) {
normalized = "snapshot";
}
if (!Character.isLetter(normalized.charAt(0))) {
normalized = "s_" + normalized;
}
if (normalized.length() > OntapStorageConstants.MAX_SNAPSHOT_NAME_LENGTH) {
normalized = normalized.substring(0, OntapStorageConstants.MAX_SNAPSHOT_NAME_LENGTH);
}
return normalized;
}

/**
* Builds an ONTAP-safe snapshot name that preserves the CloudStack UI snapshot name
* and appends a uniqueness suffix.
*/
public static String buildOntapSnapshotName(String cloudStackSnapshotName, String uniquenessSuffix) {
String normalizedBase = (cloudStackSnapshotName == null || cloudStackSnapshotName.trim().isEmpty())
? "snapshot"
: getOntapCloneName(cloudStackSnapshotName);
String suffix = (uniquenessSuffix == null || uniquenessSuffix.isEmpty())
? ""
: "_" + uniquenessSuffix.replaceAll("[^a-zA-Z0-9_]", "_");
int maxLength = OntapStorageConstants.MAX_SNAPSHOT_NAME_LENGTH;
int maxBaseLength = maxLength - suffix.length();
if (maxBaseLength <= 0) {
return normalizedBase.substring(0, maxLength);
}
if (normalizedBase.length() > maxBaseLength) {
normalizedBase = normalizedBase.substring(0, maxBaseLength);
}
return normalizedBase + suffix;
}

}
Loading
Loading