@@ -171,7 +171,7 @@ public void createAsync(DataStore dataStore, DataObject dataObject, AsyncComplet
171171 // Create the backend storage object: a clone of the cached template when the
172172 // orchestrator asked for one, otherwise a blank LUN (iSCSI) or qcow2 file (NFS).
173173 Long cloneOfTemplateId = getTemplateIdForCloning (volInfo .getId ());
174- CloudStackVolume created = cloneOfTemplateId != null
174+ CloudStackVolume clonedCloudStackVolume = cloneOfTemplateId != null
175175 ? cloneCloudStackVolumeFromTemplate (storagePool , volInfo , details , cloneOfTemplateId )
176176 : createCloudStackVolume (storagePool , volInfo , details );
177177
@@ -181,17 +181,14 @@ public void createAsync(DataStore dataStore, DataObject dataObject, AsyncComplet
181181 logger .info ("createAsync: Volume format set to [{}] for hypervisor [{}] and protocol [{}]" , volumeVO .getFormat (), storagePool .getHypervisor (), details .get (OntapStorageConstants .PROTOCOL ));
182182
183183 if (ProtocolType .ISCSI .name ().equalsIgnoreCase (details .get (OntapStorageConstants .PROTOCOL ))) {
184- String lunName = created != null && created .getLun () != null ? created .getLun ().getName () : null ;
185- if (lunName == null ) {
186- throw new CloudRuntimeException ("Missing LUN name for volume " + volInfo .getId ());
187- }
184+ // createCloudStackVolume validates the Feign response (LUN name + uuid) before returning
185+ Lun createdLun = clonedCloudStackVolume .getLun ();
186+ String lunName = createdLun .getName ();
188187
189188 // Persist LUN details for future operations (delete, grant/revoke access)
190- volumeDetailsDao .addDetail (volInfo .getId (), OntapStorageConstants .LUN_DOT_UUID , created . getLun () .getUuid (), false );
189+ volumeDetailsDao .addDetail (volInfo .getId (), OntapStorageConstants .LUN_DOT_UUID , createdLun .getUuid (), false );
191190 volumeDetailsDao .addDetail (volInfo .getId (), OntapStorageConstants .LUN_DOT_NAME , lunName , false );
192- if (created .getLun ().getUuid () != null ) {
193- volumeVO .setFolder (created .getLun ().getUuid ());
194- }
191+ volumeVO .setFolder (createdLun .getUuid ());
195192
196193 logger .info ("createAsync: Created LUN [{}] for volume [{}]. LUN mapping will occur during grantAccess() to per-host igroup." ,
197194 lunName , volumeVO .getId ());
@@ -264,22 +261,31 @@ private CreateCmdResult createTemplateOnPrimary(StoragePoolVO storagePool, Templ
264261 }
265262
266263 StorageStrategy storageStrategy = OntapStorageUtils .getStrategyByStoragePoolDetails (details );
267- CloudStackVolume created = storageStrategy .createCloudStackVolume (
268- createTemplateLunRequest (storagePool , details , templateInfo .getId (), sizeInBytes ));
269-
270- if (created == null || created .getLun () == null || created .getLun ().getName () == null ) {
271- throw new CloudRuntimeException ("ONTAP returned no LUN for the cache of template [" + templateInfo .getId () + "]" );
264+ CloudStackVolume createdCloudStackVolume = null ;
265+ try {
266+ createdCloudStackVolume = storageStrategy .createCloudStackVolume (
267+ createTemplateLunRequest (storagePool , details , templateInfo .getId (), sizeInBytes ));
268+ // createCloudStackVolume validates the Feign response (LUN name + uuid) before returning
269+ Lun lun = createdCloudStackVolume .getLun ();
270+ templatePoolRef .setLocalDownloadPath (lun .getUuid ());
271+ templatePoolRef .setTemplateSize (sizeInBytes );
272+ vmTemplatePoolDao .update (templatePoolRef .getId (), templatePoolRef );
273+
274+ logger .info ("createTemplateOnPrimary: Created template cache LUN [{}] (uuid [{}], {} bytes) on pool [{}] for template [{}]" ,
275+ lun .getName (), lun .getUuid (), sizeInBytes , storagePool .getId (), templateInfo .getId ());
276+
277+ return new CreateCmdResult (lun .getName (), new Answer (null , true , null ));
278+ } catch (Exception e ) {
279+ // Compensating delete: orchestrator only cleans DB metadata on create failure.
280+ bestEffortDeleteTemplateCacheLun (storageStrategy , details .get (OntapStorageConstants .SVM_NAME ),
281+ getTemplateLunName (storagePool , templateInfo .getId ()),
282+ createdCloudStackVolume != null && createdCloudStackVolume .getLun () != null ? createdCloudStackVolume .getLun ().getUuid () : null );
283+ if (e instanceof CloudRuntimeException ) {
284+ throw (CloudRuntimeException ) e ;
285+ }
286+ throw new CloudRuntimeException ("Failed to create template cache LUN for template [" + templateInfo .getId ()
287+ + "]: " + e .getMessage (), e );
272288 }
273-
274- Lun lun = created .getLun ();
275- templatePoolRef .setLocalDownloadPath (lun .getUuid ());
276- templatePoolRef .setTemplateSize (sizeInBytes );
277- vmTemplatePoolDao .update (templatePoolRef .getId (), templatePoolRef );
278-
279- logger .info ("createTemplateOnPrimary: Created template cache LUN [{}] (uuid [{}], {} bytes) on pool [{}] for template [{}]" ,
280- lun .getName (), lun .getUuid (), sizeInBytes , storagePool .getId (), templateInfo .getId ());
281-
282- return new CreateCmdResult (lun .getName (), new Answer (null , true , null ));
283289 }
284290
285291 /**
@@ -299,7 +305,8 @@ private CloudStackVolume cloneCloudStackVolumeFromTemplate(StoragePoolVO storage
299305 : createCloneFileRequest (storagePool , volumeInfo , templatePoolRef , templateId );
300306
301307 CloudStackVolume cloned = storageStrategy .cloneCloudStackVolume (request );
302- if (cloned == null || (iscsi && (cloned .getLun () == null || cloned .getLun ().getName () == null ))) {
308+ // SAN cloneCloudStackVolume validates the Feign response (LUN name + uuid) before returning
309+ if (cloned == null ) {
303310 throw new CloudRuntimeException ("ONTAP returned nothing when cloning template [" + templateId
304311 + "] for volume [" + volumeInfo .getId () + "]" );
305312 }
@@ -366,31 +373,60 @@ private void deleteTemplateOnPrimary(DataStore store, TemplateInfo templateInfo)
366373
367374 StorageStrategy storageStrategy = OntapStorageUtils .getStrategyByStoragePoolDetails (details );
368375 if (isIscsi (details )) {
369- deleteIscsiTemplateCache (storagePool , templateInfo , templatePoolRef , storageStrategy );
376+ deleteIscsiTemplateCache (storagePool , templateInfo , templatePoolRef , storageStrategy , details );
370377 } else {
371378 deleteNfsTemplateCache (details , templateInfo , templatePoolRef , storageStrategy );
372379 }
373380 }
374381
375382 private void deleteIscsiTemplateCache (StoragePoolVO storagePool , TemplateInfo templateInfo ,
376- VMTemplateStoragePoolVO templatePoolRef , StorageStrategy storageStrategy ) {
383+ VMTemplateStoragePoolVO templatePoolRef , StorageStrategy storageStrategy ,
384+ Map <String , String > details ) {
385+ String lunName = getTemplateLunName (storagePool , templateInfo .getId ());
377386 String lunUuid = templatePoolRef .getLocalDownloadPath ();
378- if (lunUuid == null || lunUuid .isEmpty ()) {
379- logger .warn ("deleteTemplateOnPrimary: No cached LUN recorded for template [{}] on pool [{}]; nothing to delete" ,
380- templateInfo .getId (), storagePool .getId ());
381- return ;
387+ deleteTemplateCacheLun (storageStrategy , details .get (OntapStorageConstants .SVM_NAME ), lunName , lunUuid );
388+ logger .info ("deleteTemplateOnPrimary: Deleted template cache LUN [{}] for template [{}] on pool [{}]" ,
389+ lunName , templateInfo .getId (), storagePool .getId ());
390+ }
391+
392+ /**
393+ * Deletes a template-cache LUN. When {@code lunUuid} is missing, resolves the LUN by its
394+ * deterministic name so eviction and create-time rollback still work.
395+ */
396+ private void deleteTemplateCacheLun (StorageStrategy storageStrategy , String svmName , String lunName , String lunUuid ) {
397+ String uuid = lunUuid ;
398+ if (uuid == null || uuid .isEmpty ()) {
399+ CloudStackVolume existing = getCloudStackVolumeByName (storageStrategy , svmName , lunName );
400+ if (existing == null || existing .getLun () == null || existing .getLun ().getUuid () == null ) {
401+ logger .warn ("deleteTemplateCacheLun: LUN [{}] not found on SVM [{}]; nothing to delete" , lunName , svmName );
402+ return ;
403+ }
404+ uuid = existing .getLun ().getUuid ();
382405 }
383406
384407 Lun lun = new Lun ();
385- lun .setUuid (lunUuid );
386- lun .setName (getTemplateLunName ( storagePool , templateInfo . getId ()) );
408+ lun .setUuid (uuid );
409+ lun .setName (lunName );
387410
388411 CloudStackVolume deleteRequest = new CloudStackVolume ();
389412 deleteRequest .setLun (lun );
390413 storageStrategy .deleteCloudStackVolume (deleteRequest );
414+ }
391415
392- logger .info ("deleteTemplateOnPrimary: Deleted template cache LUN [{}] for template [{}] on pool [{}]" ,
393- lun .getName (), templateInfo .getId (), storagePool .getId ());
416+ /**
417+ * Best-effort cleanup of a template-cache LUN after a failed create. Never masks the original
418+ * create failure.
419+ */
420+ private void bestEffortDeleteTemplateCacheLun (StorageStrategy storageStrategy , String svmName , String lunName ,
421+ String lunUuid ) {
422+ try {
423+ deleteTemplateCacheLun (storageStrategy , svmName , lunName , lunUuid );
424+ logger .info ("bestEffortDeleteTemplateCacheLun: Removed leftover template cache LUN [{}] on SVM [{}]" ,
425+ lunName , svmName );
426+ } catch (Exception cleanupEx ) {
427+ logger .warn ("bestEffortDeleteTemplateCacheLun: Failed to remove leftover template cache LUN [{}] on SVM [{}]: {}" ,
428+ lunName , svmName , cleanupEx .getMessage ());
429+ }
394430 }
395431
396432 private void deleteNfsTemplateCache (Map <String , String > details , TemplateInfo templateInfo ,
@@ -683,15 +719,30 @@ private void grantAccessTemplate(TemplateInfo templateInfo, Host host, DataStore
683719
684720 ensureAccessGroupForHost (sanStrategy , host , storagePool , svmName , accessGroupName );
685721
686- String lunNumber = sanStrategy .ensureLunMapped (svmName , lunName , accessGroupName );
687- String iscsiPath = buildIscsiPath (storagePool , lunNumber );
688-
689- templatePoolRef .setInstallPath (iscsiPath );
690- vmTemplatePoolDao .update (templatePoolRef .getId (), templatePoolRef );
691- refreshManagedStoreTarget (dataStore , iscsiPath );
692-
693- logger .info ("grantAccessTemplate: Mapped template cache LUN [{}] to igroup [{}] as [{}] for template [{}]" ,
694- lunName , accessGroupName , iscsiPath , templateInfo .getId ());
722+ boolean lunMapped = false ;
723+ try {
724+ String lunNumber = sanStrategy .ensureLunMapped (svmName , lunName , accessGroupName );
725+ lunMapped = true ;
726+ String iscsiPath = buildIscsiPath (storagePool , lunNumber );
727+
728+ templatePoolRef .setInstallPath (iscsiPath );
729+ vmTemplatePoolDao .update (templatePoolRef .getId (), templatePoolRef );
730+ refreshManagedStoreTarget (dataStore , iscsiPath );
731+
732+ logger .info ("grantAccessTemplate: Mapped template cache LUN [{}] to igroup [{}] as [{}] for template [{}]" ,
733+ lunName , accessGroupName , iscsiPath , templateInfo .getId ());
734+ } catch (RuntimeException e ) {
735+ // Undo only this call's side effect (map). Do not delete the cache LUN — create already succeeded.
736+ if (lunMapped ) {
737+ try {
738+ unmapLunFromHost (sanStrategy , svmName , lunName , accessGroupName , host );
739+ } catch (Exception rollbackEx ) {
740+ logger .warn ("grantAccessTemplate: Failed to unmap template cache LUN [{}] from igroup [{}] after grant failure: {}" ,
741+ lunName , accessGroupName , rollbackEx .getMessage ());
742+ }
743+ }
744+ throw e ;
745+ }
695746 }
696747
697748 /**
0 commit comments