4343import org .apache .cloudstack .engine .subsystem .api .storage .DataObject ;
4444import org .apache .cloudstack .engine .subsystem .api .storage .DataStore ;
4545import org .apache .cloudstack .engine .subsystem .api .storage .DataStoreCapabilities ;
46- import org .apache .cloudstack .engine .subsystem .api .storage .EndPoint ;
47- import org .apache .cloudstack .engine .subsystem .api .storage .EndPointSelector ;
4846import org .apache .cloudstack .engine .subsystem .api .storage .PrimaryDataStoreDriver ;
4947import org .apache .cloudstack .engine .subsystem .api .storage .SnapshotInfo ;
50- import org .apache .cloudstack .engine .subsystem .api .storage .StorageAction ;
5148import org .apache .cloudstack .engine .subsystem .api .storage .TemplateInfo ;
5249import org .apache .cloudstack .engine .subsystem .api .storage .VolumeInfo ;
5350import org .apache .cloudstack .framework .async .AsyncCompletionCallback ;
5451import org .apache .cloudstack .storage .command .CommandResult ;
5552import org .apache .cloudstack .storage .command .CreateObjectAnswer ;
56- import org .apache .cloudstack .storage .command .DeleteCommand ;
5753import org .apache .cloudstack .storage .datastore .db .PrimaryDataStoreDao ;
5854import org .apache .cloudstack .storage .datastore .db .StoragePoolDetailsDao ;
5955import org .apache .cloudstack .storage .datastore .db .StoragePoolVO ;
@@ -94,7 +90,6 @@ public class OntapPrimaryDatastoreDriver implements PrimaryDataStoreDriver {
9490 @ Inject private VolumeDao volumeDao ;
9591 @ Inject private VolumeDetailsDao volumeDetailsDao ;
9692 @ Inject private SnapshotDetailsDao snapshotDetailsDao ;
97- @ Inject private EndPointSelector epSelector ;
9893
9994 @ Override
10095 public Map <String , String > getCapabilities () {
@@ -239,8 +234,7 @@ private CloudStackVolume createCloudStackVolume(DataStore dataStore, DataObject
239234 * Deletes a volume or snapshot from the ONTAP storage system.
240235 *
241236 * <p>For volumes, deletes the backend storage object (LUN for iSCSI, no-op for NFS).
242- * For snapshots, delegates to the hypervisor agent to delete the QCOW2 snapshot
243- * from the NFS-mounted ONTAP volume.</p>
237+ * For snapshots, deletes the FlexVolume snapshot from ONTAP that was created by takeSnapshot.</p>
244238 */
245239 @ Override
246240 public void deleteAsync (DataStore store , DataObject data , AsyncCompletionCallback <CommandResult > callback ) {
@@ -266,23 +260,8 @@ public void deleteAsync(DataStore store, DataObject data, AsyncCompletionCallbac
266260 commandResult .setResult (null );
267261 commandResult .setSuccess (true );
268262 } else if (data .getType () == DataObjectType .SNAPSHOT ) {
269- // Delegate snapshot deletion to the hypervisor agent, same as
270- // CloudStackPrimaryDataStoreDriverImpl. The KVM host deletes the
271- // QCOW2 snapshot file from the NFS-mounted ONTAP volume.
272- DeleteCommand cmd = new DeleteCommand (data .getTO ());
273- EndPoint ep = epSelector .select (data , StorageAction .DELETESNAPSHOT );
274- if (ep == null ) {
275- String errMsg = "No remote endpoint to send DeleteCommand for snapshot, check if host or ssvm is down?" ;
276- s_logger .error (errMsg );
277- commandResult .setResult (errMsg );
278- } else {
279- Answer answer = ep .sendMessage (cmd );
280- if (answer != null && !answer .getResult ()) {
281- commandResult .setResult (answer .getDetails ());
282- } else {
283- commandResult .setSuccess (true );
284- }
285- }
263+ // Delete the ONTAP FlexVolume snapshot that was created by takeSnapshot
264+ deleteOntapSnapshot ((SnapshotInfo ) data , commandResult );
286265 } else {
287266 throw new CloudRuntimeException ("deleteAsync: Unsupported data object type: " + data .getType ());
288267 }
@@ -295,6 +274,82 @@ public void deleteAsync(DataStore store, DataObject data, AsyncCompletionCallbac
295274 }
296275 }
297276
277+ /**
278+ * Deletes an ONTAP FlexVolume snapshot.
279+ *
280+ * <p>Retrieves the snapshot details stored during takeSnapshot and calls the ONTAP
281+ * REST API to delete the FlexVolume snapshot.</p>
282+ *
283+ * @param snapshotInfo The CloudStack snapshot to delete
284+ * @param commandResult Result object to populate with success/failure
285+ */
286+ private void deleteOntapSnapshot (SnapshotInfo snapshotInfo , CommandResult commandResult ) {
287+ long snapshotId = snapshotInfo .getId ();
288+ s_logger .info ("deleteOntapSnapshot: Deleting ONTAP FlexVolume snapshot for CloudStack snapshot [{}]" , snapshotId );
289+
290+ try {
291+ // Retrieve snapshot details stored during takeSnapshot
292+ String flexVolUuid = getSnapshotDetail (snapshotId , Constants .BASE_ONTAP_FV_ID );
293+ String ontapSnapshotUuid = getSnapshotDetail (snapshotId , Constants .ONTAP_SNAP_ID );
294+ String snapshotName = getSnapshotDetail (snapshotId , Constants .ONTAP_SNAP_NAME );
295+ String poolIdStr = getSnapshotDetail (snapshotId , Constants .PRIMARY_POOL_ID );
296+
297+ if (flexVolUuid == null || ontapSnapshotUuid == null ) {
298+ s_logger .warn ("deleteOntapSnapshot: Missing ONTAP snapshot details for snapshot [{}]. " +
299+ "flexVolUuid={}, ontapSnapshotUuid={}. Snapshot may have been created by a different method or already deleted." ,
300+ snapshotId , flexVolUuid , ontapSnapshotUuid );
301+ // Consider this a success since there's nothing to delete on ONTAP
302+ commandResult .setSuccess (true );
303+ commandResult .setResult (null );
304+ return ;
305+ }
306+
307+ long poolId = Long .parseLong (poolIdStr );
308+ Map <String , String > poolDetails = storagePoolDetailsDao .listDetailsKeyPairs (poolId );
309+
310+ StorageStrategy storageStrategy = Utility .getStrategyByStoragePoolDetails (poolDetails );
311+ SnapshotFeignClient snapshotClient = storageStrategy .getSnapshotFeignClient ();
312+ String authHeader = storageStrategy .getAuthHeader ();
313+
314+ s_logger .info ("deleteOntapSnapshot: Deleting ONTAP snapshot [{}] (uuid={}) from FlexVol [{}]" ,
315+ snapshotName , ontapSnapshotUuid , flexVolUuid );
316+
317+ // Call ONTAP REST API to delete the snapshot
318+ JobResponse jobResponse = snapshotClient .deleteSnapshot (authHeader , flexVolUuid , ontapSnapshotUuid );
319+
320+ if (jobResponse != null && jobResponse .getJob () != null ) {
321+ // Poll for job completion
322+ Boolean jobSucceeded = storageStrategy .jobPollForSuccess (jobResponse .getJob ().getUuid (), 30 , 2 );
323+ if (!jobSucceeded ) {
324+ throw new CloudRuntimeException ("deleteOntapSnapshot: Delete job failed for snapshot [" +
325+ snapshotName + "] on FlexVol [" + flexVolUuid + "]" );
326+ }
327+ }
328+
329+ s_logger .info ("deleteOntapSnapshot: Successfully deleted ONTAP snapshot [{}] (uuid={}) for CloudStack snapshot [{}]" ,
330+ snapshotName , ontapSnapshotUuid , snapshotId );
331+
332+ commandResult .setSuccess (true );
333+ commandResult .setResult (null );
334+
335+ } catch (Exception e ) {
336+ // Check if the error indicates snapshot doesn't exist (already deleted)
337+ String errorMsg = e .getMessage ();
338+ if (errorMsg != null && (errorMsg .contains ("404" ) || errorMsg .contains ("not found" ) ||
339+ errorMsg .contains ("does not exist" ))) {
340+ s_logger .warn ("deleteOntapSnapshot: ONTAP snapshot for CloudStack snapshot [{}] not found, " +
341+ "may have been already deleted. Treating as success." , snapshotId );
342+ commandResult .setSuccess (true );
343+ commandResult .setResult (null );
344+ } else {
345+ s_logger .error ("deleteOntapSnapshot: Failed to delete ONTAP snapshot for CloudStack snapshot [{}]: {}" ,
346+ snapshotId , e .getMessage (), e );
347+ commandResult .setSuccess (false );
348+ commandResult .setResult (e .getMessage ());
349+ }
350+ }
351+ }
352+
298353 @ Override
299354 public void copyAsync (DataObject srcData , DataObject destData , AsyncCompletionCallback <CopyCommandResult > callback ) {
300355 }
0 commit comments