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 @@ -313,7 +313,7 @@ private void deleteCloudStackVolumeSnapshot(SnapshotInfo snapshotInfo, CommandRe
commandResult.setSuccess(true);
commandResult.setResult(null);
} catch (Exception e) {
if (isSnapshotNotFoundError(e)) {
if (OntapStorageUtils.isOntapSnapshotNotFoundError(e)) {
logger.warn("deleteCloudStackVolumeSnapshot: ONTAP snapshot for CloudStack snapshot [{}] "
+ "already absent (idempotent success): {}", snapshotId, e.getMessage());
commandResult.setSuccess(true);
Expand All @@ -327,25 +327,6 @@ private void deleteCloudStackVolumeSnapshot(SnapshotInfo snapshotInfo, CommandRe
}
}

/**
* Returns true when the exception indicates the ONTAP snapshot was already removed.
* Delete is idempotent: a missing backend snapshot is treated as success.
*/
private boolean isSnapshotNotFoundError(Throwable error) {
if (error == null) {
return false;
}
String message = error.getMessage();
if (message != null) {
String lower = message.toLowerCase();
if (lower.contains("404") || lower.contains("not found") || lower.contains("does not exist")
|| lower.contains("entry doesn't exist")) {
return true;
}
}
return isSnapshotNotFoundError(error.getCause());
}

private long resolveSnapshotPoolId(String poolIdStr, long snapshotId) {
if (poolIdStr != null && !poolIdStr.isEmpty()) {
return Long.parseLong(poolIdStr);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -805,21 +805,35 @@ public void deleteFlexVolSnapshotForCloudStackVolume(String flexVolUuid, String
logger.info("deleteFlexVolSnapshotForCloudStackVolume: issuing ONTAP REST delete for snapshot [{}] "
+ "(uuid={}) on FlexVol [{}]", snapshotName, snapshotUuid, flexVolUuid);

JobResponse jobResponse = snapshotFeignClient.deleteSnapshot(getAuthHeader(), flexVolUuid, snapshotUuid);

if (jobResponse == null || jobResponse.getJob() == null) {
logger.debug("deleteFlexVolSnapshotForCloudStackVolume: no async job returned for snapshot [{}] "
+ "(uuid={}); treating HTTP success as completion", snapshotName, snapshotUuid);
} else {
logger.debug("deleteFlexVolSnapshotForCloudStackVolume: polling ONTAP delete job [{}] for snapshot [{}]",
jobResponse.getJob().getUuid(), snapshotName);
}
try {
JobResponse jobResponse = snapshotFeignClient.deleteSnapshot(getAuthHeader(), flexVolUuid, snapshotUuid);

if (jobResponse == null || jobResponse.getJob() == null) {
logger.debug("deleteFlexVolSnapshotForCloudStackVolume: no async job returned for snapshot [{}] "
+ "(uuid={}); treating HTTP success as completion", snapshotName, snapshotUuid);
} else {
logger.debug("deleteFlexVolSnapshotForCloudStackVolume: polling ONTAP delete job [{}] for snapshot [{}]",
jobResponse.getJob().getUuid(), snapshotName);
}

pollJobIfPresent(jobResponse, "delete FlexVol snapshot [" + snapshotName + "] uuid [" + snapshotUuid + "]",
OntapStorageConstants.ONTAP_SNAPSHOT_DELETE_JOB_MAX_RETRIES,
OntapStorageConstants.ONTAP_SNAPSHOT_DELETE_JOB_POLL_INTERVAL_MS);
pollJobIfPresent(jobResponse, "delete FlexVol snapshot [" + snapshotName + "] uuid [" + snapshotUuid + "]",
OntapStorageConstants.ONTAP_SNAPSHOT_DELETE_JOB_MAX_RETRIES,
OntapStorageConstants.ONTAP_SNAPSHOT_DELETE_JOB_POLL_INTERVAL_MS);

logger.info("deleteFlexVolSnapshotForCloudStackVolume: ONTAP FlexVol snapshot [{}] (uuid={}) removed from [{}]",
snapshotName, snapshotUuid, flexVolUuid);
logger.info("deleteFlexVolSnapshotForCloudStackVolume: ONTAP FlexVol snapshot [{}] (uuid={}) removed from [{}]",
snapshotName, snapshotUuid, flexVolUuid);
} catch (Exception e) {
if (OntapStorageUtils.isOntapSnapshotNotFoundError(e)) {
logger.warn("deleteFlexVolSnapshotForCloudStackVolume: ONTAP snapshot [{}] (uuid={}) on FlexVol [{}] "
+ "already absent; treating delete as success: {}", snapshotName, snapshotUuid, flexVolUuid,
e.getMessage());
return;
}
if (e instanceof CloudRuntimeException) {
throw (CloudRuntimeException) e;
}
throw new CloudRuntimeException("Failed to delete ONTAP FlexVol snapshot [" + snapshotName + "]: "
+ e.getMessage(), e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,23 @@ public static String extractUuidFromOntapJobDescription(String description, Stri
return remainder.isEmpty() ? null : remainder;
}

/**
* Returns true when the exception indicates the ONTAP snapshot was already removed.
* Delete workflows treat a missing backend snapshot as idempotent success.
*/
public static boolean isOntapSnapshotNotFoundError(Throwable error) {
if (error == null) {
return false;
}
String message = error.getMessage();
if (message != null) {
String lower = message.toLowerCase();
if (lower.contains("404") || lower.contains("not found") || lower.contains("does not exist")
|| lower.contains("entry doesn't exist")) {
return true;
}
}
return isOntapSnapshotNotFoundError(error.getCause());
}
Comment thread
rajiv-jain-netapp marked this conversation as resolved.

}
Original file line number Diff line number Diff line change
Expand Up @@ -929,4 +929,24 @@ void testDeleteFlexVolSnapshotForCloudStackVolume_PollsJobAndSucceeds() {

verify(snapshotFeignClient).deleteSnapshot(anyString(), eq("fv-uuid-1"), eq("snap-uuid-1"));
}

@Test
void testDeleteFlexVolSnapshotForCloudStackVolume_AlreadyAbsentOnOntap() {
Job job = new Job();
job.setUuid("delete-job-missing");
JobResponse response = new JobResponse();
response.setJob(job);
when(snapshotFeignClient.deleteSnapshot(anyString(), eq("fv-uuid-1"), eq("snap-uuid-1")))
.thenReturn(response);

Job failedJob = new Job();
failedJob.setUuid("delete-job-missing");
failedJob.setState(OntapStorageConstants.JOB_FAILURE);
failedJob.setMessage("entry doesn't exist");
when(jobFeignClient.getJobByUUID(anyString(), eq("delete-job-missing"))).thenReturn(failedJob);

storageStrategy.deleteFlexVolSnapshotForCloudStackVolume("fv-uuid-1", "snap-uuid-1", "snap-name-1");

verify(snapshotFeignClient).deleteSnapshot(anyString(), eq("fv-uuid-1"), eq("snap-uuid-1"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
*/
package org.apache.cloudstack.storage.utils;

import com.cloud.utils.exception.CloudRuntimeException;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class OntapStorageUtilsTest {
Expand Down Expand Up @@ -79,4 +81,16 @@ public void getIgroupName_truncates_whenOneCharOverMaxLength() {

assertEquals(OntapStorageConstants.IGROUP_NAME_MAX_LENGTH, result.length());
}

@Test
public void isOntapSnapshotNotFoundError_matchesEntryDoesNotExist() {
CloudRuntimeException ex = new CloudRuntimeException("Job failed with error: entry doesn't exist");
assertTrue(OntapStorageUtils.isOntapSnapshotNotFoundError(ex));
}

@Test
public void isOntapSnapshotNotFoundError_rejectsUnrelatedErrors() {
assertFalse(OntapStorageUtils.isOntapSnapshotNotFoundError(
new CloudRuntimeException("Job failed with error: permission denied")));
}
}
Comment thread
rajiv-jain-netapp marked this conversation as resolved.
Loading