|
22 | 22 | import com.cloud.agent.api.to.DataObjectType; |
23 | 23 | import com.cloud.agent.api.to.DataStoreTO; |
24 | 24 | import com.cloud.agent.api.to.DataTO; |
| 25 | +import com.cloud.agent.api.to.DiskTO; |
25 | 26 | import com.cloud.exception.InvalidParameterValueException; |
26 | 27 | import com.cloud.host.Host; |
27 | 28 | import com.cloud.storage.Storage; |
@@ -186,7 +187,12 @@ public void createAsync(DataStore dataStore, DataObject dataObject, AsyncComplet |
186 | 187 | volumeDao.update(volumeVO.getId(), volumeVO); |
187 | 188 | } |
188 | 189 | } else if (dataObject.getType() == DataObjectType.SNAPSHOT) { |
189 | | - createTempVolume((SnapshotInfo)dataObject, dataStore.getId()); |
| 190 | + //createTempVolume((SnapshotInfo)dataObject, dataStore.getId()); |
| 191 | + // No-op: ONTAP's takeSnapshot() already creates a LUN clone that is directly accessible. |
| 192 | + // The framework calls createAsync(SNAPSHOT) via createVolumeFromSnapshot/deleteVolumeFromSnapshot, |
| 193 | + // but ONTAP doesn't need a separate temp volume — the cloned LUN is used as-is. |
| 194 | + s_logger.info("createAsync: SNAPSHOT type — no-op for ONTAP (LUN clone already exists from takeSnapshot)"); |
| 195 | + createCmdResult = new CreateCmdResult(null, new Answer(null, true, null)); |
190 | 196 | } else { |
191 | 197 | errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to createAsync"; |
192 | 198 | s_logger.error(errMsg); |
@@ -268,6 +274,11 @@ public void deleteAsync(DataStore store, DataObject data, AsyncCompletionCallbac |
268 | 274 | s_logger.error("deleteAsync : Volume deleted: " + volumeInfo.getId()); |
269 | 275 | commandResult.setResult(null); |
270 | 276 | commandResult.setSuccess(true); |
| 277 | + } else if (data.getType() == DataObjectType.SNAPSHOT) { |
| 278 | + s_logger.info("deleteAsync: SNAPSHOT type — no-op for ONTAP (LUN clone already exists from takeSnapshot)"); |
| 279 | + //deleteSnapshotClone((SnapshotInfo) data, store); |
| 280 | + commandResult.setResult(null); |
| 281 | + commandResult.setSuccess(true); |
271 | 282 | } |
272 | 283 | } catch (Exception e) { |
273 | 284 | s_logger.error("deleteAsync: Failed for data object [{}]: {}", data, e.getMessage()); |
@@ -373,6 +384,9 @@ public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore |
373 | 384 | volumeVO.setPoolType(storagePool.getPoolType()); |
374 | 385 | volumeVO.setPoolId(storagePool.getId()); |
375 | 386 | volumeDao.update(volumeVO.getId(), volumeVO); |
| 387 | + } else if (dataObject.getType() == DataObjectType.SNAPSHOT) { |
| 388 | + s_logger.info("grantAccess: SNAPSHOT type — no-op for ONTAP (LUN clone already exists from takeSnapshot)"); |
| 389 | + //grantAccessForSnapshot((SnapshotInfo) dataObject, host, storagePool); |
376 | 390 | } else { |
377 | 391 | s_logger.error("Invalid DataObjectType (" + dataObject.getType() + ") passed to grantAccess"); |
378 | 392 | throw new CloudRuntimeException("Invalid DataObjectType (" + dataObject.getType() + ") passed to grantAccess"); |
@@ -434,6 +448,9 @@ public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore) |
434 | 448 | throw new CloudRuntimeException("revokeAccess: CloudStack Volume not found for id: " + dataObject.getId()); |
435 | 449 | } |
436 | 450 | revokeAccessForVolume(storagePool, volumeVO, host); |
| 451 | + } else if (dataObject.getType() == DataObjectType.SNAPSHOT) { |
| 452 | + s_logger.info("revokeAccess: SNAPSHOT type — no-op for ONTAP (LUN clone already exists from takeSnapshot)"); |
| 453 | + //revokeAccessForSnapshot((SnapshotInfo) dataObject, host, storagePool); |
437 | 454 | } else { |
438 | 455 | s_logger.error("revokeAccess: Invalid DataObjectType (" + dataObject.getType() + ") passed to revokeAccess"); |
439 | 456 | throw new CloudRuntimeException("Invalid DataObjectType (" + dataObject.getType() + ") passed to revokeAccess"); |
@@ -482,6 +499,134 @@ private void revokeAccessForVolume(StoragePoolVO storagePool, VolumeVO volumeVO, |
482 | 499 | } |
483 | 500 | } |
484 | 501 |
|
| 502 | + /** |
| 503 | + * Grants host access to a snapshot's cloned LUN for copy-to-secondary-storage. |
| 504 | + * Maps the snapshot LUN to the host's igroup and stores the IQN in snapshot_details |
| 505 | + * so that the KVM agent can connect via iSCSI to copy the data. |
| 506 | + */ |
| 507 | + private void grantAccessForSnapshot(SnapshotInfo snapshotInfo, Host host, StoragePoolVO storagePool) { |
| 508 | + s_logger.info("grantAccessForSnapshot: Granting access to snapshot [{}] for host [{}]", snapshotInfo.getId(), host.getName()); |
| 509 | + |
| 510 | + Map<String, String> details = storagePoolDetailsDao.listDetailsKeyPairs(storagePool.getId()); |
| 511 | + |
| 512 | + if (ProtocolType.ISCSI.name().equalsIgnoreCase(details.get(Constants.PROTOCOL))) { |
| 513 | + String svmName = details.get(Constants.SVM_NAME); |
| 514 | + String storagePoolUuid = storagePool.getUuid(); |
| 515 | + |
| 516 | + // snapshotInfo.getPath() contains the cloned LUN name set during takeSnapshot() |
| 517 | + String snapshotLunName = snapshotInfo.getPath(); |
| 518 | + if (snapshotLunName == null) { |
| 519 | + throw new CloudRuntimeException("grantAccessForSnapshot: Snapshot path (LUN name) is null for snapshot id: " + snapshotInfo.getId()); |
| 520 | + } |
| 521 | + |
| 522 | + UnifiedSANStrategy sanStrategy = (UnifiedSANStrategy) Utility.getStrategyByStoragePoolDetails(details); |
| 523 | + String accessGroupName = Utility.getIgroupName(svmName, storagePoolUuid); |
| 524 | + |
| 525 | + // Map the snapshot's cloned LUN to the igroup |
| 526 | + String lunNumber = sanStrategy.ensureLunMapped(svmName, snapshotLunName, accessGroupName); |
| 527 | + |
| 528 | + // Store DiskTO.IQN in snapshot_details so StorageSystemDataMotionStrategy.getSnapshotDetails() |
| 529 | + // can build the iSCSI source details for the CopyCommand sent to the KVM agent |
| 530 | + String iqnPath = storagePool.getPath() + Constants.SLASH + lunNumber; |
| 531 | + snapshotDetailsDao.addDetail(snapshotInfo.getId(), DiskTO.IQN, iqnPath, false); |
| 532 | + |
| 533 | + s_logger.info("grantAccessForSnapshot: Snapshot LUN [{}] mapped as LUN number [{}], IQN path [{}]", |
| 534 | + snapshotLunName, lunNumber, iqnPath); |
| 535 | + } |
| 536 | + } |
| 537 | + |
| 538 | + /** |
| 539 | + * Revokes host access to a snapshot's cloned LUN after copy-to-secondary-storage completes. |
| 540 | + * Unmaps the snapshot LUN from the igroup and removes the IQN from snapshot_details. |
| 541 | + */ |
| 542 | + private void revokeAccessForSnapshot(SnapshotInfo snapshotInfo, Host host, StoragePoolVO storagePool) { |
| 543 | + s_logger.info("revokeAccessForSnapshot: Revoking access to snapshot [{}] for host [{}]", snapshotInfo.getId(), host.getName()); |
| 544 | + |
| 545 | + Map<String, String> details = storagePoolDetailsDao.listDetailsKeyPairs(storagePool.getId()); |
| 546 | + |
| 547 | + if (ProtocolType.ISCSI.name().equalsIgnoreCase(details.get(Constants.PROTOCOL))) { |
| 548 | + StorageStrategy storageStrategy = Utility.getStrategyByStoragePoolDetails(details); |
| 549 | + String storagePoolUuid = storagePool.getUuid(); |
| 550 | + |
| 551 | + // Build a CloudStackVolume request using the snapshot's cloned LUN name |
| 552 | + String snapshotLunName = snapshotInfo.getPath(); |
| 553 | + if (snapshotLunName == null) { |
| 554 | + s_logger.warn("revokeAccessForSnapshot: Snapshot path is null for snapshot id: {}, skipping", snapshotInfo.getId()); |
| 555 | + return; |
| 556 | + } |
| 557 | + |
| 558 | + CloudStackVolume snapshotVolumeRequest = new CloudStackVolume(); |
| 559 | + Lun snapshotLun = new Lun(); |
| 560 | + snapshotLun.setName(snapshotLunName); |
| 561 | + Svm svm = new Svm(); |
| 562 | + svm.setName(details.get(Constants.SVM_NAME)); |
| 563 | + snapshotLun.setSvm(svm); |
| 564 | + snapshotVolumeRequest.setLun(snapshotLun); |
| 565 | + |
| 566 | + // Retrieve the LUN from ONTAP to get its UUID |
| 567 | + CloudStackVolume cloudStackVolume = storageStrategy.getCloudStackVolume(snapshotVolumeRequest); |
| 568 | + |
| 569 | + // Get the igroup to get its UUID |
| 570 | + AccessGroup accessGroupRequest = getAccessGroupRequestByProtocol(details, storagePoolUuid); |
| 571 | + AccessGroup accessGroup = storageStrategy.getAccessGroup(accessGroupRequest); |
| 572 | + |
| 573 | + // Remove the LUN mapping from the igroup |
| 574 | + Map<String, String> disableLogicalAccessMap = new HashMap<>(); |
| 575 | + disableLogicalAccessMap.put(Constants.LUN_DOT_UUID, cloudStackVolume.getLun().getUuid()); |
| 576 | + disableLogicalAccessMap.put(Constants.IGROUP_DOT_UUID, accessGroup.getIgroup().getUuid()); |
| 577 | + storageStrategy.disableLogicalAccess(disableLogicalAccessMap); |
| 578 | + |
| 579 | + // Remove the IQN detail from snapshot_details |
| 580 | + snapshotDetailsDao.removeDetail(snapshotInfo.getId(), DiskTO.IQN); |
| 581 | + |
| 582 | + s_logger.info("revokeAccessForSnapshot: Successfully revoked access to snapshot LUN [{}] for host [{}]", |
| 583 | + snapshotLunName, host.getName()); |
| 584 | + } |
| 585 | + } |
| 586 | + |
| 587 | + /** |
| 588 | + * Deletes the cloned LUN that was created during takeSnapshot(). |
| 589 | + * Called by the framework after the snapshot data has been copied to secondary storage. |
| 590 | + */ |
| 591 | + private void deleteSnapshotClone(SnapshotInfo snapshotInfo, DataStore store) { |
| 592 | + s_logger.info("deleteSnapshotClone: Deleting snapshot clone for snapshot [{}]", snapshotInfo.getId()); |
| 593 | + |
| 594 | + StoragePoolVO storagePool = storagePoolDao.findById(store.getId()); |
| 595 | + if (storagePool == null) { |
| 596 | + throw new CloudRuntimeException("deleteSnapshotClone: Storage Pool not found for id: " + store.getId()); |
| 597 | + } |
| 598 | + |
| 599 | + Map<String, String> details = storagePoolDetailsDao.listDetailsKeyPairs(store.getId()); |
| 600 | + StorageStrategy storageStrategy = Utility.getStrategyByStoragePoolDetails(details); |
| 601 | + |
| 602 | + // Get the cloned LUN name from snapshotInfo.getPath() (set during takeSnapshot) |
| 603 | + String snapshotLunName = snapshotInfo.getPath(); |
| 604 | + if (snapshotLunName == null) { |
| 605 | + s_logger.warn("deleteSnapshotClone: Snapshot path is null for snapshot id: {}, nothing to delete", snapshotInfo.getId()); |
| 606 | + return; |
| 607 | + } |
| 608 | + |
| 609 | + // Get the cloned LUN UUID from snapshot_details |
| 610 | + SnapshotDetailsVO snapDetail = snapshotDetailsDao.findDetail(snapshotInfo.getSnapshotId(), Constants.ONTAP_SNAP_ID); |
| 611 | + String lunUuid = (snapDetail != null) ? snapDetail.getValue() : null; |
| 612 | + |
| 613 | + // Build delete request for the cloned LUN |
| 614 | + CloudStackVolume deleteRequest = new CloudStackVolume(); |
| 615 | + Lun lun = new Lun(); |
| 616 | + lun.setName(snapshotLunName); |
| 617 | + if (lunUuid != null) { |
| 618 | + lun.setUuid(lunUuid); |
| 619 | + } |
| 620 | + deleteRequest.setLun(lun); |
| 621 | + |
| 622 | + storageStrategy.deleteCloudStackVolume(deleteRequest); |
| 623 | + s_logger.info("deleteSnapshotClone: Successfully deleted cloned LUN [{}] for snapshot [{}]", snapshotLunName, snapshotInfo.getId()); |
| 624 | + |
| 625 | + // Clean up snapshot details |
| 626 | + snapshotDetailsDao.removeDetail(snapshotInfo.getSnapshotId(), Constants.ONTAP_SNAP_ID); |
| 627 | + snapshotDetailsDao.removeDetail(snapshotInfo.getSnapshotId(), Constants.ONTAP_SNAP_SIZE); |
| 628 | + } |
| 629 | + |
485 | 630 | @Override |
486 | 631 | public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool storagePool) { |
487 | 632 | return 0; |
|
0 commit comments