3939import org .apache .cloudstack .engine .subsystem .api .storage .DataObject ;
4040import org .apache .cloudstack .engine .subsystem .api .storage .DataStore ;
4141import org .apache .cloudstack .engine .subsystem .api .storage .DataStoreCapabilities ;
42- import org .apache .cloudstack .engine .subsystem .api .storage .PrimaryDataStore ;
4342import org .apache .cloudstack .engine .subsystem .api .storage .PrimaryDataStoreDriver ;
4443import org .apache .cloudstack .engine .subsystem .api .storage .SnapshotInfo ;
4544import org .apache .cloudstack .engine .subsystem .api .storage .TemplateInfo ;
4948import org .apache .cloudstack .storage .datastore .db .PrimaryDataStoreDao ;
5049import org .apache .cloudstack .storage .datastore .db .StoragePoolDetailsDao ;
5150import org .apache .cloudstack .storage .datastore .db .StoragePoolVO ;
52- import org .apache .cloudstack .storage .to .PrimaryDataStoreTO ;
5351import org .apache .cloudstack .storage .feign .model .OntapStorage ;
5452import org .apache .cloudstack .storage .provider .StorageProviderFactory ;
5553import org .apache .cloudstack .storage .service .StorageStrategy ;
@@ -90,37 +88,7 @@ public DataTO getTO(DataObject data) {
9088 }
9189
9290 @ Override
93- public DataStoreTO getStoreTO (DataStore store ) {
94- // Load storage pool details from database (includes "mountpoint" added during pool creation)
95- Map <String , String > poolDetails = storagePoolDetailsDao .listDetailsKeyPairs (store .getId ());
96-
97- // Set details on the store before creating PrimaryDataStoreTO
98- // This ensures PrimaryDataStoreTO constructor gets the details from database
99- PrimaryDataStore primaryStore = (PrimaryDataStore ) store ;
100- if (poolDetails != null && !poolDetails .isEmpty ()) {
101- // Merge existing details (if any) with database details
102- Map <String , String > existingDetails = primaryStore .getDetails ();
103- if (existingDetails == null ) {
104- primaryStore .setDetails (poolDetails );
105- } else {
106- // Merge: database details take precedence
107- Map <String , String > mergedDetails = new HashMap <>(existingDetails );
108- mergedDetails .putAll (poolDetails );
109- primaryStore .setDetails (mergedDetails );
110- }
111- }
112-
113- // Now create PrimaryDataStoreTO - it will get details from primaryStore.getDetails()
114- PrimaryDataStoreTO storeTO = new PrimaryDataStoreTO (primaryStore );
115-
116- s_logger .info ("OntapPrimaryDatastoreDriver: getStoreTO: Created PrimaryDataStoreTO for pool: " + store .getName ());
117- s_logger .info (" Pool UUID: " + store .getUuid ());
118- s_logger .info (" Host Address: " + primaryStore .getHostAddress ());
119- s_logger .info (" Path: " + primaryStore .getPath ());
120- s_logger .info (" Port: " + primaryStore .getPort ());
121- s_logger .info (" Final details in storeTO: " + storeTO .getDetails ());
122- return storeTO ;
123- }
91+ public DataStoreTO getStoreTO (DataStore store ) { return null ; }
12492
12593 @ Override
12694 public void createAsync (DataStore dataStore , DataObject dataObject , AsyncCompletionCallback <CreateCmdResult > callback ) {
@@ -142,12 +110,6 @@ public void createAsync(DataStore dataStore, DataObject dataObject, AsyncComplet
142110 if (dataObject .getType () == DataObjectType .VOLUME ) {
143111 path = createCloudStackVolumeForTypeVolume (dataStore , dataObject );
144112 createCmdResult = new CreateCmdResult (path , new Answer (null , true , null ));
145- } else if (dataObject .getType () == DataObjectType .TEMPLATE ) {
146- // For templates, return the UUID as the install path
147- // This will be used as the filename for the qcow2 file on NFS
148- path = dataObject .getUuid ();
149- s_logger .info ("createAsync: Template [{}] will use UUID as install path: {}" , ((TemplateInfo )dataObject ).getName (), path );
150- createCmdResult = new CreateCmdResult (path , new Answer (null , true , null ));
151113 } else {
152114 errMsg = "Invalid DataObjectType (" + dataObject .getType () + ") passed to createAsync" ;
153115 s_logger .error (errMsg );
@@ -263,20 +225,23 @@ private String createCloudStackVolumeForTypeVolume(DataStore dataStore, DataObje
263225 /**
264226 * Creates Managed NFS Volume with ONTAP backing storage.
265227 *
266- * Architecture: 1 CloudStack Storage Pool = 1 ONTAP Volume (shared by all volumes)
228+ * Architecture: 1 CloudStack Volume = 1 ONTAP Volume = 1 NFS Export
267229 *
268230 * Flow:
269- * 1. createAsync() stores volume metadata and NFS mount point
270- * 2. Volume attach triggers ManagedNfsStorageAdaptor.connectPhysicalDisk()
271- * 3. KVM mounts: nfs://nfsServer/junctionPath to /mnt/volumeUuid
272- * 4. Libvirt creates qcow2 file via storageVolCreateXML()
273- * 5. File created at: /vol/ontap_volume/volumeUuid (on ONTAP)
231+ * 1. Create ONTAP FlexVolume via REST API with junction path /cloudstack_vol_<volumeUuid>
232+ * 2. ONTAP automatically creates NFS export for the junction path
233+ * 3. Store volume metadata in CloudStack DB
234+ * 4. Volume attach triggers OntapNfsStorageAdaptor.connectPhysicalDisk()
235+ * 5. KVM mounts: nfs://nfsServer/cloudstack_vol_<volumeUuid> to /mnt/volumeUuid
236+ * 6. qemu-img creates qcow2 file in the mounted directory
237+ * 7. File created at: /vol/cloudstack_vol_<volumeUuid>/<volumeUuid>.qcow2 (on ONTAP)
274238 *
275239 * Key Details:
276- * - All volumes in same pool share the same ONTAP volume NFS export
277- * - Each volume gets separate libvirt mount point: /mnt/<volumeUuid>
278- * - All qcow2 files stored in same ONTAP volume: /vol/<pool_volume_name>/
279- * - volume._iScsiName stores the NFS junction path (pool.path)
240+ * - Each CloudStack volume gets its own dedicated ONTAP FlexVolume
241+ * - Each ONTAP volume has unique junction path: /cloudstack_vol_<volumeUuid>
242+ * - Each volume mounted at: /mnt/<volumeUuid>
243+ * - qcow2 file stored at root of ONTAP volume
244+ * - volume._iScsiName stores the NFS junction path for OntapNfsStorageAdaptor
280245 *
281246 * @param dataStore CloudStack data store (storage pool)
282247 * @param dataObject Volume data object
@@ -288,31 +253,52 @@ private String createManagedNfsVolume(DataStore dataStore, DataObject dataObject
288253 VolumeVO volume = volumeDao .findById (volumeInfo .getId ());
289254 String volumeUuid = volumeInfo .getUuid ();
290255
291- // Get the NFS junction path from storage pool
292- // This is the path that was set during pool creation (e.g., "/my_pool_volume")
293- String junctionPath = storagePool .getPath ();
256+ // Step 1: Create ONTAP FlexVolume via REST API
257+ Map <String , String > details = storagePoolDetailsDao .listDetailsKeyPairs (dataStore .getId ());
258+ StorageStrategy storageStrategy = getStrategyByStoragePoolDetails (details );
259+
260+ String ontapVolumeName = "cloudstack_vol_" + volumeUuid ;
261+ String junctionPath = "/" + ontapVolumeName ;
262+ long sizeInBytes = volumeInfo .getSize ();
263+
264+ s_logger .info ("Creating ONTAP FlexVolume: name={}, junctionPath={}, size={}GB for CloudStack volume {}" ,
265+ ontapVolumeName , junctionPath , sizeInBytes / (1024 * 1024 * 1024 ), volumeUuid );
294266
295- // Update volume metadata in CloudStack database
296- // Use OntapNFS pool type - matches pool type set during pool creation
267+ try {
268+ // Create ONTAP volume with NFS export
269+ org .apache .cloudstack .storage .feign .model .Volume ontapVolume =
270+ storageStrategy .createStorageVolume (ontapVolumeName , sizeInBytes );
271+
272+ if (ontapVolume == null || ontapVolume .getUuid () == null ) {
273+ String errMsg = "Failed to create ONTAP volume: " + ontapVolumeName ;
274+ s_logger .error (errMsg );
275+ throw new CloudRuntimeException (errMsg );
276+ }
277+
278+ s_logger .info ("ONTAP FlexVolume created successfully: name={}, uuid={}, junctionPath={}" ,
279+ ontapVolumeName , ontapVolume .getUuid (), junctionPath );
280+
281+ } catch (Exception e ) {
282+ String errMsg = "Exception creating ONTAP volume " + ontapVolumeName + ": " + e .getMessage ();
283+ s_logger .error (errMsg , e );
284+ throw new CloudRuntimeException (errMsg , e );
285+ }
286+
287+ // Step 2: Update CloudStack volume metadata
297288 volume .setPoolType (Storage .StoragePoolType .OntapNFS );
298289 volume .setPoolId (dataStore .getId ());
299290 volume .setPath (volumeUuid ); // Filename for qcow2 file
300291
301- // For OntapNFS, _iScsiName stores the per-volume junction path
302- // OntapNfsStorageAdaptor will use this to mount the specific ONTAP volume
303- // Format: /cloudstack_vol_<volumeUuid>
304- volume .set_iScsiName ("/cloudstack_vol_" + volumeUuid );
292+ // Store junction path in _iScsiName field
293+ // OntapNfsStorageAdaptor will use this to mount: nfs://<server>/cloudstack_vol_<volumeUuid>
294+ volume .set_iScsiName (junctionPath );
305295
306296 volumeDao .update (volume .getId (), volume );
307297
308- s_logger .info ("ONTAP Managed NFS Volume Created: uuid={}, path={}, junctionPath={}, format=QCOW2, " +
309- "pool={}, size={}GB. Libvirt will create qcow2 file at mount time." ,
310- volumeUuid , volumeUuid , junctionPath , storagePool .getName (),
298+ s_logger .info ("CloudStack volume metadata updated: uuid={}, path={}, junctionPath={}, poolType={}, size={}GB" ,
299+ volumeUuid , volumeUuid , junctionPath , "OntapNFS" ,
311300 volumeInfo .getSize () / (1024 * 1024 * 1024 ));
312301
313- // Optional: Prepare ONTAP volume for optimal qcow2 storage (future enhancement)
314- // prepareOntapVolumeForQcow2Storage(dataStore, volumeInfo);
315-
316302 return volumeUuid ;
317303 }
318304
@@ -379,15 +365,31 @@ public void deleteAsync(DataStore store, DataObject data, AsyncCompletionCallbac
379365 throw new CloudRuntimeException ("deleteAsync: store or data is null" );
380366 }
381367 if (data .getType () == DataObjectType .VOLUME ) {
382- StoragePoolVO storagePool = storagePoolDao .findById (store .getId ());
383368 Map <String , String > details = storagePoolDetailsDao .listDetailsKeyPairs (store .getId ());
384369 if (ProtocolType .NFS .name ().equalsIgnoreCase (details .get (Constants .PROTOCOL ))) {
385- // ManagedNFS qcow2 backing file deletion handled by KVM host/libvirt; nothing to do via ONTAP REST.
386- s_logger .info ("deleteAsync: ManagedNFS volume {} no-op ONTAP deletion" , data .getId ());
370+ VolumeInfo volumeInfo = (VolumeInfo ) data ;
371+ String volumeUuid = volumeInfo .getUuid ();
372+ String ontapVolumeName = "cloudstack_vol_" + volumeUuid ;
373+
374+ s_logger .info ("deleteAsync: Deleting ONTAP FlexVolume {} for CloudStack volume {}" ,
375+ ontapVolumeName , volumeUuid );
376+
377+ // TODO: Implement ONTAP volume deletion via StorageStrategy.deleteStorageVolume()
378+ // For now, ONTAP volumes will remain after CloudStack volume deletion
379+ // This allows manual cleanup and prevents accidental data loss during development
380+ // Future implementation:
381+ // StorageStrategy storageStrategy = getStrategyByStoragePoolDetails(details);
382+ // Volume ontapVolume = new Volume();
383+ // ontapVolume.setName(ontapVolumeName);
384+ // storageStrategy.deleteStorageVolume(ontapVolume);
385+
386+ s_logger .warn ("deleteAsync: ONTAP volume deletion not yet implemented. " +
387+ "Manual cleanup required for ONTAP volume: {}" , ontapVolumeName );
387388 }
388389 }
389390 } catch (Exception e ) {
390391 commandResult .setResult (e .getMessage ());
392+ s_logger .error ("deleteAsync: Exception deleting volume" , e );
391393 } finally {
392394 callback .complete (commandResult );
393395 }
0 commit comments