diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapter.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapter.java index 9c0db25d52e..5851ee44d2e 100644 --- a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapter.java +++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapter.java @@ -87,8 +87,9 @@ public interface ProviderAdapter { /** * Copy a source object to a destination volume. The source object can be a Volume, Snapshot, or Template + * @param newSize the desired size in bytes for the destination volume (supports resize-during-copy) */ - public ProviderVolume copy(ProviderAdapterContext context, ProviderAdapterDataObject sourceVolume, ProviderAdapterDataObject targetVolume); + public ProviderVolume copy(ProviderAdapterContext context, ProviderAdapterDataObject sourceVolume, ProviderAdapterDataObject targetVolume, Long newSize); /** * Make a device-specific snapshot of the provided volume diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/driver/AdaptiveDataStoreDriverImpl.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/driver/AdaptiveDataStoreDriverImpl.java index e573f453a6c..40d99526394 100644 --- a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/driver/AdaptiveDataStoreDriverImpl.java +++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/driver/AdaptiveDataStoreDriverImpl.java @@ -337,7 +337,8 @@ public class AdaptiveDataStoreDriverImpl extends CloudStackPrimaryDataStoreDrive ProviderAdapterDataObject sourceIn = newManagedDataObject(srcdata, storagePool); ProviderAdapterDataObject destIn = newManagedDataObject(destdata, storagePool); - outVolume = api.copy(context, sourceIn, destIn); + // Call provider adapter copy method with destination size parameter for resize-during-copy support + outVolume = api.copy(context, sourceIn, destIn, destdata.getSize()); // populate this data - it may be needed later destIn.setExternalName(outVolume.getExternalName()); diff --git a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayAdapter.java b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayAdapter.java index 715379daf86..41125f3e113 100644 --- a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayAdapter.java +++ b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayAdapter.java @@ -367,7 +367,8 @@ public class FlashArrayAdapter implements ProviderAdapter { @Override public ProviderVolume copy(ProviderAdapterContext context, ProviderAdapterDataObject sourceDataObject, - ProviderAdapterDataObject destDataObject) { + ProviderAdapterDataObject destDataObject, Long newSize) { + // Add new parameter as newSize to match method declaration but not used anywhere // private ManagedVolume copy(ManagedVolume sourceVolume, String destNamespace, // String destName) { if (sourceDataObject == null || sourceDataObject.getExternalName() == null diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraAdapter.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraAdapter.java index 036144103b1..ee6b710efa6 100644 --- a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraAdapter.java +++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraAdapter.java @@ -288,15 +288,22 @@ public class PrimeraAdapter implements ProviderAdapter { @Override public ProviderVolume copy(ProviderAdapterContext context, ProviderAdapterDataObject sourceVolumeInfo, - ProviderAdapterDataObject targetVolumeInfo) { + ProviderAdapterDataObject targetVolumeInfo, Long newSize) { + // Log the start of the copy operation with source volume details + logger.debug("PrimeraAdapter: Starting volume copy operation - source volume: '{}', target volume: '{}', requested new size: {} bytes ({} MiB)", + sourceVolumeInfo.getExternalName(), targetVolumeInfo.getName(), newSize, newSize / PrimeraAdapter.BYTES_IN_MiB); + + // Flag to determine copy method: online copy (direct clone) vs offline copy (with resize) + boolean onlineCopy = true; PrimeraVolumeCopyRequest request = new PrimeraVolumeCopyRequest(); PrimeraVolumeCopyRequestParameters parms = new PrimeraVolumeCopyRequestParameters(); assert sourceVolumeInfo.getExternalName() != null: "External provider name not provided on copy request to Primera volume provider"; - // if we have no external name, treat it as a new volume + // Generate external name for target volume if not already set if (targetVolumeInfo.getExternalName() == null) { targetVolumeInfo.setExternalName(ProviderVolumeNamer.generateObjectName(context, targetVolumeInfo)); + logger.debug("PrimeraAdapter: Generated external name '{}' for target volume", targetVolumeInfo.getExternalName()); } ProviderVolume sourceVolume = this.getVolume(context, sourceVolumeInfo); @@ -304,23 +311,71 @@ public class PrimeraAdapter implements ProviderAdapter { throw new RuntimeException("Source volume " + sourceVolumeInfo.getExternalUuid() + " with provider name " + sourceVolumeInfo.getExternalName() + " not found on storage provider"); } + // Determine copy method based on size difference + // Online copy: Direct clone without size change (faster, immediate) + // Offline copy: Copy with potential resize (slower, requires task completion wait) + Long sourceSize = sourceVolume.getAllocatedSizeInBytes(); + if (newSize == null || sourceSize == null || !newSize.equals(sourceSize)) { + logger.debug("PrimeraAdapter: Volume size change detected (source: {} bytes, target: {} bytes) - using offline copy method", + sourceSize, newSize); + onlineCopy = false; + } else { + logger.debug("PrimeraAdapter: No size change required (both {} bytes) - using online copy method for faster cloning", newSize); + } + + // Check if target volume already exists on the storage provider ProviderVolume targetVolume = this.getVolume(context, targetVolumeInfo); if (targetVolume == null) { - this.create(context, targetVolumeInfo, null, sourceVolume.getAllocatedSizeInBytes()); + if (!onlineCopy) { + // For offline copy, pre-create the target volume with the desired size + logger.debug("PrimeraAdapter: Offline copy mode - pre-creating target volume '{}' with size {} bytes", + targetVolumeInfo.getName(), sourceVolume.getAllocatedSizeInBytes()); + this.create(context, targetVolumeInfo, null, sourceVolume.getAllocatedSizeInBytes()); + } else { + // For online copy, the target volume will be created automatically during the clone operation + logger.debug("PrimeraAdapter: Online copy mode - target volume '{}' will be created automatically during clone operation", + targetVolumeInfo.getName()); + } + } else { + logger.warn("PrimeraAdapter: Target volume '{}' already exists on storage provider - proceeding with copy operation", + targetVolumeInfo.getExternalName()); } parms.setDestVolume(targetVolumeInfo.getExternalName()); - parms.setOnline(false); - parms.setPriority(1); + if (onlineCopy) { + // Online copy configuration: immediate clone with deduplication and compression + parms.setOnline(true); + parms.setDestCPG(cpg); + parms.setTpvv(false); + parms.setReduce(true); + logger.debug("PrimeraAdapter: Configuring online copy - destination CPG: '{}', deduplication enabled, thin provisioning disabled", cpg); + } else { + // Offline copy configuration: background task with high priority + parms.setOnline(false); + parms.setPriority(1); // Set high priority for faster completion + logger.debug("PrimeraAdapter: Configuring offline copy with high priority for target volume '{}'", targetVolumeInfo.getName()); + } + + // Set request parameters and initiate the copy operation request.setParameters(parms); PrimeraTaskReference taskref = POST("/volumes/" + sourceVolumeInfo.getExternalName(), request, new TypeReference() {}); if (taskref == null) { + logger.error("PrimeraAdapter: Failed to initiate copy operation - no task reference returned from storage provider"); throw new RuntimeException("Unable to retrieve task used to copy to newly created volume"); } - waitForTaskToComplete(taskref.getTaskid(), "copy volume " + sourceVolumeInfo.getExternalName() + " to " + - targetVolumeInfo.getExternalName(), taskWaitTimeoutMs); + // Handle task completion based on copy method + if (!onlineCopy) { + // Offline copy requires waiting for task completion + logger.debug("PrimeraAdapter: Offline copy initiated - waiting for task completion (TaskID: {})", taskref.getTaskid()); + waitForTaskToComplete(taskref.getTaskid(), "copy volume " + sourceVolumeInfo.getExternalName() + " to " + + targetVolumeInfo.getExternalName(), taskWaitTimeoutMs); + logger.debug("PrimeraAdapter: Offline copy operation completed successfully"); + } else { + // Online copy completes immediately + logger.debug("PrimeraAdapter: Online copy operation completed successfully (TaskID: {})", taskref.getTaskid()); + } return this.getVolume(context, targetVolumeInfo); }