Copilot commented on code in PR #14243:
URL: https://github.com/apache/cloudstack/pull/14243#discussion_r4325771484


##########
plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java:
##########
@@ -495,6 +498,27 @@ public String getNetworkInterface() {
      */
     abstract public CloudStackVolume createCloudStackVolume(CloudStackVolume 
cloudstackVolume);
 
+    /**
+     * Creates the protocol-specific backend object that caches a template on 
this pool.
+     *
+     * <p>iSCSI creates an empty LUN ({@code 
/vol/&lt;flexVol&gt;/cs_tmpl_&lt;id&gt;}) sized to
+     * {@code sizeInBytes}. NFS is a no-op on the array: the KVM agent later 
writes the qcow2
+     * into the mounted FlexVolume.</p>
+     *
+     * <p>Returns a {@link CloudStackVolume} so the driver can map it to 
{@code CreateCmdResult}
+     * and update {@code template_spool_ref}. SAN populates {@code lun}; NAS 
returns an empty
+     * volume (no LUN / file yet).</p>
+     *
+     * @param storagePool   CloudStack primary storage pool (one FlexVolume)
+     * @param templateInfo  template being cached
+     * @param details       pool details (SVM, protocol, etc.)
+     * @param sizeInBytes   virtual size for the cache object (required for 
SAN; ignored for NAS)
+     * @return created cache identity, or an empty {@link CloudStackVolume} 
when nothing is
+     *         pre-created on the array
+     */
+    abstract public CloudStackVolume createTemplateCache(StoragePoolVO 
storagePool, TemplateInfo templateInfo,
+            Map<String, String> details, long sizeInBytes);

Review Comment:
   The Javadoc states “NAS returns an empty volume” / “return … an empty 
CloudStackVolume”, but `UnifiedNASStrategy.createTemplateCache(...)` currently 
returns `null`. Please align the contract and implementations (either document 
that `null` is returned for NAS/no-op, or change NAS to return an empty 
`CloudStackVolume`) so callers don’t have to guess.



##########
plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java:
##########
@@ -87,12 +91,97 @@ public CloudStackVolume 
createCloudStackVolume(CloudStackVolume cloudstackVolume
             logger.error("FeignException occurred while creating LUN: {}, 
Status: {}, Exception: {}",
                     cloudstackVolume.getLun().getName(), e.status(), 
e.getMessage());
             throw new CloudRuntimeException("Failed to create Lun: " + 
e.getMessage());
+        } catch (CloudRuntimeException e) {
+            throw e;
         } catch (Exception e) {
             logger.error("Exception occurred while creating LUN: {}, 
Exception: {}", cloudstackVolume.getLun().getName(), e.getMessage());
             throw new CloudRuntimeException("Failed to create Lun: " + 
e.getMessage());
         }
     }
 
+    /**
+     * Creates an empty LUN that caches a template on this pool's FlexVolume.
+     *
+     * <p>Sized to the template virtual disk size (what KVM writes after 
{@code qemu-img convert}
+     * to RAW). On create failure, best-effort deletes any leftover LUN before 
rethrowing.</p>
+     */
+    @Override
+    public CloudStackVolume createTemplateCache(StoragePoolVO storagePool, 
TemplateInfo templateInfo,
+                                                Map<String, String> details, 
long sizeInBytes) {
+        if (sizeInBytes <= 0) {
+            throw new CloudRuntimeException("Unknown virtual size for template 
[" + templateInfo.getId()
+                    + "]; cannot size the template LUN on pool [" + 
storagePool.getId() + "]");
+        }
+
+        CloudStackVolume request = buildTemplateLunRequest(storagePool, 
details, templateInfo.getId(), sizeInBytes);
+        String lunName = request.getLun().getName();
+        CloudStackVolume created = null;
+        try {
+            created = createCloudStackVolume(request);
+            Lun lun = created.getLun();
+            logger.info("createTemplateCache: Created template cache LUN [{}] 
(uuid [{}], {} bytes) on pool [{}] for template [{}]",
+                    lun.getName(), lun.getUuid(), sizeInBytes, 
storagePool.getId(), templateInfo.getId());
+            return created;
+        } catch (Exception e) {
+            
bestEffortDeleteTemplateCacheLun(details.get(OntapStorageConstants.SVM_NAME), 
lunName,
+                    created != null && created.getLun() != null ? 
created.getLun().getUuid() : null);
+            if (e instanceof CloudRuntimeException) {
+                throw (CloudRuntimeException) e;
+            }
+            throw new CloudRuntimeException("Failed to create template cache 
LUN for template [" + templateInfo.getId()
+                    + "]: " + e.getMessage(), e);
+        }
+    }
+
+    private CloudStackVolume buildTemplateLunRequest(StoragePoolVO 
storagePool, Map<String, String> details,
+                                                     long templateId, long 
sizeInBytes) {
+        Svm svm = new Svm();
+        svm.setName(details.get(OntapStorageConstants.SVM_NAME));
+
+        Lun lunRequest = new Lun();
+        lunRequest.setSvm(svm);
+        lunRequest.setName(OntapStorageUtils.getLunName(storagePool.getName(),
+                OntapStorageConstants.TEMPLATE_LUN_PREFIX + templateId));
+        lunRequest.setOsType(Lun.OsTypeEnum.valueOf(
+                
OntapStorageUtils.getOSTypeFromHypervisor(storagePool.getHypervisor().name())));
+        LunSpace lunSpace = new LunSpace();
+        lunSpace.setSize(sizeInBytes);
+        lunRequest.setSpace(lunSpace);
+
+        CloudStackVolume request = new CloudStackVolume();
+        request.setLun(lunRequest);
+        return request;
+    }
+
+    private void bestEffortDeleteTemplateCacheLun(String svmName, String 
lunName, String lunUuid) {
+        try {
+            String uuid = lunUuid;
+            if (uuid == null || uuid.isEmpty()) {
+                Map<String, String> lookup = Map.of(
+                        OntapStorageConstants.NAME, lunName,
+                        OntapStorageConstants.SVM_DOT_NAME, svmName);
+                CloudStackVolume existing = getCloudStackVolume(lookup);
+                if (existing == null || existing.getLun() == null || 
existing.getLun().getUuid() == null) {

Review Comment:
   `Map.of(...)` throws a `NullPointerException` if `svmName` or `lunName` is 
null, which can prevent the intended best-effort cleanup path from even 
attempting resolution/deletion. Consider guarding for blank/null 
`svmName`/`lunName` before building the map and/or using a mutable `HashMap` so 
the cleanup routine can fail gracefully while still logging a clear reason.



##########
plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java:
##########
@@ -112,9 +131,106 @@ public void deleteCloudStackVolume(CloudStackVolume 
cloudstackVolume) {
         }
     }
 
+    /**
+     * Clones a file inside the FlexVolume using ONTAP's file clone API.
+     *
+     * <p>The source is taken from {@code file.path} and the destination from
+     * {@code destinationPath}, both relative to the root of the FlexVolume 
backing the pool.</p>
+     */
     @Override
-    public void copyCloudStackVolume(CloudStackVolume cloudstackVolume) {
+    public CloudStackVolume cloneCloudStackVolume(CloudStackVolume 
cloudstackVolume) {
+        if (cloudstackVolume == null || cloudstackVolume.getFile() == null
+                || cloudstackVolume.getFile().getPath() == null || 
cloudstackVolume.getDestinationPath() == null) {
+            logger.error("cloneCloudStackVolume: File clone failed. Invalid 
request: {}", cloudstackVolume);
+            throw new CloudRuntimeException("Failed to clone file, invalid 
request");
+        }
+        if (cloudstackVolume.getDatastoreId() == null) {
+            throw new CloudRuntimeException("Failed to clone file, no 
datastore id in the request");
+        }
+        if (cloudstackVolume.getVolumeInfo() == null) {
+            throw new CloudRuntimeException("Failed to clone file, volume info 
is missing from the request");
+        }
 
+        Map<String, String> details = 
storagePoolDetailsDao.listDetailsKeyPairs(Long.parseLong(cloudstackVolume.getDatastoreId()));
+        String flexVolUuid = details.get(OntapStorageConstants.VOLUME_UUID);
+        String flexVolName = details.get(OntapStorageConstants.VOLUME_NAME);
+        if (flexVolUuid == null || flexVolUuid.isEmpty()) {
+            throw new CloudRuntimeException("Failed to clone file, FlexVolume 
uuid is missing from pool details");

Review Comment:
   `Long.parseLong(cloudstackVolume.getDatastoreId())` can throw 
`NumberFormatException`, which currently bypasses the method’s error 
handling/logging and will surface as an unwrapped runtime exception. Suggest 
validating `datastoreId` (and throwing a `CloudRuntimeException` with datastore 
id value included) or moving the parse inside the try/catch so failures are 
reported consistently.



##########
plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java:
##########
@@ -203,8 +217,224 @@ public void createAsync(DataStore dataStore, DataObject 
dataObject, AsyncComplet
      */
     private CloudStackVolume createCloudStackVolume(StoragePoolVO storagePool, 
VolumeInfo volumeObject, Map<String, String> details) {
         StorageStrategy storageStrategy = 
OntapStorageUtils.getStrategyByStoragePoolDetails(details);
-        CloudStackVolume cloudStackVolumeRequest = 
OntapStorageUtils.createCloudStackVolumeRequestByProtocol(storagePool, details, 
volumeObject);
-        return storageStrategy.createCloudStackVolume(cloudStackVolumeRequest);
+        return 
storageStrategy.createCloudStackVolume(createVolumeRequest(storagePool, 
details, volumeObject));
+    }
+
+    /**
+     * Creates the backend object that caches a template on this pool's 
FlexVolume.
+     *
+     * <p>Protocol-specific work is delegated to {@link 
StorageStrategy#createTemplateCache}.
+     * This method maps the result to {@link CreateCmdResult} and records SAN 
identity on
+     * {@code template_spool_ref} ({@code local_download_path} = LUN uuid).</p>
+     */
+    private CreateCmdResult createTemplateOnPrimary(StoragePoolVO storagePool, 
TemplateInfo templateInfo, Map<String, String> details) {
+        StorageStrategy storageStrategy = 
OntapStorageUtils.getStrategyByStoragePoolDetails(details);
+        long sizeInBytes = 
getDataObjectSizeIncludingHypervisorSnapshotReserve(templateInfo, storagePool);
+        CloudStackVolume created = null;
+        try {
+            created = storageStrategy.createTemplateCache(storagePool, 
templateInfo, details, sizeInBytes);
+            String path = recordTemplateCacheOnSpoolRef(storagePool, 
templateInfo, created, sizeInBytes);
+            return new CreateCmdResult(path, new Answer(null, true, null));
+        } catch (Exception e) {
+            // Compensating delete for SAN: strategy cleans create-time LUN 
failures; this covers
+            // post-create failures (e.g. template_spool_ref update) after a 
LUN was returned.
+            if (created != null && created.getLun() != null) {
+                bestEffortDeleteTemplateCacheLun(storageStrategy, 
details.get(OntapStorageConstants.SVM_NAME),
+                        created.getLun().getName(), 
created.getLun().getUuid());
+            }
+            if (e instanceof CloudRuntimeException) {
+                throw (CloudRuntimeException) e;
+            }
+            throw new CloudRuntimeException("Failed to create template cache 
for template [" + templateInfo.getId()
+                    + "]: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Persists SAN cache identity on {@code template_spool_ref} when a LUN 
was created.
+     * NFS returns the template uuid as the create path; {@code install_path} 
is filled later.
+     */
+    private String recordTemplateCacheOnSpoolRef(StoragePoolVO storagePool, 
TemplateInfo templateInfo,
+                                                 CloudStackVolume created, 
long sizeInBytes) {
+        if (created == null || created.getLun() == null) {
+            return templateInfo.getUuid();
+        }
+        Lun lun = created.getLun();
+        VMTemplateStoragePoolVO templatePoolRef = 
findTemplatePoolRef(storagePool.getId(), templateInfo.getId());
+        templatePoolRef.setLocalDownloadPath(lun.getUuid());
+        templatePoolRef.setTemplateSize(sizeInBytes);
+        vmTemplatePoolDao.update(templatePoolRef.getId(), templatePoolRef);
+        return lun.getName();
+    }
+
+    /**
+     * Clones the cached template into a new volume on the same FlexVolume.
+     *
+     * <p>Invoked when {@code StorageSystemDataMotionStrategy} has recorded a
+     * {@code cloneOfTemplate} detail on the volume.</p>
+     */
+    private CloudStackVolume cloneCloudStackVolumeFromTemplate(StoragePoolVO 
storagePool, VolumeInfo volumeInfo,
+                                                               Map<String, 
String> details, long templateId) {
+        VMTemplateStoragePoolVO templatePoolRef = 
findTemplatePoolRef(storagePool.getId(), templateId);
+        StorageStrategy storageStrategy = 
OntapStorageUtils.getStrategyByStoragePoolDetails(details);
+        boolean iscsi = isIscsi(details);
+
+        long templateSize = templatePoolRef.getTemplateSize();
+        if (templateSize <= 0) {
+            throw new CloudRuntimeException("Template cache size is missing or 
invalid (" + templateSize
+                    + ") for template [" + templateId + "] on pool [" + 
storagePool.getId()
+                    + "]; cannot clone volume [" + volumeInfo.getId() + "]");
+        }
+
+        CloudStackVolume request = iscsi
+                ? createCloneLunRequest(storagePool, details, volumeInfo, 
templatePoolRef, templateId)
+                : createCloneFileRequest(storagePool, volumeInfo, 
templatePoolRef, templateId);
+
+        CloudStackVolume cloned = 
storageStrategy.cloneCloudStackVolume(request);
+        // SAN cloneCloudStackVolume validates the Feign response (LUN name + 
uuid) before returning
+        if (cloned == null) {
+            throw new CloudRuntimeException("ONTAP returned nothing when 
cloning template [" + templateId
+                    + "] for volume [" + volumeInfo.getId() + "]");
+        }
+
+        logger.info("cloneCloudStackVolumeFromTemplate: Cloned template [{}] 
for volume [{}] on pool [{}]",
+                templateId, volumeInfo.getId(), storagePool.getId());
+
+        long requestedSize = 
getDataObjectSizeIncludingHypervisorSnapshotReserve(volumeInfo, storagePool);
+        if (requestedSize > templateSize) {
+            logger.info("cloneCloudStackVolumeFromTemplate: Growing clone of 
template [{}] from {} to {} bytes for volume [{}]",
+                    templateId, templateSize, requestedSize, 
volumeInfo.getId());
+            storageStrategy.resizeCloudStackVolume(cloned, requestedSize);
+        }
+
+        return cloned;
+    }
+
+    /**
+     * Returns the CloudStack template id the volume should be cloned from, or 
null for a blank volume.
+     *
+     * <p>{@code StorageSystemDataMotionStrategy} persists this detail 
immediately before calling
+     * {@code createAsync} and removes it right after, so it is only visible 
during creation.</p>
+     */
+    private Long getTemplateIdForCloning(long volumeId) {
+        VolumeDetailVO detail = volumeDetailsDao.findDetail(volumeId, 
OntapStorageConstants.CLONE_OF_TEMPLATE);
+        if (detail == null || detail.getValue() == null || 
detail.getValue().isEmpty()) {
+            return null;
+        }
+        return Long.valueOf(detail.getValue());
+    }

Review Comment:
   `Long.valueOf(detail.getValue())` may throw `NumberFormatException` if the 
persisted value is malformed, causing volume creation to fail with a low-signal 
exception. Wrapping this in a `CloudRuntimeException` that includes `volumeId` 
and the invalid stored value would make failures diagnosable and consistent 
with other driver error handling.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to